保护机制是可靠的多任务运行环境所必需的:
- 它可用于避免各个任务相互干扰。
- 在软件开发的任何阶段都可以使用段级和页级保护来协助寻找和检测设计问题和错误。
- 当程序对错误内存空间执行了一次非期望的引用,保护机制可以阻止这种操作并且报告此类事件。
保护机制可以被用于分段和分页机制。处理器寄存器的2个位定义了当前执行程序的特权级,称为当前特权级(Current Privilege Level,CPL)。 在分段和分页地址转换过程中,处理器将对CPL进行验证。
通过设置控制寄存器CR0的PE标志(位0)可以让处理器工作在保护模式下,从而也就开启了分段保护机制。 一旦进入保护模式,处理器中并不存在明确的控制标志来停止或启用保护机制。 不过基于特权级的保护机制部分可以通过把所有段选择符和段描述符的特权级都设置为0级来隐含地关闭。 这种处理方式可以在段之间禁止特权级保护壁垒,但是其他段长度和段类型检查等保护机制仍然起作用。
设置控制寄存器CR0的PG标志(位31)可以开启分页机制,同时也开启了分页保护机制。 同样,处理器中也没有相关的标志用来在分页开启条件下禁止或开启页级保护机制。 但是通过设置每个页目录项和页表项的读/写(R/W)标志和用户/超级用户(U/S)标志, 我们可以禁止页级保护机制。设置这两个标志可以使得每个页面都可以被任意读/写,因此实际上也就禁止了页级保护。
对于分段级保护机制,处理器使用段寄存器中选择符(RPL和CPL)和段描述符中各个字段执行保护验证。 对于分页机制,则主要利用页目录和页表项中的R/W和U/S标志来实现保护操作。
在保护模式下,80x86提供了段级和页级保护机制。这种保护机制根据特权级(4级段保护和2级页保护) 提供了对某些段和页面的访问限制能力。 例如,操作系统代码和数据存放在要比普通应用程序具有高特权级的段中。 此后处理器的保护机制将会限制应用程序只能按照受控制的和规定的方式访问操作系统的代码和数据。
当使用保护机制时,每个内存引用都将受到检查以验证内存引用符合各种保护要求。 因为检查操作是与地址变换同时进行的,所以处理器性能并没有受到影响。 所进行的保护检查可分为以下几类:段界限检查、段类型检查、特权级检查、可寻址范围限制、过程入口点限制、指令集限制。
所有违反保护的操作都将导致产生一个异常。下面各节描述保护模式下的保护机制。
段描述符的段限长(或称段界限)字段用于防止程序或过程寻址到段外内存位置。 段限长的有效值依赖于颗粒度G标志的设置状态。对于数据段,段限长还与标志E(扩展方向) 和标志B(默认栈指针大小和/或上界限)有关。E标志是数据段类型的段描述符中类型字段的一个位。
当G标志清零时(字节颗粒度),有效的段长度是20位的段描述符中段限长字段Limit的值。 在这种情况下,Limit的范围从0到0xFFFFF(1MB)。当G标志置位时(4KB页颗粒度), 处理器把Limit字段的值乘上一个因子4K。在这种情况下,有效的Limit范围是从0xFFF到0xFFFFFFFF(4GB)。 请注意,当设置了G标志时,段偏移(地址)的低12位不会与Limit进行对照检查。例如,当段限长Limit等于0时,偏移值0到0xFFF仍然是有效的。
除了下扩段以外的所有段类型,有效Limit的值是段中允许被访问的最后一个地址,它要比段长度小1字节。 任何超出段限长字段指定的有效地址范围都将导致产生一个一般保护异常。
对于下扩数据段,段限长具有同样的功能,但其含义不同。这里,段限长指定了段中最后一个不允许访问的地址, 因此在设置了B标志的情况下,有效偏移范围是从(有效段偏移+1)到0xFFFF FFFF; 当B清零时,有效偏移值范围是从(有效段偏移+1)到0xFFFF。当下扩段的段限长为0时,段会有最大长度。
除了对段限长进行检查,处理器也会检查描述符表的长度。 GDTR、IDTR和LDTR寄存器中包含有16位的限长值,处理器用它来防止程序在描述符表的外面选择描述符。 描述符表的限长值指明了表中最后一个有效字节。因为每个描述符是8字节长,因此含有N个描述符项的表应该具有限长值8N-1。
选择符可以具有0值。这样的选择符指向GDT表中的第一个不用的描述符项。 尽管这个空选择符可以被加载进一个段寄存器中,但是任何使用这种描述符引用内存的企图都将产生一个一般保护性异常。
除了应用程序代码和数据段有描述符以外,处理器还有系统段和门两种描述符类型。 这些数据结构用于管理任务以及异常和中断。请注意,并非所有的描述符都定义一个段, 门描述符中存放有指向一个过程入口点的指针。段描述符在两个地方含有类型信息, 即描述符中的S标志和类型字段TYPE。处理器利用这些信息对由于非法使用段或门导致的编程错误进行检测。
S标志用于指出一个描述符是系统类型的还是代码或数据类型的。 TYPE字段另外提供了4位用于定义代码、数据和系统描述符的各种类型。
当操作段选择符和段描述符时,处理器会随时检查类型信息。主要在以下两种情况下检查类型信息:
- 当一个描述符的选择符加载进一个段寄存器中。此时某些段寄存器只能存放特定类型的描述符,例如: CS寄存器中只能被加载进一个可执行段的选择符。不可读可执行段的选择符不能被加载进数据段寄存器中。 只有可写数据段的选择符才能被加载进SS寄存器中。
- 当指令访问一个段,而该段的描述符已经加载进段寄存器中,指令只能使用某些预定义的方法来访问某些段。 任何指令不能写一个可执行段; 任何指令不能写一个可写位没有置位的数据段; 任何指令不能读一个可执行段,除非可执行段设置了可读标志。
处理器的段保护机制可以识别0~3级共4个特权级(或特权层),数值越大,特权越小。
处理器利用特权级来防止运行在较低特权级的程序或任务访问具有较高特权级的一个段,除非是在受控的条件下。 当处理器检测到一个违反特权级的操作时,它就会产生一个一般保护性异常。
为了在各个代码段和数据段之间进行特权级检测处理,处理器可以识别以下3种类型的特权级:
- 当前特权级CPL(Current Privilege Level)。CPL是当前正在执行程序或任务的特权级。 它存放在CS和SS段寄存器的位0和位1中。通常,CPL等于当前代码段的特权级。 当程序把控制转移到另一个具有不同特权级的代码段中时,处理器就会改变CPL。 当访问一个一致性(conforming)代码段时,则处理器对CPL的设置有些不同。 特权级值高于(即低特权级)或等于一致代码段DPL的任何段都可以访问一致代码段, 并且当处理器访问一个特权级不同于CPL的一致代码段时,CPL并不会被修改成一致代码段的DPL。
- 描述符特权级DPL(Descriptor Privilege Level)。DPL是一个段或门的特权级。 它存放在段或门描述符的DPL字段中。在当前执行代码段试图访问一个段或门时, 段或门的DPL会用来与CPL以及段或门选择符中的RPL(见下面说明)作比较。根据被访问的段或门的类型不同,DPL也有不同的含义:
数据段(Data Segment): 其DPL指出允许访问本数据段的程序或任务应具有的最大特权级数值。 例如,如果数据段的特权级DPL是1,那么只有运行在CPL为0或1的程序可以访问这个段。
非一致代码段(Nonconforming Code Segment)(不使用调用门): 其DPL指出程序或任务访问该段必须具有的特权级。 例如,某个非一致代码段的DPL是0,那么只有运行在CPL为0的程序能够访问这个段。
调用门(Call Gate): 其DPL指出访问调用门的当前执行程序或任务可处于的最大特权级数值(这与数据段的访问规则相同)。
一致和非一致代码段(通过调用门访问): 其DPL指出允许访问本代码段的程序或任务应具有的最小特权级数值。 例如,一致代码段的DPL是2,那么运行在CPL为0的程序就不能访问这个代码段。
任务状态段TSS: 其DPL指出访问TSS的当前执行程序或任务可处于的最大特权级数值(这与数据段的访问规则相同)。
- 请求特权级RPL(Request Privilege Level)。RPL是一种赋予段选择符的超越特权级,它存放在选择符的位0和位1中。 处理器会同时检查RPL和CPL,以确定是否允许访问一个段。即使程序或任务具有足够的特权级(CPL)来访问一个段, 但是如果提供的RPL特权级不足则访问也将被拒绝。即:如果段选择符的RPL的数值大于CPL,那么RPL将覆盖CPL(而使用RPL作为检查比较的特权级), 反之亦然,即始终取RPL和CPL中数值最大的特权级作为访问段时的比较对象。因此,RPL可用来确保高特权级的代码不会代表应用程序去访问一个段, 除非应用程序自己具有访问这个段的权限。
当段描述符的段选择符被加载进一个段寄存器时就会进行特权级检查操作, 但用于数据访问的检查方式和那些用于在代码段之间进行程序控制转移的检查方式不一样。因此下面分两种访问情况来考虑:
为了访问数据段中的操作数,数据段的段选择符必须被加载进数据段寄存器(DS、ES、FS或GS)或堆栈段寄存器(SS)中。 (可以使用指令MOV、POP、LDS、LES、LFS、LGS和LSS来加载段寄存器)。在把一个段选择符加载进段寄存器中之前, 处理器会进行特权级检查,如图所示: 它会把当前运行程序或任务的CPL、段选择符的RPL和段描述符的DPL进行比较。 只有当段的DPL数值大于或等于CPL和RPL两者时,处理器才会把选择符加载进段寄存器中;否则,就会产生一个一般保护异常,并且不加载段选择符。
https://github.com/novelinux/arch-x86/blob/master/res/ds_check.png
可知一个程序或任务可寻址的区域随着其CPL的改变而变化。 当CPL是0时,此时所有特权级上的数据段都可被访问; 当CPL是1时,只有在特权级1~3的数据段可被访问; 当CPL是3时,只有处于特权级3的数据段可被访问。
另外,有可能会把数据保存在代码段中。例如,当代码和数据是在ROM中时。因此,有些时候我们会需要访问代码段中的数据。 此时可以使用以下方法来访问代码段中的数据:
- 把非一致可读代码段的选择符加载进一个数据段寄存器中。
- 把一致可读代码段的选择符加载进一个数据段寄存器中。
- 使用代码段覆盖前缀(CS)来读取一个选择符已经在CS寄存器中的可读代码段。
访问数据段的相同规则也适用方法1。方法2则是总是有效的,因为一致代码段的特权级等同于CPL,而不管代码段的DPL。 方法3也总是有效的,因为CS寄存器选择的代码段的DPL与CPL相同。 当使用堆栈段选择符加载SS段寄存器时也会执行特权级检查。这里与堆栈段相关的所有特权级必须与CPL匹配。 即CPL、堆栈段选择符的RPL以及堆栈段描述符的DPL都必须相同。如果RPL或DPL与CPL不同,处理器就会产生一个一般保护性异常。
对于将程序控制权从一个代码段转移到另一个代码段,目标代码段的段选择符必须加载进代码段寄存器(CS)中。 作为这个加载过程的一部分,处理器会检测目标代码段的段描述符并执行各种限长、类型和特权级检查。 如果这些检查都通过了,则目标代码段选择符就会加载进CS寄存器,于是程序的控制权就被转移到新代码段中,程序将从EIP寄存器指向的指令处开始执行。
程序的控制转移使用指令JMP、RET、INT和IRET以及异常和中断机制来实现。 异常和中断是一些特殊实现,将在后面描述,本节主要说明JMP、CALL和RET指令的实现方法。 JMP或CALL指令可以利用以下4种方法之一来引用另外一个代码段:
- 目标操作数含有目标代码段的段选择符。
- 目标操作数指向一个调用门描述符,而该描述符中含有目标代码段的选择符。
- 目标操作数指向一个TSS,而该TSS中含有目标代码段的选择符。
- 目标操作数指向一个任务门,该任务门指向一个TSS,而该TSS中含有目标代码段的选择符。
JMP、CALL和RET指令的近转移形式只是在当前代码段中执行程序控制转移,因此不会执行特权级检查。 JMP、CALL或RET指令的远转移形式会把控制转移到另外一个代码段中,因此处理器一定会执行特权级检查。 当不通过调用门把程序控制权转移到另一个代码段时,处理器会验证4种特权级和类型信息,如下图所示:
https://github.com/novelinux/arch-x86/blob/master/res/cs_check.png
当前特权级CPL(这里,CPL是执行调用的代码段的特权级,即含有执行调用或跳转程序的代码段的CPL)。
- 含有被调用过程的目的代码段段描述符中的描述符特权级DPL。
- 目的代码段的段选择符中的请求特权级RPL。
- 目的代码段描述符中的一致性标志C。它确定了一个代码段是非一致代码段还是一致代码段。
处理器检查CPL、RPL和DPL的规则依赖于一致标志C的设置状态。当访问非一致代码段时(C=0), 调用者(程序)的CPL必须等于目的代码段的DPL,否则将会产生一般保护异常。 指向非一致代码段的段选择符的RPL对检查所起的作用有限。RPL在数值上必须小于或等于调用者的CPL才能使得控制转移成功完成。 当非一致代码段的段选择符被加载进CS寄存器中时,特权级字段不会改变,即它仍然是调用者的CPL。即使段选择符的RPL与CPL不同,这也是正确的。
当访问一致代码段时(C=1),调用者的CPL可以在数值上大于或等于目的代码段的DPL。 仅当CPL < DPL时,处理器才会产生一般保护异常。对于访问一致代码段,处理器忽略对RPL的检查。 对于一致代码段,DPL表示调用者对代码段进行成功调用可以处于的最低数值特权级。
当程序控制被转移到一个一致代码段中,CPL并不改变,即使目的代码段的DPL在数值上小于CPL。 这是CPL与可能的当前代码段DPL不相同的唯一一种情况。同样,由于CPL没有改变,因此堆栈也不会切换。
大多数代码段都是非一致代码段。对于这些段,程序的控制权只能转移到具有相同特权级的代码段中, 除非转移是通过一个调用门进行,见下面说明。
为了对具有不同特权级的代码段提供受控的访问,处理器提供了称为门描述符的特殊描述符集。共有4种门描述符:
- 调用门(Call Gate),类型TYPE=12。
- 陷阱门(Trap Gate),类型TYPE=15。
- 中断门(Interrupt Gate),类型TYPE=14。
- 任务门(Task Gate),类型TYPE=5。
任务门用于任务切换,陷阱门和中断门是调用门的特殊类,专门用于调用异常和中断的处理程序,
调用门用于在不同特权级之间实现受控的程序控制转移。它们通常仅用于使用特权级保护机制的操作系统中。 下图给出了调用门描述符的格式。调用门描述符可以存放在GDT或LDT中,但是不能放在中断描述符表IDT中。 一个调用门主要具有以下功能:
https://github.com/novelinux/arch-x86/blob/master/res/call_gate.png
- 指定要访问的代码段。
- 在指定代码段中定义过程(程序)的一个入口点。
- 指定访问过程的调用者需具备的特权级。
- 若会发生堆栈切换,它会指定在堆栈之间需要复制的可选参数个数。
- 指明调用门描述符是否有效。
调用门中的段选择符字段指定要访问的代码段。偏移值字段指定段中入口点。这个入口点通常是指定过程的第一条指令。 DPL字段指定调用门的特权级,从而指定通过调用门访问特定过程所要求的特权级。 标志P指明调用门描述符是否有效。参数个数字段(Param Count)指明在发生堆栈切换时从调用者堆栈复制到新堆栈中的参数个数。 Linux内核中并没有用到调用门。
为了访问调用门,我们需要为CALL或JMP指令的操作数提供一个远指针。该指针中的段选择符用于指定调用门, 而指针的偏移值虽然需要但CPU并不会用它。该偏移值可以设置为任意值,如图所示:
https://github.com/novelinux/arch-x86/blob/master/res/far_jmp.png
当处理器访问调用门时,它会使用调用门中的段选择符来定位目的代码段的段描述符。 然后CPU会把代码段描述符的基地址与调用门中的偏移值进行组合,形成代码段中指定程序入口点的线性地址。 通过调用门进行程序控制转移时,CPU会对当前特权级CPL、调用门选择符中的请求特权级RPL、 调用门描述符中的描述符特权级DPL和目的代码段描述符中的DPL四种不同的特权级进行检查,以确定控制转移的有效性,如图所示:
https://github.com/novelinux/arch-x86/blob/master/res/call_gate_check.png
另外,目的代码段描述符中的一致性标志C也将受到检查。
CALL指令和JMP指令具有不同的特权级检测规则,见下表: 调用门描述符的DPL字段指明了调用程序能够访问调用门的数值最大的特权级(最小特权级),即为了访问调用门, 调用者程序的特权级CPL必须小于或等于调用门的DPL。调用门段选择符的RPL也要同调用CPL一样遵守相同的规则, 即RPL也必须小于或等于调用门的DPL。
https://github.com/novelinux/arch-x86/blob/master/res/call_jmp.png
如果调用者与调用门之间的特权级检查成功通过,CPU就会接着把调用者的CPL与代码段描述符的DPL进行比较检查。 在这方面,CALL指令和JMP指令的检查规则就不同了。只有CALL指令可以通过调用门把程序控制转移到特权级更高的 非一致性代码段中,即可以转移到DPL小于CPL的非一致性代码段中去执行。而JMP指令只能通过调用门把控制转移到 DPL等于CPL的非一致性代码段中。但CALL指令和JMP指令都可以把控制转移到更高特权级的一致性代码段中,即转移到DPL小于或等于CPL的一致性代码段中。
如果一个调用把控制转移到了更高特权级的非一致性代码段中,那么CPL就会被设置为目的代码段的DPL值,并且会引起堆栈切换。 但是如果一个调用或跳转把控制转移到更高级别的一致性代码段上,那么CPL并不会改变,并且也不会引起堆栈切换。
调用门可以让一个代码段中的过程被不同特权级的程序访问。例如,位于一个代码段中的操作系统代码可能含有操作系统自身 和应用软件都允许访问的代码(比如处理字符I/O的代码)。因此可以为这些过程设置一个所有特权级代码都能访问的调用门。 另外可以专门为仅用于操作系统的代码设置一些更高特权级的调用门。
每当调用门用于把程序控制转移到一个更高级别的非一致性代码段时,CPU会自动切换到目的代码段特权级的堆栈去。 执行栈切换操作的目的是为了防止高特权级程序由于栈空间不足而引起崩溃,同时也为了防止低特权级程序通过共享的堆栈有意或无意地干扰高特权级的程序。
每个任务只能定义最多4个栈。一个用于运行在特权级3的应用程序代码,其他分别用于用到的特权级2、1和0。 如果一个系统中只使用了3和0两个特权级,那么每个任务就只需设置两个栈。每个栈都位于不同的段中,并且使用段选择符和段中偏移值指定。
当特权级3的程序在执行时,特权级3的堆栈的段选择符和栈指针会被分别存放在SS和ESP中,并且在发生堆栈切换时被保存在被调用过程的堆栈上。
特权级0、1和2的堆栈的初始指针值都存放在当前运行任务的TSS段中。TSS段中这些指针都是只读值。在任务运行时CPU并不会修改它们。 当调用更高特权级程序时,CPU才用它们来建立新堆栈。当从调用过程返回时,相应栈就不存在了。下一次再调用该过程时,就会再次使用TSS中的初始指针值建立一个新栈。
操作系统需要负责为所有用到的特权级建立堆栈和堆栈段描述符,并且在任务的TSS中设置初始指针值。每个栈必须可读可写,并且具有足够的空间来存放以下信息:
- 调用过程的SS、ESP、CS和EIP寄存器内容。
- 被调用过程的参数和临时变量所需使用的空间。
- 当隐含调用一个异常或中断过程时标志寄存器EFLAGS和出错码使用的空间。
- 由于一个过程可调用其他过程,因此每个栈必须有足够大的空间来容纳多帧(多套)上述信息。
当通过调用门执行一个过程调用而造成特权级改变时,CPU就会执行以下步骤切换堆栈并开始在新的特权级上执行被调用过程如下图所示:
https://github.com/novelinux/arch-x86/blob/master/res/stack_switch.png
- 使用目的代码段的DPL(即新的CPL)从TSS中选择新栈的指针。从当前TSS中读取新栈的段选择符和栈指针。 在读取栈段选择符、栈指针或栈段描述符过程中,任何违反段界限的错误都将导致产生一个无效TSS异常。
- 检查栈段描述符特权级和类型是否有效,若无效则同样产生一个无效TSS异常。
- 临时保存SS和ESP寄存器的当前值,把新栈的段选择符和栈指针加载到SS和ESP中。然后把临时保存的SS和ESP内容压入新栈中。
- 把调用门描述符中指定参数个数的参数从调用过程栈复制到新栈中。调用门中参数个数值最大为31,如果个数为0,则表示无参数,不需复制。
- 把返回指令指针(即当前CS和EIP内容)压入新栈。把新(目的)代码段选择符加载到CS中,同时把调用门中偏移值(新指令指针)加载到EIP中。 最后开始执行被调用过程。
指令RET用于执行近返回(near return)、同特权级远返回(far return)和不同特权级的远返回。 该指令用于从使用CALL指令调用的过程中返回。近返回仅在当前代码段中转移程序控制权,因此CPU仅进行界限检查。 对于相同特权级的远返回,CPU同时从堆栈中弹出返回代码段的选择符和返回指令指针。由于通常情况下这两个指针 是CALL指令压入栈中的,因此它们应该是有效的。但是CPU还是会执行特权级检查以应付当前过程可能修改指针值或者堆栈出现问题时的情况。
会发生特权级改变的远返回仅允许返回到低特权级程序中,即返回到的代码段DPL在数值上要大于CPL。 CPU会使用CS寄存器中选择符的RPL字段来确定是否要求返回到低特权级。如果RPL的数值要比CPL大, 就会执行特权级之间的返回操作。当执行远返回到一个调用过程时,CPU会执行以下步骤:
- 检查保存的CS寄存器中RPL字段值,以确定在返回时特权级是否需要改变。
- 弹出并使用被调用过程堆栈上的值加载CS和EIP寄存器。在此过程中会对代码段描述符和代码段选择符的RPL进行特权级与类型检查。
- 如果RET指令包含一个参数个数操作数并且返回操作会改变特权级,那么就在弹出栈中CS和EIP值之后把参数个数值加到ESP寄存器值中, 以跳过(丢弃)被调用者栈上的参数。此时ESP寄存器指向原来保存的调用者堆栈的指针SS和ESP。
- 把保存的SS和ESP值加载到SS和ESP寄存器中,从而切换回调用者的堆栈。而此时被调用者堆栈的SS和ESP值被抛弃。
- 如果RET指令包含一个参数个数操作数,则把参数个数值加到ESP寄存器值中,以跳过(丢弃)调用者栈上的参数。
- 检查段寄存器DS、ES、FS和GS的内容。如果其中有指向DPL小于新CPL的段(一致代码段除外),那么CPU就会用NULL选择符加载这个段寄存器。
页目录和页表表项中的读写标志R/W和用户/超级用户标志U/S提供了分段机制保护属性的一个子集。 分页机制只识别两级权限。特权级0、1和2被归类为超级用户级,而特权级3为普通用户级。 普通用户级的页面可以被标志成只读/可执行或可读/可写/可执行。超级用户级的页面对于超级用户来讲总是可读/可写/可执行的, 但普通用户不可访问,如下表所示: 对于分段机制,在最外层用户级执行的程序只能访问用户级的页面,但是在任何超级用户级(0、1、2)执行的程序不仅可以 访问用户级的页面,也可以访问超级用户级的页面。与分段机制不同的是,在内层超级用户级执行的程序对任何页面都具有 可读/可写/可执行权限,包括那些在用户级标注为只读/可执行的页面。
https://github.com/novelinux/arch-x86/blob/master/res/page_protection.png
正如在整个80x86地址转换机制中分页机制是在分段机制之后实施一样,页级保护也是在分段机制提供的保护措施之后发挥作用。 首先,所有段级保护被检查和测试。如果通过检查,就会再进行页级保护检查。 例如,仅当一个字节位于级别3上执行的程序可访问的段中,并且处于标志为用户级页面中时,这个内存中的字节才可被级别3上的程序访问。 仅当分段和分页都允许写时,才能对页面执行写操作。如果一个段是读/写类型的段,但是地址对应的相应页面被标注为只读/可执行, 那么还是不能对页面执行写操作。如果段的类型是只读/可执行,那么无论对应页面被赋予何种保护属性,页面始终是没有写权限的。 可见分段和分页的保护机制就像串联电路,其中哪个开关没有合上线路都不会通。
类似地,一个页面的保护属性由页目录和页表中表项的"串行"或"与"操作构成,见下表所示: 页表表项中的U/S标志和R/W标志应用于该表项映射的单个页面。页目录项中的U/S和R/W标志则对该目录项所映射的所有页面起作用。 页目录和页表的组合保护属性由两者属性的"与"(AND)操作构成,因此保护措施非常严格。
https://github.com/novelinux/arch-x86/blob/master/res/page_dir_table_protection.png
提供一些有关操作系统软件修改页表项内容所需遵守的规则。分页转换缓冲要求所有系统必须遵守这些规则。 为了避免每次内存应用都要访问驻留内存的页表,从而加快速度,最近使用的线性到物理地址的转换信息被 保存在处理器内的页转换高速缓冲中。处理器在访问内存中的页表之前会首先利用缓冲中的信息。 只有当必要的转换信息不在高速缓冲中时,处理器才会搜寻内存中的页目录和页表。页转换高速缓冲作用类似 于前面描述的加速段转换的段寄存器的影子描述符寄存器。 页转换高速缓冲的另一个术语称为转换查找缓冲(Translation Lookaside Buffer,TLB)。
80x86处理器并没有维护页转换高速缓冲和页表中数据的相关性,但是需要操作系统软件来确保它们一致, 即处理器并不知道什么时候页表被软件修改过了。因此操作系统必须在改动过页表之后刷新高速缓冲以确保两者一致。 通过简单地重新加载寄存器CR3,我们就可以完成对高速缓冲的刷新操作。
有一种特殊情况,在这种情况下修改页表项不需要刷新页转换高速缓冲,即当不存在页面的表项被修改时, 即使把P标志从0改成1来标注表项对页转换有效,也不需要刷新高速缓冲。因为无效的表项不会被存入高速缓冲中。 所以在把一个页面从磁盘调入内存以使页面存在时,我们不需要刷新页转换高速缓冲。
当启用了分页机制,CPU会首先执行段级保护,然后再处理页级保护。如果CPU在任何一级检测到一个保护违规错误, 则会放弃内存访问并产生一个异常。如果是段机制产生的异常,那么就不会再产生一个页异常。
页级保护不能替代或忽略段级保护。例如,若一个代码段被设定为不可写,那么代码段被分页后, 即使页面的R/W标志被设置成可读可写也不会让页面可写。此时段级保护检查会阻止任何对页面的写操作企图。 页级保护可被用来增强段级保护。例如,一个可读可写数据段被分页,那么页级保护机制可用来对个别页面进行写保护。