Linux驱动有两种运行方式,第一种就是将驱动编译进Linux内核中,这样当Linux内核启动时就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux下模块扩展名为.ko),在Linux内核启动以后使用”insmod”命令加载驱动模块。
在调试的时候一般编译成模块,这样不需要编译整个Linux代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。最大的好处就是方便。
模块加载和卸载注册函数如下
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
module__init、module__exit这两个宏是定义在include/linux/init.h中:
static __init int hello _init(void) 宏就被展开为 static __section(.init.text) __cold notrace int hello_init(void)
static __exit int hello _init(void) 宏就被展开为 static __section(.exit.text) __exitused__cold notrace int hello_exit
这里的*__section*为gcc链接选项,他表示把该函数链接到Linux内核映像文件的相应段中,这样hello_init将会被链接进.init.text段中,而hello_exit将会被链接进.exit.text段中。被链接进这两段中的函数的代码在调试完之后,内核将会自动释放他们所占用的内存资源。因为这些函数只需要初始化或退出一次,所以hello_init()和hello_exit()函数做好在前面加上__init和__exit。
module_inti(hello_init)宏被展开为:
satic int (*initcall_t)(void) _initcall_hello_init6_used_attribute((section(“initcall”“6"”.init")))=hello_init
这段代码也就是定义了一个叫 __initcall_hello_init6的函数指针,他指向hello_init这个函数,gcc的链接选项__attribute__和__section__将该指针变量链接到linux内核映像的.initcall段中。linux系统在启动时,完成CPU和板级初始化之后,就会从该段中读入所有的模块初始化函数执行。每一个Linux内核模块都需要使用module_init()和module_exit()宏来修饰,这样系统启动时才能自动调用并初始化他们。
当使用”insmod”命令来加载驱动的时候,xxx_init函数就会被调用。
当使用”rmmod”命令卸载具体驱动的时候,xxx_exit函数就会被调用。
#include
#include
#include
static __init int hello_init(void)
{
printk(KERN_ALERT "hello world\n");
return 0;
}
static __exit void hello_exit(void)
{
printk(KERN_ALERT "Goodbye\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("xxx");
MODULE_AUTHOR("xxx");
定义了个名为 xxx_init 的驱动入口函数,并且使用了“__init”来修饰。
定义了个名为 xxx_exit 的驱动出口函数,并且使用了“__exit”来修饰。
调用函数 module_init 来声明 xxx_init 为驱动入口函数,当加载驱动的时候 xxx_init函数就会被调用。
调用函数module_exit来声明xxx_exit为驱动出口函数,当卸载驱动的时候xxx_exit函数就会被调用。
如架构为x86系统编写Makefile如下:
KERNAL_DIR ?= /lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
obj-m := kernel_hello.o
modules:
$(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules
@make clear
clear:
@rm -f *.o *.cmd *.mod *.mod.c
@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
@rm -f .*ko.cmd .*.o.cmd .*.o.d
@rm -f *.unsigned
clean:
@rm -f *.ko
用dmesg查看Linux内核打印信息,dmesg -c将会清除之前Linux 内核的打印信息
安装Linux模块,并查看内核打印信息:
111@ubuntu:~/x86$ sudo insmod kernel_hello.ko
111@ubuntu:~/x86$ sudo rmmod kernel_hello
111@ubuntu:~/x86$ sudo insmod kernel_hello.ko
111@ubuntu:~/x86$ dmesg | tail -3
[1120961.091209] hello world
[1121083.590510] Goodbye
[1121093.936166] hello world
用lsmod命令查看当前linux内核安装了的内核模块
KERNAL_DIR是指定开发板所运行的源码路径,并且这个linux内核源码必须make menuconfig并且make过的,因为Linux内核的一个模块可能依赖于另一个模块,如果另一个没有编译则出问题。所以Linux内核必须编译过,这样才能确认这种依赖关系;
obj-m +:= kernel_hello.o 该行告诉Makefile要将kernel_hello.c源码编译生成内核模块kernel_hello.ko;
$(MAKE) -C ( K E R N A L D I R ) M = (KERNAL_DIR) M= (KERNALDIR)M=(PWD) modules ,-C:把工作目录切换到-C后面指定的参数目录,M是Makefile里面的一个变量,作用是回到当前目录继续读取Makefile。当前使用make命令编译内核驱动模块时,将会进入到KERNAL_DIR指定的linux内核源码中去编译,并在当前目录下生成很多临时文件以及驱动模块文件kernel_hello.ko;
clear目标将编译linux内核过程生成的一些临时文件全部删掉;