Linux系統調用詳解

Linux系統調用

摘要:本期重點和大家討論系統調用機制。其中涉及到了一些及系統調用的性能、上下文深層問題,同時也穿插着講述了一些內核調試方法。並且最後試驗部分我們利用系統調用與相關內核服務完成了一個蒐集系統調用序列的特定任務,該試驗具有較強的實用和教學價值。

 轉自:http://blog.csdn.net/kanghua

什麼是系統調用

   顧名思意,系統調用說的是操作系統提供給用戶程序調用的一組“特殊”接口。用戶程序可以通過這組“特殊”接口來獲得操作系統內核提供的服務,比如用戶可以通過文件系統相關的調用請求系統打開文件、關閉文件或讀寫文件,可以通過時鐘相關的系統調用獲得系統時間或設置系統時間等。

從邏輯上來說,系統調用可被看成是一個內核與用戶空間程序交互的接口——它好比一箇中間人,把用戶進程的請求傳達給內核,待內核把請求處理完畢後再將處理結果送回給用戶空間。

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

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

 

Linux的系統調用

     對於現代操作系統,系統調用是一種內核與用戶空間通訊的普遍手段,Linux系統也不例外。但是Linux系統的系統調用相比很多Unix和windows等系統具有一些獨特之處,無處不體現出Linux的設計精髓——簡潔和高效。

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

這些系統調用按照功能邏輯大致可分爲“進程控制”、“文件系統控制”、“系統控制”、“存管管理”、“網絡管理”、“socket控制”、“用戶管理”、“進程間通信”幾類,詳細情況可參閱文章系統調用列表

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

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

要彌補這個鴻溝,第一,你必須明白系統調用在內核裏的主要用途。雖然上面給出了數種分類,不過總的概括來講系統調用主要在系統中的用途無非以下幾類:

l        控制硬件——系統調用往往作爲硬件資源和用戶空間的抽象接口,比如讀寫文件時用到的write/read調用。

l        設置系統狀態或讀取內核數據——因爲系統調用是用戶空間和內核的唯一通訊手段[2],所以用戶設置系統狀態,比如開/關某項內核服務(設置某個內核變量),或讀取內核數據都必須通過系統調用。比如getpgid、getpriority、setpriority、sethostname

l        進程管理——一系列調用接口是用來保證系統中進程能以多任務,在虛擬內存環境下得以運行。比如 forkcloneexecveexit

第二,什麼服務應該存在於內核;或者說什麼功能應該實現在內核而不是在用戶空間。這個問題並不沒有明確的答案,有些服務你可以選擇在內核完成,也可以在用戶空間完成。選擇在內核完成通常基於以下考慮:

l        服務必須獲得內核數據,比如一些服務必須獲得中斷或系統時間等內核數據。

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

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

l        如果內核和用戶空間都需要使用該服務,那麼最好實現在內核空間,比如隨機數產生。

   理解上述道理對掌握系統調用本質意義很大,希望網友們能從使用中多總結,多思考。

 

系統調用、用戶編程接口(API)、系統命令、和內核函數的關係

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

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

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

不過封裝並非必須的,如果你願意直接調用,Linux內核也提供了一個syscall()函數來實現調用,我們看個例子來對比一下通過C庫調用和直接調用的區別。

 

#include <syscall.h>

#include <unistd.h>

#include <stdio.h>

#include <sys/types.h>

int main(void) {

long ID1, ID2;

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

/* 直接系統調用*/

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

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

ID1 = syscall(SYS_getpid);

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

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

/* 使用"libc"封裝的系統調用 */

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

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

ID2 = getpid();

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

return(0);

}

 

系統命令相對編程接口更高了一層,它是內部引用API的可執行程序,比如我們常用的系統命令ls、hostname等。Linux的系統命令格式遵循系統V的傳統,多數放在/bin和/sbin下(相關內容可看看shell等章節)。

有興趣的話可以通過strace lsstrace hostname命令查看一下它們用到的系統調用,你會發現諸如openbrkfstatioctl等系統調用被用在系統命令中。

下一個需要解釋一下的問題是內核函數和系統調用的關係,內核函數大家不要想像的過於複雜,其實它們和普通函數很像,只不過在內核實現,因此要滿足一些內核編程的要求[3]。系統調用是一層用戶進入內核的接口,它本身並非內核函數,進入內核後,不同的系統調用會找到對應到各自的內核函數——換個專業說法就叫:系統調用服務服務例程。實際對請求服務的是內核函數而非調用接口。

