日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Linux系統(tǒng)調(diào)用

 一葉扁舟 2006-12-23

Linux系統(tǒng)調(diào)用

摘要本期重點和大家討論系統(tǒng)調(diào)用機制。其中涉及到了一些及系統(tǒng)調(diào)用的性能、上下文深層問題,同時也穿插著講述了一些內(nèi)核調(diào)試方法。并且最后試驗部分我們利用系統(tǒng)調(diào)用與相關(guān)內(nèi)核服務(wù)完成了一個搜集系統(tǒng)調(diào)用序列的特定任務(wù),該試驗具有較強的實用和教學(xué)價值。

 

什么是系統(tǒng)調(diào)用

   顧名思意,系統(tǒng)調(diào)用說的是操作系統(tǒng)提供給用戶程序調(diào)用的一組“特殊”接口。用戶程序可以通過這組“特殊”接口來獲得操作系統(tǒng)內(nèi)核提供的服務(wù),比如用戶可以通過文件系統(tǒng)相關(guān)的調(diào)用請求系統(tǒng)打開文件、關(guān)閉文件或讀寫文件,可以通過時鐘相關(guān)的系統(tǒng)調(diào)用獲得系統(tǒng)時間或設(shè)置系統(tǒng)時間等。

從邏輯上來說,系統(tǒng)調(diào)用可被看成是一個內(nèi)核與用戶空間程序交互的接口——它好比一個中間人,把用戶進程的請求傳達給內(nèi)核,待內(nèi)核把請求處理完畢后再將處理結(jié)果送回給用戶空間。

系統(tǒng)服務(wù)之所以需要通過系統(tǒng)調(diào)用提供給用戶空間的根本原因是為了對系統(tǒng)“保護”,因為我們知道Linux的運行空間分為內(nèi)核空間與用戶空間,它們各自運行在不同的級別中,邏輯上相互隔離。所以用戶進程在通常情況下不允許訪問內(nèi)核數(shù)據(jù),也無法使用內(nèi)核函數(shù),它們只能在用戶空間操作用戶數(shù)據(jù),調(diào)用戶用空間函數(shù)。比如我們熟悉的“hello world”程序(執(zhí)行時)就是標(biāo)準(zhǔn)的戶空間進程,它使用的打印函數(shù)printf就屬于用戶空間函數(shù),打印的字符“hello word”字符串也屬于用戶空間數(shù)據(jù)。

但是很多情況下,用戶進程需要獲得系統(tǒng)服務(wù)(調(diào)用系統(tǒng)程序),這時就必須利用系統(tǒng)提供給用戶的“特殊”接口——系統(tǒng)調(diào)用了,它的特殊性主要在于規(guī)定了用戶進程進入內(nèi)核的具體位置;換句話說用戶訪問內(nèi)核的路徑是事先規(guī)定好的,只能從規(guī)定位置進入內(nèi)核,而不準(zhǔn)許肆意跳入內(nèi)核。有了這樣的陷入內(nèi)核的統(tǒng)一訪問路徑限制才能保證內(nèi)核安全無虞。我們可以形象地描述這種機制:作為一個游客,你可以買票要求進入野生動物園,但你必須老老實實的坐在觀光車上,按照規(guī)定的路線觀光游覽。當(dāng)然,不準(zhǔn)下車,因為那樣太危險,不是讓你丟掉小命,就是讓你嚇壞了野生動物。

 

Linux的系統(tǒng)調(diào)用

     對于現(xiàn)代操作系統(tǒng),系統(tǒng)調(diào)用是一種內(nèi)核與用戶空間通訊的普遍手段,Linux系統(tǒng)也不例外。但是Linux系統(tǒng)的系統(tǒng)調(diào)用相比很多Unixwindows等系統(tǒng)具有一些獨特之處,無處不體現(xiàn)出Linux的設(shè)計精髓——簡潔和高效。

     Linux系統(tǒng)調(diào)用很多地方繼承了Unix的系統(tǒng)調(diào)用(但不是全部),但Linux相比傳統(tǒng)Unix的系統(tǒng)調(diào)用做了很多揚棄,它省去了許多Unix系統(tǒng)冗余的系統(tǒng)調(diào)用,僅僅保留了最基本和最有用的系統(tǒng)調(diào)用,所以Linux全部系統(tǒng)調(diào)用只有250個左右(而有些操作系統(tǒng)系統(tǒng)調(diào)用多達1000個以上)。

這些系統(tǒng)調(diào)用按照功能邏輯大致可分為“進程控制”、“文件系統(tǒng)控制”、“系統(tǒng)控制”、“存管管理”、“網(wǎng)絡(luò)管理”、“socket控制”、“用戶管理”、“進程間通信”幾類,詳細情況可參閱文章系統(tǒng)調(diào)用列表

如果你想詳細看看系統(tǒng)調(diào)用的說明,可以使用man 2 syscalls 命令查看,或干脆到 <內(nèi)核源碼目錄>/include/asm-i386/unistd.h源文件種找到它們的原本。

