由一道牛客題目想到系統調用和庫函數

本文內容概要:
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)。


   內核態與用戶態是操作系統的兩種運行級別,當程序運行在3級特權級上時,就可以稱之爲運行在用戶態,因爲這是最低特權級,是普通的用戶進程運行的特權級,大部分用戶直接面對的程序都是運行在用戶態;反之,當程序運行在0級特權級上時,就可以稱之爲運行在內核態。運行在用戶態下的程序不能直接訪問操作系統內核數據結構和程序。當我們在系統中執行一個程序時,大部分時間是運行在用戶態下的,在其需要操作系統幫助完成某些它沒有權力和能力完成的工作時就會切換到內核態。
 這兩種狀態的主要差別是: 處於用戶態執行時,進程所能訪問的內存空間和對象受到限制,其所處於佔有的處理機是可被搶佔的 ; 而處於核心態執行中的進程,則能訪問所有的內存空間和對象,且所佔有的處理機是不允許被搶佔的。

運行在內核態的進程可以毫無限制的訪問各種資源,而在用戶態下的用戶進程的各種操作都有着限制,比如不能隨意的訪問內存、不能開閉中斷以及切換運行的特權級別。顯然,屬於內核的系統調用一定是運行在內核態下,但是如何切換到內核態呢?
操作系統一般是通過中斷從用戶態切換到內核態。
學習過計算機組成原理這類課程之後,我們都知道中斷是有兩個屬性,一個是中斷號(從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章節。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章