分享

内核代码学习==>深入介绍Linux内核(六)

 快乐学习 2006-12-10

深入介绍Linux内核(六)

.......(续第四章)


4.5 保护

保护机制是可靠的多工执行环境所必须的。它可用於保护各个任务免受相互之间的干扰。在软体发展的任何阶段都可以使用段级和页级保护来协助寻找和检测设计问题和错误。当程式对错误记忆体空间执行了一次非期望的引用,保护机制可以阻止这种操作並且报告此类事件。


保护机制可以被用於分段和分页机制。处理器寄存器的2个Bit位元定义了当前执行程式的特权级,称为当前特权级CPL(Current Privilege Level)。在分段和分页位址转換行程中,处理器将对CPL进行验证。


透过设置控制寄存器CR0的PE标志(位元0)可以让处理器工作在保护模式下,从而也就开啟了分段保护机制。一旦进入保护模式,处理器中並不存在明确的控制标志来停止或啟用保护机制。不过基於特权级的保护机制部分可以透过把所有段选择符号和段描述符号的特权级都设置为0级来隐含地关闭。这种处理方式可以在段之间禁止特权级保护壁垒,但是其他段长度和段类型检查等保护机制仍然起作用。

设置控制寄存器CR0的PG标志(位元31)可以开啟分页机制,同时也开啟了分页保护机制。同樣,处理器中也沒有相关的标志用来在分页开啟条件下禁止或开啟页级保护机制。但是透过设置每个页目錄项和页表项的读/写(R/W)标志和用戶/超级用戶(U/S)标志,我们可以禁止页级保护机制。设置这两个标志可以使得每个页面都可以被任意读/写,因此实际上也就禁止了页级保护。


对於分段级保护机制,处理器使用段寄存器中选择符号(RPL和CPL)和段描述符号中各个栏位执行保护验证。对於分页机制,则主要利用页目錄和页表项中的R/W和U/S标志来实现保护操作。


4.5.1 段级保护

在保护模式下,80X86提供了段级和页级保护机制。这种保护机制根据特权级(4级段保护和2级页保护)提供了对某些段和页面的存取限制能力。例如,作业系统代码和资料存放在要比普通应用程式具有高特权级的段中。此后处理器的保护机制将会限制应用程式只能按照受控制的和规定的方式存取作业系统的代码和资料。

当使用保护机制时,每个记忆体引用都将受到检察以验证记忆体引用符合各种保护要求。因为检查操作是与位址变換同时平行作业,所以处理器效能並沒有受到影响。所进行的保护检查可分为以下几类:

▓ 段界限检查;

▓ 段类型检查;

▓ 特权级检查;

▓ 可定址范围限制;

▓ 行程入口鲇限制;

▓ 指令集限制。


所有违反保护的操作都将导致產生一个异常。下面各节描述保护模式下的保护机制。


段限长Limit检查


段描述符号的段限长(或称段界限)栏位用於防止程式或行程定址到段外记忆体位置。段限长的有效值依赖於颗粒度G标志的设置状态。对於资料段,段限长还与标志 E (扩展方向)和标志 B (预设堆疊指标大小和/或上界限)有关。E标志是资料段类型的段描述符号中类型栏位的一个Bit。

当G标志清除零时(位元组颗粒度) ,有效的段长度是20位的段描述符号中段限长栏位Limit的值。在这种情況下,Limit的范围从0到0xFFFFF(1MB)。当G标志置位元时(4KB页颗粒度) ,处理器把Limit栏位的值乘上一个因数4 K。 在这种情况下,有效的Limit范围是从0xFFFFFFFF(4GB)。请注意,当设置了G标志时,段偏栘(位址)的低 12 位不会与Limit进行对照检查。例如,当段限长Limit等於0时,偏移值0到0xFFF仍然是有效的。

除了下扩段以外的所有段类型,有效Limit的值是段中允许被存取的最后一个位址,它要比段长度小l个位元组。任何超出段限长栏位指定的有效位址范围都将导致產生一个一般保护異常。

对於下扩资料段,段限长具有同樣的功能,但其含义不同。这裡,段限长指定了段中最后一个不允许存取的位址,因此在设置了B标志的情況下,有效偏移范围是从(有效段偏移+1)到0xFFFF FFFF;当B清除零时,有效偏移范围是从(有效段偏移+1)到0xFFFF。当下扩段的段限长为0时,段会有最大长度。

除了对段限长进行检查,处理器也会检查描述符号表的长度。GDTR、IDTR和LDTR寄存器中包含有16位的限长值,处理器用它来防止程式在描述符号表的外面选择描述符号。描述符号表的限长值指明了表中最后一个有效位元组。因为每个描述符号是8位元组长,因此含有N个描述符号项的表应该具有限值8N-l。

选择符号可以具有0值。这樣的选择符号指向GDT表中的第一个不用的描述符号项。尽管这个空选择符号可以被载入进一个段寄存器中,但是任何使用这种描述符号引用记忆体的企图都将產生一个一般保护性異常。

段类型TYPE检查

除了应用程式码和资料段有描述符号以外,处理器还有系统段和门两种描述符号类型。这些资料结构用於管理任务以及異常和中断。请注意,並非所有描述符号都定义一个段,门描述符号中存放有指向一个行程入口点的指标。段描述符号在两个地方含有类型资讯,即描述符号中的 S 标志和类型栏位TYPE。处理器利用这些资讯对于非法使用段或闸导致的程式设计错误进行检测。

当操作段选择符号和段描述符号时,处理器会随时检查类型资讯。主要在以下两种情况下检查类型资讯:


⑴ 当一个描述符号的选择符号载入进一个段寄存器中。此时某些段寄存器只能存放特定类型的描述符号,例如:



● CS暂存器中只能被载入进一个可执行段的选择符号;

● 不可读可执行段的选择符号不能被载入进资料段寄存器中;

● 只有可写资料段的选择符号才能被载入进SS寄存器中。

⑵ 当指令存取一个段,而该段的描述符号已经载入进段寄存器中。指令只能使用某些预定义的方法来存取某些段。


● 任何指令不能写一个可执行段;

● 任何指令不能写一个可写位沒有置位的资料段;

● 任何指令不能读一个可执行段,除非可执行段设置了可读标志。



特权级

处理器的段保护机制可以识別4个特权级(或特权层) ,0级到3级。数值越大,特权越小。图4-19示出了这些特权级如何能被解释成保护环形式。环中心(保留给最高级的代码、资料和堆栈)用於含有最重要软体的段,通常用於作业系统核心部分。中间两个环用於较为重要的软体。只使用2各特权级的系统应该使用特权级0和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是l,那麼只有执行CPL为0或l的程式可以存取这个段。


● 非一致代码段(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和位元l中。处理器会把RPL与CPL比较,以确定是否允许存取一个段。即使程式或任务具有足夠的特权级(CPL)来存取一个段,但是如果提供的RPL特权级不足的话存取也将被拒絕。也即如果段选择符号的RPL其数值大於CPL,那麼RPL将覆盖CPL(而使用RPL作为检查比较的特权级),反之也然。即始终取RPL和CPL中数值最大的特权级作为存取段时的比较对象。因此,RPL可用来确保高特权级的代码不会代表应用程式去存取一个段,除非应用程式自己具有存取这个段的许可权。

当段描述符号的段选择符号被载入进一个段寄存器时就会进行特权级检查操作,但用於资料存取的检查方式和那些用於在代码段之间进行程式控制转移的检查方式不一樣。因此下面分两种存取情況来考虑。



4.5.2存取资料段时的特权级检查

为了存取资料段中的运算元,资料段的段选择符号必须被载入进资料段寄存器(DS、ES、FS或GS)或堆栈段寄存器(SS)中。 (可以使用指令MOV、POP、LDS、LES、LFS、LGS和LSS来载入段寄存器)。在把一个段选择符号载入进段寄存器中之前,处理器会进行特权级检查,见图4-20所示。它会把当前执行程式或任务的CPL、段选择符号的RPL和段描述符号的DPL进行比较。只有当段的DPL数值大於或等於CPL和RPL两者时,处理器才会把选择符号载入进段寄存器中。否则就会產生一个一般保护異常,並且不载入段选择符号。






可程式或任务可定址的区域随著其CPL改变而变化,当CPL是0时,此时所有特权级上的资料段都可被存取:当CPL是1时,只有在特权级1到3的资料段可被存取:当CPL是3时,只有处於特权级3的资料段可被存取。

另外,有可能会把资料保存在代码段中。例如,当代码和资料是在ROM中时。因此,有些时候我们会需要存取代码段中的资料。此时可以使用以下方法来存取代码段中的资料:

1.把非一致可读代码段的选择符号载入进一个资料段寄存器中。
2.把一致可读代码段的选择符号载入进一个资料段寄存器中。
3.使用代码段覆盖首码(CS)来读取一个选择符号已经在CS寄存器中的可读代码段。

存取资料段的相同规则也适用方法l。方法2则是总是有效的,因为一致代码段的特权级等同於CPL,而不管代码段的DPL。方法3也总是有效的,因为CS寄存器选择的代码的DPL与CPL相同。


当使用堆栈段选择符号载入SS段寄存器时也会执行特权级检查。这裡与堆栈段相关的所有特权级必须与CPL匹配。也即,CPL、堆栈段选择符号的RPL以及堆栈段描述符号的DPL都必须相同。如果RPL或DPL与CPL不同,处理器就会產生一个一般保护性異常。


4.5.3 代码段之间转移控制时的特权级检查

对于将程式控制权从一个代码段转移到另一个代码段,目标代码段的段选择符号必须载入进代码段寄存器(CS)中。作为这个载入行程的一部分,处理器会检测目标代码段的段描述符号並执行各种限长、类型和特权级检查。如果这检查都透过了,则目标代码段选择符号就会载入进CS寄存器,於是程式的控制权就被转移到新代码段中,程式将从EIP寄存器指向的指令处开始执行。


程式的控制转移使用指令JMP、RET、INT和IRET以及異常和中断机制来实现。異常和中断是一些特殊实现,将在后面描述,本节主要說明JMP、CALL和RET指令的实现方法。JMP或CALL指令可以利用一下四种方法之一来引用另外一个代码段:

▓ 目标运算元含有目标代码段的段选择符号;

▓ 目标运算元指向一个呼叫门描述符号,而该描述符号中含有目标代码段的选择号;

▓ 目标运算元指向一个TSS,而该TSS中含有目标代码段的选择符号;

▓ 目标运算元指向一个任务门,该任务门指向一个TSS,而该TSS含有目标代码段的选择符号;

下面描述前两种参照类型,后两种将放在有关任务管理一节中进行说明。

直接呼叫或跳转到代码段

JMP、CALL和RET指令的近转移形式只是在当前代码段中执行程式控制转移,因此不会执行特权级检查。JMP、CALL或RET指令的远转移形式会把控制转移到另外一个代码段中,因此处理器一定会之醒特权级检查。

当不透过呼叫门把程式控制权转移到另外一个代码段时,处理器会验证4种特权级和类型资讯,见图4-21所示:






▓ 当前特权级CPL。(这里,CPL是执行呼叫的代码段的特权级,即含有执行呼叫或跳转城市的代码段的CPL。)

▓ 含有被呼叫行程的目的代码段段描述符号中的描述符号特权级DPL。

▓ 目的代码段的段选择符号中的请求特权级RPL o

▓ 目的代码段描述符号中的一致性标志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 Gatc) ,类型TYPE=14;