熟練了解和掌握上面這些系統(tǒng)調(diào)用是對系統(tǒng)程序員的必備要求,但對于一個開發(fā)內(nèi)核者或內(nèi)核開發(fā)者來[1]說死記硬背下這些調(diào)用還遠遠不夠。如果你僅僅知道存在的調(diào)用而不知道為什么它們會存在,或只知道如何使用調(diào)用而不知道這些調(diào)用在系統(tǒng)中的主要用途,那么你離駕馭系統(tǒng)還有不小距離。

要彌補這個鴻溝,第一,你必須明白系統(tǒng)調(diào)用在內(nèi)核里的主要用途。雖然上面給出了數(shù)種分類,不過總的概括來講系統(tǒng)調(diào)用主要在系統(tǒng)中的用途無非以下幾類:

l        控制硬件——系統(tǒng)調(diào)用往往作為硬件資源和用戶空間的抽象接口,比如讀寫文件時用到的write/read調(diào)用。

l        設(shè)置系統(tǒng)狀態(tài)或讀取內(nèi)核數(shù)據(jù)——因為系統(tǒng)調(diào)用是用戶空間和內(nèi)核的唯一通訊手段[2],所以用戶設(shè)置系統(tǒng)狀態(tài),比如開/關(guān)某項內(nèi)核服務(wù)(設(shè)置某個內(nèi)核變量),或讀取內(nèi)核數(shù)據(jù)都必須通過系統(tǒng)調(diào)用。比如getpgid、getprioritysetpriority、sethostname

l        進程管理——一系列調(diào)用接口是用來保證系統(tǒng)中進程能以多任務(wù),在虛擬內(nèi)存環(huán)境下得以運行。比如 fork、clone、execve、exit

第二,什么服務(wù)應(yīng)該存在于內(nèi)核;或者說什么功能應(yīng)該實現(xiàn)在內(nèi)核而不是在用戶空間。這個問題并不沒有明確的答案,有些服務(wù)你可以選擇在內(nèi)核完成,也可以在用戶空間完成。選擇在內(nèi)核完成通?;谝韵驴紤]:

l        服務(wù)必須獲得內(nèi)核數(shù)據(jù),比如一些服務(wù)必須獲得中斷或系統(tǒng)時間等內(nèi)核數(shù)據(jù)。

l        從安全角度考慮,在內(nèi)核中提供的服務(wù)相比用戶空間提供的毫無疑問更安全,很難被非法訪問到。

l        從效率考慮,在內(nèi)核實現(xiàn)服務(wù)避免了和用戶空間來回傳遞數(shù)據(jù)以及保護現(xiàn)場等步驟,因此效率往往要比實現(xiàn)在用戶空間高許多。比如,httpd等服務(wù)。

l        如果內(nèi)核和用戶空間都需要使用該服務(wù),那么最好實現(xiàn)在內(nèi)核空間,比如隨機數(shù)產(chǎn)生。

   理解上述道理對掌握系統(tǒng)調(diào)用本質(zhì)意義很大,希望網(wǎng)友們能從使用中多總結(jié),多思考。

 

系統(tǒng)調(diào)用、用戶編程接口(API)、系統(tǒng)命令、和內(nèi)核函數(shù)的關(guān)系

系統(tǒng)調(diào)用并非直接和程序員或系統(tǒng)管理員打交道,它僅僅是一個通過軟中斷機制(我們后面講述)向內(nèi)核提交請求,獲取內(nèi)核服務(wù)的接口。而在實際使用中程序員調(diào)用的多是用戶編程接口——API,而管理員使用的則多是系統(tǒng)命令。

用戶編程接口其實是一個函數(shù)定義,說明了如何獲得一個給定的服務(wù),比如read()、malloc()free()、abs()等。它有可能和系統(tǒng)調(diào)用形式上一致,比如read()接口就和read系統(tǒng)調(diào)用對應(yīng),但這種對應(yīng)并非一一對應(yīng),往往會出現(xiàn)幾種不同的API內(nèi)部用到統(tǒng)一個系統(tǒng)調(diào)用,比如malloc()free()內(nèi)部利用brk( )系統(tǒng)調(diào)用來擴大或縮小進程的堆;或一個API利用了好幾個系統(tǒng)調(diào)用組合完成服務(wù)。更有些API甚至不需要任何系統(tǒng)調(diào)用——因為它不必需要內(nèi)核服務(wù),如計算整數(shù)絕對值的abs()接口。

另外要補充的是Linux的用戶編程接口遵循了Unix世界中最流行的應(yīng)用編程界面標(biāo)準(zhǔn)——POSIX標(biāo)準(zhǔn),這套標(biāo)準(zhǔn)定義了一系列API。在Linux中(Unix也如此)這些API主要是通過C庫(libc)實現(xiàn)的,它除了定義的一些標(biāo)準(zhǔn)的C函數(shù)外,一個很重要的任務(wù)就是提供了一套封裝例程wrapper routine)將系統(tǒng)調(diào)用在用戶空間包裝后供用戶編程使用。

不過封裝并非必須的,如果你愿意直接調(diào)用,Linux內(nèi)核也提供了一個syscall()函數(shù)來實現(xiàn)調(diào)用,我們看個例子來對比一下通過C庫調(diào)用和直接調(diào)用的區(qū)別。

 

