用戶空間與內核空間數據交換的方式

1、編寫自己的系統調用
從前文可以看出,系統調用是用戶級程序訪問內核最基本的方法。目前linux大致提供了二百多個標準的系統調用(參見內核代碼樹中的include/ asm-i386/unistd.h和arch/i386/kernel/entry.S文件),並且允許我們添加自己的系統調用來實現和內核的信息交換。比如我們希望建立一個系統調用日誌系統,將所有的系統調用動作記錄下來,以便進行***檢測。此時,我們可以編寫一個內核服務程序。該程序負責收集所有的系統調用請求,並將這些調用信息記錄到在內核中自建的緩衝裏。我們無法在內核裏實現複雜的***檢測程序,因此必須將該緩衝裏的記錄提取到用戶空間。最直截了當的方法是自己編寫一個新系統調用實現這種提取緩衝數據的功能。當內核服務程序和新系統調用都實現後,我們就可以在用戶空間裏編寫用戶程序進行***檢測任務了,***檢測程序可以定時、輪訓或在需要的時候調用新系統調用從內核提取數據,然後進行***檢測了。

2、編寫驅動程序
Linux/UNIX的一個特點就是把所有的東西都看作是文件(every thing is a file)。系統定義了簡潔完善的驅動程序界面,客戶程序可以用統一的方法透過這個界面和內核驅動程序交互。而大部分系統的使用者和開發者已經非常熟悉這種界面以及相應的開發流程了。驅動程序運行於內核空間,用戶空間的應用程序通過文件系統中/dev/目錄下的一個文件來和它交互。這就是我們熟悉的那個文件操作流程:open() —— read() —— write() —— ioctl() —— close()。(需要注意的是也不是所有的內核驅動程序都是這個界面,網絡驅動程序和各種協議棧的使用就不大一致,比如說套接口編程雖然也有open()close()等概念,但它的內核實現以及外部使用方式都和普通驅動程序有很大差異。)
設備驅動程序在內核中要做的中斷響應、設備管理、數據處理等等各種工作這篇文章不去關心,我們把注意力集中在它與用戶級程序交互這一部分。操作系統爲此定義了一種統一的交互界面,就是前面所說的open(), read(), write(), ioctl()和close()等等。每個驅動程序按照自己的需要做獨立實現,把自己提供的功能和服務隱藏在這個統一界面下。客戶級程序選擇需要的驅動程序或服務(其實就是選擇/dev/目錄下的文件),按照上述界面和文件操作流程,就可以跟內核中的驅動交互了。其實用面向對象的概念會更容易解釋,系統定義了一個抽象的界面(abstract interface),每個具體的驅動程序都是這個界面的實現(implementation)。所以驅動程序也是用戶空間和內核信息交互的重要方式之一。其實ioctl, read, write本質上講也是通過系統調用去完成的,只是這些調用已被內核進行了標準封裝,統一定義。因此用戶不必向填加新系統調用那樣必須修改內核代碼,重新編譯新內核,使用虛擬設備只需要通過模塊方法將新的虛擬設備安裝到內核中(insmod上)就能方便使用。
在/dev/目錄下建立一個設備文件對應我們新加入內核的系統調用日誌系統驅動程序。

3、 使用proc 文件系統
proc是Linux提供的一種特殊的文件系統,推出它的目的就是提供一種便捷的用戶和內核間的交互方式。它以文件系統作爲使用界面,使應用程序可以以文件操作的方式安全、方便的獲取系統當前運行的狀態和其它一些內核數據信息。proc文件系統多用於監視、管理和調試系統,我們使用的很多管理工具如ps,top等,都是利用proc來讀取內核信息的。除了讀取內核信息,proc文件系統還提供了寫入功能。所以我們也就可以利用它來向內核輸入信息。比如,通過修改proc文件系統下的系統參數配置文件(/proc/sys),我們可以直接在運行時動態更改內核參數;再如,通過下面這條指令:

echo 1 > /proc/sys/net/ip_v4/ip_forward

開啓內核中控制IP轉發的開關,我們就可以讓運行中的Linux系統啓用路由功能。類似的,還有許多內核選項可以直接通過proc文件系統進行查詢和調整。

除了系統已經提供的文件條目,proc還爲我們留有接口,允許我們在內核中創建新的條目從而與用戶程序共享信息數據。比如,我們可以爲系統調用日誌程序(不管是作爲驅動程序也好,還是作爲單純的內核模塊也好)在proc文件系統中創建新的文件條目,在此條目中顯示系統調用的使用次數,每個單獨系統調用的使用頻率等等。我們也可以增加另外的條目,用於設置日誌記錄規則,比如說不記錄open系統調用的使用情況等。

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

