成人性生交大片免费看视频r_亚洲综合极品香蕉久久网_在线视频免费观看一区_亚洲精品亚洲人成人网在线播放_国产精品毛片av_久久久久国产精品www_亚洲国产一区二区三区在线播_日韩一区二区三区四区区区_亚洲精品国产无套在线观_国产免费www

主頁 > 知識庫 > Linux內(nèi)核模塊編寫詳解

Linux內(nèi)核模塊編寫詳解

熱門標簽:萊蕪移動外呼系統(tǒng) 小語股票電銷機器人 柯城手機地圖如何做地圖標注 高德地圖標注在電腦上 襄陽地圖標注店 杭州電銷機器人有效果嗎 申請400電話流程好嗎 軟件電話機器人 金華呼叫中心外呼系統(tǒng)廠家

內(nèi)核編程常??雌饋硐袷呛谀Хǎ趤喩?C 克拉克的眼中,它八成就是了。Linux內(nèi)核和它的用戶空間是大不相同的:拋開漫不經(jīng)心,你必須小心翼翼,因為你編程中的一個bug就會影響到整個系統(tǒng)。浮點運算做起來可不容易,堆棧固定而狹小,而你寫的代碼總是異步的,因此你需要想想并發(fā)會導致什么。而除了所有這一切之外,Linux內(nèi)核只是一個很大的、很復雜的C程序,它對每個人開放,任何人都去讀它、學習它并改進它,而你也可以是其中之一。


學習內(nèi)核編程的最簡單的方式也許就是寫個內(nèi)核模塊:一段可以動態(tài)加載進內(nèi)核的代碼。模塊所能做的事是有限的——例如,他們不能在類似進程描述符這樣的公共數(shù)據(jù)結(jié)構(gòu)中增減字段(LCTT譯注:可能會破壞整個內(nèi)核及系統(tǒng)的功能)。但是,在其它方面,他們是成熟的內(nèi)核級的代碼,可以在需要時隨時編譯進內(nèi)核(這樣就可以摒棄所有的限制了)。完全可以在Linux源代碼樹以外來開發(fā)并編譯一個模塊(這并不奇怪,它稱為樹外開發(fā)),如果你只是想稍微玩玩,而并不想提交修改以包含到主線內(nèi)核中去,這樣的方式是很方便的。

在本教程中,我們將開發(fā)一個簡單的內(nèi)核模塊用以創(chuàng)建一個/dev/reverse設(shè)備。寫入該設(shè)備的字符串將以相反字序的方式讀回(“Hello World”讀成“World Hello”)。這是一個很受歡迎的程序員面試難題,當你利用自己的能力在內(nèi)核級別實現(xiàn)這個功能時,可以使你得到一些加分。在開始前,有一句忠告:你的模塊中的一個bug就會導致系統(tǒng)崩潰(雖然可能性不大,但還是有可能的)和數(shù)據(jù)丟失。在開始前,請確保你已經(jīng)將重要數(shù)據(jù)備份,或者,采用一種更好的方式,在虛擬機中進行試驗。

盡可能不要用root身份

默認情況下,/dev/reverse只有root可以使用,因此你只能使用sudo來運行你的測試程序。要解決該限制,可以創(chuàng)建一個包含以下內(nèi)容的/lib/udev/rules.d/99-reverse.rules文件:

SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"
別忘了重新插入模塊。讓非root用戶訪問設(shè)備節(jié)點往往不是一個好主意,但是在開發(fā)其間卻是十分有用的。這并不是說以root身份運行二進制測試文件也不是個好主意。
模塊的構(gòu)造

由于大多數(shù)的Linux內(nèi)核模塊是用C寫的(除了底層的特定于體系結(jié)構(gòu)的部分),所以推薦你將你的模塊以單一文件形式保存(例如,reverse.c)。我們已經(jīng)把完整的源代碼放在GitHub上——這里我們將看其中的一些片段。開始時,我們先要包含一些常見的文件頭,并用預定義的宏來描述模塊:

這里一切都直接明了,除了MODULE_LICENSE():它不僅僅是一個標記。內(nèi)核堅定地支持GPL兼容代碼,因此如果你把許可證設(shè)置為其它非GPL兼容的(如,“Proprietary”[專利]),某些特定的內(nèi)核功能將在你的模塊中不可用。

bash/shell Code復制內(nèi)容到剪貼板
  1. #include linux/init.h>   
  2. #include linux/kernel.h>   
  3. #include linux/module.h>   
  4. MODULE_LICENSE("GPL");   
  5. MODULE_AUTHOR("Valentine Sinitsyn valentine.sinitsyn@gmail.com>");   
  6. MODULE_DESCRIPTION("In-kernel phrase reverser");   

什么時候不該寫內(nèi)核模塊

內(nèi)核編程很有趣,但是在現(xiàn)實項目中寫(尤其是調(diào)試)內(nèi)核代碼要求特定的技巧。通常來講,在沒有其它方式可以解決你的問題時,你才應(yīng)該在內(nèi)核級別解決它。以下情形中,可能你在用戶空間中解決它更好:

你要開發(fā)一個USB驅(qū)動 —— 請查看libusb。
你要開發(fā)一個文件系統(tǒng) —— 試試FUSE。
你在擴展Netfilter —— 那么libnetfilter_queue對你有所幫助。
通常,內(nèi)核里面代碼的性能會更好,但是對于許多項目而言,這點性能丟失并不嚴重。
由于內(nèi)核編程總是異步的,沒有一個main()函數(shù)來讓Linux順序執(zhí)行你的模塊。取而代之的是,你要為各種事件提供回調(diào)函數(shù),像這個:

bash/shell Code復制內(nèi)容到剪貼板
  1. static int __init reverse_init(void)   
  2. {   
  3.     printk(KERN_INFO "reverse device has been registered\n");   
  4.     return 0;   
  5. }   
  6. static void __exit reverse_exit(void)   
  7. {   
  8.     printk(KERN_INFO "reverse device has been unregistered\n");   
  9. }   
  10. module_init(reverse_init);   
  11. module_exit(reverse_exit);  

這里,我們定義的函數(shù)被稱為模塊的插入和刪除。只有第一個的插入函數(shù)是必要的。目前,它們只是打印消息到內(nèi)核環(huán)緩沖區(qū)(可以在用戶空間通過dmesg命令訪問);KERN_INFO是日志級別(注意,沒有逗號)。__init和__exit是屬性 —— 聯(lián)結(jié)到函數(shù)(或者變量)的元數(shù)據(jù)片。屬性在用戶空間的C代碼中是很罕見的,但是內(nèi)核中卻很普遍。所有標記為__init的,會在初始化后釋放內(nèi)存以供重用(還記得那條過去內(nèi)核的那條“Freeing unused kernel memory…[釋放未使用的內(nèi)核內(nèi)存……]”信息嗎?)。__exit表明,當代碼被靜態(tài)構(gòu)建進內(nèi)核時,該函數(shù)可以安全地優(yōu)化了,不需要清理收尾。最后,module_init()和module_exit()這兩個宏將reverse_init()和reverse_exit()函數(shù)設(shè)置成為我們模塊的生命周期回調(diào)函數(shù)。實際的函數(shù)名稱并不重要,你可以稱它們?yōu)閕nit()和exit(),或者start()和stop(),你想叫什么就叫什么吧。他們都是靜態(tài)聲明,你在外部模塊是看不到的。事實上,內(nèi)核中的任何函數(shù)都是不可見的,除非明確地被導出。然而,在內(nèi)核程序員中,給你的函數(shù)加上模塊名前綴是約定俗成的。

這些都是些基本概念 – 讓我們來做更多有趣的事情吧。模塊可以接收參數(shù),就像這樣:

# modprobe foo bar=1

modinfo命令顯示了模塊接受的所有參數(shù),而這些也可以在/sys/module//parameters下作為文件使用。我們的模塊需要一個緩沖區(qū)來存儲參數(shù) —— 讓我們把這大小設(shè)置為用戶可配置。在MODULE_DESCRIPTION()下添加如下三行:

bash/shell Code復制內(nèi)容到剪貼板
  1. static unsigned long buffer_size = 8192;   
  2. module_param(buffer_size, ulong, (S_IRUSR | S_IRGRP | S_IROTH));   
  3. MODULE_PARM_DESC(buffer_size, "Internal buffer size");  

這兒,我們定義了一個變量來存儲該值,封裝成一個參數(shù),并通過sysfs來讓所有人可讀。這個參數(shù)的描述(最后一行)出現(xiàn)在modinfo的輸出中。

由于用戶可以直接設(shè)置buffer_size,我們需要在reverse_init()來清除無效取值。你總該檢查來自內(nèi)核之外的數(shù)據(jù) —— 如果你不這么做,你就是將自己置身于內(nèi)核異?;虬踩┒粗?。

bash/shell Code復制內(nèi)容到剪貼板
  1. static int __init reverse_init()   
  2. {   
  3.     if (!buffer_size)   
  4.         return -1;   
  5.     printk(KERN_INFO   
  6.         "reverse device has been registered, buffer size is %lu bytes\n",   
  7.         buffer_size);   
  8.     return 0;   
  9. }  

來自模塊初始化函數(shù)的非0返回值意味著模塊執(zhí)行失敗。

導航

但你開發(fā)模塊時,Linux內(nèi)核就是你所需一切的源頭。然而,它相當大,你可能在查找你所要的內(nèi)容時會有困難。幸運的是,在龐大的代碼庫面前,有許多工具使這個過程變得簡單。首先,是Cscope —— 在終端中運行的一個比較經(jīng)典的工具。你所要做的,就是在內(nèi)核源代碼的頂級目錄中運行make cscope cscope。Cscope和Vim以及Emacs整合得很好,因此你可以在你最喜愛的編輯器中使用它。

如果基于終端的工具不是你的最愛,那么就訪問http://lxr.free-electrons.com吧。它是一個基于web的內(nèi)核導航工具,即使它的功能沒有Cscope來得多(例如,你不能方便地找到函數(shù)的用法),但它仍然提供了足夠多的快速查詢功能。
現(xiàn)在是時候來編譯模塊了。你需要你正在運行的內(nèi)核版本頭文件(linux-headers,或者等同的軟件包)和build-essential(或者類似的包)。接下來,該創(chuàng)建一個標準的Makefile模板:

bash/shell Code復制內(nèi)容到剪貼板
  1. obj-m += reverse.o   
  2. all:   
  3.     make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules   
  4. clean:   
  5.     make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean  

現(xiàn)在,調(diào)用make來構(gòu)建你的第一個模塊。如果你輸入的都正確,在當前目錄內(nèi)會找到reverse.ko文件。使用sudo insmod reverse.ko插入內(nèi)核模塊,然后運行如下命令:

bash/shell Code復制內(nèi)容到剪貼板
  1. $ dmesg | tail -1   
  2. [ 5905.042081] reverse device has been registered, buffer size is 8192 bytes  

恭喜了!然而,目前這一行還只是假象而已 —— 還沒有設(shè)備節(jié)點呢。讓我們來搞定它。

混雜設(shè)備