#include <syscall.h>

#include <unistd.h>

#include <stdio.h>

#include <sys/types.h>

int main(void) {

long ID1, ID2;

/*-----------------------------*/

/* 直接系統(tǒng)調(diào)用*/

/* SYS_getpid (func no. is 20) */

/*-----------------------------*/

ID1 = syscall(SYS_getpid);

printf ("syscall(SYS_getpid)=%ld\n", ID1);

/*-----------------------------*/

/* 使用"libc"封裝的系統(tǒng)調(diào)用 */

/* SYS_getpid (Func No. is 20) */

/*-----------------------------*/

ID2 = getpid();

printf ("getpid()=%ld\n", ID2);

return(0);

}

 

系統(tǒng)命令相對編程接口更高了一層,它是內(nèi)部引用API的可執(zhí)行程序,比如我們常用的系統(tǒng)命令lshostname等。Linux的系統(tǒng)命令格式遵循系統(tǒng)V的傳統(tǒng),多數(shù)放在/bin/sbin下(相關(guān)內(nèi)容可看看shell等章節(jié))。

有興趣的話可以通過strace lsstrace hostname 命令查看一下它們用到的系統(tǒng)調(diào)用,你會發(fā)現(xiàn)諸如open、brk、fstat、ioctl 等系統(tǒng)調(diào)用被用在系統(tǒng)命令中。

下一個需要解釋一下的問題是內(nèi)核函數(shù)和系統(tǒng)調(diào)用的關(guān)系,內(nèi)核函數(shù)大家不要想像的過于復(fù)雜,其實它們和普通函數(shù)很像,只不過在內(nèi)核實現(xiàn),因此要滿足一些內(nèi)核編程的要求[3]。系統(tǒng)調(diào)用是一層用戶進入內(nèi)核的接口,它本身并非內(nèi)核函數(shù),進入內(nèi)核后,不同的系統(tǒng)調(diào)用會找到對應(yīng)到各自的內(nèi)核函數(shù)——換個專業(yè)說法就叫:系統(tǒng)調(diào)用服務(wù)服務(wù)例程。實際對請求服務(wù)的是內(nèi)核函數(shù)而非調(diào)用接口。

比如系統(tǒng)調(diào)用 getpid實際就是調(diào)用內(nèi)核函數(shù)sys_getpid。

asmlinkage long sys_getpid(void)

{

       return current->tpid;

}

Linux系統(tǒng)種存在許多的內(nèi)核函數(shù),有些是內(nèi)核文件種自己使用的,有些則是可以export出來供內(nèi)核其他部分共同使用的,具體情況自己決定。

內(nèi)核公開的內(nèi)核函數(shù)——export出來的——可以使用命令ksyms cat /proc/ksyms來查看。另外網(wǎng)上還有一本歸納分類內(nèi)核函數(shù)的書叫作《The Linux Kernel API Book》,有興趣的讀者可以去看看。

    總而言之,從用戶角度向內(nèi)核看,依次是系統(tǒng)命令、編程接口、系統(tǒng)調(diào)用和內(nèi)核函數(shù)。再講述了系統(tǒng)調(diào)用實現(xiàn)后,我們會回過頭來看看整個執(zhí)行路徑。

系統(tǒng)調(diào)用實現(xiàn)

Linux中實現(xiàn)系統(tǒng)調(diào)用利用了0x86體系結(jié)構(gòu)中的軟件中斷[4]。軟件中斷和我們常說的中斷(硬件中斷)不同之處在于——它是通過軟件指令觸發(fā)而并非外設(shè),也就是說又編程人員出發(fā)的一種異常,具體的講就是調(diào)用int $0x80匯編指令,這條匯編指令將產(chǎn)生向量為128的編程異常。

之所以系統(tǒng)調(diào)用需要借助異常實現(xiàn),是因為當(dāng)用戶態(tài)的進程調(diào)用一個系統(tǒng)調(diào)用時,CPU便被切換到內(nèi)核態(tài)執(zhí)行內(nèi)核函數(shù)[5],而我們在i386體系結(jié)構(gòu)部分已經(jīng)講述過了進入內(nèi)核——進入高特權(quán)級別——必須經(jīng)過系統(tǒng)的門機制,這里異常實際上就是通過系統(tǒng)門陷入內(nèi)核(除了int 0x80外用戶空間還可以通過int3——向量3、into——向量4 bound——向量5等異常指令進入內(nèi)核,而其他異常用戶空間程序無法利用,都是由系統(tǒng)使用的)。

我們更詳細的解釋一下這個過程。int $0x80指令目的是產(chǎn)生一個編號為128的編程異常,這個編程異常對應(yīng)的中斷描述符表IDT中的第128項——也就是對應(yīng)的系統(tǒng)門描述符。門描述符中含有一個預(yù)設(shè)的內(nèi)核空間地址,它指向了系統(tǒng)調(diào)用處理程序:system_call()(別和系統(tǒng)調(diào)用服務(wù)程序混淆,這個程序在entry.S文件中用匯編語言編寫)。

