OS內(nèi)核中都有一組實(shí)現(xiàn)系統(tǒng)功能的過程,系統(tǒng)調(diào)用就是對(duì)上述過程的調(diào)用。編程人員利用系統(tǒng)調(diào)用,向OS提出服務(wù)請(qǐng)求,由OS代為完成。 一般情況下,進(jìn)程是不能夠存取系統(tǒng)內(nèi)核的。它不能存取內(nèi)核使用的內(nèi)存段,也不能調(diào)用內(nèi)核函數(shù),CPU的硬件結(jié)構(gòu)保證了這一點(diǎn)。只有系統(tǒng)調(diào)用是一個(gè)例外。 統(tǒng)調(diào)用是用戶態(tài)進(jìn)入內(nèi)核態(tài)的唯一入口:一夫當(dāng)關(guān),萬夫莫開。常用系統(tǒng)調(diào)用:
優(yōu)點(diǎn) 編程容易,從硬件設(shè)備的低級(jí)編程中解脫出來 提高了系統(tǒng)的安全性,可以先檢查請(qǐng)求的正確性
Linux中實(shí)現(xiàn)系統(tǒng)調(diào)用利用了i386體系結(jié)構(gòu)中的軟件中斷。即調(diào)用了int $0x80匯編指令。 這條匯編指令將產(chǎn)生向量為128的編程異常,CPU便被切換到內(nèi)核態(tài)執(zhí)行內(nèi)核函數(shù),轉(zhuǎn)到了系統(tǒng)調(diào)用處理程序的入口:system_call()。 int $0x80指令將用戶態(tài)的執(zhí)行模式轉(zhuǎn)變?yōu)閮?nèi)核態(tài),并將控制權(quán)交給系統(tǒng)調(diào)用過程的起點(diǎn)system_call()處理函數(shù)。 system_cal()檢查系統(tǒng)調(diào)用號(hào),該號(hào)碼告訴內(nèi)核進(jìn)程請(qǐng)求哪種服務(wù)。 內(nèi)核進(jìn)程查看系統(tǒng)調(diào)用表(sys_call_table)找到所調(diào)用的內(nèi)核函數(shù)入口地址。 接著調(diào)用相應(yīng)的函數(shù),在返回后做一些系統(tǒng)檢查,最后返回到進(jìn)程。 system_call()函數(shù) 系統(tǒng)調(diào)用和普通函數(shù)調(diào)用 API是用于某種特定目的的函數(shù),供應(yīng)用程序調(diào)用,而系統(tǒng)調(diào)用供應(yīng)用程序直接進(jìn)入系統(tǒng)內(nèi)核。 Linux內(nèi)核提供了一些C語言函數(shù)庫,這些庫對(duì)系統(tǒng)調(diào)用進(jìn)行了一些包裝和擴(kuò)展,因?yàn)檫@些庫函數(shù)與系統(tǒng)調(diào)用的關(guān)系非常緊密,所以習(xí)慣上把這些函數(shù)也稱為系統(tǒng)調(diào)用。 有的API函數(shù)在用戶空間就可以完成工作,如一些用于數(shù)學(xué)計(jì)算的函數(shù),因此不需要使用系統(tǒng)調(diào)用。 有的API函數(shù)可能會(huì)進(jìn)行多次系統(tǒng)調(diào)用。 不同的API 函數(shù)也可能會(huì)有相同的系統(tǒng)調(diào)用。比如malloc(),calloc(),free()等函數(shù)都使用相同的方法分配和釋放內(nèi)存。 系統(tǒng)命令、內(nèi)核函數(shù) 系統(tǒng)調(diào)用與系統(tǒng)命令
系統(tǒng)調(diào)用與內(nèi)核函數(shù)
封裝例程(wrapper routine) 由于陷入指令是一條特殊指令,依賴操作系統(tǒng)實(shí)現(xiàn)的平臺(tái),如在i386體系結(jié)構(gòu)中,這條指令是int $0x80(陷入指令),不是用戶在編程時(shí)應(yīng)該使用的語句,因?yàn)檫@將使得用戶程序難于移植。 在標(biāo)準(zhǔn)C庫函數(shù)中,為每個(gè)系統(tǒng)調(diào)用設(shè)置了一個(gè)封裝例程,當(dāng)一個(gè)用戶程序執(zhí)行了一個(gè)系統(tǒng)調(diào)用時(shí),就會(huì)調(diào)用到C函數(shù)庫中的相對(duì)應(yīng)的封裝例程。 系統(tǒng)調(diào)用過程 system_call()片段 首先將系統(tǒng)調(diào)用號(hào)(eax)和可以用到的所有CPU寄存器保存到相應(yīng)的堆棧中(由SAVE_ALL完成); 對(duì)用戶態(tài)進(jìn)程傳遞過來的系統(tǒng)調(diào)用號(hào)進(jìn)行有效性檢查(eax是系統(tǒng)調(diào)用號(hào),它應(yīng)該小于 NR_syscalls) 如果是合法的系統(tǒng)調(diào)用,再進(jìn)一步檢測(cè)該系統(tǒng)調(diào)用是否正被跟蹤; 根據(jù)eax中的系統(tǒng)調(diào)用號(hào)調(diào)用相應(yīng)的服務(wù)例程。 服務(wù)例程結(jié)束后,從eax寄存器獲得它的返回值,并把這個(gè)返回值存放在堆棧中,讓其位于用戶態(tài)eax寄存器曾存放的位置。 然后跳轉(zhuǎn)到ret_from_sys_call(),終止系統(tǒng)調(diào)用程序的執(zhí)行。 SAVE_ALL宏定義
系統(tǒng)調(diào)用表與調(diào)用號(hào) 這樣系統(tǒng)調(diào)用處理程序一旦運(yùn)行,就可以從eax中得到系統(tǒng)調(diào)用號(hào),然后再去系統(tǒng)調(diào)用表中尋找相應(yīng)服務(wù)例程。 一個(gè)應(yīng)用程序調(diào)用fork()封裝例程,那么在執(zhí)行int $0x80之前就把eax寄存器的值置為2(即__NR_fork)。 這個(gè)寄存器的設(shè)置是libc庫中的封裝例程進(jìn)行的,因此用戶一般不關(guān)心系統(tǒng)調(diào)用號(hào) 核心中為每個(gè)系統(tǒng)調(diào)用定義了一個(gè)唯一的編號(hào),這個(gè)編號(hào)的定義在linux/include/asm/unistd.h中(最大為NR_syscall) 同時(shí)在內(nèi)核中保存了一張系統(tǒng)調(diào)用表,該表中保存了系統(tǒng)調(diào)用編號(hào)和其對(duì)應(yīng)的服務(wù)例程地址。第n個(gè)表項(xiàng)包含系統(tǒng)調(diào)用號(hào)為n的服務(wù)例程的地址。 系統(tǒng)調(diào)用陷入內(nèi)核前,需要把系統(tǒng)調(diào)用號(hào)一起傳入內(nèi)核。而該標(biāo)號(hào)實(shí)際上是系統(tǒng)調(diào)用表( sys_call_table)的下標(biāo) 在i386上,這個(gè)傳遞動(dòng)作是通過在執(zhí)行int $0x80前把調(diào)用號(hào)裝入eax寄存器實(shí)現(xiàn)。 這樣系統(tǒng)調(diào)用處理程序一旦運(yùn)行,就可以從eax中得到系統(tǒng)調(diào)用號(hào),然后再去系統(tǒng)調(diào)用表中尋找相應(yīng)服務(wù)例程。 系統(tǒng)調(diào)用號(hào) 系統(tǒng)調(diào)用表 (arch/i386/kernel/entry.s)
系統(tǒng)調(diào)用的返回 當(dāng)服務(wù)例程結(jié)束時(shí),system_call( ) 從eax獲得系統(tǒng)調(diào)用的返回值,并把這個(gè)返回值存放在曾保存用戶態(tài) eax寄存器棧單元的那個(gè)位置上,然后跳轉(zhuǎn)到ret_from_sys_call( ),終止系統(tǒng)調(diào)用處理程序的執(zhí)行。 當(dāng)進(jìn)程恢復(fù)它在用戶態(tài)的執(zhí)行前,RESTORE_ALL宏會(huì)恢復(fù)用戶進(jìn)入內(nèi)核前被保留到堆棧中的寄存器值。其中eax返回時(shí)會(huì)帶回系統(tǒng)調(diào)用的返回碼(負(fù)數(shù)說明調(diào)用錯(cuò)誤,0或正數(shù)說明正常完成) ret_from_sys_call 系統(tǒng)調(diào)用的返回值 所有的系統(tǒng)調(diào)用返回一個(gè)整數(shù)值。
這里的返回值與封裝例程返回值的約定不同
假設(shè)源文件名為getpid.c,內(nèi)容是:
很多系統(tǒng)調(diào)用需要不止一個(gè)參數(shù) 普通C函數(shù)的參數(shù)傳遞是通過把參數(shù)值寫入堆棧(用戶態(tài)堆?;騼?nèi)核態(tài)堆棧)來實(shí)現(xiàn)的。但因?yàn)橄到y(tǒng)調(diào)用是一種特殊函數(shù),它由用戶態(tài)進(jìn)入了內(nèi)核態(tài),所以既不能使用用戶態(tài)的堆棧也不能直接使用內(nèi)核態(tài)堆棧
在int $0x80匯編指令之前,系統(tǒng)調(diào)用的參數(shù)被寫入CPU的寄存器。然后,在進(jìn)入內(nèi)核態(tài)調(diào)用系統(tǒng)調(diào)用服務(wù)例程之前,內(nèi)核再把存放在CPU寄存器中的參數(shù)拷貝到內(nèi)核態(tài)堆棧中。因?yàn)楫吘狗?wù)例程是C函數(shù),它還是要到堆棧中去尋找參數(shù)的
系統(tǒng)調(diào)用使用寄存器來傳遞參數(shù),要傳遞的參數(shù)有:
用于傳遞參數(shù)的寄存器有:
進(jìn)入內(nèi)核態(tài)后,system_call通過使用SAVE_ALL宏把這些寄存器的值保存在內(nèi)核態(tài)堆棧中。 用寄存器傳遞參數(shù)必須滿足3個(gè)條件:
參數(shù)傳遞舉例 處理write系統(tǒng)調(diào)用的sys_write服務(wù)例程聲明如下
該函數(shù)期望在棧頂找到fd,buf和count參數(shù) 在封裝sys_write()的封裝例程中,將會(huì)在ebx、ecx和edx寄存器中分別填入這些參數(shù)的值,然后在進(jìn)入system_call時(shí),SAVE_ALL會(huì)把這些寄存器保存在堆棧中,進(jìn)入sys_write服務(wù)例程后,就可以在相應(yīng)的位置找到這些參數(shù) asmlinkage使得編譯器不通過寄存器(x=0)而 使用堆棧傳遞參數(shù) SAVE_ALL 設(shè)C庫中封裝的系統(tǒng)調(diào)用號(hào)為3的函數(shù)原形如下:
功能要求 首先,自定義一個(gè)系統(tǒng)調(diào)用mysyscall ,它的功能是使用戶的uid等于0 。然后,編寫一段測(cè)試程序進(jìn)行調(diào)用。 執(zhí)行步驟如下
(1)添加系統(tǒng)調(diào)用號(hào):它位于unistd.h,每個(gè)系統(tǒng)調(diào)用號(hào)都以“_NR_開頭”,
(2)在系統(tǒng)調(diào)用表中添加相應(yīng)的表項(xiàng)
3)實(shí)現(xiàn)系統(tǒng)調(diào)用服務(wù)例程 把一小段程序添加在kernel/sys.c (4)重新編譯內(nèi)核,啟動(dòng)新內(nèi)核 (5)編寫一段測(cè)試程序檢驗(yàn)實(shí)驗(yàn)結(jié)果 _syscall1(int,print_info,int,testflag) 如果要在用戶程序中使用系統(tǒng)調(diào)用函數(shù),那么在主函數(shù)main前必須申明調(diào)用_syscall,其中1 表示該系統(tǒng)調(diào)用只有一個(gè)入口參數(shù),第一個(gè)int 表示系統(tǒng)調(diào)用的返回值為整型,print_info為系統(tǒng)調(diào)用函數(shù)名,第二個(gè)int 表示入口參數(shù)的類型為整型,testflag為入口參數(shù)名。
|
|
|