在Linux中,有一種特殊的字符設(shè)備類型,叫做“混雜設(shè)備”(或者簡稱為“misc”)。它是專為單一接入點的小型設(shè)備驅(qū)動而設(shè)計的,而這正是我們所需要的。所有混雜設(shè)備共享同一個主設(shè)備號(10),因此一個驅(qū)動(drivers/char/misc.c)就可以查看它們所有設(shè)備了,而這些設(shè)備用次設(shè)備號來區(qū)分。從其他意義來說,它們只是普通字符設(shè)備。

要為該設(shè)備注冊一個次設(shè)備號(以及一個接入點),你需要聲明struct misc_device,填上所有字段(注意語法),然后使用指向該結(jié)構(gòu)的指針作為參數(shù)來調(diào)用misc_register()。為此,你也需要包含linux/miscdevice.h頭文件:

bash/shell Code復制內(nèi)容到剪貼板
  1. static struct miscdevice reverse_misc_device = {   
  2.     .minor = MISC_DYNAMIC_MINOR,   
  3.     .name = "reverse",   
  4.     .fops = reverse_fops   
  5. };   
  6. static int __init reverse_init()   
  7. {   
  8.     ...   
  9.     misc_register(reverse_misc_device);   
  10.     printk(KERN_INFO ...   
  11. }   
  12.   

這兒,我們?yōu)槊麨?ldquo;reverse”的設(shè)備請求一個第一個可用的(動態(tài)的)次設(shè)備號;省略號表明我們之前已經(jīng)見過的省略的代碼。別忘了在模塊卸下后注銷掉該設(shè)備。

bash/shell Code復制內(nèi)容到剪貼板
  1. static void __exit reverse_exit(void)   
  2. {   
  3.     misc_deregister(reverse_misc_device);   
  4.     ...   
  5. }   
  6.   

‘fops’字段存儲了一個指針,指向一個file_operations結(jié)構(gòu)(在Linux/fs.h中聲明),而這正是我們模塊的接入點。reverse_fops定義如下:

bash/shell Code復制內(nèi)容到剪貼板
  1. static struct file_operations reverse_fops = {   
  2.     .owner = THIS_MODULE,   
  3.     .open = reverse_open,   
  4.     ...   
  5.     .llseek = noop_llseek   
  6. };  

另外,reverse_fops包含了一系列回調(diào)函數(shù)(也稱之為方法),當用戶空間代碼打開一個設(shè)備,讀寫或者關(guān)閉文件描述符時,就會執(zhí)行。如果你要忽略這些回調(diào),可以指定一個明確的回調(diào)函數(shù)來替代。這就是為什么我們將llseek設(shè)置為noop_llseek(),(顧名思義)它什么都不干。這個默認實現(xiàn)改變了一個文件指針,而且我們現(xiàn)在并不需要我們的設(shè)備可以尋址(這是今天留給你們的家庭作業(yè))。

關(guān)閉和打開

讓我們來實現(xiàn)該方法。我們將給每個打開的文件描述符分配一個新的緩沖區(qū),并在它關(guān)閉時釋放。這實際上并不安全:如果一個用戶空間應(yīng)用程序泄漏了描述符(也許是故意的),它就會霸占RAM,并導致系統(tǒng)不可用。在現(xiàn)實世界中,你總得考慮到這些可能性。但在本教程中,這種方法不要緊。

我們需要一個結(jié)構(gòu)函數(shù)來描述緩沖區(qū)。內(nèi)核提供了許多常規(guī)的數(shù)據(jù)結(jié)構(gòu):鏈接列表(雙聯(lián)的),哈希表,樹等等之類。不過,緩沖區(qū)常常從頭設(shè)計。我們將調(diào)用我們的“struct buffer”:

bash/shell Code復制內(nèi)容到剪貼板
  1. struct buffer {   
  2.     char *data, *end, *read_ptr;   
  3.     unsigned long size;   
  4. };  

data是該緩沖區(qū)存儲的一個指向字符串的指針,而end指向字符串結(jié)尾后的第一個字節(jié)。read_ptr是read()開始讀取數(shù)據(jù)的地方。緩沖區(qū)的size是為了保證完整性而存儲的 —— 目前,我們還沒有使用該區(qū)域。你不能假設(shè)使用你結(jié)構(gòu)體的用戶會正確地初始化所有這些東西,所以最好在函數(shù)中封裝緩沖區(qū)的分配和收回。它們通常命名為buffer_alloc()和buffer_free()。

bash/shell Code復制內(nèi)容到剪貼板
  1. static struct buffer buffer_alloc(unsigned long size)    
  2. { struct buffer *buf; buf = kzalloc(sizeof(buf), GFP_KERNEL);   
  3.  if    
  4. (unlikely(!buf)) goto out; … out: return buf;    
  5. }  

內(nèi)核內(nèi)存使用kmalloc()來分配,并使用kfree()來釋放;kzalloc()的風格是將內(nèi)存設(shè)置為全零。不同于標準的malloc(),它的內(nèi)核對應(yīng)部分收到的標志指定了第二個參數(shù)中請求的內(nèi)存類型。這里,GFP_KERNEL是說我們需要一個普通的內(nèi)核內(nèi)存(不是在DMA或高內(nèi)存區(qū)中)以及如果需要的話函數(shù)可以睡眠(重新調(diào)度進程)。sizeof(*buf)是一種常見的方式,它用來獲取可通過指針訪問的結(jié)構(gòu)體的大小。

你應(yīng)該隨時檢查kmalloc()的返回值:訪問NULL指針將導致內(nèi)核異常。同時也需要注意unlikely()宏的使用。它(及其相對宏likely())被廣泛用于內(nèi)核中,用于表明條件幾乎總是真的(或假的)。它不會影響到控制流程,但是能幫助現(xiàn)代處理器通過分支預測技術(shù)來提升性能。

