歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> 黑客內核:編寫屬於你的第一個Linux內核模塊

黑客內核:編寫屬於你的第一個Linux內核模塊

日期:2017/3/1 9:42:40   编辑:Linux內核

曾經多少次想要在內核游蕩?曾經多少次茫然不知方向?你不要再對著它迷惘,讓我們指引你走向前方……

內核編程常常看起來像是黑魔法,而在亞瑟 C 克拉克的眼中,它八成就是了。Linux內核和它的用戶空間是大不相同的:拋開漫不經心,你必須小心翼翼,因為你編程中的一個bug就會影響到整個系統。浮點運算做起來可不容易,堆棧固定而狹小,而你寫的代碼總是異步的,因此你需要想想並發會導致什麼。而除了所有這一切之外,Linux內核只是一個很大的、很復雜的C程序,它對每個人開放,任何人都去讀它、學習它並改進它,而你也可以是其中之一。

學習Linux內核模塊編寫總結 http://www.linuxidc.com/Linux/2011-03/33251.htm

增加Linux系統調用——通過重新編譯內核 http://www.linuxidc.com/Linux/2014-05/102423.htm

Linux內核編譯步驟(手動安裝內核) http://www.linuxidc.com/Linux/2013-03/80271.htm

Linux內核編譯錯誤:error: read-only variable '__r2' used as 'asm' output http://www.linuxidc.com/Linux/2012-12/76859.htm

Linux內核編譯與安裝 http://www.linuxidc.com/Linux/2012-10/71689.htm

Linux內核升級及內核編譯 http://www.linuxidc.com/Linux/2012-08/68569.htm

Linux內核編譯與裁剪(ARM版) http://www.linuxidc.com/Linux/2012-07/65196.htm

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

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

盡可能不要用root身份

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

SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"

別忘了重新插入模塊。讓非root用戶訪問設備節點往往不是一個好主意,但是在開發其間卻是十分有用的。這並不是說以root身份運行二進制測試文件也不是個好主意。

模塊的構造

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

  1. #include<linux/init.h>
  2. #include<linux/kernel.h>
  3. #include<linux/module.h>
  4. MODULE_LICENSE("GPL");
  5. MODULE_AUTHOR("Valentine Sinitsyn <[email protected]>");
  6. MODULE_DESCRIPTION("In-kernel phrase reverser");

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

什麼時候不該寫內核模塊

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

  • 你要開發一個USB驅動 —— 請查看libusb。
  • 你要開發一個文件系統 —— 試試FUSE。
  • 你在擴展Netfilter —— 那麼libnetfilter_queue對你有所幫助。

通常,內核裡面代碼的性能會更好,但是對於許多項目而言,這點性能丟失並不嚴重。

由於內核編程總是異步的,沒有一個main()函數來讓Linux順序執行你的模塊。取而代之的是,你要為各種事件提供回調函數,像這個:

  1. staticint __init reverse_init(void)
  2. {
  3. printk(KERN_INFO "reverse device has been registered\n");
  4. return0;
  5. }
  6. staticvoid __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);

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

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-06/103611p2.htm

Copyright © Linux教程網 All Rights Reserved