很顯然所有的系統(tǒng)調(diào)用都會統(tǒng)一的轉(zhuǎn)到這個地址,但Linux一共有2、3百個系統(tǒng)調(diào)用都從這里進入內(nèi)核后又該如何派發(fā)它們到各自的服務(wù)程序去呢?別發(fā)昏,解決這個問題的方法非常簡單:首先Linux為每個系統(tǒng)調(diào)用都進行了編號(0NR_syscall,同時在內(nèi)核中保存了一張系統(tǒng)調(diào)用表,該表中保存了系統(tǒng)調(diào)用編號和其對應(yīng)的服務(wù)例程,因此在系統(tǒng)調(diào)入通過系統(tǒng)門陷入內(nèi)核前,需要把系統(tǒng)調(diào)用號一并傳入內(nèi)核,x86上,這個傳遞動作是通過在執(zhí)行int0x80前把調(diào)用號裝入eax寄存器實現(xiàn)的。這樣系統(tǒng)調(diào)用處理程序一旦運行,就可以從eax中得到數(shù)據(jù),然后再去系統(tǒng)調(diào)用表中尋找相應(yīng)服務(wù)例程了。

除了需要傳遞系統(tǒng)調(diào)用號以外,許多系統(tǒng)調(diào)用還需要傳遞一些參數(shù)到內(nèi)核,比如sys_write(unsigned int fd, const char * buf, size_t count)調(diào)用就需要傳遞文件描述符號fd和要寫入的內(nèi)容buf和寫入字節(jié)數(shù)count等幾個內(nèi)容到內(nèi)核。碰到這種情況,Linux會有6個寄存器使用來傳遞這些參數(shù):eax (存放系統(tǒng)調(diào)用號)、 ebxecxedx、esiedi來存放這些額外的參數(shù)字母遞增的順序。具體做法是在system_call( )中使用SAVE_ALL宏把這些寄存器的值保存在內(nèi)核態(tài)堆棧中。

 

 

有始便有終,當(dāng)服務(wù)例程結(jié)束時,system_call( ) eax獲得系統(tǒng)調(diào)用的返回值,并把這個返回值存放在曾保存用戶態(tài) eax寄存器棧單元的那個位置上。然后跳轉(zhuǎn)到ret_from_sys_call( ),終止系統(tǒng)調(diào)用處理程序的執(zhí)行。

當(dāng)進程恢復(fù)它在用戶態(tài)的執(zhí)行前,RESTORE_ALL宏會恢復(fù)用戶進入內(nèi)核前被保留到堆棧中的寄存器值。其中eax返回時會帶回系統(tǒng)調(diào)用的返回碼。(負數(shù)說明調(diào)用錯誤,0或正數(shù)說明正常完成)

 

我們可以通過分析一下getpid系統(tǒng)調(diào)用的真是過程來將上述概念具體化,分析getpid系統(tǒng)調(diào)用一個辦法是查看entry.s中的代碼細節(jié),逐步跟蹤源碼來分析運行過程,另外就是可借助一些內(nèi)核調(diào)試工具,動態(tài)跟蹤運行路徑。

假設(shè)我們的程序源文件名為getpid.c,內(nèi)容是:

#include <syscall.h>

#include <unistd.h>

#include <stdio.h>

#include <sys/types.h>

int main(void) {

long ID;

ID = getpid();

printf ("getpid()=%ld\n", ID);

return(0);

}

將其編譯成名為getpid的執(zhí)行文件”gcc –o getpid <路徑>/getpid.c”, 我們使用KDB來產(chǎn)看它進入內(nèi)核后的執(zhí)行路徑。

l         激活KDB (按下pause鍵,當(dāng)然你必須已經(jīng)給內(nèi)核打了KDB補丁);設(shè)置內(nèi)核斷點 “bp sys_getpid” ;退出kdb “go”;然后執(zhí)行./getpid 。瞬間,進入內(nèi)核調(diào)試狀態(tài),執(zhí)行路徑停止在斷點sys_getpid處。

l         KDB>提示符下,執(zhí)行bt命令觀察堆棧,發(fā)現(xiàn)調(diào)用的嵌套路徑,可以看到在sys_getpid是在內(nèi)核函數(shù)system_call中被嵌套調(diào)用的。

l         KDB>提示符下,執(zhí)行rd命令查看寄存器中的數(shù)值,可以看到eax中存放的getpid調(diào)用號——0x00000014(=20).

l         KDB>提示符下,執(zhí)行ssb(或ss)命令跟蹤內(nèi)核代碼執(zhí)行路徑,可以發(fā)現(xiàn)sys_getpid執(zhí)行后,會返回system_call函數(shù),然后接者轉(zhuǎn)入ret_from_sys_call例程。(再往后還有些和調(diào)度有關(guān)其他例程,我們這里不說了它們了。)

 

結(jié)合用戶空間的執(zhí)行路徑,大致該程序可歸結(jié)為一下幾個步驟:

1  該程序調(diào)用libc庫的封裝函數(shù)getpid。該封裝函數(shù)中將系統(tǒng)調(diào)用號_NR_getpid(第20個)壓入EAX寄存器,

2  調(diào)用軟中斷 int 0x80 進入內(nèi)核。

(以下進入內(nèi)核態(tài))

3  在內(nèi)核中首先執(zhí)行system_call,接著執(zhí)行根據(jù)系統(tǒng)調(diào)用號在調(diào)用表中查找到對應(yīng)的系統(tǒng)調(diào)用服務(wù)例程sys_getpid。

4.執(zhí)行sys_getpid服務(wù)例程。

5.執(zhí)行完畢后,轉(zhuǎn)入ret_from_sys_call例程,系統(tǒng)調(diào)用中返回。

 

   內(nèi)核調(diào)試是一個很有趣的話題,方法多種多樣,我個人認為比較好用的是UMLuser mode linux+gdb)和 KDB 這兩個工具。尤其KDB對于調(diào)試小規(guī)模內(nèi)核模塊或查看內(nèi)核運行路徑很有效,對于它的使用方法可以看看Linux 內(nèi)核調(diào)試器內(nèi)幕這片文章。

