一步一步实现Linux设备驱动的Helloworld模块
学了那么多程序语言,总是有一个Hello world开头,不禁感叹Hello world的强大。呵呵,废话少说,咋们的故事当然要从这个Hello world开始。
先查看自己OS使用的内核版本
[dongliang@dongliang:~]$ uname -r
2.6.22-14-generic /* 这是我显示的结果 */
如果安装系统时,自动安装了源码。在 /usr/src 目录下有对应的使用的版本目录。例如下(我是自己下的)
[root@localhost :/usr/src]# ls
linux-headers-2.6.22-14
linux-headers-2.6.22-14-generic
linux-source-2.6.22 /*这个就是解压后的源码目录 */
linux-source-2.6.22.tar.bz2 /* 这是我下的源码 包 */
如果没有源码。(一般ubuntu 都没有吧)
查看一下可一下载的源码包(切记不要使用超级用户使用此命令否则……会提示没有此命令)
root@localhost :/usr/src# apt-cache search linux-source
linux-source - Linux kernel source with Ubuntu patches
xen-source-2.6.16 - Linux kernel source for version 2.6.17 with Ubuntu patches
linux-source-2.6.22 - Linux kernel source for version 2.6.22 with Ubuntu patches
root@localhost :/usr/src#
我选择了 linux-source-2.6.22 - Linux kernel source for version 2.6.22 with Ubuntu patches 这个~
然后 install 之
root@localhost # sudo apt-get install linux-source-2.6.22
下载完成后,在/usr/src下,文件名为:linux-source-2.6.22.tar.bz2,是一个压缩包,解压缩既可以得到整个内核的源代码:
注意 已经切换到超级用户模式
root@localhost :/usr/src# tar -jxvf linux-source-2.6.20.tar.bz2
解压后生成一个新的目录/usr/src/linux-source-2.6.22,所有的源代码都在该目录下。
进入该目录
开始配置内核 选择最快的原版的配置(默认)方式 (我是如此)
root@localhost :/usr/src/linux-source-2.6.22# make menuconfig
当然你也可以使用 自己喜欢的配置方式 如 menuconfig , xconfig(必须有GTK环境吧)。反正不用剪裁什么,所以不管那种方式能配置它就行了。
完成后,开始make 吧 这儿比较久 一般有1一个小时吧。(保证空间足够 我编译完成后 使用了1.8G) 我分区时分给/目录30G的空间,我没遇到这问题。倒是我朋友遇到了。
root@localhost :/usr/src/linux-source-2.6.22$ make
root@localhost :/usr/src/linux-source-2.6.22$ make bzImage
当然,第一个make也可以不执行,直接make bzImage。执行结束后,可以看到在当前目录下生成了一个新的文件: vmlinux, 其属性为-rwxr-xr-x。
然后 :
root@localhost :/usr/src/linux-source-2.6.22# make modules /* 编译 模块 */
root@localhost :/usr/src/linux-source-2.6.22# make modules_install /* 安装 模块 */
执行结束之后,会在/lib/modules下生成新的目录/lib/modules/2.6.22-14-generic/
。 在随后的编译模块文件时,要用到这个路径下的build目录。至此,内核编译完成。可以重启一下系统。
至此 内核树就建立啦 原来不是很难.....
(1)linux开源当然少不了源代码的贡献,请看下边(至于什么是开源,悲剧的我现在也没整明白):
#include <linux/init.h> //所有模块代码中都包含一下两个头文件 #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); //所有模块代码都应该指定所使用的许可证 static int hello_init(void) { printk(KERN_ALERT "Hello,world\n"); return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye,Cruel world\n"); } module_init(hello_init); module_exit(hello_exit);
看到这里我们明白了,驱动程序说白了就是提供函数接口给用户空间的程序调用。在c语言中都有main()入口,那设备驱动程序的入口在哪儿呢?你猜对了,就是module_init(),它的参数是一个函数指针,告诉说咋们的入口在hello_init()。明白这层意思,module_exit()就不用多说了吧..
(2)连源码都给你了,也就不吝啬一个makefile了如下:
ifneq ($(KERNELRELEASE),) obj-m := hello.o #设置模块名字 else KERNELDIR :=/lib/modules/$(shell uname -r)/build #将目录改为内核所在目录 PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules endif clean: rm -f *.o *.ko *.mod.c .hello*
千万不要说不懂makefile,大千世界,连地图都不懂,也不知道咋混的。这里要说的是 $(MAKE) 这里一定是大写MAKE,我开始小写,怎么都过不去,郁闷啊..
$(MAKE) -C $(KERNELDIR) SUBDIRS = $(PWD) modules这句啥意思?就是说首先改变目录到-C选项指定的目录(即内核源代码目录),其中保存了内核的顶层makefile文件。SUBDIRS=选项让该makefile在构造modules目标返回之前到模块源代码目录。然后,modules目标指向obj-m变量设定的模块。(其实,这样的写Makefile命令还是有些烦人,可有啥办法呢,谁让咱们是笨鸟,聪明的方法?有,那要见下篇介绍-----聪明的makefile,嘿嘿)。
(3)好了,该有的都有了,不该有的咋一点也不贪。下面make一番:
[root@localhost~]# make
make -C /lib/modules/2.6.29.4-167.fc11.i686.PAE/build SUBDIRS=/root/device modules
make[1]: Entering directory `/usr/src/kernels/2.6.29.4-167.fc11.i686.PAE'
Building modules, stage 2.
MODPOST 1 modules
CC /root/device/hello.mod.o
LD [M] /root/device/hello.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.29.4-167.fc11.i686.PAE'
这是生成了hello.ko模块(如果没有,是有八九就是makefile有问题,还说会写,露了原形吧),接下来可以看结果了
首先再打开一个终端B(刚才make的那个不要关了,叫它A吧),像这样
[root@localhost~]# tail -f /var/log/messages
然后在A终端输入
[root@localhost~]# insmod ./hello.ko 哈哈在B终端是不是看到了localhost kernel:hello,world
然后在A终端输入
[root@localhost ~]# rmmod hello 哈哈在B终端是不是看到了localhost kernel:Goodbye,Cruel world
查看加载模块
[root@localhost ~]# lsmod
Module Size Used by
hello 2560 0
已经加载上咯~~
那程序的输出在那呢?书中说明 如果不出现在终端 则会写进 syslog 文件中
dongliang@dongliang:~/linux_驱动开发$ cat /var/log/syslog | grep world
Mar 16 12:14:53 shana kernel: [ 5937.529297] Hello, world
Mar 16 12:16:05 shana kernel: [ 6009.439036] Goodbye, cruel world
至此,一个最简单的helloworld的设备驱动演示程序就完成了,是不是挺好玩,关键是挺兴奋,这才是关键。哈哈
当工人就得劳动;当军人就的准备打仗(不然某些人老是欺负咋们了不是),所以嘛,当程序员,就一定要在最后来个说明注意什么的,烦躁啊..
那就说明吧:
(1) 这个helloworld,我建议在fedora 下实现,在centos或redhat或那些我没试过的,是有些问题的。 这主要是要在某些系统下是不支持模块的,这就要重新编译内核,选上“Enable loadable module support”,这样才可以。你偏要忘北走,我也不能用个绳子套着你喝水是吧,不管了,反正我今天关心的helloworld完成了,这难道就是传说中的责任分工,那个?呵呵,等着,我改天一定不上。
(2) 在编写makefile时,所有的换行后的空格都是tab键,而不是space(空格键)。
(3) 本例程的文件保存为 hello.c ,不然编译不通过。
学习心得:
(1)驱动模块运行在内核空间,运行时不能依赖于任何函数库和模块连接,所以在写驱动时所调用的函数只能是作为内核一部分的函数。
(2)驱动模块和应用程序的一个重要不同是:应用程序退出时可不管资源释放或者其他的清除工作,但模块的退出函数必须仔细撤销初始化函数所作的一切,否则,在系统重新引导之前某些东西就会残留在系统中。
(3)处理器的多种工作模式(级别)其实就是为了操作系统的用户空间和内核空间设计的。在Unix类的操作系统中只用到了两个级别:最高和最低级别。
(4)要十分注意驱动程序的并发处理。
(5)内核API中具有双下划线(_ _)的函数,通常是接口的底层组件,应慎用。
(6)内核代码不能实现浮点书运算。