­

羽夏看Linux內核——中斷與分頁相關入門知識

寫在前面

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。如有好的建議,歡迎回饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並聲明我的個人資訊和本人部落格地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Linux系統內核——簡述 ,方便學習本教程。

中斷

  中斷通常是由CPU外部的輸入輸出設備(硬體)所觸發的,供外部設備通知CPU有事情需要處理,因此又叫中斷請求,英文為Interrupt Request。中斷請求的目的是希望CPU暫時停止執行當前正在執行的程式,轉去執行中斷請求所對應的中斷處理常式,中斷處理程式由哪有IDT表決定。
  80x86有兩條中斷請求線:非屏蔽中斷線,NMI,全稱NonMaskable Interrupt和可屏蔽中斷線,INTR,全稱Interrupt Require

不可屏蔽中斷

  什麼是不可屏蔽中斷CPUEFLAG之中有一個位,它是IF位。如果它被置0。如果有可屏蔽中斷告訴CPU有中斷來了,你能先執行我的程式碼呢?可是IF位是0,對不起,我聽不見。左耳朵進,右耳朵出。反之,我會處理。常見的不可屏蔽中斷有電腦長按關機、鍵盤輸入等等。當非可屏蔽中斷產生時,CPU在執行完當前指令後會裡面進入中斷處理程式,非可屏蔽中斷不受那個位的影響,一旦發生,CPU必須處理。為了方便觀看,給個EFLAG圖解:

  那麼CPU是如何處理我們的不可屏蔽中斷呢?我們先來看如下表格:

(IDT表)中斷號 NMI 說明
0x2 不可屏蔽中斷 80×86 中固定為 0x2

  如果處理不可屏蔽中斷,CPU會調用2號中斷。涉及的IDT表和中斷門的知識如果忘卻請查看前面的教程。

可屏蔽中斷

  什麼是可屏蔽中斷,我就不贅述了。在硬體級,可屏蔽中斷是由一塊專門的晶片來管理的,通常稱為中斷控制器。它負責分配中斷資源和管理各個中斷源發出的中斷請求.為了便於標識各個中斷請求,中斷管理器通常用IRQ,全稱為Interrupt Request,後面加上數字來表示不同的中斷。
  那麼CPU是如何處理我們的可屏蔽中斷呢?我們先來看如下表格:

(IDT表)中斷號 IRQ 說明
0x30 IRQ0 時鐘中斷
0x31-0x3F IRQ1-IRQ15 其他硬體設備的中斷

  如果自己的程式執行時不希望CPU去處理這些中斷,可以用CLI指令清空EFLAG暫存器中的IF位,用STI指令設置EFLAG暫存器中的IF位。
  硬體中斷與IDT表中的對應關係並非固定不變的,可以參考白皮書的Chapter 10 Advanced Programmable Interrupt Controller(APIC)進行了解。

異常

  異常通常是CPU在執行指令時檢測到的某些錯誤,比如除0、訪問無效頁等。中斷與異常之間有一些相似之處,但它們是不一樣的:中斷來自於外部設備,是中斷源(比如鍵盤)發起的,CPU是被動的;而異常來自於CPU本身,是CPU主動產生的。INT N雖然被稱為「軟體中斷」,但其本質是異常,EFLAGIF位對INT N是無效。

異常處理

  無論是由硬體設備觸發的中斷請求還是由CPU產生的異常,處理程式都在IDT表。常見的異常處理程式如下表所示:

錯誤類型 (IDT表)中斷號
頁錯誤 0xE
段錯誤 0xD
除零錯誤 0x0
雙重錯誤 0x8

控制暫存器

  控制暫存器用於控制和確定CPU的操作模式。控制暫存器有Cr0Cr1Cr2Cr3Cr4Cr1被保留了,Cr3用於頁目錄表基址,其他的將繼續詳細講解。

Cr0

  Cr0是一個十分重要的暫存器,可以說它是總開關的集合體。如下圖所示:

  PE位是啟用保護模式(Protection Enable)標誌。若PE = 1是開啟保護模式,反之為實地址模式。這個標誌僅開啟段級保護,而並沒有啟用分頁機制。若要啟用分頁機制,那麼PEPG標誌都要置位。
  PG位是啟用分頁機制。在開啟這個標誌之前必須已經或者同時開啟PE標誌。PG = 0PE = 0,處理器工作在實地址模式下。PG = 0PE = 1,處理器工作在沒有開啟分頁機制的保護模式下。PG = 1PE = 0,在PE沒有開啟的情況下無法開啟PGPG = 1PE = 1,處理器工作在開啟了分頁機制的保護模式下。
  WP位對於Intel 80486或以上的CPU,是防寫(Write Proctect)標誌。當設置該標誌時,處理器會禁止超級用戶程式(例如特權級0的程式)向用戶級只讀頁面執行寫操作;當CPL < 3的時候,如果WP = 0可以讀寫任意用戶級物理頁,只要線性地址有效。如果WP = 1可以讀取任意用戶級物理頁,但對於只讀的物理頁,則不能寫。