最后,注意goto語句。它們常常為認為是邪惡的,但是,Linux內(nèi)核(以及一些其它系統(tǒng)軟件)采用它們來實施集中式的函數(shù)退出。這樣的結(jié)果是減少嵌套深度,使代碼更具可讀性,而且非常像更高級語言中的try-catch區(qū)塊。

有了buffer_alloc()和buffer_free(),open和close方法就變得很簡單了。

bash/shell Code復制內(nèi)容到剪貼板
  1. static int reverse_open(struct inode *inode, struct file *file)   
  2. {   
  3.     int err = 0;   
  4.     file->private_data = buffer_alloc(buffer_size);   
  5.     ...   
  6.     return err;   
  7. }   
  8.   

struct file是一個標準的內(nèi)核數(shù)據(jù)結(jié)構(gòu),用以存儲打開的文件的信息,如當前文件位置(file->f_pos)、標志(file->f_flags),或者打開模式(file->f_mode)等。另外一個字段file->privatedata用于關(guān)聯(lián)文件到一些專有數(shù)據(jù),它的類型是void *,而且它在文件擁有者以外,對內(nèi)核不透明。我們將一個緩沖區(qū)存儲在那里。

如果緩沖區(qū)分配失敗,我們通過返回否定值(-ENOMEM)來為調(diào)用的用戶空間代碼標明。一個C庫中調(diào)用的open(2)系統(tǒng)調(diào)用(如glibc)將會檢測這個并適當?shù)卦O(shè)置errno 。

學習如何讀和寫

“read”和“write”方法是真正完成工作的地方。當數(shù)據(jù)寫入到緩沖區(qū)時,我們放棄之前的內(nèi)容和反向地存儲該字段,不需要任何臨時存儲。read方法僅僅是從內(nèi)核緩沖區(qū)復制數(shù)據(jù)到用戶空間。但是如果緩沖區(qū)還沒有數(shù)據(jù),revers_eread()會做什么呢?在用戶空間中,read()調(diào)用會在有可用數(shù)據(jù)前阻塞它。在內(nèi)核中,你就必須等待。幸運的是,有一項機制用于處理這種情況,就是‘wait queues’。

想法很簡單。如果當前進程需要等待某個事件,它的描述符(struct task_struct存儲‘current’信息)被放進非可運行(睡眠中)狀態(tài),并添加到一個隊列中。然后schedule()就被調(diào)用來選擇另一個進程運行。生成事件的代碼通過使用隊列將等待進程放回TASK_RUNNING狀態(tài)來喚醒它們。調(diào)度程序?qū)⒃谝院笤谀硞€地方選擇它們之一。Linux有多種非可運行狀態(tài),最值得注意的是TASK_INTERRUPTIBLE(一個可以通過信號中斷的睡眠)和TASK_KILLABLE(一個可被殺死的睡眠中的進程)。所有這些都應(yīng)該正確處理,并等待隊列為你做這些事。

一個用以存儲讀取等待隊列頭的天然場所就是結(jié)構(gòu)緩沖區(qū),所以從為它添加wait_queue_headt read\queue字段開始。你也應(yīng)該包含linux/sched.h頭文件??梢允褂肈ECLARE_WAITQUEUE()宏來靜態(tài)聲明一個等待隊列。在我們的情況下,需要動態(tài)初始化,因此添加下面這行到buffer_alloc():

bash/shell Code復制內(nèi)容到剪貼板
  1. init_waitqueue_head(buf->read_queue);  

我們等待可用數(shù)據(jù);或者等待read_ptr != end條件成立。我們也想要讓等待操作可以被中斷(如,通過Ctrl+C)。因此,“read”方法應(yīng)該像這樣開始:

bash/shell Code復制內(nèi)容到剪貼板
  1. static ssize_t reverse_read(struct file *file, char __user * out,   
  2.         size_t size, loff_t * off)   
  3. {   
  4.     struct buffer *buf = file->private_data;   
  5.     ssize_t result;   
  6.     while (buf->read_ptr == buf->end) {   
  7.         if (file->f_flags  O_NONBLOCK) {   
  8.             result = -EAGAIN;   
  9.             goto out;   
  10.         }   
  11.         if (wait_event_interruptible   
  12.         (buf->read_queue, buf->read_ptr != buf->end)) {   
  13.             result = -ERESTARTSYS;   
  14.             goto out;   
  15.         }   
  16.     }   

...

我們讓它循環(huán),直到有可用數(shù)據(jù),如果沒有則使用wait_event_interruptible()(它是一個宏,不是函數(shù),這就是為什么要通過值的方式給隊列傳遞)來等待。好吧,如果wait_event_interruptible()被中斷,它返回一個非0值,這個值代表-ERESTARTSYS。這段代碼意味著系統(tǒng)調(diào)用應(yīng)該重新啟動。file->f_flags檢查以非阻塞模式打開的文件數(shù):如果沒有數(shù)據(jù),返回-EAGAIN。

我們不能使用if()來替代while(),因為可能有許多進程正等待數(shù)據(jù)。當write方法喚醒它們時,調(diào)度程序以不可預知的方式選擇一個來運行,因此,在這段代碼有機會執(zhí)行的時候,緩沖區(qū)可能再次空出。現(xiàn)在,我們需要將數(shù)據(jù)從buf->data 復制到用戶空間。copy_to_user()內(nèi)核函數(shù)就干了此事:

bash/shell Code復制內(nèi)容到剪貼板
  1. size = min(size, (size_t) (buf->end - buf->read_ptr));   
  2.    if (copy_to_user(out, buf->read_ptr, size)) {   
  3.        result = -EFAULT;   
  4.        goto out;   
  5.    }  

如果用戶空間指針錯誤,那么調(diào)用可能會失??;如果發(fā)生了此事,我們就返回-EFAULT。記住,不要相信任何來自內(nèi)核外的事物!

bash/shell Code復制內(nèi)容到剪貼板
  1.    buf->read_ptr += size;   
  2.     result = size;   
  3. out:   
  4.     return result;   
  5. }  

