1. 程式人生 > >linux下系統呼叫、API、系統命令,核心函式的區別與聯絡

linux下系統呼叫、API、系統命令,核心函式的區別與聯絡

1.系統呼叫:

應用程式和核心間的橋樑,是應用程式訪問核心的入口點;但通常情況下,應用程式通過作業系統提供的API進行程式設計而不是使用系統呼叫直接程式設計; linux的全部系統呼叫加起來大約只有250個左右。

2.API:

  API常以c庫(libc)的形式提供,c庫提供了絕大部分API,每個系統呼叫在c庫中都有對應的封裝函式(通常封裝函式與系統呼叫的名稱相同)。系統呼叫與c庫函式並不是一一對應的,有些c庫函式可能使用多個系統呼叫來實現,也有可能多個c庫函式使用同一個系統呼叫來實現,也有些c庫函式不使用任何系統呼叫來實現。

3.系統命令:

  系統命令是使用linux提供的c庫函式實現的可執行程式,可用strace檢視命令執行時所使用的系統呼叫。

4.核心函式:

  系統呼叫是使用者進入核心的介面,它本身不是核心函式,進入核心後每個系統呼叫會找到自己對應的核心函式(即系統呼叫服務例程)

從使用者的角度看,從底層往上看分別是:核心函式,系統呼叫,API,系統命令

下面附一篇看到的相關文章:

Linux系統呼叫

   顧名思意,系統呼叫說的是作業系統提供給使用者程式呼叫的一組“特殊”介面。使用者程式可以通過這組“特殊”介面來獲得作業系統核心提供的服務,比如使用者可以通過檔案系統相關的呼叫請求系統開啟檔案、關閉檔案或讀寫檔案,可以通過時鐘相關的系統呼叫獲得系統時間或設定系統時間等。

從邏輯上來說,系統呼叫可被看成是一個核心與使用者空間程式互動的介面——它好比一箇中間人,把使用者程序的請求傳達給核心,待核心把請求處理完畢後再將處理結果送回給使用者空間。

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

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

Linux的系統呼叫

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

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

這些系統呼叫按照功能邏輯大致可分為“程序控制”、“檔案系統控制”、“系統控制”、“存管管理”、“網路管理”、“socket控制”、“使用者管理”、“程序間通訊”幾類,詳細情況可參閱文章系統呼叫列表

如果你想詳細看看系統呼叫的說明,可以使用man 2 syscalls 命令檢視,或乾脆到 <核心原始碼目錄>/include/asm-i386/unistd.h原始檔種找到它們的原本。

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

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

l 控制硬體——系統呼叫往往作為硬體資源和使用者空間的抽象介面,比如讀寫檔案時用到的write/read呼叫。

l 設定系統狀態或讀取核心資料——因為系統呼叫是使用者空間和核心的唯一通訊手段[2],所以使用者設定系統狀態,比如開/關某項核心服務(設定某個核心變數),或讀取核心資料都必須通過系統呼叫。比如getpgid、getpriority、setpriority、sethostname

l 程序管理——一系列呼叫介面是用來保證系統中程序能以多工,在虛擬記憶體環境下得以執行。比如 fork、clone、execve、exit等

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

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 ls或strace hostname 命令檢視一下它們用到的系統呼叫,你會發現諸如open、brk、fstat、ioctl 等系統呼叫被用在系統命令中。

下一個需要解釋一下的問題是核心函式和系統呼叫的關係,核心函式大家不要想像的過於複雜,其實它們和普通函式很像,只不過在核心實現,因此要滿足一些核心程式設計的要求[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——向量3、into——向量4 、bound——向量5等異常指令進入核心,而其他異常使用者空間程式無法利用,都是由系統使用的)。

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

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

除了需要傳遞系統呼叫號以外,許多系統呼叫還需要傳遞一些引數到核心,比如sys_write(unsigned int fd, const char * buf, size_t count)呼叫就需要傳遞檔案描述符號fd和要寫入的內容buf和寫入位元組數count等幾個內容到核心。碰到這種情況,Linux會有6個暫存器使用來傳遞這些引數:eax (存放系統呼叫號)、 ebx、ecx、edx、esi及edi來存放這些額外的引數(以字母遞增的順序)。具體做法是在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例程,系統呼叫中返回。

  核心除錯是一個很有趣的話題,方法多種多樣,我個人認為比較好用的是UML(user 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函式原形,以便進行格式轉換和引數傳遞,否則系統不能識別。

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系統呼叫,並把蒐集到的資訊列印到螢幕上。

[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] 這是一個系統提供的核心函式,目的就是從核心向用戶空間傳遞資料。