在我们阅读boot loader代码时,遇到了两个非常重要的概念,实模式(real mode)和保护模式(protected mode)。

  首先我们要知道这两种模式都是CPU的工作模式,实模式是早期CPU运行的工作模式,而保护模式则是现代CPU运行的模式。

  但是为什么现代CPU在运行boot loader时仍旧要先进入实模式呢?就是为了实现软件的向后兼容性不得已才这样的。

  下面我们分别看下这两种工作模式的基本原理。

实模式(real mode)

  实模式出现于早期8088CPU时期。当时由于CPU的性能有限,一共只有20位地址线(所以地址空间只有1MB),以及8个16位的通用寄存器,以及4个16位的段寄存器。所以为了能够通过这些16位的寄存器去构成20位的主存地址,必须采取一种特殊的方式。当某个指令想要访问某个内存地址时,它通常需要用下面的这种格式来表示:

  (段基址:段偏移量)

  其中第一个字段是段基址,它的值是由段寄存器提供的。段寄存器有4种,%cs,%ds,%ss,%es。具体这个指令采用哪个段寄存器是由这个指令的类型来决定的。比如要取指令就是采用%cs寄存器,要读取或写入数据就是%ds寄存器,如果要对堆栈操作就是%ss寄存器。总之,不管什么指令,都会有一个段寄存器提供一个16位的段基址。

  第二字段是段内偏移量,代表你要访问的这个内存地址距离这个段基址的偏移。它的值就是由通用寄存器来提供的,所以也是16位。那么问题来了,两个16位的值如何组合成一个20位的地址呢?这里采用的方式是把段寄存器所提供的段基址先向左移4位。这样就变成了一个20位的值,然后再与段偏移量相加。所以算法如下:

  物理地址 = 段基址<<4 + 段内偏移

  所以假设 %cs中的值是0xff00,%ax = 0x0110。则(%cs:%ax)这个地址对应的真实物理地址是 0xff00<<4 + 0x0110 = 0xff110。

  上面就是实模式访问内存地址的原理。

