您的內核必須已經啟用這些選項進行了編譯:
Loadable module support ---> [*] Enable loadable module support [*] Module unloading [ ] Module versioning support (EXPERIMENTAL) [*] Automatic kernel module loading
如果按照第一篇教程中的說明編譯內核,那麼就已經正確地設置了這些選項。否則,修改這些選項,重新編譯內核,並引導到新內核。
一個簡單的模塊骨架
首先,找到編譯當前 Linux 內核的源代碼。將目錄切換到 Linux 源代碼目錄中的 drivers/misc/
。現在,拷貝下面的代碼並將其粘貼到一個名為 mymodule.c
的文件:
#include <linux/module.h>#include <linux/config.h>#include <linux/init.h>static int __init mymodule_init(void){ printk ("My module worked!\n"); return 0;}static void __exit mymodule_exit(void){ printk ("Unloading my module.\n"); return;}module_init(mymodule_init);module_exit(mymodule_exit);MODULE_LICENSE("GPL");
保存這個文件,並在同一目錄下編輯 Makefile
文件。添加這一行:obj-m += mymodule.o
編譯模塊:# make -C <top directory of your kernel source> SUBDIRS=$PWD modules
使用 insmod ./mymodule.ko
加載這個模塊,並查看是否打印了您的消息: dmesg | tail
。應該會在輸出的結束處看到:My module worked!
現在刪除內核模塊:rmmod mymodule
。再次查看 dmesg;應該會看到:Unloading my module.
這樣您就已經編寫並運行了一個新的內核模塊!恭喜!模塊/內核接口
現在,我們來做一些與您的模塊有關的更有趣的事情。要了解的一個關鍵內容是,模塊只能“看到”內核故意讓它訪問的函數和變量。首先,我們以錯誤的方式來進行嘗試。
編輯文件 kernel/printk.c
,在所有包含文件之後其他全局變量聲明附近(但要在所有函數之外)添加下面一行:
int my_variable = 0;
現在重新編譯內核並引導到新內核。然後,將下面的內容添加到模塊的 mymodule_init
函數起始處,置於其他代碼之前。extern int my_variable;printk ("my_variable is %d\n", my_variable);my_variable++;
保存修改並重新編譯模塊:# make -C <top directory of your kernel source> SUBDIRS=$PWD modules
加載模塊(這將失敗):insmod ./mymodule.ko
。模塊的加載會失敗,並給出消息:insmod: error inserting './mymodule.ko': -1 Unknown symbol in module
這說明內核不允許模塊訪問那個變量。當模塊加載時,它必須解析所有外部引用,比如函數名或者變量名。如果它不能找到內核導出的符號列表中所有未解析的名稱,那麼模塊就不能寫入那個變量或者調用那個函數。在內核中某個地方有為變量 my_variable
分配的空間,但模塊不知道是哪裡。為解決此問題,我們將把 my_variable
添加到內核導出的符號列表中。在很多內核目錄中,都有一個特定的文件,用於導出在那個目錄中定義的符號。再次打開 kernel/printk.c
文件,在變量聲明之後添加下面一行:
EXPORT_SYMBOL(my_variable);
重新編譯並重新引導到新內核。現在再一次嘗試加載模塊:insmod ./mymodule.ko
。這一次,當查看 dmesg 時,應該看到:my_variable is 0My module worked!
重新加載模塊:# rmmod mymodule && insmod ./mymodule.ko
現在應該看到:Unloading my module.my_variable is 1My module worked!
每次重新加載那個模塊,my_variable
都會增 1。您正在讀寫一個在主內核中定義的變量。只要被 EXPORT_SYMBOL()
顯式地聲明,模塊就可以訪問主內核中的任何變量。例如,函數 printk()
是在內核中定義的,並且在文件 kernel/printk.c
中被導出。簡單的可引導內核模塊是用來研究內核的一個有趣的途徑。例如,可以使用一個模塊來打開或關閉 printk
,方法是在內核中定義一個變量 do_print
(它初始化為 0)。然後,讓所有 printk
都依賴於“do_print
”:
if (do_print) { printk ("Big long obnoxious message\n");}
然後,只有當您的模塊被加載時才打開它。模塊參數
引導模塊時,可以向它傳遞參數。要使用模塊參數加載模塊,這樣寫:
insmod module.ko [param1=value param2=value ...]
為了使用這些參數的值,要在模塊中聲明變量來保存它們,並在所有函數之外的某個地方使用宏 MODULE_PARM(variable, type)
和 MODULE_PARM_DESC(variable, description)
來接收它們。type
參數應該是一個格式為 [min[-max]]{b,h,i,l,s}
字符串,其中 min 和 max 是數組的長度限度。如果兩者都忽略了,則默認為 1。最後一個字符是類型說明符:b byteh shorti intl longs string
可以在 MODULE_PARM_DESC
的 description
域中添加任何需要的說明符。
編寫使用中斷的模塊
現在我們將編寫一個模塊,其中有一個函數,當內核接收到某個 IRQ 上的一個中斷時會調用它。首先,將文件 mymodule.c
拷貝到 myirqtest.c
,然後刪除函數的內容,只保留返回語句。在編輯器中打開 myirqtest.c
,並使用“myirqtest”替換所出現的“mymodule”來修改函數名。另外刪除 printk
。為了能夠使用中斷,將下面一行:
#include <linux/interrupt.h>
加入到文件的頂部。
使用 cat /proc/interrupts
找出正在使用的中斷。第一列顯示出正在使用的中斷號,第二列是機器自最後一次引導後在那個 IRQ 上發行了多少次中斷,第三列是使用這個 IRQ 的設備。在這個示例中,我們將研究來自網絡接口的中斷,並使用兩個模塊參數 interface
和 irq
來指明我們要使用的接口和 IRQ 行。
為了使用模塊參數,要聲明兩個變量來存放它們,並