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

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

系統呼叫的三層機制

使用者態、核心態和中斷

  • 使用者態。較低的執行級別,只能訪問一部分記憶體,只能執行一部分指令。
  • 核心態。高階執行級別,可以訪問任意實體記憶體,可以執行特權指令。
  • 中斷。系統從使用者態進入核心態的主要方式。有硬體中斷和軟中斷。系統呼叫就是通過軟中斷進入核心態。

上下文切換

使用者態切換到核心態時,就要把使用者態暫存器上下文儲存起來,同時要把核心態暫存器的值放到當前cpu中。int指令出發中斷機制會在堆疊上儲存一些暫存器的值,會儲存(SAVE_ALL)使用者態棧頂的值當時的狀態字(flag)當時的CS:EIP的值。同時會將核心態的這些暫存器的值載入到cpu。其中核心態CS:EIP指向中斷處理程式的入口,如果是系統呼叫則指向system_call,當中斷結束後執行restore_all和INTERRUPT_RETURN。

API和系統呼叫

API就是系統呼叫的庫函式,是一個函式定義。系統呼叫是通過軟中斷向核心發出了中斷請求,int指令的執行就會觸發一箇中斷請求,一個API可能只對應一個系統呼叫,也可能由多個系統呼叫實現。

Linux中通過int $0x80來觸發系統呼叫的執行,核心給每個系統呼叫一個編號,即系統呼叫號來指明是哪個系統呼叫,通過EAX暫存器傳遞。

無引數系統呼叫

依次通過c語言和內嵌彙編的c語言實現time()函式中封裝的系統呼叫。

time.c

課本上給出的time.c在編譯時遇到如下問題:

檢視struct tm發現應該是mday:

修改後程式碼如下:

修改後順利執行:

內嵌彙編的程式碼執行順利:

帶兩個引數的系統呼叫

同樣執行順利,程式碼如下:

通用庫函式syscall

新增的沒有被封裝好的系統呼叫可以通過syscall(系統呼叫號,引數表)實現,實際上比前兩種方式舒服很多。

於是我們需要知道系統呼叫對應的系統呼叫號,在編譯器中通過檢視syscall.h中引用的unistd.h檔案可以檢視系統定義的系統呼叫號表。

至於為什麼unistd.h中的巨集定義的變數名和我們使用的不一樣,是因為在bits/syscall.h中又進行了一次巨集定義:

這樣應該更有益於程式的可讀性和不同版本的相容性(相容性是官方的解釋,但我現在對不同版本之間的相容不報什麼希望)。

感想

在檢視系統呼叫號表時,實際上系統中有大量的unistd.h,包括i386、x86_64安裝過的mykernel和Linuxkernel以及許多不知道來源的。通常是在/usr/include/asm目錄下,但在我的電腦上甚至沒有asm這個目錄。還好編譯器中連結到的檔案是在搜尋目錄中的,所以能比較快的找到需要的標頭檔案。

之前在程式設計中要用到系統中的命令都是由system(command)實現,現在想來這應該是通過shell的封裝來呼叫的系統函式,效率應該比直接在程式中進行系統呼叫低不少。

一個小問題

這章之後對Linux系統的層次似乎多了些理解。按照我的理解,核心處於最底層,c語言中的系統呼叫和shell中的內建命令都是通過呼叫核心中的函式實現,是對核心中的函式的不同的封裝。C語言中syscall()是直接呼叫核心中的函式,而C語言中的system()函式則是通過呼叫shell間接的呼叫核心中的函式,所以命令可以和終端中的寫法保持一致,傳參也是直接傳字串。希望這麼理解是對的,在以後的實踐中去檢驗。