Linux系統呼叫詳解(如何從使用者空間進入核心空間)
系統呼叫概述
計算機系統的各種硬體資源是有限的,在現代多工作業系統上同時執行的多個程序都需要訪問這些資源,為了更好的管理這些資源程序是不允許直接操作的,所有對這些資源的訪問都必須有作業系統控制。也就是說作業系統是使用這些資源的唯一入口,而這個入口就是作業系統提供的系統呼叫(System Call)。在linux中系統呼叫是使用者空間訪問核心的唯一手段,除異常和陷入外,他們是核心唯一的合法入口。
一般情況下應用程式通過應用程式設計介面API,而不是直接通過系統呼叫來程式設計。在Unix世界,最流行的API是基於POSIX標準的。
作業系統一般是通過中斷從使用者態切換到核心態。中斷就是一個硬體或軟體請求,要求CPU暫停當前的工作,去處理更重要的事情。比如,在x86機器上可以通過int指令進行軟體中斷,而在磁碟完成讀寫操作後會向CPU發起硬體中斷。
中斷有兩個重要的屬性,中斷號和中斷處理程式。中斷號用來標識不同的中斷,不同的中斷具有不同的中斷處理程式。在作業系統核心中維護著一箇中斷向量表(Interrupt Vector Table),這個陣列儲存了所有中斷處理程式的地址,而中斷號就是相應中斷在中斷向量表中的偏移量。
一般地,系統呼叫都是通過軟體中斷實現的,x86系統上的軟體中斷由int $0x80指令產生,而128號異常處理程式就是系統呼叫處理程式system_call(),它與硬體體系有關,在entry.S中用匯編寫。接下來就來看一下Linux下系統呼叫具體的實現過程。
Linux下系統呼叫的實現
前文已經提到了Linux下的系統呼叫是通過0x80實現的,但是我們知道作業系統會有多個系統呼叫(Linux下有319個系統呼叫),而對於同一個中斷號是如何處理多個不同的系統呼叫的?最簡單的方式是對於不同的系統呼叫採用不同的中斷號,但是中斷號明顯是一種稀缺資源,Linux顯然不會這麼做;還有一個問題就是系統呼叫是需要提供引數,並且具有返回值的,這些引數又是怎麼傳遞的?也就是說,對於系統呼叫我們要搞清楚兩點:
1. 系統呼叫的函式名稱轉換。
2. 系統呼叫的引數傳遞。
首先看第一個問題。實際上,Linux中每個系統呼叫都有相應的系統呼叫號作為唯一的標識,核心維護一張系統呼叫表,sys_call_table,表中的元素是系統呼叫函式的起始地址,而系統呼叫號就是系統呼叫在呼叫表的偏移量。在x86上,系統呼叫號是通過eax暫存器傳遞給核心的。比如fork()的實現:
在/usr/include/asm/unistd_32.h,可以通過find / -name unistd_32.h -print查詢)
[cpp] view plaincopyprint- #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
- mov eax, 2
- int 0x80
- mov eax, 2
- mov ebx, 1
- int 0x80
Linux中,在使用者態和核心態執行的程序使用的棧是不同的,分別叫做使用者棧和核心棧,兩者各自負責相應特權級別狀態下的函式呼叫。當進行系統呼叫時,程序不僅要從使用者態切換到核心態,同時也要完成棧切換,這樣處於核心態的系統呼叫才能在核心棧上完成呼叫。系統呼叫返回時,還要切換回使用者棧,繼續完成使用者態下的函式呼叫。
暫存器%esp(棧指標,指向棧頂)所在的記憶體空間叫做當前棧,比如%esp在使用者空間則當前棧就是使用者棧,否則是核心棧。棧切換主要就是%esp在使用者空間和核心空間間的來回賦值。在Linux中,每個程序都有一個私有的核心棧,當從使用者棧切換到核心棧時,需完成儲存%esp以及相關暫存器的值(%ebx,%ecx...)並將%esp設定成核心棧的相應值。而從核心棧切換會使用者棧時,需要恢復使用者棧的%esp及相關暫存器的值以及儲存核心棧的資訊。一個問題就是使用者棧的%esp和暫存器的值儲存到什麼地方,以便於恢復呢?答案就是核心棧,在呼叫int指令機型系統呼叫後會把使用者棧的%esp的值及相關暫存器壓入核心棧中,系統呼叫通過iret指令返回,在返回之前會從核心棧彈出使用者棧的%esp和暫存器的狀態,然後進行恢復。
相信大家一定聽過說,系統呼叫很耗時,要儘量少用。通過上面描述系統呼叫的實現原理,大家也應該知道這其中的原因了。第一,系統呼叫通過中斷實現,需要完成棧切換。第二,使用暫存器傳參,這需要額外的儲存和恢復的過程。
上面關於系統呼叫的闡述,如有錯誤歡迎指正。。