系統(tǒng)調(diào)用思考

    系統(tǒng)調(diào)用的內(nèi)在過程并不復(fù)雜,我們不再多說了,下面這節(jié)我們主要就系統(tǒng)調(diào)用所涉及的一些重要問題作一些討論和分析,希望這樣能更有助了解系統(tǒng)調(diào)用的精髓。

調(diào)用上下文分析

系統(tǒng)調(diào)用雖說是要進入內(nèi)核執(zhí)行,但它并非一個純粹意義上的內(nèi)核例程。首先它是代表用戶進程的,這點決定了雖然它會陷入內(nèi)核執(zhí)行,但是上下文仍然是處于進程上下文中,因此可以訪問進程的許多信息(比如current結(jié)構(gòu)——當(dāng)前進程的控制結(jié)構(gòu)),而且可以被其他進程搶占(在從系統(tǒng)調(diào)用返回時,由system_call函數(shù)判斷是否該再調(diào)度),可以休眠,還可接收信號[6]等等。

所有這些特點都涉及到了進程調(diào)度的問題,我們這里不做深究,只要大家明白系統(tǒng)調(diào)用完成后,再回到或者說把控制權(quán)交回到發(fā)起調(diào)用的用戶進程前,內(nèi)核會有一次調(diào)度。如果發(fā)現(xiàn)有優(yōu)先級別更高的進程或當(dāng)前進程的時間片用完,那么就會選擇高優(yōu)先級的進程或重新選擇進程運行。除了再調(diào)度需要考慮外,再就是內(nèi)核需要檢查是否有掛起的信號,如果發(fā)現(xiàn)當(dāng)前進程有掛起的信號,那么還需要先返回用戶空間處理信號處理例程(處于用戶空間),然后再回到內(nèi)核,重新返回用戶空間,有些麻煩但這個反復(fù)過程是必須的。

 

調(diào)用性能問題

系統(tǒng)調(diào)用需要從用戶空間陷入內(nèi)核空間,處理完后,又需要返回用戶空間。其中除了系統(tǒng)調(diào)用服務(wù)例程的實際耗時外,陷入/返回過程和系統(tǒng)調(diào)用處理程序(查系統(tǒng)調(diào)用表、存儲\恢復(fù)用戶現(xiàn)場)也需要花銷一些時間,這些時間加起來就是一個系統(tǒng)調(diào)用的響應(yīng)速度。系統(tǒng)調(diào)用不比別的用戶程序,它對性能要求很苛刻,因為它需要陷入內(nèi)核執(zhí)行,所以和其他內(nèi)核程序一樣要求代碼簡潔、執(zhí)行迅速。幸好Linux具有令人難以置信的上下文切換速度,使得其進出內(nèi)核都被優(yōu)化得簡潔高效;同時所有Linux系統(tǒng)調(diào)用處理程序和每個系統(tǒng)調(diào)用本身也都非常簡潔。

絕大多數(shù)情況下,Linux系統(tǒng)調(diào)用性能是可以接受的,但是對于一些對性能要求非常高的應(yīng)用來說,它們雖然希望利用系統(tǒng)調(diào)用的服務(wù),但卻希望加快相應(yīng)速度,避免陷入/返回和系統(tǒng)調(diào)用處理程序帶來的花銷,因此采用由內(nèi)核直接調(diào)用系統(tǒng)調(diào)用服務(wù)例程,最好的例子就HTTPD——它為了避免上述開銷,從內(nèi)核調(diào)用socket等系統(tǒng)調(diào)用服務(wù)例程。

 

什么時候添加系統(tǒng)調(diào)用

 系統(tǒng)調(diào)用是用戶空間和內(nèi)核空間交互的唯一手段,但是這并非時說要完成交互功能非要添加新系統(tǒng)調(diào)用不可。添加系統(tǒng)調(diào)用需要修改內(nèi)核源代碼、重新編譯內(nèi)核,因此如果想靈活的和內(nèi)核交互信息,最好使用一下幾種方法。

l        編寫字符驅(qū)動程序

利用字符驅(qū)動程序可以完成和內(nèi)核交互數(shù)據(jù)的功能。它最大的好處在于可以模塊式加載,這樣以來就避免了編譯內(nèi)核等手續(xù),而且調(diào)用接口固定,容易操作。

