驱动程序是系统的重要组成部分,Linux中所有对硬件的的操作都是基于驱动程序的。驱动程序可以直接编译进内核也可以使用module的方式手动添加到内核之中,为了学习方便,我将使用模块module的方式将模块加载到内核。
编写驱动的第一步是搭建环境,我有一台运行Ubuntu16的虚拟机,可以直接在Ubuntu上进行驱动模块的编写工作,因为Ubuntu下已经有完整的Linux源代码和头文件,在目录/lib/modules/和/usr/src/下,具体不知道两个目录的区别,可能是通过连接文件的链接功能使得最终两个目录的文件其实是一样的。源码目录下有多个文件夹,表示多个不同的Linux内核版本,需要选择与机器匹配的内核版本才能保证编译出来的模块可以正确使用,查看本机的Linux内核版本的方法为:uname -r,出现机器内核版本,例如我的机器为“4.4.0-53-generic”,这样选择源码目录的时候就需要指定4.4.0-53-generic版本。假如,虽然我没有遇到过,机器中没有找到源码,可以从Linux官方网站上下载对应内核版本的Linux源码包,这个源码包就可以作为模块编写的环境了。有了源码路径之后基本上就算搭建好了模块设计的开发环境了,因为我不是使用交叉编译,我是在本机上进行模块设计,编写的模块给本机使用,如果是为嵌入式Linux系统编写模块的话需要有嵌入式Linux的源码包和嵌入式Linux的编译器,也就是大名鼎鼎的arm-linux-gcc编译器,关于嵌入式Linux的模块编写以后在说,这里处于学习的目的现在PC电脑上进行实验,需要注意的是树莓派以及类似的运行Debian或者Ubuntu的π都可以直接在本身Linux环境中进行驱动模块的开发,因为它们本身就是一个完整的计算机系统。
接下来就可以进行驱动模块的编写了,驱动程序的编写和应用程序不同,它没有main函数,只有初始化、释放、操作函数,显得很复杂的样子,但是只要清楚了驱动程序的原理,一切还是很明朗的,至少我是这么想的,虽然我还没很明朗。下面是一个最简单的驱动程序:
#include <linux/init.h> /* printk() */
#include <linux/module.h> /* __init __exit */
static int __init hello_init(void) /*模块加载函数,通过insmod命令加载模块时,被自动执行*/
{
printk(KERN_INFO " Hello World enter\n");
return 0;
}
static void __exit hello_exit(void) /*模块卸载函数,当通过rmmod命令卸载时,会被自动执行*/
{
printk(KERN_INFO " Hello World exit\n ");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR("tangquan"); /*模块作者,可选*/
MODULE_LICENSE("Dual BSD/GPL"); /*模块许可证明,描述内核模块的许可权限,必须*/
MODULE_DESCRIPTION("A simple Hello World Module"); /*模块说明,可选*/
MODULE_ALIAS("a simplest module"); /*模块说明,可选*/
驱动程序源码完成之后就可以进行编译了,这里使用和编译应用程序一样的方法使用Makefile文件自动编译,所以需要先编辑Makefile文件:
obj-m := hello.o
module-objs := hello.o
KDIR :=/lib/modules/4.4.0-51-generic/build/
#KDIR :=/usr/src/linux-headers-4.4.0-53-generic/
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
@rm -rf *.mod.*
@rm -rf .*.cmd
@rm -rf *.o
@rm -rf Module.*
clean:
rm -rf *.ko
obj-m := hello.o表示的是目标文件,这样会生成hello.ko文件。
module-objs := hello.o表示连接的文件。
KDIR表示Linux源码路径。
PWD就是驱动程序的路径。
关于编译模块出错参考上一篇文章:http://blog.csdn.net/tq384998430/article/details/53994637
Linux驱动编写参考文章:http://www.linuxidc.com/Linux/2011-10/44721.htm