保护模式(protected mode)

  但是随着CPU的发展,CPU的地址线的个数也从原来的20根变为现在的32根,所以可以访问的内存空间也从1MB变为现在4GB,寄存器的位数也变为32位。所以实模式下的内存地址计算方式就已经不再适合了。所以就引入了现在的保护模式,实现更大空间的,更灵活的内存访问。

  在介绍保护模式的工作原理之前,我们必须先清楚以下几个容易混淆的概念。逻辑地址(logical address),虚拟地址(virtual address),线性地址(linear address),物理地址(physical address)。

  我们都知道,如今在编写程序时,程序时运行在虚拟地址空间下的,也就是说,在程序员编写程序时指令中出现的地址并不一定时这个程序在内存中运行时真正要访问的内存地址。这样做的目的就是为了能够让程序员在编程时不需要直接操作真实地址,因为当它在真实运行时,内存中各个程序的分布情况是不可能在你编写程序时就知道的。所以这个程序的这条指令到底要访问哪个内存单元是由操作系统来确定的。所以这就是一个从虚拟地址(virtual address)到真实主存中的物理地址(physical address)的转换。

  那么逻辑地址(logical address)又是什么呢?根据上面一段文字我们知道,程序员编写时看到的是虚拟地址,但是并不是说程序员是直接把这个虚拟地址写到指令中的。它是由逻辑地址推导得到的。所以指令中真实出现的是逻辑地址。一个逻辑地址是由两部分组成的,一个段选择子(segment selector),一个段内偏移量(offset),通常被写作segment:offset。而且采用哪个段选择子通常也是在指令中隐含的,程序员通常只需要指明段内偏移量。然后分段管理机构(segmentation hardware)将会把这个逻辑地址转换为线性地址(linear address)。如果该机器没有采用分页机制(paging hardware)的话,此时linear address就是最后的主存物理地址。但是如果机器中还有分页设备的话,比如内存大小实际只有1G,但是根据前面我们知道可访问的空间有4G。所以此时还需要分页机构(paging hardware)把这个线性地址转换为最终的真实物理地址。所以可见虚拟地址和线性地址的含义是差不多的。我们可以再下图中看到我们上面叙述的地址转换过程。在boot loader中,并没有开启分页机构。所以计算出来的线性地址就是真实要访问的主存地址。

  

  那么在保护模式下,我们是如何通过segment:offset最终得到物理地址的呢?

  首先,在计算机中存在两个表,GDT,LDT。它们两个其实是同类型的表,前者叫做全局段描述符表,后者叫做本地段描述符表。他们都是用来存放关于某个运行在内存中的程序的分段信息的。比如某个程序的代码段是从哪里开始,有多大;数据段又是从哪里开始,有多大。GDT表是全局可见的,也就是说每一个运行在内存中的程序都能看到这个表。所以操作系统内核程序的段信息就存在这里面。还有一个LDT表,这个表是每一个在内存中的程序都包含的,里面指明了每一个程序的段信息。我们可以看一下这两个表的结构,如下图所示:

  

  我们从图中可以看到,无论是GDT,还是LDT。每一个表项都包括三个字段:

  Base : 32位,代表这个程序的这个段的基地址。

  Limit : 20位,代表这个程序的这个段的大小。

  Flags :12位,代表这个程序的这个段的访问权限。

  当程序中给出逻辑地址 segment:offset时,他并不是像实模式那样,用segment的值作为段基址。而是把这个segment的值作为一个selector,代表这个段的段表项在GDT/LDT表的索引。比如你当前要访问的地址是segment:offset = 0x01:0x0000ffff,此时由于每个段表项的长度为8,所以此时应该取出地址8处的段表项。然后首先根据Flags字段来判断是否可以访问这个段的内容,这样做是为了能够实现进程间地址的保护。如果能访问,则把Base字段的内容取出,直接与offset相加,就得到线性地址(linear address)了。之后就是要根据是否有分页机构来进行地址转换了。

  比如当前Base字段的值是0x00f0000,则最后线性地址的值为0x00f0ffff。

  如上所述就是保护模式下,内存地址的计算方法。

  

  综述,通过上面的叙述可见,保护模式还是要比实模式的工作方式灵活许多,可以在以下几个方面看出来:

  1. 实模式下段基地址必须是16的整数倍,保护模式下段基地址可以是4GB空间内的任意一个地址。

  2. 实模式下段的长度是65536B,但是保护模式下段的长度也是可以达到4GB的。

  3. 保护模式下可以对内存的访问多加一层保护,但是实模式没有。

  

  有什么问题,大家可以给我发邮件~

    zzqwf12345@163.com

