X86 CPU段式与页式存管分析

来源:互联网 发布:30岁后的美工 编辑:程序博客网 时间:2024/05/01 01:48

一、热身- CPU概念介绍

Intel X86历史系列:

X86是指Intel从16位微处理器开始的整个CPU芯片系列,主要8086,8088,80186,80286,80386,80486,以及以后各种型号的Pentium芯片,8086和8088为是16位处理器,20根地址线,,80286是个过渡产品,16位处理器,24根地址线,有不那么健全的保护模式,所以说它的历史意义在于:在寻址上开始了从实地址模式到保护模式的过渡。从80386开始为32位处理器,32根地址线。实现了保护模式及页式内存管理。

ALU:

算术逻辑单元 (Arithmetic Logic Unit, ALU)是中央处理器(CPU)的执行单元,是所有中央处理器的核心组成部分,由"And Gate" 和"Or Gate"构成的算术逻辑单元,基本操作包括加、减、乘、除四则运算,与、或、非、异或等逻辑操作,以及移位、比较和传送等操作。计算机运行时,运算器的操作和操作种类由控制器决定。运算器处理的数据来自存储器;处理后的结果数据通常送回存储器,或暂时寄存在运算器中。基本上,在所有现代CPU体系结构中,二进制都以二补数的形式来表示。我们通常说的一个CPU是16位或者是32位,指的是ALU 的宽度,即字长,它是CPU在同一时间内能处理的二进制的位数。字长反映了CPU的计算精度。

三大总线:

数据总线DB: 用于传送数据信息。数据总线是双向三态形式的总线,即他既可以把CPU的数据传送到存储器或I/O接口等其它部件,也可以将其它部件的数据传送到CPU。数据总线的位数通常与微处理的字长相一致,但也有例外。它传送的即可以是真正的数据,也可以指令代码或状态信息,有时甚至是一个控制信息。  

地址总线AB:是专门用来传送地址的,由于地址只能从CPU传向外部存储器或I/O端口,所以地址总线总是单向三态的,这与数据总线不同。地址总线的位数决定了CPU可直接寻址的内存空间大小。一般来说,若地址总线为n位,则可寻址空间为2^n字节。  

控制总线CB:用来传送控制信号和时序信号。控制信号中,有的是微处理器送往存储器和I/O接口电路的,如读/写信号,片选信号、中断响应信号等;也有是其它部件反馈给CPU的,比如:中断申请信号、复位信号、总线请求信号、限备就绪信号等。因此,控制总线的传送方向由具体控制信号而定,一般是双向的,控制总线的位数要根据系统的实际控制需要而定。 

寄存器:

16位处理器:即80386之前的系列,一般以8086为代表,8086  CPU 中寄存器总共为 14 个,且均为 16 位 。即 AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES 共 14 个。更详细的可以参考我转载的《8086 CPU 寄存器简介》

32位处理器:以80386为代表,除了段寄存器位数仍为16位之外,其他的寄存器都为32位(以E开头命名),同时新增FS,GS两个段寄存器,即:

 4个数据寄存器(EAX、EBX、ECX和EDX)

 2个变址和指针寄存器(ESI和EDI)

 2个指针寄存器(ESP和EBP)

 6个段寄存器(ES、CS、SS、DS、FS和GS)

 1个指令指针寄存器(EIP)

 1个标志寄存器(EFlags)

二、实模式寻址

实模式也称为实地址模式,对于8086/8088来说计算实际地址是用绝对地址对1M求模。8086的地址线的物理结构:20根,也就是它可以物理寻址的内存范围为2^20个字节,即1 M空间,但由于8086/8088所使用的寄存器都是16位,能够表示的地址范围只有0-64K,所以为了在8086/8088下能够访问1M内存,Intel采取了分段寻址的模式:16位段基地址:16位偏移EA。其绝对地址计算方法为:16位基地址左移4位+16位偏移=20位地址。  比如:DS=1000H EA=FFFFH 那么绝对地址就为:10000H + 0FFFFH = 1FFFFH 地址单元。下面这张图描述了实模式的过程:

由于实模式下,任何一个进程都能够访问从段寄存器所确定的基地址进程的64K字节的的连续地址空间,并且可以随意更改段寄存器的内容从而达到访问内存中任一个单元,而不受到任何限制,这种方式缺乏对内存访问的限制,或者说保护,而没有保护,就谈不上什么内存管理,也谈不上什么现代意义上的操作系统。

三、保护模式寻址-段式存储管理