為了使數(shù)據(jù)在任意塊可讀,需要進行簡單運算。該方法返回讀入的字節(jié)數(shù),或者一個錯誤代碼。

寫方法更簡短。首先,我們檢查緩沖區(qū)是否有足夠的空間,然后我們使用copy_from_userspace()函數(shù)來獲取數(shù)據(jù)。再然后read_ptr和結(jié)束指針會被重置,并且反轉(zhuǎn)存儲緩沖區(qū)內(nèi)容:

bash/shell Code復制內(nèi)容到剪貼板
  1. buf->end = buf->data + size;   
  2.    buf->read_ptr = buf->data;   
  3.    if (buf->end > buf->data)   
  4.        reverse_phrase(buf->data, buf->end - 1);  

這里, reverse_phrase()干了所有吃力的工作。它依賴于reverse_word()函數(shù),該函數(shù)相當簡短并且標記為內(nèi)聯(lián)。這是另外一個常見的優(yōu)化;但是,你不能過度使用。因為過多的內(nèi)聯(lián)會導致內(nèi)核映像徒然增大。

最后,我們需要喚醒read_queue中等待數(shù)據(jù)的進程,就跟先前講過的那樣。wake_up_interruptible()就是用來干此事的:

bash/shell Code復制內(nèi)容到剪貼板
  1. wake_up_interruptible(buf->read_queue);  


耶!你現(xiàn)在已經(jīng)有了一個內(nèi)核模塊,它至少已經(jīng)編譯成功了?,F(xiàn)在,是時候來測試了。

調(diào)試內(nèi)核代碼

或許,內(nèi)核中最常見的調(diào)試方法就是打印。如果你愿意,你可以使用普通的printk() (假定使用KERN_DEBUG日志等級)。然而,那兒還有更好的辦法。如果你正在寫一個設(shè)備驅(qū)動,這個設(shè)備驅(qū)動有它自己的“struct device”,可以使用pr_debug()或者dev_dbg():它們支持動態(tài)調(diào)試(dyndbg)特性,并可以根據(jù)需要啟用或者禁用(請查閱Documentation/dynamic-debug-howto.txt)。對于單純的開發(fā)消息,使用pr_devel(),除非設(shè)置了DEBUG,否則什么都不會做。要為我們的模塊啟用DEBUG,請?zhí)砑右韵滦械組akefile中:

bash/shell Code復制內(nèi)容到剪貼板
  1. CFLAGS_reverse.o := -DDEBUG  

完了之后,使用dmesg來查看pr_debug()或pr_devel()生成的調(diào)試信息。 或者,你可以直接發(fā)送調(diào)試信息到控制臺。要想這么干,你可以設(shè)置console_loglevel內(nèi)核變量為8或者更大的值(echo 8 /proc/sys/kernel/printk),或者在高日志等級,如KERN_ERR,來臨時打印要查詢的調(diào)試信息。很自然,在發(fā)布代碼前,你應(yīng)該移除這樣的調(diào)試聲明。

注意內(nèi)核消息出現(xiàn)在控制臺,不要在Xterm這樣的終端模擬器窗口中去查看;這也是在內(nèi)核開發(fā)時,建議你不在X環(huán)境下進行的原因。
驚喜,驚喜!

編譯模塊,然后加載進內(nèi)核:

bash/shell Code復制內(nèi)容到剪貼板
  1. make  
  2. sudo insmod reverse.ko buffer_size=2048   
  3. $ lsmod   
  4. reverse 2419 0   
  5. ls -l /dev/reverse   
  6. crw-rw-rw- 1 root root 10, 58 Feb 22 15:53 /dev/reverse  

一切似乎就位。現(xiàn)在,要測試模塊是否正常工作,我們將寫一段小程序來翻轉(zhuǎn)它的第一個命令行參數(shù)。main()(再三檢查錯誤)可能看上去像這樣:

bash/shell Code復制內(nèi)容到剪貼板
  1. int fd = open("/dev/reverse", O_RDWR);   
  2. write(fd, argv[1], strlen(argv[1]));   
  3. read(fd, argv[1], strlen(argv[1]));   
  4. printf("Read: %s\n", argv[1]);  

像這樣運行:

bash/shell Code復制內(nèi)容到剪貼板
  1. $ ./test 'A quick brown fox jumped over the lazy dog'  
  2. Read: dog lazy the over jumped fox brown quick A  
    它工作正常!玩得更逗一點:試試傳遞單個單詞或者單個字母的短語,空的字符串或者是非英語字符串(如果你有這樣的鍵盤布局設(shè)置),以及其它任何東西。

現(xiàn)在,讓我們讓事情變得更好玩一點。我們將創(chuàng)建兩個進程,它們共享一個文件描述符(及其內(nèi)核緩沖區(qū))。其中一個會持續(xù)寫入字符串到設(shè)備,而另一個將讀取這些字符串。在下例中,我們使用了fork(2)系統(tǒng)調(diào)用,而pthreads也很好用。我也省略打開和關(guān)閉設(shè)備的代碼,并在此檢查代碼錯誤(又來了):

