学习一个新东西最好的方式就是去实践它。在实践的过程中会不断遇到问题、产生疑问。解决这些问题的过程就是我们进步成长的过程。我们今天就来尝试写一个驱动程序吧!
在学习C语言的时候有一个著名的hello world程序。在学习驱动程序的时候我们也可以写一个类似的小程序,它的源码如下:
//file name module_ts.c
#include
#include
static int __init mod_init(void)
{
printk(KERN_ALERT"hello, kernel\n");
return 0;
}
static void __exit mod_exit(void)
{
printk(KERN_ALERT"bye, kernel\n");
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("[email protected]");
MODULE_VERSION("v0.1");
MODULE_DESCRIPTION("this is just a demo program");
好了,我们就由这个小程序来开始我们的驱动程序之旅吧!由于以前没接触过内核编程,好多东西都觉得好陌生啊。没关系,下面就来为大家一一解答吧!
首先我们看到程序的函数的主题其实就是由两个函数构成的tatic int __init mod_init(void) 和 static void __exit mod_exit(void)。这两个函数头和我们之前遇到的函数头相比多了__init和__exit,这是两个标记(token),他们对内核来说是一种暗示:
__init表明该函数仅在初始化期间使用。在模块被装载之后,模块装载器就会将初始化函数扔掉,这样可将该函数占用的内存释放出来。
__exit表明该函数只能在模块被卸载或者系统关闭时被调用,其他任何用法都是错误的。
这两个函数都调用了printk函数,printk是在内核中定义的功能和printf类似的一个函数。KERN_ALERT指定了这条打印消息的优先级。注意:KERN_ALERT和后面的格式字符串之间是没有逗号的。
module_init(mod_init)和module_exit(mod_exit)是两个宏,这两个宏指定我们的驱动程序在被加入内核中和从内核中移除时所要调用的函数。从字面上我们就可以知道我们定义的两个函数一个是初始化函数,一个是清除函数。
大部分的内核代码中都要包含相当数量的头文件,以便获得函数、数据结构和变量的定义。程序中所使用的两个头文件是专门用于驱动程序的,module.h包含有可装载模块需要的大量符号和函数定义,module_init和module_exit这两个宏包含在init.h中。我们写的所有驱动程序都应该包含下面两行代码:
#include
#include
程序最底下的几个宏其中MODULE_LICENSE是指定驱动程序所使用的许可证。常用的许可证有”GPL”/”GPL v2”/”Dual BSD/GPL”等,我们这里用的是GPL许可证。
由于驱动程序要使用到内核中的函数符号等,所以在编译之前先在系统中安装linux内核头文件。我使用的编译平台是VMware+Ubuntu10.04。可用下面的命令来安装和你内核对应的头文件。
sudo apt-get install linux-headers-$(uname -r)驱动程序和普通程序的构造过程有很大的不同,由于本人也是刚开始学习对其具体细节还不大了解,暂时就先将Makefile贴下:
KO_NAME=module_ts
PWD:=$(shell pwd)
KERNEL_BUILD_PATH=/usr/src/linux-headers-2.6.32-38-generic
obj-m += $(KO_NAME).o
default:
$(MAKE) -C $(KERNEL_BUILD_PATH) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNEL_BUILD_PATH) M=$(PWD) clean
KO_NAME指示我们生成的目标文件的名字,KERNEL_BUILD_PATH是我们刚安装的kernel头文件的路径。Make之后会生成下面的文件:
其中的modules_ts.ko文件是我们最终生成的驱动程序文件,其他文件都是一些中间文件。
我的前一篇文章Linux设备驱动程序学习笔记01:设备驱动程序简介中列出了应用程序,操作系统,驱动程序以及硬件设备之间的关系。可以发现驱动程序是被包含在操作系统里面的。那我们要怎么运行这个程序呢?
Linux提供了一个特性:内核可在运行时进行扩展。这意味着,当系统启动并运行时,我们可以向添加功能(当然也可以移除功能)。可在运行时添加到内核中的代码称为模块,每个模块由目标代码组成(没有链接成一个完整的可执行程序),我们刚生成的module_ts.ko文件(ko其实就是kernel object )就是一个模块。我们可以使用insmod程序将我们的模块添加到内核,也可以使用rmmod程序将它从内核中移除。
我们执行sudo insmod module_ts.ko 命令,发现没有任何打印信息。这是因为printk输出到我们的调试信息文件中了。我们执行dmesg命令,在输出的最后可以看到: hello, kernel 信息。执行lsmod命令可以看到module_ts模块的确已经加载到内核了。最后,我们还可以用sudo rmmod module_ts 命令将我们的模块从内核中卸载。
至此,驱动程序的编写,编译,加载的内容我们已经全部介绍完了。编写一个驱动程序似乎并不是很难吧,其实编写驱动程序最大的困难在于理解实际的设备并最大化其性能。
参考资料:
[1].《Linux设备驱动程序》第三版 JonathanCorbet etc. 魏永明等译 中国电力出版社