Linux内核模块学习笔记(转载)
Linux內核模塊 ?? Linux設備驅動會以內核模塊的形式出現,因此學會編寫Linux內核模塊編程是學習linux設備驅動的先決條件。
1.1linux內核模塊簡介
? ? Linux內核的整體結構非常龐大,其包含的組件非常多。我們如何把需要的部分都包含在內核中呢? ●把需要的功能都編譯到linux內核。 ●以模塊方式擴展內核功能。
??? 為了使學生對模塊建立初步的感性認識,我們先來看一個最簡單的內核模塊”hello world”,代碼如下: #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { ??? printk("hello world\n”); ??? return 0; } static void hello_exit(void) { ??? printk(1? "hello module exit\n "); } module_init(hello_init); module_exit(hello_exit);
MODULE_AUTHOR("zky"); MODULE_DESCRIPTION("A simple? hello Module "); MODULE_VERSION("V1.0");
?? 這個最簡單的內核模塊只包含內核加載函數、卸載函數和對Dual BSD/GPL許可權限的聲明以及一些描述信息。編譯會產生hello.ko目標文件,通過”insmod ./hello.ko”命令可以加載它,通過”rmmod hello”命令可以卸載它,加載時輸出”hello world”, 卸載時輸出”hello module exit”,查看輸出信息可通過dmesg命令。
??? 內核模塊中用于輸出的函數式內核空間的printk()而非用戶空間的printf(),printk()的用法和printf()相似,但前者可定義輸出級別。printk()可作為一種最基本的內核調試手段。
printk有8個loglevel,定義在<linux/kernel.h>中: #define KERN_EMERG????????? "<0>"??? /* system is unusable */ #define KERN_ALERT????????? "<1>"??? /* action must be taken immediately */ #define KERN_CRIT???????????? "<2>"??? /* critical conditions */ #define KERN_ERR?????????????? "<3>"??? /* error conditions */ #define KERN_WARNING??? ? "<4>"??? /* warning conditions */ #define KERN_NOTICE???????? "<5>"??? /* normal but significant condition */ #define KERN_INFO???????????? "<6>"??? /* informational */ #define KERN_DEBUG????????? "<7>"??? /* debug-level messages */
??? 未指定優先級的默認級別定義在/kernel/printk.c中: #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
??? 當優先級的值小于console_loglevel這個整數變量的值,信息才能顯示出來。而console_loglevel的初始值DEFAULT_CONSOLE_LOGLEVEL也定義在/kernel/printk.c中: #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */
在linux系統中,使用lsmod命令可以獲得系統中加載了的所有模塊以及模塊間的依賴關系,例如:
lsmod命令實際上讀取并分析/proc/modules文件,與上述lsmod命令結果對應的/proc/modules文件如下:
??? 內核中已加載模塊的信息也存在于/sys/module目錄下,加載hello.ko后,內核中將包含/sys/module/hello目錄,該目錄下又包含一個refcnt文件和一個sections目錄,在/sys/module/hello目錄下運行”tree -a”得到如下目錄樹:
? ? Modprobe命令比insmod命令要強大,它在加載某模塊時會同時加載該模塊所依賴的其他模塊。使用modprobe命令加載的模塊若以”modprobe –r? filename”的方式卸載將同時其他依賴的模塊。
使用modinfo <模塊名>命令可以獲得模塊的信息,包括模塊的作者、模塊的說明、模塊所支持的參數以及vermagic,如下所示:
1.2模塊的編譯
??? 接下來我們來簡單看看模塊是如何構造的,模塊的構造和用戶空間應用程序的構造過程有很大的不同。實際上對本章先前給出的”hello world”示例來說,下面一行就足以了: Obj-m? := hello.o
??? 如果大家熟悉make但對2.6內核構造系統還不熟悉的話,則可能會對此makefile的工作方式感到疑惑。畢竟上面這行并不是makefile文件的常見格式。問題的答案就是內核的構造系統處理了其余的問題。上面的賦值語句說明了又一個模塊需要從目標文件hello.o中構造,而從目標文件中構造的模塊名稱為hello.ko。
??? 如果我們要構造的模塊名稱為module.ko,并有兩個源文件生成(比如file1.c和file2.c),則正確的makefile可如下編寫: Obj-m? := module.o Module-objs :=file1.o file2.o
??? 為了讓上面這種類型的makefile文件正常工作,必須在大的內核構造系統環境中調用它們。如果內核源碼保存在~/kernel-2.6目錄中,則用來構造模塊的make命令應該為(在包含模塊源代碼和makefile的目錄中鍵入): Make –C? ~/kernel-2.6 M=`pwd` modules
??? 上述命令首先改變目錄到-C選項指定的位置(既內核源代碼目錄),其中保存有內核的頂層makefile文件,M=選項讓該makefile在構造modules目標之前返回到模塊源代碼目錄。然后modules目標指向obj-m變量中設定的模塊;在上面的例子中,我們將該變量設置成了module.o。
??? 上面這樣的make命令還是有些煩人,因此內核開發者又開發了一種makefile方法,這種方法將使得內核樹之外的模塊構造變得更加容易,其技巧就是用下面的方法來編寫makefile: #如果已定義KERNELRELEASE,則說明是從內核構造系統調用的,因此可利用其內建語句。 Ifneq($(KERNELRELEASE),) obj-m :=hello.o #否則,是直接從命令行調用的,這時要調用內核構造系統 Else KERNELDIR? ?=~/KERNEL-2.6 PWD? := $(shell pwd) Default: $(MAKE) –C $(KERNELDIR) M=$(PWD) modules endif
?? 需要注意的是上面的makefile文件并不完整;一個真正的makefile文件應該包含通常用來清除無用文件的目標,安裝模塊的目標等等。
1.3linux內核模塊的結構
??? 一個linux內核模塊主要由以下幾個部分組成: ●模塊加載函數(必須) ??? 當通過insmod或modprobe命令加載內核模塊時,模塊的加載函數會自動被內核執行,完成本模塊相關初始化工作。 ●模塊卸載函數(必須) ??? 當通過rmmod命令卸載模塊時,模塊的卸載函數會自動被內核執行,完成與模塊加載函數相反的功能。 ●模塊許可證聲明(必須) ??? 模塊許可證(LICENCE)聲明描述內核模塊的許可權限,如果不聲明LICENCE,模塊被加載時將收到內核被污染的警告。
??? 在2.6內核中,可接受得LICENSE包括“GPL”、“GPL v2”、“GPL and additional right”、“Dual BSD/GPL”、“Dual MPL/GPL和“Proprietary”。
??? 大多數情況下,內核模塊應遵循GPL兼容許可權。Linux2.6內核模塊最常見的是以MODULE_LICENSE(“Dual BSD/GPL”)語句聲明模塊采用BSD/GPL雙LICENSE。 ●模塊參數(可選) 模塊參數是模塊被加載的時候可以被傳遞給他的值,它本身對應模塊內部的全局變量。 ●模塊導出符號(可選) 內核模塊可以導出符號(symbol,對應于函數或變量),這樣其他模塊可以使用本模塊中的變量或函數。 ●模塊作者等信息聲明(可選)。
1.4模塊加載函數
??? Linux內核模塊加載函數一般以__init標識聲明,典型的模塊加載函數的形式如下: Static? int? __init? initialization_function(void) { ???? //初始化代碼 } Module_init(initialization_function);
??? 模塊加載函數必須以“module_init(函數名)”的形式指定。它返回整形值,若初始化成功,應返回0。而在初始化失敗時。應該返回錯誤編碼。在linux內核里,錯誤編碼是一個負值,在<linux/errno.h>中定義,包含-ENODEV、-ENOMEM之類的符號值。返回相應的錯誤編碼是種非常好的習慣,因為只有這樣,用戶程序才可以利用perror等方法把它們轉換成有意義的錯誤信息字符串。
??? 在linux2.6內核中,所有標識為__init的函數在連接的時候都會放在.init.text(這是module_init宏在目標代碼中增加的一個特殊區段,用于說明內核初始化函數的所在位置)這個區段中,此外,所有的__init函數在區段.initcall.init中還保存著一份函數指針,在初始化時內核會通過這些函數指針調用這些__init函數,并在初始化完成后釋放init區段(包括.init.text和.initcall.init等)。所以大家應注意不要在結束初始化后仍要使用的函數上使用這個標記。
1.5模塊卸載函數
??? Linux內核卸載模塊函數一般以__exit標識聲明,典型的模塊卸載函數的形式如下: Satic void __exit cleanup_function(void) { ?? //釋放代碼 } Module_exit(cleanup_function);
??? 模塊卸載函數在模塊卸載時被調用,不返回任何值,必須以”module_exit(函數名)”的形式來指定。
??? 一般來說,模塊卸載函數完成與模塊加載函數相反的功能: ●如果模塊加載函數注冊了 XXX模塊,則模塊卸載函數應注銷XXX。 ●若模塊加載函數動體申請了內存,則模塊卸載函數應釋放該內存。 ●若模塊加載函數申請了硬件資源,則模塊卸載函數應釋放這些硬件資源。 ●若模塊加載函數開啟了硬件,則模塊卸載函數應關閉硬件。
??? 和__init一樣__exit也可以使對應函數在運行完成后自動回收內存。
1.6模塊參數
??? 我們可以用”module_param(參數名,參數類型,參數讀/寫權限)”為模塊定義一個參數,例如下列代碼定義了一個整形參數和一個字符指針參數: Static? char *book_name = “linux 模塊”; Static?? int? num = 4000; Module_param(num, int, S_IRUGO); Module_param(book_name, charp, S_IRUGO);
??? 在裝載內核模塊時,用戶可以向模塊傳遞參數,形式為”insmod (或 modprobe) 模塊名 參數名=參數值”,如果不傳遞,參數將使用模塊內定義的默認值。
??? 參數類型可以是byte、short、ushort、int、uint、long、ulong、charp、bool、或invbool(布爾的反),在模塊被編譯時會將module_param中聲明的類型與變量定義的類型進行比較,判斷是否一致。
?? 模塊被加載后,在/sys/module/目錄下將出現以此模塊命名的目錄。當“參數讀/寫權限”為0時,表示此參數不存在sysfs文件系統下對應的文件節點,如果此模塊存在“參數讀/寫權限”不為0的命令行參數,在此模塊的目錄下還將出現parameters目錄,包含一系列以參數名命名的文件節點,這些文件的權限值就是傳入module_param()的“參數讀/寫權限”,而文件的內容為參數的值。
?? 現在我們定義一個包含兩個參數的模塊,并觀察模塊加載時被傳遞參數和不傳遞參數時的輸出。 #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL");
static char *book_name = "dissecting Linux Device Driver"; static int num = 4000;
static int book_init(void)
{ printk(KERN_INFO " book name:%s\n",book_name); printk(KERN_INFO " book num:%d\n",num); return 0; } static void book_exit(void) { printk(KERN_INFO " Book module exit\n "); } module_init(book_init); module_exit(book_exit); module_param(num, int, S_IRUGO); module_param(book_name, charp, S_IRUGO);
MODULE_AUTHOR("zky"); MODULE_DESCRIPTION("A simple Module for testing module params"); MODULE_VERSION("V1.0");
??? 對上述模塊運新“insmod? book.ko”命令加載,相應輸出都為模塊內的默認值,通過查看“/var/log/messages”日志文件可以看到內核的輸出,如下所示:
??? 當用戶運行“insmod book.ko book_name=’mybook’ num=3000”命令時,輸出的是用戶傳遞的參數,如下所示:
1.7導出符號
??? Linux2.6的/proc/kallsyms文件對應著內核符號表,它記錄了符號以及符號所在的內存地址。模塊可使用如下宏導出符號到內核符號表: EXPORT_SYMBOL(符號名); EXPORT_SYMBOL_GPL(符號名);
??? 導出的符號將可以被其他模塊使用,使用前聲明一下即可。EXPORT_SYMBOL_GPL()只適用于包含GPL許可權的模塊。如下代碼給出了一個導出整數加、減運算函數符號的內核模塊的例子。 #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL");
int add_integar(int a,int b) { return a+b;
}
int sub_integar(int a,int b) { return a-b; }
EXPORT_SYMBOL(add_integar); EXPORT_SYMBOL(sub_integar);
??? 從/proc/kallsyms文件中找出add_integar、sub_integar相關信息:
1.8模塊聲明與描述
?? 在linux模塊中,我們可以使用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分別聲明模塊的作者、描述、版本、設備表和別名,例如: MODULE_AUTHOR(author); MODULE_DESCRIPTION(description); MODULE_VERSION(version); MODULE_DEVICE_TABLE(device table); MODULE_ALIAS(alternate_name);
??? 對于USB、PCI等設備驅動,通常會創建一個MODULE_DEVICE_TABLE,如下所示:
1.9模塊與GPL
??? 對于自己編寫的驅動等內核代碼,如果不編譯為模塊則無法繞開GPL,編譯為模塊后企業在產品中使用模塊,則公司對外不需要提供對應的源代碼,為了使國內公司產品所使用的Linux操作系統支持模塊,需要完成如下工作。 ●在內核編譯時應該選上”Enable loadble module support”,嵌入式產品一般不需要動態卸載模塊,所以“可以卸載模塊”不用選,當然選了也沒有關系,如圖:
??? 如果有項目被選擇”M”,則編譯時除了make bzImage以外,也要make modules. ●將我們編譯的內核模塊.ko文件放置在目標文件系統的相關目錄中。 ●產品的文件系統中應該包含了新內核的insmod、lsmod、rmmod等工具。 ●在使用中用戶可使用insmod 命令手動加載模塊,如insmod xxx.ko
轉載于:https://www.cnblogs.com/sn-dnv-aps/archive/2012/11/04/2754276.html
總結
以上是生活随笔為你收集整理的Linux内核模块学习笔记(转载)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 弱类型语言的优势:C#的委托概念在Jav
- 下一篇: Linux硬件信息查看