Linux 使用者態通過中斷切換到核心態詳解
文章目錄
一、使用者態與核心態
-
Linux 把記憶體主要分為 4 個段,分別是核心程式碼段、核心資料段、使用者程式碼段、使用者資料段。
-
核心兩個段特權級都為最高階 0,使用者兩個段特權級都為最低階 3。核心程式碼段可以訪問核心資料段,但不能訪問使用者資料段和使用者程式碼段,同樣地,使用者程式碼段可以訪問使用者資料段,但不能訪問核心資料段或核心程式碼段。
-
當前程序執行的程式碼若屬於核心程式碼段,則稱當前程序處於核心態,若屬於使用者程式碼段,則稱當前程序處於使用者態。使用者程式碼段和核心程式碼段的程式碼分別執行在使用者棧上和核心棧上。
-
處於使用者態的程序若想要切換到核心態,便要依靠中斷。
二、中斷
- CPU 每次執行完一條指令後,總會先檢查有無中斷請求,無則執行下一條指令,有則在總線上讀取中斷的標識碼即中斷向量。
- 記憶體中存放著一箇中斷描述符表(Interrupt Descriptor Table,簡稱 IDT,也可以叫做中斷向量表),表裡有許多箇中斷描述符,所以要在中斷描述符表中找到特定一項中斷描述符需要一個下標,這個下標就是上文提及的中斷向量,就像訪問陣列中的特定元素一樣。每個中斷描述符的內容中有 3 項資訊比較重要——該描述符的特權級,中斷服務函式的入口地址,中斷服務函式所在段的特權級。在 Linux 中,所有的中斷服務函式都放在核心程式碼段,故中斷服務函式所在段的特權級都為 0。
- 那 CPU 通過什麼手段 IDT 在記憶體中的地址呢?答案是暫存器。CPU 有個暫存器叫 IDTR,用於存放 IDT 的首地址和長度,就像 C++
string
類的實現一樣,有一個char*
指標指向字串首地址和一個整形變數記錄字串長度。 - 設當前程序的程式碼段特權級為 ,通過中斷向量找到的中斷描述符的特權級為 ,中斷服務函式所在段的特權級為 ,中斷的特權級檢查有兩道關卡,只有通過了這兩道檢查才能進入中斷的重頭戲——執行中斷服務函式。第一關是 和 的比較,若 ,則進入下一道檢查,否則觸發異常;第二關是 和 的比較,若前者小於等於後者 ,則檢查通過,否則觸發異常。需要特別說明的是,第一道檢查基本上都能通過,上文提到,Linux 的中斷服務函式所在段皆為核心程式碼段,故 為0,且特權級數值只能是 0 到 3 的整數;其次,中斷描述符的特權級大多數為 0,只有 能讓不等式 成立,也就是說大多數情況下能通過第二道檢查的只有核心程式碼段,那使用者程式碼段是不是不用指望觸發中斷了呢?並不是,Linux 中有少數中斷描述符的特權級為 3,這類中斷描述符有 4 個在 IDT 中的下標即中斷向量分別為 3、4、5、128,對應的 4 箇中斷服務函式分別是斷點、溢位、邊界檢查、系統呼叫。
三、任務狀態段
-
任務狀態段(Task State Segment),簡稱 TSS,用於儲存處理器中各種與暫存器有關的重要資訊。
-
只有核心態程式碼段可以訪問 TSS,使用者態程式碼段並沒有這個權力,故作業系統必須用硬體機制攔阻使用者態程式碼段非法訪問 TSS——將 TSS 的特權級設定為最高階 0,特權級為最低階 3 的使用者態程式碼段若要訪問 TSS 便會被保護模式的特權級比較機制半路攔截。
-
任務狀態段存放著當前程序使用者棧的棧頂地址和核心棧的棧頂地址,這兩項資訊是使用者態切換到核心態的關鍵之一。
四、Linux 程序從使用者態切換到核心態的過程
-
第一步,當前程序在使用者態用匯編指令
int <中斷向量號>
發出中斷請求,設中斷向量號為 , 。 -
第二步,CPU 在總線上捕捉到中斷向量 ,通過暫存器 IDTR 找到 IDT,再找到下標為 的中斷描述符,進行兩道特權級檢查。
-
第三步,使用者棧切換到核心棧,切換的關鍵在於棧地址的切換,而棧地址由某些特定暫存器的值共同決定,故關鍵在於改變這些特定暫存器的值。如何改變?依靠 TSS——由於 TSS 存放著核心棧的棧地址,把 TSS 裡的核心棧棧地址賦給這些特定暫存器,便能完成從使用者棧到核心棧的切換,即完成了使用者態到核心態的切換。
-
補充一點,棧切換成功後,還有許多操作,在此強調兩處操作,一是將使用者棧棧地址儲存在核心棧上,以便從核心棧切換回使用者棧,二是取出第二步訪問過的中斷描述符的中斷服務函式的入口地址將其儲存在核心棧上,並跳轉到中斷服務函式開始執行。