深入介紹Linux內核(六)
.......(續第四章)
4.5 保護
保護機制是可靠的多工執行環(huán)境所必須的。它可用於保護各個(gè)任務(wù)免受相互之間的干擾。在軟體發(fā)展的任何階段都可以使用段級和頁(yè)級保護來(lái)協(xié)助尋找和檢測設計問(wèn)題和錯誤。當程式對錯誤記憶體空間執行了一次非期望的引用,保護機制可以阻止這種操作並且報告此類(lèi)事件。
保護機制可以被用於分段和分頁(yè)機制。處理器寄存器的2個(gè)Bit位元定義了當前執行程式的特權級,稱(chēng)為當前特權級CPL(Current Privilege Level)。在分段和分頁(yè)位址轉換行程中,處理器將對CPL進(jìn)行驗證。
透過(guò)設置控制寄存器CR0的PE標志(位元0)可以讓處理器工作在保護模式下,從而也就開(kāi)啟了分段保護機制。一旦進(jìn)入保護模式,處理器中並不存在明確的控制標志來(lái)停止或啟用保護機制。不過(guò)基於特權級的保護機制部分可以透過(guò)把所有段選擇符號和段描述符號的特權級都設置為0級來(lái)隱含地關(guān)閉。這種處理方式可以在段之間禁止特權級保護壁壘,但是其他段長(cháng)度和段類(lèi)型檢查等保護機制仍然起作用。
設置控制寄存器CR0的PG標志(位元31)可以開(kāi)啟分頁(yè)機制,同時(shí)也開(kāi)啟了分頁(yè)保護機制。同樣,處理器中也沒(méi)有相關(guān)的標志用來(lái)在分頁(yè)開(kāi)啟條件下禁止或開(kāi)啟頁(yè)級保護機制。但是透過(guò)設置每個(gè)頁(yè)目錄項和頁(yè)表項的讀/寫(xiě)(R/W)標志和用戶(hù)/超級用戶(hù)(U/S)標志,我們可以禁止頁(yè)級保護機制。設置這兩個(gè)標志可以使得每個(gè)頁(yè)面都可以被任意讀/寫(xiě),因此實(shí)際上也就禁止了頁(yè)級保護。
對於分段級保護機制,處理器使用段寄存器中選擇符號(RPL和CPL)和段描述符號中各個(gè)欄位執行保護驗證。對於分頁(yè)機制,則主要利用頁(yè)目錄和頁(yè)表項中的R/W和U/S標志來(lái)實(shí)現保護操作。
4.5.1 段級保護
在保護模式下,80X86提供了段級和頁(yè)級保護機制。這種保護機制根據特權級(4級段保護和2級頁(yè)保護)提供了對某些段和頁(yè)面的存取限制能力。例如,作業(yè)系統代碼和資料存放在要比普通應用程式具有高特權級的段中。此后處理器的保護機制將會(huì )限制應用程式只能按照受控制的和規定的方式存取作業(yè)系統的代碼和資料。
當使用保護機制時(shí),每個(gè)記憶體引用都將受到檢察以驗證記憶體引用符合各種保護要求。因為檢查操作是與位址變換同時(shí)平行作業(yè),所以處理器效能並沒(méi)有受到影響。所進(jìn)行的保護檢查可分為以下幾類(lèi):
▓ 段界限檢查;
▓ 段類(lèi)型檢查;
▓ 特權級檢查;
▓ 可定址范圍限制;
▓ 行程入口鲇限制;
▓ 指令集限制。
所有違反保護的操作都將導致產(chǎn)生一個(gè)異常。下面各節描述保護模式下的保護機制。
段限長(cháng)Limit檢查
段描述符號的段限長(cháng)(或稱(chēng)段界限)欄位用於防止程式或行程定址到段外記憶體位置。段限長(cháng)的有效值依賴(lài)於顆粒度G標志的設置狀態(tài)。對於資料段,段限長(cháng)還與標志 E (擴展方向)和標志 B (預設堆疊指標大小和/或上界限)有關(guān)。E標志是資料段類(lèi)型的段描述符號中類(lèi)型欄位的一個(gè)Bit。
當G標志清除零時(shí)(位元組顆粒度) ,有效的段長(cháng)度是20位的段描述符號中段限長(cháng)欄位Limit的值。在這種情況下,Limit的范圍從0到0xFFFFF(1MB)。當G標志置位元時(shí)(4KB頁(yè)顆粒度) ,處理器把Limit欄位的值乘上一個(gè)因數4 K。 在這種情況下,有效的Limit范圍是從0xFFFFFFFF(4GB)。請注意,當設置了G標志時(shí),段偏栘(位址)的低 12 位不會(huì )與Limit進(jìn)行對照檢查。例如,當段限長(cháng)Limit等於0時(shí),偏移值0到0xFFF仍然是有效的。
除了下擴段以外的所有段類(lèi)型,有效Limit的值是段中允許被存取的最后一個(gè)位址,它要比段長(cháng)度小l個(gè)位元組。任何超出段限長(cháng)欄位指定的有效位址范圍都將導致產(chǎn)生一個(gè)一般保護異常。
對於下擴資料段,段限長(cháng)具有同樣的功能,但其含義不同。這裡,段限長(cháng)指定了段中最后一個(gè)不允許存取的位址,因此在設置了B標志的情況下,有效偏移范圍是從(有效段偏移+1)到0xFFFF FFFF;當B清除零時(shí),有效偏移范圍是從(有效段偏移+1)到0xFFFF。當下擴段的段限長(cháng)為0時(shí),段會(huì )有最大長(cháng)度。
除了對段限長(cháng)進(jìn)行檢查,處理器也會(huì )檢查描述符號表的長(cháng)度。GDTR、IDTR和LDTR寄存器中包含有16位的限長(cháng)值,處理器用它來(lái)防止程式在描述符號表的外面選擇描述符號。描述符號表的限長(cháng)值指明了表中最后一個(gè)有效位元組。因為每個(gè)描述符號是8位元組長(cháng),因此含有N個(gè)描述符號項的表應該具有限值8N-l。
選擇符號可以具有0值。這樣的選擇符號指向GDT表中的第一個(gè)不用的描述符號項。盡管這個(gè)空選擇符號可以被載入進(jìn)一個(gè)段寄存器中,但是任何使用這種描述符號引用記憶體的企圖都將產(chǎn)生一個(gè)一般保護性異常。
段類(lèi)型TYPE檢查
除了應用程式碼和資料段有描述符號以外,處理器還有系統段和門(mén)兩種描述符號類(lèi)型。這些資料結構用於管理任務(wù)以及異常和中斷。請注意,並非所有描述符號都定義一個(gè)段,門(mén)描述符號中存放有指向一個(gè)行程入口點(diǎn)的指標。段描述符號在兩個(gè)地方含有類(lèi)型資訊,即描述符號中的 S 標志和類(lèi)型欄位TYPE。處理器利用這些資訊對于非法使用段或閘導致的程式設計錯誤進(jìn)行檢測。
當操作段選擇符號和段描述符號時(shí),處理器會(huì )隨時(shí)檢查類(lèi)型資訊。主要在以下兩種情況下檢查類(lèi)型資訊:
⑴ 當一個(gè)描述符號的選擇符號載入進(jìn)一個(gè)段寄存器中。此時(shí)某些段寄存器只能存放特定類(lèi)型的描述符號,例如:
● CS暫存器中只能被載入進(jìn)一個(gè)可執行段的選擇符號;
● 不可讀可執行段的選擇符號不能被載入進(jìn)資料段寄存器中;
● 只有可寫(xiě)資料段的選擇符號才能被載入進(jìn)SS寄存器中。
⑵ 當指令存取一個(gè)段,而該段的描述符號已經(jīng)載入進(jìn)段寄存器中。指令只能使用某些預定義的方法來(lái)存取某些段。
● 任何指令不能寫(xiě)一個(gè)可執行段;
● 任何指令不能寫(xiě)一個(gè)可寫(xiě)位沒(méi)有置位的資料段;
● 任何指令不能讀一個(gè)可執行段,除非可執行段設置了可讀標志。
特權級
處理器的段保護機制可以識別4個(gè)特權級(或特權層) ,0級到3級。數值越大,特權越小。圖4-19示出了這些特權級如何能被解釋成保護環(huán)形式。環(huán)中心(保留給最高級的代碼、資料和堆棧)用於含有最重要軟體的段,通常用於作業(yè)系統核心部分。中間兩個(gè)環(huán)用於較為重要的軟體。只使用2各特權級的系統應該使用特權級0和3。
處理器利用特權等級來(lái)防止執行較低特權級的程式或任務(wù)存取具有較高特權級的那一個(gè)段,
除非是在受控的條件下。當處理器檢測到一個(gè)違反特權級的操作時(shí),它就會(huì )產(chǎn)生一個(gè)一般保護性異常。
為了在各個(gè)代碼段和資料段之間進(jìn)行特權級檢測處理,處理器可以識別以下三種類(lèi)型的特權等級:
▓ 當前特權等級CPL(Current Privilege Level)。CPL是當前正在執行程式或任務(wù)的特權級。它存放存CS 和SS段寄存器的位元0和位元1 中。通常,CPL等於當前代碼段的特權級。當程式把控制轉移到另一個(gè)具有不同特權級的代碼段中時(shí),則處理器就對處理器就會(huì )改變CPL。當存取一個(gè)一致性(conforming)代碼段時(shí),則處理器對CPL的設置有些不同。特權級值高於(即低特權級)或等於一致代碼段DPL的任何段都可以存取一致代碼段。並且當處理器存取一個(gè)特權級不同於CPL的一致代碼段時(shí),CPL並不會(huì )被修改成一致代碼段的DPL。
▓ 描述符號特權級DPL(Descriptor Privilege Level)。 DPL是一個(gè)段或閘的特權級。它存放在段或閘描述符號的DPL欄位中。正當前執行代碼段試圖存取一個(gè)段或閘時(shí),段或閘的DPL會(huì )用來(lái)與CPL以及段或閘選擇符號中的RPL(見(jiàn)下面說(shuō)明)作比較。根據被存取的段或閘的類(lèi)型不同,DPL也有不同的含義:
● 資料段(Data Segment)。其DPL指出允許存取本資料段的程式或任務(wù)應具有的最大特權級數值。例如,如果資料段的特權級DPL是l,那麼只有執行CPL為0或l的程式可以存取這個(gè)段。
● 非一致代碼段(Nonconforming code segment) (不使用呼叫門(mén))。其DPL指出程式或任務(wù)存取該段必須具有的特權級。例如,如果某個(gè)非一致代碼段的DPL是0,那麼只有執行在CPL為0的程式能夠存取這個(gè)段。
● 呼叫門(mén)(Call Gate)。其DPL指出存取呼叫門(mén)的當前執行程式或任務(wù)可處于的最大特權級數值。(這與資料段的存取規則相同。)
● 一致和非一致代碼段(透過(guò)呼叫門(mén)存取)。其DPL指出允許存取本代碼段的程式或任務(wù)應具有的最小特權級數值。例如,如果一致代碼段的DPL是2,那么執行在CPL為0的程式就不能存取這個(gè)代碼段。
● 任務(wù)狀態(tài)段TSS。其DPL指出存取TSS的當前執行程式或任務(wù)可處於的最大特權級數值。(這與資料段的存取規則相同。)
▓ 請求特權級RPL(Request Privilege Level)。RPL是一種賦予段選擇符號的越特權級,它存放在選擇符號的位元0和位元l中。處理器會(huì )把RPL與CPL比較,以確定是否允許存取一個(gè)段。即使程式或任務(wù)具有足夠的特權級(CPL)來(lái)存取一個(gè)段,但是如果提供的RPL特權級不足的話(huà)存取也將被拒絕。也即如果段選擇符號的RPL其數值大於CPL,那麼RPL將覆蓋CPL(而使用RPL作為檢查比較的特權級),反之也然。即始終取RPL和CPL中數值最大的特權級作為存取段時(shí)的比較對象。因此,RPL可用來(lái)確保高特權級的代碼不會(huì )代表應用程式去存取一個(gè)段,除非應用程式自己具有存取這個(gè)段的許可權。
當段描述符號的段選擇符號被載入進(jìn)一個(gè)段寄存器時(shí)就會(huì )進(jìn)行特權級檢查操作,但用於資料存取的檢查方式和那些用於在代碼段之間進(jìn)行程式控制轉移的檢查方式不一樣。因此下面分兩種存取情況來(lái)考慮。
4.5.2存取資料段時(shí)的特權級檢查
為了存取資料段中的運算元,資料段的段選擇符號必須被載入進(jìn)資料段寄存器(DS、ES、FS或GS)或堆棧段寄存器(SS)中。 (可以使用指令MOV、POP、LDS、LES、LFS、LGS和LSS來(lái)載入段寄存器)。在把一個(gè)段選擇符號載入進(jìn)段寄存器中之前,處理器會(huì )進(jìn)行特權級檢查,見(jiàn)圖4-20所示。它會(huì )把當前執行程式或任務(wù)的CPL、段選擇符號的RPL和段描述符號的DPL進(jìn)行比較。只有當段的DPL數值大於或等於CPL和RPL兩者時(shí),處理器才會(huì )把選擇符號載入進(jìn)段寄存器中。否則就會(huì )產(chǎn)生一個(gè)一般保護異常,並且不載入段選擇符號。
可程式或任務(wù)可定址的區域隨著(zhù)其CPL改變而變化,當CPL是0時(shí),此時(shí)所有特權級上的資料段都可被存?。寒擟PL是1時(shí),只有在特權級1到3的資料段可被存?。寒擟PL是3時(shí),只有處於特權級3的資料段可被存取。
另外,有可能會(huì )把資料保存在代碼段中。例如,當代碼和資料是在ROM中時(shí)。因此,有些時(shí)候我們會(huì )需要存取代碼段中的資料。此時(shí)可以使用以下方法來(lái)存取代碼段中的資料:
1.把非一致可讀代碼段的選擇符號載入進(jìn)一個(gè)資料段寄存器中。
2.把一致可讀代碼段的選擇符號載入進(jìn)一個(gè)資料段寄存器中。
3.使用代碼段覆蓋首碼(CS)來(lái)讀取一個(gè)選擇符號已經(jīng)在CS寄存器中的可讀代碼段。
存取資料段的相同規則也適用方法l。方法2則是總是有效的,因為一致代碼段的特權級等同於CPL,而不管代碼段的DPL。方法3也總是有效的,因為CS寄存器選擇的代碼的DPL與CPL相同。
當使用堆棧段選擇符號載入SS段寄存器時(shí)也會(huì )執行特權級檢查。這裡與堆棧段相關(guān)的所有特權級必須與CPL匹配。也即,CPL、堆棧段選擇符號的RPL以及堆棧段描述符號的DPL都必須相同。如果RPL或DPL與CPL不同,處理器就會(huì )產(chǎn)生一個(gè)一般保護性異常。
4.5.3 代碼段之間轉移控制時(shí)的特權級檢查
對于將程式控制權從一個(gè)代碼段轉移到另一個(gè)代碼段,目標代碼段的段選擇符號必須載入進(jìn)代碼段寄存器(CS)中。作為這個(gè)載入行程的一部分,處理器會(huì )檢測目標代碼段的段描述符號並執行各種限長(cháng)、類(lèi)型和特權級檢查。如果這檢查都透過(guò)了,則目標代碼段選擇符號就會(huì )載入進(jìn)CS寄存器,於是程式的控制權就被轉移到新代碼段中,程式將從EIP寄存器指向的指令處開(kāi)始執行。
程式的控制轉移使用指令JMP、RET、INT和IRET以及異常和中斷機制來(lái)實(shí)現。異常和中斷是一些特殊實(shí)現,將在后面描述,本節主要說(shuō)明JMP、CALL和RET指令的實(shí)現方法。JMP或CALL指令可以利用一下四種方法之一來(lái)引用另外一個(gè)代碼段:
▓ 目標運算元含有目標代碼段的段選擇符號;
▓ 目標運算元指向一個(gè)呼叫門(mén)描述符號,而該描述符號中含有目標代碼段的選擇號;
▓ 目標運算元指向一個(gè)TSS,而該TSS中含有目標代碼段的選擇符號;
▓ 目標運算元指向一個(gè)任務(wù)門(mén),該任務(wù)門(mén)指向一個(gè)TSS,而該TSS含有目標代碼段的選擇符號;
下面描述前兩種參照類(lèi)型,后兩種將放在有關(guān)任務(wù)管理一節中進(jìn)行說(shuō)明。
直接呼叫或跳轉到代碼段
JMP、CALL和RET指令的近轉移形式只是在當前代碼段中執行程式控制轉移,因此不會(huì )執行特權級檢查。JMP、CALL或RET指令的遠轉移形式會(huì )把控制轉移到另外一個(gè)代碼段中,因此處理器一定會(huì )之醒特權級檢查。
當不透過(guò)呼叫門(mén)把程式控制權轉移到另外一個(gè)代碼段時(shí),處理器會(huì )驗證4種特權級和類(lèi)型資訊,見(jiàn)圖4-21所示:
▓ 當前特權級CPL。(這里,CPL是執行呼叫的代碼段的特權級,即含有執行呼叫或跳轉城市的代碼段的CPL。)
▓ 含有被呼叫行程的目的代碼段段描述符號中的描述符號特權級DPL。
▓ 目的代碼段的段選擇符號中的請求特權級RPL o
▓ 目的代碼段描述符號中的一致性標志C。它確定了一個(gè)代碼段是非一致代碼段還是一致代碼段。
處理器檢查CPL、RPL和DPL的規則依賴(lài)於一致標志C的設置狀態(tài)。當存取非一致代碼段時(shí)(C=0),呼叫者(程式)的CPL必須等於目的代碼段的DPL,否則將會(huì )產(chǎn)生一般保護異常。指向非一致代碼段的段選擇符號的RPL對檢查所起的作用有限。RPL在數值上必須小於或等於呼叫者的CPL才能使得控制轉移成功完成。當非一致代碼段的段選擇符號被載入進(jìn)CS寄存器中時(shí),特權級欄位不會(huì )改變,即它仍然是呼叫者的CPL。即使段選擇符號的RPL與CPL不同,這也是正確的。
當存取一致代碼段時(shí)(C=1),呼叫者的CPL可以在數值上大於或等於目的代碼段的DPL。僅當CPL< DPL時(shí),處理器才會(huì )產(chǎn)生一般保護異常。對於存取一致代碼段,處理器忽略對RPL的檢查。對於一致代碼段,DPL表示呼叫者對代碼段進(jìn)行成功呼叫可以處於的最低數值特權級。
當程式控制被轉栘到一個(gè)一致代碼段中,CPL並不改變,即使目的代碼段DPL在數值上小於CPL。這是CPL與可能與當前代碼段DPL不相同的唯一一種情況。同樣,由於CPL沒(méi)有改變,因此堆棧也不會(huì )切換。
大多數代碼段都是非一致代碼段。對於這些段,程式的控制權只能轉移到具有相同特權級的代碼段中,除非轉移是透過(guò)一個(gè)呼叫門(mén)進(jìn)行,見(jiàn)下面說(shuō)明。
門(mén)描述符號
為了對具有不同特權級的代碼段提供受控的存取,處理器提供了稱(chēng)為門(mén)描述符號的特殊描述符號集。共有4種門(mén)描述符號:
▓ 呼叫門(mén)(Call Gate),類(lèi)型、TYPE=12;
▓ 陷阱門(mén)或稱(chēng)呼活板門(mén)(Trap Gate),類(lèi)型TYPE=15;
▓ 中斷門(mén)(Interrupt Gatc) ,類(lèi)型TYPE=14;
▓ 任務(wù)門(mén)(Task Gate),類(lèi)型TYPE=5。
任務(wù)門(mén)用於任務(wù)切換,將在后面任務(wù)管理一節說(shuō)明。陷阱門(mén)和中斷門(mén)是呼叫門(mén)的特殊類(lèi),專(zhuān)門(mén)用於呼叫異常和中斷的處理程式,這將在下一節進(jìn)行說(shuō)明。本節僅說(shuō)明呼叫門(mén)的使用方法。
呼叫門(mén)用於在不同特權級之間實(shí)現受控的程式控制轉移。它們通常僅用於使用特權級保護機制的作業(yè)系統中。圖4-22 給出了呼叫門(mén)描述符號的格式。呼叫門(mén)描述符號可以存放在GDT,或LDT中,但是不能放在中斷描述符號表IDT中。一個(gè)呼叫門(mén)主要具有一下幾個(gè)功能:
▓ 指定要存取的代碼段:
▓ 在指定代碼段中定義行程(程式)的一個(gè)入口點(diǎn);
▓ 指定存取行程的呼叫者需具備的特權級;
▓ 若會(huì )發(fā)生堆棧切換,它會(huì )指定在堆棧之間需要復制的可選參數個(gè)數;
▓ 指明呼叫門(mén)描述符號是否有效,
呼叫門(mén)中的段選擇符號欄位指定要存取的代碼段。偏移值欄位指定段中入口點(diǎn)。這個(gè)入口點(diǎn)通常是指定行程的第一條指令。DPL欄位指定呼叫門(mén)的特權級,從而指定透過(guò)呼叫門(mén)存取特定行程所要求的特權級。標志P指明呼叫門(mén)描述符號是否有效。參數個(gè)數欄位(Param Count)指明在發(fā)生堆棧切換時(shí)從呼叫者堆棧復制到新堆棧中的參數個(gè)數。Linux內核中並沒(méi)有用到呼叫門(mén)。這裡對呼叫門(mén)進(jìn)行說(shuō)明是為下一節介紹利用中斷和異常門(mén)進(jìn)行處理作準備。
透過(guò)呼叫門(mén)存取代碼段
為了存取呼叫門(mén),我們需要為CALL或JMP指令的運算元提供一個(gè)遠指標。該指標中的段選擇符號用於指定呼叫門(mén),而指標的偏移值雖然需要但CPU並不會(huì )用它。該偏移值可以設置為任意值。見(jiàn)圖4-23所示。
當處理器存取呼叫門(mén)時(shí),它會(huì )使用呼叫門(mén)中的段選擇符號來(lái)定位目的代碼段的段描述符號。然后CPU會(huì )把代碼段描述符號的基底位址與呼叫門(mén)中的偏栘值進(jìn)行組合,形成代碼段中指定程式入口的線(xiàn)性位址。
透過(guò)呼叫門(mén)進(jìn)行程式控制轉移時(shí),CPU會(huì )對4種不同的特權級進(jìn)行檢查,以確定控制轉移的有效性,見(jiàn)圖4-24所示。
▓ 當前特權級CPL;
▓ 呼叫門(mén)選擇符號中的請求特權級RPL;
▓ 呼叫門(mén)描述符號中的描述符號特權級DPL;
▓ 目的代碼段描述符號中的DPL;
另外,目的代碼段描述符號中的一致性標志C也將受到檢查。
使用CALL指令和JMP指令分別具有不不同的特權級檢測規則,見(jiàn)表4-5所示。呼叫門(mén)描述符號的DPL欄位指明了呼叫程式能夠存取呼叫門(mén)的數值最大的特權級(最小特權級),即為了存取呼叫門(mén),呼叫者程式的特權級CPL必須小於或等於呼叫門(mén)的DPL。呼叫門(mén)段選擇符號的RPL也需同呼叫這的CPL遵守同樣的規則,即RPL也必須小於或等於呼叫門(mén)的DPL。
如果呼叫者與呼叫門(mén)之間的特權級檢查成功透過(guò),CPU就會(huì )接著(zhù)把呼叫者的CPL與代碼段描述符號的DPL進(jìn)行比較檢查。在這方面,CALL指令和JMP指令的檢查規則就不同了。只有CALL指令可以透過(guò)呼叫門(mén)把程式控制轉移到特權級更高的非一致性代碼段中,即可以轉移到DPL小於CPL的非一致性代碼段中去執行。而JMP指令只能透過(guò)呼叫門(mén)把控制轉移到DPL等於CPL的非一致性代碼段中。但CALL指令和JMP指令都可以把控制轉移到更高特權級的一致性代碼段中,即轉移到DPL小於或等於CPL的一致性代碼段中。
如果一個(gè)呼叫把控制轉移到了更高特權級的非一致性代碼段中,那麼CPL就會(huì )被設置為目的代碼段的DPL值,並且會(huì )引起堆棧切換。但是如果一個(gè)呼叫或跳轉把控制轉移到更高級別的一致性代碼段上,那麼CPL不會(huì )改變,並且也不會(huì )引起堆棧切換。
呼叫門(mén)可以讓一個(gè)代碼段中的行程被不同特權級的程式存取。例如,位於一個(gè)碼段中的作業(yè)系統代碼可能含有作業(yè)系統自身和應用軟體都允許存取的代碼(比如處理字元I/O的代碼) 。因此可以為這些行程設置一個(gè)所有特權級代碼都能存取的呼叫門(mén)。另外可以專(zhuān)門(mén)為僅用於作業(yè)系統的代碼設置一些更高特權級的呼叫門(mén)。
堆棧切換
每當呼叫門(mén)用於把程式控制轉移到一個(gè)更高級別的非一致性代碼段時(shí),CPU會(huì )自動(dòng)切換到目的代碼段特權級的堆棧去。執行堆棧切換操作的目的是為了防止高權級程式由於堆??臻g不足而引起崩潰,同時(shí)也為了防止低特權級程式透過(guò)共用的堆棧有意或無(wú)意地干擾高特權級的程式。
每個(gè)任務(wù)必須定義最多4個(gè)堆棧。一個(gè)用於執行在特權級3的應用程式碼,其他分別用於用到的特權級2、l和0。如果一個(gè)系統中只使用了3 和0 兩個(gè)特權級,那麼每個(gè)任務(wù)就只需設置兩個(gè)堆棧。每個(gè)堆棧都位於不同的段中,並且使用段選擇符號和段中偏移值指定。
當特權級3的程式在執行時(shí),特權級3的堆棧的段選擇符號和堆棧指標會(huì )被分別存放在SS和ESP中,並且在發(fā)生堆棧切換時(shí)被保存在被呼叫行程的堆棧上。
特權級0、1和2的堆疊的初始指標值都存放在當前執行任務(wù)的了SS段中。TSS段中這些指標都是唯讀值。在任務(wù)執行時(shí)CPU並不會(huì )修改它們。當呼叫更高特權級程式時(shí),CPU才用它們來(lái)建立新堆棧。當從呼叫行程返回時(shí),相應堆棧就不存在了。下一次再呼叫該行程時(shí),就又會(huì )再次使用TSS中的初始指標值建立一個(gè)新堆棧。
作業(yè)系統需要負責為所有用到的特權級建立堆棧和堆棧段描述符號,並且在壓務(wù)的TSS中設置初始指標值。每個(gè)堆棧必須可讀可寫(xiě),並且具有足夠的空間來(lái)存放以下一些資訊:
▓ 呼叫行程的SS、ESP、CS和EIP寄存器內容:
▓ 被呼叫行程的參數和臨時(shí)變數所需使用的空間。
▓ 當隱含呼叫一個(gè)異?;蛑袛嘈谐虝r(shí)標志寄存器EFLAGS和出錯碼使用的空間。
由于一個(gè)行程可以呼叫其他行程,因此每個(gè)堆棧必須有足夠大的空間來(lái)容納多框(多套)上述資訊。
當透過(guò)呼叫門(mén)執行一個(gè)程序呼叫而造成特權級政變時(shí),CPU就會(huì )執行以下步驟切換堆棧並開(kāi)始在新的特權級上執行被呼叫行程。(見(jiàn)圖4-25所示):
1. 使用目的代碼段的DPL(即新的CPL)從TSS中選擇新堆棧的指標。從當前TSS中讀取新堆棧的段選擇符號和堆棧指標。在讀取堆棧段選擇符號、堆棧指標或堆棧段描述符號行程中,任何違反段界限的錯誤都將導致產(chǎn)生一個(gè)無(wú)效TSS異常;
2. 檢查堆棧段描述符號特權級和類(lèi)型是否有效,若無(wú)效者同樣產(chǎn)生一個(gè)無(wú)效TSS異常。
3. 臨時(shí)保存SS和ESP寄存器的當前值,把新堆棧的段選擇符號和堆棧指標載入到SS和ESP中。然后把臨時(shí)保存的SS和ESP內容壓入新堆棧中。
4. 把呼叫門(mén)描述符號中指定參數個(gè)數的參數從呼叫行程堆棧復制到新堆棧中。呼叫門(mén)中參數個(gè)數值最大為31,如果個(gè)數為0則表示無(wú)參數,不需復制。
5. 把返回指令指標(即當前CS和ElP內容)壓入新堆棧。把新(目的)代碼段選擇符號載入到CS中,同時(shí)把呼叫門(mén)中偏栘值(新指令指標)載入到ElP中。最后開(kāi)始執行被呼叫行程。
從被呼叫行程返回
指令RET 用於執行近返回(near return) 、同時(shí)特權級返回(far return)和不同特權級的遠返回。該指令用於從使用C ALL指令呼叫的行程中返回。近返回僅在當前代碼段中轉移程式控制權,因此CPU僅進(jìn)行界限檢查,對於相同特權級的遠返回,CPU同時(shí)從堆棧中彈出返回代碼段的選擇符號和返回指令指標。由於通常情況下這兩個(gè)指標是CALL指令壓入堆棧中的,因此它們因該是有效的。但是CPU還是會(huì )執行特權級檢查以應付當前行程可能修改指標值或者堆棧出現問(wèn)題時(shí)的情況。
會(huì )發(fā)生特權級政變的遠返回僅允許返回到底特權級程式中,即返回到的代碼段DPL在數值上要大於CPL。CPU會(huì )使用CS寄存器中選擇符號的RPL欄位來(lái)確定是否要求返回到低特權級。如果RPL的數值要比CPL大,就會(huì )執行特權級之間的返回操作。當執行遠返回到一個(gè)呼叫行程時(shí),CPU會(huì )執行以下步驟:
① 檢查保存的CS暫存器中RPL欄位值,以確定在返回時(shí)特權級是否需要改變。
② 彈出並使用被呼叫行程堆疊上的值載入CS和EIP暫存器。在此行程中會(huì )對代碼段描述符號和代碼段選擇符號的RPL進(jìn)行特權級與類(lèi)型檢查。
③ 如果RET指令包含一個(gè)參數個(gè)數運算元並且返回操作會(huì )改變特權級,那麼就在彈出堆棧中CS和EIP值之后把參數個(gè)數值加到ESP寄存器值中,以跳過(guò)(丟棄)被呼叫者堆棧上的參數。此時(shí)ESP寄存器指向原來(lái)保存的呼叫者堆棧的指標SS和ESP。
④ 把保存的SS和ESP值載入到SS和ESP寄存器中,從而切換回呼叫者的堆棧。而此時(shí)被呼叫者堆棧的SS和ESP值被拋棄。
⑤ 如果RET指令包含一個(gè)參數個(gè)數運算元,則把參數個(gè)數值加到ESP寄存器值中,以跳過(guò)(丟棄)呼叫者堆棧上的參數。
⑥ 檢查段寄存器DS、ES、FS和GS的內容。如果其中有指向DPL小於新CPL的段(一致性代碼段除外),那麼CPU就會(huì )用NULL選擇符號載入這個(gè)段寄存器。
4.5.4 頁(yè)級保護
頁(yè)目錄和頁(yè)表表項中的讀寫(xiě)標志 R/W 和 用戶(hù)/超級用戶(hù) 標志 U/S 提供了分段機制保護屬性的一個(gè)子集。分頁(yè)機制只識別兩級許可權。特權級0、1和2被歸類(lèi)為超級用戶(hù)級,而特權級3 被作為普通用戶(hù)級。普通用戶(hù)級的頁(yè)面可以被標識成唯讀,可執行或可讀,可寫(xiě),可執行。超級用戶(hù)級的頁(yè)面對於超級用戶(hù)來(lái)講總是可讀/可寫(xiě)/可執行的,但普通用戶(hù)不可存取,見(jiàn)表4-6所示。對於分段機制,在最外層用戶(hù)級執行的程式只能存取用戶(hù)級的頁(yè)面,但是在任何超級用戶(hù)層(0、1、2)執行的程式不僅可以存取用戶(hù)層的頁(yè)面,也可以存取超級用戶(hù)層的頁(yè)面。與分段機制不同的是,在內層超級用戶(hù)級執行的程式對任何頁(yè)面都具有可讀/可寫(xiě)/可執行許可權,包括那些在用戶(hù)級標注為唯讀/可執行的頁(yè)面。
正如在整個(gè)80X86位址轉換機制中分頁(yè)機制是在分段機制之后實(shí)施一樣,頁(yè)級保護也是在分段機制提供的保護措施之后發(fā)揮作用。首先,所有段級保護被檢查和測試。如果透過(guò)檢查,就會(huì )再進(jìn)行頁(yè)級保護檢查。例如,僅當位元組位於級別3上執行的程式可存取的段中,並且處於標志為用戶(hù)級頁(yè)面中時(shí),這個(gè)記憶體中的位元組才可被級別3上的程式存取。僅當分段和分頁(yè)都允許寫(xiě)時(shí),才能對頁(yè)面執行寫(xiě)操作。如果一個(gè)段是讀/寫(xiě)類(lèi)型的段,但是位址對應的相應頁(yè)面被標注為唯讀/可執行,那麼還是不能對頁(yè)面執行寫(xiě)操作。如果段的類(lèi)型是唯讀/可執行,那麼不管對應頁(yè)面被賦予何保護屬性,頁(yè)面始終是沒(méi)有寫(xiě)許可權的??梢?jiàn)分段和分頁(yè)的保護機制就像電子線(xiàn)路中的串列線(xiàn)路,其中那個(gè)開(kāi)關(guān)沒(méi)有合上線(xiàn)路都不會(huì )通。
類(lèi)似地,一個(gè)頁(yè)面的保護屬性由頁(yè)目錄和頁(yè)表中表項的“串列”或“與操作”構成,見(jiàn)表4-7所示。頁(yè)表表項中的U/S標志和R/W標志應用于該表項映射的單個(gè)頁(yè)面。頁(yè)目錄項中的U/S和R/W標志則對該目錄項所映射的所有頁(yè)面起作用。頁(yè)目錄和頁(yè)表的組合保護屬性由兩者屬性的“與”(AND)操作構成,因此保護措施非常嚴格。
修改頁(yè)表項的軟件問(wèn)題
本節提供一些有關(guān)作業(yè)系統軟體修改頁(yè)表項內容所需遵守的規則。分頁(yè)轉換緩沖要求所有系統都須遵守這些規則。為了避免每次記憶體應用都要存取駐留記憶體的頁(yè)表,從而加快速度,最近使用的線(xiàn)性到實(shí)體位址的轉換資訊被保存在處理器內的頁(yè)轉換高速緩沖中。處理器在存取記憶體中的頁(yè)表之前會(huì )首先利用緩沖中的資訊。只有當必要的轉換資訊不在高速緩沖中時(shí),處理器才會(huì )搜尋記憶體中的目錄和頁(yè)表。頁(yè)轉換高速緩沖作用類(lèi)似於前面描述的加速段轉換的寄存存器的影子描述符號寄存器。頁(yè)轉換高速緩沖的另一個(gè)術(shù)語(yǔ)稱(chēng)為轉換查找緩沖TLB
(Translation Lookaside Buffer)。
80X86處理器並沒(méi)有維護頁(yè)轉換高速緩沖和頁(yè)表中資料的相關(guān)性,但是需要作業(yè)系統軟體來(lái)確保它們一致。即處理器並不知道什麼時(shí)候頁(yè)表被軟體修改過(guò)了。因此作業(yè)系統必須在改動(dòng)過(guò)頁(yè)表之后更新高速緩沖以確保兩者一致。透過(guò)簡(jiǎn)單地重新載入寄存器CR3,我們就可以完成對高速緩沖的更新操作。
有一種特殊情況,在這種情況下修改頁(yè)表項不需要更新頁(yè)轉換高速緩沖。也即當不存在頁(yè)面的表項被修改時(shí),即使是把P標志從0改成1來(lái)標注表項對頁(yè)轉換有效,也不需要更新高速緩沖。因為無(wú)效的表項不會(huì )被存入高速緩沖中。所以在把一個(gè)頁(yè)面從磁片調入記憶體以使頁(yè)面存在時(shí),我們不需要更新頁(yè)轉換高速緩沖。
4.5.5 組合頁(yè)級和段級保護
當啟用了分頁(yè)機制,CPU會(huì )首先執行段級保護,然后再處理頁(yè)級保護。如果CPU在任何一級檢測到一個(gè)保護違規錯誤,則會(huì )放棄記憶體存取並產(chǎn)生一個(gè)異常。如果是段機制產(chǎn)生的異常,那麼就不會(huì )再產(chǎn)生一個(gè)頁(yè)異常。
頁(yè)級保護不能替代或忽略段級保護。例如,若一個(gè)代碼段被設定為不可寫(xiě),那麼代碼段被分頁(yè)后,即使頁(yè)面的R/W標志被設置成可讀可寫(xiě)也不會(huì )讓頁(yè)面可寫(xiě)。此時(shí)段級保護檢查會(huì )阻止任何對頁(yè)面的寫(xiě)操作企圖。頁(yè)級保護可被用來(lái)增強段級保護。例如,如果一個(gè)可讀可寫(xiě)資料段被分頁(yè),那麼頁(yè)級保護機制可用來(lái)對個(gè)別頁(yè)面進(jìn)行防寫(xiě)。
4.6 中斷和異常處理
中斷(Interrupt)和異常(Exception)是指明系統、處理器或當前執行程式(或任務(wù))的某處出現一個(gè)事件,該事件需要處理器進(jìn)行處理。通常,這種事件會(huì )導致執行控制被強迫從當前執行程式轉移到被稱(chēng)為中斷處理程式 (interrupt handler) 或異常處理程式(exception handler)的特殊軟體函數或任務(wù)中。處理器回應中斷或異常所採取的行動(dòng)被稱(chēng)為中斷/異常服務(wù)(處理) 。
通常,中斷發(fā)生在程式執行的隨機時(shí)刻,以回應硬體發(fā)出的信號。系統硬體使用中斷來(lái)處理外部事件,例如要求為外部設備提供服務(wù)。當然,軟體也能透過(guò)執行INT n指令產(chǎn)生中斷。
異常發(fā)生在處理器執行一條指令時(shí),檢測到一個(gè)出錯條件時(shí)發(fā)生,例如被0除的出錯條件。處理器可以檢測到各種出錯條件,包括違反保護機制、頁(yè)錯誤以及機器內部錯誤。
對應用程式和作業(yè)系統來(lái)說(shuō),80X86的中斷和異常處理機制可以透明地處理發(fā)生的中斷和異常事件。當收到一個(gè)中斷或檢測到一個(gè)異常時(shí),處理器會(huì )自動(dòng)地把當前正在執行的程式或任務(wù)掛起,並開(kāi)始執行中斷或異常處理程式。當處理程式執行完畢,處理器就會(huì )恢復並繼續執行被中斷的程式或任務(wù)。被中斷程式的恢復行程并不會(huì )失去程式執行的連貫性,除非從異常中恢復是不可能的或者中斷導致當前執行程式被終止。本節描述保護模式中處理器中斷和異常的處理機制。
4.6.1 異常和中斷向量
為了有助於處理異常和中斷,每個(gè)需要被處理器進(jìn)行特殊處理的處理器定義的異常和中斷條件都被賦予了一個(gè)標識號,稱(chēng)為向量(vector) 。處理器把賦予異?;蛑袛嗟南蛄坑米髦袛嗝枋龇柋鞩DT(Interrupt Descriptor Table)中的一個(gè)索引號,來(lái)定位一個(gè)異?;蛑袛嗟奶幚沓淌饺肟邳c(diǎn)位置。
允許的向量號碼范圍是0到255。其中。到31保留用作80X86處理器定義的異常和中斷,不過(guò)目前該范圍內的向量號並非每個(gè)都已定義了功能,未定義功能的向量號將留作今后使用。
范圍在32到255的向量號用於用戶(hù)定義的中斷。這些中斷通常用於外部I/O設備,使得這些設備可以透過(guò)外部硬體中斷機制向處理器發(fā)送中斷。下表中給出了為80X86定義的異常和NMI中斷分配的向量。對於每個(gè)異常,該表給出了異常類(lèi)型以及是否會(huì )產(chǎn)生一個(gè)錯誤碼並保存在堆棧上。同時(shí)還給出了每個(gè)預先定義好的異常和NMI中斷源。
4.6.2 中段源和異常來(lái)源
中斷來(lái)源
處理器從兩種地方接收中斷:
▓ 外部(硬體產(chǎn)生)的中斷:
▓ 軟件產(chǎn)生的中斷。
外部中斷透過(guò)處理器晶片上兩個(gè)接腳 (INTR和NMI) 接收。當接腳INTR接收到外部發(fā)生的中斷信號時(shí),處理器就會(huì )從系統匯流排上讀取外部中段控制器(例如8259A)提供的中斷向量號。當接腳NMI接收到信號時(shí),就產(chǎn)生一個(gè)非遮罩中斷。它使用固定的中斷向量號2。任何透過(guò)處理器INTR接腳接收的外部中斷都被稱(chēng)為可遮罩硬件中斷,包括中斷向量號0到255。標志寄存器EFLAGS中的lF標志可用來(lái)遮罩所有這些硬件中斷。
透過(guò)在指令中運算元中提供中斷向量號,INT n指令可用于從軟件中產(chǎn)生中斷。例如,指令I(lǐng)NT 0x80會(huì )執行Linux的系統中斷呼叫中斷0x80。向量0到255中的任何一個(gè)都可以用作INT指令中斷號。然后,如果使用了處理器預先定義的NMI向量,那麼處理器對它的回應將與普通方式產(chǎn)生的該中斷不同。如果NMI的向量號2用於該指令,就會(huì )呼叫NMI的中斷處理器程式,但是並不會(huì )啟動(dòng)處理器的NMI處理硬件。
注意,EFLAGS中的IF標志不能夠遮罩使用INT指令從軟體中產(chǎn)生的中斷。
處理器接收的異常也有兩個(gè)來(lái)源:
▓ 處理器檢測到的程式錯誤異常;
▓ 軟體產(chǎn)生的異常。
在應用程式或作業(yè)系統執行期間,如果處理器檢測到程式錯誤,就會(huì )產(chǎn)生一個(gè)或多個(gè)異常。80X86處理器為其檢測到的每個(gè)異常定義了一個(gè)向量。異??梢员患毞譃楣收?faults) 、陷阱(traps)和中止(aborts) ,見(jiàn)后面說(shuō)明。
指令I(lǐng)NTO、INT 3和BOUND指令可以用來(lái)從軟體中產(chǎn)生異常。這些指令可以對指令流中指定點(diǎn)執行的特殊異常條件進(jìn)行檢查。例如,INT3指令會(huì )產(chǎn)生一個(gè)中斷點(diǎn)異常。
INT n指令可用於在軟體中模擬指定的異常,但有一個(gè)限制。如果INT指令中的運算元n是80X86異常的向量號之一,那麼處理器將為該向量號產(chǎn)生一個(gè)中斷,該中斷就會(huì )云執行與該向量有關(guān)的異常處理程式。但是,因為這實(shí)際上是一個(gè)中斷,因此處理器並不會(huì )把一個(gè)錯誤號壓入堆棧,即使硬件產(chǎn)生的該向量相關(guān)的中斷通常會(huì )產(chǎn)生一個(gè)錯誤碼。對於那些會(huì )產(chǎn)生錯誤碼的異常,異常的處理程式會(huì )試圖從堆棧上彈出錯誤碼。因此,如果使用INT指令來(lái)模擬產(chǎn)生一個(gè)異常,處理程式則會(huì )把EIP(正好處於缺少的錯誤碼位置處)彈出堆棧,從而會(huì )造成返回位置錯誤。
4.6.3 異常分類(lèi)
根據異常被報告的方式以及導致異常的指令是否能夠被重新執行,異??杀患毞殖晒收?Fault) 、陷阱(Trap)和中止(Abort)。
▓ Fault是一種通??梢员患m正的異常,並且一旦被糾正程式就可以繼續執行。當出現一個(gè)Fault處理器會(huì )把機器狀態(tài)恢復到產(chǎn)生Fault的指令之前的狀態(tài)。此時(shí)異常處理程式的返回位址會(huì )指向產(chǎn)生Fault的指令,而不是其后面一條指令。因此返回后產(chǎn)生Fault的指令將被重新執行。
▓ Trap是一個(gè)引起陷阱的指令被執行后立刻會(huì )報告的異常。Trap也能夠讓程式后任務(wù)連貫地執行。Trap處理程式的返回位址指向引起陷阱指令的隨后一條指令,因此在返回后會(huì )執行下一條指令。
▓ Abort是一種不會(huì )總是回報導致異常的指令精確位置的異常,並且不允許導致異常的程式重新繼續執行。Abort用於報告嚴重錯誤,例如硬體錯誤以及系統表中存在不一致性或非法值。
4.6.4 程式或任務(wù)的重新執行
為了讓程式或任務(wù)在一個(gè)異?;蛑袛嗵幚硗曛竽苤匦禄謴蛨绦?,除了終止(Abort)之外的所有異常都能報告精確的指令位置,並且所有中斷保證是在指令邊界上發(fā)生。
對於故障類(lèi)異常,處理器產(chǎn)生異常時(shí)保存的返回指標指向出錯指令。因為,當程式或任務(wù)在故障處理程式返回后重新開(kāi)始執行時(shí),原出錯指令會(huì )被重新執行。重新執行引發(fā)出錯的指令通常用於處理存取指令運算元受阻的情況。Fault最常見(jiàn)的一個(gè)例子是頁(yè)面故障(Page-fault)異常。當程式引用不在記憶體中頁(yè)面上的一個(gè)運算元時(shí)就會(huì )出現這種異常。當頁(yè)故障異常發(fā)生時(shí),異常處理程式可以把該頁(yè)面載入到記憶體中並透過(guò)重新執行出錯指令來(lái)恢復程式執行。為了確保重新執行對於當前執行程式具有透明性,處理器會(huì )保存必要的寄存器和堆棧指標資訊,以使得自己能夠返回到執行出錯指令之前的狀態(tài)。
對於陷阱Trap類(lèi)異常,處理器產(chǎn)生異常時(shí)保存的返回指標指向引起陷阱操作的后一條指令。如果在一條執行控制轉移的指令執行期問(wèn)檢測到一個(gè)Trap,則返回指令指標會(huì )反映出控制的轉移情況。例如,如果在執行JMP指令時(shí)檢測到一個(gè)Trap異常,那麼返回指令指標會(huì )指向JMP指令的目標位置,而非指向JMP指令隨后的一條指令。
中止Abort類(lèi)異常不支援可靠地重新執行程式或任務(wù)。中止異常的處理程式通常用來(lái)收集異常發(fā)生時(shí)有關(guān)處理器狀態(tài)的診斷資訊,並且盡可能恰當地關(guān)閉程式和系統。
中斷會(huì )嚴格地支援被中斷程式的重新執行而不會(huì )丟失任何連貫性。中斷所保存的返回指令指標指向處理器獲取中斷時(shí)將要執行的下一條指令邊界處。如果剛執行的指令有一個(gè)重復首碼,則中斷會(huì )在當前重復結束並且暫存器已為下一次重復操作設置好時(shí)發(fā)生。
4.6.5 開(kāi)啟和禁止中斷
標志寄存器EFLAGS的中斷允許標志IF(Interrupt enable Flag)能夠禁止為處理器INTR接腳上收到的可遮罩硬體中斷提供服務(wù)。當IF=0時(shí),處理器禁止發(fā)送到INTR接腳的中斷:當IF=1時(shí),則發(fā)送到INTR接腳的中斷信號會(huì )被處理器進(jìn)行處理。
IF標志並不影響發(fā)送到NMI接腳的非遮罩中斷,也不影響處理器產(chǎn)生的異常。如同EFLAGS中的其他旗標一樣,處理器在回應硬體重定操作時(shí)會(huì )清除IF標志(IF=0)。
If旗標可以使用指令STI和CLI來(lái)設置或清除。只有當程式的CPL<=IOPL時(shí)才可執行這兩條指令,否則將引發(fā)一般保護性異常。IF旗標也會(huì )受一下操作影響:
▓ PUSHF指令會(huì )把EFLAGS內容存入堆疊中,並且可以在那裡被修改。而POPF指令可用於把已被修改過(guò)的標志內容放入EFLAGS寄存器中。
▓ 任務(wù)切換、POPF和IRETA指令會(huì )載入EFLAGS寄存器。因此,它們可用來(lái)修改IF標志。
▓ 當透過(guò)中斷門(mén)處理一個(gè)中斷時(shí),IF旗標會(huì )被自動(dòng)清除(復位) ,從而會(huì )禁止可遮罩硬體中斷。但如果是透過(guò)陷阱門(mén)來(lái)處理一個(gè)中斷,則IF旗標不會(huì )被重定。
4.6.6 異常和中斷的優(yōu)先順序
如果在一條指令邊界有多個(gè)異?;蛑袛嗟却幚頃r(shí),處理器會(huì )按規定的次序對它們進(jìn)行處理。表4-8給出了異常和中斷源類(lèi)的優(yōu)先順序。處理器會(huì )首先處理最高優(yōu)先順序類(lèi)中的異?;蛑袛?。低優(yōu)先順序的異常會(huì )被丟棄,而低優(yōu)先順序的中斷則會(huì )保持等待。當中斷處理程式返回到產(chǎn)生異常和/或中斷的程式或任務(wù)時(shí),被丟棄的異常會(huì )重新發(fā)生。
4.6.7 中斷描述符號表
中斷描述符號表IDT(Interrupt Descriptor Table)將每個(gè)異?;蛑袛嘞蛄糠謩e與它們的處理行程聯(lián)系起來(lái)。與GDT和LDT表類(lèi)似,IDT也是由8位元組長(cháng)描述符號組成的一個(gè)陣列。與GDT不同的是,表中第1項可以包含描述符號。為了構成IDT表中的一個(gè)索引值,處理器把異?;蛑袛嗟南蛄刻?8。因為最多只有256個(gè)中斷或異常向量,所以IDT無(wú)需包含多於256個(gè)描述符號。IDT中可以含有少於256個(gè)描述符號,因為只有可能發(fā)生的異?;蛑袛嗖判枰枋龇?。不過(guò)IDT中所有空描述符號項應該設置其存在位元(標志)為0。 {
IDT表可以駐留在線(xiàn)性位址空間的任何地方,處理器使用IDTR寄存器來(lái)定位IDT表的位置。這個(gè)寄存器中含有IDT表32位的基底位址和16位的長(cháng)度(限長(cháng))值,見(jiàn)圖4-26所示。IDT表基底位址應該對齊在8位元組邊界上以提供處理器的存取效率。限長(cháng)值是以位元組為單位的IDT表的長(cháng)度。
指令LIDT和SIDT指令分別用於載入和保存IDTR寄存器的內容。LIDT指令把在記憶體中的限長(cháng)值和基底位址運算元載入到IDTR寄存器中。該指令僅能由當前特權級CPL是0的代碼執行,通常被用於建立IDT時(shí)的作業(yè)系統初始化代碼中。SIDT指令用於把IDTR中的基底位址和限長(cháng)內容復制到記憶體中。該指令可在任何特權級上執行。
|
如果中斷或異常向量引用的描述符號超過(guò)了IDT的界限,處理器會(huì )產(chǎn)生一個(gè)一般保護性異常。
4.6.8IDT描述符號
IDT表中可以存放三種類(lèi)型的門(mén)描述符號:
▓ 中斷門(mén)(Interrupt gate)描述符號
▓ 陷阱門(mén)(Trap gate)描述符號
▓ 任務(wù)門(mén)(/ask gate)描述符號
圖4-27說(shuō)明了這三種門(mén)描述符號的格式。中斷門(mén)和陷阱門(mén)含有一個(gè)長(cháng)指標(即段選擇符號和偏移值) ,處理器使用這個(gè)長(cháng)指標把程式執行權轉移到代碼段中異?;蛑袛嗟奶幚硇谐讨?。這兩個(gè)段的主要區別在於處理器操作EFLAGS寄存器IF標志上。IDT中任務(wù)門(mén)描述符號的格式與GDT和LDT中任務(wù)門(mén)的格式相同。任務(wù)門(mén)描述符號中含有一個(gè)任務(wù)TSS段的選擇符號,該任務(wù)用於處理異常和/或中斷。
4.6.9 異常與中斷處理
處理器對異常和中斷處理行程的呼叫操作方法與使用CALL指令呼叫程式行程和任務(wù)的方法類(lèi)似。當回應一個(gè)異?;蛑袛鄷r(shí),處理器使用異?;蛑袛嗟南蛄孔鳛镮D7表中的索引。如果索引值指向中斷門(mén)或陷阱門(mén),則處理器使用輿CALL指令操作呼叫門(mén)類(lèi)似的方法呼叫異?;蛑袛嗵幚硇谐?。如果索引值指向任務(wù)門(mén),則處理器使用與CALL指令操作任務(wù)門(mén)類(lèi)似的方法進(jìn)行任務(wù)切換,執行異?;蛑袛嗟奶幚砣蝿?wù)。
異?;蛑袛嚅T(mén)引用執行在當前任務(wù)上下文中的異?;蛑袛嗵幚硇谐?,見(jiàn)圖4-28所示。門(mén)中的段選擇符號指向GDT或當前LDT中的可執行代碼段描述符號。門(mén)描述符號中的偏移欄位指向異?;蛑袛嗵幚硇谐痰拈_(kāi)始處。
當處理器執行異?;蛑袛嗵幚沓绦蚝艚袝r(shí)會(huì )進(jìn)行以下操作:
▓ 如果處理行程將在高特權級(例如0級)上執行時(shí)就會(huì )發(fā)生堆棧切換操作,堆棧切換行程如下:
● 處理器從當前執行任務(wù)的TSS段中得到中斷或異常處理行程使用的堆棧的段選擇符號和堆棧指標(例如tss.ss0、tss.esp0)。然后處理器會(huì )把被中斷程式(或任務(wù))的堆棧選擇符號和堆棧指標壓入新堆棧中,見(jiàn)圖4-29所示。
● 接著(zhù)處理器會(huì )把EFLAGS、CS和EIP寄存器的當前值也壓入新堆棧中。
● 如果異常會(huì )產(chǎn)生一個(gè)錯誤號,那麼該錯誤號也會(huì )被最后壓入新堆棧中。
▓ 如果處理行程將在被中斷任務(wù)同一個(gè)特權級上執行,那麼:
● 處理器把EFLAGS、CS和EIP寄存器的當前值保存在當前堆棧上。
● 如果異常會(huì )產(chǎn)生一個(gè)錯誤號,那麼該錯誤號也會(huì )被最后壓入新堆棧中。
為了從中斷處理行程中返回,處理行程必須使用IRET指令。IRET指令與RET指令類(lèi)似,但IRET 還會(huì )把保存的寄存器內容恢復到EFLAGS中。不過(guò)只有當CPL是0時(shí)才會(huì )恢復EFLAGS中的IOPL欄位,并且只有當CPL〈=IOPL回時(shí)IF標志才會(huì )被改變。如果當時(shí)呼叫中斷處理行程發(fā)生了堆棧切換,那么在返回時(shí)IRET指令會(huì )切換回到原來(lái)的堆棧。
⑴ 異常和中斷處理行程的保護
異常和中斷處理行程的特權級保護機制與透過(guò)呼叫門(mén)呼叫普透行程類(lèi)似。處理器不允許把控制轉移到比CPL更低特權級代碼段的中斷處理行程中,否則將產(chǎn)生一個(gè)一般保護性異常。另外,中斷和異常的保護機制在以下方面與一般呼叫門(mén)行程不同:
▓ 因為中斷異常向量沒(méi)有RPL,因此在隱式呼叫異常和中斷處理行程時(shí)不會(huì )檢查RPL。
▓ 只有當一個(gè)異?;蛑袛嗍抢檬褂肐NT n、INT 3或INTO指令產(chǎn)生時(shí),處理器才會(huì )檢查中斷或陷阱門(mén)中的DPL。此時(shí)CPL必須小於等於門(mén)的DPL。這個(gè)限制可以防止執行在特權級3的應用程式使用軟體中斷存取重要的異常處理行程,例如頁(yè)錯誤處理行程,假設這些處理行程已被存放在更高特權級的代碼段中。對於硬件產(chǎn)生的中斷和處理器檢測到的異常,處理器會(huì )忽略中斷門(mén)和陷阱門(mén)中的DPL。
因為異常和中斷通常不會(huì )定期發(fā)生,因此這些有關(guān)特權級的規則有效地增強了異常和中斷處理行程能夠執行的特權級限制。我們可以利用以下技術(shù)之一來(lái)避免違反特權級保護:
▓ 異?;蛑袛嗵幚沓淌娇梢源娣旁谝粋€(gè)…致性代碼段中。這個(gè)技術(shù)可以用於只需存取堆棧上資料的處理行程(例如,除出錯異常)。如果處理程式需要資料段中的資料,那麼特權級3必須能夠存取這個(gè)資料段。但這樣一來(lái)就沒(méi)有保護可言了。
▓ 處理行程可以放在具有特權級。的非一致代碼段中。這種處理行程總是可以執行的,而不管被中斷程式或任務(wù)的當前特權級CPL。
⑵ 異?;蛑袛嗵幚硇谐痰钠鞓耸褂梅绞?br>
當透過(guò)中斷門(mén)或陷阱門(mén)存取一個(gè)異?;蛑袛嗵幚硇谐虝r(shí),處理器會(huì )在把EFLAGS寄存器內容保存到堆棧上之后清除EFLAGS中的TF標志。清除TF標志可以防止指令跟蹤影響中斷回應。而隨后的IRET指令會(huì )用堆棧上的內容恢復EFLAGS的原TF標志。
中斷門(mén)與陷阱門(mén)唯一的區別在於處理器操作EFLAGS寄存器IF標志的方法。當透過(guò)中斷門(mén)存取一個(gè)異?;蛑袛嗵幚硇谐虝r(shí),處理器會(huì )重定IF標志以防止其他中斷干擾當前中斷處理行程。隨后的IRET指令則會(huì )用保存在堆棧上的內容恢復EFLAGS寄存器的IF標志。而透過(guò)陷阱門(mén)存取處理行程並不會(huì )影響IF標志。
⑶ 執行中斷處理行程的任務(wù)
當透過(guò)IDT表中任務(wù)門(mén)存取異?;蛑袛嗵幚硇谐虝r(shí),就會(huì )導致任務(wù)切換。從而可以在一個(gè)專(zhuān)用任務(wù)中執行中斷或異常處理行程。IDT表中的任務(wù)門(mén)引用GDT中的TSS描述符號。切換到處理行程任務(wù)的方法與普通任務(wù)切換一樣。由於我們介紹的Linux作業(yè)系統沒(méi)有使用這種中斷處理方式,因此這裡不再贅述。
4.6.10 中斷處理任務(wù)
當透過(guò)IDT中任務(wù)門(mén)來(lái)存取異?;蛑袛嗵幚硇谐虝r(shí)就會(huì )導致任務(wù)切換。使用單獨的任務(wù)來(lái)處理異?;蛑袛嘤腥缦潞锰帲?br>
▓ 被中斷程式或任務(wù)的完整上下文會(huì )被自動(dòng)保存:
▓ 在處理異?;蛑袛鄷r(shí),新的TSS可以允許處理行程使用新特權級0的堆棧。在當前特權級0的堆棧已毀壞時(shí)如果發(fā)生了一個(gè)異?;蛑袛?,那麼在為中斷行程提供一個(gè)新特權級0的堆棧條件下,透過(guò)任務(wù)門(mén)存取中斷處理行程能夠防止系統崩潰;
▓ 透過(guò)使用單獨的LDT給中斷或異常處理任務(wù)獨立的位址空間,可以把它與其他任務(wù)隔離開(kāi)來(lái)。
使用獨立任務(wù)處理異?;蛑袛嗟牟蛔阒幨牵涸谌蝿?wù)切換時(shí)必須對大量機器狀態(tài)進(jìn)行保存,使得它此使用中斷門(mén)的回應速度要慢,導致中斷延時(shí)增加。
IDT中的任務(wù)門(mén)會(huì )引用GDT中的TSS描述符號,圖4-30所示。切換到控制碼任務(wù)的行程與普通任務(wù)切換行程相同。到被中斷任務(wù)的反向鏈結會(huì )被保存在控制碼任務(wù)TSS的前一任務(wù)鏈結欄位中。如果一個(gè)異常會(huì )產(chǎn)生一個(gè)出錯碼,則該出錯碼會(huì )被復制到新任務(wù)堆棧上。
當異?;蛑袛嗫刂拼a任務(wù)用於作業(yè)系統中時(shí),實(shí)際上有兩種分派調度任務(wù)的機制:作業(yè)系統軟體調度和處理器中斷機制的硬體調度。使用軟件調度方法時(shí)需要考慮到中斷開(kāi)啟時(shí)採用中斷處理任務(wù)。
4.6.11 錯誤碼
當異常條件與一個(gè)特定的段相關(guān)時(shí),處理器會(huì )把一個(gè)錯誤碼壓入異常處理行程的堆棧上。出錯碼的格式見(jiàn)圖4-3 l所示。錯誤碼很象一個(gè)段選擇符號,但是最低3Bit不是TI和RPL欄位,而是以下3個(gè)旗標:
▓ 位元0是外部事件EXT(External event)標志。當設置位元時(shí),表示執行程式以外的事件造成了異常,例如硬件中斷。
▓ 位元l是描述符號位置IDT(Descriptor location)標志。當該位置位元時(shí),表示錯誤碼的索引部分指向1DT中的一個(gè)門(mén)描述符號。當該位元重定時(shí),表示索引部分指向GDT或LDT中的一個(gè)段描述符號。
▓ 位元2是GDT/LDT表選擇標志TI。只有當位1的IDT=0才有用。當該TI=l時(shí),表示錯誤碼的索引部分指向LDT中的一個(gè)描述符號。當TI=0時(shí),說(shuō)明錯誤碼中的索引部分指向GDT表中的一個(gè)描述符號。
段選擇索引欄位提供了錯誤碼引用的IDT、GDT或者當前LDT中段或閘描述符號的索引值。在某些情況下錯誤碼是空的(即低16位全0)??斟e誤碼表示錯誤不是由於引用某個(gè)特定段造成,或者是在操作中引用了一個(gè)空段描述符號。
頁(yè)故障(Page-fault)異常的錯誤碼格式與上面的不同,見(jiàn)圖4-32所示。只有最低3個(gè)Bit位有用,它們的名稱(chēng)與頁(yè)表項中的最后三位相同(U/S、W/R、P)。含義和作用分別是:
▓ 位元0 (P) ,異常是由於頁(yè)面不存在或違反存取特權而引發(fā)。P=0,表示頁(yè)不存在;P=l表示違反頁(yè)級保護許可權。
▓ 位元l (W/R),異常是由於記憶體讀或寫(xiě)操作引起。 W/R=0,表示由讀操作引起;W/R=l,表示由寫(xiě)操作引起。
▓ 位2 (U/S) ,發(fā)生異常時(shí)CPU執行的代碼級別o。U/S=0,表示CPU正在執行超級用戶(hù)代碼:U/S=l,表示CPU正在執行一般用戶(hù)代碼。
另外,處理器還會(huì )把引起頁(yè)面故障異常所存取用的線(xiàn)性位址存放在CR2中。頁(yè)出錯異常處理程式可以使用這個(gè)位址來(lái)定位相關(guān)的頁(yè)目錄和頁(yè)表項。
注意,錯誤不會(huì )把IRET指令自動(dòng)地彈出堆棧,因此中斷處理程式在返回之前必須清除堆棧上的錯誤碼。另外,雖然處理產(chǎn)生的某些異常會(huì )產(chǎn)生錯誤碼并會(huì )自動(dòng)地保存到處理行程的堆棧中,但是外部硬件中斷或者程式執行INT n指令產(chǎn)生的異常並不會(huì )把錯誤碼壓入堆棧中。
注譯:
Stack - 堆疊 或者稱(chēng)呼為 堆棧
Segment - 段
Flag - 旗標 或者稱(chēng)呼為 標志
暫存器又稱(chēng)呼為寄存器
Trap Gate: 陷阱門(mén)或稱(chēng)呼為 活板門(mén)