MIT 6.828 JOS学习笔记6. Appendix 1: 实模式(real mode)与保护模式(protected mode)的更多相关文章

  1. MIT 6.828 JOS学习笔记2. Lab 1 Part 1.2: PC bootstrap

    Lab 1 Part 1: PC bootstrap 我们继续~ PC机的物理地址空间 这一节我们将深入的探究到底PC是如何启动的.首先我们看一下通常一个PC的物理地址空间是如何布局的:        ...

  2. MIT 6.828 JOS学习笔记4. Lab 1 Part 2.1: The Boot Loader

    Part 2: The Boot Loader 对于PC来说,软盘,硬盘都可以被划分为一个个大小为512字节的区域,叫做扇区.一个扇区是一次磁盘操作的最小粒度.每一次读取或者写入操作都必须是一个或多个 ...

  3. MIT 6.828 JOS学习笔记0. 写在前面的话

    0. 简介 操作系统是计算机科学中十分重要的一门基础学科,是一名计算机专业毕业生必须要具备的基础知识.但是在学习这门课时,如果仅仅把目光停留在课本上一些关于操作系统概念上的叙述,并不能对操作系统有着深 ...

  4. MIT 6.828 JOS学习笔记15. Lab 2.1

    Lab 2: Memory Management lab2中多出来的几个文件: inc/memlayout.h kern/pmap.c kern/pmap.h kern/kclock.h kern/k ...

  5. MIT 6.828 JOS学习笔记5. Exercise 1.3

    Lab 1 Exercise 3 设置一个断点在地址0x7c00处,这是boot sector被加载的位置.然后让程序继续运行直到这个断点.跟踪/boot/boot.S文件的每一条指令,同时使用boo ...

  6. MIT 6.828 JOS学习笔记7. Lab 1 Part 2.2: The Boot Loader

    Lab 1 Part 2 The Boot Loader Loading the Kernel 我们现在可以进一步的讨论一下boot loader中的C语言的部分,即boot/main.c.但是在我们 ...

  7. MIT 6.828 JOS学习笔记1. Lab 1 Part 1: PC Bootstrap

    Lab 1: Booting a PC Part 1: PC Bootstrap 介绍这一部分知识的目的就是让你能够更加熟悉x86汇编语言,以及PC启动的整个过程,而且也会首次学习使用QEMU软件来仿 ...

  8. MIT 6.828 JOS学习笔记18. Lab 3.2 Part B: Page Faults, Breakpoints Exceptions, and System Calls

    现在你的操作系统内核已经具备一定的异常处理能力了,在这部分实验中,我们将会进一步完善它,使它能够处理不同类型的中断/异常. Handling Page Fault 缺页中断是一个非常重要的中断,因为我 ...

  9. MIT 6.828 JOS学习笔记17. Lab 3.1 Part A User Environments

    Introduction 在这个实验中,我们将实现操作系统的一些基本功能,来实现用户环境下的进程的正常运行.你将会加强JOS内核的功能,为它增添一些重要的数据结构,用来记录用户进程环境的一些信息:创建 ...

随机推荐

  1. Java基础-重写System.out.println方法

    PrintStream myStream = new PrintStream(System.out) { @Override public void println(String x) { super ...

  2. python下ssh的简单实现

    python下的ssh都需要借助第三方模块paramiko来实现,在使用前需要手动安装. 一.python实现ssh (1) linux下的ssh登录 root@ubuntu:~# ssh morra ...

  3. CSS-border属性制作小三角

    1--三角向上下左上.右上.右下.左下这四个方向突出的样式 向左上角突出: border-color: transparent transparent transparent #FFCC00; bor ...

  4. 面试题目——《CC150》数学与概率

    面试题7.2:三角形的三个顶点上各有一只蚂蚁.如果蚂蚁开始沿着三角形的边爬行,两只或三只蚂蚁撞到一起的概率有多大?假定每只蚂蚁会随机选一个方向,每个方向被选到的几率相等,而且三只蚂蚁的爬行速度相同. ...

  5. Top 5 iPad Pro Apps for Your Apple Pencil

    1. Procreate - 5 to 10 dollars 2. Adobe Sketch - Free 3. Paper - Free 4. Pixelmator 5. Notes

  6. PHP系统声明式事务处理

    转自:http://www.jianshu.com/p/34261804bc45 1.数据库事务 事务(Transaction)是并发控制的基本单位.所谓的事务,它是一个操作序列,这些操作要么都执行, ...

  7. 表设置了自增后往里面插入不自增的id时的处理方法

    SET IDENTITY_INSERT 表名 ON 中间写insert语句,但是这里必须把列名更上 SET IDENTITY_INSERT 表名 OFF

  8. Uva 2034

    求定积分 (结果当时我没看到平均值) //正常多项式求 #include<iostream> #include<cstdio> #include<cmath> us ...

  9. PYTHON 全局变量和局部变量

    #局部变量,只能调用函数体内的变量 def fun(): a = 234 print(a) #全局变量,在函数体外声明,在函数体内都可调用 b = 'gyc' def fun(): a = 234 p ...

  10. django orm字段和参数

    字段 1.models.AutoField 自增列 = int(11) 如果没有的话,默认会生成一个名称为 id 的列,如果要显示的自定义一个自增列,必须将给列设置为主键 primary_key=Tr ...