▓ 任务门(Task Gate),类型TYPE=5。

任务门用於任务切換,将在后面任务管理一节說明。陷阱门和中断门是呼叫门的特殊类,专门用於呼叫異常和中断的处理程式,这将在下一节进行說明。本节仅說明呼叫门的使用方法。

呼叫门用於在不同特权级之间实现受控的程式控制转移。它们通常仅用於使用特权级保护机制的作业系统中。图4-22 给出了呼叫门描述符号的格式。呼叫门描述符号可以存放在GDT,或LDT中,但是不能放在中断描述符号表IDT中。一个呼叫门主要具有一下几个功能:

▓ 指定要存取的代码段:

▓ 在指定代码段中定义行程(程式)的一个入口点;

▓ 指定存取行程的呼叫者需具备的特权级;

▓ 若会发生堆栈切換,它会指定在堆栈之间需要复制的可选参数个数;

▓ 指明呼叫门描述符号是否有效,






呼叫门中的段选择符号栏位指定要存取的代码段。偏移值栏位指定段中入口点。这个入口点通常是指定行程的第一条指令。DPL栏位指定呼叫门的特权级,从而指定透过呼叫门存取特定行程所要求的特权级。标志P指明呼叫门描述符号是否有效。参数个数栏位(Param Count)指明在发生堆栈切換时从呼叫者堆栈复制到新堆栈中的参数个数。Linux內核中並沒有用到呼叫门。这裡对呼叫门进行說明是为下一节介绍利用中断和異常门进行处理作準备。


透过呼叫门存取代码段

为了存取呼叫门,我们需要为CALL或JMP指令的运算元提供一个远指标。该指标中的段选择符号用於指定呼叫门,而指标的偏移值虽然需要但CPU並不会用它。该偏移值可以设置为任意值。见图4-23所示。

当处理器存取呼叫门时,它会使用呼叫门中的段选择符号来定位目的代码段的段描述符号。然后CPU会把代码段描述符号的基底位址与呼叫门中的偏栘值进行组合,形成代码段中指定程式入口的線性位址。






透过呼叫门进行程式控制转移时,CPU会对4种不同的特权级进行检查,以确定控制转移的有效性,见图4-24所示。


▓ 当前特权级CPL;

▓ 呼叫门选择符号中的请求特权级RPL;

▓ 呼叫门描述符号中的描述符号特权级DPL;

▓ 目的代码段描述符号中的DPL;


另外,目的代码段描述符号中的一致性标志C也将受到检查。








使用CALL指令和JMP指令分别具有不不同的特权级检测规则,见表4-5所示。呼叫门描述符号的DPL栏位指明了呼叫程式能夠存取呼叫门的数值最大的特权级(最小特权级),即为了存取呼叫门,呼叫者程式的特权级CPL必须小於或等於呼叫门的DPL。呼叫门段选择符号的RPL也需同呼叫这的CPL遵守同樣的规则,即RPL也必须小於或等於呼叫门的DPL。







如果呼叫者与呼叫门之间的特权级检查成功透过,CPU就会接著把呼叫者的CPL与代码段描述符号的DPL进行比较检查。在这方面,CALL指令和JMP指令的检查规则就不同了。只有CALL指令可以透过呼叫门把程式控制转移到特权级更高的非一致性代码段中,即可以转移到DPL小於CPL的非一致性代码段中去执行。而JMP指令只能透过呼叫门把控制转移到DPL等於CPL的非一致性代码段中。但CALL指令和JMP指令都可以把控制转移到更高特权级的一致性代码段中,即转移到DPL小於或等於CPL的一致性代码段中。



如果一个呼叫把控制转移到了更高特权级的非一致性代码段中,那麼CPL就会被设置为目的代码段的DPL值,並且会引起堆栈切換。但是如果一个呼叫或跳转把控制转移到更高级別的一致性代码段上,那麼CPL不会改变,並且也不会引起堆栈切換。

呼叫门可以让一个代码段中的行程被不同特权级的程式存取。例如,位於一个码段中的作业系统代码可能含有作业系统自身和应用软体都允许存取的代码(比如处理字元I/O的代码) 。因此可以为这些行程设置一个所有特权级代码都能存取的呼叫门。另外可以专门为仅用於作业系统的代码设置一些更高特权级的呼叫门。


堆栈切換

每当呼叫门用於把程式控制转移到一个更高级別的非一致性代码段时,CPU会自动切換到目的代码段特权级的堆栈去。执行堆栈切換操作的目的是为了防止高权级程式由於堆栈空间不足而引起崩溃,同时也为了防止低特权级程式透过共用的堆栈有意或无意地干扰高特权级的程式。

每个任务必须定义最多4个堆栈。一个用於执行在特权级3的应用程式码,其他分別用於用到的特权级2、l和0。如果一个系统中只使用了3 和0 两个特权级,那麼每个任务就只需设置两个堆栈。每个堆栈都位於不同的段中,並且使用段选择符号和段中偏移值指定。

