【轉】深入理解Linux的系統呼叫
一、 什麼是系統呼叫
在Linux的世界裡,我們經常會遇到系統呼叫這一術語,所謂系統呼叫,就是核心提供的、功能十分強大的一系列的函式。這些系統呼叫是在核心中實現的,再通過一定的方式把系統呼叫給使用者,一般都通過門(gate)陷入(trap)實現。系統呼叫是使用者程式和核心互動的介面。
二、 系統呼叫的作用
系統呼叫在Linux系統中發揮著巨大的作用,如果沒有系統呼叫,那麼應用程式就失去了核心的支援。
我們在程式設計時用到的很多函式,如fork、open等這些函式最終都是在系統呼叫裡實現的,比如說我們有這樣一個程式:
這裡我們用到了兩個函式,即fork和exit,這兩函式都是glibc中的函式,但是如果我們跟蹤函式的執行過程,看看glibc對fork和exit函式的實現就可以發現在glibc的實現程式碼裡都是採用軟中斷的方式陷入到核心中再通過系統呼叫實現函式的功能的。具體過程我們在系統呼叫的實現過程會詳細的講到。
由此可見,系統呼叫是使用者介面在核心中的實現,如果沒有系統呼叫,使用者就不能利用核心。
三、 系統呼叫的現實及呼叫過程
詳細講述系統呼叫的之前也講一下Linux系統的一些保護機制。
Linux系統在CPU的保護模式下提供了四個特權級別,目前核心都只用到了其中的兩個特權級別,分別為“特權級0”和“特權級3”,級別0也就是我們通常所講的核心模式,級別3也就是我們通常所講的使用者模式。劃分這兩個級別主要是對系統提供保護。核心模式可以執行一些特權指令和進入使用者模式,而使用者模式則不能。
這裡特別提出的是,核心模式與使用者模式分別使用自己的堆疊,當發生模式切換的時候同時要進行堆疊的切換。
每個程序都有自己的地址空間(也稱為程序空間),程序的地址空間也分為兩部分:使用者空間和系統空間,在使用者模式下只能訪問程序的使用者空間,在核心模式下則可以訪問程序的全部地址空間,這個地址空間裡的地址是一個邏輯地址,通過系統段面式的管理機制,訪問的實際記憶體要做二級地址轉換,即:邏輯地址?線性地址?實體地址。
系統呼叫對於核心來說就相當於函式,我們是關鍵問題是從使用者模式到核心模式的轉換、堆疊的切換以及引數的傳遞。
下面將結合核心原始碼對這些過程進行分析,以下分析環境為FC2,kernel 2.6.5
下面是核心原始碼裡arch/i386/kernel/entry.S的一段程式碼。
以上這段程式碼裡定義了兩個非常重要的巨集,即SAVE_ALL和RESTORE_ALL
SAVE_ALL先儲存使用者模式的暫存器和堆疊資訊,然後切換到核心模式,巨集__SWITCH_KERNELSPACE實現地址空間的轉換RESTORE_ALL的過程過SAVE_ALL的過程正好相反。
在核心原始碼裡有一個系統呼叫表:(entry.S的檔案裡)
在2.6.5的核心裡,有280多個系統呼叫,這些系統呼叫的名稱全部在這個系統呼叫表裡。
在這個原檔案裡,還有非常重要的一段。
這一段完成系統呼叫的執行。
system_call函式根據使用者傳來的系統呼叫號,在系統呼叫表裡找到對應的系統呼叫再執行。
從glibc的函式到系統呼叫還有一個很重要的環節就是系統呼叫號。
系統呼叫號的定義在include/asm-i386/unistd.h裡
每一個系統呼叫號都對應有一個系統呼叫
接下來就是系統呼叫巨集的展開
沒有引數的系統呼叫的巨集展開
!!!程式碼6::
帶一個引數的系統呼叫的巨集展開
!!!程式碼7::
兩個引數
程式碼8::
#define _syscall2(type,name,type1,arg1,type2,arg2)
三個引數的
程式碼9::
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
四個引數的
程式碼10::
#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)
五個引數的
程式碼11::
#define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,
type5,arg5)
六個引數的
程式碼12::
#define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,
type5,arg5,type6,arg6)
_res);
從這段程式碼我們可以看出int $0x80通過軟中斷開觸發系統呼叫,當發生呼叫時,函式中的name會被系統系統呼叫名所代替。然後呼叫前面所講的system_call。這個過程裡包含了系統呼叫的初始化,系統呼叫的初始化原始碼在:
arch/i386/kernel/traps.c中每當使用者執行int 0x80時,系統進行中斷處理,把控制權交給核心的system_call。
整個系統呼叫的過程可以總結如下:
1. 執行使用者程式(如:fork)
2. 根據glibc中的函式實現,取得系統呼叫號並執行int $0x80產生中斷。
3. 進行地址空間的轉換和堆疊的切換,執行SAVE_ALL。(進行核心模式)
4. 進行中斷處理,根據系統呼叫表呼叫核心函式。
5. 執行核心函式。
6. 執行RESTORE_ALL並返回使用者模式
解了系統呼叫的實現及呼叫過程,我們可以根據自己的需要來對核心的系統呼叫作修改或新增。