今天我们就来系统的说说实模式和保护模式。我觉得可以很形象地说明保护模式存在的意义。看看下面的代码。intmain(){int*地址=(int*)0;cli();//关闭中断while(1){*addr=0;地址++;}return0;}这段代码要是能顺利执行的话,其实是很恐怖的。你会发现他直接获取了内存的第0位,可以依次往下遍历,然后直接往内存中写入数据。顺便说一句,这段代码也关闭了中断。想象一下,如果你使用一个有多个用户的服务器,一旦一个用户执行了这段代码,所有还在内存中的数据都会被删除的一干??二净,删除的过程中数据也会被删除。没有办法中断这段代码对应的进程。此代码的工作方式与实模式相同。实模式的特点主要在于实模式的寻址方式,而实模式的查找方式进一步决定了实模式在寻址范围和保护方面不如保护模式。目前实模式的存在主要是为了兼容以前的系统。操作系统读取bootsect.s并执行bootsect.s进行setup和系统读取时,是在实模式下执行,然后在保护模式下执行。下面我们分别看看实模式和保护模式是如何工作的。Part1实模式今天就详细讲一下加载过程。正好赶上我们了解实模式的工作原理。首先说一下实模式应该如何指示指令在内存中的位置。在实模式下,“CS”和“IP”这两个寄存器用来表示位置。CS寄存器存储基索引,IP寄存器存储偏移量。这里简单介绍一下为什么要用segmentrepresentation。懂汇编的同学可能知道,汇编代码会将代码分成很多段,包括代码段、数据段等等。这样程序在加载到内存的时候,也是分成不同的段加载到内存中。这样,在取指令时,需要找到段的基地址,然后确定偏移量,从而得到指令的地址。具体方法是取CS寄存器中的值左移4位,然后加上IP偏移量的值。例如获取BIOS的地址,从CS寄存器获取的段基地址为0xFFFF,IP获取的偏移量为0x0000。CS偏移4位加IP为0xFFFF0(地址用16进制表示,但是左移4位就是二进制偏移,16进制偏移1位和二进制偏移4位效果是一样的)。这样就找到了BIOS所在的位置,然后取指令执行。CS寄存器中的值取出后偏移了4位,因为CS寄存器和IP寄存器都是16位的,16位只能表示64k的大小。CS左移4位加上偏移量,可以用20位表示地址,寻址范围可达1M。虽然通过将从CS寄存器中取出的段的基地址向左移动四位来增加寻址范围到1M,但是寻址范围对于现代计算机来说太小了(即使对于32位的计算机CS寄存器也是如此)16位),这是实模式下的一个主要缺点。让我们来看看保护模式是如何工作的。Part2保护模式是针对32位电脑的,如果内存寻址只能找到1M,显然太小了。理论上,适配32位计算机的地址线也有32位,我们应该用32位的地址来表示地址。但是为了适应以前的系统(现在的操作系统是加载BIOS,使用BIOS加载bootsect.s等一直使用上面介绍的实模式寻址方式),CS段寄存器一直是16bits,只是存储偏移量的寄存器有一个32位的寄存器,叫做EIP。为了使寻址适应这种变化,CS寄存器中不再存放真正的段基地址,而是段选择符,其中段选择符的第0-2位用于权限控制,第3-15位记录GDT表的索引。得到段选择符后,就可以通过选择符找到段描述符,再通过段描述符(GDT表)找到对应的段基地址,再将段基地址和偏移量相加得到真正的地址。这个GDT表非常有用,不仅可以解决寻址问题,还可以保护内存。这个段描述符中的段长和段基地址是断开的。这是由于历史原因。我们还是主要看段描述符中的内容和对应的函数。段长可以限制段内的偏移地址,也就是说你得到段基地址后,只能在这个限制内偏移。如果偏移量超过限制值,访问将是非法的。段基地址与实模式下的段基地址相同,只是现在多了一个32位的段基地址。在描述方面,主要是描述可读和可执行段,代码段或数据段。另外,描述中很重要的一个部分就是DPL。该DPL限制了权限级别,只有段选择器中记录的发起访问者才能访问,只有在权限满足DPL要求的情况下才能访问。不难看出,GDT表对对应段的可读可写性以及该段是代码段还是数据段都做了详细的描述。每次通过段选择器找到段描述符,都会判断访问是否成功。下面通过流程图来看一下保护模式完整的取指令过程。