当特权级3的程式在执行时,特权级3的堆栈的段选择符号和堆栈指标会被分別存放在SS和ESP中,並且在发生堆栈切換时被保存在被呼叫行程的堆栈上。

特权级0、1和2的堆疊的初始指标值都存放在当前执行任务的了SS段中。TSS段中这些指标都是唯读值。在任务执行时CPU並不会修改它们。当呼叫更高特权级程式时,CPU才用它们来建立新堆栈。当从呼叫行程返回时,相应堆栈就不存在了。下一次再呼叫该行程时,就又会再次使用TSS中的初始指标值建立一个新堆栈。


作业系统需要负责为所有用到的特权级建立堆栈和堆栈段描述符号,並且在压务的TSS中设置初始指标值。每个堆栈必须可读可写,並且具有足夠的空间来存放以下一些资讯:

▓ 呼叫行程的SS、ESP、CS和EIP寄存器內容:

▓ 被呼叫行程的参数和临时变数所需使用的空间。

▓ 当隐含呼叫一个異常或中断行程时标志寄存器EFLAGS和出错码使用的空间。


由于一个行程可以呼叫其他行程,因此每个堆栈必须有足够大的空间来容纳多框(多套)上述资讯。

当透过呼叫门执行一个程序呼叫而造成特权级政变时,CPU就会执行以下步骤切換堆栈並开始在新的特权级上执行被呼叫行程。(见图4-25所示):

1. 使用目的代码段的DPL(即新的CPL)从TSS中选择新堆栈的指标。从当前TSS中读取新堆栈的段选择符号和堆栈指标。在读取堆栈段选择符号、堆栈指标或堆栈段描述符号行程中,任何违反段界限的错误都将导致產生一个无效TSS異常;

2. 检查堆栈段描述符号特权级和类型是否有效,若无效者同样产生一个无效TSS异常。

3. 临时保存SS和ESP寄存器的当前值,把新堆栈的段选择符号和堆栈指标载入到SS和ESP中。然后把临时保存的SS和ESP內容压入新堆栈中。

4. 把呼叫门描述符号中指定参数个数的参数从呼叫行程堆栈复制到新堆栈中。呼叫门中参数个数值最大为31,如果个数为0则表示无参数,不需复制。

5. 把返回指令指标(即当前CS和ElP內容)压入新堆栈。把新(目的)代码段选择符号载入到CS中,同时把呼叫门中偏栘值(新指令指标)载入到ElP中。最后开始执行被呼叫行程。







从被呼叫行程返回

指令RET 用於执行近返回(near return) 、同时特权级返回(far return)和不同特权级的远返回。该指令用於从使用C ALL指令呼叫的行程中返回。近返回仅在当前代码段中转移程式控制权,因此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选择符号载入这个段寄存器。


4.5.4 页级保护

页目錄和页表表项中的读写标志 R/W 和 用戶/超级用戶 标志 U/S 提供了分段机制保护属性的一个子集。分页机制只识別两级许可权。特权级0、1和2被归类为超级用戶级,而特权级3 被作为普通用戶级。普通用戶级的页面可以被标识成唯读,可执行或可读,可写,可执行。超级用戶级的页面对於超级用戶来讲总是可读/可写/可执行的,但普通用戶不可存取,见表4-6所示。对於分段机制,在最外层用戶级执行的程式只能存取用戶级的页面,但是在任何超级用戶层(0、1、2)执行的程式不仅可以存取用戶层的页面,也可以存取超级用戶层的页面。与分段机制不同的是,在內层超级用戶级执行的程式对任何页面都具有可读/可写/可执行许可权,包括那些在用戶级标注为唯读/可执行的页面。






正如在整个80X86位址转換机制中分页机制是在分段机制之后实施一樣,页级保护也是在分段机制提供的保护措施之后发挥作用。首先,所有段级保护被检查和测试。如果透过检查,就会再进行页级保护检查。例如,仅当位元组位於级别3上执行的程式可存取的段中,並且处於标志为用戶级页面中时,这个记忆体中的位元组才可被级別3上的程式存取。仅当分段和分页都允许写时,才能对页面执行写操作。如果一个段是读/写类型的段,但是位址对应的相应页面被标注为唯读/可执行,那麼还是不能对页面执行写操作。如果段的类型是唯读/可执行,那麼不管对应页面被赋予何保护属性,页面始终是沒有写许可权的。可见分段和分页的保护机制就像电子線路中的串列線路,其中那个开关沒有合上線路都不会通。

类似地,一个页面的保护属性由页目錄和页表中表项的“串列”或“与操作”构成,见表4-7所示。页表表项中的U/S标志和R/W标志应用于该表项映射的单个页面。页目錄项中的U/S和R/W标志则对该目錄项所映射的所有页面起作用。页目錄和页表的组合保护属性由两者属性的“与”(AND)操作构成,因此保护措施非常严格。





修改页表项的软件问题

本节提供一些有关作业系统软体修改页表项內容所需遵守的规则。分页转换缓冲要求所有系统都须遵守这些规则。为了避免每次记忆体应用都要存取驻留记忆体的页表,从而加快速度,最近使用的線性到实体位址的转換资讯被保存在处理器內的页转換高速缓冲中。处理器在存取记忆体中的页表之前会首先利用缓冲中的资讯。只有当必要的转換资讯不在高速缓冲中时,处理器才会搜寻记忆体中的目录和页表。页转換高速缓冲作用类似於前面描述的加速段转換的寄存存器的影子描述符号寄存器。页转換高速缓冲的另一个术语称为转換查找缓冲TLB
(Translation Lookaside Buffer)。