保护模式中,内存的管理模式分为两种,段模式和页模式,其中页模式也是基于段模式的。也就是说,保护模式的内存管理模式事实上是:纯段模式和段页式。进一步说,段模式是必不可少的,而页模式则是可选的——如果使用页模式,则是段页式;否则这是纯段模式。

段选择器:32位汇编中16位段寄存器(CS、DS、ES、SS、FS、GS)中不再存放段基址,而 是段描述符在段描述符表中的索引值,D3-D15位是索引值,D0-D1位是优先级(RPL)用于特权检查,D2位是描述符表引用指示位TI,TI=0指 示从全局描述表GDT中读取描述符,TI=1指示从局部描述符中LDT中读取描述符。这些信息总称段选择器(段选择子).
段描述符:8个 字节64位,每一个段都有一个对应的描述符。根据描述符描述符所描述的对象不同,描述符可分为三类:储存段描述符,系统段描述符,门描述符(控制描述 符)。在描述符中定义了段的基址,限长和访问内型等属性。其中基址给出该段的基础地址,用于形成线性地址;限长说明该段的长度,用于存储空间保护;段属性 说明该段的访问权限、该段当前在内存中的存在性,以及该段所在的特权级。

每当一个寄存器的内容发生改变时,CPU就把由这段寄存器的新内容所决定的段描述项装入CPU内部的一个影子描述项。这样,CPU中有几个段寄存器就有几个影子描述项,所以也可以看作是对段寄存器的扩充。扩充后的段寄存器分成两部分,一部分是可见的(对程序而言),还与原先的段寄存器一样;另一部分是不可见的,就是用来存放影子描述项的空间,这一部分是专供CPU内部使用的。
在80386的段式内存管理的基础上,如果把每个段寄存器都指向同一个描述项,而在该描述项中则将基地址设成0,并将段长度设成最大,这样便形成一个从0开始覆盖整个32位地址空间的一个整段。由于基地址为0,此时的物理地址与逻辑地址相同,CPU放到地址总线上去的地址就是在指令中给出的地址。这样的地址有别于由“段寄存器/位移量”构成的层次式地址,所以Intel称其为平面地址。
利用80386对段式内存管理的硬件支持,可以实现段式虚存管理。当一个段寄存器的内容改变时,CPU要根据新的段寄存器内存以及GDTR或LDTR的内容找到相应的段描述项并将其装入CPU中。在此过程中,CPU会检查该描述项中的p标志位,如果p标志为0,就表示该描述项所指向的那一段内容不在内存中(在磁盘上的某个地方),此时CPU会产生一次异常,而相应的服务程序便可以从磁盘交换区将这一段的内容读入内存中的某个地方,并据此设置描述项中的基地址,再将p标志位设置成1。相应地,内存中暂时不用的存储段则可以写入磁盘,并将其描述项中p标志位改为0
对段式内存管理的支持只是i386保护模式的一个组成部分,如果没有系统状态和用户状态的分离,以及特权指令(只允许在系统状态下使用)的设立,那么尽管有了段式内存管理,也不能起到保护的效果
80386将执行权限划分4个特权级别,其中0级最高,3级最低。每一条指令也都有其适用界别。一般程序的当前运行级别由其代码段的局部描述项中的dpl字段决定。每个描述项中的dpl字段都是在0级状态下由内核设定的。而全局段描述的dpl字段,表示所需的级别。当改变一个段寄存器的内容时,CPU会加以检查,以确保该段程序的当前执行权限和段寄存器所指定要求的权限均不低于所要访问的那一段内存的权限dpl。


段描述符表:IA-32处理器把所有段描述符按顺序组织成线性表 放在内存中,称为段描述符表。分为三类:全局描述符表GDT,局部描述符表LDT和中断描述符表IDT。GDT和IDT在整个系统中只有一张,而每个任务 都有自己私有的一张局部描述符表LDT,用于记录本任务中涉及的各个代码段、数据段和堆栈段以及本任务的使用的门描述符。GDT包含系统使用的代码段、数 据段、堆栈段和特殊数据段描述符,以及所有任务局部描述符表LDT的描述符。GDT,LDT为内存中的一个结构数组。段描述符是一个结构,即数组中的元素。

GDTR全局描述符寄存器:48位,高32位存放GDT基址,低16为存放GDT限长。
LDTR局部描述符寄存器:16位,高13为存放LDT在GDT中的索引值。

在段选择器中,当 TI=0时表示段描述符在GDT中,如下图所示:

① 先从GDTR寄存器中获得GDT基址。

② 然后再GDT中以段选择器高13位位置索引值得到段描述符。

