
#include <linux/init.h> // __init __exit #include <linux/module.h> // module_init module_exit static int __init hello_init(void) { printk(KERN_ALERT "helloworld!\n"); return 0; } static __exit void hello_exit(void) { printk(KERN_ALERT "helloworld exit!\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Musk <qq:739112417>"); MODULE_DESCRIPTION("A Simple Hello World"); MODULE_ALIAS("A simplest module");
一. 分析module_init宏定義
1.1. module_init宏被定義在kernel/include/linux/init.h文件里

#define module_init(x) __initcall(x); #define __initcall(fn) device_initcall(fn); #define device_initcall(fn) __define_initcall(fn, 6); #define __define_initcall(fn, id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" #id ".init"))) = fn
1.2. module_init(hello_init)深入分析宏定義:

#define module_init(hello_init) __initcall(hello_init); #define __initcall(hello_init) device_initcall(hello_init); #define device_initcall(hello_init) __define_initcall(hello_init, 6); #define __define_initcall(hello_init, 6) \ static initcall_t __initcall_hello_init6 __used \ __attribute__((__section__(".initcall6.init"))) = hello_init
1.3. 其中static initcall_t 這里 initcall_t的定義是 : typedef int (*initcall_t)(void) 這里決定了hello_init函數類型。
上面大概的意思就是定義一個指向函數的指針,以hello_init為例,就是定義了__initcall_hello_init6,把hello_init函數的指針賦給它
並把__initcall_hello_init6放到在vmlinux.lds文件里邊指定的.initcall6.init區間里邊
1.4. 這里再介紹一下像int __init hello_init(void) { }這種用包含在module_init里邊的函數定義,函數名字前面加上__init到底什么意 思。
__init的定義是 #define __init __section(.init.text) __cold notrace
在init.h中對這些宏的說明如下:

