本實驗選擇的系統(tǒng)調(diào)用號為34,在syscall_32.tbl中對應(yīng)如下項:
41 i386 dup sys_dup
功能描述:
Dup() duplicates an existing object descriptor and returns its value to
the calling process (fildes2 = dup(fildes)). The argument fildes is a
small non-negative integer index in the per-process descriptor table.
The value must be less than the size of the table, which is returned by
getdtablesize(2). The new descriptor returned by the call is the lowest
numbered descriptor currently not in use by the process.
RETURN VALUES
Upon successful completion, the new file descriptor is returned. Otherwise, a value of -1 is returned and the global integer variable errno is set to indicate the error.
使用 libc 中提供的庫函數(shù)調(diào)用 的 C 代碼以及運行結(jié)果如圖1所示:

其中,給 dup 函數(shù)傳遞的參數(shù)是1.
其中的程序成功通過新復(fù)制的 fild 進行了寫入操作,在屏幕上輸出字符串.
直接在 C 代碼中使用內(nèi)聯(lián)匯編調(diào)用該系統(tǒng)調(diào)用的代碼及運行結(jié)果如圖2所示:

從圖中可以看出對 ebx 寄存器賦值為1,達(dá)到了傳遞參數(shù)的效果.同樣在屏幕上輸出了字符串.可知fild 復(fù)制成功.
匯編代碼調(diào)用系統(tǒng)調(diào)用的程序如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int new_fildes=0;
char *szTemp="test_dup\n";
asm volatile(
"mov $1,%%ebx\n\t" //傳遞參數(shù)
"mov $41,%%eax\n\t" //設(shè)置系統(tǒng)調(diào)用號
"int $0x80\n\t" //進行軟中斷觸發(fā)
"mov %%eax,%0\n\t" //保存返回值
:"=m"(new_fildes)
);
printf("new fildes: %d\n",new_fildes);
write(new_fildes,szTemp,strlen(szTemp));
return 0;
}
系統(tǒng)調(diào)用的總?cè)肟谑?x80中斷,用 eax 寄存器存儲具體的中斷調(diào)用號,本例中是41.除了系統(tǒng)調(diào)用號以外,大部分系統(tǒng)調(diào)用都還需要一些外部的參數(shù)輸人。所以,在發(fā)生異常的時候,應(yīng)該把這些參數(shù)從用戶空間傳給內(nèi)核。最簡單的辦法就是像傳遞系統(tǒng)調(diào)用號一樣把這些參數(shù)也存放在寄存器里。在x86系統(tǒng)上,ebx, ecx, edx, esi和edi按照順序存放前五個參數(shù)。需要六個或六個以上參數(shù)的情況不多見,此時,應(yīng)該用一個單獨的寄存器存放指向所有這些參數(shù)在用戶空間地址的指針。 給用戶空間的返回值也通過寄存器傳遞。在x86系統(tǒng)上,它存放在eax寄存器中。接下來許多關(guān)于系統(tǒng)調(diào)用處理程序的描述都是針對x86版本的。但不用擔(dān)心,所有體系結(jié)構(gòu)的實現(xiàn)都很類似。最終依據(jù)具體調(diào)用號調(diào)用對應(yīng)的系統(tǒng)調(diào)用處理過程并使用傳遞的參數(shù).
總結(jié)
系統(tǒng)調(diào)用在用戶空間進程和硬件設(shè)備之間的接口,它和普通庫函數(shù)調(diào)用非常相似,只是系統(tǒng)調(diào)用由操作系統(tǒng)核心提供,運行于核心態(tài),而普通的函數(shù)調(diào)用由函數(shù)庫或用戶自己提供,運行于用戶態(tài)。主要有三個作用:
它為用戶空間提供了一種統(tǒng)一的硬件的抽象接口。比如當(dāng)需要讀些文件的時候,應(yīng)用程序就可以不去管磁盤類型和介質(zhì),甚至不用去管文件所在的文件系統(tǒng)到底是哪種類型。
系統(tǒng)調(diào)用保證了系統(tǒng)的穩(wěn)定和安全。作為硬件設(shè)備和應(yīng)用程序之間的中間人,內(nèi)核可以基于權(quán)限和其他一些規(guī)則對需要進行的訪問進行裁決。舉例來說,這樣可以避免應(yīng)用程序不正確地使用硬件設(shè)備,竊取其他進程的資源,或做出其他什么危害系統(tǒng)的事情。
每個進程都運行在虛擬系統(tǒng)中,而在用戶空間和系統(tǒng)的其余部分提供這樣一層公共接口,也是出于這種考慮。如果應(yīng)用程序可以隨意訪問硬件而內(nèi)核又對此一無所知的話,幾乎就沒法實現(xiàn)多任務(wù)和虛擬內(nèi)存,當(dāng)然也不可能實現(xiàn)良好的穩(wěn)定性和安全性。在Linux中,系統(tǒng)調(diào)用是用戶空間訪問內(nèi)核的惟一手段;除異常和中斷外,它們是內(nèi)核惟一的合法入口。
API與系統(tǒng)調(diào)用并不存在一一對應(yīng)的關(guān)系,只是為用戶提供的標(biāo)準(zhǔn)接口,提高了程序移植性。用戶在請求系統(tǒng)調(diào)用的時候,一般只與API打交道。而內(nèi)核只與系統(tǒng)調(diào)用打交道,至于API是怎樣申請系統(tǒng)調(diào)用的,是由Glibc等標(biāo)準(zhǔn)制定者負(fù)責(zé)的。UNIX尊奉的一句話是:Provide mechanism, not policy,即提供機制而不是策略。這里的機制就是:
用戶空間的程序無法直接執(zhí)行內(nèi)核代碼。它們不能直接調(diào)用內(nèi)核空間中的函數(shù),因為內(nèi)核駐留在受保護的地址空間上。如果進程可以直接在內(nèi)核的地址空間上讀寫的話,系統(tǒng)安全就會失去控制。所以,應(yīng)用程序應(yīng)該以某種方式通知系統(tǒng),告訴內(nèi)核自己需要執(zhí)行一個系統(tǒng)調(diào)用,希望系統(tǒng)切換到內(nèi)核態(tài),這樣內(nèi)核就可以代表應(yīng)用程序來執(zhí)行該系統(tǒng)調(diào)用了。
通知內(nèi)核的機制是靠軟件中斷實現(xiàn)的。首先,用戶程序為系統(tǒng)調(diào)用設(shè)置參數(shù)。其中一個參數(shù)是系統(tǒng)調(diào)用編號。參數(shù)設(shè)置完成后,程序執(zhí)行“系統(tǒng)調(diào)用”指令。x86系統(tǒng)上的軟中斷由int產(chǎn)生。這個指令會導(dǎo)致一個異常:產(chǎn)生一個事件,這個事件會致使處理器切換到內(nèi)核態(tài)并跳轉(zhuǎn)到一個新的地址,并開始執(zhí)行那里的異常處理程序。此時的異常處理程序?qū)嶋H上就是系統(tǒng)調(diào)用處理程序。它與硬件體系結(jié)構(gòu)緊密相關(guān)。
新地址的指令會保存程序的狀態(tài),計算出應(yīng)該調(diào)用哪個系統(tǒng)調(diào)用,調(diào)用內(nèi)核中實現(xiàn)那個系統(tǒng)調(diào)用的函數(shù),恢復(fù)用戶程序狀態(tài),然后將控制權(quán)返還給用戶程序。系統(tǒng)調(diào)用是設(shè)備驅(qū)動程序中定義的函數(shù)最終被調(diào)用的一種方式。