5、 使用內存映像
Linux通過內存映像機制來提供用戶程序對內存直接訪問的能力。內存映像的意思是把內核中特定部分的內存空間映射到用戶級程序的內存空間去。也就是說,用戶空間和內核空間共享一塊相同的內存。這樣做的直觀效果顯而易見:內核在這塊地址內存儲變更的任何數據,用戶可以立即發現和使用,根本無須數據拷貝。而在使用系統調用交互信息時,在整個操作過程中必須有一步數據拷貝的工作——或者是把內核數據拷貝到用戶緩衝區,或只是把用戶數據拷貝到內核緩衝區——這對於許多數據傳輸量大、時間要求高的應用,這無疑是致命的一擊:許多應用根本就無法忍受數據拷貝所耗費的時間和資源。
前面介紹的交互方法最大的不同在於這些方式是由內核採取主動,而不是等系統調用來被動的返回信息的。

A 從內核空間調用用戶程序。
即使在內核中,我們有時也需要執行一些在用戶級才提供的操作:如打開某個文件以讀取特定數據,執行某個用戶程序從而完成某個功能。因爲許多數據和功能在用戶空間是現有的或者已經被實現了,那麼沒有必要耗費大量的資源去重複。此外,內核在設計時,爲了擁有更好的彈性或者性能以支持未知但有可能發生的變化,本身就要求使用用戶空間的資源來配合完成任務。比如內核中動態加載模塊的部分需要調用kmod。但在編譯kmod的時候不可能把所有的內核模塊都訂下來(要是這樣的話動態加載模塊就沒有存在意義了),所以它不可能知道在它以後纔出現的那些模塊的位置和加載方法。因此,模塊的動態加載就採用瞭如下策略:加載任務實際上由位於用戶空間的modprobe程序幫助完成——最簡單的情形是modprobe用內核傳過來的模塊名字作爲參數調用insmod。用這種方法來加載所需要的模塊。

內核中啓動用戶程序還是要通過execve這個系統調用原形,只是此時的調用發生在內核空間,而一般的系統調用則在用戶空間進行。如果系統調用帶參數,那將會碰到一個問題:因爲在系統調用的具體實現代碼中要檢查參數合法性,該檢查要求所有的參數必須位於用戶空間——地址處於0x0000000——0xC0000000之間,所以如果我們從內核傳遞參數(地址大於0xC0000000),那麼檢查就會拒絕我們的調用請求。爲了解決這個問題,我們可以利用set_fs宏來修改檢查策略,使得允許參數地址爲內核地址。這樣內核就可以直接使用該系統調用了。

例如:在kmod通過調用execve來執行modprobe的代碼前需要有set_fs(KERNEL_DS):

......
set_fs(KERNEL_DS);

/* Go, go, go... */
if (execve(program_path, argv, envp) < 0)
< 0)
 0)
< 0)
< 0)
 0)
return -errno;
上述代碼中program_path 爲"/sbin/modprobe",argv爲{ modprobe_path, "-s", "-k", "--", (char*)module_name, NULL },envp爲{ 

"HOME=/", "TERM=linux", "ATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL }。
從內核中打開文件同樣使用帶參數的open系統調用,所需的仍是要先調用set_fs宏。 

B 利用brk系統調用來導出內核數據
內核和用戶空間傳遞數據主要是用get_user(ptr)和put_user(datum,ptr)例程。所以在大部分需要傳遞數據的系統調用中都可以找到它們的身影。可是,如果我們不是通過用戶程序發起的系統調用——也就是說,沒有明確的提供用戶空間內的緩衝區位置——的情況下,如何向用戶空間傳遞內核數據呢?顯然,我們不能再直接使用put_user()了,因爲我們沒有辦法給它指定目的緩衝區。所以,我們要借用brk系統調用和當前進程空間:brk用於給進程設置堆空間的大小。每個進程擁有一個獨立的堆空間,malloc等動態內存分配函數其實就是進程的堆空間中獲取內存的。我們將利用brk在當前進程(current process)的堆空間上擴展一塊新的臨時緩衝區,再用put_user將內核數據導出到這個確定的用戶空間去。

C: 使用信號:
信號在內核裏的用途主要集中在通知用戶程序出現重大錯誤,強行殺死當前進程,這時內核通過發送SIGKILL信號通知進程終止,內核發送信號使用send_sign(pid,sig)例程,可以看到信號發送必須要事先知道進程序號(pid),所以要想從內核中通過發信號的方式異步通知用戶進程執
行某項任務,那麼必須事先知道用戶進程的進程號纔可。而內核運行時搜索到特定進程的進程號是個費事的工作,可能要遍歷整個進程控制塊鏈表。所以用信號通知特定用戶進程的方法很糟糕,一般在內核不會使用。

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