例程代碼路徑:ELF 1開(kāi)發(fā)板資料包3-例程源碼3-2 驅(qū)動(dòng)例程源碼1_helloworldhello
本章節(jié)先寫(xiě)一個(gè)helloworld驅(qū)動(dòng),不涉及對(duì)硬件的操作,它的目的是展示驅(qū)動(dòng)程序的基本結(jié)構(gòu)和加載過(guò)程。
源碼編寫(xiě)
(一)首先包含頭文件
#include <linux/module.h> ??// 包含模塊相關(guān)函數(shù)的頭文件
#include <linux/kernel.h> ??// 包含內(nèi)核相關(guān)函數(shù)的頭文件 #include <linux/init.h> ????// 包含初始化和清理函數(shù)的頭文件 |
(二)驅(qū)動(dòng)模塊入口和出口函數(shù)
module_init(helloworld_init); ?// 指定驅(qū)動(dòng)程序的初始化函數(shù)
module_exit(helloworld_exit); ?// 指定驅(qū)動(dòng)程序的清理函數(shù) |
module_init()的定義:
#define module_init(init_fn)
static inline initcall_t __initcall_##init_fn##_wrapper(void) { return init_fn; } int init_module(void) __attribute__((alias(#init_fn))); |
module_init()宏接受一個(gè)初始化函數(shù)作為參數(shù),并將其定義為內(nèi)聯(lián)函數(shù)__initcall_##init_fn##_wrapper()。然后,它使用__attribute__((alias(#init_fn)))將init_module()函數(shù)與初始化函數(shù)進(jìn)行關(guān)聯(lián)。
在Linux內(nèi)核加載模塊時(shí),會(huì)調(diào)用init_module()函數(shù),它實(shí)際上是一個(gè)入口點(diǎn)函數(shù)。通過(guò)將module_init()定義為init_module()的別名,使得初始化函數(shù)成為模塊加載時(shí)的入口點(diǎn)函數(shù)。
使用module_init()宏的目的是在驅(qū)動(dòng)程序中指定初始化函數(shù),以便在模塊加載時(shí)調(diào)用該函數(shù)執(zhí)行必要的初始化操作。
當(dāng)使用insmod將模塊加載進(jìn)內(nèi)核的時(shí)候,初始化函數(shù)的代碼將會(huì)被執(zhí)行。模塊初始化代碼只與內(nèi)核模塊管理子系統(tǒng)打交道,并不與應(yīng)用程序直接交互。
module_exit()的定義:
#define module_exit(exit_fn)
static inline exitcall_t __exitcall_##exit_fn##_wrapper(void) { return exit_fn; } void cleanup_module(void) __attribute__((alias(#exit_fn))); |
module_exit()宏接受一個(gè)退出函數(shù)作為參數(shù),并將其定義為內(nèi)聯(lián)函數(shù)__exitcall_##exit_fn##_wrapper()。然后,它使用__attribute__((alias(#exit_fn)))將cleanup_module()函數(shù)與退出函數(shù)進(jìn)行關(guān)聯(lián)。
在Linux內(nèi)核卸載模塊時(shí),會(huì)調(diào)用cleanup_module()函數(shù),它實(shí)際上是一個(gè)出口點(diǎn)函數(shù)。通過(guò)將module_exit()定義為cleanup_module()的別名,使得退出函數(shù)成為模塊卸載時(shí)的出口點(diǎn)函數(shù)。
使用module_exit()宏的目的是在驅(qū)動(dòng)程序中指定退出函數(shù),以便在模塊卸載時(shí)調(diào)用該函數(shù)執(zhí)行必要的清理操作。
幾點(diǎn)說(shuō)明:
(1)模塊退出沒(méi)有返回值;
(2)__exit標(biāo)記這段代碼僅用于模塊卸載;
(3)module_exit不是必須的。但是,沒(méi)有module_exit定義的模塊無(wú)法被卸載,如果需要支持模塊卸載則必須有module_exit;
當(dāng)使用當(dāng)使用rmmod卸載模塊時(shí),退出函數(shù)的代碼將被執(zhí)行。模塊退出代碼只與內(nèi)核模塊管理子系統(tǒng)打交道,并不直接與應(yīng)用程序交互。
(三)入口函數(shù)和出口函數(shù)實(shí)現(xiàn)
static int __init helloworld_init(void)
{ printk(KERN_INFO "Hello, World!n"); ?// 打印消息到內(nèi)核日志 return 0; } static void __exit helloworld_exit(void) { printk(KERN_INFO "Goodbye, World!n"); ?// 打印消息到內(nèi)核日志 } |
內(nèi)核加載時(shí)輸出Hello, World!,內(nèi)核卸載時(shí)輸出Goodbye, World!
(四)聲明模塊信息
MODULE_LICENSE("GPL"); ?// 指定模塊的許可證信息
MODULE_AUTHOR("Your Name"); ?// 指定模塊的作者信息 MODULE_DESCRIPTION("A simple Hello World driver"); ?// 指定模塊的描述信息 |
hello.c示例源碼
#include <linux/module.h> ??// 包含模塊相關(guān)函數(shù)的頭文件
#include <linux/kernel.h> ??// 包含內(nèi)核相關(guān)函數(shù)的頭文件 #include <linux/init.h> ????// 包含初始化和清理函數(shù)的頭文件 static int __init helloworld_init(void) { printk(KERN_INFO "Hello, World!n"); // 打印消息到內(nèi)核日志 return 0; } static void __exit helloworld_exit(void) { printk(KERN_INFO "Goodbye, World!n"); // 打印消息到內(nèi)核日志 } module_init(helloworld_init); // 指定驅(qū)動(dòng)程序的初始化函數(shù) module_exit(helloworld_exit); // 指定驅(qū)動(dòng)程序的清理函數(shù) MODULE_LICENSE("GPL"); // 指定模塊的許可證信息 MODULE_AUTHOR("Your Name"); // 指定模塊的作者信息 MODULE_DESCRIPTION("A simple Hello World driver"); // 指定模塊的描述信息 |
編譯
編譯驅(qū)動(dòng)分為兩種形式,一種是編譯現(xiàn)成模塊,生成.ko文件然后通過(guò)手動(dòng)加載;另一種是編譯到內(nèi)核,在內(nèi)核啟動(dòng)時(shí)自動(dòng)加載。一般在驅(qū)動(dòng)調(diào)試階段會(huì)編譯成模塊,這樣便于調(diào)試,不用每次都重新編譯整個(gè)內(nèi)核,本章節(jié)以編譯成模塊進(jìn)行講解。
在hello.c同級(jí)目錄下編寫(xiě)Makefile:
LINUX_KERNEL_PATH = /home/elf/work/linux-imx-imx_4.1.15_2.0.0_ga
CURRENT_PATH := $(shell pwd) CROSS_COMPILE = arm-poky-linux-gnueabi- CC = $(CROSS_COMPILE)gcc -march=armv7ve -mfpu=neon -mcpu=cortex-a7 --sysroot=/opt/fsl-imx-x11/4.1.15-2.0.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi obj-m :=hello.o all: $(MAKE) -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules clean .PHONY: modules clean |
-C:參數(shù)后面加.config文件所在的文件夾;
M:參數(shù)后面是要編譯的模塊;
LINUX_KERNEL_PATH:為要依賴內(nèi)核的目錄;
CURRENT_PATH:目前模塊的目錄;
CROSS_COMPILE:編譯器的前綴;
CC:編譯器以及參數(shù);
注意:LINUX_KERNEL_PATH的路徑要以實(shí)際源碼存放的路徑為準(zhǔn)。
注意:復(fù)制粘貼過(guò)程中可能會(huì)出現(xiàn)格式問(wèn)題。
添加后效果如下:
設(shè)置環(huán)境變量,編譯:
?. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/test/01_helloworld/hello$ make |
將生成的hello.ko文件拷貝到開(kāi)發(fā)板中測(cè)試。在開(kāi)發(fā)板中執(zhí)行insmod加載驅(qū)動(dòng),rmmod卸載驅(qū)動(dòng):
root@ELF1:~#?insmod hello.ko
Hello, World! root@ELF1:~#?rmmod hello.ko Goodbye, World! |
可以看出加載時(shí)進(jìn)入到了helloworld_init函數(shù),并打印了Hello, World!,卸載函數(shù)時(shí)進(jìn)入了helloworld_exit函數(shù)并打印了Goodbye, World!