初探处理器体系结构及寻址模式

转载于:http://blog.csdn.net/jn1158359135/article/details/7055317


由8086/8088、x86、Pentium发展到core系列短短40年间,处理器的时钟频率几乎已接近极限,尽管如此,自从86年Intel推出386至今除了增加一些有关流媒体的指令如mmx/sse之外,其他新增的大多数指令都可以从最初的指令集中组合实现同样的功能,整个编程模型维持了约有20多年。

1、处理器体系结构下的编程模型
首先概要的介绍下程序设计模型:


图1-1

图1-2

以上所列出的一些通用寄存器(注:其中RSP为专用寄存器,之所以把它放在通用寄存器组中只是为了方便记忆整个模型),除了数据位宽度不同之外,并无多大差别:

  • RAX(累加器):RAX如果是8/16/32位寻址,则只改变该寄存器的一部分。累加器用于乘法、除法及一些调整指令,同时也可以保存存储单元的偏移地址。
  • RBX(基址):用于保存存储单元的偏移地址,同时也能寻址存储器数据,作为偏移地址访问数据时默认使用数据段基址DS作为段前缀。
  • RCX(计数):可保存访问存储单元的偏移地址,或在串指令(REP/REPE/REPNE)以及移位、循环和LOOP/LOOPD指令中用作计数器。
  • RDX(数据):可使用RDX/EDX/DX/DH/DL寻址,同时作为通用寄存器也用于保存乘法形成的部分结果或者除法之前的部分被除数,也可用于寻址存储单元。
  • RBP(基指针):可用RBP/EBP/BP寻址,同时作为偏移地址访问存储单元时默认使用堆栈段基址SS作为段前缀。
  • RDI(目的变址):可用RDI/EDI/DI寻址,常用于在串指令中寻址目的数据串。
  • RSI(源变址):如RDI一样,RSI也可作为通用寄存器使用,通常为串指令寻址源数据串。


段寄存器CS、DS、ES、SS、FS、GS以及RSP为专用寄存器,以下是这些寄存器的概要描述:

  • RSP(堆栈指针):RSP寻址称为堆栈的存储区,通过该指针存取堆栈数据。用作16位寄存器时使用SP,如果是32位则为ESP。
  • CS(代码段):代码段寄存器存放程序所使用的代码在存储器中的基地址。
  • DS(数据段):存放数据段的基地址。
  • ES(附加段):该段寄存器通常在串指令(LODS/STOS/MOVS/INS/OUTS)中使用,主要用于在存储器中将数据进行成块转移。
  • SS(堆栈段):为堆栈定义一个存储区域。主要用来存放过程调用所需参数、本地局部变量以及处理器状态等。
  • FS与GS:这两个段寄存器是386~Core2中新增的段寄存器,以允许程序访问附加的存储器段。可以将其视为“通用的段寄存器”,通过将段的基地址存入这两个寄存器中可以实现自定义的寻址操作,从而增加了编程的灵活性,不过我们一般不用这两个寄存器,仅作了解即可。



图1-3

如上图所示,在Pentium4及更高型号处理器中增加了R8~R15这8个64位通用寄存器,这些新增的64位寄存器仍支持按字节、字、双字或四字方式寻址,而不同之处在于只有最右边的数据位可以用来作为单独的一个字节/字等。注意在使用这些新增寄存器的其中一个部分时需要在寄存器末尾添加控制字,例如:

[plain]  view plain  copy
  1. mov R11D, R8D ;其中字母D用于表示双字访问  
  2. ;也可以将D改为B或者W,B表示字节访问,W表示字访问  
  3. ;如果不加任何控制字则使用整个寄存器   


图1-4

RIP寻址代码段中当前执行指令的下一条指令,当处理器工作在实模式下时使用16位的IP寄存器,当工作于保护模式时则使用32位的EIP。指令指针可由转移指令或调用指令修改。需要注意的是,在64位模式中由于处理器包含40位地址总线,所以总共可以寻址240=1TB的内存

有关EFLAGS寄存器的详细介绍参见x86-EFLAGS寄存器详解一文。


2、寻址模式
x86主要存在2种寻址模式,分别为实模式与保护模式。在实模式中,通常寻址时都是通过段寄存器(如图1-2所示)+通用寄存器(如图1-1所示),即基址+变址的方式进行寻址。举例如下:

