1、实验二:Linux 驱动初始模块【1】查询 Linux 内核#uname -r2.6.31-14-generic#ls /usr/src/linux-headers-2.6.31-14 linux-headers-2.6.31-14-generic由此可见内核版本和内核头文件版本是一致的,都是 2.6.31-14。 (如果不一致的话在insmod 一步必定出错:Error inserting ./hello.ko: -1 Invalid module format【2】编写 hello.c新建 hello.c 文件,加入以下内容:#include #include #include /使用 p
2、rintk()需要包含此文件MODULE_LICENSE(“Dual BSD/GPL“);static int hello_init(void)printk(“hello worldn“);return 0;static void hello_exit(void)printk(“Good bye worldn“);module_init(hello_init);module_exit(hello_exit);MODULE_AUTHOR(“name“);MODULE_DESCRIPTION(“A simple hello Module“);MODULE_VERSION(“V1.0“);这个最简单
3、的内核模块只包含内核加载函数、卸载函数和对 Dual BSD/GPL 许可权限的声明以及一些描述信息。【3】模块的编译Linux 驱动程序的 Makefile 与一般的应用程序 Makefile 有所不同,驱动 Makfile 要指定内核源代码位置,先看一个简单的驱动 Makefile:#如果已定义 KERNELRELEASE,则说明是从内核构造系统调用的,因此可利用其内建语句。ifneq ($(KERNELRELEASE),)obj-m := hello.o#否则,是直接从命令行调用的,这时要调用内核构造系统elseKERNELDIR = /usr/src/linux-headers-2.6
4、.31-14-generic/PWD := $(shell pwd)default:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean:rm -rf *.o * core .depend .*.cmd *.ko *.mod.cendif下面逐一分析一下各个语句:obj-m := hello.o这句意为有一个模块需要从目标文件 hello.o 中构造,构造的模块名称为 hello.ko。KERNELDIR = /usr/src/linux-headers-2.6.31-14-generic/这里是定义一个变量 KERNELDIR,并且赋值为/usr/src
5、/linux-headers-2.6.31-14-generic。即当前内核的源代码目录。PWD := $(shell pwd)获取当前目录了。总之在 Makefile 里,$(shell xxx) 就是相当于在终端中执行 xxx 命令。$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesKERNELRELEASE 是在内核源码的顶层 Makefile 中定义的一个变量,在第一次读取执行此 Makefile 时,KERNELRELEASE 没有被定义,所以 make 将读取执行 else 之后的内容。当 make 的目标为 all 时,-C $(KERNELDIR)
6、指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的 Makefile。当从内核源码目录返回时,KERNELRELEASE 已被被定义,kbuild 也被启动去解析kbuild 语法的语句,make 将继续读取 else 之前的内容。else 之前的内容为 kbuild 语法的语句,指明模块源码中各文件的依赖关系,以及要生成的目标模块名。(这就是编译模块了:首先改变目录到-C 选项指定的位置(即内核源代码目录) ,其中保存有内核的顶层 makefile;M=选项让该 makefile 在构造 modules 目标之前返回到模块源代码目
7、录;然后,modueles 目标指向 obj-m 变量中设定的模块;在上面的例子中,我们将该变量设置成了 hello.o。 )【4】开始 make#make输出类似以下消息:make -C /usr/src/linux-headers-2.6.31-14-generic/ M=/home/book/hello modulesmake1: Entering directory /usr/src/linux-headers-2.6.31-14-genericCC M /home/book/hello/hello.oBuilding modules, stage 2.MODPOST 1 module
8、sCC /home/book/hello/hello.mod.oLD M /home/book/hello/hello.komake1: Leaving directory /usr/src/linux-headers-2.6.31-14-generic则编译成功,用 ls 查看应该可以看到生成一堆文件,如:hello.c hello.ko hello.mod.c hello.mod.o hello.o Makefile Module.markers modules.order Module.symvers【5】安装模块:#insmod hello.ko用 lsmod 命令也能查看到 hell
9、o 模块。Module Size Used byhello 1148 0 binfmt_misc 8356 1 nfsd 241100 9 exportfs 4412 1 nfsdsnd_ens1371 22016 2 lsmod命令实际上读取并分析/proc/modules文件,与上述lsmod命令结果对应的。/proc/modules文件如下:#cat /proc/modules hello 1148 0 - Live 0xe086b000binfmt_misc 8356 1 - Live 0xe0855000nfsd 241100 9 - Live 0xe0c85000exportfs
10、4412 1 nfsd, Live 0xe08d9000snd_ens1371 22016 2 - Live 0xe08e7000使用modinfo 命令可以获得模块的信息,包括模块的作者、模块的说明、模块所支持的参数以及vermagic,如下所示:#modinfo hello.kofilename: hello.koversion: V1.0description: A simple hello Module author: namelicense: Dual BSD/GPLsrcversion: 16BA31B053E9BEDA151A9DFdepends: vermagic: 2.6.3
11、1-14-generic SMP mod_unload modversions 586【6】卸载模块#rmmod hello.ko再用#lsmod发现 hello 模块已经不存在了。【7】关于 printk现在谈谈这个问题:insmod 的时候,printk 问什么没有在终端中显示 Hello World 呢?因为hello.c 中明明是这么写的:static int hello_init(void)printk(“Hello World!n“);return 0;内核模块中用于输出的函数式内核空间的 printk()而非用户空间的 printf(),printk()的用法和 printf()
12、相似,但前者可定义输出级别。Printk()可作为一种最基本的内核调试手段。printk 有 8 个 loglevel,定义在 中:#define KERN_EMERG “ /* system is unusable */#define KERN_ALERT “ /* action must be taken immediately*/#define KERN_CRIT “ /* critical conditions */#define KERN_ERR “ /* error conditions */#define KERN_WARNING “ /* warning conditions
13、*/#define KERN_NOTICE “ /* normal but significant condition */#define KERN_INFO “ /* informational */#define KERN_DEBUG “ /* debug-level messages */未指定优先级的默认级别定义在/kernel/printk.c 中:#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */当优先级的值小于 console_loglevel 这个整数变量的值,信息才能显示出来。而console_loglevel 的初始值
14、 DEFAULT_CONSOLE_LOGLEVEL 也定义在/kernel/printk.c 中:#define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */【8】printk 调试信息打印的补救方法用命令:#dmesg | tail -8这个命令格式类似于 lsmod(只显示最后 8 行) 。 95.586960 Good bye, ubuntu 96.483964 Hello World! 97.031787 Good bye, ubuntu 97.594151 Hello World! 98.
15、109896 Good bye, ubuntu 98.615569 Hello World! 99.098943 Good bye, ubuntu 1893.976170 Hello World!以上的信息显示:在 95.586960 做了 rmmod hello.ko,在 96.483964 做了 insmod hello.ko这里中的数字表示执行模块安装/卸载聚开机的时间,如 96.483964 S(约开机一个半小时以后) 。【9】那么这些信息都存在哪儿呢?都说默认是在/val/log/messages 目录下;可是有时候你看了,没有;那么请看看另一个目录:#cd /etc#ls你会找到包
16、含 syslog 的文件,如:syslog.conf那么就是它了。#cat syslog.conf | head -20同理,只显示前 20 行的东东;看到类似以下的东东:# /etc/syslog.conf Configuration file for syslogd.# For more information see syslog.conf(5)# manpage.# First some standard logfiles. Log by facility.#auth,authpriv.* /var/log/auth.log*.*;auth,authpriv.none -/var/lo
17、g/syslog#cron.* /var/log/cron.logdaemon.* -/var/log/daemon.logkern.* -/var/log/kern.loglpr.* -/var/log/lpr.logmail.* -/var/log/mail.loguser.* -/var/log/user.log# Logging for the mail system. Split it up so that.看到 kern.*了吧看到/var/log/kern.log 了吧!于是,# cat /var/log/kern.log | tail -10显示:Aug 4 23:40:53
18、ubuntu kernel: 38.848634 eth0: no IPv6 routers presentAug 4 23:41:47 ubuntu kernel: 92.893834 Hello World!Aug 4 23:41:50 ubuntu kernel: 95.586960 Good bye, ubuntuAug 4 23:41:51 ubuntu kernel: 96.483964 Hello World!Aug 4 23:41:51 ubuntu kernel: 97.031787 Good bye, ubuntuAug 4 23:41:52 ubuntu kernel: 97.594151 Hello World!Aug 4 23:41:52 ubuntu kernel: 98.109896 Good bye, ubuntuAug 4 23:41:53 ubuntu kernel: 98.615569 Hello World!Aug 4 23:41:53 ubuntu kernel: 99.098943 Good bye, ubuntuAug 5 00:11:48 ubuntu kernel: 1893.976170 Hello World!跟先前的 dmesg 显示的内容相似而更完整了!