我的博客: http://www.yewusishi.com/ 希望大家指教
写在前面的话:
linux的学习有很多的切入点,但是从我学习的观点来看,设备驱动是一个不错的切入点。
至于理由,个人觉得最重要的一点就是容易去实践。众所周知,读万卷书不如行万里路。研究linux也是如此,只有在实践中摸索,在实践中将自己所学的知识融会贯通。而设备驱动的学习可以做到这一点。
在讲述设备驱动程序的书籍中,开篇都会讲到两个词的概念--机制和策略。
我们先来看看ldd3中对它们的定义:
机制---需要提供什么功能
策略---如何使用这些功能
这样就很清楚了啊,我们驱动程序提供的是一种机制,而所谓的策略则是我们的上一层去解释,在不同的场合去应用这些机制。
因此,在这里,我们引用ldd3中的一句话:编写访问硬件的内核代码时,不要给用户强加任何的策略。
好了,废话不多说了,我们就直接切入主题吧。
作为一个菜鸟,我也就只能将我自己的一些见解和学习中的问题和大家进行讨论,如有不足之处,请大家多多批评指教。
最先我们接触的就是我们今天重点要讲的模块编程。
是不是很想动手写点什么呢?不要急,稳住心态。工欲善其事,必先利其器。我们似乎还缺少了点什么。对了,就是驱动程序运行的测试系统。我们总不能让自己写的驱动“无家可归”吧。因此,我们首先要构建我们的内核源码树。
或许你是第一次看到这个名词,那么我建议你先去google一下吧,可以找到很多相关的资料。在这里,我只简单的介绍一下。
所谓构建源码树,按我的理解,通俗一点讲,就是下载内核,配置和编译内核,编译和安装模块。最后会在/lib/modules目录下生成我们以后要用的东西。这里我先不展开了啊。
不过我们在更多时候需要的是一种交叉编译的系统。如果对交叉编译还有疑惑的童鞋,先去了解一下吧,我也会写一篇关于这方面的文章供大家参考。其实交叉编译和本地编译最大的区别还是在于一些编译参数上,因而,也没必要觉得它会很难。
好了,假设我们的编译已经OK,我们接下去要做的就是我们期盼已久的编程了。
每种语言的入门程序都是Hello World。那么我们这里也不例外啊。先来看一下Hello World 模块的代码。
1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("Dual BSD/GPL");
4
5 static int hello_init(void)
6 {
7 printk(KERN_ALERT "Hello, world\n");
8 return 0;
9 }
10
11 static void hello_exit(void)
12 {
13 printk(KERN_ALERT "Goodbye, cruel world\n");
14 }
15
16 module_init(hello_init);
17 module_exit(hello_exit);
我们可以看见上面的代码中定义了两个函数,其中一个函数在模块被装载到内核时调用(hello_init),而另一个则在模块移出时调用 (hello_exit)。module_init和module_exit行使用了内核的特殊宏来表示上述两个函数所扮演的角色。另外一个特殊宏 (MODULE_LICENSE)用来表示内核,该模块采用自由许可证;如果没有这样的声明,内核在装载该模块时会产生抱怨。还有很多类似的特殊宏,如下表所示:
我们可以看见上面的代码中定义了两个函数,其中一个函数在模块被装载到内核时调用(hello_init),而另一个则在模块移出时调用 (hello_exit)。module_init和module_exit行使用了内核的特殊宏来表示上述两个函数所扮演的角色。另外一个特殊宏 (MODULE_LICENSE)用来表示内核,该模块采用自由许可证;如果没有这样的声明,内核在装载该模块时会产生抱怨。还有很多类似的特殊宏,如下 表所示:
在上面,我们似乎看见了一个熟悉的身影---printk()。呵呵,看清楚,不是printf哦。但是看它们的样子这么 像,应该是兄弟吧。是的,它们的功能都是打印输出。而printk是用在内核中的,因为内核在运行时不能依赖于C库。而字符串KERN_ALERT定义了 这条消息的优先级。我们需要在模块代码中显式地指定高优先级的原因是:具有默认优先级的消息可能不会输出在控制台上。这主要依赖于内核版本。
介绍完最简单的驱动模块,我们还是要来了解一点内在的东西---核心模块与应用程序的对比。
我们接下来继续往前走,编写完模块之后,我们要做就是对模块程序进行编译和加载。那么此时,我们就需要写一个简单的Makefile。先来看一下下面的Makefile。
1 #如果已经定义KERNELRELEASE,则说明是从内核构造系统调用的,
2 #因此,可利用其内建语句
3 ifneq ($(KERNELRELEASE),)
4 obj-m := hello.o
5 #否则,是从直接从命令行调用的,
6 #这时要调用内核构造系统。
7 else 8 #这就是我们的内核源码树的路径
9 KERNELDIR := ~/zhoubb/linux-3A/lib/modules/2.6.36-master.lemote/build
10 PWD := $(shell pwd)
11 default:
12 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
13 clean:
14 rm -rf *.o *.mod.* *.ko *.order *.symvers
15 endif
这就是我在编译Hello World模块时所用的Makefile文件。当然,你如果要使用的话,记得要将KERNELDIR替换成自己的内核源码树路径哦。
好,现在我们就来演示一下模块的编译,加载和卸载。简单一点,我们就只演示本地编译的吧。
1。编译hello.c文件
1 root@yafeng-VirtualBox:~/hello# ls 2 hello.c Makefile
3 root@yafeng-VirtualBox:~/hello# make /*编译*/
4 make -C /lib/modules/3.0.0-12-generic/build M=/root/hello modules
5 make[1]: Entering directory `/usr/src/linux-headers-3.0.0-12-generic`
6 CC [M] /root/hello/hello.o
7 Building modules, stage 2.
8 MODPOST 1 modules
9 CC /root/hello/hello.mod.o
10 LD [M] /root/hello/hello.ko
11 make[1]: Leaving directory `/usr/src/linux-headers-3.0.0-12-generic`
12 root@yafeng-VirtualBox:~/hello# ls 13 hello.c hello.ko hello.mod.c hello.mod.o hello.o Makefile modules.order Module.symvers
14 root@yafeng-VirtualBox:~/hello#
上面的hello.ko就是我们最终需要的。
2.加载hello.ko
1 root@yafeng-VirtualBox:~/hello#
2 root@yafeng-VirtualBox:~/hello# ls 3 hello.c hello.ko hello.mod.c hello.mod.o hello.o Makefile modules.order Module.symvers
4 root@yafeng-VirtualBox:~/hello# insmod hello.ko
5 root@yafeng-VirtualBox:~/hello# lsmod 6 Module Size Used by
7 hello 12393 0
8 vboxvideo 12511 1
9 drm 192226 2 vboxvideo
10 vesafb 13489 1
11 vboxsf 38117 1
我们可以看见hello模块已经加载到了系统。可是我们并没有看见我们预期打印的语句!这是和系统设置的打印级别有关,现在我们就去/var/log/syslog文件去看看是不是有我们想要的打印语句。
1 root@yafeng-VirtualBox:/var/log#
2 root@yafeng-VirtualBox:/var/log# cat syslog | grep world
3 Oct 29 16:58:02 yafeng-VirtualBox kernel: [ 4917.585665] Hello,world!
4 root@yafeng-VirtualBox:/var/log#
有没有发现还少一句啊?是的,因为你没有卸载模块啊!
3.卸载hello.ko
root@yafeng-VirtualBox:~/hello#
root@yafeng-VirtualBox:~/hello# rmmod hello
root@yafeng-VirtualBox:~/hello# lsmod Module Size Used by vboxvideo 12511 1
drm 192226 2 vboxvideo
vesafb 13489 1
然后再来看看syslog文件:
1 root@yafeng-VirtualBox:/var/log#
2 root@yafeng-VirtualBox:/var/log# cat syslog | grep world
3 Oct 29 16:58:02 yafeng-VirtualBox kernel: [ 4917.585665] Hello,world!
4 Oct 29 16:58:25 yafeng-VirtualBox kernel: [ 4941.391066] GoodBey,world!
5 root@yafeng-VirtualBox:/var/log#
总结:
本文从整个驱动模块的准备工作开始到模块的卸载,比较初略的理了一遍。给出的例程里其实什么都没有去完成,只是简简单单的打印 了数据。但是麻雀虽小,五脏俱全,该程序中已经包含了一个驱动模块最基本的 东西----init和exit。当然仅仅有这些是不够的,无法做的更多,因而,我会在下一次中去扩充。