linux驱动学习----模块编程

我的博客: 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)用来表示内核,该模块采用自由许可证;如果没有这样的声明,内核在装载该模块时会产生抱怨。还有很多类似的特殊宏,如下 表所示:

                          特殊宏                         说明                    
MODULE_AUTHOR(author) 描述模块作者
MODULE_DESCRIPTION(description) 说明模块用途的简短描述
MODULE_VERSION(version_string) 代码修订号
MODULE_DEVICE_TABLE(table_info) 告诉用户空间模块所支持的设备
MODULE_ALIAS(alternate_name) 模块的别名

在上面,我们似乎看见了一个熟悉的身影---printk()。呵呵,看清楚,不是printf哦。但是看它们的样子这么 像,应该是兄弟吧。是的,它们的功能都是打印输出。而printk是用在内核中的,因为内核在运行时不能依赖于C库。而字符串KERN_ALERT定义了 这条消息的优先级。我们需要在模块代码中显式地指定高优先级的原因是:具有默认优先级的消息可能不会输出在控制台上。这主要依赖于内核版本。

介绍完最简单的驱动模块,我们还是要来了解一点内在的东西---核心模块与应用程序的对比。

  1. 执行过程中的不同:大多数小规模及中规模应用程序是从头到尾执行单个任务,而模块却只是预先注册自己以便服务于将来的某个请求,然后它的初始化函 数就立即结束。换言之,模块初始化函数的任务就是为以后调用模块函数预先做准备。最后在退出时,应用程序可以不管资源的释放或者其他的清除工作。但是模块 必须仔细撤销初始化函数所做的一切,否则,在系统重新启动之前某些东西就会残留在系统中。
  2. 调用函数的不同:应用程序可以调用它未定义的函数,因为应用程序可以在连接过程中解析外部引用从而使用适当的函数库。可是反观模块,它仅仅被连接到内核,因此只能调用由内核导出的函数,不存在任何可连接的函数库。

我们接下来继续往前走,编写完模块之后,我们要做就是对模块程序进行编译和加载。那么此时,我们就需要写一个简单的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。当然仅仅有这些是不够的,无法做的更多,因而,我会在下一次中去扩充。

你可能感兴趣的:(linux)