深入介紹Linux內(nèi)核(四)
深入介紹Linux內(nèi)核(第四篇)
第四章
本文介紹的Linux作業(yè)系統(tǒng)是以Intel公司80X86及相關(guān)週邊硬體組成的PC系統(tǒng)為基礎(chǔ)的。有關(guān)80X86 CPU系統(tǒng)程式設(shè)計的最佳參考書籍當(dāng)然是Intel公司發(fā)行的一套三卷的英文版《IA-32 Intel體系結(jié)構(gòu)軟體發(fā)展者手冊》,尤其足其中第3卷:《系統(tǒng)程式設(shè)計指南》是理解使用80X86 CPU的作業(yè)系統(tǒng)工作原理或進(jìn)行系統(tǒng)程式設(shè)計必不可少的參考資料,實際上本章內(nèi)容就主要取自於該書。這些資料可以從Intel公司的網(wǎng)站上免費下載。本章主要概要描述80X86 CPU的體系結(jié)構(gòu)以及保護(hù)模式下程式設(shè)計的一些基礎(chǔ)知識,為準(zhǔn)備閱讀基於80X86CPU的Linux內(nèi)核原始碼打下扎實基礎(chǔ)。主要包括:1. 80X86基礎(chǔ)知識:2. 保護(hù)模式記憶體管理:3. 各種保護(hù)措施:4. 中斷和異常處理;5. 任務(wù)管理:6. 保護(hù)模式程式設(shè)計的初始化;7. 一個簡單的多工內(nèi)核例子。
本章最后部分介紹的一個簡單多工內(nèi)核足以Linux 0.12內(nèi)核為基礎(chǔ)的一個簡化實例。該實例用於演示記憶體分段管理和任務(wù)管理的實現(xiàn)方法,沒有包括分頁機(jī)制內(nèi)容。但若能徹底理解這個實例的運作機(jī)制,那麼在隨后閱讀Linux內(nèi)核原始碼時就不應(yīng)該再會碰到什麼大問題了。
若讀者對這部分內(nèi)容已經(jīng)比較熟悉,那麼可以直接閱讀本章最后給出的一個可執(zhí)行的內(nèi)核實例。當(dāng)然,在閱讀內(nèi)核原始碼時讀者可以隨時回過頭來參考本章內(nèi)容。因此並不勉強(qiáng)讀者需要完全理解本章內(nèi)容之后才開始閱讀后續(xù)章節(jié)中的Linux內(nèi)核代碼。
4.180X86系統(tǒng)寄存器和系統(tǒng)指令
為了協(xié)助處理器執(zhí)行初始化和控制系統(tǒng)操作,80X86提供了一個標(biāo)志寄存器ELAGS和幾個系統(tǒng)寄存器,除了一些通用狀態(tài)標(biāo)志外,EFLAGS中還包含幾個系統(tǒng)標(biāo)志。這些系統(tǒng)標(biāo)志用於控制任務(wù)切換、中斷處理、指令追蹤以及存取許可權(quán)。系統(tǒng)寄存器用於記憶體管理和控制處理器操作,含有分段和分頁處理機(jī)制系統(tǒng)表的基底位址、控制處理器操作的Bit標(biāo)志位元。
4.1.1 標(biāo)志寄存器
標(biāo)志寄存器EFLAGS中的系統(tǒng)標(biāo)志和IOPL欄位用於控制I/O存取,可遮罩硬體中斷,除錯、任務(wù)切換以及虛擬-8086模式,見圖4-l所示。通常只允許作業(yè)系統(tǒng)代碼有權(quán)修改這些標(biāo)志。EFLAGS中的其他標(biāo)志是一些通用標(biāo)志(進(jìn)位元CF、奇偶PF、輔助進(jìn)位元AF、零標(biāo)志ZF、負(fù)號SF、方向DF、溢出OF)。這里我們僅對EFLAGS中的系統(tǒng)標(biāo)志進(jìn)行說明。
TF 位元8是追蹤標(biāo)志(Trap Flag)。當(dāng)設(shè)置該位時可為除錯操作啟動單步執(zhí)行方式;復(fù)位時則禁止單步執(zhí)行。在單步執(zhí)行方式下,處理器會在每個指令執(zhí)行之后產(chǎn)生一個除錯異常,這樣我們就可以觀察執(zhí)行程式在執(zhí)行每條指令后的狀態(tài)。如果程式使用POPF、POPFD或IREF指令設(shè)置了TF標(biāo)志,那麼在隨后指令之后處理器就會產(chǎn)生一個除錯異常。
IOPL 位元13-12是I/O特權(quán)級(I/O Privilege Level)欄位。該欄位指明當(dāng)前執(zhí)行程式或任務(wù)的I/O特權(quán)級IOPL。當(dāng)前執(zhí)行程式或任務(wù)的CPL必須大於等於這個IOPL才能存取I/O位址空間。只有當(dāng)CPL為特權(quán)級0時,程式才可以使用POPF或IRET指令修改這個欄位。 IOPL也是控制對IF標(biāo)志修改的機(jī)制之一。
NT 位元14是巢狀任務(wù)標(biāo)志(Nested Task) 。它控制著被中斷任務(wù)和呼叫任務(wù)之間的鏈結(jié)關(guān)系。在使用CALl+指令、中斷或異常執(zhí)行任務(wù)呼叫時,處理器會設(shè)置該標(biāo)志。在透過使用IRET指令從一個任務(wù)返回時,處理器會檢查並修改這個NT標(biāo)志,使用POPF/POPFD指令也可以修改這個標(biāo)志,但是在應(yīng)用程式中改變這個標(biāo)志的狀態(tài)會產(chǎn)生不可意料的異常。
RF 位元16足恢復(fù)標(biāo)志(Resume Flag) 。該標(biāo)志用於控制處理器對中斷點指令的回應(yīng)。當(dāng)設(shè)置時,這個標(biāo)志會臨時禁止中斷點指令產(chǎn)生的除錯異常;當(dāng)該標(biāo)志重定時,則中斷點指令將會產(chǎn)生異常。RF標(biāo)志的主要功能是允許在除錯異常之后重新執(zhí)行一條指令。當(dāng)除錯軟體使用IRETD指令返回被中斷程式之前,需要設(shè)置堆棧上EFLAGS內(nèi)容中的RF標(biāo)志,以防止指令中斷點造成另一個異常。處理器會在指令返回之后自動地清除該標(biāo)志,從而再次允許指令中斷點異常。
VM 位元17是虛擬-8086方式(Virtual-8086 Mode)標(biāo)志,當(dāng)設(shè)置該標(biāo)志時,就開啟虛擬一8086方式:當(dāng)重定該標(biāo)志時,則回到保護(hù)模式。
4.1.2 記憶體管理寄存器
處理器提供了4個記憶體管理寄存器(GDTR、LDTR、IDTR和TR) ,用於指定記憶體分段管理所用系統(tǒng)表的基底位址,見圖4-2所示。處理器為這些寄存器的載入和保存提供了特定的指令。有關(guān)系統(tǒng)表的作用請參見下一節(jié)“保護(hù)模式記憶體管理”中的詳細(xì)說明。
GDTR、LDTR、IDTR和TR都是段基址寄存器,這些段中含有分段機(jī)制的重要資訊表。GDTR、IDTR和LDTR用與定址存放描述呼號表的段。TR用于定址一個特殊的任務(wù)狀態(tài)段TSS(Task State Segment)。TSS段中包含著當(dāng)前執(zhí)行任務(wù)的重要資訊。
1. 全域描述符號表寄存器GDTR
TDTR寄存器中用于存放全域描述符號表GDT的32位元的線性基底位址和16位元的表限長值?;孜恢分付℅DT表中位元組0在線性位址空間中的位址,表長度指明GDT表的位元組長度值。指令LTDT和SGDT分別用於載入和保存GDTR寄存器的內(nèi)容。在機(jī)器剛加電或處理器復(fù)位后,基底位址被預(yù)設(shè)地設(shè)置為0,而長度值被設(shè)置成0xFFFF。在保護(hù)模式初始化行程中必須給GDTR載入一個新值。
2. 中斷描述符號表寄存器IDTR
與GDTR的作用類似,IDTR寄存器用於存放中斷描述符號表IDT的32位元線性基底位址和16位元表長度值,指令LIDT和SIDT分別用於載入和保存IDTR寄存器的內(nèi)容。在機(jī)器剛加電或處理器復(fù)位后,基底位址被預(yù)設(shè)地設(shè)置為0,而長度值被設(shè)置成0xFFFF。
3. 區(qū)域描述符號表暫存器LDTR
LDTR寄存器中用於存放區(qū)域描述符號表LDT的32位元線性基底位址、16位元段限長和描述符號屬性值。指令LLDT和SLDT分別用於載入和保存LDTR寄存器的段描述符號部分。包含LDT表的段必須在GDT表中有一個段描述符號項。當(dāng)使用LLDT指令把含有LDT表段的選擇符號載入進(jìn)LDTR時,LDT段描述符號的段基底位址、段限長度以及描述符號屬性會被自動地載入到LDTR中。當(dāng)進(jìn)行任務(wù)切換時,處理器會把新任務(wù)LDT的段選擇符號和段描述符號自動地載入進(jìn)LDTR中。在機(jī)器加電或處理器復(fù)位后,段選擇符號和基底位址被預(yù)設(shè)地設(shè)置為0,而段長度被設(shè)置成0xFFFF。
4. 任務(wù)寄存器TR
TR寄存器用於存放當(dāng)前任務(wù)TSS段的16位段選擇符號、32位基底位址、16位段長度和描述符號屬性值。它引用GDT表中的一個TSS類型的描述符號。指令LTR和STR分別用於載入和保存TR暫存器的段選擇符號部分。當(dāng)使用LTR指令把選擇符號載入進(jìn)任務(wù)寄存器時,TSS描述符號中的段基底位址、段限長度以及描述符號屬性會被自動地載入到任務(wù)寄存器中。當(dāng)執(zhí)行任務(wù)切換時,處理器會把新任務(wù)的TSS的段選擇符號和段描述符號自動地載入進(jìn)任務(wù)寄存器TR中。
4.1.3 控制寄存器
控制寄存器(CR0、CR 1、CR2和CR3)用於控制和確定處理器的操作模式以及當(dāng)前執(zhí)行任務(wù)的特性,見圖4-3所示。CR0中含有控制處理器操作模式和狀態(tài)的系統(tǒng)控制標(biāo)志;CR1保留不用;CR2含有導(dǎo)致頁錯誤的線性位址。CR3中含有頁目錄表實體記憶體基底位址,因此該寄存器也被稱為頁目錄基底位址寄存器PDBR(Page-Directory Base address Register) 。
1. CR0中輔助運算器控制位
CR0的4個Bit位:擴(kuò)允類型位ET、任務(wù)切換位TS、模擬位EM和數(shù)學(xué)存在位MP用於控制80X86浮點(數(shù)學(xué))輔助運算器的操作。有關(guān)輔助運算器的詳細(xì)說明,請參看往后介紹的11章內(nèi)容。CR0的ET位元(標(biāo)志)用於選擇與輔助運算器進(jìn)行通信所使用的協(xié)定,即指明系統(tǒng)中使用的是80387還是80287輔助運算器。TS、MP和EM位元用於確定浮點指令或WAlT指今是否應(yīng)該產(chǎn)生一個設(shè)備不存在DNA(Device Not Available)異常。這個異常可用來僅為使用浮點運算的任務(wù)保存和恢復(fù)浮點寄存器。對於沒有使用浮點迎算的任務(wù),這樣做可以加快它們之間的切換操作。
ET CR0的位4是擴(kuò)允類型標(biāo)志(Extension Type)。當(dāng)該標(biāo)志為l時,表示指明系統(tǒng)有80387輔助運算器存在,并使用32位元輔助運算器協(xié)定。ET=0指明使用80287輔助運算器。加果模擬位EM=1,則該位將被忽略。在處理器復(fù)位操作時,ET位元俞被初始化指明系統(tǒng)中使用的輔助器類型。如果系統(tǒng)中有80387,則ET被設(shè)置成1,否則若有一個80287或者沒有輔助運算器,則ET被設(shè)置成0。
TS CR0的位3是任務(wù)已切換(Task Switched)標(biāo)志。該標(biāo)志用於推遲保存任務(wù)切換時的輔助運算器內(nèi)容,直到新任務(wù)開始實際執(zhí)行輔助運算器指令。處理器在每次任務(wù)切換時都會設(shè)置該標(biāo)志,並且在執(zhí)行輔助運算器指令時測試該標(biāo)志。
如果設(shè)置了TS標(biāo)志並且CR0的EM標(biāo)志為0,那麼在執(zhí)行任何輔助運算器指令之前會產(chǎn)生一個設(shè)備不存在DNA(Device Not Available)異常。如果設(shè)置了TS標(biāo)志但沒有設(shè)置CR0的MP和EM標(biāo)志,那么在執(zhí)行輔助運算器指令WAIT/FWAIT之前不會產(chǎn)生設(shè)備不存在異常。如果設(shè)置了EM標(biāo)志,那麼TS標(biāo)志對輔助運算器指令的執(zhí)行無影響。見表4-1所示。
在任務(wù)切換時,處理器並不自動保存輔助運算器的上下文,而是會設(shè)置TS標(biāo)志。這個標(biāo)志會使得處理器在執(zhí)行新任務(wù)指令流的任何時候遇到一條輔助運算器指令時產(chǎn)生設(shè)備不存在異常。設(shè)備不存在異常的處理程式可使用CLTS指令清除TS標(biāo)志,並且保存輔助運算器的上下文。如果任務(wù)從沒有使用過輔助運算器,那麼相應(yīng)輔助運算器上下文就不用保存。
EM CR0的位元2是模擬(EMulation)標(biāo)志。當(dāng)該位設(shè)置時,表示處理器沒有內(nèi)部或外部輔助運算器,執(zhí)行輔助運算器指令時會引起設(shè)備不存在異;當(dāng)清除時,表示系統(tǒng)有輔助運算器。設(shè)置這個標(biāo)志可以迫使所有浮點指令使用軟體來模擬。
MP CR0的位元l是監(jiān)控輔助運算器(Monitor Coprocessor或Math Pesent)標(biāo)志。用於控制WAIT/FWAIT指令與TS標(biāo)志的交互作用。如果MP=1、TS=1,那麼執(zhí)行WAIT指令將產(chǎn)生一個設(shè)備不存在異常;如果MP=0,則TS標(biāo)志不會影響WAIT的執(zhí)行。
2. CR0中保護(hù)控制位
PE CR0的位元。是啟用保護(hù)(Protection Enable)標(biāo)志。當(dāng)設(shè)置該位元時即開啟了保護(hù)模式;當(dāng)重定時即進(jìn)入真實位址模式。這個標(biāo)志僅開啟段級保護(hù),而並沒有啟用分頁機(jī)制。若要啟用分頁機(jī)制,那麼PE和PG標(biāo)志都要置位元。
PG CR0的位元31是分頁(Paging)標(biāo)志。當(dāng)設(shè)置該位時即開啟了分頁機(jī)制;當(dāng)重定時則禁止分頁機(jī)制,此時所有線性位址等同於實體位址,在開啟這個標(biāo)志之前必須已經(jīng)或者同時開啟PE標(biāo)志。即若要啟用分頁機(jī)制,那麼PE和PG標(biāo)志都要置位元。
WP 對於Intel 80486或以上的CPU、CR0的位元16是防寫(Write Protect)標(biāo)志。當(dāng)設(shè)置該標(biāo)志時,處理器會禁止超級用戶程式(例如特權(quán)級0的程式)向用戶級唯讀頁面執(zhí)行寫操作;當(dāng)該位復(fù)位時則反之。該標(biāo)志有利於UNIX類作業(yè)系統(tǒng)在建立進(jìn)程時實現(xiàn)寫時復(fù)制(Copy on Write)技術(shù)。
NE 對於Intel 80486或以上的CPU,CR0的位元5是輔助運算器錯誤(Numeric Error)標(biāo)志。當(dāng)設(shè)置該標(biāo)志時,就啟用了X87輔助運算器錯誤的內(nèi)部報告機(jī)制;若重定該標(biāo)志,那麼就使用PC機(jī)形式的X87輔助運算器錯誤報告機(jī)制。當(dāng)NE為重定模式並且CPU的IGNNE輸入接腳有信號時,那麼數(shù)學(xué)輔助運算器X87錯誤將被忽略。當(dāng)NE為重定模式並且CPU的IGNNE輸入接腳無信號時,那麼非遮罩的數(shù)學(xué)輔助運算器X87錯誤將導(dǎo)致處理器透過FERR接腳在外部產(chǎn)生一個中斷,並且在執(zhí)行下一個等待形式浮點指令或WAIT/FWAIT指令之前立刻停止指令執(zhí)行。CPU的FERR接腳用於模擬外部輔助運算器80387的ERROR接腳,因此通常連接到中斷控制器輸入請求接腳上。NE標(biāo)志、IGNNE接腳和FERR接腳用於利用外部邏輯來實現(xiàn)PC機(jī)形式的外部錯誤報告機(jī)制。
啟用保護(hù)模式PE(Protected Enable)位元(位0)和開敔分頁PG(Paging)位元(位31)分別用於控制分段和分頁機(jī)制。PE用於控制分段機(jī)制。如果PE=l,處理器就工作在開啟分段機(jī)制環(huán)境下,即執(zhí)行在保護(hù)模式下。如果PE=0,則處理器關(guān)閉了分段機(jī)制,並如同8086工作於真實位址模式下。PG用於控制分頁機(jī)制。如果PG=1,則開啟了分頁機(jī)制。如果PG=-0,分頁機(jī)制被禁止,此時線性位址被直接為實體位址使用。
如果PE=0、PG=0,處理器工作在真實位址模式下;如果PG=0、PE=l,處理器工作在沒有開啟分頁機(jī)制的保護(hù)模式下;如果PG=l、PE=0,此時由於不在保護(hù)模式下不能啟用分頁機(jī)制,因此處理器會產(chǎn)生一個一般保護(hù)異常,即這種標(biāo)志組合無效;如果PG=1、PE=l,則處理器工作在開啟了分頁機(jī)制的保護(hù)模式下。
當(dāng)改變PE和PG位時,我們必須小心。只有當(dāng)執(zhí)行程式起碼有部分代碼司資料在線性位址空間和實體位址空間中具有相同位址時,我們才能改變PG位的設(shè)置。此時這部分具有相同位址的代碼在分頁和未分頁世界之問起著橋樑的作用。無論是否開啟分頁機(jī)制,這部分代碼部具有相同的位址。另外,在開啟分頁(PG=l)之前必須先更新頁高速緩沖TLB。
在修改該了PE位元之后程式必須立刻使用一條跳轉(zhuǎn)指令,以更新處理器執(zhí)行管道中已經(jīng)獲取的不同模式下的任何指令。在設(shè)置PE位元之前,程式必須初始化幾個系統(tǒng)段和控制寄存器。在系統(tǒng)剛上電時,處理器被復(fù)位成PE=0、PG=0(即實模式狀態(tài)),以允許開機(jī)代碼在啟用分段和分頁機(jī)制之前能夠初始化這些寄存器和資料結(jié)構(gòu)。
3. CR2和CR3
CR2和CR3用於分頁機(jī)制。CR3含有存放頁目錄表頁面的實體位址,因此CR3也被稱為PDBR。因為頁目錄表頁面是頁對齊的,所以該寄存器只有高20位是有效的。而低12位保留供更高級處理器使用,因此在往CR3中載入一個新值時低12位必須設(shè)置為0。
使用MOV指令載入CR3時具有讓頁高速緩沖無效的副作用。為了減少位址轉(zhuǎn)換所要求的匯流排週期數(shù)量,最近存取的頁目錄和頁表會被存放在處理器的頁高速緩沖器件中,該緩沖器件被稱為轉(zhuǎn)換查找緩沖區(qū)TLB(Translation Lookaside Buffer) 。只有當(dāng)TLB中不包含要求的頁表項時才會使用額外的匯流排週期從記憶體中讀取頁表項。
即使CR0中的PG位處於重定模式(PG=0) ,我們也能先載入CR3。以允許對分頁機(jī)制進(jìn)行初始化。當(dāng)切換任務(wù)時,CR3的內(nèi)容也會隨之改變。但是如果新任務(wù)的CR3值與原任務(wù)的一樣,處理器就無需更新頁高速緩沖。這樣共用頁表的任務(wù)可以執(zhí)行得更快。
CR2用於出現(xiàn)頁異常時報告出錯資訊。在報告頁異常時,處理器會把引起異常的線性位址存放在CR2中。因此作業(yè)系統(tǒng)中的頁異常處理程式可以透過檢查CR2的內(nèi)容來確定線性位址空間中哪一個頁面引發(fā)了異常。
4.1.4 系統(tǒng)指令
系統(tǒng)指令用於處理系統(tǒng)級功能,例如載入系統(tǒng)寄存器、管理中斷等。大多數(shù)系統(tǒng)指令只能由處於特權(quán)級。的作業(yè)系統(tǒng)軟體執(zhí)行,其余一些指令可以在任何特權(quán)級上執(zhí)行,因此應(yīng)用程式也能使用。表4-2中列出了我們將用到的一些系統(tǒng)指令。其中還指出了它們是否受到保護(hù)。
4.2 保護(hù)模式記憶體管理
4.2.1 記憶體定址
記憶體是指一組有序位元組組成的陣列,每個位元組有唯一的記憶體位址。記憶體定址則是指對儲存在記憶體中的某個指定資料物件的位址進(jìn)行定位。這里,資料物件是指儲存在記憶體中的一個指定資料類型的數(shù)值或字串。80X86支持多種資料類型:1位元組、2位元組(1個字)或4位元組(雙字或長字)的無符號整型數(shù)或帶符號整型數(shù),以及多位元組字元串等。通常位元組中某一Bit位的定位或定址可以基於位元組來定址,因此最小資料類型的定址是對l位元組資料(數(shù)值或字元)的定位。通常記憶體位址從。開始編址,對於80X86 CPU來說,其位址匯流排寬度為32位元,因此一共有2^32個不同實體位址。即記憶體實體位址空間有4G,總共可以定址4G位元組的實體記憶體。對於多位元組資料類型(例如2位元組整數(shù)資料類型),在記憶體中這些位元組相鄰存放。80X86首先存放低值位元組,隨后位址處存放高值位元組。因此80X86 CPU是一種先存小值(Little Endium)的處理器。
對於80X86 CPU,一條指令主要由操作碼(Opcode)和操作對象即運算元(Operand)構(gòu)成。運算元可以位於一個寄存器中,也可以在記憶體中。若要定位記憶體中的運算元,就要進(jìn)行記憶體定址。80X86有許多指令的運算元涉及記憶體定址,並且針對所定址物件資料類型的不同,也有很多不同的定址方案可供選擇。
為了進(jìn)行記憶體定址,80X86使用了一種稱為段(Segment)的定址技術(shù)。這種定址技術(shù)把記憶體空間分成一個或多個稱為段的線性區(qū)域,從而對記憶體中一個資料物件的定址就需要使用一個段的起始位址(即段位址)和一個段內(nèi)偏移位址兩部分構(gòu)成。段位址部分使用16位元的段選擇符號指定,其中14位可以選擇2^14次方即16384個段。段內(nèi)偏移位址部分使用32位元的值來指定,因此段內(nèi)位址可以是O到4G。即一個段的最大長度可達(dá)4G。程式中由16位元的段和32位的偏移構(gòu)成的48位位址或長指標(biāo)稱為一個邏輯位址(虛擬位址) 。它唯一確定了一個資料物件的段位址和段內(nèi)偏移位址。而僅由32位偏移位址或指標(biāo)指定的位址是基於當(dāng)前段的物件位址。
80X86為段部分提供了6個存放段選擇符號的段寄存器:CS、DS、ES、SS、FS和GS。其中CS總是用於定址代碼段,而堆棧段則專門使用SS段寄存器。在任何指定時刻由CS定址的段稱為當(dāng)前代碼段。此時EIP寄存器中包含了當(dāng)前代碼段內(nèi)下一條要執(zhí)行指令的段內(nèi)偏移位址。因此要執(zhí)行指令的位址可表示成CS : [EIP]。后面將說明的段間控制轉(zhuǎn)移指令可以被用來為CS和EIP代入新值,從而可以把執(zhí)行位置政變到其他的代碼段中,這樣就實現(xiàn)了在不同段中程式的控制傳遞。
由段寄存器SS定址的段稱為當(dāng)前堆棧段。堆棧頂端由ESP寄存器內(nèi)容指定。因此堆棧頂端位址是SS :[ESP]。另外4個段寄存器是通用段寄存器。當(dāng)指令中沒有指定所運算元據(jù)的段時,那麼DS將足預(yù)設(shè)的資料段寄存器。
為了指定記憶體運算元的段內(nèi)偏移位址,80X86指令規(guī)定了計算偏移量的很多方式,稱為指令定址方式。指令的偏移量由三部分相加組成:基底位址寄存器、變址暫存器和一個偏移常數(shù)。即:
偏移位址 = 基底位址+ (變址 x 比例 因數(shù)) +偏移量
4.2.2 地址變換
任何完整的記憶體管理系統(tǒng)都包含兩個關(guān)鍵部分:保護(hù)和地址變換。提供保護(hù)措施是可以防止一個任務(wù)存取另一個任務(wù)或作業(yè)系統(tǒng)的記憶體區(qū)域。位址變換能夠讓作業(yè)系統(tǒng)在給任務(wù)分配記憶體時具有靈活性,並且因為我們可以讓某些實體位址不被任何邏輯位址所映射,所以在位址變換行程中同時也提供了記憶體保護(hù)功能。
正如上面提到的,電腦中的實體記憶體是位元組的線性陣列,每個位元組具有一個唯一的實體位址;程式中的位址是由兩部分構(gòu)成的邏輯地址。這種邏輯位址並不能直接用於存取實體記憶體,而需要使用位址變換機(jī)制將它變換或映射到實體記憶體位址上。記憶體管理機(jī)制即用於將這種邏輯位址轉(zhuǎn)換成實體記憶體位址。
為了減少確定位址變換所需要的資訊,變換或映射通常以記憶體塊作為操作單位。分段機(jī)制和分頁機(jī)制是兩種廣泛使用的位址變換技術(shù)。它們的不同之處在於邏輯位址是如何組織成被映射的記憶體區(qū)塊、變換資訊如何指定以及程式設(shè)計人員如何進(jìn)行操作。分段和分頁操作都使用駐留在記憶體中的表來指定它們各自的變換資訊。這些表只能由作業(yè)系統(tǒng)存取,以防止應(yīng)用程式擅自修改。
80X86在從邏輯位址到實體位址變換行程中使用了分段和分頁兩種機(jī)制,見圖4-4所示。第一階段使用分段機(jī)制把程式的邏輯位址變換成處理器可定址記憶體空問(稱為線性位址空間)中的位址。第二階段使用分頁機(jī)制把線性位址轉(zhuǎn)換為實體位址。在位址變換行程中,第一階段的分段變換機(jī)制總是使用的,而第二階段的分頁機(jī)制則是供選用的。如果沒有啟用分頁機(jī)制,那麼分段機(jī)制產(chǎn)生的線性位址空間就直接映射到處理器的實體位址空間上。實體位址空間定義為處理器在其位址匯流排上能夠產(chǎn)生的位址范圍。
⒈ 分段機(jī)制
分段提供了隔絕各個代碼、資料和堆棧區(qū)域的機(jī)制,因此多個程式(或任務(wù))可以執(zhí)行在同一個處理器上而不會互相干擾。分頁機(jī)制為傳統(tǒng)需求頁、虛擬記憶體系統(tǒng)提供了實現(xiàn)機(jī)制。其中虛擬記憶體系統(tǒng)用於實現(xiàn)程式碼按要求被映射到實體記憶體中。分頁機(jī)制當(dāng)然也能用於提供多工之間的隔離措施。
如圖4-5所示,分段提供了一種機(jī)制,用於把處理器可定址的線性位址空間劃分成一些較小的稱為段的受保護(hù)位址空間區(qū)域。段可以用來存放程式的代碼資料和堆棧,或者用來存放系統(tǒng)資料結(jié)構(gòu)(例如TSS或LDT)。如果處理器中有多個程式或任務(wù)在執(zhí)行,那麼每個程式可分配各自的一套段。此時處理器就可以加強(qiáng)這些段之間的界限,並且確保一個程式不會透過存取另一個程式的段而干擾程式的執(zhí)行。分段機(jī)制還允許對段進(jìn)行分類。這樣,對特定類型段的操作能夠受到限制。
一個系統(tǒng)中所有使用的段都包含在處理器線性位址空間中。為了定位指定段中的一個位元組,程式必須提供一個邏輯位址。邏輯位址包括一個段選擇符號和一個偏移量。段選擇符號是一個段的唯一標(biāo)識。另外,段選擇符號提供了段描述符號表(例如全域描述符號表GDT)中一個資料結(jié)構(gòu)(稱為段描述符號)的偏移量。每個段都有一個段描述符號。段描述符號指明段的大小、存取許可權(quán)和段的特權(quán)級、段類型以及段的第l個位元組在線性位址空間中的位置(稱為段的基底位址)。邏輯位址的偏移量部分加到段的基底位址上就可以定位段中某個位元組位置。因此基底位址加上偏移量就形成了處理器線性位址空間中的位址。
線性位址空間與實體位址空間具有相同的結(jié)構(gòu)。相對於二維的邏輯位址空間來說,它們兩者都是一維位址空間。虛擬位址(邏輯位址)空間可包含最多16K的段,而每個段最長可達(dá)4GB,使得虛擬位址空問容量達(dá)到64T。線性位醱和實體位址空間都是4GB(2³²)。實際上,如果禁用分頁機(jī)制,那麼線性位址空間就是實體位址空間。
⒉ 分頁機(jī)制
因為多工系統(tǒng)通常定義的線性位址空間都要比其含有的實體記憶體容量大得多,所以需要使用某種“虛擬化”線性位址空間的方法,即使用虛擬儲存技術(shù)。虛擬儲存是一種記憶體管理技術(shù),使用這種管理技術(shù)可讓程式人員產(chǎn)生記憶體空間要比電腦中實際記憶體容量大很多的錯覺。利用這種錯覺,我們可以隨意編制大型程式而無考慮實際實體記憶體究竟有多少。
分頁機(jī)制支援虛擬儲存技術(shù)。在使用虛擬儲存的環(huán)境中,大容量的線性位址空間需要使用小塊的實體記憶體(RAM或ROM)以及某些外部儲存空間(例如大容量硬碟)來模擬。當(dāng)使用分頁時,每個段被劃分成頁面(通常每頁為4KB大小),頁面會被儲存於實體記憶體中或硬碟上。作業(yè)系統(tǒng)透過維護(hù)一個頁目錄和一些頁表來留意這些頁面。當(dāng)程式(或任務(wù))試圖存取線性位址空間中的一個位址位置時,處理器就會使用頁目錄和頁表把線性位址轉(zhuǎn)換成一個實體位址,然后在該記憶體位置上執(zhí)行所要求的操作(讀或?qū)? 。
如果當(dāng)前被存取的頁面不在實體記憶體中,處理器就會中斷程式的執(zhí)行(透過產(chǎn)生一個頁錯誤異常)。然后作業(yè)系統(tǒng)就可以從硬碟上把該頁面讀入實體記憶體中,並繼續(xù)執(zhí)行剛才被中斷的程式。當(dāng)作業(yè)系統(tǒng)嚴(yán)格實現(xiàn)了分頁機(jī)制時,那麼對於正確執(zhí)行的程式來說頁面在實體記憶體和硬碟之間的交換就是透明的。
80X86分頁機(jī)制最適合支援虛擬儲存技術(shù)。分頁機(jī)制會使用大小固定的記憶體塊,而分段管理則使用了大小可變的區(qū)塊來管理記憶體。無論在實體記憶體中遺是在硬碟上,分頁使用固定大小的區(qū)塊更為適合管理實體記憶體。另一方面,分段機(jī)制使用大小可變的區(qū)塊更適合處理復(fù)雜系統(tǒng)的邏輯分區(qū)??梢远x與邏輯區(qū)塊大小適合的記憶體單元而無需受到固定大小頁面的限制。每個段都可以作為一個單元來處理,從而簡化了段的保護(hù)和共用操作。
分段和分頁是兩種不同的位址變換機(jī)制,它們都對整個位址變換操作提供獨立的處理階段。盡管兩種機(jī)制都使用儲存在記憶體中的變換表,但所用的表結(jié)構(gòu)不同。實際上,段表儲存在線性位址空間,而頁表則保存在實體位址空間。因而段變換表可由分頁機(jī)制重新定位而無需段機(jī)制的資訊或合作。段變換機(jī)制把虛擬位址(邏輯位址)變換成線性位址,並且在線性位址空間中存取自己的表,但是並不知曉分頁機(jī)制把這些線性位址轉(zhuǎn)換到實體位址的行程。類似地,分頁機(jī)制也不知道程式產(chǎn)生位址的虛擬位址空間。分頁機(jī)制只是簡單地把線性位址轉(zhuǎn)換成實體位址,並且在實體記憶體中存取自己的轉(zhuǎn)換表。
4.2.3 保護(hù)
80X86支持兩類保護(hù)。其一是透過給每個任務(wù)不同的虛擬位址(邏輯位址)空間來完全隔離各個任務(wù)。這是透過給每個任務(wù)邏輯位址到實體位址不同的變換映射來做到。另一個保護(hù)機(jī)制對任務(wù)進(jìn)行操作,以保護(hù)作業(yè)系統(tǒng)記憶體段和處理器特殊系統(tǒng)暫存器不被應(yīng)用程式存取。
⒈ 任務(wù)之間的保護(hù)
保護(hù)的一個重要方面是提供應(yīng)用程式各任務(wù)之間的保護(hù)能力。80X86使用的方法是透過把每個任務(wù)放置在不同的虛擬位址空間中,並給予每個任務(wù)不同的邏輯位址到實體位址的變換映射。每個任務(wù)中的位址變換功能被定義成一個任務(wù)中的邏輯位址映射到實體記憶體的一部分區(qū)域,而另一個任務(wù)中的邏輯位址映射到實體記憶體中的不同區(qū)域中。這樣,因為一個任務(wù)不可能生成能夠映射到其他任務(wù)邏輯位址對應(yīng)使用的實體記憶體部分,所以所有任務(wù)都被隔絕開了。只需給每個任務(wù)各自獨立的映射表,每個任務(wù)就會有不同的位址變換函數(shù)。在80X86中,每個任務(wù)都有自己的段表和頁表。當(dāng)處理器切換云執(zhí)行一個新任務(wù)時,任務(wù)切換
的關(guān)鍵部分就是切換到新任務(wù)的變換表。
透過在所有任務(wù)中安排具有相同的虛擬到實體位址映射部分,並且把作業(yè)系統(tǒng)儲存在這個公共的虛擬位址空間部分,作業(yè)系統(tǒng)可以被所有任務(wù)共用。這個所有任務(wù)都具有的相同虛擬位址空間部分被稱為全域位址空間(Global address space) 。這也正是現(xiàn)代Linux作業(yè)系統(tǒng)使用虛擬位址空間的方式。
每個任務(wù)唯一的虛擬位址空間部分被稱為區(qū)域位址空間(Local address space)。區(qū)域位址空間含有需要與系統(tǒng)中其他任務(wù)區(qū)別開的私有的代碼和資料,由於每個任務(wù)中具有不同的區(qū)域位址空間,因此兩個不同任務(wù)中對相同虛擬位址處的引用將轉(zhuǎn)換到不同的實體位址處。這使得作業(yè)系統(tǒng)可以給與每個任務(wù)的記憶體相同的虛擬位址,但仍然能隔絕每個任務(wù)。另一方面,所有任務(wù)在全域位址空間中對相同虛擬位址的引用將被轉(zhuǎn)換到同一個實體位址處。這讓公共代碼和資料(例如作業(yè)系統(tǒng))的共用有了支援。
⒉ 特權(quán)級保護(hù)
在一個任務(wù)中,定義了4個執(zhí)行特權(quán)級(Privilege Levels) ,用於依據(jù)段中含有資料的敏感度以及任務(wù)中不同程式部分的受信程度,來限制對任務(wù)中各段的存取。最敏感的資料被賦予了最高特權(quán)級,它們只能被任務(wù)中最受信任的部分存取。不太敏感的資料被賦予較低的特權(quán)級,它們可以被任務(wù)中較低特權(quán)級的代碼存取。
特權(quán)級用數(shù)字0到3表示,0具有最高特權(quán)級,而3則是最低特權(quán)級。每個記憶體段都與一個特權(quán)級相關(guān)聯(lián)。這個特權(quán)級限制具有足夠特權(quán)級的程式來存取一個段。我們知道,處理器從CS暫存器指定的段中取得和執(zhí)行指令,當(dāng)前特權(quán)級(Current Privilege Level) ,即CPL就是當(dāng)前活動代碼段的特權(quán)級,並且它定義了當(dāng)前所執(zhí)行程式的特權(quán)級別。CPL確定了哪些段能夠被程式存取。
每當(dāng)程式企圖存取一個段時,當(dāng)前特權(quán)級就會與段的特權(quán)級進(jìn)行比較,以確定是否有存取許可。在給定CPL執(zhí)行的一個程式被允許存取同級別的資料段,或者低級別段。任何對高級別段的參照引用都是非法的,並且會引發(fā)一個異常來通知作業(yè)系統(tǒng)。
每個特權(quán)級都有自己的程式堆棧,以避免使用共用堆棧帶來的保護(hù)問題。當(dāng)程式從一個特權(quán)級切換到另一個特權(quán)級上執(zhí)行時,堆棧段也隨之改換到新級別的
堆棧中。
4.3 分段機(jī)制
分段機(jī)制可用於實現(xiàn)多種系統(tǒng)設(shè)計。這些設(shè)計范圍從使用分段機(jī)制的最小功能來保護(hù)程式的平坦模型,到使用分段機(jī)制建立一個可同時可靠地執(zhí)行多個程式(或任務(wù))的具有穩(wěn)固操作環(huán)境的多段模型。
多段模型能夠利用分段機(jī)制全部功能提供由硬體增強(qiáng)的代碼、資料結(jié)構(gòu)、程式和任務(wù)的保護(hù)措施。通常,每個程式(或任務(wù))都使用自己的段描述符號表以及自己的段。對程式來說段能夠完全是私有的,或者是程式之間共用的,對所有段以及系統(tǒng)上執(zhí)行程式各自執(zhí)行環(huán)境的存取都由硬體控制。
存取檢查不僅能夠用來保護(hù)對段界限以外位址的引用,而且也能用來在某些段中防止執(zhí)行不允許的操作。例如,因為代碼段被設(shè)計成是唯讀形式的段,因此可以用硬體來防止對代碼段執(zhí)行寫操作。段中的存取許可權(quán)資訊也可以用來設(shè)置保護(hù)環(huán)或級別。保護(hù)級別可用於保護(hù)作業(yè)系統(tǒng)程式不受應(yīng)用程式非法存取。
4.3.1 段的定義
在上一節(jié)概述中已經(jīng)提到,保護(hù)模式中80X86提供了4GB的實體位址空間。這是處理器在其位址匯流排上可以定址的位址空間。這個位址空間是平坦的,位址范圍從。到0xFFFFFFFF。這個實體位址空間可以映射到讀寫記憶體、唯讀記憶體以及記憶體映射I/O中。分段機(jī)制就是把虛擬位址空間中的虛擬記憶體組織成一些長度可變的稱為段的記憶體塊單元。80386虛擬位址空問中的虛擬位址(邏輯位址)由一個段部分和一個偏移部分構(gòu)成。段是虛擬位址到線性位址轉(zhuǎn)換機(jī)制的基礎(chǔ)。每個段由三個參數(shù)定義:
1.段基底位址(Base address),指定段在線性位址空間中的開始位址。基底位址足線性
位址,對應(yīng)于段中偏移0處。
2.段限長(1imit),是虛擬位址空間中段內(nèi)最大可用偏移位置。它定義了段的長度。
3. 段屬性(Attributes)指定段的特性。例如該段是否可讀、可寫或可作為一個程式執(zhí)行;段的特權(quán)級等。
段限長定義了在虛擬位址空間中段的大小。段基址和段限長定義了段所映響的線性位址范圍或區(qū)域。段內(nèi)0到limit的位址范圍對應(yīng)線性位址中范圍base到base + limit。偏移量大於段限長的虛擬位址是無意義的,如果使用則會導(dǎo)致異常,另,若存取一個段並沒有得到段屬性許可則也會導(dǎo)致異常。例如,如果你試圖寫一個唯讀的段,那麼80386就會產(chǎn)生一個異常。另外,多個段映射到線性位址中的范圍可以部分重疊或覆蓋,甚至完全重疊,見圖4-6所示。在本文章中介紹的Linux 0.1x系統(tǒng)中,一個任務(wù)的代碼段和資料段的段限長相同,並被映射到線性位址完全相同而重疊的區(qū)域上。
段的基底位址、段限長以及段的保護(hù)屬性儲存在一個稱為段描述符號(Segment Descriptor)的結(jié)構(gòu)項中。在邏輯位址到線性位址的轉(zhuǎn)換映射行程中會使用這個段描述符號。段描述符號保存在記憶體中的段描述符號表(Descriptor table)中。段描述符號表是包含段描述符號項的一個簡單陣列。前面介紹的段選擇符號即用於透過指定表中一個段描述符號的位置來指定相應(yīng)的段。
即使利用段的最小功能,使用邏輯位址也能存取處理器位址空間中的每個位元組。邏輯位址由16位元的段選擇符號和32位的偏移量組成,見圖4-7所示。段選擇符號指定位元組所在的段,而偏移量指定該位元組在段中相對于段基底位址的位置。處理器會把每個邏輯位址轉(zhuǎn)換成線性位址。線性位址是處理器線性位址空間中的32位元位址。與實體位址空間類似,線性位址空問也是平坦的4GB位址空間,位址范圍從。到0xFFFFFFFF 。線性位址空間中含有為系統(tǒng)定義的所有段和系統(tǒng)表。
為了把邏輯位址轉(zhuǎn)換成一個線性位址,處理器會執(zhí)行以下操作:
⒈使用段選擇符號中的偏移值(段索引)在GDT或LDT表中定位相應(yīng)的段描述符號。
(僅當(dāng)一個新的段選擇符號載入到段暫存器中時才需要這一步。)
⒉利用段描述符號檢驗段的存取許可權(quán)和范圍,以確保該段是可存取的並且偏移量位於段
界限內(nèi)。
⒊把段描述符號中取得的段基底位址加到偏移量上,最后形成一個線性位址。
如果沒有開啟分頁,那麼處理器直接把線性位址映射到實體位址(即線性位址被送到處理器位址匯流排上)。如果對線性位址空間進(jìn)行了分頁處理,那麼就會使用二級位址轉(zhuǎn)換把線性位址轉(zhuǎn)換成實體位址。頁轉(zhuǎn)換將在稍后進(jìn)行說明。
注譯:
Stack - 堆疊 或者稱呼為 堆棧
Segment - 段
Flag - 旗標(biāo) 或者稱呼為 標(biāo)志
暫存器又稱呼為寄存器