③ 段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy才得到最后的线性地址。



当TI=1时表示段描述符在LDT中,如下图所示:

① 还是先从GDTR寄存器中获得GDT基址。

② 从LDTR寄存器中获取LDT所在段的位置索引(LDTR高13位)。(比GDT多的步骤)

③ 以这个位置索引在GDT中得到LDT段描述符从而得到LDT段基址。(比GDT多的步骤)

④ 用段选择器高13位位置索引值从LDT段中得到段描述符。

⑤ 段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy才得到最后的线性地址。

四、保护模式寻址-页式存储管理

80386 的段式内存管理机制,是将指令中结合段寄存器使用的 32 位逻辑地址映射 ( 转换 ) 成同样是 32 位的物理地址。之所以称为“物理地址”,是因为这是真正放到地址总线上去,并用以寻访物理上存在着的具体内存单元的地址。但是,段式存储管理机制的灵活性和效率都比较差。一方面“段”是可变长度的,这就给盘区交换操作带来了不便 ; 另一方面,如果为了增加灵活性而将一个进程的空间划分成很多小段时,就势必要求在程序中频繁地改变段寄存器的内容。同时,如果将段分小,虽然一个段描述表中可容纳 8192 个描述项 ( 因为有 13 位下标 ) ,也未必就能保证足够使用。所以,比较好的办法还是采用页式存储管理。本来,页式存储管理并不需要建立在段式存储管理的基础上,这是两种不同的机制。可是,在 80386 中,保护模式的实现是与段存储密不可分的。例如, CPU 的当前执行权限是在有关的代码段描述项中规定的。因此,在 80386 中,既然决定利用部分已经存在的资源,而不是另起炉灶,那就无法绕过段式存储管理来实现页式存储管理。这也意味着,页式存储管理的作用是在由段式存储管理所映射而成的地址上再加上一层地址映射。由于此时由段式存储管理映射而成的地址不再是“物理地址”了, Intel 就称之为“线性地址”。于是,段式存储管理先将逻辑地址映射成线性地址,然后再由页式存储管理将线性地址映射成物理地址,或者,当不使用页式管理时,就将线性地址直接用作物理地址。 
80386 把线性地址空间划分成 4K 字节的页面,每个页面可以被映射至物理存储空间中任意一块 4K 字节大小的区间 ( 边界必须与 4K 字节对齐 ) 。在段式存储管理中,连续的逻辑地址经过映射后在线性地址空间还是连续的。但是在页式存储管理中,连续的线性地址经过映射后在物理空间却不一定连续 ( 其灵活性也正在于此 ) 。这里值得指出的是,虽然页式存储管理是建立在段式存储管理的基础上,但一旦启用了页式存储管理,所有的线性地址都要经过页式映射,连 GDTR 与 LDTR 中给出的段描述表起始地址也不例外。 
由于页式存储管理的引入,对 32 位线性地址有了新的解释 ( 以前就是物理地址 ) : 

typedef   struct   {        unsignedint  dir:  10;  /* 用作页面表目录中的下标,该目录项指向一个页面表 */        unsignedint  page:   10; /* 用作具体页面表中的下标,该表项指向一个物理页面 */        unsignedint  offset:  12; /* 在 4K 字节物理页面内的偏移量 */ } 线性地址 ; 

这个结构可用下图形象地表示: 

可以看出,在页面目录中共有 210=1024 个目录项,每个目录项指向一个页面表,而在每个页面表中又共有 1024 个页面描述项。类似于 GDTR 和 LDTR ,又增加了一个新的寄存器 CR3 作为指向当前页面目录的指针。这样,从线性地址到物理地址的映射过程为: 
1、 从 CR3 取得页面目录的基地址。 
2、 以线性地址中的 dir 位段为下标,在目录中取得相应页面表的基地址。 
3、 以线性地址中的 page 位段为下标,在所得到的页面表中取得相应的页面描述项。 
4、 将页面描述项中给出的页面基地址与线性地址中的 offset 位段相加得到物理地址。 
上述映射过程可用下图直观地表示

