最简单的hello,world程序,在内核上挂载和卸载我们自己编写的hello模块。说明:本文调试环境fc4,教材是《Linux Device Drivers》第3版英文电子图书。
一、编写hello.c文件
利用vi编辑器,我们键入下面的代码,并保存为hello.c文件。
/**
* hello.c
* ------Test for kernel module
*/
#i nclude
#i nclude
MODULE_LICENSE("GPL"); //(1)
static int hello_init(void) //(2)
{
printk(KERN_ALERT "Hello, world\n"); //(3)
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init); //(4)
module_exit(hello_exit); //(5)
对上面这段代码中有5个地方需要加以说明:
(1):本句代码可以不要,但不要的话,运行时会出现"hello: module license 'unspecified' taints kernel.",词典上对taints的解释是"感染,污点".嘿嘿,还要注意的一点是,这句不要写到最后,放到(5)后面,还是会出现内核污染的提示
(2):我们可以看出,对模块内的函数一般需要加上static关键字进行修饰,这样可防止模块外访问该函数,这是应该养成的一个好习惯。
(3):printk函数相当于C标准库函数printf, KERN_ALERT是指的输出消息的优先级别,且KERN_ALERT优先级别最高。
(4)(5):模块初始化和模块退出时有专门的函数,我们只需要为这两个函数指定初始化和退出时的具体调用的函数名即可。
二、编译
编译一般采用makefile,利用make命令自动编译。编写下面的makefile:
# Makefile for hello module
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules
endif
clean:
$(shell rm -f *.bak)
$(shell rm -f hello.o)
$(shell rm -f hello.ko)
$(shell rm -f hello.mod.c)
$(shell rm -f hello.mod.o)
对这段脚本需要说明下面几点:
(1)、将上面的脚本保存为Makefile,注意必须保存为M为大写的Makefile。这是因为编译的时候首先看环境变量KERNELRELEASE 是否定义,如果没定义则调用Linux内核编译build脚本。该脚本会首先编译内核,其间会创建环境变量KERNELRELEASE,接着编译当前工作目录下的hello模块,此时会第二遍读取Makefile,再次判断环境变量KERNELRELEASE是否定义,已经定义的情况下开始编译hello 模块。
如果将该脚本保存为小写m开头的makefile,编译时会出现这样的提示:
scripts/Makefile.build:13: /root/zhou/Makeifle: No such file or directory.
(2)、因为build脚本会首先判断有无必要重新编译内核,所以如果需要先重新编译内核的话,编译过程会运行一段时间。
(3)、在Makefile中行首缩进需要用Tab键,否则会出问题(这个问题曾经折磨我很长一段时间,后来改用Tab键缩进,问题一下子就得到了解决)。
(4)、在default部分,原书中原来的语句是:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
利用man make命令查看make的使用手册发现,对-m的说明:
-m These options are ignored for compatibility with other version of make.
即-m参数被忽略掉了,所以编译时会出现:
Nothing to be done for '/root/zhou'.
为了避免上述问题,可将M=$(PWD)改成SUBDIRS=$(PWD),即重新定义环境变量SUBDIRS,供编译时使用。
编译完成后,在当前工作目录下会多了几个文件:hello.o, hello.ko, hello.mod.c, hello.mod.o。其中hello.ko就是我们需要的。
三、运行
必须有root用户的权限,才能进行内核模块操作。
(1)、挂载模块
执行insmod hello.ko命令,即可挂载并运行hello模块。insmod意思是insert module。成功挂载上hello模块后,会输出:
Hello, world
(2)、卸载模块
执行rmmod hello命令(也可以执行rmmod hello.ko命令),即可卸载模块。rmmod意思是remove module。成功卸载模块后,会输出:
Goodbye, cruel world
注意,这里的printk消息,一般界面下是看不到的,它放在log文件里:/var/log/messages
可这样查看: tail -n 10 /var/log/messages
或者
四、总结
1、在linux下编程一定要注意大小写。
2、编写Makefile文件要注意用Tab键缩进。
3、linux模块化构架非常灵活,要学习和借鉴模块化设计思想。
4、在进行模块编写时,要养成将模块变量和模块函数加上static关键字进行限定的好习惯。
五、遇到的典型错误
这里的Makefile 怎么make不过去:
错误:Makefile:1 *** missing separator. stop??
回答:原因是ifneq ($(KERNELRELEASE),)之间少个空格,hoho