2018-2019-1 20189203《Linux核心原理與分析》第五週作業
第一部分 課本學習
使用者態、核心態和中斷
- 1.核心態:處於高的執行級別下,程式碼可以執行特權指令,訪問任意的實體地址,這時的CPU就對應核心態,對所有的指令包括特權指令都可以執行。
- 2.使用者態:處於低的執行級別下,程式碼只能在級別允許的特定範圍內活動。在日常操作下,執行系統呼叫的方式是通過庫函式,庫函式封裝系統呼叫,為使用者提供介面以便直接使用。
-3.Intel x86 CPU有4種不同的執行級別0、1、2、3,Linux只使用了其中的0和 3兩個級別分別表示核心態和使用者態。使用者態和核心態很顯著的區別方法就是CS:EIP的指向範圍,核心態cs:eip的值是任意的,即可以訪問所有的地址空間。使用者態只能訪問其中的一部分記憶體地址(0x00000000-0xbbbbbbbf),0xc0000000以上的地址只能在核心態下訪問。 4.中斷處理是從使用者態進入核心態的主要方式,系統呼叫是一種特殊的中斷。從使用者態切換到核心態時,中斷/int指令會在堆疊上儲存使用者態的暫存器上下文,其中包括使用者態棧頂地址、當時的狀態字、當時的cs:eip的值,還有核心態的棧頂地址、核心態的狀態字、中斷處理程式的入口。中斷髮生後的第一件事就是儲存現場,儲存一系列的暫存器的值;中斷處理結束前的最後一件事就是恢復現場,退出中斷程式,恢復儲存暫存器的資料。
系統呼叫
- 1.系統呼叫的意義:把使用者從底層的硬體程式設計中解放出來、極大地提高系統的安全性、使使用者程式具有可移動性。
- 2.系統呼叫的庫函式就是讀者使用的作業系統提供的API(應用程式程式設計介面),API只是函式定義。系統呼叫是通過軟中斷向核心發出了中斷請求,int指令的執行就會觸發一箇中斷請求。Libc庫函式定義的一些API內部使用了系統呼叫的封裝例程,其主要目的是釋出系統呼叫,使程式設計師在寫程式碼時不需要用匯編指令和暫存器 傳遞引數來觸發系統呼叫。一般每個系統呼叫對應一個系統呼叫的封裝例程,函式庫再用這些封裝例程定義出給程式設計師呼叫的API,這樣把系統呼叫最終封裝成方便程式設計師使用的庫函式。
如下圖所示,User mode表示使用者態,kernel mode表示核心態。Xyz()就是一個API函式,是系統呼叫對應的API,其中封裝了一個系統呼叫,會觸發int $0x80的中斷,對應system_call核心程式碼的起點,即中斷向量0X80對應的中斷服務程式入口,內部會有sys_xyz()系統呼叫處理函式,執行完sys_xyz()後會ret_from_sys_call,這裡是程序排程最常見的排程時機點。如果沒有發生程序排程,就會執行iret再返回到使用者態接著執行。系統呼叫的3層機制分別為xyz(),system_call和sys_xyz()。
3.當用戶態程序呼叫一個系統呼叫時,CPU切換到核心態並開始執行一個核心函式。在Linux中是通過執行int$0x80來執行系統呼叫的,這條彙編指令產生向量為128的程式設計異常,Intel Pentium II 中引入了sysenter指令(快速系統呼叫),2.6已經支援。除了系統呼叫外,系統呼叫也可能需要傳遞引數。核心實現了很多不同的系統呼叫,程序必須指明需要哪個系統呼叫,這需要傳遞一個名為系統呼叫號的引數,使用eax暫存器。系統呼叫也需要輸入輸出引數,例如:實際的值,使用者態程序地址空間的變數的地址,甚至是包含指向使用者態函式的指標的資料結構的地址。system_call是linux中所有系統呼叫的入口點,每個系統呼叫至少有一個引數,即由eax傳遞的系統呼叫號。一個應用程式呼叫fork()封裝例程,那麼在執行into$0x80之前就把eax暫存器的值置為2(即_NR_fork)。這個暫存器的設定是libc庫中的封裝例程進行的,因此使用者一般不關心繫統呼叫號,進入sys_call之後,立即將eax的值壓入核心堆疊。暫存器傳遞引數具有如下限制: 1)每個引數的長度不能超過暫存器的長度,即32位 2)在系統呼叫號(eax)之外,引數的個數不能超過6個(ebx, ecx,edx,esi,edi,ebp) 3)超過6個怎麼辦?超過6個的話就把某一個暫存器作為一個指標,指向某一塊記憶體。
第二部分 使用庫函式API和C程式碼中嵌入彙編程式碼觸發同一個系統呼叫
- 一、編寫程式輸出當前系統時間
編寫time.c程式,編譯後結果如下:
在C程式碼中嵌入彙編程式碼觸發系統呼叫,然後編譯程式,發現了問題,通過查閱資料,發現是版本問題:
下載安裝gcc-4.8後解決此問題,執行結果如下:
二、實驗樓實驗
我選用了系統呼叫處理函式是sys_rename(),系統呼叫號是38。
程式碼如圖所示:
C程式碼嵌入彙編程式碼如圖所示:
movl %2,%%ecx\n\t
將newname存入eax暫存器;
movl %1,%%ebx/n/t
將oldname存入ebx暫存器;
movl $0x26,%%eax\n\t
系統呼叫號38(16進位制是0X26)存入EAX暫存器。
兩個程式的執行結果分別如下:
System_call根據傳入的系統呼叫號在系統呼叫列表中查詢到對應的系統呼叫核心函式,然後根據EBX暫存器和ECX暫存器中儲存的引數呼叫列表中查詢到對應的系統呼叫核心函式,然後分局EBX暫存器和ECX暫存器中儲存的引數呼叫核心函式sys_rename,執行完後將執行結果存放到EAX暫存器中,將EAX暫存器的值傳給ret。