比如系統調用 getpid實際就是調用內核函數sys_getpid。

asmlinkage long sys_getpid(void)

{

       return current->tpid;

}

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

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

    總而言之,從用戶角度向內核看,依次是系統命令、編程接口、系統調用和內核函數。再講述了系統調用實現後,我們會回過頭來看看整個執行路徑。

系統調用實現

Linux中實現系統調用利用了0x86體系結構中的軟件中斷[4]。軟件中斷和我們常說的中斷(硬件中斷)不同之處在於——它是通過軟件指令觸發而並非外設,也就是說又編程人員出發的一種異常,具體的講就是調用int $0x80彙編指令,這條彙編指令將產生向量爲128的編程異常。

之所以系統調用需要藉助異常實現,是因爲當用戶態的進程調用一個系統調用時,CPU便被切換到內核態執行內核函數[5],而我們在i386體系結構部分已經講述過了進入內核——進入高特權級別——必須經過系統的門機制,這裏異常實際上就是通過系統門陷入內核(除了int 0x80外用戶空間還可以通過int3——向量3into——向量4bound——向量5等異常指令進入內核,而其他異常用戶空間程序無法利用,都是由系統使用的)。

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

很顯然所有的系統調用都會統一的轉到這個地址,但Linux一共有23百個系統調用都從這裏進入內核後又該如何派發它們到各自的服務程序去呢?別發昏,解決這個問題的方法非常簡單:首先Linux爲每個系統調用都進行了編號(0NR_syscall),同時在內核中保存了一張系統調用表,該表中保存了系統調用編號和其對應的服務例程,因此在系統調入通過系統門陷入內核前,需要把系統調用號一併傳入內核,在x86上,這個傳遞動作是通過在執行int0x80前把調用號裝入eax寄存器實現的。這樣系統調用處理程序一旦運行,就可以從eax中得到數據,然後再去系統調用表中尋找相應服務例程了。

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

 

 

有始便有終,當服務例程結束時,system_call( ) 從eax獲得系統調用的返回值,並把這個返回值存放在曾保存用戶態 eax寄存器棧單元的那個位置上。然後跳轉到ret_from_sys_call( ),終止系統調用處理程序的執行。

當進程恢復它在用戶態的執行前,RESTORE_ALL宏會恢復用戶進入內核前被保留到堆棧中的寄存器值。其中eax返回時會帶回系統調用的返回碼。(負數說明調用錯誤,0或正數說明正常完成)

 

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

假設我們的程序源文件名爲getpid.c,內容是:

#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的執行文件”gcc –o getpid <路徑>/getpid.c”, 我們使用KDB來產看它進入內核後的執行路徑。

l         激活KDB (按下pause鍵,當然你必須已經給內核打了KDB補丁);設置內核斷點 “bp sys_getpid” ;退出kdb “go”;然後執行./getpid 。瞬間,進入內核調試狀態,執行路徑停止在斷點sys_getpid處。

l         在KDB>提示符下,執行bt命令觀察堆棧,發現調用的嵌套路徑,可以看到在sys_getpid是在內核函數system_call中被嵌套調用的。

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

l         在KDB>提示符下,執行ssb(或ss)命令跟蹤內核代碼執行路徑,可以發現sys_getpid執行後,會返回system_call函數,然後接者轉入ret_from_sys_call例程。(再往後還有些和調度有關其他例程,我們這裏不說了它們了。)

 

結合用戶空間的執行路徑,大致該程序可歸結爲一下幾個步驟:

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

2  調用軟中斷 int 0x80 進入內核。

(以下進入內核態)

3  在內核中首先執行system_call,接着執行根據系統調用號在調用表中查找到對應的系統調用服務例程sys_getpid。

4.執行sys_getpid服務例程。

5.執行完畢後,轉入ret_from_sys_call例程,系統調用中返回。

 

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

系統調用思考

    系統調用的內在過程並不複雜,我們不再多說了,下面這節我們主要就係統調用所涉及的一些重要問題作一些討論和分析,希望這樣能更有助瞭解系統調用的精髓。

調用上下文分析

系統調用雖說是要進入內核執行,但它並非一個純粹意義上的內核例程。首先它是代表用戶進程的,這點決定了雖然它會陷入內核執行,但是上下文仍然是處於進程上下文中,因此可以訪問進程的許多信息(比如current結構——當前進程的控制結構),而且可以被其他進程搶佔(在從系統調用返回時,由system_call函數判斷是否該再調度),可以休眠,還可接收信號[6]等等。

