迷彩 潜伏 隐蔽 伪装
分类: LINUX
2014-07-16 11:26:43
原文地址:linux内核分析(四)——保护模式 作者:lvyilong316
保护模式(Protected Mode),是从80386开始(准确的说是80286,但286只能寻址24位地址空间,386才开启CPU的32位时代),Intel x86 CPU的一种复杂的工作模式,区别于实模式(Real Mode)。保护模式顾名思义是一个具有“保护”功能的CPU模式,保护模式与实模式最大的区别在于寻址方式的改变,因此我觉得理解了保护模式的寻址,就能理解保护模式。
先考虑一下,Intel为何要放着简简单单的实模式不用,要设计如此复杂的保护模式呢?
在实模式CPU上编程,系统程序员要考虑如何避免用户进程访问系统空间,如何禁止进程A非法访问进程B的空间;应用程序员要精确计算程序大小避免自己的程序占用其他进程的空间,但对于运行时才能知道所需内存的程序却几乎无能为力,导致有时候程序干脆无法运行……实模式的优点恰恰是它的缺点:它很简单,但过于简单,难以支持现代操作系统提供多进程的能力。
怎么办?还是那句话:计算机的所有问题都通过增加一个层来解决。如果在进程与进程之间、进程与系统之间加上一个层,就能解决这个问题。保护模式就是这个层(layer)。
这个层怎么加?要看这个层如何起作用。这个层的作用,就是要避免一个进程访问别的进程空间,避免用户进程访问操作系统空间。显然问题出在了访问内存地址空间这儿,因此只要把层加在这儿,保护模式就成功了:既保护了用户进程,又保护了操作系统。下面我们就来看一下保护模式和实模式有什么不同。
让我们首先来回顾实模式下的寻址方式
段首地址×16+偏移量 = 物理地址
为什么要×16?因为在8086CPU中,地址线是20位,但寄存器是16位的,最高寻址64KB,它无法寻址到1M内存。于是,Intel设计了这种寻址方式,先缩小4位成16位放入到段寄存器,用到时候,再将其扩大到20位,这也造成了段的首地址必须是16的倍数的限制。
保护模式下分段机制是利用一个称作段选择子的偏移量,从而到描述符表找到需要的段描述符,而这个段描述符中就存放着真正的段的物理首地址,再加上偏移量。
一段话,出现了三个新名词:
1、段选择子 2、描述符表 3、段描述符
我们现在可以这样来理解这段话: 有一个结构体类型,它有三个成员变量:段物理首地址、段界限、段属性。内存中维护一个该结构体类型的一个数组。而分段机制就是利用一个索引,找到该数组中对应的结构构体,从而得到段的物理首地址,然后加上偏移量,得到真正的物理地址。
公式:xxxx:yyyyyyyy
其中,xxxx也就是索引,yyyyyyyy是偏移量(因为32位寄存器,所以8个16进制)xxxx存放在段寄存器中。
现在,我们来到过来分析一下那三个新名词。
(1) 段描述符:相当于一个结构体,它有三个成员变量:段物理首地址、段界限、段属性;
(2) 描述符表:相当于一个数组,什么样的数组呢?是一个段描述符组成的数组。
(3) 段选择子:相当于也就是数组的索引,但这时候的索引不在是高级语言中数组的下标,而是我们将要找的那个段描述符相对于数组首地址(也就是全局描述表的首地址)偏移位置。
就这么简单,如图:
图中,通过Selector(段选择子)找到存储在Descriptor Table(描述符表)中某个Descriptor(段描述符),该段描述符中存放有该段的物理首地址,所以就可以找到内存中真正的物理段首地址Segment。
Offset(偏移量):就是相对该段的偏移量。物理首地址 + 偏移量 就得到了物理地址,本图就是DATA。但这时,心细的朋友就发现了一个GDTR这个家伙还没有提到!
我们来看一下什么是GDTR ? Global Descriptor Table Register(全局描述符表寄存器)但是这个寄存器有什么用呢 ? 大家想一下,段描述符表现在是存放在内存中,那CPU是如何知道它在哪里呢?所以,Intel 公司设计了一个全局描述符表寄存器,专门用来存放段描述符表的首地址,以便找到内存中段描述符表。这时,段描述符表地址被存到GDTR寄存器中了。
好了,分析就到这,我们来看一下正式的定义:
当x86 CPU 工作在保护模式时,可以使用全部32根地址线访问4GB的内存,因为80386的所有通用寄存器都是32位的,所以用任何一个通用寄存器来间接寻址,不用分段就可以访问4G空间中任意的内存地址。也就是说我们直接可以用Eip寄存器就可以找到茫茫内存里面所有的值! 但这并不意味着,此时段寄存器就不再有用了[其实 还有部分原因是要与8086兼容] 。实际上,段寄存器更加有用了,虽然再寻址上没有分段的限制了,但在保护模式下,一个地址空间是否可以被写入,可以被多少优先级的代码写入,是不是允许执行等等涉及保护的问题就出来了。要解决这些问题,必须对一个地址空空间定义一些安全上的属性。段寄存器这时就派上了用场。但是设计属性和保护模式下段的参数,要表示的信息太多了,要用64位长的数据才能表示。我们把着64位的属性数据叫做段描述符,上面说过,它包含3个变量:段物理首地址、段界限、段属性。
80386的段寄存器是16位(注意:通用寄存器在保护模式下都是32位,但段寄存器没有被改变,比如cs还是16位的,16位的段寄存器怎么可能装下一个64位的段描述符)的,无法放下保护模式下64位的段描述符。如何解决这个问题呢? 方法是把所有段的段描述符顺序存放在内存中的指定位置,组成一个段描述符表(Descriptor Table);而段寄存器中的16位用来做索引信息,这时,段寄存器中的信息不再是段地址了,而是段选择子(Selector)。可以通过它在段描述符表中“选择”一个项目已得到段的全部信息。也就是说我们在另一个地方把段描述符放好,然后通过选择子来找到这个段描述符。
那么段描述符表存放在哪里呢?80386引入了两个新的寄存器来管理段描述符,就是GDTR和LDTR,(LDTR大家先忘记它,随着学习的深入,我们会在以后学习)。
这样,用以下几步来总体体验下保护模式下寻址的机制
(1) 段寄存器中存放段选择子Selector;
(2) GDTR中存放着段描述符表的首地址;
(3) 通过选择子根据GDTR中的首地址,就能找到对应的段描述符;
(4) 段描述符中有段的物理首地址,就得到段在内存中的首地址;
(5) 加上偏移量,就找到在这个段中存放的数据的真正物理地址。
总结:
不管是实模式下的物理地址,还是保护模式下的物理地址,反正他们都是物理地址,实模式下求的物理地址,也能在保护模式下使用,只是他们不同的是,如何寻址的方式不一样。