[plain]  view plain  copy
  1. mov ax, ds:[bx]  ;如ds:[bx]所示为段寄存器+通用寄存器的方式进行寻址  
  2. ;因为在使用通用寄存器寻址时默认使用数据段寄存器,因此上述指令等价于 mov ax, [bx]  

以下是关于三类地址的概要描述:

  • 逻辑地址:由段和偏移量组成,偏移量指明了从段开始到实际地址之间的距离。如上例中ds为段,而bx则为偏移量。
  • 线性地址:一个32位无符号整数,用来表示内存地址空间。
  • 物理地址:用于内存芯片单元寻址的实际地址,从处理器的地址引脚发送到内存总线上,实际即为高低电平信号。由于PC的内存容量为4GB,因此物理地址由32位无符号整数表示。

无论是逻辑地址还是线性地址最终都将转换为实际的物理地址用以寻址内存空间,该过程是由内存管理单元(MMU)通过一种称为分段单元(segmentation unit)的硬件电路把一个逻辑地址转换成线性地址;接着,第二个称为分页单元(paging unit)的硬件电路把线性地址转换为物理地址,其中启用分页机制是通过置位CR0寄存器中的PG位来实现的。图解如下:


(注意上述注解中有误,实模式下不能启用分页机制,原因见下文—详解保护模式下的分页机制

而由实模式向保护模式的切换,则是通过置位cr0寄存中的PE位来实现的:

[plain]  view plain  copy
  1. ;处于实模式下  
  2.   
  3. ;禁用中断  
  4. cli  
  5.   
  6. ;打开A20线  
  7. in al, 92h  
  8. or al, 00000010b  
  9. out 92h, al  
  10.   
  11. ;准备切换到保护模式,设置PE为1  
  12. mov eax, cr0  
  13. or eax, 1  
  14. mov cr0, eax  
  15.   
  16. ;处于保护模式下  

下面是两种处理器模式的简要介绍:

  • 实模式:该模式下只能寻址起始的1MB大小的内存地址空间,并且在寻址时采用16位的段和偏移量。处理器在每次加电或复位后都默认以实模式开始工作。
  • 保护模式:寻址采用32位段和偏移量,最大寻址空间可达4GB,最大分段4GB 。

在实模式下,x86所采用的寻址方式是在16位段寄存器的最右边补上一个0,之后再加上作为偏移量的通用寄存器,从而生成20位的逻辑地址,用以访问1MB存储空间内的任意一个单元。由于所使用的通用寄存器的数据位的宽度为16,所以一个段的最大容量为216=64KB。举例来说,如果段寄存器的内容为1000h,而偏移地址为F000h,那么最终生成的逻辑地址为1F000h,图解如下:

注意在实模式下,程序中的代码段、数据段及堆栈段在物理内存中连续存放。当一个段内存放的数据或是代码少于64KB时,那么与其邻接的下一个段就在当前段可寻址的64KB范围之内,因此这就允许各个段之间发生堆叠。

在保护模式下使用32位通用寄存器,因而可供寻址的物理内存多达232=4GB。并且此时处理器对段寄存器的使用方式也发生了改变,段寄存器不再被解释为段的基地址,而是将该寄存器的16个位分成3个用于不同功能的域:


图2-1

如上图所示,第3~15位存放选择子(Selector),用于索引描述符表内的一个描述符,该描述符用于描述存储器段的位置、长度和访问权限。并且在TI=0时选择全局描述符表(Global Descriptor Table, GDT),TI=1时选择局部描述符表(Local Descriptor Table, LDT)。其中全局描述符表包含所有进程的段定义,而局部描述符表则通常由某个指定的进程所使用。因为段选择子为13位,所以总共可以在一个全局/局部描述符表中索引出8192(213=8192)项,而每个描述符的大小为8个字节,因此每个全局/局部描述符表占用64KB内存空间。通常情况下操作系统并不为应用创建LDT,除非应用程序显示要求这么做,并且所有的进程均共享同一个GDT,这就意味着默认情况下整个系统的分段结构只由一个GDT指示。此外上述段描述符表的基地址被存放在一组专用寄存器中,这些专用寄存器被称为段描述符表寄存器:


图2-2

如图2-2所示,TR中包含的选择子用于从任务的描述符表中索引出一个描述符,从而在多任务系统中实现上下文切换操作,LDTR寄存器中包含的选择子则用于从局部描述符表中索引出一个描述符。另外的GDTR与IDTR寄存器包含基地址及界限域,其中界限域的数据位宽度为16,基地址域的数据位宽度为32。在进入保护模式之前,必须先初始化中断描述符表,此后在保护模式下,全局描述符表的基地址及其界限才被装入GDTR中。

  • 寻址全局描述符时,首先根据GDTR得到全局描述符表的基地址,之后通过段寄存器中的13位段选择子(如图2-1所示)索引出其中的一个描述符。
  • 在寻址局部描述符之前,操作系统会在全局描述符表中为某个具体应用的局部描述符表进行注册。此后若段寄存器中的TI域被置位,则通过GDTR中的基地址域及LDTR中的段选择子从全局描述符表中找到对应的描述符,该描述符包含了局部描述符的基地址,界限及访问权限等,接着根据段寄存器中的13位段选择子(如图2-1所示)在局部描述符表中索引出相应的局部描述符表项,图解如下:

图2-3

另外段寄存器中的RPL域指示对存储器段的请求优先级。因为该域数据位的宽度为2,所以总共有4种可以使用的优先级。但Windows/Linux均只使用其中的两种,且将优先级00赋予内核和驱动,而将优先级11分配给应用程序。优先级从环0~环3逐渐降低,注意只有请求优先级(RPL)等于或高于段描述符中访问权限域的优先级(DPL)才允许访问,否则系统将指示应用程序违例

以下是一个典型的代码段描述符:


图2-4
  • 基地址域的数据位宽度为16+8+8=32,该域指示存储器段的起始位置。
  • 20位的界限域指示段的最大偏移量,通常与描述符中的特征位(G位,也称为粒度位)一起使用,当G置位时,将在20位的界限域的尾部添加FFFH形成一个32位的值。
  • AV位指示段是否有效,当AV=1时指示当前存储器段有效,反之则无效,该位由操作系统使用,但Linux系统通常将其所省略。
  • 偏移量的数据位宽度为32时D位被置位,为16时该位被清零。


下图详细解释了访问权限域的各个位:


图2-5

下表为Linux内核对段描述符的典型设置方式:

基地址 G 界限 S TYPE DPL D P
用户代码段 0x0000 0000 1 0xF FFFF 1 10(0x1010) 3 1 1
用户数据段 0x0000 0000 1 0xF FFFF 1 2(0x0010) 3 1 1
内核代码段 0x0000 0000 1 0xF FFFF 1 10(0x1010) 0 1 1
内核数据段 0x0000 0000 1 0xF FFFF 1 2(0x0010) 0 1 1

上述设置分别与Linux内核中的宏__USER_CS,__USER_DS,__KERNEL_CS,__KERNEL_DS相对应。

  • 4个段的基地址均被设置为0,这意味着在Linux下逻辑地址即为线性地址。
  • 界限为0xF FFFF且粒度位G被置位为1,因此所有段的大小最多可达4GB。
  • D位置位,所以偏移量的数据位宽度为32。
  • P位被置位为1,指示所有段的基地址和界限域均是有效的。
  • S位被置位,指示该描述符为代码段或数据段描述符。
  • DPL域指示段的优先级,上述设置方式表示将最高优先级00分配给内核代码/数据段,而将最低优先级11分配给用户代码/数据段。
  • 根据TYPE域中的E位指示当前段为代码段或是数据段。在用户/内核代码段描述符中,C=0表示忽视描述符优先级,R=1表示当前段可读,A=0表示当前段尚未被访问。相应地,在用户/内核数据段描述符中,ED=0表示该段将向上扩展,W=1表示数据可写入,A=0同样表示当前段尚未被访问过。


后记
整个处理器体系结构的编程模型相对比较容易理解,在实际编程时参考即可。两种寻址模式则可以看成是微处理器从上世纪70年代发展至今的一个缩影,因为早期存储容量和编程模型的限制导致对于寻址方式需要有一种相对另类的解释,又由于处理器的发展是不断向上兼容的,这也就导致了多种寻址模式共存的局面,但问题的本质其实在于这两种模式对段寄存器的解释方式不同而已。另外在本文中并未涉及到内存分页机制,所以其实在本文中保护模式的讲解是不够彻底的,然限于篇幅所致以及其重要程度,故打算另成一文。


猜你喜欢

转载自blog.csdn.net/u013656962/article/details/50772458