所有這些特點都涉及到了進程調度的問題,我們這裏不做深究,只要大家明白系統調用完成後,再回到或者說把控制權交回到發起調用的用戶進程前,內核會有一次調度。如果發現有優先級別更高的進程或當前進程的時間片用完,那麼就會選擇高優先級的進程或重新選擇進程運行。除了再調度需要考慮外,再就是內核需要檢查是否有掛起的信號,如果發現當前進程有掛起的信號,那麼還需要先返回用戶空間處理信號處理例程(處於用戶空間),然後再回到內核,重新返回用戶空間,有些麻煩但這個反覆過程是必須的。

 

調用性能問題

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

絕大多數情況下,Linux系統調用性能是可以接受的,但是對於一些對性能要求非常高的應用來說,它們雖然希望利用系統調用的服務,但卻希望加快相應速度,避免陷入/返回和系統調用處理程序帶來的花銷,因此採用由內核直接調用系統調用服務例程,最好的例子就HTTPD——它爲了避免上述開銷,從內核調用socket等系統調用服務例程。

 

什麼時候添加系統調用

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

l        編寫字符驅動程序

利用字符驅動程序可以完成和內核交互數據的功能。它最大的好處在於可以模塊式加載,這樣以來就避免了編譯內核等手續,而且調用接口固定,容易操作。

l        使用proc文件系統

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

l        使用虛擬文件系統

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

 

 

實驗部分

 

代碼功能介紹

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

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

第一:記錄系統調用日誌,將其寫入緩衝區(內核中),以便用戶讀取;

第二:建立新的系統調用,以便將內核緩衝中的系統調用日誌返回到用戶空間。

第三:循環利用系統調用,以便能動態實時返回系統調用日誌。

 

代碼結構體系介紹

基本函數

代碼功能一節介紹中的基本功能對應程序代碼集中的三個子程序。它們分別是syscall_auydit、Sys_audit和auditd。接下來我們介紹代碼具體結構。

日誌記錄例程Syscall_audit

syscall_audit該程序是一個內核態的服務例程,該例程負責記錄系統調用的運行日誌。

記錄系統調用日誌的具體做法是在內核中修改系統調用處理程序system_call[7],在其中需要監控的每個調用(在我們例子鍾222個系統調用都監控了,當然你也可以根據自己需求有選擇的監控)執行完畢後都插入一個日誌記錄指令,該指令會轉去調用內核服務函數syscall_audit來記錄該次調用的信息[8]

Syscall_audit內核服務例程會建立了一個內核緩衝區來存放被記錄的函數。當蒐集的數據量到達一定閥值時(比如設定爲到達緩衝區總大小的%80,這樣作可避免在丟失新調用),喚醒系統調用進程取回數據。否則繼續蒐集,這時系統調用程序會堵塞在一個等待隊列上,直到被喚醒,也就是說如果緩衝區還沒接近滿時,系統調用會等待(被掛起)它被填充。

系統調用Sys_audit

由於系統調用是在內核中被執行,因此記錄其執行日誌也應該在內核態收集,所以我們需要利用一個新的系統調用來完成將內核信息帶回到用戶空間——sys_audit就是我們新填加的系統調用,它功能非常簡單,就是從緩衝區中取數據返回用戶空間。

爲了保證數據連續性,防止丟失。我們會建立一個內核緩衝區存放每刻蒐集到的日誌數據,並且當蒐集的數據量到達一定閥值時(比如設定爲到達緩衝區總大小的%80),系統調用進程就會被喚醒[9],以取回數據。否則在日誌蒐集時,系統調用程序會堵塞在等待隊列上,直到被喚醒,也就是說如果緩衝區還沒接近滿時,系統調用會等待它被填充。

用戶空間服務程序auditd

不用多說,我們需要一個用戶空間服務進程來不斷的調用audit系統調用,取回系統中蒐集到的的調用日誌信息。要知道,長時間的調用日誌序列對於分析入侵或系統行爲等才有價值。

 

把代碼集成到內核中

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

