注意:linux
下文轉載至:https://blog.csdn.net/bingqing07/article/details/5888875shell
首先得了解一下什麼是模塊: 模塊是具備獨立功能的程序,它能夠被單獨編譯,但不能獨立運行。它在運行時被連接到內核做爲內核的一部分在內核空間運行,這與運行在用戶空間的進程是不一樣的。模塊一般由一組函數和數據結構組成,用來實現一種文件系統、一個驅動程序或其餘內核上層的功能。編程
這樣說吧,模塊就是整個內核的一部分。可是跟C程序中函數不同的一點是,內核模塊能夠在它所認爲適當的時候,插入到內核或者從內核中刪除,並且還不影響內核的正常運行。從而能夠在必要的時候對內核進行裁剪,這樣可以更好的適應於用戶的需求。數據結構
廢話少說了。咱們如今就開始進入內核編寫的階段,看一看怎麼樣將一個C程序一步步變成相應的內核模塊。編程語言
每個學習過編程語言的人都知道,第一個示例程序確定是hello world。咱們內核編程的第一個例子也不例外,就是編寫一個hello world模塊。函數
首先讓咱們在電腦裏編寫一段C語言代碼,hello.c。代碼以下:學習
//必要的頭文件 #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> //模塊許可證聲明(必須) MODULE_LICENSE("Dual BSD/GPL"); //模塊加載函數(必須) static int hello_init(void) { printk(KERN_ALERT "Hello World enter/n"); return 0; } //模塊卸載函數(必須) static void hello_exit(void) { printk(KERN_ALERT "Hello World exit/n"); } //模塊的註冊 module_init(hello_init); module_exit(hello_exit); //聲明模塊的做者(可選) MODULE_AUTHOR("XXX"); //聲明模塊的描述(可選) MODULE_DESCRIPTION("This is a simple example!/n"); //聲明模塊的別名(可選) MODULE_ALIAS("A simplest example");
以上就是一個最簡單的內核模塊的例子程序。咱們經過這個例子來分析一下內核模塊的特色。.net
一、在內核模塊的開始一部分,跟C語言的通常程序同樣,是模塊所須要的頭文件。日誌
二、模塊許可證聲明,這部分是必須有的。模塊許可證(LICENSE)聲明描述內核模塊的許可權限,若是不聲明LICENSE,模塊被加載時,將收到內核被污染(kernel tainted)的警告。大多數狀況下,內核模塊應遵照GPL兼允許可權。Linux2.6內核模塊最多見的是以MODULE_LICENSE("Dual BSD/GPL")語句聲明模塊採用BSD/GPL雙LICENSE。code
三、模塊加載函數,這部分是必須的。模塊加載函數必須以「module_init(函數名)「的形式被指定。它返回整形值,若初始化成功,應返回0。在上面那個例子當中,hello_init()函數就是模塊加載函數須要執行的,主要是打印一條信息。
四、跟模塊加載函數相對應的就是模塊卸載函數,這部分也是必須的。模塊卸載函數在模塊卸載的時候執行,不返回任何值,必須以「module_exit(函數名)「的形式來指定。在上面的例子中,hello_exit()函數就是模塊卸載函數須要執行的,只要是打印了一條退出信息。
五、函數最後的一部分,是模塊聲明與描述部分。這部分能夠有,也能夠省略。在Linux內核模塊中,咱們能夠用MODULE_AUTHOR,MODULE_DESCRIPTION,MODULE_VERSION,MODULE_DEVICE_TABLE,MODULE_ALIAS分別聲明模塊的做者、描述、版本、設備表和別名。
注:
一、module_init()是驅動程序初始化的入口點。它就至關於c語言程序中的main()函數。對於內置的模塊,內核在引導時調用該引導點,對於可加載模塊則在模塊插入到內核時才調用。
二、模塊加載函數和模塊卸載函數中都用到了printk()函數,該函數是由內核定義的,功能與C庫中的printf()相似,它把要打印的信息輸出到終端或系統日誌中。在本例中,咱們是將初始化的打印信息輸出到日誌中,咱們在看它對應的輸出時,這時能夠用dmesg命令來查看。
編寫完了.c文件,下面咱們就要對其進行對應的操做,要把一個普通的.c文件變成咱們所須要的內核文件。通常咱們理解,應該是應用幾條Linux下的命令就能夠搞定(如gcc,g++……),這裏的理解是對的,咱們就是須要幾個命令就OK。可是咱們知道,編譯這個須要敲打的命令過於多,要輸入內核版本的號,路徑,和編寫模塊的路徑與信息。若是每次都輸入這麼多,那確定是太麻煩。這時咱們就想到了Makefile文件,經過它來管理一個龐大的項目是再好不過的。下面咱們就在剛纔.c文件目錄下編寫一個Makefile文件。對應的代碼以下:
obj-m += hello.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #clean clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
首先第一句話就是指定要編譯的文件。
pwd是得到當前的相對路徑,而後就是得到當前的內核版本號,咱們能夠用uname -r命令,這樣咱們就得到了當前內核的絕對路徑。這樣作的一個好處,就是你能夠在不一樣的內核版本中進行移植,並且可讀性也加強了。
對於其餘的Makefile語法上的問題就不在這裏介紹。不會的,能夠看看相應的語法介紹。
有了Makefile文件後,咱們就離成功不遠了。在.c文件的同一目錄下,執行make命令,系統會在當前目錄下生成好多個文件。其中就有與之相關的.o和.ko文件。hello.ko就是模塊目標文件。到此,模塊編譯好了。
模塊編譯好了,可是還不能爲咱們工做。下面就是將目標模塊插入到內核和從內核中刪除。這裏須要用到兩個命令,insmod和rmmod 咱們光看這兩個命令單詞就能猜出他們的意思。輸入命令:sudo insmod hello.ko(注意要用sudo),這時沒有任何提示,不少人會很奇怪,剛纔不是說過,模塊加載後,程序中要對應輸出一條提示信息,怎麼這裏什麼都沒有。你們不要急,再想想剛纔所用到的打印信息的函數printk(),它與咱們日常C庫的printf()函數不同,不是運行在用戶界面上的,因此確定不會在終端上顯示出信息。要看信息必需要進入到日誌文件中。這時咱們再輸入命令進到系統日誌:dmesg,咱們把界面拖到最後,會發現有一條信息,Hello World enter。哈哈,這正是咱們所須要的,說明咱們剛纔編寫的模塊已經插入到內核當中了。接下來再試一試刪除命令,輸入命令:sudo rmmod hello.ko,這時跟剛纔的插入命令同樣,沒有什麼反應。再輸入命令打開系統日誌,咱們會發如今剛纔 Hello World enter命令後面會有一個新的信息Hello World exit,這說明咱們的模塊卸載成功。這樣咱們就大功告成,慶祝一下吧。
除了加載和刪除模塊,咱們也能夠用命令 lsmod 來查看當前系統中加載的全部模塊及模塊間的依賴關係。若是剛纔咱們加載了hello模塊並無刪除,用這個命令,咱們會在其中找到hello這一項,這樣也能夠說明咱們本身編寫的模塊加載成功。相應的,若是咱們刪除了該模塊,用這個命令後,hello模塊不會出現。lsmod命令實際上讀取並分析/proc/modules文件。
使用modinfo <模塊名> 命令能夠得到模塊的信息,包括模塊的做者,說明,參數……也就是咱們剛纔編寫模塊時可選的幾個部分。