80X86处理器並沒有维护页转換高速缓冲和页表中资料的相关性,但是需要作业系统软体来确保它们一致。即处理器並不知道什麼时候页表被软体修改过了。因此作业系统必须在改动过页表之后更新高速缓冲以确保两者一致。透过简单地重新载入寄存器CR3,我们就可以完成对高速缓冲的更新操作。

有一种特殊情況,在这种情況下修改页表项不需要更新页转換高速缓冲。也即当不存在页面的表项被修改时,即使是把P标志从0改成1来标注表项对页转換有效,也不需要更新高速缓冲。因为无效的表项不会被存入高速缓冲中。所以在把一个页面从磁片调入记忆体以使页面存在时,我们不需要更新页转換高速缓冲。



4.5.5 组合页级和段级保护

当啟用了分页机制,CPU会首先执行段级保护,然后再处理页级保护。如果CPU在任何一级检测到一个保护违规错误,则会放棄记忆体存取並產生一个异常。如果是段机制產生的異常,那麼就不会再產生一个页異常。

页级保护不能替代或忽略段级保护。例如,若一个代码段被设定为不可写,那麼代码段被分页后,即使页面的R/W标志被设置成可读可写也不会让页面可写。此时段级保护检查会阻止任何对页面的写操作企图。页级保护可被用来增強段级保护。例如,如果一个可读可写资料段被分页,那麼页级保护机制可用来对个别页面进行防写。



4.6 中断和異常处理

中断(Interrupt)和異常(Exception)是指明系统、处理器或当前执行程式(或任务)的某处出现一个事件,该事件需要处理器进行处理。通常,这种事件会导致执行控制被強迫从当前执行程式转移到被称为中断处理程式 (interrupt handler) 或異常处理程式(exception handler)的特殊软体函数或任务中。处理器回应中断或異常所採取的行动被称为中断/異常服务(处理) 。

通常,中断发生在程式执行的随机时刻,以回应硬体发出的信号。系统硬体使用中断来处理外部事件,例如要求为外部设备提供服务。当然,软体也能透过执行INT n指令產生中断。

異常发生在处理器执行一条指令时,检测到一个出错条件时发生,例如被0除的出错条件。处理器可以检测到各种出错条件,包括违反保护机制、页错误以及机器內部错误。

对应用程式和作业系统来說,80X86的中断和異常处理机制可以透明地处理发生的中断和異常事件。当收到一个中断或检测到一个異常时,处理器会自动地把当前正在执行的程式或任务掛起,並开始执行中断或異常处理程式。当处理程式执行完毕,处理器就会恢复並继续执行被中断的程式或任务。被中断程式的恢复行程并不会失去程式执行的连贯性,除非从異常中恢复是不可能的或者中断导致当前执行程式被终止。本节描述保护模式中处理器中断和異常的处理机制。



4.6.1 異常和中断向量


为了有助於处理異常和中断,每个需要被处理器进行特殊处理的处理器定义的異常和中断条件都被赋予了一个标识号,称为向量(vector) 。处理器把赋予異常或中断的向量用作中断描述符号表IDT(Interrupt Descriptor Table)中的一个索引号,来定位一个異常或中断的处理程式入口点位置。

允许的向量号码范围是0到255。其中。到31保留用作80X86处理器定义的異常和中断,不过目前该范围內的向量号並非每个都已定义了功能,未定义功能的向量号将留作今后使用。

范围在32到255的向量号用於用戶定义的中断。这些中断通常用於外部I/O设备,使得这些设备可以透过外部硬体中断机制向处理器发送中断。下表中给出了为80X86定义的異常和NMI中断分配的向量。对於每个異常,该表给出了異常类型以及是否会產生一个错误码並保存在堆栈上。同时还给出了每个预先定义好的異常和NMI中断源。


4.6.2 中段源和異常来源

中断来源

处理器从两种地方接收中断:

▓ 外部(硬体產生)的中断:

▓ 软件產生的中断。

外部中断透过处理器晶片上两个接腳 (INTR和NMI) 接收。当接腳INTR接收到外部发生的中断信号时,处理器就会从系统汇流排上读取外部中段控制器(例如8259A)提供的中断向量号。当接腳NMI接收到信号时,就產生一个非遮罩中断。它使用固定的中断向量号2。任何透过处理器INTR接腳接收的外部中断都被称为可遮罩硬件中断,包括中断向量号0到255。标志寄存器EFLAGS中的lF标志可用来遮罩所有这些硬件中断。

透过在指令中运算元中提供中断向量号,INT n指令可用于从软件中产生中断。例如,指令INT 0x80会执行Linux的系统中断呼叫中断0x80。向量0到255中的任何一个都可以用作INT指令中断号。然后,如果使用了处理器预先定义的NMI向量,那麼处理器对它的回应将与普通方式產生的该中断不同。如果NMI的向量号2用於该指令,就会呼叫NMI的中断处理器程式,但是並不会启动处理器的NMI处理硬件。


注意,EFLAGS中的IF标志不能夠遮罩使用INT指令从软体中產生的中断。

处理器接收的異常也有两个来源:

▓ 处理器检测到的程式错误異常;

▓ 软体產生的異常。


在应用程式或作业系统执行期间,如果处理器检测到程式错误,就会產生一个或多个異常。80X86处理器为其检测到的每个異常定义了一个向量。異常可以被细分为故障(faults) 、陷阱(traps)和中止(aborts) ,见后面說明。

