一、 什么是系統調用
在Linux的世界里,我們經(jīng)常會(huì )遇到系統調用這一術(shù)語(yǔ),所謂系統調用,就是內核提供的、功能十分強大的一系列的函數。這些系統調用是在內核中實(shí)現的,再通過(guò)一定的方式把系統調用給用戶(hù),一般都通過(guò)門(mén)(gate)陷入(trap)實(shí)現。系統調用是用戶(hù)程序和內核交互的接口。
二、 系統調用的作用
系統調用在Linux系統中發(fā)揮著(zhù)巨大的作用,如果沒(méi)有系統調用,那么應用程序就失去了內核的支持。
我們在編程時(shí)用到的很多函數,如fork、open等這些函數最終都是在系統調用里實(shí)現的,比如說(shuō)我們有這樣一個(gè)程序:
這里我們用到了兩個(gè)函數,即fork和exit,這兩函數都是glibc中的函數,但是如果我們跟蹤函數的執行過(guò)程,看看glibc對fork和exit函數的實(shí)現就可以發(fā)現在glibc的實(shí)現代碼里都是采用軟中斷的方式陷入到內核中再通過(guò)系統調用實(shí)現函數的功能的。具體過(guò)程我們在系統調用的實(shí)現過(guò)程會(huì )詳細的講到。
由此可見(jiàn),系統調用是用戶(hù)接口在內核中的實(shí)現,如果沒(méi)有系統調用,用戶(hù)就不能利用內核。
三、 系統調用的現實(shí)及調用過(guò)程
詳細講述系統調用的之前也講一下Linux系統的一些保護機制。
Linux系統在CPU的保護模式下提供了四個(gè)特權級別,目前內核都只用到了其中的兩個(gè)特權級別,分別為“特權級0”和“特權級3”,級別0也就是我們通常所講的內核模式,級別3也就是我們通常所講的用戶(hù)模式。劃分這兩個(gè)級別主要是對系統提供保護。內核模式可以執行一些特權指令和進(jìn)入用戶(hù)模式,而用戶(hù)模式則不能。
這里特別提出的是,內核模式與用戶(hù)模式分別使用自己的堆棧,當發(fā)生模式切換的時(shí)候同時(shí)要進(jìn)行堆棧的切換。
每個(gè)進(jìn)程都有自己的地址空間(也稱(chēng)為進(jìn)程空間),進(jìn)程的地址空間也分為兩部分:用戶(hù)空間和系統空間,在用戶(hù)模式下只能訪(fǎng)問(wèn)進(jìn)程的用戶(hù)空間,在內核模式下則可以訪(fǎng)問(wèn)進(jìn)程的全部地址空間,這個(gè)地址空間里的地址是一個(gè)邏輯地址,通過(guò)系統段面式的管理機制,訪(fǎng)問(wèn)的實(shí)際內存要做二級地址轉換,即:邏輯地址?線(xiàn)性地址?物理地址。
系統調用對于內核來(lái)說(shuō)就相當于函數,我們是關(guān)鍵問(wèn)題是從用戶(hù)模式到內核模式的轉換、堆棧的切換以及參數的傳遞。
下面將結合內核源代碼對這些過(guò)程進(jìn)行分析,以下分析環(huán)境為FC2,kernel 2.6.5
下面是內核源代碼里arch/i386/kernel/entry.S的一段代碼。
以上這段代碼里定義了兩個(gè)非常重要的宏,即SAVE_ALL和RESTORE_ALL
SAVE_ALL先保存用戶(hù)模式的寄存器和堆棧信息,然后切換到內核模式,宏__SWITCH_KERNELSPACE實(shí)現地址空間的轉換RESTORE_ALL的過(guò)程過(guò)SAVE_ALL的過(guò)程正好相反。
在內核原代碼里有一個(gè)系統調用表:(entry.S的文件里)
在2.6.5的內核里,有280多個(gè)系統調用,這些系統調用的名稱(chēng)全部在這個(gè)系統調用表里。
在這個(gè)原文件里,還有非常重要的一段。
這一段完成系統調用的執行。
system_call函數根據用戶(hù)傳來(lái)的系統調用號,在系統調用表里找到對應的系統調用再執行。
從glibc的函數到系統調用還有一個(gè)很重要的環(huán)節就是系統調用號。
系統調用號的定義在include/asm-i386/unistd.h里
每一個(gè)系統調用號都對應有一個(gè)系統調用
接下來(lái)就是系統調用宏的展開(kāi)
沒(méi)有參數的系統調用的宏展開(kāi)
?。。?!代碼6::
帶一個(gè)參數的系統調用的宏展開(kāi)
?。。?!代碼7::
兩個(gè)參數
代碼8::
#define _syscall2(type,name,type1,arg1,type2,arg2)
三個(gè)參數的
代碼9::
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
四個(gè)參數的
代碼10::
#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)
五個(gè)參數的
代碼11::
#define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,
type5,arg5)
六個(gè)參數的
代碼12::
#define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,
type5,arg5,type6,arg6)
_res);
從這段代碼我們可以看出int $0x80通過(guò)軟中斷開(kāi)觸發(fā)系統調用,當發(fā)生調用時(shí),函數中的name會(huì )被系統系統調用名所代替。然后調用前面所講的system_call。這個(gè)過(guò)程里包含了系統調用的初始化,系統調用的初始化原代碼在:
arch/i386/kernel/traps.c中每當用戶(hù)執行int 0x80時(shí),系統進(jìn)行中斷處理,把控制權交給內核的system_call。
整個(gè)系統調用的過(guò)程可以總結如下:
1. 執行用戶(hù)程序(如:fork)
2. 根據glibc中的函數實(shí)現,取得系統調用號并執行int $0x80產(chǎn)生中斷。
3. 進(jìn)行地址空間的轉換和堆棧的切換,執行SAVE_ALL。(進(jìn)行內核模式)
4. 進(jìn)行中斷處理,根據系統調用表調用內核函數。
5. 執行內核函數。
6. 執行RESTORE_ALL并返回用戶(hù)模式
解了系統調用的實(shí)現及調用過(guò)程,我們可以根據自己的需要來(lái)對內核的系統調用作修改或添加。