計算機系統的各種硬體資源是有限的,在現代多工作業系統上同時執行的多個程序都需要訪問這些資源,為了更好的管理這些資源程序是不允許直接操作的,所有對這些資源的訪問都必須有作業系統控制。也就是說作業系統是使用這些資源的唯一入口,而這個入口就是作業系統提供的系統呼叫(system call)。
系統呼叫是屬於作業系統核心的一部分的,必須以某種方式提供給程序讓它們去呼叫。cpu可以在不同的特權級別下執行,而相應的作業系統也有不同的執行級別,使用者態和核心態。執行在核心態的程序可以毫無限制的訪問各種資源,而在使用者態下的使用者程序的各種操作都有著限制,比如不能隨意的訪問記憶體、不能開閉中斷以及切換執行的特權級別。顯然,屬於核心的系統呼叫一定是執行在核心態下,但是如何切換到核心態呢?
答案是中斷。作業系統一般是通過中斷從使用者態切換到核心態。中斷就是乙個硬體或軟體請求,要求cpu暫停當前的工作,去處理更重要的事情。比如,在x86機器上可以通過int指令進行軟體中斷,而在磁碟完成讀寫操作後會向cpu發起硬體中斷。
中斷有兩個重要的屬性,中斷號和中斷處理程式。中斷號用來標識不同的中斷,不同的中斷具有不同的中斷處理程式。在作業系統核心中維護著乙個中斷向量表(interrupt vector table),這個陣列儲存了所有中斷處理程式的位址,而中斷號就是相應中斷在中斷向量表中的偏移量。
一般地,系統呼叫都是通過中斷實現的,比如,linux下中斷號0x80就是進行系統呼叫的。接下來就來看一下linux下系統呼叫具體的實現過程。
前文已經提到了linux下的系統呼叫是通過0x80實現的,但是我們知道作業系統會有多個系統呼叫(linux下有319個系統呼叫),而對於同乙個中斷號是如何處理多個不同的系統呼叫的?最簡單的方式是對於不同的系統呼叫採用不同的中斷號,但是中斷號明顯是一種稀缺資源,linux顯然不會這麼做;還有乙個問題就是系統呼叫是需要提供引數,並且具有返回值的,這些引數又是怎麼傳遞的?也就是說,對於系統呼叫我們要搞清楚兩點:
1. 系統呼叫的函式名稱轉換。
2. 系統呼叫的引數傳遞。
首先看第乙個問題。實際上,linux中處理系統呼叫的方式與中斷類似。每個系統呼叫都有相應的系統呼叫號作為唯一的標識,核心維護一張系統呼叫表,表中的元素是系統呼叫函式的起始位址,而系統呼叫號就是系統呼叫在呼叫表的偏移量。在進行系統呼叫是只要指定對應的系統呼叫號,就可以明確的要呼叫哪個系統呼叫,這就完成了系統呼叫的函式名稱的轉換。舉例來說,linux中fork的呼叫號是2(具體定義,在我的計算機上是在/usr/include/asm/unistd_32.h,可以通過find / -name unistd_32.h -print查詢)
[cpp]view plain
copy
print?
#ifndef _asm_x86_unistd_32_h
#define _asm_x86_unistd_32_h
/** this file contains the system call numbers.
*/#define __nr_restart_syscall 0
#define __nr_exit 1
#define __nr_fork 2
#define __nr_read 3
#define __nr_write 4
#define __nr_open 5
linux中是通過暫存器%eax傳遞系統呼叫號,所以具體呼叫fork的過程是:將2存入%eax中,然後進行系統呼叫,偽**:
[plain]view plain
copy
print?
mov eax, 2
int 0x80
對於引數傳遞,linux是通過暫存器完成的。linux最多允許向系統呼叫傳遞6個引數,分別依次由%ebx,%ecx,%edx,%esi,%edi和%ebp這個6個暫存器完成。比如,呼叫exit(1),偽**是:
[plain]view plain
copy
print?
mov eax, 2
mov ebx, 1
int 0x80
因為exit需要乙個引數1,所以這裡只需要使用ebx。這6個暫存器可能已經被使用,所以在傳參前必須把當前暫存器的狀態儲存下來,待系統呼叫返回後再恢復,這個在後面棧切換再具體講。
linux中,在使用者態和核心態執行的程序使用的棧是不同的,分別叫做使用者棧和核心棧,兩者各自負責相應特權級別狀態下的函式呼叫。當進行系統呼叫時,程序不僅要從使用者態切換到核心態,同時也要完成棧切換,這樣處於核心態的系統呼叫才能在核心棧上完成呼叫。系統呼叫返回時,還要切換回使用者棧,繼續完成使用者態下的函式呼叫。
暫存器%esp(棧指標,指向棧頂)所在的記憶體空間叫做當前棧,比如%esp在使用者空間則當前棧就是使用者棧,否則是核心棧。棧切換主要就是%esp在使用者空間和核心空間間的來回賦值。在linux中,每個程序都有乙個私有的核心棧,當從使用者棧切換到核心棧時,需完成儲存%esp以及相關暫存器的值(%ebx,%ecx...)並將%esp設定成核心棧的相應值。而從核心棧切換會使用者棧時,需要恢復使用者棧的%esp及相關暫存器的值以及儲存核心棧的資訊。乙個問題就是使用者棧的%esp和暫存器的值儲存到什麼地方,以便於恢復呢?答案就是核心棧,在呼叫int指令機型系統呼叫後會把使用者棧的%esp的值及相關暫存器壓入核心棧中,系統呼叫通過iret指令返回,在返回之前會從核心棧彈出使用者棧的%esp和暫存器的狀態,然後進行恢復。
相信大家一定聽過說,系統呼叫很耗時,要盡量少用。通過上面描述系統呼叫的實現原理,大家也應該知道這其中的原因了。第一,系統呼叫通過中斷實現,需要完成棧切換。第二,使用暫存器傳參,這需要額外的儲存和恢復的過程。
上面關於系統呼叫的闡述,如有錯誤歡迎指正。。
系統呼叫的實現原理
計算機系統的各種硬體資源是有限的,在現代多工作業系統上同時執行的多個程序都需要訪問這些資源,為了更好的管理這些資源程序是不允許直接操作的,所有對這些資源的訪問都必須有作業系統控制。也就是說作業系統是使用這些資源的唯一入口,而這個入口就是作業系統提供的系統呼叫 system call 系統呼叫是屬於作...
系統呼叫的原理
原始的系統呼叫是通過中斷向量80所代表的的中斷來實現 把系統呼叫號存到乙個暫存器 中,然後發出int 0x80。已經註冊號的中斷處理程式會檢測暫存器的內容,根據不同的系 統呼叫號,提供具體的服務。中斷的處理過程分三步 a 保持暫存器的內容 b 呼叫中斷 處理程式進行處理 c 重新載入之前暫存器的內容...
系統呼叫原理
系統呼叫 系統呼叫是應用程式和作業系統核心的介面,無論程式是直接進行系統呼叫還是通過執行庫,最終還是會到達系統呼叫層面上。之所以要系統呼叫,是因為現在作業系統都將可能產生衝突的系統資源給保護起來,組織應用程式直接訪問。這些系統資源包括檔案 網路 io 各種裝置等。所有的這些操作都必須經由作業系統所規...