指令INTO、INT 3和BOUND指令可以用来从软体中產生異常。这些指令可以对指令流中指定点执行的特殊異常条件进行检查。例如,INT3指令会產生一个中断点異常。

INT n指令可用於在软体中模拟指定的異常,但有一个限制。如果INT指令中的运算元n是80X86異常的向量号之一,那麼处理器将为该向量号產生一个中断,该中断就会云执行与该向量有关的異常处理程式。但是,因为这实际上是一个中断,因此处理器並不会把一个错误号压入堆栈,即使硬件產生的该向量相关的中断通常会產生一个错误码。对於那些会產生错误码的異常,異常的处理程式会试图从堆栈上弹出错误码。因此,如果使用INT指令来模拟產生一个異常,处理程式则会把EIP(正好处於缺少的错误码位置处)弹出堆栈,从而会造成返回位置错误。


4.6.3 异常分类


根据異常被报告的方式以及导致異常的指令是否能夠被重新执行,異常可被细分成故障(Fault) 、陷阱(Trap)和中止(Abort)。

▓ Fault是一种通常可以被纠正的異常,並且一旦被纠正程式就可以继续执行。当出现一个Fault处理器会把机器状态恢复到產生Fault的指令之前的状态。此时异常处理程式的返回位址会指向產生Fault的指令,而不是其后面一条指令。因此返回后產生Fault的指令将被重新执行。

▓ Trap是一个引起陷阱的指令被执行后立刻会报告的異常。Trap也能夠让程式后任务连贯地执行。Trap处理程式的返回位址指向引起陷阱指令的随后一条指令,因此在返回后会执行下一条指令。

▓ Abort是一种不会总是回报导致異常的指令精确位置的異常,並且不允许导致异常的程式重新继续执行。Abort用於报告严重错误,例如硬体错误以及系统表中存在不一致性或非法值。


4.6.4 程式或任务的重新执行


为了让程式或任务在一个異常或中断处理完之后能重新恢复执行,除了终止(Abort)之外的所有異常都能报告精确的指令位置,並且所有中断保证是在指令边界上发生。

对於故障类異常,处理器產生異常时保存的返回指标指向出错指令。因为,当程式或任务在故障处理程式返回后重新开始执行时,原出错指令会被重新执行。重新执行引发出错的指令通常用於处理存取指令运算元受阻的情況。Fault最常见的一个例子是页面故障(Page-fault)異常。当程式引用不在记忆体中页面上的一个运算元时就会出现这种異常。当页故障異常发生时,異常处理程式可以把该页面载入到记忆体中並透过重新执行出错指令来恢复程式执行。为了确保重新执行对於当前执行程式具有透明性,处理器会保存必要的寄存器和堆栈指标资讯,以使得自己能夠返回到执行出错指令之前的状态。

对於陷阱Trap类異常,处理器產生異常时保存的返回指标指向引起陷阱操作的后一条指令。如果在一条执行控制转移的指令执行期问检测到一个Trap,则返回指令指标会反映出控制的转移情況。例如,如果在执行JMP指令时检测到一个Trap異常,那麼返回指令指标会指向JMP指令的目标位置,而非指向JMP指令随后的一条指令。


中止Abort类異常不支援可靠地重新执行程式或任务。中止異常的处理程式通常用来收集異常发生时有关处理器状态的诊断资讯,並且尽可能恰当地关闭程式和系统。

中断会严格地支援被中断程式的重新执行而不会丟失任何连贯性。中断所保存的返回指令指标指向处理器获取中断时将要执行的下一条指令边界处。如果刚执行的指令有一个重复首码,则中断会在当前重复结束並且暂存器已为下一次重复操作设置好时发生。


4.6.5 开啟和禁止中断


标志寄存器EFLAGS的中断允许标志IF(Interrupt enable Flag)能夠禁止为处理器INTR接腳上收到的可遮罩硬体中断提供服务。当IF=0时,处理器禁止发送到INTR接腳的中断:当IF=1时,则发送到INTR接腳的中断信号会被处理器进行处理。

IF标志並不影响发送到NMI接腳的非遮罩中断,也不影响处理器產生的異常。如同EFLAGS中的其他旗标一樣,处理器在回应硬体重定操作时会清除IF标志(IF=0)。

If旗标可以使用指令STI和CLI来设置或清除。只有当程式的CPL<=IOPL时才可执行这两条指令,否则将引发一般保护性異常。IF旗标也会受一下操作影响:

▓ PUSHF指令会把EFLAGS內容存入堆疊中,並且可以在那裡被修改。而POPF指令可用於把已被修改过的标志內容放入EFLAGS寄存器中。

▓ 任务切換、POPF和IRETA指令会载入EFLAGS寄存器。因此,它们可用来修改IF标志。

▓ 当透过中断门处理一个中断时,IF旗标会被自动清除(复位) ,从而会禁止可遮罩硬体中断。但如果是透过陷阱门来处理一个中断,则IF旗标不会被重定。



4.6.6 異常和中断的优先顺序

如果在一条指令边界有多个異常或中断等待处理时,处理器会按规定的次序对它们进行处理。表4-8给出了異常和中断源类的优先顺序。处理器会首先处理最高优先顺序类中的異常或中断。低优先顺序的異常会被丟棄,而低优先顺序的中断则会保持等待。当中断处理程式返回到產生異常和/或中断的程式或任务时,被丟棄的異常会重新发生。







4.6.7 中断描述符号表


