例程代码路径:ELF 1开发板资料包\03-例程源码\03-2 驱动例程源码\01_helloworld\hello 本章节先写一个helloworld驱动,不涉及对硬件的操作,它的目的是展示驱动程序的基本结构和加载过程。 源码编写 (一)首先包含头文件 #include // 包含模块相关函数的头文件 #include // 包含内核相关函数的头文件 #include // 包含初始化和清理函数的头文件 (二)驱动模块入口和出口函数 module_init(helloworld_init); // 指定驱动程序的初始化函数 module_exit(helloworld_exit); // 指定驱动程序的清理函数 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()宏接受一个初始化函数作为参数,并将其定义为内联函数__initcall_##init_fn##_wrapper()。然后,它使用__attribute__((alias(#init_fn)))将init_module()函数与初始化函数进行关联。 在Linux内核加载模块时,会调用init_module()函数,它实际上是一个入口点函数。通过将module_init()定义为init_module()的别名,使得初始化函数成为模块加载时的入口点函数。 使用module_init()宏的目的是在驱动程序中指定初始化函数,以便在模块加载时调用该函数执行必要的初始化操作。 当使用insmod将模块加载进内核的时候,初始化函数的代码将会被执行。模块初始化代码只与内核模块管理子系统打交道,并不与应用程序直接交互。 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()宏接受一个退出函数作为参数,并将其定义为内联函数__exitcall_##exit_fn##_wrapper()。然后,它使用__attribute__((alias(#exit_fn)))将cleanup_module()函数与退出函数进行关联。 在Linux内核卸载模块时,会调用cleanup_module()函数,它实际上是一个出口点函数。通过将module_exit()定义为cleanup_module()的别名,使得退出函数成为模块卸载时的出口点函数。 使用module_exit()宏的目的是在驱动程序中指定退出函数,以便在模块卸载时调用该函数执行必要的清理操作。 几点说明: (1)模块退出没有返回值; (2)__exit标记这段代码仅用于模块卸载; (3)module_exit不是必须的。但是,没有module_exit定义的模块无法被卸载,如果需要支持模块卸载则必须有module_exit; 当使用当使用rmmod卸载模块时,退出函数的代码将被执行。模块退出代码只与内核模块管理子系统打交道,并不直接与应用程序交互。 (三)入口函数和出口函数实现 static int __init helloworld_init(void) { printk(KERN_INFO "Hello, World!\n"); // 打印消息到内核日志 return 0; }
static void __exit helloworld_exit(void) { printk(KERN_INFO "Goodbye, World!\n"); // 打印消息到内核日志 } 内核加载时输出Hello, World!,内核卸载时输出Goodbye, World! (四)声明模块信息 MODULE_LICENSE("GPL"); // 指定模块的许可证信息 MODULE_AUTHOR("Your Name"); // 指定模块的作者信息 MODULE_DESCRIPTION("A simple Hello World driver"); // 指定模块的描述信息 hello.c示例源码 #include // 包含模块相关函数的头文件 #include // 包含内核相关函数的头文件 #include // 包含初始化和清理函数的头文件
static int __init helloworld_init(void) { printk(KERN_INFO "Hello, World!\n"); // 打印消息到内核日志 return 0; }
static void __exit helloworld_exit(void) { printk(KERN_INFO "Goodbye, World!\n"); // 打印消息到内核日志 }
module_init(helloworld_init); // 指定驱动程序的初始化函数 module_exit(helloworld_exit); // 指定驱动程序的清理函数
MODULE_LICENSE("GPL"); // 指定模块的许可证信息 MODULE_AUTHOR("Your Name"); // 指定模块的作者信息 MODULE_DESCRIPTION("A simple Hello World driver"); // 指定模块的描述信息
编译 编译驱动分为两种形式,一种是编译现成模块,生成.ko文件然后通过手动加载;另一种是编译到内核,在内核启动时自动加载。一般在驱动调试阶段会编译成模块,这样便于调试,不用每次都重新编译整个内核,本章节以编译成模块进行讲解。 在hello.c同级目录下编写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:参数后面加.config文件所在的文件夹; M:参数后面是要编译的模块; LINUX_KERNEL_PATH:为要依赖内核的目录; CURRENT_PATH:目前模块的目录; CROSS_COMPILE:编译器的前缀; CC:编译器以及参数; 注意:LINUX_KERNEL_PATH的路径要以实际源码存放的路径为准。 注意:复制粘贴过程中可能会出现格式问题。 添加后效果如下:
设置环境变量,编译: . /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文件拷贝到开发板中测试。在开发板中执行insmod加载驱动,rmmod卸载驱动: root@ELF1:~# insmod hello.ko Hello, World! root@ELF1:~# rmmod hello.ko Goodbye, World!
可以看出加载时进入到了helloworld_init函数,并打印了Hello, World!,卸载函数时进入了helloworld_exit函数并打印了Goodbye, World!
|