本文內容概要: 1、linux結構圖; 2、系統調用和庫函數概述; 3、基于int的linux的系統調用的具體實現; 4、為什么需要系統調用; 5、系統調用和庫函數的關系。
引入: 前段時間在牛客網站上刷題時,看到這樣一道題目,當時也是不知道該怎么做。之后去查閱了資料,才知道原來是考查系統調用和庫函數,這里先貼出原題。 正確答案是C,因為C選項是系統調用,其他選項都是庫函數。 下邊就來簡述系統調用和庫函數,以及Linux下系統調用是如何實現的。
1.linux結構圖:
這張圖片來自于以下文章,http://blog.csdn.net/lf_2016/article/details/54587020感謝作者~~
2.系統調用與庫函數概述: 系統調用是應用程序(庫函數也是應用程序的一部分)與操作系統內核之間的接口(對于windows來講,它并不是與應用程序的最終接口,API才是最終接口),它決定了應用程序是如何與內核打交道的。無論程序是直接進行系統調用還是通過運行庫,則最終還是會達到系統調用這個層面上。(下文將會具體分析) 系統調用既然是應用程序與操作系統之間的接口,那么所有的應用程序都必須依賴于系統調用,所以系統調用必須有嚴格而又明確的定義,并且具有穩定性和向后兼容性(所謂的向后兼容性就是隨著系統的更新,之前舊的系統調用仍然可以使用,只是需要增加新的接口)。 庫函數是為了方便人們編寫應用程序而引出的。比如C語言寫的第一個程序hello world,就是調用庫函數中的PRintf()函數完成輸出的。 很多操作系統是以系統調用作為應用程序的最底層的,而windows的最底層接口是windows API。盡管windows的內核提供了數百個系統調用(windows下又把系統調用稱為系統服務),但是出于種種原因,windows并沒有將系統調用公開,而是在系統調用之上,建立了這樣一個API層,讓應用程序只能調用API層的函數,而不是如Linux這樣的操作系統直接使用系統調用。
3.基于int的linux的系統調用的具體實現: 系統調用是屬于操作系統內核的一部分的,必須以某種方式提供給進程讓它們去調用。CPU可以在不同的特權級別下運行,而相應的操作系統也有不同的運行級別,用戶態和內核態。 首先,我們得知道用戶態和內核態的區別,參考文(http://blog.csdn.net/xieyutian1990/article/details/38413413)。
運行在內核態的進程可以毫無限制的訪問各種資源,而在用戶態下的用戶進程的各種操作都有著限制,比如不能隨意的訪問內存、不能開閉中斷以及切換運行的特權級別。顯然,屬于內核的系統調用一定是運行在內核態下,但是如何切換到內核態呢? 操作系統一般是通過中斷從用戶態切換到內核態。 學習過計算機組成原理這類課程之后,我們都知道中斷是有兩個屬性,一個是中斷號(從0開始編號),一個是中斷處理程序。通常,中斷是有兩種類型,一種是硬件中斷(一般來源于硬件的異常或者其他事件的發生,比如鍵盤被按下),一種是軟件中斷(通常是一條指令,i386下是int指令),通常有一個參數,記錄中斷號。在i386下,windows的絕大多數的系統調用都是由int 0x2e來觸發,而Linux則是使用int 0x80來觸發所有的系統調用。 在Linux內核版本2.6.19版本一共提供了319個系統調用。EAX寄存器是用來存儲系統調用的接口號。 EAX = 1,表示退出進程exit;EAX=2,表示創建進程fork,等等。 Linux的系統中斷流程:
細節實現: 1>觸發中斷: Linux下的系統調用是通過 宏函數來定義的。比如_syscall0是用于定義一個沒有參數的系統調用的封裝,定義在include /asm-i386/unistd.h中,總共有 7個宏函數。 Linux下支持的系統調用的參數至多有6個,用6個寄存器來傳遞,分別是EBX,ECX,EDX,ESI,EDI,EBP。使用的時候是依次使用,也就是說系統調用有一個參數的時候,使用寄存器EBX,而不是其他。 以上是系統調用的一種方式,那么另一種方式是什么呢?應用程序調用C庫函數,庫函數底層調用的是系統調用,就如下圖所示(引自http://blog.csdn.net/skyflying2012/article/details/10044343)
2>切換堆棧: 在學習C語言的中后期,我們大概掌握了一些基本知識,就開始想:為什么點擊運行按鈕程序就會跑起來?編譯器在背后都干了哪些事,函數調用是怎么實現的等等問題。我們知道,函數的調用其實也就是利用中斷的,借助于堆棧來保存函數的當前信息,當調用函數執行完成之后,就會繼續執行當前函數。同樣,系統調用也是有自己的調用堆棧,內核態有內核棧,用戶態有用戶棧,當要觸發系統調用的時候,程序的執行流是從用戶態到內核態,這時,程序的當前棧也是從用戶棧切換到內核棧,當系統調用執行完成之后,又會從內核棧返回到用戶棧。 總結堆棧的切換: 當系統調用的中斷被觸發的時候,CPU會切換到內核態,找到當前進程的內核棧,并在當前進程的內核棧中寫入用戶棧的信息,包括,SS(當前棧所在的頁),ESP等信息;當系統調用執行完成之后,CPU會調用iret指令切換到用戶態 ,iret指令會從內核棧中讀取用戶棧的信息,進行返回。
3>中斷處理程序: 從上邊的一個圖(Linux系統中斷流程),我們可以看出,當觸發中斷之后,系統會查詢中斷向量表,得知,0x80觸發的是系統調用,然后從EAX寄存器中得到系統調用編號,去執行相應的系統調用。這里也有點類似于函數的調用過程。
4.為什么需要系統調用? (1)系統調用可以為用戶控件提供訪問硬件資源的統一接口,以至于應用程序不必去關注具體的硬件訪問的操作。比如:fopen函數,使用的時候用戶就不需要去管底層的尋道找道得到原理等等。再說,就是計算機的硬件資源是有限的,不能做到多個進程同時訪問硬件資源,所以需要系統調用來控制。 (2)可以對系統進行保護,保證系統的穩定和安全。也就是說,用戶訪問內核的路徑事先是已經被規定好的。
5.系統調用和C庫函數的關系: 并不是一一對應,幾個庫函數對應一個系統調用(malloc()和free()對應的是brk系統調用)。也有一個庫函數對應一個系統調用(open()函數對應open系統調用)。也有些庫函數不需要系統調用(比如strcpy函數,atoi函數)。
【總結 】 (1)系統調用是應用程序與操作系統內核的接口,但是不是最終接口。windows下API才是最終接口,因為windows下的系統調用不公開。 (2)系統調用是通過中斷來觸發的,Linux下是0x80來觸發。 (3)系統調用最多可以有6個參數,放在6個寄存器EBX,ECX,EDX,ESI,EDI,EBP中,但是系統調用號是放在EAX中。 (4)觸發系統調用的時候,CPU會從用戶態切換到內核態,程序的當前棧也會從用戶棧切換到內核棧,系統調用執行完成之后執行相反的操作。 (5)操作系統包括內核和其他程序,內核中包括進程管理,進程調度等等,其他程序包括函數庫等等。所以我們可以認為內核約等于操作系統。
參考書籍: 《程序員的自我修養—-鏈接、裝載、庫》俞甲子 石凡 潘愛民 著
本人小白,如有問題,請不吝指出~~如果感興趣的讀者,可以自行去閱讀《程序員的自我修養》一書的第12章節。
新聞熱點
疑難解答