系統呼叫的目的是:請求系統服務。作業系統不允許使用者直接操作各種硬體資源,因此使用者程式只能透過系統呼叫的方式來請求核心為其服務,間接地使用各種資源。
由作業系統提供的功能,通常應用程式本身是無法實現的。例如對檔案進行操作,應用程式必須透過系統呼叫才能做到,因為只有作業系統才具有直接管理週邊設備的權限。又如進程或執行緒間的同步互斥操作,也必需經由作業系統對核心變數進行維護才能完成。
從下到上看一個完整的電腦系統:實體硬體->OS核心->OS服務->應用程式。這裡的OS核心起到了「承上啟下」的關鍵作用,向下管理實體硬件,向上為作業系統服務和應用程式提供接口,這裡的接口就是系統呼叫了。
應用程式的程序通常在user模式下運行,當它呼叫一個系統呼叫時,進程進入kernel模式,執行的是kernel內部的程式碼,從而具有執行特權指令的權限,完成特定的功能。換句話說,系統呼叫是應用程式主動進入作業系統核心的入口。
顧名思義是把函式放到函式庫裡,是把一些常用來的函數編完放到一個檔案裡,供別人用。別人用的時候把所在的檔名用#include加到裡面就可以了,通常放到lib檔裡。
函式庫函數主要由兩方面提供:一是作業系統提供的;另一類是由第三方提供的。
系統提供的這些函數把系統呼叫進行封裝或組合,可以實現更多的功能,這樣的函式庫函數能夠實現一些對於核心來說比較複雜的操作。例如read函數根據參數,直接就能讀文件,而背後隱藏的文件例如在那個磁軌,那個扇區,載入到那個內存,是程式設計師不必關心的問題。這些操作裡面也包含了系統呼叫。例如write()這個系統函數,會呼叫同名的系統調用,來完成寫入操作。
對於第三方函式庫,其實和系統函式庫一樣,只是他直接利用系統呼叫的可能性要小一些,而是系統提供的API介面來是實作。例如printf,實際上呼叫了write()這個系統函數。第三方函式庫函數大部分是系統函數的封裝。
系統呼叫與函式庫函數的連結:
事實上,系統呼叫所提供給使用者的是直接而純碎的高級服務,如果想要更加人性化,具有更符合特定情況的功能,那麼就要我們用戶自己定義,因此衍生了庫函數,它把部分系統調用包裝起來。例如當我們要用C語言印出一句話的時候,如果沒有用到函式庫函數printf,那麼我們就需要自己實作就需要呼叫putc()和write()等這樣一些系統函數。顯得比較麻煩,所以系統呼叫是為了方便使用作業系統的接口,而函式庫函數則是為了人們編程的方便。
例如,在Linux作業系統下,C語言的函式庫函數printf,實際上使用了write系統呼叫;而函式庫函數strcpy(字串拷貝)卻沒有使用任何系統呼叫。另外,一個系統的系統呼叫介面通常是能夠完成所有必需功能的最小集合,可能存在多個函式庫函數對同一個系統呼叫進行封裝。例如,在Linux中,malloc、calloc和free三個函式庫函數底層都是呼叫brk系統呼叫完成的。
應用程式、函式庫函數和系統呼叫的關係如下圖所示:
#系統呼叫和函式庫函數的差異:
函式庫函數的呼叫是語言或應用程式的一部分,而係統呼叫則是作業系統的一部分。
系統呼叫是應用程式與核心互動的介面。人們在長期的程式設計中發現使用系統函數有個重大的缺點,那就是程式的移植性。例如linux提供的系統呼叫的函數和windows就不一樣。
函式庫函數呼叫則是針對應用程式開發的,相當於應用程式的api,採用這樣的方式有很多原因:
二、CPU的核心模式與使用者模式
##############################################################################通常,處理器設有兩種模式:“用戶模式”與“核心模式”,透過一個標籤位元來辨別目前正處於什麼模式。核心模式可以執行所有指令,包括特權指令(主要是一些硬體管理的指令,例如修改基址暫存器內容的指令) ,而使用者模式不能執行特權指令。這樣的設計主要為了安全問題,即由作業系統負責管理硬件,避免上層應用因錯誤設計而導致硬體問題。
既然只有作業系統能直接操作硬件,作業系統有必要提供介面來為應用程式提供使用硬體功能的入口,這些介面就被稱為系統呼叫。
當作業系統接收到系統呼叫請求後,會讓處理器進入核心模式,從而執行諸如I/O操作,修改基址暫存器內容等指令,而當處理完系統呼叫內容後,操作系統會讓處理器返回使用者模式,來執行使用者程式碼。
對應CPU的核心模式和使用者模式,進程運作的狀態分為管態(核心態)和目態(使用者態)。具體請看文章:作業系統--使用者狀態和核心狀態
中斷(Interrupt)通常是指在CPU內部或外部發生了某個待處理的事件,從而CPU必要改變當前指令的執行順序去處理這類事件。在介紹中斷和系統呼叫的關係之前,以下先把中斷做一個分類。
中斷可以大體分為兩大類:
Asynchronous interrupts(外中斷): 由CPU外部的其它硬體產生,說這類別中斷是異步的,意思是中斷訊號可以在任意時間發射,與CPU本身的時脈節拍沒有關係。如時鐘中斷,硬碟讀寫服務請求中斷等。
Synchronous interrupts(內中斷/異常):在CPU內部產生,說這類中斷是同步的,意思是中斷訊號的發射時間一定在當前指令執行結束之後。一般來自CPU的內部事件或程式執行中的事件,如非法操作碼、位址越界、浮點溢位等。
Synchronous interrupts (例外)又分為以下若干類別:
Processor-detected exceptions:處理器在執行指令時偵測到的中斷,如除零操作。
Faults:發生了某個異常條件,但異常條件被消除後,原來的程式流程可以繼續執行而不受任何影響,如缺頁異常。注意觸發中斷的指令會重新執行。
Traps:由陷入指令引起的中斷,通常用於程式偵錯。
Aborts:CPU內部有重要錯誤發生,例如硬體錯誤或系統表值發生錯誤。一旦這種中斷發生,錯誤將不可恢復,只能將目前程序終止。
Programmed exceptions:也稱為software interrupts (軟體中斷) ,由程式設計師的程式碼主動發起的中斷,用來實現系統調用。如在Linux中,就是用int 0x80
指令實作系統呼叫。
至此,我們發現了中斷與系統呼叫的關係:系統呼叫是一種特殊的中斷類型(軟中斷)。
五、核心對於系統呼叫的處理
#在x86的機器中,用一個8bit的數字(0~255)來區分各種中斷,這個數字稱為中斷向量(vector)。其中一個中斷向量,即128 (0x80),專門用來執行系統呼叫。
在Linux系統中,存有一個系統表,叫做Interrupt DescriptorTable,簡稱IDT。 IDT表共有256項,存放了從中斷向量到對應處理例程(interrupt or exceptionhandler)的映射關係。當某個中斷發生時,CPU從IDT表中查找到對應的處理例程的位址來執行。
系統呼叫的處理例程在IDT表中佔有一項。這一項是在trap_init函式中被初始化的,如下:set_system_gate(SYSCALL_VECTOR,&system_call);
。如前所述,上面程式碼中的SYSCALL_VECTOR的值為128。
當系統呼叫發生時,透過中斷機制,系統呼叫例程system_call被呼叫。它的執行過程大概分為4個步驟:
1、從暫存器中取出系統呼叫號碼、輸入參數,然後將這些暫存器的值壓入kernel堆疊中。根據系統呼叫號碼尋找系統呼叫分派表(system call dispatch table),找到系統呼叫服務例程(一個核心函數)。
2、呼叫查到的系統呼叫服務例程。
3、將系統呼叫服務例程的回傳值出棧,重新儲存在暫存器中。
上面描述的系統呼叫例程system_call在kernel空間中執行。在執行前,系統呼叫號碼和輸入參數已經存入了暫存器,這個存入過程由user空間的程式碼完成。實際上,如同第一節所講,每個真正的系統呼叫基本上都有一個封裝它的函式庫函數,一般是在這個函式庫函數中完成系統呼叫號碼和輸入參數的保存動作。當系統呼叫例程system_call執行完畢後,傳回值會透過暫存器再傳回user空間的函式庫函數。
以上是系統調用的目的是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!