原創内容(cxsmarkchan 陳曉爽)
轉載請注明出處
《Linux核心分析》MOOC課程學習筆記
為保證系統的穩定運作,CPU運作狀态被分為核心态和使用者态。作業系統在核心态下運作,是以擁有所有計算機資源的操作權限。而一般的應用程式則在使用者态下運作,它們不能直接操作底層的硬體裝置,進而保證應用軟體不會破壞系統的穩定。但是,應用程式在運作時常常需要和各種資源打交道,為此,作業系統提供了“系統調用”的功能,提供一組API,由使用者态程式調用。本文通過asm内聯彙編,分析系統調用的全過程。
1 系統調用的概念
下圖是一個典型的系統調用圖示:
從該函數可以看到,系統調用分成如下過程:
1. 使用者态程式調用API函數
xyz
2. 在
xyz
接口函數内部,通過中斷号
0x80
進入系統調用,此時CPU進入核心态。
3. CPU開始執行中斷處理程式,根據使用者傳入的資訊(系統調用号和相關參數),執行相應的核心态函數,并傳回結果。
這裡有兩個問題:
1. 核心态的切換是通過中斷方式進入的,而産生中斷時隻能傳入一個中斷向量(即
0x80
),而系統調用有大量的API函數,系統如何知道調用哪一個函數呢?
2. 有一些API函數帶有參數,而系統調用并沒有才有函數調用(
call
)方式,那麼參數如何傳遞到被調用函數?
答案是:在調用
int 0x80
進入系統調用前,預先把系統調用号和相關參數存入指定的寄存器中。這樣,系統調用函數隻需要通路相應的寄存器,就可以獲得所有的資訊。
事實上,在進入系統調用前,首先需要将系統調用号傳入
eax
寄存器中,并将參數依次傳入
ebx
、
ecx
、
edx
、
esi
、
edi
、
ebp
寄存器中。系統調用最多隻能傳入6個參數,如果參數多于6個,則需要将參數預存在記憶體中,然後将參數指針傳入寄存器。系統調用結束後,
eax
會被替換為系統調用傳回值。
2 用asm實作系統調用的執行個體
本文運作平台為實驗樓Linux核心分析的第4個實驗,運作環境為linux系統。
為了驗證系統調用的全過程,我們以exit函數為例,給出代碼如下:
#include <stdio.h>
#include <stdlib.h>
int main(){
int id;
scanf("%d", &id);
switch(id){
case :
//用C語言的方式調用exit函數
printf("C exit\n");
exit();
break;
case :
//用内聯彙編的方式調用exit函數
printf("asm exit\n");
asm volatile(
"mov $0, %%ebx\n\t"
"mov $0x1, %%eax\n\t"
"int $0x80\n\t"
:
:
);
break;
default:
printf("others\n");
break;
}
printf("before return\n");
return ;
}
該函數中,首先輸入參數id。如果id為0,則調用C代碼的exit函數。如果id為1,則調用彙編代碼的exit函數。如果id為其他值,則順序執行至main函數結尾。
事實上,上文的代碼采用C代碼和内聯彙編實作了等價的功能,即
exit(0)
功能。可以分析一下内聯彙編的工作方式:
1.
mov $0, %ebx
:exit函數的第1個參數(也是唯一一個參數)為0,按照寄存器順序,應該放在
ebx
中。
2.
mov $0x1, %eax
:exit函數的系統調用号為
0x1
,是以把系統調用号放入
eax
中。
3.
int 0x80
:産生中斷,中斷号
0x80
表示系統調用。
執行了以上3步,即為執行了exit(0)函數。程式的運作結果如下:
可見,輸入0或1時,都沒有輸出
"before return"
,說明exit被成功調用,程式提前退出。
3 小結
系統調用既保證了作業系統的安全運作,也友善了使用者态程式使用系統資源。用内聯彙編的方式處理系統調用,可以很清晰地看出系統調用的過程,以及系統調用的參數傳遞方式。系統調用通過寄存器傳遞參數,是以在進行系統調用前,通常還需要備份相關寄存器中的資訊。不過,在内聯彙編中,這個工作會被編譯器代勞。