Cr2

  當CPU訪問某個無效頁面時,會產生缺頁異常,此時,CPU會將引起異常的線性地址存放在CR2中,如下圖所示:

Cr4

  Cr4的結構如下圖所示:

  VME用於虛擬8086模式。PAE用於確認是哪個分頁,PAE = 1,是2-9-9-12分頁,PAE = 010-10-12分頁。PSE是大頁是否開啟的總開關,如果置0,就算PDE中設置了大頁你也得是普通的頁。至於分頁到底是什麼,將會在下一篇進行講解。

中斷小節

  有些結構的位我並沒有詳細介紹,詳情請查看白皮書的控制暫存器的篇章,如下圖所示:

  如果對分頁不了解看不懂沒關係,下一篇將會介紹相關知識。

分頁

  在講解分頁基礎之前,我們先大體了解CPU是如何在保護模式下訪問數據的,如下圖所示:

  比如我們執行mov eax,ds:[0x12345678]這句彙編指令的時候,0x12345678這個線性地址會傳遞給CPU,先查詢TLB快取有沒有,有的話直接取出來返回;如果沒有,經過MMU(記憶體管理單元)處理得到物理地址,通過固定的分頁模式直接找到,取出數據返回。
  前面的教程講解了段的機制,接下來將介紹頁的機制。CPU為了方便管理物理記憶體,按照頁的方式進行管理記憶體。可用的所有記憶體可以類比為一本書,而所有的記憶體被分為這本書的一個頁。對於32位來說,它有10-10-12分頁和2-9-9-12分頁。其中10-10-12分頁最為簡單,故拿其作為詳細講解,作為分頁講解的基礎。
  我們都了解一個進程都有4GB的虛擬地址空間,它們並不是真正的地址,而是個索引。它通過某種方式進行轉換,從而指向真正的物理地址,示意圖如下所示:

  而虛擬地址也被稱作線性地址。舉個例子,比如某個進程裡面我想讀取一個0x12345678,它就是線性地址,通過一些轉換,找到了對應的物理地址0x10101010,如下圖所示:

  每個進程都有一個CR3,準確的說是都一個CR3的值。CR3本身是個暫存器,一核一套。CR3裡面放的是一個真正的物理地址,指向一個物理頁,一共4096位元組,如下圖所示:

  對於10-10-12分頁來說,線性地址對應的物理地址是有對應關係的,它被分成了三個部分,每個部分都有它具體的含義。線性地址分配的結構如下圖所示:

  第一個部分指的是PDEPDT的索引,第二部分是PTEPTT的索引,第三個部分是在PTE指向的物理頁的偏移。PDT被稱為頁目錄表,PTT被稱為頁表。PDEPTE分別是它們的成員,大小為4個位元組。我們接下來將詳細介紹每一個部分是咋用的。

10-10-12 分頁整體結構

  通過實驗我們了解了它們的結構,接下來將詳細介紹了。根據實驗結果的體驗,可以給出如下圖:

  分頁並不是由作業系統決定的,而是由CPU決定的。只是作業系統遵守了CPU的約定來實現的。物理頁是什麼?物理頁是作業系統對可用的物理記憶體的抽象,按照4KB的大小進行管理(Intel是按照這個值做的,別的CPU就不清楚了),和真實硬體層面上的記憶體有一層的映射關係,這個不是保護模式的範疇,故不介紹。

PDE 與 PTE

  前面我們簡單了解PDEPTE,接下來將學習它們的屬性結構,結構如下:

P 位

  表示PDE或者PTE是否有效,如果有效為1,反之為0

R/W 位

  如果R/W = 0,表示是只讀的,反之為可讀可寫。

U/S 位

  如果U/S = 0,則為特權用戶(super user),即非3環許可權。反之,則為普通用戶,即為3環許可權。

PS位

  這個位只對PDE有意義。如果PS == 1,則PDE直接指向物理頁,不再指向PTE,低22位是頁內偏移。它的大小為4MB,俗稱「大頁」。

A 位

  是否被訪問,即是否被讀或者寫過,如果被訪問過則置1

D 位

  臟位,指示是否被寫過。若沒有被寫過為0,被寫過為1

注意,下面的三個位的講解將涉及 TLB 和控制暫存器相關知識,為了保證文章的完整性,故先介紹。之後將會詳細講解。

G 位

  表示是否為全局頁。它的作用是什麼呢?舉個例子,作業系統的進程的高2G映射基本不變,如果Cr3改了,TLB刷新重建高2G以上很浪費。所以PDEPTE中有個G位,如果為1,刷新TLB時將不會刷新它指向的頁。

PWT 位

  當PWT = 1,寫快取的時候也要將數據寫入記憶體中。

PCD 位

  當PCD = 1時,禁止某個頁寫入快取,直接寫記憶體。比如,做頁表用的頁,已經存儲在TLB中了,可能不需要再快取了。