l        使用proc 文件系統(tǒng)

利用proc文件系統(tǒng)修訂系統(tǒng)狀態(tài)是一種很常見的手段,比如通過修改proc文件系統(tǒng)下的系統(tǒng)參數(shù)配置文件(/proc/sys),我們可以直接在運行時動態(tài)更改內(nèi)核參數(shù);再如,通過下面這條指令:echo 1 > /proc/sys/net/ip_v4/ip_forward開啟內(nèi)核中控制IP轉(zhuǎn)發(fā)的開關(guān)。類似的,還有許多內(nèi)核選項可以直接通過proc文件系統(tǒng)進行查詢和調(diào)整。

l        使用虛擬文件系統(tǒng)

有些內(nèi)核開發(fā)者認為利用ioctl()系統(tǒng)調(diào)用(字符設(shè)備驅(qū)動接口)往往會似的系統(tǒng)調(diào)用意義不明確,而且難控制。而將信息放入到proc文件系統(tǒng)中會使信息組織混亂,因此也不贊成過多使用。他們建議實現(xiàn)一種孤立的虛擬文件系統(tǒng)來代替ioctl()/proc,因為文件系統(tǒng)接口清楚,而且便于用戶空間訪問,同時利用虛擬文件系統(tǒng)使得利用腳本執(zhí)行系統(tǒng)管理任務(wù)更家方便、有效。

 

 

實驗部分

 

代碼功能介紹

我們希望收集Linux系統(tǒng)運行時系統(tǒng)調(diào)用被執(zhí)行的信息,既實時獲取系統(tǒng)調(diào)用日志。這些日志信息將能以可讀形式實時的返回給用戶空間,以便用戶觀察或做近一步的日志分析(如入侵檢測等)。

所以簡單的講實驗代碼集需要完成以下幾個基本功能:

第一:記錄系統(tǒng)調(diào)用日志,將其寫入緩沖區(qū)(內(nèi)核中),以便用戶讀??;

第二:建立新的系統(tǒng)調(diào)用,以便將內(nèi)核緩沖中的系統(tǒng)調(diào)用日志返回到用戶空間。

第三:循環(huán)利用系統(tǒng)調(diào)用,以便能動態(tài)實時返回系統(tǒng)調(diào)用日志。

 

代碼結(jié)構(gòu)體系介紹

基本函數(shù)

代碼功能一節(jié)介紹中的基本功能對應(yīng)程序代碼集中的三個子程序。它們分別是syscall_auydit、Sys_auditauditd。接下來我們介紹代碼具體結(jié)構(gòu)。

日志記錄例程Syscall_audit

syscall_audit該程序是一個內(nèi)核態(tài)的服務(wù)例程,該例程負責(zé)記錄系統(tǒng)調(diào)用的運行日志。

記錄系統(tǒng)調(diào)用日志的具體做法是在內(nèi)核中修改系統(tǒng)調(diào)用處理程序system_call[7],在其中需要監(jiān)控的每個調(diào)用(在我們例子鐘222個系統(tǒng)調(diào)用都監(jiān)控了,當(dāng)然你也可以根據(jù)自己需求有選擇的監(jiān)控)執(zhí)行完畢后都插入一個日志記錄指令,該指令會轉(zhuǎn)去調(diào)用內(nèi)核服務(wù)函數(shù)syscall_audit來記錄該次調(diào)用的信息[8]。

Syscall_audit內(nèi)核服務(wù)例程會建立了一個內(nèi)核緩沖區(qū)來存放被記錄的函數(shù)。當(dāng)搜集的數(shù)據(jù)量到達一定閥值時(比如設(shè)定為到達緩沖區(qū)總大小的%80,這樣作可避免在丟失新調(diào)用),喚醒系統(tǒng)調(diào)用進程取回數(shù)據(jù)。否則繼續(xù)搜集,這時系統(tǒng)調(diào)用程序會堵塞在一個等待隊列上,直到被喚醒,也就是說如果緩沖區(qū)還沒接近滿時,系統(tǒng)調(diào)用會等待(被掛起)它被填充。

系統(tǒng)調(diào)用Sys_audit

由于系統(tǒng)調(diào)用是在內(nèi)核中被執(zhí)行,因此記錄其執(zhí)行日志也應(yīng)該在內(nèi)核態(tài)收集,所以我們需要利用一個新的系統(tǒng)調(diào)用來完成將內(nèi)核信息帶回到用戶空間——sys_audit就是我們新填加的系統(tǒng)調(diào)用,它功能非常簡單,就是從緩沖區(qū)中取數(shù)據(jù)返回用戶空間。

為了保證數(shù)據(jù)連續(xù)性,防止丟失。我們會建立一個內(nèi)核緩沖區(qū)存放每刻搜集到的日志數(shù)據(jù),并且當(dāng)搜集的數(shù)據(jù)量到達一定閥值時(比如設(shè)定為到達緩沖區(qū)總大小的%80),系統(tǒng)調(diào)用進程就會被喚醒[9],以取回數(shù)據(jù)。否則在日志搜集時,系統(tǒng)調(diào)用程序會堵塞在等待隊列上,直到被喚醒,也就是說如果緩沖區(qū)還沒接近滿時,系統(tǒng)調(diào)用會等待它被填充。

