2018-2019-1 20189206 《Linux核心原理與分析》第五週作業
linux核心分析學習筆記 ——第四章 系統呼叫的三層機制
學習重點——系統呼叫
使用者態、核心態和中斷
- Intel x86 CPU有四種不同的執行級別,分別是0,1,2,3其中數字越小,特權越高。
- Linux作業系統只採用了其中的0和3兩個特權級別,分別對應核心態和使用者態。
- 核心態:對應高執行級別,程式碼可以執行特權指令,訪問任意實體記憶體,CPU執行級別對應的核心態。
核心態的CS:EIP指向範圍是任意地址 - 使用者態:對應底執行級別,程式碼能夠掌控的範圍會受到限制。
使用者態時,以32位x86機器為例,4G的程序地址空間只能訪問0x000000~0xbfffffff的地址空間。
- 核心態:對應高執行級別,程式碼可以執行特權指令,訪問任意實體記憶體,CPU執行級別對應的核心態。
- 每個程序都有一個4G大小的虛擬地址空間,在這個4G大小的虛擬地址空間中,前0~3G為使用者空間,每個程序的使用者空間之間是相互獨立的,互不相干。
- Linux作業系統只採用了其中的0和3兩個特權級別,分別對應核心態和使用者態。
以下圖示表示x86 32位機器的程序記憶體
- 中斷
- 進入核心態一般是由終端觸發的,以下時進入核心態的兩種情況
- 硬體中斷,在使用者程序執行時,硬體中斷訊號到來進入核心態,就會執行這個中斷對應的中斷服務歷程。
- 使用者態程式執行過程中,呼叫了一個系統中斷,陷入核心態(Trap)。系統呼叫就是一種特殊的中斷
- 從使用者態到核心態的暫存器上下文的切換
- 從使用者態切換到核心態,將使用者態暫存器的上下文儲存起來,同時將核心態暫存器的值放入當前CPU中。
- int指令觸發中斷機制
- 會在核心棧內儲存一些暫存器的值
- 包括 使用者態棧頂地址%esp 當前狀態字 當前CS:EIP的值
- 同時會將核心態的棧頂地址、核心態狀態字放入CPU對應的暫存器中。CS:EIP指向中斷處理程式的入口,對於系統來講就是system_call
- 包括 使用者態棧頂地址%esp 當前狀態字 當前CS:EIP的值
- 會在核心棧內儲存一些暫存器的值
中斷處理的過程
Linux系統呼叫通過中斷向量0x80實現
int 0x80
,中斷儲存了使用者態CS:EIP的值,及當前堆疊段暫存器的棧頂(注意在這裡的棧頂是使用者棧的棧頂,入棧到核心棧- 完成中斷服務程式,發生程序排程
- 如果沒有發生程序排程,直接恢復現場,
iret
回到原來的狀態。 - 如果發生了程序排程,當前這些狀態暫時儲存在核心堆疊中,下一次發生程序排程再切換回當前程序。
- 如果沒有發生程序排程,直接恢復現場,
- 完成中斷服務程式,發生程序排程
- 進入核心態一般是由終端觸發的,以下時進入核心態的兩種情況
系統呼叫
概述
系統呼叫的意義是作業系統為使用者態程序與硬體裝置之間進行互動提供了一組介面。
作業系統的主要功能是為管理硬體資源和為應用程式開發人員提供良好的環境來使應用程式具有更好的相容性,為了達到這個目的,核心提供一系列具備預定功能的多核心函式,通過一組稱為系統呼叫(system call)的介面呈現給使用者。系統呼叫把應用程式的請求傳給核心,呼叫相應的的核心函式完成所需的處理,將處理結果返回給應用程式
- 把使用者從底層的硬體程式設計中解放出來
- 極大的提高了系統的安全性
- 使使用者程式具有可移植性
系統呼叫的3層機制
系統呼叫的庫函式就是我們使用的作業系統提供的API,系統呼叫是通過軟中斷向核心發出中斷請求,int指令觸發中斷請求。
libc函式庫內部定義的一些API內部就使用了系統呼叫的封裝歷程。
每個系統呼叫對應一個系統呼叫的封裝例程,函式庫再使用這些封裝例程定義給出程式設計師可以呼叫的API一個API可能只對應一個系統呼叫,也可能由多個系統呼叫實現。
如上圖所示,在使用者態中
- 使用者態中的
xyz()
函式屬於API函式 - 該API中
SYSTEMCALL
就是一個系統呼叫的封裝例程由作業系統給,出會觸發int $0x80
中斷
在核心態中
- 觸發中斷後進入核心態,
system_call
對應核心程式碼的起點,即中斷向量0x80對應的終端服務程式的入口 - 核心程式碼中的
sys_xyz()
系統呼叫處理函式- 處理結束後,如果發生程序排程會
ret_from_sys_call
- 如果沒有發生程序排程,會執行
iret
返回使用者態繼續執行
- 處理結束後,如果發生程序排程會
觸發系統呼叫及引數傳遞方式
- 觸發系統呼叫的方式
- 當用戶執行系統呼叫時,CPU切換到核心態執行
system_call
這是中斷的入口函式也是核心程式碼的起點。 - linux中使用0x80觸發系統呼叫所對應的中斷異常。
- 核心通過給每個系統呼叫一個編號來區分,即系統呼叫號,實現將API函式
xyz()
和系統呼叫核心函式sys_xyz()
- 使用者程序必須指明需要哪一個系統呼叫,需要使用EAX暫存器
- 當用戶執行系統呼叫時,CPU切換到核心態執行
- 引數傳遞的方式
- 系統呼叫從使用者態切換到核心態時使用的不同的堆疊,所以引數的傳遞無法通過引數壓棧的方式進行傳遞。
- 引數按照順序賦值給EBX ECX EDX ESI EDI EBP 引數的個數不能超過6個暫存器。如果引數過多,就把暫存器作為指標指向記憶體,以傳遞更多的引數
實驗:使用兩種方法實現觸發系統呼叫
使用庫函式API觸發系統呼叫
利用上面的程式碼實現的是檢視當前程序的pid和其父程序的pid,首先是利用庫函式提供的API實現檢視。下面是執行結果。
使用內嵌彙編的方式實現系統呼叫
將上面的呼叫API轉換成內嵌彙編程式碼的方式。通過查詢可以看到對應的作業系統給出的系統呼叫的封裝對應的系統呼叫號
下面對彙編程式碼解釋其含義
movl $0,%%ebx\n\t
表示傳入引數,這裡不需要傳遞引數,就將EBX暫存器清零
movl $0x14,%%eax\n\t
EAX用於傳遞系統呼叫號,表示這裡呼叫的是20號系統呼叫。
int $0x80\n\t
是觸發系統呼叫陷入核心執行20號系統呼叫的核心處理函式
movl %eax,%0\n\t
系統呼叫會有一個返回值,通過EAX暫存器返回。
這樣就完成了系統呼叫。
通用的觸發系統呼叫函式
當libc沒有提供某個系統呼叫的封裝,可以利用libc提供的syscall函式直接呼叫