bash/shell Code復制內(nèi)容到剪貼板
  1. char *phrase = "A quick brown fox jumped over the lazy dog";   
  2. if (fork())   
  3.        
  4. /* Parent is the writer */   
  5.     while (1)   
  6.         write(fd, phrase, len);   
  7. else  
  8.        
  9. /* child is the reader */   
  10.     while (1) {   
  11.         read(fd, buf, len);   
  12.         printf("Read: %s\n", buf);   
  13. }   
  14.   

你希望這個程序會輸出什么呢?下面就是在我的筆記本上得到的東西:

Read: dog lazy the over jumped fox brown quick A
Read: A kcicq brown fox jumped over the lazy dog
Read: A kciuq nworb xor jumped fox brown quick A
Read: A kciuq nworb xor jumped fox brown quick A
...
這里發(fā)生了什么呢?就像舉行了一場比賽。我們認為read和write是原子操作,或者從頭到尾一次執(zhí)行一個指令。然而,內(nèi)核確實無序并發(fā)的,隨便就重新調(diào)度了reverse_phrase()函數(shù)內(nèi)部某個地方運行著的寫入操作的內(nèi)核部分。如果在寫入操作結(jié)束前就調(diào)度了read()操作呢?就會產(chǎn)生數(shù)據(jù)不完整的狀態(tài)。這樣的bug非常難以找到。但是,怎樣來處理這個問題呢?

基本上,我們需要確保在寫方法返回前沒有read方法能被執(zhí)行。如果你曾經(jīng)編寫過一個多線程的應(yīng)用程序,你可能見過同步原語(鎖),如互斥鎖或者信號。Linux也有這些,但有些細微的差別。內(nèi)核代碼可以運行進程上下文(用戶空間代碼的“代表”工作,就像我們使用的方法)和終端上下文(例如,一個IRQ處理線程)。如果你已經(jīng)在進程上下文中和并且你已經(jīng)得到了所需的鎖,你只需要簡單地睡眠和重試直到成功為止。在中斷上下文時你不能處于休眠狀態(tài),因此代碼會在一個循環(huán)中運行直到鎖可用。關(guān)聯(lián)原語被稱為自旋鎖,但在我們的環(huán)境中,一個簡單的互斥鎖 —— 在特定時間內(nèi)只有唯一一個進程能“占有”的對象 —— 就足夠了。處于性能方面的考慮,現(xiàn)實的代碼可能也會使用讀-寫信號。

鎖總是保護某些數(shù)據(jù)(在我們的環(huán)境中,是一個“struct buffer”實例),而且也常常會把它們嵌入到它們所保護的結(jié)構(gòu)體中。因此,我們添加一個互斥鎖(‘struct mutex lock’)到“struct buffer”中。我們也必須用mutex_init()來初始化互斥鎖;buffer_alloc是用來處理這件事的好地方。使用互斥鎖的代碼也必須包含linux/mutex.h。

互斥鎖很像交通信號燈 —— 要是司機不看它和不聽它的,它就沒什么用。因此,在對緩沖區(qū)做操作并在操作完成時釋放它之前,我們需要更新reverse_read()和reverse_write()來獲取互斥鎖。讓我們來看看read方法 —— write的工作原理相同:

bash/shell Code復制內(nèi)容到剪貼板
  1. static ssize_t reverse_read(struct file *file, char __user * out,   
  2.         size_t size, loff_t * off)   
  3. {   
  4.     struct buffer *buf = file->private_data;   
  5.     ssize_t result;   
  6.     if (mutex_lock_interruptible(buf->lock)) {   
  7.         result = -ERESTARTSYS;   
  8.         goto out;   
  9. }   
  10.   

我們在函數(shù)一開始就獲取鎖。mutex_lock_interruptible()要么得到互斥鎖然后返回,要么讓進程睡眠,直到有可用的互斥鎖。就像前面一樣,_interruptible后綴意味著睡眠可以由信號來中斷。

bash/shell Code復制內(nèi)容到剪貼板
  1. while (buf->read_ptr == buf->end) {   
  2.         mutex_unlock(buf->lock);   
  3. /* ... wait_event_interruptible() here ... */   
  4.         if (mutex_lock_interruptible(buf->lock)) {   
  5.             result = -ERESTARTSYS;   
  6.             goto out;   
  7.         }   
  8.     }  

下面是我們的“等待數(shù)據(jù)”循環(huán)。當獲取互斥鎖時,或者發(fā)生稱之為“死鎖”的情境時,不應(yīng)該讓進程睡眠。因此,如果沒有數(shù)據(jù),我們釋放互斥鎖并調(diào)用wait_event_interruptible()。當它返回時,我們重新獲取互斥鎖并像往常一樣繼續(xù):

bash/shell Code復制內(nèi)容到剪貼板
  1. if (copy_to_user(out, buf->read_ptr, size)) {   
  2.         result = -EFAULT;   
  3.         goto out_unlock;   
  4.     }   
  5.     ...   
  6. out_unlock:   
  7.     mutex_unlock(buf->lock);   
  8. out:   
  9.     return result;  

最后,當函數(shù)結(jié)束,或者在互斥鎖被獲取過程中發(fā)生錯誤時,互斥鎖被解鎖。重新編譯模塊(別忘了重新加載),然后再次進行測試?,F(xiàn)在你應(yīng)該沒發(fā)現(xiàn)毀壞的數(shù)據(jù)了。

接下來是什么?

現(xiàn)在你已經(jīng)嘗試了一次內(nèi)核黑客。我們剛剛為你揭開了這個話題的外衣,里面還有更多東西供你探索。我們的第一個模塊有意識地寫得簡單一點,在從中學到的概念在更復雜的環(huán)境中也一樣。并發(fā)、方法表、注冊回調(diào)函數(shù)、使進程睡眠以及喚醒進程,這些都是內(nèi)核黑客們耳熟能詳?shù)臇|西,而現(xiàn)在你已經(jīng)看過了它們的運作?;蛟S某天,你的內(nèi)核代碼也將被加入到主線Linux源代碼樹中 —— 如果真這樣,請聯(lián)系我們!