用戶空間服務(wù)程序auditd

不用多說,我們需要一個用戶空間服務(wù)進程來不斷的調(diào)用audit系統(tǒng)調(diào)用,取回系統(tǒng)中搜集到的的調(diào)用日志信息。要知道,長時間的調(diào)用日志序列對于分析入侵或系統(tǒng)行為等才有價值。

 

把代碼集成到內(nèi)核中

除了上面介紹的內(nèi)容外,我們還需要一些輔助性,但卻很必要的工作,這些工作將幫助我們將上述代碼靈活地機結(jié)成一體,完成需要的功能。

n        其一是修改entry.S匯編代碼,該代碼中含有系統(tǒng)調(diào)用表和系統(tǒng)調(diào)用入口代碼system_call。我們首先需要在系統(tǒng)調(diào)用表中加入新的系統(tǒng)調(diào)用(名為sys_audit,223號。.long SYMBOL_NAME(sys_audit));下來在系統(tǒng)調(diào)用入口中加入跳轉(zhuǎn)到日志記錄服務(wù)例程中(跳轉(zhuǎn) “je auditsys, 而auditsys代碼段會真正調(diào)用系統(tǒng)調(diào)用記錄例程syscall_audit);

n        其二是填加代碼文件audit.c,該文件中包含syscall_audit與系統(tǒng)調(diào)用sys_audit兩個函數(shù)體,我們這里只說包含函數(shù)體,而并非函數(shù),是因為這里我們并不想把函數(shù)的實現(xiàn)在內(nèi)核中寫死,而是希望利用了函數(shù)指針,即做了兩個鉤子函數(shù),來完成把具體函數(shù)實現(xiàn)放在模塊中完成,以便能動態(tài)加載,方便調(diào)試(請見下一節(jié)介紹)。

u      其三是修改i386_ksyms.c文件,再最后加入

extern void (*my_audit)(int,int);

EXPORT_SYMBOL(my_audit);

extern int(*my_sysaudit)(unsigned char,unsigned char*,unsigned short,unsigned char);

EXPORT_SYMBOL(my_sysaudit);

這樣做是為了導(dǎo)出內(nèi)核符號表,以便能模塊代碼中能掛接上以上函數(shù)指針。

n        其四是修改內(nèi)核原代碼目錄下/kernel自目錄下的Makefile文件,很簡單只需要在obj-y    := 。。。。。最后加上audit.o,告訴編譯內(nèi)核是把audit.o編進去。

 

關(guān)鍵代碼解釋   

     我們的日志收集例程與取日志系統(tǒng)調(diào)用這兩個關(guān)鍵函數(shù)的實現(xiàn)是放在內(nèi)核模塊中實現(xiàn)。其中有些需要解釋的地方:

1.        模塊編程的必要原則,如初始化、注銷等都應(yīng)該實現(xiàn),所不同的是我們在初始化與注銷時會分別掛上或卸下[10]了兩個鉤子函數(shù)的實現(xiàn)。

2.      我們系統(tǒng)調(diào)用日志記錄采用了一個結(jié)構(gòu)體:syscall_buf,它含有諸如系統(tǒng)調(diào)用號——syscall、進程ID——pid、調(diào)用程序名——comm[COMM_SIZE]等字段,共52字節(jié);我們的內(nèi)核緩沖區(qū)為audit_buf,它是一個可容納100syscall_buf的數(shù)組。

3.      系統(tǒng)調(diào)用實現(xiàn)極簡單,要做的僅僅是利用__copy_to_user[11]將內(nèi)核緩沖中的日志數(shù)據(jù)取到用戶空間。為了提高效率在緩沖區(qū)未滿時未到%80的閥值時),系統(tǒng)調(diào)用會掛起等待wait_event_interruptible(buffer_wait, current_pos >= AUDIT_BUF_SIZE*8/10);相應(yīng)地當(dāng)緩沖區(qū)收集快滿時,則喚醒系統(tǒng)調(diào)用繼續(xù)收集日志wake_up_interruptible(&buffer_wait)。

4.      最后要補充說明一下,在auditd用戶服務(wù)程序中調(diào)用我們新加的系統(tǒng)調(diào)用前必須利用宏_syscall4(int, audit, u8, type, u8 *, buf, u16, len, u8, reset)來“聲明”該調(diào)用——展開成audit函數(shù)原形,以便進行格式轉(zhuǎn)換和參數(shù)傳遞,否則系統(tǒng)不能識別。

 

STEP BY STEP

下面具體講述一下如何添加這個調(diào)用。

1 修改entry.S ——在其中的添加audit調(diào)用,并且在system_call中加入搜集例程。(該函數(shù)位于<內(nèi)核源代碼>/arch/i386/kernel/下)

2 添加audit.c文件到<內(nèi)核源代碼>/arch/i386/kernel/下——該文件中定義了

sys_auditsyscall_audit 兩個函數(shù)需要的鉤子函數(shù)(my_auditmy_sysaudit),它們會在entry.S中被使用。

