例子
首先看一個標準C庫的例子:當我們程序中使用了C庫中的printf()函數,實際在底層是在內核態中調用了write()函數。圖中右側則是將程序代碼與C庫都算到應用程序中,內核提供了一個系統調用接口。
從這個例子我們可以得到以下幾點:
1. 系統調用是操作系統服務的編程接口;
2. 系統調用通常由高級語言編寫(C或者C++);
3. 程序訪問通常是通過高層次的API接口(比如C庫)而不是直接進行系統調用。最常見的三種:1. Windows下的Win32 API;2. POSIX-based系統(包括UNIX、LINUX、Mac OS等)下的 POSIX API;3. JAVA虛擬機中的Java API。
實現:
每個系統調用對應一個系統調用編號。當使用系統調用時,系統調用首先通過軟中斷的方式進入到內核的中斷向量表產生中斷,發現是系統調用軟中斷后轉移到系統調用表,系統調用表中記錄系統調用編號與具體實現之間的映射關系,根據系統調用編號選取不同的系統調用實現,得到結果之后返回。通過這種方式,用戶不需要知道系統調用內部是如何實現的,而只需要設置調用參數和湖區返回結果即可,并且系統調用接口的細節大部分都隱藏函數庫后面,通過調用庫函數實現。
函數調用與系統調用的不同:
使用的指令不同。系統調用使用INT和IRET,函數調用使用CALL和RET,他們的指令級是完全不同的。具體還有哪些不同呢?我們知道在調用一個函數的時候需要把參數壓到堆棧中去,然后轉到相應函數去執行,執行的時候從堆棧獲取我的參數信息執行,然后返回結果。而對于系統調用來講,內核是受保護的,為了保護內核,內核與應用程序之間使用不同的堆棧,因此系統調用時會有一個堆棧和特權級的切換,首先切換到內核態,此時可以使用特權指令,并擁有自己的堆棧,執行完后再切換回用戶態。而常規調用是沒有堆棧切換的。
開銷不同。系統調用比函數調用更安全,但是開銷更大。主要原因就是有一個用戶態的切換。具體有以下操作導致開銷更大:1. 引導機制,需要引導用戶態到內核態的切換;2. 建立內核堆棧,第一次調用時需要創建新的內核堆棧;3. 驗證參數,需要對傳入的參數的有效性合法性進行驗證;4. 在內核態中需要使用到用戶態中的一些信息,此時需要建立內核態到用戶態地址空間的映射關系;5. 建立用戶態內核態地址空間映射時會導致緩存的變化,TLB中有些內容會失效。
示例:文件復制
一個文件復制過程可以拆分如下圖所示,我們可以先看下整個過程中哪些過程會使用到系統調用:1.鍵盤輸入;2. 屏幕顯示;3. 讀取文件;4. 創建文件?5. 寫入文件。而在操作系統內部,鍵盤、屏幕與文件都視為文件系統里的,只是鍵盤、屏幕作為特殊文件來使用。因此用到了右圖中標紅的系統調用(還有一個creat)。
???
首先是一個read()函數如下圖,我們要知道參數意義和返回值。int fd是要讀的文件句柄,void *buf是緩沖區頭指針, int length是緩沖區的最大長度,返回值為讀出的數據長度int return_value。
那么內部是如何實現的呢?首先準備參數,如下圖中上面的匯編代碼,前6行都是壓棧,壓棧完最后一行是一個函數調用,這個函數調用還不是系統調用。因為所有系統調用都是通過一個宏展開成相應的函數。函數內的int就是進入內核態的指令,i就是系統調用的中斷向量編號,T_SYSCALL代表該軟中斷是系統調用,a(num)是系統調用編號,后面是相應的參數。
進入到內核態之后的過程如下圖:系統調用進入內核態實際是一個軟中斷,所有這些軟中斷會到最開始的一段匯編程序叫做alltraps(),在這里面獲取中斷的相關信息組成的數據結構,也就是TF數據結構。接下來來到trap(),判斷tf中有一個成員trapno(中斷向量)表明了該軟中斷是系統調用,則進入syscall(),發現系統調用編號代表的是sys_read()函數,接下來執行sys_read()函數,該函數讀取堆棧中的參數信息,之后進入sysfile_read()函數,該函數則是直接操作底層的驅動程序進行讀取,最后返回時調用trapret()函數返回給用戶態
補充:
大多數計算機系統將CPU執行狀態分為管態和目態。管態又稱為特權狀態、系統態或核心態。通常,操作系統在管態下運行。目態又叫做常態或用戶態,用戶程序只能在目態下運行,如果用戶程序在目態下執行特權指令,硬件將發生中斷,由操作系統獲得控制,特權指令執行被禁止,這樣可以防止用戶程序有意或無意的破壞系統。從目態轉換為管態的唯一途徑是中斷。
系統調用與庫函數的區別
- 系統調用:運行在用戶空間的應用程序向操作系統內核請求某些服務的調用過程。是內核提供給應用程序的接口函數,屬于系統的一部分。是為了方便應用使用操作系統的接口
- 函數庫調用是語言或應用程序的一部分。是為了方便人們編寫應用程序而引出的,比如你自己編寫一個函數其實也可以說就是一個庫函數。
- write/read就是系統調用,而printf/fread就是C標準庫函數.
?