標簽:鶴壁 景德鎮(zhèn) 黔南 海北 天門 欽州 河南 威海

巨人網(wǎng)絡(luò)通訊聲明:本文標題《Linux內(nèi)核模塊編寫詳解》,本文關(guān)鍵詞  Linux,內(nèi)核,模塊,編寫,詳解,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Linux內(nèi)核模塊編寫詳解》相關(guān)的同類信息!
  • 本頁收集關(guān)于Linux內(nèi)核模塊編寫詳解的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    亚洲女同女同女同女同女同69| 亚洲一区二区三区精品动漫| 国产字幕中文| 国产欧美综合一区| 性人久久久久| 国产在线2020| 免费亚洲电影在线| 久久久精品国产一区二区三区| 国产欧美视频一区二区| av资源网在线播放| 3d成人动漫网站| 日韩高清在线观看一区二区| 国产精品一级二级三级| 中日韩免费毛片| 51国产成人精品午夜福中文下载| 免费拍拍拍网站| 九九热中文字幕| 欧美日韩国产精品成人| 蜜桃视频在线观看网站| 911精品美国片911久久久| 国产免费播放一区二区| 中文字幕不卡免费视频| 欧美做受xxxxxⅹ性视频| 欧美老熟妇喷水| 欧美日韩精品欧美日韩精品一综合| 亚洲精品国产精品乱码不99| 俺去了亚洲欧美日韩| 91在线视频免费观看| 国产一区二区免费电影| 91偷拍一区二区三区精品| 欧美日韩在线免费观看视频| 97久久夜色精品国产| 国产精品yjizz视频网一二区| 欧美一级日韩免费不卡| 国产一区二中文字幕在线看| 全彩无遮挡全彩口工漫画h#| 国产精品动漫网站| 欧美大奶一区二区| 成人免费不卡视频| 免费理论片在线观看播放老| 色开心亚洲综合| 26uuu精品一区二区三区四区在线| 国产精品69精品一区二区三区| www.成人在线.com| 男男互摸gay网站| 日本精品在线免费观看| 国产黄色片中文字幕| 在线观看天堂av| 免费毛片b在线观看| 欧美xxx黑人xxx水蜜桃| 日韩欧美在线国产| 成人影院久久久久久影院| 日韩欧美123| 高清欧美精品xxxxx在线看| 最新亚洲国产精品| 国产中文一区二区| 艳妇乳肉豪妇荡乳xxx| 女人在下体塞跳蛋在线观看| 久久精品蜜桃| 青娱乐在线免费视频| 久久99成人| 一区二区高清免费观看影视大全| 图片区小说区区亚洲五月| 欧美日韩一卡二卡| 91精品成人| 超碰在线电影| aaa在线视频| 国产精品第157页| 日韩欧美国产综合在线一区二区三区| 国产一二三在线观看| 国产精品色悠悠| 亚洲第一免费播放区| 动漫av免费观看| 2020久久国产精品| 91毛片在线观看| 亚洲日本在线观看视频| 两根大肉大捧一进一出好爽视频| 亚洲精品视频网上网址在线观看| 无码人妻精品一区二区中文| 天天操综合网| 国产成人综合精品在线| 欧美一区二区三区在线观看免费| jk破处视频在线| 亚洲成人久久久| 亚洲精品一线| 91亚洲精品久久久蜜桃借种| 老司机精品视频在线观看6| 一区二区三区影院| 在线看日韩av| 欧美精品一二三四区| 国产一级黄色| 国产一区二区视频免费观看| 亚洲精品18在线观看| 亚洲国产天堂久久国产91| 国产精品一区二区三区在线播放| 日韩美女视频中文字幕| 国产麻豆永久视频| 网站永久看片免费| 日日摸夜夜爽人人添av| 午夜精品视频在线| 99re6热在线精品视频播放| 深夜影院在线观看| 久久夜色精品国产亚洲aⅴ| 成人午夜精品无码区| 精品视频色一区| 国产成人综合自拍| 一本—道久久a久久精品蜜桃| 一区二区三区亚洲视频| 欧美一级成年大片在线观看| 激情五月婷婷综合| 天天色天天爽| 日韩高清av在线| 黄色片网站免费在线观看| 欧美色另类天堂2015| 91性感美女视频| 波多野结衣一区二区| 一区二区三区丝袜| 日韩免费电影一区二区三区| 欧美极度另类videos高清| 亚洲尤物在线视频观看| 国产色综合一区| 视频一区视频二区中文字幕| 国产女人在线观看| 午夜欧美巨大性欧美巨大| 欧美日在线观看| gogo大胆日本视频一区| 一区二区三区免费观看| 免费观看成年在线视频网站| 中文字幕一区日韩电影| 91精品国产丝袜白色高跟鞋| 亚洲第一精品久久忘忧草社区| 激情久久一区二区| 丁香花在线观看完整版电影| 九九精品在线| 国产精品私人自拍| 夜夜操天天干| 99久久精品费精品国产一区二区| 精品人妻少妇AV无码专区| 国产精品久久久久7777婷婷| 精品三区视频| 欧美黄色片在线观看| 成人自拍网站| 国产欧美激情| 五月婷婷综合激情网| 欧美一区二区三区| 91福利免费| 日本欧美色综合网站免费| 精品一卡二卡三卡| 日韩视频免费观看高清| 久久亚洲私人国产精品va媚药| 男男gay无套免费视频欧美| 国产免费a∨片在线观看不卡| 久久电影一区二区| 久久精品视频免费在线观看| 午夜精品中文字幕| 免费国产在线视频| 国产男女无套免费网站| 亚洲欧美春色| 日本成人免费在线观看| 日韩精品久久久久久久| 日韩福利在线播放| 国产视频一视频二| 国产精品av免费| 国产肉体xxxx裸体784大胆| 日本在线视频一区| 国产精品卡一卡二| 色香欲www7777综合网| 中国老女人av| 日本黄色免费在线| www.17c.com喷水少妇| 国产日韩v精品一区二区| 视频一区二区三区入口| jizzjizz视频| 国产在线看片免费视频在线观看| 欧美一区二区日韩一区二区| 国产日韩av在线播放| 日韩极品在线观看| 亚洲最新av网址| 人偷久久久久久久偷女厕| 中文字幕欧美人妻精品| 人妻无码中文久久久久专区| 免费国产黄线在线观看视频| 欧美白人最猛性xxxxx69交| 国产在线视频你懂| 一级视频在线免费观看| 精品久久国产97色综合| 在线观看午夜av| 你懂得在线视频| 久久精品国产亚洲5555| 波多野结衣家庭教师在线| 中文字幕国产在线| 91精品久久久久久久| 国产精品黄页免费高清在线观看| 欧美亚洲专区| 国产精品一区二区黑人巨大| 图片区亚洲欧美小说区| 五月天av在线| 先锋av资源色| 精品视频www| 91麻豆国产福利在线观看宅福利| 久久亚洲精品一区| 亚洲成人激情社区| 午夜精品一区二区三区免费视频| 国产欧美一区二区三区在线看| 国产精品一区二区三区四区在线观看| 最新亚洲国产| 成人午夜免费电影| 一区二区不卡久久精品| 四季av一区二区三区| 欧美精选一区二区三区| 激情欧美国产欧美| japanese国产精品| 亚洲精品久久久久久宅男| 欧美肥胖老妇做爰| 男女全黄做爰文章| 日韩欧美伦理电影院| a级女人18毛片| 久久久久成人精品| 国产日韩欧美综合在线| 日韩三级视频在线| 91嫩草丨国产丨精品| 国内外激情在线| 欧美精品色婷婷五月综合| 黑人操亚洲女人| 视频区小说区图片区| 国产一区二区三区免费视频| 久久久久久九九| 啊啊啊啊啊好爽| 国产亚洲视频中文字幕视频| 北条麻妃国产九九精品视频| 欧美黄色性视频| 欧美xxxxbb| 亚洲黄色视屏| 91在线观看高清| 欧美精品久久久久a| 亚洲三级免费| 日本精品一区二区三区不卡无字幕| 亚洲第一天堂久久| www在线观看黄色| 亚洲免费观看高清完整| 一区二区在线不卡| 91系列在线播放| 国内精品一区二区三区四区| 亚洲视频国产精品| 91在线观看地址| 羞羞在线观看网站| 91久久久亚洲精品| 91麻豆精品国产91久久久平台| 欧美亚洲国产成人精品| 日产国产精品精品a∨| 99免在线观看免费视频高清| 午夜精彩国产免费不卡不顿大片| 色综合亚洲精品激情狠狠| 国产真实乱偷精品视频免| 国产va免费精品观看精品| 欧美国产日韩xxxxx| 一区二区在线观看视频在线观看| 久久综合色婷婷| 成人黄色777网| 国产美女高潮一区二区三区| 91免费国产精品| 在线观看91精品国产麻豆| a级一a一级在线观看| 99精品国产91久久久久久| 国内高清免费在线视频| 久久www免费人成看片高清| 成人资源av| 亚洲精品一区二区三区av| 91精品国产综合久久婷婷香蕉| 国产成人无码a区在线观看视频| 国外成人在线视频| 99久久亚洲精品日本无码| 国产精品人人妻人人爽| 黄色的电影在线-骚虎影院-骚虎视频| 久久青草欧美一区二区三区| 国产精品手机在线| 手机在线理论电影| 日韩亚洲一区在线播放| 国产视频一区二区三区在线观看| 欧美大陆一区二区| 欧美美女性视频| 色婷婷精品久久二区二区密| 欧美日韩一区二区三区四区| 在线视频欧美精品| 欧洲一区二区三区在线| 天堂8在线视频| 日日噜噜夜夜狠狠视频| 欧美aa在线观看| 亚洲精品www久久久久久广东| 亚洲精品写真福利| 亚洲图区一区| 国产成+人+综合+亚洲欧美丁香花| 国产精品一二| 美女av在线免费看| 综合毛片免费视频| 你微笑时很美电视剧整集高清不卡| 午夜影院免费体验区| 欧美性猛交99久久久久99按摩| 激情综合色综合啪啪开心| 欧美综合久久| 黄色欧美成人| 肉色超薄丝袜脚交| 色喇叭免费久久综合网| 91精品黄色片免费大全| 欧美另类极品| 同性恋视频网站资源| 成人黄色小视频| 久久99久久久久| 亚洲精品老司机| 无套内谢丰满少妇中文字幕| 高清成人av| 丰满熟女人妻一区二区三区| 亚洲成人激情在线| 色呦呦在线资源| 国产亚洲综合久久| 国内精品伊人久久| 日韩精品欧美国产精品忘忧草| 国产精品**亚洲精品| 嫩草影院在线观看网站成人| 亚洲欧美高清| 九一免费在线观看| 高清视频国产| 538精品在线视频| 欧美日韩另类国产亚洲欧美一级| 麻豆精品免费视频入口| 中文字幕免费高清| 日韩欧美a级片| 后进极品白嫩翘臀在线视频|