/* These macros are used to mark some functions or * initialized data (doesn't apply to uninitialized data) * as `initialization' functions. The kernel can take this * as hint that the function is used only during the initialization * phase and free up used memory resources after * * Usage: * For functions: * * You should add __init immediately before the function name, like: * * static void __init initme(int x, int y) * { * extern int z; z = x * y; * } * * If the function has a prototype somewhere, you can also add * __init between closing brace of the prototype and semicolon: * * extern int initialize_foobar_device(int, int, int) __init; * * For initialized data: * You should insert __initdata between the variable name and equal * sign followed by value, e.g.: * * static int init_variable __initdata = 0; * static const char linux_logo[] __initconst = { 0x32, 0x36, ... }; * * Don't forget to initialize data not at file scope, i.e. within a function, * as gcc otherwise puts the data into the bss section and not into the init * section. * * Also note, that this data cannot be "const". */ /* These are for everybody (although not all archs will actually discard it in modules) */ #define __init __section(.init.text) __cold notrace //__cold 和notrace具體干嘛的? #define __initdata __section(.init.data) #define __initconst __constsection(.init.rodata) #define __exitdata __section(.exit.data) #define __exit_call __used __section(.exitcall.exit)
就是說如果某個函數或者變量只在初始化的時候用到,后面可以被清理掉也 沒有關系的話,就最好定義成上面的一種,以便釋放更多空間。
這里具體怎么釋放加了上面定義的的函數和變量? 應該是放到了特定區域里 邊一起釋放,具體后面再說。
還有這種__init定義方式需要注意,不要用在函數里邊的
二. 模塊操作命令
2.1. 常用模塊操作命令
2.1.1. lsmod(list module,將模塊列表顯示),功能是打印出當前內核中已經 安裝的模塊列表
2.1.2. insmod(install module,安裝模塊),功能是向當前內核中去安裝 一個模塊,用法是insmod xxx.ko
2.1.3. modinfo(module information,模塊信息),功能是打印出一個內 核模塊的自帶信息。
用法是modinfo xxx.ko,注意要加.ko,也就是說是一個靜態的文件形式
2.1.4. rmmod(remove module,卸載模塊),功能是從當前內核中卸載 一個已經安裝了的模塊,
用法是rmmod xxx.ko rmmod xxx都可以
2.2. 模塊的安裝
2.2.1. insmod與module_init宏。模塊源代碼中用module_init宏聲明了一 個函數(在我們這個例子里是hello_init函數),
作用就是指定hello_init這個函數和insmod命令綁定起來,也就是說當我們 insmod xxx.ko時,insmod命令內部實際執行的操作就是幫我們調用hello_init 函數。
2.2.2. 模塊安裝時insmod內部除了幫我們調用module_init宏所聲明的函數 外,實際還做了一些別的事(譬如lsmod能看到多了一個模塊也是insmod幫我 們在內部做了記錄,
也就是將我們的模塊加入到內核的一個數據結構中去),但是我們就不用管了
2.3. 模塊的卸載
2.3.1. module_exit和rmmod的對應關系當我們執行rmmod命令的時候, 就會執行模塊的module_exit宏聲明的函數,
同樣也會將我們這個模塊信息從我們內核的模塊管理的數據結構中將其刪除
2.4. 模塊的版本信息
2.4.1. 使用modinfo查看模塊的版本信息
2.4.2. 內核zImage中也有一個確定的版本信息
2.4.3. insmod時模塊的vermagic必須和內核的相同,否則不能安裝,報錯 信息為:insmod: ERROR: could not insert module module_test.ko: Invalid module format
2.4.4. 模塊的版本信息是為了保證模塊和內核的兼容性,是一種安全措施
2.4.5. 如何保證模塊的vermagic和內核的vermagic一致?編譯模塊的內核源 碼樹就是我們編譯正在運行的這個內核的那個內核源碼樹即可。說白了就是模 塊和內核要同出一門
2.5. 模塊中常用宏
2.5.1. MODULE_LICENSE("GPL"),模塊的許可證。一般聲明為GPL許證,而且最好不要少,否則可能會出現莫名其妙的錯誤(譬如一些明顯存在 的函數提升找不到)。
2.5.2. MODULE_AUTHOR(Musk <qq:739112417> "),用來添加模塊的作者信息
2.5.3. MODULE_DESCRIPTION("xxx"),用來添加模塊的描述信息
2.5.4. MODULE_ALIAS("xxxx"),用來添加模塊的別名
2.6. printk函數詳解
2.6.1.printk在內核源碼中用來打印信息的函數,用法和printf非常相似
2.6.2. printk和printf最大的差別:printf是C庫函數,是在應用層編程中使用 的,不能在linux內核源代碼中使用;printk是linux內核源代碼中自己封裝出來的 一個打印函數,是內核源碼中的一個普通函數,只能在內核源碼范圍內使用,不 能在應用編程中使用
2.6.3. printk相比printf來說還多了個:打印級別的設置。printk的打印級別 是用來控制printk打印的這條信息是否在終端上顯示的。應用程序中的調試信 息要么全部打開要么全部關閉,一般用條件編譯來實現(DEBUG宏),但是在內核中,因為內核非常龐大,打印信息非常多,有時候整體調試內核時打印信息要么太多找不到想要的要么一個沒有沒法調試。所以才有了打印級別這個概念
2.6.4. 命令行設置級別的信息會被放行打印出來,大於的就被攔截的。譬如我的ubuntu中的打印級別默認是4,那么printk中設置的級別比4小的就能打印出來,比4大的就不能打印出來.可以通過這個命令查看 : cat /proc/sys/kernel/printk。
2.6.4. ubuntu中這個printk的打印級別控制沒法實踐,ubuntu中不管你把級別怎么設置都不能直接打印出來,必須dmesg命令去查看。
2.7. 關於驅動模塊中的頭文件
驅動源代碼中包含的頭文件和原來應用編程程序中包含的頭文件不是一回事。應用編程中包含的頭文件是應用層的頭文件,是應用程序的編譯器帶來的(譬如gcc的頭文件路徑在 /usr/include下,這些東西是和操作系統無關的)。驅動源碼屬於內核源碼的一部分,驅動源碼中的頭文件其實就是內核源代碼目錄下的所有include目錄下的頭文件。
2.8. 用開發板調試模塊
2.8.1. 設置bootcmd使開發板通過tftp下載自己建立的內核源碼樹編譯得到的zImage: set bootcmd 'tftp 0x30008000 zImage;bootm 0x30008000'
2.8.2. 設置bootargs使開發板從nfs去掛載rootfs(內核配置時需要使其支持掛載NFS文件系統,這個在uboot中已經說過)setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/porting_x210/rootfs/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200
2.8.3. 修改Makefile中的KERN_DIR使其指向自己建立的內核源碼樹
2.8.4. 將自己編譯好的驅動.ko文件放入nfs共享目錄下去
2.8.5 開發板啟動后使用insmod、rmmod、lsmod等去進行模塊實驗
索引文獻:https://www.cnblogs.com/deng-tao/p/6165573.html
索引文獻:https://blog.csdn.net/hongzg1982/article/details/54836465