那么为什么要使用两个层次,先找到目录项,再找到页面描述项,而不是像在使用段寄存器时那样一步到位呢?这是出于空间效率的考虑。如果将线性地址中的 dir 和 page 两个位段合并在一起是 20 位,因此页面表的大步就将是 1Kx1K=1M 个表项。由于每个页面的大小为 4K 字节,总的空间大小仍为 4Kx1M=4G ,正好是 32 位地址空间的大小。但是,实际上是很难想象有一个进程会需要用到 4G 的全部空间,所以大部分青藏势必是空着的。可是,在一个数组中,即使是空着的表项也占空间,这样就造成了浪费。而若分成两层,则页表可以视需要而设置,如果目录中某项为空,就不必设立相应的页表,从而省下了存储空间。当然,在最坏的情况下,如果一个进程真的要用到全部 4G 的存储空间,那就不仅不能节省,反而要多消耗一个目录所占用的空间,但那概率基本上是 0. 另外,一个页面的大小是 4K 字节,而每一个页面表项或目录项的大小是 4 个字节。 1024 个表项正好也是 4K 字节,恰好可以放在一个页面中。而若多于 1024 项就要使目录或页面表跨页面存放了。也正因如此,在 64 位的 AlphaCPU 中页面的大小是 8K 字节,因为目录项和页面表项的大小都变成了 8 个字节。如前所述,目录项中含有指向一个页面表的指针,而页面表项中则含有指向一个页面起始地址的指针。由于页面表和页面起始地址都总是在 4K 字节的边界上,这些指针的低 12 位都永远是 0. 这样,在目录项和页面项中都只要有 20 位用于指针就够了,而余下的 12 位则可以用于控制或其他的目的。于是,目录项的结构为: 

typedef   struct      {        unsignedint    ptba:       20;/* 页表基地址的高 20 位 */        unsignedint    avail:       3;/* 供系统程序员使用 */        unsignedint    g:     1/*global, 全局性页面 */        unsignedint    ps:   1/* 页面大小, 0 表示 4K 字节 */        unsignedint    reserved:       1/* 保留,永远是 0*/        unsignedint    a:     1;/*accessed, 已被访问过 */        unsignedint    pcd:1;/* 关闭 ( 不使用 ) 缓冲存储器 */        unsignedint    pwt:1;/*write Through, 用于缓冲存储器 */        unsignedint   u_s:1;/* 为 0 时表示系统 ( 或超级 ) 权限,为 1 时表示用户权限 */        unsignedint    r_w:       1;/* 只读或可写 */        unsignedint    p:    1;/* 为 0 时表示相应的页面不在内存中 */ } 目录项 ; 


页表项的结构基本上与此相同,但没有“页面大小”位 ps ,所以第 8 位保留不用,但第 7 位 ( 在目录项中保留不用 ) 则为 D(Dirty) 标志,表示该页面已经被写过,所以已经“脏”了。当页面表项或目录项中的最低位 p 为 0 时,表示相应的页面或页面表不在内存中,根据其他一些有关寄存器的设置, CPU 可以产生一个“页面错” (Page Fault) 异常 ( 也称为缺页中断,但异常和中断其实是有区别的 ) 。这样,内核中的有关异常服务程序就可以从磁盘上的页面交换区将相应的页面读入内存,并且相应地设置表项中的基地址,并将 p 位设置成 1. 相反,也可以将内存中暂不使用的页面写入磁盘的交换区,然后将相应的页面表项的 p 位设置为 0. 这样,就可以实现页式虚存了。当 p 位为 0 时,表项的其余各位均无意义,所以可被用来临时存储其他信息,如被换出的页面在磁盘上的位置等等。 
       当目录项中的 ps(page size) 位为 0 时,包含在由该目录项所指的页面表中所有的页面大小都是 4K 字节,这也是目前在 Linux 内核中所采用的页面大小。但是,从 Pentium 处理器开始, Intel 引入了 PSE 页面大小扩充机制。当 ps 位为 1 时,页面的大小就成了 4M 字节,而页面表就不再使用了。这时候,线性地址中的低 22 位就全部用在 4M 字节页面中的位移。这样,总的寻址能力还是没有改变,即 1024x1024=4G ,但是映射的过程减少了一个层次。随着内存容量和磁盘容量的日益增加,磁盘访问速度的显著提高,以及对图像处理要求的日益增加, 4M 字节的页面大小有可能成为主流。 
       最后, i386CPU 中还有个寄存器 CR0, 其最高位 PG 是页式映射机制的总开关。当 PG 位被设置成 1 时, CPU 就开启了页式存储管理的映射机制。 
从 Pentium Pro 开始, Intel 又作了扩充。这一次扩充的是物理地址的宽度。 Intel 在另一个控制寄存器 CR4 中又增加了一个 PAE(Physical Address Extension) ,当 PAE 位设置成 1 时,地址总线的宽度就变成了 36 位。与此相应,页式存储管理的映射机制也自然地有所改变。


0 0