1. 程式人生 > >2018-2019-1 20189213《Linux核心原理與分析》第五週作業

2018-2019-1 20189213《Linux核心原理與分析》第五週作業

第四章:系統呼叫的三層機制(上)

系統呼叫的"三層皮"

分別指的是:使用者態函式(API)、system_call(中斷服務程式入口)以及sys_xyz()系統呼叫處理函式封裝例程。它們各自的作用如下:

API

第一層是指Libc中定義的API,這些API封裝了系統呼叫,使用int0x80觸發一個系統呼叫中斷;
當然,並非所有的API都使用了系統呼叫,如完成數學加減運算的API就沒有使用系統呼叫;
也有可能某個API使用了多個系統呼叫;
這一層的作用就是為應用程式設計師提供易於使用的API來呼叫系統呼叫;

system_call

它運行於核心態。system_call是所有系統呼叫在核心的入口點,在其中的開始處保護使用者態程式執行上下文,結束處恢復使用者態程式執行上下文,在中間根據傳入的系統呼叫號對應的中斷服務程式;

sys_xyz()系統呼叫封裝例程

執行具體的系統呼叫操作,完成使用者的系統呼叫請求;每個系統呼叫都對應一個封裝例程。
由上面的分析我們可以知道,理論上要請求一個系統呼叫,我們既可以使用Libc提供的API,也可以直接在C中內嵌彙編程式碼觸發0x80中斷來完成。

下面實驗,我們就用實際的例子來演示這兩種方法使用同一個系統的呼叫:

getpid和getuid

首先我們選擇的是比較簡單的系統呼叫sys_pid(程序ID)(),sys_uid(使用者ID),通過查閱系統呼叫列表發現它對應的系統呼叫號分別是20,24;
利用這個系統呼叫可以在螢幕上輸出程序id和使用者id;而這兩個系統呼叫函式對應的API就是getpid和getuid;
第一步我們首先使用C語言程式碼實現:
C語言程式碼如下圖所示:


然後執行後我們發現程序id與使用者id已輸出:

接下來我們運用C語言中內嵌彙編程式碼來實現呼叫getpid和getuid系統呼叫:
下圖是內嵌彙編程式碼的具體程式碼:

然後是編譯成功介面:

可以看出兩種方法都輸出了程序id和使用者id;
引數傳遞的過程:
以getpid系統呼叫為例,首先是系統呼叫傳遞第一個引數使EBX暫存器的值為0,然後使用eax暫存器傳遞系統呼叫號20,然後用int 0x80觸發系統呼叫,最後通過eax暫存器返回系統呼叫值。

rename

後面我們又選擇了系統呼叫sys_rename(),它含有兩個引數,通過查閱系統呼叫列表發現它對應的系統呼叫號是38;
利用這個系統呼叫可以給一個檔案重新命名;而這個系統呼叫函式對應的API就是rename。
第一步我們使用C語言程式碼呼叫rename系統呼叫:
C語言程式碼如下圖所示:


然後執行後我們發現原有資料夾hello.c改變成了newhello.c:

接下來我們運用C語言中內嵌彙編程式碼來實現呼叫rename系統呼叫:
下圖是內嵌彙編程式碼的具體程式碼:

然後是編譯成功執行介面:

可以看出hello.c也成功改為了newhello.c。
兩種方法都實現了給資料夾重新命名的功能,但C語言中內嵌彙編涉及了很多引數。
下面分析rename系統呼叫引數的傳遞:
首先是將oldname存入EBX暫存器,將newname存入ECX暫存器,這是由多個引數需要存入暫存器時按照EBX、ECX、EDX、ESI、EDI、EBP的順序儲存的;
然後將系統呼叫號38存入EAX暫存器,接著執行int 0X80來執行系統呼叫陷入核心態。其中system_call根據傳入的系統呼叫號查詢對應系統呼叫核心函式,根據EBX和ECX暫存器傳入的引數呼叫核心函式sys_rename,執行完後將執行結果存放到EAX暫存器中,並同時將EAX的值傳給ret。

總結:

即便是最簡單的程式,也難免要用到諸如輸入、輸出以及退出等操作,而要進行這些操作則需要呼叫作業系統所提供的服務,也就是系統呼叫。
Linux下的系統呼叫是通過中斷(int 0x80)來實現的。在執行int 0x80 指令時,暫存器eax 中存放的是系統呼叫的功能號,而傳給系統呼叫的引數則必須按順序放到暫存器ebx,ecx,edx,esi,edi中,當系統呼叫完成之後,返回值可以在暫存器 eax中獲得。
所有的系統呼叫功能號都可以在相關網站中找到,為了便於使用,它們是用 SYS_