網(wǎng)站建設(shè)管理維護(hù)制度百度推廣員工工資怎么樣
系統(tǒng)調(diào)用介紹
? ? ? ? 什么是系統(tǒng)調(diào)用
? ? ? ? ? ? ? ? ? ? ? ? 為了讓應(yīng)用程序有能力訪問系統(tǒng)資源,也為了讓程序借助操作系統(tǒng)做一些由操作系統(tǒng)支持的行為,每個(gè)操作系統(tǒng)都會提供一套接口,以供應(yīng)用程序使用。系統(tǒng)調(diào)用涵蓋的功能很廣,有程序運(yùn)行所必需的支持,例如創(chuàng)建/退出進(jìn)程和線程,進(jìn)程內(nèi)存管理,也有對系統(tǒng)資源的訪問,例如文件,網(wǎng)絡(luò),進(jìn)程間通信,硬件設(shè)備的訪問,也可能有對圖形界面的操作支持。
? ? ? ? Linux系統(tǒng)調(diào)用
? ? ? ? ? ? ? ? ? ? ? ? 下面讓我們來看看Linux系統(tǒng)調(diào)用的定義,有一個(gè)比較直觀的概念。在x86下,系統(tǒng)調(diào)用由0x80中斷完成,各個(gè)通用寄存器用于傳遞參數(shù),EAX寄存器用于表示系統(tǒng)調(diào)用的接口號,比如EAX=1表示退出進(jìn)程(exit);EAX=2表示創(chuàng)建進(jìn)程(fork);EAX=3表示讀取文件或IO(read);EAX=4表示寫文件或IO(write)等,每個(gè)系統(tǒng)調(diào)用都對應(yīng)內(nèi)核源代碼中的一個(gè)函數(shù),它們都是以"sys_"開頭,比如exit調(diào)用對應(yīng)內(nèi)核中的sys_exit函數(shù)。當(dāng)系統(tǒng)調(diào)用返回時(shí),EAX又作為調(diào)用結(jié)果的返回值。
EAX | 名字 | C語言定義 | 含義 | 參數(shù) |
1 | exit | void _exit(int status) | 退出進(jìn)程 | EBX表示退出碼 |
3 | read | ssize_t read(int fd, void* buf, size_t count) | 讀文件 | EBX表示文件句柄,ECX表示讀取緩沖區(qū)地址,EDX表示讀取大小 |
? ? ? ? 系統(tǒng)調(diào)用的弊端
? ? ? ? ? ? ? ? ? ? ? ? 系統(tǒng)調(diào)用完成了應(yīng)用程序和內(nèi)核交流的工作,因此理論上只需要系統(tǒng)調(diào)用就可以完成一些程序,但是:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 理論上,理論總是成立的。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 事實(shí)上,包括Linux,大部分操作系統(tǒng)的系統(tǒng)調(diào)用都有兩個(gè)特點(diǎn):
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1、使用不便。操作系統(tǒng)提供的系統(tǒng)調(diào)用接口往往過于原始,程序員需要了解很多與操作系統(tǒng)相關(guān)的細(xì)節(jié)。如果沒有進(jìn)行很好的包裝,使用起來不方便。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2、各個(gè)操作系統(tǒng)之間系統(tǒng)調(diào)用不兼容。
系統(tǒng)調(diào)用原理
? ? ? ? 特權(quán)級與中斷
? ? ? ? ? ? ? ? ? ? ? ? 現(xiàn)在的CPU常??梢栽诙喾N截然不同的特權(quán)級別下執(zhí)行指令,在現(xiàn)代操作系統(tǒng)中,通常也據(jù)此有兩種特權(quán)級別,分別為用戶模式和內(nèi)核模式,也被稱為用戶態(tài)和內(nèi)核態(tài)。由于有多種特權(quán)模式的存在,操作系統(tǒng)就可以讓不同的代碼運(yùn)行在不同的模式下,以限制它們的權(quán)利,提高穩(wěn)定性和安全性。普通應(yīng)用程序運(yùn)行在用戶態(tài)的模式下,諸多操作將受到限制,這些操作包括訪問硬件設(shè)備,開關(guān)中斷,改變特權(quán)模式等。
? ? ? ? ? ? ? ? ? ? ? ? 一般來說,運(yùn)行在高特權(quán)級的代碼將自己降至低特權(quán)級是允許的,但反過來低特權(quán)級的代碼將自己提升至高特權(quán)級則不是輕易就能進(jìn)行的。在將低特權(quán)級的環(huán)境轉(zhuǎn)為高特權(quán)級時(shí),必須使用一種較為受控和安全的形式,以防止低特權(quán)模式的代碼破壞高特權(quán)模式代碼的執(zhí)行。
? ? ? ? ? ? ? ? ? ? ? ? 系統(tǒng)調(diào)用是運(yùn)行在內(nèi)核態(tài)的,而應(yīng)用程序基本都是運(yùn)行在用戶態(tài)的。用戶態(tài)的程序如何運(yùn)行內(nèi)核態(tài)的代碼呢?操作系統(tǒng)一般是通過中斷來從用戶態(tài)切換到內(nèi)核態(tài)。什么是中斷呢?中斷是一個(gè)硬件或軟件發(fā)出的請求,要求CPU暫停當(dāng)前的工作轉(zhuǎn)手去處理更加重要的事情。
? ? ? ? ? ? ? ? ? ? ? ? 中斷一般具有兩個(gè)屬性,一個(gè)稱為中斷號(從0開始),一個(gè)稱為中斷處理程序。不同的中斷具有不同的中斷號,而同時(shí)一個(gè)中斷處理程序一一對應(yīng)一個(gè)中斷號。在內(nèi)核中,有一個(gè)數(shù)組稱為中斷向量表,這個(gè)數(shù)組的第n項(xiàng)包含了第n號中斷的中斷處理程序的指針。當(dāng)中斷到來時(shí),CPU會暫停當(dāng)前執(zhí)行的代碼,根據(jù)中斷號,在中斷向量表中找到對應(yīng)的中斷處理程序,并調(diào)用它。中斷處理程序執(zhí)行完成之后,CPU會繼續(xù)執(zhí)行之前的代碼。一個(gè)簡單的示意圖如圖1所示。
?圖1 CPU中斷過程
? ? ? ? ? ? ? ? ? ? ? ? 通常意義上,中斷有兩種類型,一種稱為硬件中斷,這種中斷來自硬件的異?;蚱渌录陌l(fā)生,如電源掉電,鍵盤被按下。另一種稱為軟件中斷,軟件中斷通常是一條指令,帶有一個(gè)參數(shù)記錄中斷號,使用這條指令用戶可以手動觸發(fā)某個(gè)中斷并執(zhí)行其中斷處理程序。例如在i386下,int 0x80這條指令會調(diào)用第0x80號中斷的處理程序。
? ? ? ? ? ? ? ? ? ? ? ? 由于中斷號是很有限的,操作系統(tǒng)不會舍得用一個(gè)中斷號來對應(yīng)一個(gè)系統(tǒng)調(diào)用,而更傾向于一個(gè)或少數(shù)幾個(gè)終端號來對應(yīng)所有的系統(tǒng)調(diào)用。對于同一個(gè)中斷號,操作系統(tǒng)如何知道是哪一個(gè)系統(tǒng)調(diào)用被調(diào)用呢?和中斷一樣,系統(tǒng)調(diào)用都有一個(gè)系統(tǒng)調(diào)用號,就像身份標(biāo)識一樣來表明是那個(gè)系統(tǒng)調(diào)用,這個(gè)系統(tǒng)調(diào)用號通常就是系統(tǒng)調(diào)用在系統(tǒng)調(diào)用表中的位置,例如Linux里的fork系統(tǒng)調(diào)用號是2。這個(gè)系統(tǒng)調(diào)用號在執(zhí)行int指令前會被放置在某個(gè)固定的寄存器里,對應(yīng)的中斷代碼會取得這個(gè)系統(tǒng)調(diào)用號,并且調(diào)用正確的函數(shù)。以Linux的int 0x80為例,系統(tǒng)調(diào)用號是由eax來傳入。用戶將系統(tǒng)調(diào)用號放入eax,然后使用int 0x80調(diào)用中斷,中斷服務(wù)程序就可以從eax里取得系統(tǒng)調(diào)用號,進(jìn)而調(diào)用對應(yīng)的函數(shù)。
? ? ? ? 基于int的Linux的經(jīng)典系統(tǒng)調(diào)用實(shí)現(xiàn)
? ? ? ? ? ? ? ? 圖2是以fork為例的Linux系統(tǒng)調(diào)用的執(zhí)行流程。
圖2 Linux系統(tǒng)中斷流程?
? ? ? ? ? ? ? ? 觸發(fā)中斷
? ? ? ? ? ? ? ? ? ? ? ? 首先當(dāng)程序在代碼里調(diào)用一個(gè)系統(tǒng)調(diào)用時(shí),是以一個(gè)函數(shù)的形式調(diào)用的,例如程序調(diào)用fork:
? ? ? ? ? ? ? ? ? ? ? ? int main(){ fork();}
? ? ? ? ? ? ? ? ? ? ? ? fork函數(shù)是一個(gè)對系統(tǒng)調(diào)用fork的封裝,可以用下列宏來定義它:
? ? ? ? ? ? ? ? ? ? ? ? _syscall0(pid_t, fork);
? ? ? ? ? ? ? ? ? ? ? ? _syscall0是一個(gè)宏函數(shù),用于定義一個(gè)沒有參數(shù)的系統(tǒng)調(diào)用的封裝。它的第一個(gè)參數(shù)為這個(gè)系統(tǒng)調(diào)用的返回值類型,這里為pit_t,是一個(gè)Linux自定義類型,代表進(jìn)程的id。_syscall0的第二個(gè)參數(shù)是系統(tǒng)調(diào)用的名稱,_syscall0展開之后會形成一個(gè)與系統(tǒng)調(diào)用名稱同名的函數(shù)。下面的代碼是i386版本的syscall0定義:
? ? ? ? ? ? ? ? ? ? ? ? #define _syscall0(type, name?)? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? type name(void)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? {? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? long __res;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? __asm__ volatile("int $0x80"
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? : "=a" (__res)? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? : "0" (__NR__##name));? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? __syscall_return(type, __res);? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? 對應(yīng)syscall0(pid_t, fork),上面的宏將展開為:
? ? ? ? ? ? ? ? ? ? ? ? pid_t fork(void)
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? long __res;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? __asm__ volatile("int $0x80"
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? : "=a" (__res)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? : "0" (__NR_fork));
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? __syscall_return(pid_t, __res);
????????????????????????}
? ? ? ? ? ? ? ? ? ? ? ? 首先__asm__是一個(gè)gcc的關(guān)鍵字,表示接下來要嵌入?yún)R編代碼。volatile
關(guān)鍵字告訴gcc對這段代碼不進(jìn)行任何優(yōu)化。
? ? ? ? ? ? ? ? ? ? ? ? __asm__的第一個(gè)參數(shù)是一個(gè)字符串,代表匯代碼的文本。這里的匯編代碼只有一句: int $0x80,這就要調(diào)用0x80號中斷
? ? ? ? ? ? ? ? ? ? ? ? "=a"(__res)表示用eax(a表示eax)輸出返回?cái)?shù)據(jù)并存儲在__res里。
? ? ? ? ? ? ? ? ? ? ? ? "0"(__NR_#name)表示__NR_#name為輸入,"0"指示由編譯器選擇和輸出相同的寄存器(eax)來傳遞參數(shù)。
? ? ? ? ? ? ? ? ? ? ? ? 更直觀一點(diǎn),可以把這段匯編改寫為更為可讀的格式:
? ? ? ? ? ? ? ? ? ? ? ? main-->fork:
? ? ? ? ? ? ? ? ? ? ? ? pid_t fork(void)
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? long __res;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? $eax=__NR_fork
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int $0x80
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? __res = $eax
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? __syscall_return(pid_t, __res);
????????????????????????}
? ? ? ? ? ? ? ? ? ? ? ? 如果系統(tǒng)調(diào)用本身由參數(shù)要如何實(shí)現(xiàn)呢?下面是x86Linux下的syscall1,用于帶1個(gè)參數(shù)的系統(tǒng)調(diào)用:
? ? ? ? ? ? ? ? ? ? ? ? #define _syscall2(type, name, type1, arg1)
? ? ? ? ? ? ? ? ? ? ? ? type name(type1, arg1)
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? long __res;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? __asm__ volatile("int $0x80"
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? : "a=" (__res)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? : "0" (__NR_##name), "b"((long)(arg1)));
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? __syscall_return(type, __res);? ? ? ? ? ? ? ? ??
????????????????????????}
? ? ? ? ? ? ? ? ? ? ? ? 這段代碼和_syscall0不同的是,它多了一個(gè)"b"((long)(arg1))。這一句的意思是先把a(bǔ)rg1強(qiáng)制轉(zhuǎn)換為long,然后存放在EBX(b代表EBX)里作為輸入。編譯器還會生產(chǎn)相應(yīng)的代碼來保護(hù)原理的EBX的值不被破壞。
? ? ? ? ? ? ? ? ? ? ? ? 可見,如果系統(tǒng)調(diào)用由一個(gè)參數(shù),那么參數(shù)通過EBX來傳入。x86下Linux支持的系統(tǒng)調(diào)用參數(shù)至多由6個(gè),分別使用6個(gè)寄存器來傳遞,它們分別是EBX,ECX,EDX,ESI,EDI和EBP。
? ? ? ? ? ? ? ? ? ? ? ? 當(dāng)用戶調(diào)用某個(gè)系統(tǒng)調(diào)用的時(shí)候,實(shí)際是執(zhí)行了以上一段匯編代碼。CPU執(zhí)行到int $0x80時(shí),會保存現(xiàn)場以便恢復(fù),接著會將特權(quán)狀態(tài)切換到內(nèi)核態(tài)。然后CPU便會查找中斷向量表中的第0x80號元素。
? ? ? ? ? ? ? ? 切換堆棧
? ? ? ? ? ? ? ? ? ? ? ? 在實(shí)際執(zhí)行中斷向量表中的第0x80號元素所對應(yīng)的函數(shù)之前,CPU首先還要進(jìn)行棧的切換。在Linux中,用戶態(tài)和內(nèi)核態(tài)使用的是不同的棧,兩者各自負(fù)責(zé)各自的函數(shù)調(diào)用,互不干擾。但在應(yīng)用程序調(diào)用0x80號中斷時(shí),程序的執(zhí)行流程從用戶態(tài)切換到內(nèi)核態(tài),這時(shí)程序的當(dāng)前棧必須也相應(yīng)的從用戶棧切換到內(nèi)核棧。從中斷處理函數(shù)返回時(shí),程序的當(dāng)前棧還要從內(nèi)核棧切換回用戶棧。
? ? ? ? ? ? ? ? ? ? ? ? 所謂的"當(dāng)前棧",指的是ESP的值所在的棧空間。如果ESP的值位于用戶棧的范圍內(nèi),那么程序的當(dāng)前棧就是用戶棧,反之亦然。此外,寄存器SS的值還應(yīng)該指向當(dāng)前棧所在的頁。所以,將當(dāng)前棧由用戶棧切換為內(nèi)核棧的實(shí)際行為就是:
? ? ? ? ? ? ? ? ? ? ? ? 1、保存當(dāng)前的ESP,SS的值。
? ? ? ? ? ? ? ? ? ? ? ? 2、將ESP,SS的值設(shè)置為內(nèi)核棧的相應(yīng)值。
? ? ? ? ? ? ? ? ? ? ? ? 反過來,將當(dāng)前棧由內(nèi)核棧切換為用戶棧的實(shí)際行為則是:
? ? ? ? ? ? ? ? ? ? ? ? 1、恢復(fù)原來ESP,SS的值。
? ? ? ? ? ? ? ? ? ? ? ? 2、將用戶態(tài)的ESP,SS的值保存在內(nèi)核棧上。
? ? ? ? ? ? ? ? ? ? ? ? 當(dāng)0x80號中斷發(fā)生的時(shí)候,CPU除了切入內(nèi)核態(tài)之外,還會自動完成下列幾件事:
? ? ? ? ? ? ? ? ? ? ? ? 1、找到當(dāng)前進(jìn)程的內(nèi)核棧(每一個(gè)進(jìn)程都有自己的內(nèi)核棧)。
? ? ? ? ? ? ? ? ? ? ? ? 2、在內(nèi)核棧中依次壓入用戶態(tài)的寄存器SS,ESP,EFLAGS,CS,EIP。
? ? ? ? ? ? ? ? ? ? ? ? 而當(dāng)內(nèi)核從系統(tǒng)調(diào)用中返回的時(shí)候,需要調(diào)用iret指令來回倒用戶態(tài),iret指令則會從內(nèi)核棧里彈出寄存器SS,ESP,EFLAGS,CS,EIP的值,使得?;謴?fù)到用戶態(tài)的狀態(tài)。這個(gè)過程可以用圖3來表示。
圖3 中斷時(shí)用戶棧和內(nèi)核棧切換
?