中断描述符号表IDT(Interrupt Descriptor Table)将每个異常或中断向量分別与它们的处理行程联系起来。与GDT和LDT表类似,IDT也是由8位元组长描述符号组成的一个阵列。与GDT不同的是,表中第1项可以包含描述符号。为了构成IDT表中的一个索引值,处理器把異常或中断的向量号*8。因为最多只有256个中断或異常向量,所以IDT无需包含多於256个描述符号。IDT中可以含有少於256个描述符号,因为只有可能发生的異常或中断才需要描述符号。不过IDT中所有空描述符号项应该设置其存在位元(标志)为0。 {

IDT表可以驻留在線性位址空间的任何地方,处理器使用IDTR寄存器来定位IDT表的位置。这个寄存器中含有IDT表32位的基底位址和16位的长度(限长)值,见图4-26所示。IDT表基底位址应该对齐在8位元组边界上以提供处理器的存取效率。限长值是以位元组为单位的IDT表的长度。







指令LIDT和SIDT指令分別用於载入和保存IDTR寄存器的內容。LIDT指令把在记忆体中的限长值和基底位址运算元载入到IDTR寄存器中。该指令仅能由当前特权级CPL是0的代码执行,通常被用於建立IDT时的作业系统初始化代码中。SIDT指令用於把IDTR中的基底位址和限长內容复制到记忆体中。该指令可在任何特权级上执行。
|
如果中断或異常向量引用的描述符号超过了IDT的界限,处理器会產生一个一般保护性異常。


4.6.8IDT描述符号

IDT表中可以存放三种类型的门描述符号:

▓ 中断门(Interrupt gate)描述符号

▓ 陷阱门(Trap gate)描述符号

▓ 任务门(/ask gate)描述符号

图4-27說明了这三种门描述符号的格式。中断门和陷阱门含有一个长指标(即段选择符号和偏移值) ,处理器使用这个长指标把程式执行权转移到代码段中異常或中断的处理行程中。这两个段的主要区別在於处理器操作EFLAGS寄存器IF标志上。IDT中任务门描述符号的格式与GDT和LDT中任务门的格式相同。任务门描述符号中含有一个任务TSS段的选择符号,该任务用於处理異常和/或中断。



4.6.9 異常与中断处理

处理器对異常和中断处理行程的呼叫操作方法与使用CALL指令呼叫程式行程和任务的方法类似。当回应一个異常或中断时,处理器使用異常或中断的向量作为ID7表中的索引。如果索引值指向中断门或陷阱门,则处理器使用舆CALL指令操作呼叫门类似的方法呼叫異常或中断处理行程。如果索引值指向任务门,则处理器使用与CALL指令操作任务门类似的方法进行任务切換,执行異常或中断的处理任务。

异常或中断门引用执行在当前任务上下文中的异常或中断处理行程,见图4-28所示。门中的段选择符号指向GDT或当前LDT中的可执行代码段描述符号。门描述符号中的偏移栏位指向异常或中断处理行程的开始处。







当处理器执行異常或中断处理程序呼叫时会进行以下操作:

▓ 如果处理行程将在高特权级(例如0级)上执行时就会发生堆栈切換操作,堆栈切換行程如下:

● 处理器从当前执行任务的TSS段中得到中断或異常处理行程使用的堆栈的段选择符号和堆栈指标(例如tss.ss0、tss.esp0)。然后处理器会把被中断程式(或任务)的堆栈选择符号和堆栈指标压入新堆栈中,见图4-29所示。

● 接著处理器会把EFLAGS、CS和EIP寄存器的当前值也压入新堆栈中。

● 如果異常会產生一个错误号,那麼该错误号也会被最后压入新堆栈中。

▓ 如果处理行程将在被中断任务同一个特权级上执行,那麼:

● 处理器把EFLAGS、CS和EIP寄存器的当前值保存在当前堆栈上。

● 如果異常会產生一个错误号,那麼该错误号也会被最后压入新堆栈中。







为了从中断处理行程中返回,处理行程必须使用IRET指令。IRET指令与RET指令类似,但IRET 还会把保存的寄存器内容恢复到EFLAGS中。不过只有当CPL是0时才会恢复EFLAGS中的IOPL栏位,并且只有当CPL〈=IOPL回时IF标志才会被改变。如果当时呼叫中断处理行程发生了堆栈切换,那么在返回时IRET指令会切换回到原来的堆栈。


⑴ 異常和中断处理行程的保护

異常和中断处理行程的特权级保护机制与透过呼叫门呼叫普透行程类似。处理器不允许把控制转移到比CPL更低特权级代码段的中断处理行程中,否则将產生一个一般保护性異常。另外,中断和異常的保护机制在以下方面与一般呼叫门行程不同:

▓ 因为中断异常向量没有RPL,因此在隐式呼叫异常和中断处理行程时不会检查RPL。

▓ 只有当一个異常或中断是利用使用INT n、INT 3或INTO指令產生时,处理器才会检查中断或陷阱门中的DPL。此时CPL必须小於等於门的DPL。这个限制可以防止执行在特权级3的应用程式使用软体中断存取重要的異常处理行程,例如页错误处理行程,假设这些处理行程已被存放在更高特权级的代码段中。对於硬件產生的中断和处理器检测到的異常,处理器会忽略中断门和陷阱门中的DPL。

因为異常和中断通常不会定期发生,因此这些有关特权级的规则有效地增強了異常和中断处理行程能夠执行的特权级限制。我们可以利用以下技术之一来避免违反特权级保护:

▓ 異常或中断处理程式可以存放在一个…致性代码段中。这个技术可以用於只需存取堆栈上资料的处理行程(例如,除出错異常)。如果处理程式需要资料段中的资料,那麼特权级3必须能夠存取这个资料段。但这樣一来就沒有保护可言了。

▓ 处理行程可以放在具有特权级。的非一致代码段中。这种处理行程总是可以执行的,而不管被中断程式或任务的当前特权级CPL。


⑵ 異常或中断处理行程的旗标使用方式

当透过中断门或陷阱门存取一个異常或中断处理行程时,处理器会在把EFLAGS寄存器內容保存到堆栈上之后清除EFLAGS中的TF标志。清除TF标志可以防止指令跟蹤影响中断回应。而随后的IRET指令会用堆栈上的內容恢复EFLAGS的原TF标志。

中断门与陷阱门唯一的区別在於处理器操作EFLAGS寄存器IF标志的方法。当透过中断门存取一个異常或中断处理行程时,处理器会重定IF标志以防止其他中断干扰当前中断处理行程。随后的IRET指令则会用保存在堆栈上的內容恢复EFLAGS寄存器的IF标志。而透过陷阱门存取处理行程並不会影响IF标志。

⑶ 执行中断处理行程的任务

当透过IDT表中任务门存取異常或中断处理行程时,就会导致任务切換。从而可以在一个专用任务中执行中断或異常处理行程。IDT表中的任务门引用GDT中的TSS描述符号。切換到处理行程任务的方法与普通任务切換一樣。由於我们介绍的Linux作业系统沒有使用这种中断处理方式,因此这裡不再赘述。


4.6.10 中断处理任务

当透过IDT中任务门来存取異常或中断处理行程时就会导致任务切換。使用单独的任务来处理異常或中断有如下好处:

▓ 被中断程式或任务的完整上下文会被自动保存:

▓ 在处理異常或中断时,新的TSS可以允许处理行程使用新特权级0的堆栈。在当前特权级0的堆栈已毀坏时如果发生了一个異常或中断,那麼在为中断行程提供一个新特权级0的堆栈条件下,透过任务门存取中断处理行程能夠防止系统崩溃;

▓ 透过使用单独的LDT给中断或異常处理任务独立的位址空间,可以把它与其他任务隔离开来。

使用独立任务处理異常或中断的不足之处是:在任务切換时必须对大量机器状态进行保存,使得它此使用中断门的回应速度要慢,导致中断延时增加。

IDT中的任务门会引用GDT中的TSS描述符号,图4-30所示。切換到控制码任务的行程与普通任务切換行程相同。到被中断任务的反向链结会被保存在控制码任务TSS的前一任务链结栏位中。如果一个異常会產生一个出错码,则该出错码会被复制到新任务堆栈上。

当異常或中断控制码任务用於作业系统中时,实际上有两种分派调度任务的机制:作业系统软体调度和处理器中断机制的硬体调度。使用软件调度方法时需要考虑到中断开啟时採用中断处理任务。







4.6.11 错误码

当異常条件与一个特定的段相关时,处理器会把一个错误码压入異常处理行程的堆栈上。出错码的格式见图4-3 l所示。错误码很象一个段选择符号,但是最低3Bit不是TI和RPL栏位,而是以下3个旗标:

▓ 位元0是外部事件EXT(External event)标志。当设置位元时,表示执行程式以外的事件造成了異常,例如硬件中断。

▓ 位元l是描述符号位置IDT(Descriptor location)标志。当该位置位元时,表示错误码的索引部分指向1DT中的一个门描述符号。当该位元重定时,表示索引部分指向GDT或LDT中的一个段描述符号。

▓ 位元2是GDT/LDT表选择标志TI。只有当位1的IDT=0才有用。当该TI=l时,表示错误码的索引部分指向LDT中的一个描述符号。当TI=0时,說明错误码中的索引部分指向GDT表中的一个描述符号。







段选择索引栏位提供了错误码引用的IDT、GDT或者当前LDT中段或闸描述符号的索引值。在某些情況下错误码是空的(即低16位全0)。空错误码表示错误不是由於引用某个特定段造成,或者是在操作中引用了一个空段描述符号。

页故障(Page-fault)異常的错误码格式与上面的不同,见图4-32所示。只有最低3个Bit位有用,它们的名称与页表项中的最后三位相同(U/S、W/R、P)。含义和作用分別是:

▓ 位元0 (P) ,異常是由於页面不存在或违反存取特权而引发。P=0,表示页不存在;P=l表示违反页级保护许可权。

▓ 位元l (W/R),異常是由於记忆体读或写操作引起。 W/R=0,表示由读操作引起;W/R=l,表示由写操作引起。

▓ 位2 (U/S) ,发生異常时CPU执行的代码级別o。U/S=0,表示CPU正在执行超级用戶代码:U/S=l,表示CPU正在执行一般用戶代码。

另外,处理器还会把引起页面故障異常所存取用的線性位址存放在CR2中。页出错異常处理程式可以使用这个位址来定位相关的页目錄和页表项。






注意,错误不会把IRET指令自动地弹出堆栈,因此中断处理程式在返回之前必须清除堆栈上的错误码。另外,虽然处理產生的某些異常会產生错误码并会自动地保存到处理行程的堆栈中,但是外部硬件中断或者程式执行INT n指令产生的異常並不会把错误码压入堆栈中。



注译:

Stack - 堆叠 或者称呼为 堆栈
Segment - 段
Flag - 旗标 或者称呼为 标志
暂存器又称呼为寄存器
Trap Gate: 陷阱门或称呼为 活板门

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多