在應(yīng)用程序開發(fā)過(guò)程中經(jīng)常會(huì)進(jìn)行IO設(shè)備的操作,比如磁盤的讀寫,網(wǎng)卡的讀寫,鍵盤,鼠標(biāo)的讀入等,大多數(shù)應(yīng)用開發(fā)人員使用高級(jí)語(yǔ)言進(jìn)行開發(fā),例如C,C++,java,python等,這些高級(jí)語(yǔ)言都提供了標(biāo)準(zhǔn)庫(kù)或者API去操作IO設(shè)備,不過(guò)標(biāo)準(zhǔn)庫(kù)或者API最終還是通過(guò)系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)操作IO設(shè)備的,系統(tǒng)調(diào)用操作系統(tǒng)提供的,它是操作系統(tǒng)內(nèi)核的一部分。 系統(tǒng)調(diào)用封裝了對(duì)硬件操作的所有細(xì)節(jié),而標(biāo)準(zhǔn)庫(kù)或者SDK又在系統(tǒng)調(diào)用的基礎(chǔ)上做了高度抽象的封裝和優(yōu)化,因此使得應(yīng)用程序開發(fā)人員的日子好過(guò)多了,開發(fā)效率也提高了不少。 本篇文章主要闡述以下兩部分: 1.什么是系統(tǒng)調(diào)用? 2.系統(tǒng)調(diào)用的實(shí)現(xiàn)? 主要以Linux 操作系統(tǒng)和IA-32處理器舉例,高級(jí)語(yǔ)言以C語(yǔ)言為例,同時(shí)也會(huì)摻雜一些其它操作系統(tǒng)和處理器。 什么是系統(tǒng)調(diào)用?對(duì)于現(xiàn)代的操作系統(tǒng)來(lái)說(shuō),應(yīng)用程序運(yùn)行的時(shí)候是沒有權(quán)限去訪問系統(tǒng)資源的,操作系統(tǒng)為了防止各類應(yīng)用程序可能會(huì)破壞系統(tǒng)資源,對(duì)系統(tǒng)資源做了保護(hù),阻止應(yīng)用程序直接去訪問這些資源,而應(yīng)用程序又有訪問這些系統(tǒng)資源的需求,因此操作系統(tǒng)提供了系統(tǒng)調(diào)用,讓所有的應(yīng)用程序統(tǒng)一通過(guò)系統(tǒng)調(diào)用來(lái)訪問系統(tǒng)資源,這里所說(shuō)的系統(tǒng)資源包括文件,網(wǎng)絡(luò) ,內(nèi)存,各類IO設(shè)備等。 應(yīng)用程序可以進(jìn)行系統(tǒng)調(diào)用,也可以調(diào)用標(biāo)準(zhǔn)庫(kù)或者API,一個(gè)系統(tǒng)調(diào)用的內(nèi)部有很多的步驟,比如需要進(jìn)行用戶態(tài)模式到內(nèi)核態(tài)模式的互相切換。 這里簡(jiǎn)單介紹下模式切換,我們知道一個(gè)完整的應(yīng)用程序分為兩部分,一部分是應(yīng)用程序的代碼和數(shù)據(jù),另一部分是內(nèi)核的代碼和數(shù)據(jù),切換模式就是這兩部分的分水嶺,意味著處理器進(jìn)入了一個(gè)不同的模式,不同的模式就是不同的世界,不同的世界就有不同的權(quán)限,而內(nèi)核態(tài)模式就是王者,可以掌握所有的資源,用戶態(tài)模式只能掌握自己的一畝三分地。 正如上面所說(shuō),系統(tǒng)調(diào)用需要進(jìn)行模式切換,而每個(gè)完整的應(yīng)用程序都有兩個(gè)棧,一個(gè)用戶棧,一個(gè)內(nèi)核棧,這兩個(gè)棧是獨(dú)立的,用戶棧在用戶空間,內(nèi)核棧在內(nèi)核空間,因此切換模式時(shí),棧也得切換。 因此我們可以將系統(tǒng)調(diào)用的執(zhí)行步驟分為三步:1.執(zhí)行前的準(zhǔn)備工作。2.執(zhí)行處理程序(處理函數(shù))。3.執(zhí)行后的善后工作,當(dāng)然內(nèi)核模式切換和棧切換就是1和3的工作了,這里的三步都是在內(nèi)核模式下執(zhí)行的,如下圖所示 應(yīng)用程序直接系統(tǒng)調(diào)用步驟 從上圖得知,執(zhí)行一個(gè)系統(tǒng)調(diào)用很復(fù)雜,需要干很多的活,Linux的編譯器提供了很多共享庫(kù)(so文件)來(lái)提供系統(tǒng)調(diào)用,例如Linux的glibc庫(kù)就提供了文件操作相關(guān)的系統(tǒng)調(diào)用,例如下面的代碼: int read(int fd,void *buf,int count);//讀文件數(shù)據(jù)int write(int fd,const void *buf,int couint);//寫文件數(shù)據(jù)int open(const char * pathname,int flags,mode_t mode);//打開文件 上面的代碼只是glibc庫(kù)中幾個(gè)比較有代表性的例子,linux操作系統(tǒng)提供了幾百個(gè)系統(tǒng)調(diào)用,這些系統(tǒng)調(diào)用分散在各個(gè)共享庫(kù)中,這里就不再闡述。 Windows操作系統(tǒng)提供了API,簡(jiǎn)稱Windows API或者SDK,它不是系統(tǒng)調(diào)用而是對(duì)系統(tǒng)調(diào)用做了二次封裝,這些API是由各類DLL(動(dòng)態(tài)鏈接庫(kù))提供的,開發(fā)人員導(dǎo)入這些DLL就可以通過(guò)Windows API來(lái)開發(fā)Windows應(yīng)用程序,因此Widows應(yīng)用程序執(zhí)行系統(tǒng)調(diào)用的步驟就變成了如下圖所示 Windows應(yīng)用程序系統(tǒng)調(diào)用步驟 正如上文所述,每個(gè)操作系統(tǒng)都提供它各自的系統(tǒng)調(diào)用,那么寫一段C代碼怎樣能做到跨操作系統(tǒng)呢?答案是C語(yǔ)言標(biāo)準(zhǔn)庫(kù),C語(yǔ)言標(biāo)準(zhǔn)庫(kù)的目的就是讓開發(fā)人員寫一段C代碼,這些C代碼使用的是C標(biāo)準(zhǔn)庫(kù),那么這段代碼不需要進(jìn)行任何修改就可以跨操作系統(tǒng),前提是經(jīng)過(guò)不同操作系統(tǒng)編譯器的編譯,C標(biāo)準(zhǔn)庫(kù)的調(diào)用關(guān)系如下圖 C標(biāo)準(zhǔn)庫(kù) 由上圖得知,Linux通過(guò)共享庫(kù)直接提供系統(tǒng)調(diào)用,而Windows則通過(guò)Windows API間接進(jìn)行提供系統(tǒng)調(diào)用,中間增加了一個(gè)C標(biāo)準(zhǔn)庫(kù),它將不同操作系統(tǒng)之間系統(tǒng)調(diào)用標(biāo)準(zhǔn)化,做了二次封裝,簡(jiǎn)化了系統(tǒng)調(diào)用的復(fù)雜度,提供給應(yīng)用程序。 標(biāo)準(zhǔn)庫(kù)也有它的缺點(diǎn),缺點(diǎn)就是只能取各個(gè)操作系統(tǒng)系統(tǒng)調(diào)用的交集,這意味著只有操作系統(tǒng)都有的功能才能納入到標(biāo)準(zhǔn)庫(kù),然后有的時(shí)候,需要一些操作系統(tǒng)專有的功能時(shí),還得直接進(jìn)行系統(tǒng)調(diào)用或者調(diào)用API,這個(gè)就會(huì)出現(xiàn)跨系統(tǒng)的問題。 對(duì)于用標(biāo)準(zhǔn)庫(kù)開發(fā)的應(yīng)用程序,它的系統(tǒng)調(diào)用步驟可以總結(jié)如下圖 C標(biāo)準(zhǔn)庫(kù)系統(tǒng)調(diào)用 好了,【什么是系統(tǒng)調(diào)用】的話題介紹到這里了,下面來(lái)看看系統(tǒng)調(diào)用具體是怎么實(shí)現(xiàn)的。 系統(tǒng)調(diào)用的實(shí)現(xiàn)?上個(gè)環(huán)節(jié)闡述的是【什么是系統(tǒng)調(diào)用】以及系統(tǒng)調(diào)用的大致步驟,這個(gè)環(huán)節(jié)將以Linux操作系統(tǒng)為例來(lái)闡述系統(tǒng)調(diào)用的實(shí)現(xiàn)原理和細(xì)節(jié),當(dāng)然其它操作系統(tǒng)系統(tǒng)調(diào)用的實(shí)現(xiàn)原理比較相似,可以舉一反三。 主流的操作系統(tǒng)如Linux和Windows是通過(guò)中斷來(lái)實(shí)現(xiàn)系統(tǒng)調(diào)用的。 以操作系統(tǒng)Linux(2.5以前),處理器為Inter IA-32為例,看看fork這個(gè)系統(tǒng)調(diào)用是怎么實(shí)現(xiàn)的,其它的Linux系統(tǒng)調(diào)用類似,整體過(guò)程如下圖 Linux系統(tǒng)調(diào)用過(guò)程 上圖為系統(tǒng)調(diào)用涉及到的9個(gè)步驟,我們逐個(gè)看起 1.應(yīng)用程序調(diào)用linux庫(kù)提供的fork函數(shù),發(fā)起一個(gè)fork系統(tǒng)調(diào)用,這個(gè)系統(tǒng)調(diào)用的目的是創(chuàng)建一個(gè)子進(jìn)程,這個(gè)子進(jìn)程拷貝一份父進(jìn)程的虛擬進(jìn)程空間。 2.fork函數(shù)的第一步就是將2放入寄存器eax,每個(gè)系統(tǒng)調(diào)用都有一個(gè)編號(hào),2就是fork系統(tǒng)調(diào)用的編號(hào),eax是默認(rèn)用于傳遞系統(tǒng)調(diào)用編號(hào)的寄存器。 如果系統(tǒng)調(diào)用有參數(shù),則將參數(shù)傳入到如下的寄存器EBX,ECX,EDX,ESI,EDI,EBP,可以看出系統(tǒng)調(diào)用最多支持6個(gè)參數(shù),fork系統(tǒng)調(diào)用沒有參數(shù)。 fork函數(shù)的第二步就是執(zhí)行中斷指令int 0x80,中斷指令int用于發(fā)送中斷信號(hào)給處理器,0x80為中斷向量號(hào),這個(gè)向量號(hào)是系統(tǒng)調(diào)用中斷處理程序?qū)S谩?/p> int指令同時(shí)也會(huì)將模式從用戶態(tài)切換到內(nèi)核態(tài),用戶棧切換到內(nèi)核棧,同時(shí)會(huì)將當(dāng)前被中斷的應(yīng)用程序,中斷時(shí)的寄存器內(nèi)容入棧(SS,ESP,EFLAGS,CS,EIP),這里的入棧指的是入內(nèi)核棧(每一個(gè)應(yīng)用程序都一個(gè)用戶棧和內(nèi)核棧)。 整體來(lái)看,2步驟的匯編代碼如下:
3.處理器執(zhí)行完當(dāng)前的指令后,會(huì)檢查處理器的中斷引腳,發(fā)現(xiàn)有中斷信號(hào),然后檢查狀態(tài)寄存器(EFLAGS),發(fā)現(xiàn)中斷屏蔽IF標(biāo)志是打開的(系統(tǒng)調(diào)用中斷信號(hào)不會(huì)被屏蔽),處理器根據(jù)中斷信號(hào),分析出中斷向量號(hào),然后根據(jù)中斷向量號(hào)去查找中斷描述符表,找到了該中斷向量號(hào)對(duì)應(yīng)的中斷處理程序。 4.操作系統(tǒng)跳轉(zhuǎn)到中斷處理程序,然后開始執(zhí)行中斷處理程序,0x80對(duì)應(yīng)的中斷處理程序是系統(tǒng)調(diào)用中斷處理程序(system_call)。 該中斷處理程序首先會(huì)將EAX,EBX,ECX,EDX,ESI,EDI,EBP這幾個(gè)寄存器入棧,之所以入棧,就是為了防止后續(xù)的工作覆蓋這些寄存器,核心匯編指令如下: push EAX;push EBX;push ECX;push EDX;push ESI;push EDI;push EBP; 5.系統(tǒng)調(diào)用中斷處理程序緊接著根據(jù)系統(tǒng)調(diào)用號(hào)(這里就是fork系統(tǒng)調(diào)用號(hào)即2),去系統(tǒng)調(diào)用表進(jìn)行查找,可以找到該系統(tǒng)調(diào)用號(hào)對(duì)應(yīng)的處理程序(也可以叫處理函數(shù)),Linux操作系統(tǒng)的系統(tǒng)處理函數(shù)一般以sys開頭,fork的系統(tǒng)處理函數(shù)就是sys_fork。 6.找到了系統(tǒng)處理函數(shù)后,開始執(zhí)行該函數(shù),處理函數(shù)可以從內(nèi)核棧中獲取函數(shù)的參數(shù),函數(shù)執(zhí)行完成后,函數(shù)的返回值,默認(rèn)采用EAX寄存器進(jìn)行返回。 7~8.系統(tǒng)處理函數(shù)執(zhí)行完成后,回到了系統(tǒng)調(diào)用中斷處理程序,中斷處理程序執(zhí)行iret指令,iret指令負(fù)責(zé)從內(nèi)核態(tài)切換到用戶態(tài),將內(nèi)核態(tài)入棧的寄存器數(shù)據(jù)出棧到SS,ESP,EFLAGS,CS,EIP這幾個(gè)寄存器,然后跳轉(zhuǎn)到系統(tǒng)調(diào)用處。 9.系統(tǒng)調(diào)用fork返回到應(yīng)用程序。 Linux操作系統(tǒng)(2.5以前)的系統(tǒng)調(diào)用實(shí)現(xiàn)原理闡述完了,Windows操作系統(tǒng)的系統(tǒng)調(diào)用也采用類似的機(jī)制,另外要說(shuō)的是,自從Linux(2.5)以上,處理器Inter 奔騰二代以后,為了提高系統(tǒng)調(diào)用的效率,Inter處理器提供了兩個(gè)指令來(lái)進(jìn)行系統(tǒng)調(diào)用的進(jìn)入和退出即sysenter和sysexit指令。 sysenter指令代替了int中斷指令發(fā)起系統(tǒng)調(diào)用,執(zhí)行這個(gè)指令后,會(huì)直接跳轉(zhuǎn)到一個(gè)系統(tǒng)調(diào)用的處理函數(shù)地址處,去執(zhí)行系統(tǒng)調(diào)用,這個(gè)處理函數(shù)的地址是存儲(chǔ)在一個(gè)指定的寄存器中,sysenter這個(gè)指令也負(fù)責(zé)模式的切換和應(yīng)用程序現(xiàn)場(chǎng)寄存器的備份,這一點(diǎn)同int一樣,處理函數(shù)參數(shù)的傳遞跟以前一樣,還是通過(guò)寄存器的方式傳遞,沒有變化。 syscenter指令代替了iret恢復(fù)指令,它負(fù)責(zé)模式切換和現(xiàn)場(chǎng)寄存器的恢復(fù),這一點(diǎn)同iret指令相似。 其它的操作系統(tǒng)例如Power PC,AMD的系統(tǒng)調(diào)用與Linux(2.5以上)類似,不同的是,它們采用不同的指令來(lái)進(jìn)行模式切換和寄存器備份,參數(shù)的傳遞也是采用寄存器的方式,只是寄存器個(gè)數(shù)和名稱不一樣罷了。 好了,關(guān)于系統(tǒng)調(diào)用闡述完成,談下一個(gè)話題。 |
|