国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
Linux系統(tǒng)調(diào)用

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

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

 

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

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

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

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

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

 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   理解上述道理對(duì)掌握系統(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)管理員打交道,它僅僅是一個(gè)通過(guò)軟中斷機(jī)制(我們后面講述)向內(nèi)核提交請(qǐng)求,獲取內(nèi)核服務(wù)的接口。而在實(shí)際使用中程序員調(diào)用的多是用戶編程接口——API,而管理員使用的則多是系統(tǒng)命令。

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

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

不過(guò)封裝并非必須的,如果你愿意直接調(diào)用,Linux內(nèi)核也提供了一個(gè)syscall()函數(shù)來(lái)實(shí)現(xiàn)調(diào)用,我們看個(gè)例子來(lái)對(duì)比一下通過(guò)C庫(kù)調(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)命令相對(duì)編程接口更高了一層,它是內(nèi)部引用API的可執(zhí)行程序,比如我們常用的系統(tǒng)命令ls、hostname等。Linux的系統(tǒng)命令格式遵循系統(tǒng)V的傳統(tǒng),多數(shù)放在/bin/sbin下(相關(guān)內(nèi)容可看看shell等章節(jié))。

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

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

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

asmlinkage long sys_getpid(void)

{

       return current->tpid;

}

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

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

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

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

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

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

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

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

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

 

 

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

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

 

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

假設(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來(lái)產(chǎn)看它進(jìn)入內(nèi)核后的執(zhí)行路徑。

l         激活KDB (按下pause鍵,當(dāng)然你必須已經(jīng)給內(nèi)核打了KDB補(bǔ)丁);設(shè)置內(nèi)核斷點(diǎn) “bp sys_getpid” ;退出kdb “go”;然后執(zhí)行./getpid 。瞬間,進(jìn)入內(nèi)核調(diào)試狀態(tài),執(zhí)行路徑停止在斷點(diǎn)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)用號(hào)——0x00000014(=20).

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

 

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

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

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

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

3  在內(nèi)核中首先執(zhí)行system_call,接著執(zhí)行根據(jù)系統(tǒng)調(diào)用號(hào)在調(diào)用表中查找到對(duì)應(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)試是一個(gè)很有趣的話題,方法多種多樣,我個(gè)人認(rèn)為比較好用的是UMLuser mode linux+gdb)和 KDB 這兩個(gè)工具。尤其KDB對(duì)于調(diào)試小規(guī)模內(nèi)核模塊或查看內(nèi)核運(yùn)行路徑很有效,對(duì)于它的使用方法可以看看Linux 內(nèi)核調(diào)試器內(nèi)幕這片文章。

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

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

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

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

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

 

調(diào)用性能問(wèn)題

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

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

 

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

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

l        編寫(xiě)字符驅(qū)動(dòng)程序

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

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

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

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

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

 

 

實(shí)驗(yàn)部分

 

代碼功能介紹

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

所以簡(jiǎn)單的講實(shí)驗(yàn)代碼集需要完成以下幾個(gè)基本功能:

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

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

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

 

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

基本函數(shù)

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

日志記錄例程Syscall_audit

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

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

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

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

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

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

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

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

 

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

除了上面介紹的內(nèi)容外,我們還需要一些輔助性,但卻很必要的工作,這些工作將幫助我們將上述代碼靈活地機(jī)結(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號(hào)。.long SYMBOL_NAME(sys_audit));下來(lái)在系統(tǒng)調(diào)用入口中加入跳轉(zhuǎn)到日志記錄服務(wù)例程中(跳轉(zhuǎn) “je auditsys, 而auditsys代碼段會(huì)真正調(diào)用系統(tǒng)調(diào)用記錄例程syscall_audit);

n        其二是填加代碼文件audit.c,該文件中包含syscall_audit與系統(tǒng)調(diào)用sys_audit兩個(gè)函數(shù)體,我們這里只說(shuō)包含函數(shù)體,而并非函數(shù),是因?yàn)檫@里我們并不想把函數(shù)的實(shí)現(xiàn)在內(nèi)核中寫(xiě)死,而是希望利用了函數(shù)指針,即做了兩個(gè)鉤子函數(shù),來(lái)完成把具體函數(shù)實(shí)現(xiàn)放在模塊中完成,以便能動(dòng)態(tài)加載,方便調(diào)試(請(qǐng)見(jiàn)下一節(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)核符號(hào)表,以便能模塊代碼中能掛接上以上函數(shù)指針。

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

 

關(guān)鍵代碼解釋   

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

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

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

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

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

 

STEP BY STEP

下面具體講述一下如何添加這個(gè)調(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 兩個(gè)函數(shù)需要的鉤子函數(shù)(my_auditmy_sysaudit),它們會(huì)在entry.S中被使用。

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

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

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

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

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

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

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

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

 

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

 

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

 



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

 

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

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

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

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

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

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

 

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

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

 

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

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

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
中斷&異常
深入分析linux調(diào)度機(jī)制
Linux 2.6內(nèi)核搶占和spinlock|linux 2.6,內(nèi)核搶占,spinloc...
VxWorks內(nèi)核解讀-2
什么是Rootkit?
SYSTEM initial
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服