linux内核空间和用户空间认识和区别
原文地址:http://blog.sina.com.cn/s/blog_7df62c2a0101hvz2.html
linux驅(qū)動(dòng)程序一般工作在內(nèi)核空間,但也可以工作在用戶空間。下面我們將詳細(xì)解析,什么是內(nèi)核空間,什么是用戶空間,以及如何判斷他們。
Linux簡(jiǎn)化了分段機(jī)制,使得虛擬地址與線性地址總是一致,因此,Linux的虛擬地址空間也為0~4G.Linux內(nèi)核將這4G字節(jié)的空間分為兩部分。將最高的1G字節(jié)(從虛擬地址0xC0000000到0xFFFFFFFF),供內(nèi)核使用,稱為"內(nèi)核空間".而將較低的3G字節(jié)(從虛擬地址 0x00000000到0xBFFFFFFF),供各個(gè)進(jìn)程使用,稱為"用戶空間)。因?yàn)槊總€(gè)進(jìn)程可以通過(guò)系統(tǒng)調(diào)用進(jìn)入內(nèi)核,因此,Linux內(nèi)核由系統(tǒng)內(nèi)的所有進(jìn)程共享。于是,從具體進(jìn)程的角度來(lái)看,每個(gè)進(jìn)程可以擁有4G字節(jié)的虛擬空間。
Linux使用兩級(jí)保護(hù)機(jī)制:0級(jí)供內(nèi)核使用,3級(jí)供用戶程序使用。從圖中可以看出(這里無(wú)法表示圖),每個(gè)進(jìn)程有各自的私有用戶空間(0~3G),這個(gè)空間對(duì)系統(tǒng)中的其他進(jìn)程是不可見(jiàn)的。最高的1GB字節(jié)虛擬內(nèi)核空間則為所有進(jìn)程以及內(nèi)核所共享。
內(nèi)核空間中存放的是內(nèi)核代碼和數(shù)據(jù),而進(jìn)程的用戶空間中存放的是用戶程序的代碼和數(shù)據(jù)。不管是內(nèi)核空間還是用戶空間,它們都處于虛擬空間中。
雖然內(nèi)核空間占據(jù)了每個(gè)虛擬空間中的最高1GB字節(jié),但映射到物理內(nèi)存卻總是從最低地址(0x00000000)開(kāi)始。對(duì)內(nèi)核空間來(lái)說(shuō),其地址映射是很簡(jiǎn)單的線性映射,0xC0000000就是物理地址與線性地址之間的位移量,在Linux代碼中就叫做PAGE_OFFSET.
內(nèi)核空間和用戶空間之間如何進(jìn)行通訊?
內(nèi)核空間和用戶空間一般通過(guò)系統(tǒng)調(diào)用進(jìn)行通信。
如何判斷一個(gè)驅(qū)動(dòng)是用戶模式驅(qū)動(dòng)還是內(nèi)核模式驅(qū)動(dòng)? 判斷的標(biāo)準(zhǔn)是什么?
用戶空間模式的驅(qū)動(dòng)一般通過(guò)系統(tǒng)調(diào)用來(lái)完成對(duì)硬件的訪問(wèn),如通過(guò)系統(tǒng)調(diào)用將驅(qū)動(dòng)的io空間映射到用戶空間等。因此,主要的判斷依據(jù)就是系統(tǒng)調(diào)用。
內(nèi)核空間和用戶空間上不同太多了,說(shuō)不完,比如用戶態(tài)的鏈表和內(nèi)核鏈表不一樣;用戶態(tài)用printf,內(nèi)核態(tài)用printk;用戶態(tài)每個(gè)應(yīng)用程序空間是虛擬的,相對(duì)獨(dú)立的,內(nèi)核態(tài)中卻不是獨(dú)立的,所以編程要非常小心。等等。
還有用戶態(tài)和內(nèi)核態(tài)程序通訊的方法很多,不單單是系統(tǒng)調(diào)用,實(shí)際上系統(tǒng)調(diào)用是個(gè)不好的選擇,因?yàn)樾枰到y(tǒng)調(diào)用號(hào),這個(gè)需要統(tǒng)一分配。
可以通過(guò)ioctl、sysfs、proc等來(lái)完成。
??????
在進(jìn)行設(shè)備驅(qū)動(dòng)程序,內(nèi)核功能模塊等系統(tǒng)級(jí)開(kāi)發(fā)時(shí),通常需要在內(nèi)核和用戶程序之間交換信息。Linux提供了多種方法可以用來(lái)完成這些任務(wù)。本文總結(jié)了各種常用的信息交換方法,并用簡(jiǎn)單的例子演示這些方法各自的特點(diǎn)及用法。其中有大家非常熟悉的方法,也有特殊條件下方可使用的手段。通過(guò)對(duì)比明確這些方法,可以加深我們對(duì)Linux內(nèi)核的認(rèn)識(shí),更重要的是,可以讓我們更熟練駕御linux內(nèi)核級(jí)的應(yīng)用開(kāi)發(fā)技術(shù)。
?
內(nèi)核空間(kernel-space) VS?用戶空間(user-space)作為一個(gè)Linux開(kāi)發(fā)者,首先應(yīng)該清楚內(nèi)核空間和用戶空間的區(qū)別。關(guān)于這個(gè)話題,已經(jīng)有很多相關(guān)資料,我們?cè)谶@里簡(jiǎn)單描述如下:
現(xiàn)代的計(jì)算機(jī)體系結(jié)構(gòu)中存儲(chǔ)管理通常都包含保護(hù)機(jī)制。提供保護(hù)的目的,是要避免系統(tǒng)中的一個(gè)任務(wù)訪問(wèn)屬于另外的或?qū)儆诓僮飨到y(tǒng)的存儲(chǔ)區(qū)域。如在IntelX86體系中,就提供了特權(quán)級(jí)這種保護(hù)機(jī)制,通過(guò)特權(quán)級(jí)別的區(qū)別來(lái)限制對(duì)存儲(chǔ)區(qū)域的訪問(wèn)。 基于這種構(gòu)架,Linux操作系統(tǒng)對(duì)自身進(jìn)行了劃分:一部分核心軟件獨(dú)立于普通應(yīng)用程序,運(yùn)行在較高的特權(quán)級(jí)別上,(Linux使用Intel體系的特權(quán)級(jí)3來(lái)運(yùn)行內(nèi)核。)它們駐留在被保護(hù)的內(nèi)存空間上,擁有訪問(wèn)硬件設(shè)備的所有權(quán)限,Linux將此稱為內(nèi)核空間。
相對(duì)的,其它部分被作為應(yīng)用程序在用戶空間執(zhí)行。它們只能看到允許它們使用的部分系統(tǒng)資源,并且不能使用某些特定的系統(tǒng)功能,不能直接訪問(wèn)硬件,不能直接訪問(wèn)內(nèi)核空間,當(dāng)然還有其他一些具體的使用限制。(Linux使用Intel體系的特權(quán)級(jí)0來(lái)運(yùn)行用戶程序。)
從安全角度講將用戶空間和內(nèi)核空間置于這種非對(duì)稱訪問(wèn)機(jī)制下是很有效的,它能抵御惡意用戶的窺探,也能防止質(zhì)量低劣的用戶程序的侵害,從而使系統(tǒng)運(yùn)行得更穩(wěn)定可靠。但是,如果像這樣完全不允許用戶程序訪問(wèn)和使用內(nèi)核空間的資源,那么我們的系統(tǒng)就無(wú)法提供任何有意義的功能了。為了方便用戶程序使用在內(nèi)核空間才能完全控制的資源,而又不違反上述的特權(quán)規(guī)定,從硬件體系結(jié)構(gòu)本身到操作系統(tǒng),都定義了標(biāo)準(zhǔn)的訪問(wèn)界面。關(guān)于X86系統(tǒng)的細(xì)節(jié),請(qǐng)查閱參考資料1
一般的硬件體系機(jī)構(gòu)都提供一種“門”機(jī)制。“門”的含義是指在發(fā)生了特定事件的時(shí)候低特權(quán)的應(yīng)用程序可以通過(guò)這些“門”進(jìn)入高特權(quán)的內(nèi)核空間。對(duì)于IntelX86體系來(lái)說(shuō),Linux操作系統(tǒng)正是利用了“系統(tǒng)門”這個(gè)硬件界面(通過(guò)調(diào)用int?$0x80機(jī)器指令),構(gòu)造了形形色色的系統(tǒng)調(diào)用作為軟件界面,為應(yīng)用程序從用戶態(tài)陷入到內(nèi)核態(tài)提供了通道。通過(guò)“系統(tǒng)調(diào)用”使用“系統(tǒng)門”并不需要特別的權(quán)限,但陷入到內(nèi)核的具體位置卻不是隨意的,這個(gè)位置由“系統(tǒng)調(diào)用”來(lái)指定,有這樣的限制才能保證內(nèi)核安全無(wú)虞。我們可以形象地描述這種機(jī)制:作為一個(gè)游客,你可以買票要求進(jìn)入野生動(dòng)物園,但你必須老老實(shí)實(shí)的坐在觀光車上,按照規(guī)定的路線觀光游覽。當(dāng)然,不準(zhǔn)下車,因?yàn)槟菢犹kU(xiǎn),不是讓你丟掉小命,就是讓你嚇壞了野生動(dòng)物。
出于效率和代碼大小的考慮,內(nèi)核程序不能使用標(biāo)準(zhǔn)庫(kù)函數(shù)(當(dāng)然還有其它的顧慮,詳細(xì)原因請(qǐng)查閱參考資料2)因此內(nèi)核開(kāi)發(fā)不如用戶程序開(kāi)發(fā)那么方便。
內(nèi)核空間和用戶空間的相互作用現(xiàn)在,越來(lái)越多的應(yīng)用程序需要編寫內(nèi)核級(jí)和用戶級(jí)的程序來(lái)一起完成具體的任務(wù),通常采用以下模式:首先,編寫內(nèi)核服務(wù)程序利用內(nèi)核空間提供的權(quán)限和服務(wù)來(lái)接收、處理和緩存數(shù)據(jù);然后編寫用戶程序來(lái)和先前完成的內(nèi)核服務(wù)程序交互,具體來(lái)說(shuō),可以利用用戶程序來(lái)配置內(nèi)核服務(wù)程序的參數(shù),提取內(nèi)核服務(wù)程序提供的數(shù)據(jù),當(dāng)然,也可以向內(nèi)核服務(wù)程序輸入待處理數(shù)據(jù)。
比較典型的應(yīng)用包括:?Netfilter(內(nèi)核服務(wù)程序:防火墻)VS?Iptable(用戶級(jí)程序:規(guī)則設(shè)置程序);IPSEC(內(nèi)核服務(wù)程序:VPN協(xié)議部分)VS IKE(用戶級(jí)程序:vpn密鑰協(xié)商處理);當(dāng)然還包括大量的設(shè)備驅(qū)動(dòng)程序及相應(yīng)的應(yīng)用軟件。這些應(yīng)用都是由內(nèi)核級(jí)和用戶級(jí)程序通過(guò)相互交換信息來(lái)一起完成特定任務(wù)的。
信息交互方法用戶程序和內(nèi)核的信息交換是雙向的,也就是說(shuō)既可以主動(dòng)從用戶空間向內(nèi)核空間發(fā)送信息,也可以從內(nèi)核空間向用戶空間提交數(shù)據(jù)。當(dāng)然,用戶程序也可以主動(dòng)地從內(nèi)核提取數(shù)據(jù)。下面我們就針對(duì)內(nèi)核和用戶交互數(shù)據(jù)的方法做一總結(jié)、歸納。
信息交互按信息傳輸發(fā)起方可以分為用戶向內(nèi)核傳送/提取數(shù)據(jù)和內(nèi)核向用戶空間提交請(qǐng)求兩大類,先來(lái)說(shuō)說(shuō):
由用戶級(jí)程序主動(dòng)發(fā)起的信息交互。
(1)編寫自己的系統(tǒng)調(diào)用
從前文可以看出,系統(tǒng)調(diào)用是用戶級(jí)程序訪問(wèn)內(nèi)核最基本的方法。目前linux大致提供了二百多個(gè)標(biāo)準(zhǔn)的系統(tǒng)調(diào)用,并且允許我們添加自己的系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)和內(nèi)核的信息交換。比如我們希望建立一個(gè)系統(tǒng)調(diào)用日志系統(tǒng),將所有的系統(tǒng)調(diào)用動(dòng)作記錄下來(lái),以便進(jìn)行入侵檢測(cè)。此時(shí),我們可以編寫一個(gè)內(nèi)核服務(wù)程序。該程序負(fù)責(zé)收集所有的系統(tǒng)調(diào)用請(qǐng)求,并將這些調(diào)用信息記錄到在內(nèi)核中自建的緩沖里。我們無(wú)法在內(nèi)核里實(shí)現(xiàn)復(fù)雜的入侵檢測(cè)程序,因此必須將該緩沖里的記錄提取到用戶空間。最直截了當(dāng)?shù)姆椒ㄊ亲约壕帉懸粋€(gè)新系統(tǒng)調(diào)用實(shí)現(xiàn)這種提取緩沖數(shù)據(jù)的功能。當(dāng)內(nèi)核服務(wù)程序和新系統(tǒng)調(diào)用都實(shí)現(xiàn)后,我們就可以在用戶空間里編寫用戶程序進(jìn)行入侵檢測(cè)任務(wù)了,入侵檢測(cè)程序可以定時(shí)、輪訓(xùn)或在需要的時(shí)候調(diào)用新系統(tǒng)調(diào)用從內(nèi)核提取數(shù)據(jù),然后進(jìn)行入侵檢測(cè)(具體步驟和代碼參見(jiàn)Linux內(nèi)核之旅網(wǎng)站電子雜志第四期)。
(2)編寫驅(qū)動(dòng)程序Linux/UNIX的一個(gè)特點(diǎn)就是把所有的東西都看作是文件(every thing is a file)。系統(tǒng)定義了簡(jiǎn)潔完善的驅(qū)動(dòng)程序界面,客戶程序可以用統(tǒng)一的方法透過(guò)這個(gè)界面和內(nèi)核驅(qū)動(dòng)程序交互。而大部分系統(tǒng)的使用者和開(kāi)發(fā)者已經(jīng)非常熟悉這種界面以及相應(yīng)的開(kāi)發(fā)流程了。
驅(qū)動(dòng)程序運(yùn)行于內(nèi)核空間,用戶空間的應(yīng)用程序通過(guò)文件系統(tǒng)中/dev/目錄下的一個(gè)文件來(lái)和它交互。這就是我們熟悉的那個(gè)文件操作流程:open()?——?read()?——?write()?——?ioctl()?——close()。(需要注意的是也不是所有的內(nèi)核驅(qū)動(dòng)程序都是這個(gè)界面,網(wǎng)絡(luò)驅(qū)動(dòng)程序和各種協(xié)議棧的使用就不大一致,比如說(shuō)套接口編程雖然也有open()和close()等概念,但它的內(nèi)核實(shí)現(xiàn)以及外部使用方式都和普通驅(qū)動(dòng)程序有很大差異。)關(guān)于這部分的編程細(xì)節(jié),請(qǐng)查閱參考資料3、4。
設(shè)備驅(qū)動(dòng)程序在內(nèi)核中要做的中斷響應(yīng)、設(shè)備管理、數(shù)據(jù)處理等等各種工作這篇文章不去關(guān)心,我們把注意力集中在它與用戶級(jí)程序交互這一部分。操作系統(tǒng)為此定義了一種統(tǒng)一的交互界面,就是前面所說(shuō)的open(), read(), write(),?ioctl()和close()等等。每個(gè)驅(qū)動(dòng)程序按照自己的需要做獨(dú)立實(shí)現(xiàn),把自己提供的功能和服務(wù)隱藏在這個(gè)統(tǒng)一界面下。客戶級(jí)程序選擇需要的驅(qū)動(dòng)程序或服務(wù)(其實(shí)就是選擇/dev/目錄下的文件),按照上述界面和文件操作流程,就可以跟內(nèi)核中的驅(qū)動(dòng)交互了。其實(shí)用面向?qū)ο蟮母拍顣?huì)更容易解釋,系統(tǒng)定義了一個(gè)抽象的界面(abstract interface),每個(gè)具體的驅(qū)動(dòng)程序都是這個(gè)界面的實(shí)現(xiàn)(implementation)。
所以驅(qū)動(dòng)程序也是用戶空間和內(nèi)核信息交互的重要方式之一。其實(shí)ioctl, read, write本質(zhì)上講也是通過(guò)系統(tǒng)調(diào)用去完成的,只是這些調(diào)用已被內(nèi)核進(jìn)行了標(biāo)準(zhǔn)封裝,統(tǒng)一定義。因此用戶不必像填加新系統(tǒng)調(diào)用那樣必須修改內(nèi)核代碼,重新編譯新內(nèi)核,使用虛擬設(shè)備只需要通過(guò)模塊方法將新的虛擬設(shè)備安裝到內(nèi)核中(insmod上)就能方便使用。關(guān)于此方面設(shè)計(jì)細(xì)節(jié)請(qǐng)查閱參考資料5,編程細(xì)節(jié)請(qǐng)查閱參考資料6。
在linux中,設(shè)備大致可分為:字符設(shè)備,塊設(shè)備,和網(wǎng)絡(luò)接口(字符設(shè)備包括那些必須以順序方式,像字節(jié)流一樣被訪問(wèn)的設(shè)備;如字符終端,串口等。塊設(shè)備是指那些可以用隨機(jī)方式,以整塊數(shù)據(jù)為單位來(lái)訪問(wèn)的設(shè)備,如硬盤等;網(wǎng)絡(luò)接口,就指通常網(wǎng)卡和協(xié)議棧等復(fù)雜的網(wǎng)絡(luò)輸入輸出服務(wù))。如果將我們的系統(tǒng)調(diào)用日志系統(tǒng)用字符型驅(qū)動(dòng)程序的方式實(shí)現(xiàn),也是一件輕松愜意地工作。我們可以將內(nèi)核中收集和記錄信息的那一部分編寫成一個(gè)字符設(shè)備驅(qū)動(dòng)程序。雖然沒(méi)有實(shí)際對(duì)應(yīng)的物理設(shè)備,但這并沒(méi)什么問(wèn)題:Linux的設(shè)備驅(qū)動(dòng)程序本來(lái)就是一個(gè)軟件抽象,它可以結(jié)合硬件提供服務(wù),也完全可以作為純軟件提供服務(wù)(當(dāng)然,內(nèi)存的使用我們是無(wú)法避免的)。在驅(qū)動(dòng)程序中,我們可以用open來(lái)啟動(dòng)服務(wù),用read()返回處理好的記錄,用ioctl()設(shè)置記錄格式等,用close()停止服務(wù),write()沒(méi)有用到,那么我們可以不去實(shí)現(xiàn)它。然后在/dev/目錄下建立一個(gè)設(shè)備文件對(duì)應(yīng)我們新加入內(nèi)核的系統(tǒng)調(diào)用日志系統(tǒng)驅(qū)動(dòng)程序。
(3)?使用proc?文件系統(tǒng)proc是Linux提供的一種特殊的文件系統(tǒng),推出它的目的就是提供一種便捷的用戶和內(nèi)核間的交互方式。它以文件系統(tǒng)作為使用界面,使應(yīng)用程序可以以文件操作的方式安全、方便的獲取系統(tǒng)當(dāng)前運(yùn)行的狀態(tài)和其它一些內(nèi)核數(shù)據(jù)信息。
proc文件系統(tǒng)多用于監(jiān)視、管理和調(diào)試系統(tǒng),我們使用的很多管理工具如ps,top等,都是利用proc來(lái)讀取內(nèi)核信息的。除了讀取內(nèi)核信息,proc文件系統(tǒng)還提供了寫入功能。所以我們也就可以利用它來(lái)向內(nèi)核輸入信息。比如,通過(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),我們就可以讓運(yùn)行中的Linux系統(tǒng)啟用路由功能。類似的,還有許多內(nèi)核選項(xiàng)可以直接通過(guò)proc文件系統(tǒng)進(jìn)行查詢和調(diào)整。
除了系統(tǒng)已經(jīng)提供的文件條目,proc還為我們留有接口,允許我們?cè)趦?nèi)核中創(chuàng)建新的條目從而與用戶程序共享信息數(shù)據(jù)。比如,我們可以為系統(tǒng)調(diào)用日志程序(不管是作為驅(qū)動(dòng)程序也好,還是作為單純的內(nèi)核模塊也好)在proc文件系統(tǒng)中創(chuàng)建新的文件條目,在此條目中顯示系統(tǒng)調(diào)用的使用次數(shù),每個(gè)單獨(dú)系統(tǒng)調(diào)用的使用頻率等等。我們也可以增加另外的條目,用于設(shè)置日志記錄規(guī)則,比如說(shuō)不記錄open系統(tǒng)調(diào)用的使用情況等。關(guān)于proc文件系統(tǒng)得使用細(xì)節(jié),請(qǐng)查閱參考資料7。
(4)使用虛擬文件系統(tǒng)有些內(nèi)核開(kāi)發(fā)者認(rèn)為利用ioctl()系統(tǒng)調(diào)用往往會(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ù)更家方便、有效。
我們舉例來(lái)說(shuō)如何通過(guò)虛擬文件系統(tǒng)修改內(nèi)核信息。我們可以實(shí)現(xiàn)一個(gè)名為sagafs的虛擬文件系統(tǒng),其中文件log對(duì)應(yīng)內(nèi)核存儲(chǔ)的系統(tǒng)調(diào)用日志。我們可以通過(guò)文件訪問(wèn)特普遍方法獲得日志信息:如
# cat /sagafs/log
使用虛擬文件系統(tǒng)——VFS實(shí)現(xiàn)信息交互使得系統(tǒng)管理更加方便、清晰。但有些編程者也許會(huì)說(shuō)VFS?的API?接口復(fù)雜不容易掌握,不要擔(dān)心2.5內(nèi)核開(kāi)始就提供了一種叫做libfs的例程序幫助不熟悉文件系統(tǒng)的用戶封裝了實(shí)現(xiàn)VFS的通用操作。有關(guān)利用VFS實(shí)現(xiàn)交互的方法看參考資料。
(5)?使用內(nèi)存映像Linux通過(guò)內(nèi)存映像機(jī)制來(lái)提供用戶程序?qū)?nèi)存直接訪問(wèn)的能力。內(nèi)存映像的意思是把內(nèi)核中特定部分的內(nèi)存空間映射到用戶級(jí)程序的內(nèi)存空間去。也就是說(shuō),用戶空間和內(nèi)核空間共享一塊相同的內(nèi)存。這樣做的直觀效果顯而易見(jiàn):內(nèi)核在這塊地址內(nèi)存儲(chǔ)變更的任何數(shù)據(jù),用戶可以立即發(fā)現(xiàn)和使用,根本無(wú)須數(shù)據(jù)拷貝。而在使用系統(tǒng)調(diào)用交互信息時(shí),在整個(gè)操作過(guò)程中必須有一步數(shù)據(jù)拷貝的工作——或者是把內(nèi)核數(shù)據(jù)拷貝到用戶緩沖區(qū),或只是把用戶數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)——這對(duì)于許多數(shù)據(jù)傳輸量大、時(shí)間要求高的應(yīng)用,這無(wú)疑是致命的一擊:許多應(yīng)用根本就無(wú)法忍受數(shù)據(jù)拷貝所耗費(fèi)的時(shí)間和資源。
我們?cè)?jīng)為一塊高速采樣設(shè)備開(kāi)發(fā)過(guò)驅(qū)動(dòng)程序,該設(shè)備要求在20兆采樣率下以1KHz的重復(fù)頻率進(jìn)行16位實(shí)時(shí)采樣,每毫秒需要采樣、DMA和處理的數(shù)據(jù)量驚人,如果要使用數(shù)據(jù)拷貝的方法,根本無(wú)法達(dá)成要求。此時(shí),內(nèi)存映像成為唯一的選擇:我們?cè)趦?nèi)存中保留了一塊空間,將其配置成環(huán)形隊(duì)列供采樣設(shè)備DMA輸出數(shù)據(jù)。再把這塊內(nèi)存空間映射到在用戶空間運(yùn)行的數(shù)據(jù)處理程序上,于是,采樣設(shè)備剛剛得到并傳送到主機(jī)上的數(shù)據(jù),馬上就可以被用戶空間的程序處理。
實(shí)際上,內(nèi)存映射方式通常也正是應(yīng)用在那些內(nèi)核和用戶空間需要快速大量交互數(shù)據(jù)的情況下,特別是那些對(duì)實(shí)時(shí)性要求較強(qiáng)的應(yīng)用。X window系統(tǒng)的服務(wù)器的虛擬內(nèi)存區(qū)域,就可以被看做是內(nèi)存映像用法的一個(gè)典型例子:X服務(wù)器需要對(duì)視頻內(nèi)存進(jìn)行大量的數(shù)據(jù)交換,相對(duì)于lseek/write來(lái)說(shuō),將圖形顯示內(nèi)存直接映射到用戶空間可以顯著提高效能。
并不是任何類型的應(yīng)用都適合mmap,比如像串口和鼠標(biāo)這些基于流數(shù)據(jù)的字符設(shè)備,mmap就沒(méi)有太大的用武之地。并且,這種共享內(nèi)存的方式存在不好同步的問(wèn)題。由于沒(méi)有專門的同步機(jī)制可以讓用戶程序和內(nèi)核程序共享,所以在讀取和寫入數(shù)據(jù)時(shí)要有非常謹(jǐn)慎的設(shè)計(jì)以保證不會(huì)產(chǎn)生干繞。
mmap完全是基于共享內(nèi)存的觀念了,也正因?yàn)榇?#xff0c;它能提供額外的便利,但也特別難以控制。
由內(nèi)核主動(dòng)發(fā)起的信息交互在內(nèi)核發(fā)起的交互中,我們最關(guān)心和感興趣的應(yīng)該是內(nèi)核如何向用戶程序發(fā)消息,用戶程序又是怎樣接收這些消息的,具體問(wèn)題通常集中在下面這幾個(gè)方面:內(nèi)核可否調(diào)用用戶程序?是否可以通過(guò)向用戶進(jìn)程發(fā)信號(hào)來(lái)告知用戶進(jìn)程事件發(fā)生?
前面介紹的交互方法最大的不同在于這些方式是由內(nèi)核采取主動(dòng),而不是等系統(tǒng)調(diào)用來(lái)被動(dòng)的返回信息的。
(1) 從內(nèi)核空間調(diào)用用戶程序。即使在內(nèi)核中,我們有時(shí)也需要執(zhí)行一些在用戶級(jí)才提供的操作:如打開(kāi)某個(gè)文件以讀取特定數(shù)據(jù),執(zhí)行某個(gè)用戶程序從而完成某個(gè)功能。因?yàn)樵S多數(shù)據(jù)和功能在用戶空間是現(xiàn)有的或者已經(jīng)被實(shí)現(xiàn)了,那么沒(méi)有必要耗費(fèi)大量的資源去重復(fù)。此外,內(nèi)核在設(shè)計(jì)時(shí),為了擁有更好的彈性或者性能以支持未知但有可能發(fā)生的變化,本身就要求使用用戶空間的資源來(lái)配合完成任務(wù)。比如內(nèi)核中動(dòng)態(tài)加載模塊的部分需要調(diào)用kmod。但在編譯kmod的時(shí)候不可能把所有的內(nèi)核模塊都訂下來(lái)(要是這樣的話動(dòng)態(tài)加載模塊就沒(méi)有存在意義了),所以它不可能知道在它以后才出現(xiàn)的那些模塊的位置和加載方法。因此,模塊的動(dòng)態(tài)加載就采用了如下策略:加載任務(wù)實(shí)際上由位于用戶空間的modprobe程序幫助完成——最簡(jiǎn)單的情形是modprobe用內(nèi)核傳過(guò)來(lái)的模塊名字作為參數(shù)調(diào)用insmod。用這種方法來(lái)加載所需要的模塊。
內(nèi)核中啟動(dòng)用戶程序還是要通過(guò)execve這個(gè)系統(tǒng)調(diào)用原形,只是此時(shí)的調(diào)用發(fā)生在內(nèi)核空間,而一般的系統(tǒng)調(diào)用則在用戶空間進(jìn)行。如果系統(tǒng)調(diào)用帶參數(shù),那將會(huì)碰到一個(gè)問(wèn)題:因?yàn)樵谙到y(tǒng)調(diào)用的具體實(shí)現(xiàn)代碼中要檢查參數(shù)合法性,該檢查要求所有的參數(shù)必須位于用戶空間——地址處于0x0000000——0xC0000000之間,所以如果我們從內(nèi)核傳遞參數(shù)(地址大于0xC0000000),那么檢查就會(huì)拒絕我們的調(diào)用請(qǐng)求。為了解決這個(gè)問(wèn)題,我們可以利用set_fs宏來(lái)修改檢查策略,使得允許參數(shù)地址為內(nèi)核地址。這樣內(nèi)核就可以直接使用該系統(tǒng)調(diào)用了。
例如:在kmod通過(guò)調(diào)用execve來(lái)執(zhí)行modprobe的代碼前需要有set_fs(KERNEL_DS):
......
set_fs(KERNEL_DS);
if (execve(program_path,?argv,?envp) < 0)
return -errno;
上述代碼中program_path?為"/sbin/modprobe",argv為{?modprobe_path, "-s", "-k", "--", (char*)module_name, NULL },envp為{ "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL }。
從內(nèi)核中打開(kāi)文件同樣使用帶參數(shù)的open系統(tǒng)調(diào)用,所需的仍是要先調(diào)用set_fs宏。
?
(2)?利用brk系統(tǒng)調(diào)用來(lái)導(dǎo)出內(nèi)核數(shù)據(jù)內(nèi)核和用戶空間傳遞數(shù)據(jù)主要是用get_user(ptr)和put_user(datum,ptr)例程。所以在大部分需要傳遞數(shù)據(jù)的系統(tǒng)調(diào)用中都可以找到它們的身影。可是,如果我們不是通過(guò)用戶程序發(fā)起的系統(tǒng)調(diào)用——也就是說(shuō),沒(méi)有明確的提供用戶空間內(nèi)的緩沖區(qū)位置——的情況下,如何向用戶空間傳遞內(nèi)核數(shù)據(jù)呢?
顯然,我們不能再直接使用put_user()了,因?yàn)槲覀儧](méi)有辦法給它指定目的緩沖區(qū)。所以,我們要借用brk系統(tǒng)調(diào)用和當(dāng)前進(jìn)程空間:brk用于給進(jìn)程設(shè)置堆空間的大小。每個(gè)進(jìn)程擁有一個(gè)獨(dú)立的堆空間,malloc等動(dòng)態(tài)內(nèi)存分配函數(shù)其實(shí)就是進(jìn)程的堆空間中獲取內(nèi)存的。我們將利用brk在當(dāng)前進(jìn)程(current process)的堆空間上擴(kuò)展一塊新的臨時(shí)緩沖區(qū),再用put_user將內(nèi)核數(shù)據(jù)導(dǎo)出到這個(gè)確定的用戶空間去。
還記得剛才我們?cè)趦?nèi)核中調(diào)用用戶程序的過(guò)程嗎?在那里,我們有一個(gè)跳過(guò)參數(shù)檢查的操作,現(xiàn)在有了這種方法,可以另辟蹊徑了:我們?cè)诋?dāng)前進(jìn)程的堆上擴(kuò)展一塊空間,把系統(tǒng)調(diào)用要用到的參數(shù)通過(guò)put_user()拷貝到新擴(kuò)展得到的用戶空間里,然后在調(diào)用execve的時(shí)候以這個(gè)新開(kāi)辟空間地址作為參數(shù),于是,參數(shù)檢查的障礙不復(fù)存在了。
char *?program_path?= "/bin/ls" ;
?
mmm=current->mm->brk;
ret =?brk(*(void)(mmm+256));
put_user((void*)2,program_path,strlen(program_path)+1);
?
execve((char*)(mmm+2));
tmp?=?brk((void*)mmm);
這種方法沒(méi)有一般性(具體的說(shuō),這種方法有負(fù)面效應(yīng)嗎),只能作為一種技巧,但我們不難發(fā)現(xiàn):如果你熟悉內(nèi)核結(jié)構(gòu),就可以做到很多意想不到的事情!
(3)?使用信號(hào)信號(hào)在內(nèi)核里的用途主要集中在通知用戶程序出現(xiàn)重大錯(cuò)誤,強(qiáng)行殺死當(dāng)前進(jìn)程,這時(shí)內(nèi)核通過(guò)發(fā)送SIGKILL信號(hào)通知進(jìn)程終止,內(nèi)核發(fā)送信號(hào)使用send_sign(pid,sig)例程,可以看到信號(hào)發(fā)送必須要事先知道進(jìn)程序號(hào)(pid),所以要想從內(nèi)核中通過(guò)發(fā)信號(hào)的方式異步通知用戶進(jìn)程執(zhí)行某項(xiàng)任務(wù),那么必須事先知道用戶進(jìn)程的進(jìn)程號(hào)才可。而內(nèi)核運(yùn)行時(shí)搜索到特定進(jìn)程的進(jìn)程號(hào)是個(gè)費(fèi)事的工作,可能要遍歷整個(gè)進(jìn)程控制塊鏈表。所以用信號(hào)通知特定用戶進(jìn)程的方法很糟糕,一般在內(nèi)核不會(huì)使用。內(nèi)核中使用信號(hào)的情形只出現(xiàn)在通知當(dāng)前進(jìn)程(可以從current變量中方便獲得pid)做某些通用操作,如終止操作等。因此對(duì)內(nèi)核開(kāi)發(fā)者該方法用處不大。
類似情況還有消息操作。這里不羅嗦了。
?
總結(jié)??由用戶級(jí)程序主動(dòng)發(fā)起的信息交互,無(wú)論是采用標(biāo)準(zhǔn)的調(diào)用方式還是透過(guò)驅(qū)動(dòng)程序界面,一般都要用到系統(tǒng)調(diào)用。而由內(nèi)核主動(dòng)發(fā)起信息交互的情況不多。也沒(méi)有標(biāo)準(zhǔn)的界面,操作大不方便。所以一般情況下,盡可能用本文描述的前幾種方法進(jìn)行信息交互。畢竟,在設(shè)計(jì)的根源上,相對(duì)于客戶級(jí)程序,內(nèi)核就被定義為一個(gè)被動(dòng)的服務(wù)提供者。因此,我們自己的開(kāi)發(fā)也應(yīng)該盡量遵循這種設(shè)計(jì)原則。
總結(jié)
以上是生活随笔為你收集整理的linux内核空间和用户空间认识和区别的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【人工智能】机器学习西瓜书11——经验误
- 下一篇: linux 进程带宽限制,Linux限制