3 修改<內(nèi)核源代碼>/arch/i386/kernel/i386-kysms.c文件,在其中導(dǎo)出my_auditmy_sysaudit兩個鉤子函數(shù)。因為只有在內(nèi)核符號表里導(dǎo)出,才可被其他內(nèi)核函數(shù)使用,也就是說才能在模塊中被掛上。

4 修改<內(nèi)核源代碼>/arch/i386/kernel/Makefile文件,將audit.c編譯入內(nèi)核。

到這可以重新編譯內(nèi)核了,新內(nèi)核已經(jīng)加入了檢測點了。下一步是編寫模塊來實現(xiàn)系統(tǒng)調(diào)用與內(nèi)核搜集服務(wù)例程的功能了。

1 編寫名為audit的模塊,其中除了加載、卸載模塊函數(shù)以外主要實現(xiàn)了mod_sys_auditmod_syscall_audit兩個函數(shù)。它們會分別掛載到my_sysauditmy_audit兩個鉤子上。

2 編譯后將模塊加載 insmod audit.o。(你可通過dmesg查看是加載信息)

3 修改/usr/include/asm/unistd.h ——在其中加入audit的系統(tǒng)調(diào)用號。這樣用戶空間才可找到audit系統(tǒng)調(diào)用了。

4 最后,我們寫一個用戶deamon程序,來循環(huán)調(diào)用audit系統(tǒng)調(diào)用,并把搜集到的信息打印到屏幕上。

完了。系統(tǒng)調(diào)用還有許多細節(jié),請大家查看有關(guān)書記吧。不羅索了。再見。

 

相關(guān)代碼請下載 auditexample.tar (實現(xiàn)于2.4.18內(nèi)核)。

 

感謝SAL的開發(fā)者,例子程序基本框架來自于它們的靈感。

 



[1]我們說的開發(fā)內(nèi)核者指開發(fā)系統(tǒng)內(nèi)核,比如開發(fā)驅(qū)動模塊機制、開發(fā)系統(tǒng)調(diào)用機制;而內(nèi)核開發(fā)者則是指在內(nèi)核基礎(chǔ)之上進行的開發(fā),比如驅(qū)動開發(fā)、系統(tǒng)調(diào)用開發(fā)、文件系統(tǒng)開發(fā)、網(wǎng)絡(luò)通訊協(xié)議開發(fā)等。我們雜志所關(guān)注的問題主要在內(nèi)核開發(fā)層次,即利用內(nèi)核提供的機制進行開發(fā)。

 

[2]Linux而言,系統(tǒng)調(diào)用是用戶程序訪問內(nèi)核的唯一手段,無論是/proc方式或設(shè)備文件方式歸根到底都是利用系統(tǒng)調(diào)用完成的。

[3]內(nèi)核編程相比用戶程序編程有一些特點,簡單的講內(nèi)核程序一般不能引用C庫函數(shù)(除非你自己實現(xiàn)了,比如內(nèi)核實現(xiàn)了不少C庫種的String操作函數(shù));缺少內(nèi)存保護措施;堆棧有限(因此調(diào)用嵌套不能過多);而且由于調(diào)度關(guān)系,必須考慮內(nèi)核執(zhí)行路徑的連續(xù)性,不能有長睡眠等行為。

[4]軟件中斷雖然叫中斷,但實際上屬于異常(更準(zhǔn)確說是陷阱)——CPU發(fā)出的中斷——而且是由編程者觸發(fā)的一種特殊異常。

[5]系統(tǒng)調(diào)用過程可被理解成——由內(nèi)核在核心態(tài)代表應(yīng)用程序執(zhí)行任務(wù)。

[6]除了進程上下文外,Linux系統(tǒng)中還有另一種上下文——它被成為中斷上下文。中斷上下文不同于進程上下文,它代表中斷執(zhí)行,所以和進程是異步進行而且可以說毫不相干的。這種上下文中的程序,要避免睡眠因為無法被搶占。

[7]System_call是個通用的系統(tǒng)調(diào)用服務(wù)程序,或說系統(tǒng)調(diào)用入口程序,因為任何一個系統(tǒng)調(diào)用都要經(jīng)過system_call統(tǒng)一處理(查找系統(tǒng)調(diào)用表,跳轉(zhuǎn)到相應(yīng)調(diào)用的服務(wù)例程),所以任何一次系統(tǒng)調(diào)用的信息都可被syscall_audit記錄下來。

 

[8] 這里我們主要記錄諸如調(diào)用時刻、調(diào)用者PID、程序名等信息,這些信息可從xtimecurrent這些全局變量處取得。

[9] 這里需要利用等待隊列,具體聲明見DECLARE_WAIT_QUEUE_HEAD(buffer_wait)。

 

[10] 所謂掛上或卸下其實就是將函數(shù)指針指向模塊中實現(xiàn)的函數(shù)或指向空函數(shù),但要知道這些函數(shù)指針一定是要導(dǎo)出到內(nèi)核符號表中的,否則找不到。

[11] 這是一個系統(tǒng)提供的內(nèi)核函數(shù),目的就是從內(nèi)核向用戶空間傳遞數(shù)據(jù)。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多