MIT 6.828 JOS学习笔记6. Appendix 1: 实模式(real mode)与保护模式(protected mode)

  在我们阅读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. 保护模式下可以对内存的访问多加一层保护,但是实模式没有。

  

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

    [email protected]

时间: 2024-10-14 06:25:26

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

MIT 6.828 JOS学习笔记2. Lab 1 Part 1.2: The kernel

Lab 1 Part 1: PC bootstrap 我们继续~ PC机的物理地址空间 这一节我们将深入的探究到底PC是如何启动的.首先我们看一下通常一个PC的物理地址空间是如何布局的:                           这张图仅仅展示了内存空间的一部分. 第一代PC处理器是16位字长的Intel 8088处理器,这类处理器只能访问1MB的地址空间,即0x00000000~0x000FFFFF.但是这1MB也不是用户都能利用到的,只有低640KB(0x00000000~0x00

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

Part 2: The Boot Loader 对于PC来说,软盘,硬盘都可以被划分为一个个大小为512字节的区域,叫做扇区.一个扇区是一次磁盘操作的最小粒度.每一次读取或者写入操作都必须是一个或多个扇区.如果一个磁盘是可以被用来启动操作系统的,就把这个磁盘的第一个扇区叫做启动扇区.这一部分介绍的boot loader程序就位于这个启动扇区之中.当BIOS找到一个可以启动的软盘或硬盘后,它就会把这512字节的启动扇区加载到内存地址0x7c00~0x7dff这个区域内. 对于6.828,我们将采用

MIT 6.828 JOS学习笔记5. Exercise 1.3

Exercise 1.3 设置一个断点在地址0x7c00处,这是boot sector被加载的位置.然后让程序继续运行直到这个断点.跟踪/boot/boot.S文件的每一条指令,同时使用boot.S文件和系统为你反汇编出来的文件obj/boot/boot.asm.你也可以使用GDB的x/i指令来获取去任意一个机器指令的反汇编指令,把源文件boot.S文件和boot.asm文件以及在GDB反汇编出来的指令进行比较. 追踪到bootmain函数中,而且还要具体追踪到readsect()子函数里面.找

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.但是在我们分析之前,我们应该先回顾一些关于C语言的基础知识. Exercise 4: 阅读关于C语言的指针部分的知识.最好的参考书自然是"The C Programming Language". 阅读5.1到5.5节.然后下载pointers.c的代码,并且编译运行它,确保你理解在屏幕上打印出来的所

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

Introduction 在这个实验中,我们将实现操作系统的一些基本功能,来实现用户环境下的进程的正常运行.你将会加强JOS内核的功能,为它增添一些重要的数据结构,用来记录用户进程环境的一些信息:创建一个单一的用户环境,并且加载一个程序运行它.你也可以让JOS内核能够完成用户环境所作出的任何系统调用,以及处理用户环境产生的各种异常. Part A: User Environments and Exception Handling 新包含的文件inc/env.h里面包含了JOS内核的有关用户环境(

MIT 6.828 JOS学习笔记16. Lab 2.2

Part 3 Kernel Address Space JOS把32位线性地址虚拟空间划分成两个部分.其中用户环境(进程运行环境)通常占据低地址的那部分,叫用户地址空间.而操作系统内核总是占据高地址的部分,叫内核地址空间.这两个部分的分界线是定义在memlayout.h文件中的一个宏 ULIM.JOS为内核保留了接近256MB的虚拟地址空间.这就可以理解了,为什么在实验1中要给操作系统设计一个高地址的地址空间.如果不这样做,用户环境的地址空间就不够了. Permission and Fault

MIT 6.828 JOS学习笔记10. Lab 1 Part 3: The kernel

Lab 1 Part 3: The kernel 现在我们将开始具体讨论一下JOS内核了.就像boot loader一样,内核开始的时候也是一些汇编语句,用于设置一些东西,来保证C语言的程序能够正确的执行. 使用虚拟内存 在运行boot loader时,boot loader中的链接地址(虚拟地址)和加载地址(物理地址)是一样的.但是当进入到内核程序后,这两种地址就不再相同了. 操作系统内核程序在虚拟地址空间通常会被链接到一个非常高的虚拟地址空间处,比如0xf0100000,目的就是能够让处理器

MIT 6.828 JOS学习笔记9. Exercise 1.5

Lab 1 Exercise 5 再一次追踪一下boot loader的一开始的几句指令,找到第一条满足如下条件的指令处: 当我修改了boot loader的链接地址,这个指令就会出现错误. 找到这样的指令后,把boot loader的链接地址修改一下,我们要在boot/Makefrag文件中修改它的链接地址,修改完成后运行  make clean, 然后通过make指令重新编译内核,再找到那条指令看看会发生什么. 最后别忘了改回来. 答: 这道题希望我们能够去修改boot loader的链接地

MIT 6.828 JOS学习笔记8. Exercise 1.4

Lab 1 Exercise 4 阅读关于C语言的指针部分的知识.最好的参考书自然是"The C Programming Language". 阅读5.1到5.5节.然后下载pointers.c的代码,并且编译运行它,确保你理解在屏幕上打印出来的所有的值是怎么来的.尤其要重点理解第1行,第6行的指针地址是如何得到的,以及在第2行到第4行的值是如何得到的,还有为什么在第5行打印出来的值看起来像程序崩溃了. 答: 首先编译运行文件pointer.c,得到如下结果: 首先程序声明了3个重要的