n        其一是修改entry.S彙編代碼,該代碼中含有系統調用表和系統調用入口代碼system_call。我們首先需要在系統調用表中加入新的系統調用(名爲sys_audit,223號。.long SYMBOL_NAME(sys_audit));下來在系統調用入口中加入跳轉到日誌記錄服務例程中(跳轉 “je auditsys”, 而auditsys代碼段會真正調用系統調用記錄例程syscall_audit);

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

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);

,這樣做是爲了導出內核符號表,以便能模塊代碼中能掛接上以上函數指針。

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

 

關鍵代碼解釋   

     我們的日誌收集例程與取日誌系統調用這兩個關鍵函數的實現是放在內核模塊中實現。其中有些需要解釋的地方:

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

2.      我們系統調用日誌記錄採用了一個結構體:syscall_buf它含有諸如系統調用號——syscall、進程ID——pid、調用程序名——comm[COMM_SIZE]等字段,共52字節;我們的內核緩衝區爲audit_buf,它是一個可容納100個syscall_buf的數組。

3.      系統調用實現極簡單,要做的僅僅是利用__copy_to_user[11]將內核緩衝中的日誌數據取到用戶空間爲了提高效率在緩衝區未滿時未到%80的閥值時),系統調用會掛起等待wait_event_interruptible(buffer_wait, current_pos >= AUDIT_BUF_SIZE*8/10);相應地當緩衝區收集快滿時,則喚醒系統調用繼續收集日誌wake_up_interruptible(&buffer_wait)

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

取回數據

Audit系統調用

用戶程序auditd

系統調用服務例程sys_audit

系統調用日誌緩衝

Audit.o模塊

my_sysaudit鉤子函數

my_audit鉤子函數

用戶空間

內核空間

日誌記錄例程syscall_audit

描述日誌數據流向

 

描述系統調用關係

 

程序體系圖

 

STEP BY STEP

下面具體講述一下如何添加這個調用。

1 修改entry.S ——在其中的添加audit調用,並且在system_call中加入蒐集例程。(該函數位於<內核源代碼>/arch/i386/kernel/下)

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

sys_audit和syscall_audit兩個函數需要的鉤子函數(my_audit和my_sysaudit),它們會在entry.S中被使用。

3 修改<內核源代碼>/arch/i386/kernel/i386-kysms.c文件,在其中導出my_audit與my_sysaudit兩個鉤子函數。因爲只有在內核符號表裏導出,纔可被其他內核函數使用,也就是說才能在模塊中被掛上。

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

到這可以重新編譯內核了,新內核已經加入了檢測點了。下一步是編寫模塊來實現系統調用與內核蒐集服務例程的功能了。

1 編寫名爲audit的模塊,其中除了加載、卸載模塊函數以外主要實現了mod_sys_audit與mod_syscall_audit兩個函數。它們會分別掛載到my_sysaudit和my_audit兩個鉤子上。

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

3 修改/usr/include/asm/unistd.h ——在其中加入audit的系統調用號。這樣用戶空間纔可找到audit系統調用了。

4 最後,我們寫一個用戶deamon程序,來循環調用audit系統調用,並把蒐集到的信息打印到屏幕上。

完了。系統調用還有許多細節,請大家查看有關書記吧。不羅索了。再見。

 

相關代碼請下載 auditexample.tar(實現於2.4.18內核)。

 

感謝SAL的開發者,例子程序基本框架來自於它們的靈感。

 



[1]我們說的開發內核者指開發系統內核,比如開發驅動模塊機制、開發系統調用機制;而內核開發者則是指在內核基礎之上進行的開發,比如驅動開發、系統調用開發、文件系統開發、網絡通訊協議開發等。我們雜誌所關注的問題主要在內核開發層次,即利用內核提供的機制進行開發。

 

[2]對Linux而言,系統調用是用戶程序訪問內核的唯一手段,無論是/proc方式或設備文件方式歸根到底都是利用系統調用完成的。

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

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

[5]系統調用過程可被理解成——由內核在覈心態代表應用程序執行任務。

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

[7]System_call是個通用的系統調用服務程序,或說系統調用入口程序,因爲任何一個系統調用都要經過system_call統一處理(查找系統調用表,跳轉到相應調用的服務例程),所以任何一次系統調用的信息都可被syscall_audit記錄下來。

 

[8] 這裏我們主要記錄諸如調用時刻、調用者PID、程序名等信息,這些信息可從xtime或current這些全局變量處取得。

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

 

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

[11] 這是一個系統提供的內核函數,目的就是從內核向用戶空間傳遞數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章