羽夏看Linux內核——中斷與分頁相關入門知識
- 2022 年 8 月 9 日
- 筆記
- Linux 系統內核, 羽夏看Linux內核
寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。如有好的建議,歡迎回饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並聲明我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Linux系統內核——簡述 ,方便學習本教程。
中斷
中斷通常是由CPU
外部的輸入輸出設備(硬體)所觸發的,供外部設備通知CPU
有事情需要處理,因此又叫中斷請求,英文為Interrupt Request
。中斷請求的目的是希望CPU
暫時停止執行當前正在執行的程式,轉去執行中斷請求所對應的中斷處理常式,中斷處理程式由哪有IDT表決定。
80x86
有兩條中斷請求線:非屏蔽中斷線,NMI
,全稱NonMaskable Interrupt
和可屏蔽中斷線,INTR
,全稱Interrupt Require
。
不可屏蔽中斷
什麼是不可屏蔽中斷
?CPU
的EFLAG
之中有一個位,它是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
雖然被稱為「軟體中斷」,但其本質是異常,EFLAG
的IF
位對INT N
是無效。
異常處理
無論是由硬體設備觸發的中斷請求還是由CPU
產生的異常,處理程式都在IDT
表。常見的異常處理程式如下表所示:
錯誤類型 | (IDT表)中斷號 |
---|---|
頁錯誤 | 0xE |
段錯誤 | 0xD |
除零錯誤 | 0x0 |
雙重錯誤 | 0x8 |
控制暫存器
控制暫存器用於控制和確定CPU的操作模式。控制暫存器有Cr0
、Cr1
、Cr2
、Cr3
、Cr4
。Cr1
被保留了,Cr3
用於頁目錄表基址,其他的將繼續詳細講解。
Cr0
Cr0
是一個十分重要的暫存器,可以說它是總開關的集合體。如下圖所示:
PE
位是啟用保護模式(Protection Enable)標誌。若PE = 1
是開啟保護模式,反之為實地址模式。這個標誌僅開啟段級保護,而並沒有啟用分頁機制。若要啟用分頁機制,那麼PE
和PG
標誌都要置位。
PG
位是啟用分頁機制。在開啟這個標誌之前必須已經或者同時開啟PE
標誌。PG = 0
且PE = 0
,處理器工作在實地址模式下。PG = 0
且PE = 1
,處理器工作在沒有開啟分頁機制的保護模式下。PG = 1
且PE = 0
,在PE
沒有開啟的情況下無法開啟PG
。PG = 1
且PE = 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 = 0
是10-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
分頁來說,線性地址對應的物理地址是有對應關係的,它被分成了三個部分,每個部分都有它具體的含義。線性地址分配的結構如下圖所示:
第一個部分指的是PDE
在PDT
的索引,第二部分是PTE
在PTT
的索引,第三個部分是在PTE指向的物理頁的偏移。PDT
被稱為頁目錄表,PTT
被稱為頁表。PDE
和PTE
分別是它們的成員,大小為4個位元組。我們接下來將詳細介紹每一個部分是咋用的。
10-10-12 分頁整體結構
通過實驗我們了解了它們的結構,接下來將詳細介紹了。根據實驗結果的體驗,可以給出如下圖:
分頁並不是由作業系統決定的,而是由CPU
決定的。只是作業系統遵守了CPU
的約定來實現的。物理頁是什麼?物理頁是作業系統對可用的物理記憶體的抽象,按照4KB
的大小進行管理(Intel
是按照這個值做的,別的CPU
就不清楚了),和真實硬體層面上的記憶體有一層的映射關係,這個不是保護模式的範疇,故不介紹。
PDE 與 PTE
前面我們簡單了解PDE
和PTE
,接下來將學習它們的屬性結構,結構如下:
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
以上很浪費。所以PDE
和PTE
中有個G
位,如果為1,刷新TLB
時將不會刷新它指向的頁。
PWT 位
當PWT = 1
,寫快取的時候也要將數據寫入記憶體中。
PCD 位
當PCD = 1
時,禁止某個頁寫入快取,直接寫記憶體。比如,做頁表用的頁,已經存儲在TLB中了,可能不需要再快取了。
注意事項
PTE
可以沒有物理頁,且只能對應一個物理頁。- 多個
PTE
也可以指向同一個物理頁。 PDE
和PTE
重合的屬性共同決定著最終物理頁的屬性。比如P位
,如果有一個是0,那麼最終的物理頁就是無效的。但是PDE
和PTE
它們的屬性的影響範圍是不一樣的。數值上:物理頁的屬性 = PDE屬性 & PTE屬性。
PAE 分頁
PAE
分頁是啥,其實他就是2-9-9-12
分頁的英文縮寫。為什麼要有2-9-9-12
分頁,其實還是物理頁不夠用了,需要擴展。但想要足夠的物理頁,位數在那裡,你想大也大不了。那麼我就需要擴展物理頁地址的位數,於是乎2-9-9-12
分頁誕生了,它整體分頁的結構如下:
與10-10-12
分頁不同的地方就是,多了一層名為頁目錄指針表
的東西,英文縮寫為PDPTT
。每個PDE
和PTE
被擴展為8個位元組,物理地址描述的位數擴展為24位
,故可以描述更多的物理頁,但個數減半,變成了512個。下面詳細查看它們的結構。
首先看PDPTT
的結構。由2-9-9-12
的2
可知第一部分由兩位二進位組成,那麼最多有4種結果。也就是為什麼有五個成員,它的結構如下圖所示:
然後是PDE
,既然學過了10-10-12
分頁,直接看下面的結構示意圖吧:
非大頁
大頁
然後是PTE
,同理不多說了:
我們之前做一道作業題目知道,我寫的shellcode
寫到一個頁上,它並沒有執行許可權,但它不是程式碼,仍然可以被我執行。為了彌補這個漏洞,Intel
給我們補了一個硬體層面上的漏洞,它是一個位,處於PDE
和PTE
的最高位,如下圖所示:
如果最高位是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
分頁,屬性是PDPE
、PDE
、PTE
三個屬性共同決定的。如果是10-10-12
分頁就是PDE
和PTE
共同決定。
2. 不同的CPU
這個表的大小不一樣。
3. 只要Cr3
變了,TLB
立馬刷新,一核一套TLB
。
如果Cr3
改了,TLB
刷新重建高2G以上很浪費。所以PDE
和PTE
中有個G
標誌位,如果G
位為1刷新TLB
時將不會刷新PDE/PTE
的G
位為1的頁,當TLB
滿了,根據統計資訊將不常用的地址廢棄,最近最常用的保留。
TLB
有不同的種類,用於不同的快取目的,它在X86
體系里的實際應用最早是從Intel
的486CPU
開始的,在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 Through
,PWT = 1
時,寫Cache
的時候也要將數據寫入記憶體中。
PCD
全稱為Page Cache Disable
,PCD = 1
時,禁止某個頁寫入快取,直接寫記憶體。比如,做頁表用的頁,已經存儲在TLB
中了,可能不需要再快取了。
下一篇
羽夏看Linux內核——內核載入