注意事項

  • PTE可以沒有物理頁,且只能對應一個物理頁。
  • 多個PTE也可以指向同一個物理頁。
  • PDEPTE重合的屬性共同決定著最終物理頁的屬性。比如P位,如果有一個是0,那麼最終的物理頁就是無效的。但是PDEPTE它們的屬性的影響範圍是不一樣的。數值上:物理頁的屬性 = PDE屬性 & PTE屬性。

PAE 分頁

  PAE分頁是啥,其實他就是2-9-9-12分頁的英文縮寫。為什麼要有2-9-9-12分頁,其實還是物理頁不夠用了,需要擴展。但想要足夠的物理頁,位數在那裡,你想大也大不了。那麼我就需要擴展物理頁地址的位數,於是乎2-9-9-12分頁誕生了,它整體分頁的結構如下:

  與10-10-12分頁不同的地方就是,多了一層名為頁目錄指針表的東西,英文縮寫為PDPTT。每個PDEPTE被擴展為8個位元組,物理地址描述的位數擴展為24位,故可以描述更多的物理頁,但個數減半,變成了512個。下面詳細查看它們的結構。
  首先看PDPTT的結構。由2-9-9-122可知第一部分由兩位二進位組成,那麼最多有4種結果。也就是為什麼有五個成員,它的結構如下圖所示:

  然後是PDE,既然學過了10-10-12分頁,直接看下面的結構示意圖吧:

非大頁

大頁

  然後是PTE,同理不多說了:

  我們之前做一道作業題目知道,我寫的shellcode寫到一個頁上,它並沒有執行許可權,但它不是程式碼,仍然可以被我執行。為了彌補這個漏洞,Intel給我們補了一個硬體層面上的漏洞,它是一個位,處於PDEPTE的最高位,如下圖所示:

  如果最高位是1,說明被保護。如果這個是數據,且這個X位被置為1,則會被報出異常不能執行。反之,和正常的10-10-12分頁沒什麼兩樣。
  一個進程的線性地址仍是4GB的線性空間,有再多的物理頁有啥用呢?在10-10-12分頁下,假設進程一啟動,就把所有的物理頁都掛上,且沒有任何交換。那麼只能啟動一個;如果在2-9-9-12分頁下,同樣的情況,它可以啟動4個進程。這個就是2-9-9-12分頁的意義。

TLB

  CPU通過頁的方式對物理記憶體進行了,需要通過某種運算方式才能訪問真正的記憶體。但是,如果頻繁訪問某一個線性地址,每次都得通過推算到真正的物理地址然後進行讀寫操作,是不是挺浪費效率的?Intel就考慮到性能的問題,提供了TLB這一個機制,提供快取提高讀寫效率。
  TLB的全稱為Translation Lookaside Buffer,它的結構如下:

LA(線性地址) PA(物理地址) ATTR(屬性) LRU(統計)

  對於TLB,給出如下說明:
  1. ATTR(屬性):如果是2-9-9-12分頁,屬性是PDPEPDEPTE三個屬性共同決定的。如果是10-10-12分頁就是PDEPTE共同決定。
  2. 不同的CPU這個表的大小不一樣。
  3. 只要Cr3變了,TLB立馬刷新,一核一套TLB
  如果Cr3改了,TLB刷新重建高2G以上很浪費。所以PDEPTE中有個G標誌位,如果G位為1刷新TLB時將不會刷新PDE/PTEG位為1的頁,當TLB滿了,根據統計資訊將不常用的地址廢棄,最近最常用的保留。
  TLB有不同的種類,用於不同的快取目的,它在X86體系里的實際應用最早是從Intel486CPU開始的,在X86體系的CPU裡邊,一般都設有如下4組TLB
  第一組:快取一般頁表(4K位元組頁面)的指令頁錶快取:Instruction-TLB
  第二組:快取一般頁表(4K位元組頁面)的數據頁錶快取:Data-TLB
  第三組:快取大尺寸頁表(2M/4M位元組頁面)的指令頁錶快取:Instruction-TLB
  第四組:快取大尺寸頁表(2M/4M位元組頁面)的數據頁錶快取:Data-TLB

CPU快取

  CPU快取是位於CPU與物理記憶體之間的臨時存儲器,它的容量比記憶體小的多但是交換速度卻比記憶體要快得多。它可以做的很大,但不是TLB,它們有很大的不同。TLB存的是線性地址與物理地址的對應關係,CPU快取存的是物理地址與內容對應關係。
  更多的細節請參考白皮書的Chapter 11 Memory Cache Control,本篇教程主要是針對內核安全層面,就不再贅述了。

PWT 與 PCD

  PWT全稱為Page Write ThroughPWT = 1時,寫Cache的時候也要將數據寫入記憶體中。
  PCD全稱為Page Cache DisablePCD = 1時,禁止某個頁寫入快取,直接寫記憶體。比如,做頁表用的頁,已經存儲在TLB中了,可能不需要再快取了。

下一篇

  羽夏看Linux內核——內核載入