Linux系統調用的工作機制

作者:奮鬥的白楊

注:原創作品,轉載請註明出處

Linux內核分析》 MOOC課程http://mooc.study.163.com/course/USTC-1000029000


概述


系統調用是Linux內核提供的基礎服務入口,通過使用這一機制,應用程序可以使用內核的一些專門功能。在分析系統調用之前,以下三點需要了解:

1.系統調用將CPU從用戶態切換到核心態,以便訪問受保護的內核內存。

2.系統調用的組成是固定的,每個系統調用在內核中都由一個唯一的數字來標識。

3.系統調用的參數傳遞方式與普通C函數的方式有所不同。


工作過程


下面以系統調用dup()爲例,說明在應用程序如何使用Linux的系統調用。dup()複製一個打開的文件描述符,並返回一個新描述符,二者都指向同一個打開的文件句柄。系統會保證新描述符一定是編號低最低的未使用文件描述符。
使用庫函數API調用dup()的程序:



使用C語言嵌入彙編代碼來實現調用dup():


第一條彙編語句將立即數複製到寄存器edi,用作dup()的參數,表示要複製的描述符;
第二條彙編語句將立即數41複製到寄存器eax,41是dup()在內核中的系統調用號;
第三條彙編語句執行中斷指令int,從用戶態切換到內核態,發起系統調用;
第四條彙編語句將寄存器eax中保存的系統調用返回值複製fd,表示複製的新描述符。

上面兩個小程序的功能是等價的,複製得到的新描述符值都是3,因爲文件描述符0、1和2分別用作標準輸入,標準輸出和標準出錯。

下圖是通過庫函數API使用系統調用dup()時的事件發生序列:



從應用程序到執行系統調用要經歷諸多步驟,下面是x86-32架構的具體過程:
1.應用程序通過C語言函數庫中提供的API發起系統調用。
2.對系統調用中斷處理例程來說,API從堆棧中取得傳入的參數,發起系統調用之前,參數被複制到CPU寄存器中。
3.庫函數API將系統調用號複製到寄存器eax中。
4.執行中斷指令int,引發CPU從用戶態切換到內核態,並執行系統中斷0x80所執行的終端向量所指向的代碼。
5.爲了響應中斷0x80,內核會調用system_call()例程來處理此次中斷,包括在內核棧中保存寄存器值,檢查參數有效性並查找對應的中斷服務例程,執行完成後將結構狀態返回給system_call()。
6.如果系統調用不成功,庫函數API會使用該值設置全局變量errno,然後從API返回到應用程序中。

使用系統調用的傳統方法是通過彙編指令int。向量128(十六進制0x80)對應於內核的入口。在內核初始化時調用的函數trap_init(),用下面的方式建立對應於向量128的終端描述符表
set_system_intr_gate(SYSCALL_VECTOR, &system_call);
其中SYSCALL_VECTOR是定義在arch/x86/include/asm/irq_vectors.h中的一個宏:
#define  SYSCALL_VECTOR  0x80
執行int $0x80指令後,CPU切換到內核態並從地址system_call處開始執行指令,system_call()函數的具體分析留給《Linux系統調用的工作機制(下)》。

與普通函數類似,系統調用同城也需要輸入/輸出參數。發起系統調用時系統處於用戶態,執行系統調用時系統已進入內核態,同時操作兩個棧較複雜,因此內核利用CPU寄存器傳遞參數。使用系統調用前參數被寫入CPU寄存器(上面調用dup()時傳遞參數使用寄存器edi),執行系統調用時內核再把寄存器中的參數複製到內核堆棧中。


除了上文提到的使用int指令,應用程序還有一種方式可以調用系統調用。Intel Pentium II微處理器引入sysenter指令,從Linux 2.6開始內核也支持這條指令。內核從系統調用返回,使CPU從內核態切換回用戶態,也有兩種方式:
1.執行iret彙編語言指令。
2.執行sysexit彙編語言指令,和sysenter指令對應,同時從Intel Pentium II引入。

總結

從服務提供者的角度來看Linux系統,可以將內核視爲一個綜合性的庫,它包含了各種可面向用戶應用程序提供的功能。系統調用是應用程序與該庫之間的接口,因此瞭解系統調用的工作方式無論對於研究內核還是系統編程都是頗有益處的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章