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