Linux内核模块的编程方法(转)[@more@]
相信许多朋友和我一样都在Linux环境下使用过C语言编过程序,其大多数都属于用户应用程序,也称为普通用户程序。写了这么多应用程序后,就有点儿想写一点系统级的程序了,于是就参考了一些关于Linux内核编程原理的资料,并付之了实践,现在就让我将编写内核模块的方法给大家介绍一下吧......
一个Linux内核模块至少需要包括以下两个函数:
1.模块初始化函数——当模块被插入到Linux内核中时被调用;
2.模块卸载函数——当模块从Linux内核中被卸载时被调用。
一般来说,模块初始化函数给新模块在内核中注册,并且得到一个调用句柄;或者它使新模块的代码覆盖原有的代码(通常情况下新模块的代码增加了一些新功能,然后调用原有的代码)。
而模块卸载函数正好做了模块初始化函数相反的工作,它使新模块安全地被卸载。
下面我们来看看如何在Linux内核中插入一个模块,让其在屏幕上输出“Hello , this is module speaking!”的字样。
程序文件:hello.c
#include $#@60;linux/kernel.h$#@62; /* 我们正在干一些关于内核的事情 */
#include $#@60;linux/module.h$#@62; /* 具体来说,是在写一个模块 */
#if CONFIG_MODVERSIONS==1 /* 如果需要指明模块的版本的话 */
#define MODVERSIONS
#include $#@60;linux/modversions.h$#@62; /* 那就将linux/modversions.h文件包含*/
#endif
int init_module() /* 模块初始化函数 */
{
printk("Hello, this is the kernel speaking! n");
/* 如果我们将返回值置为非零,这说明初始化模块失败 */
return 0;
}
void cleanup_module() /* 模块卸载函数 */
{
printk(“ This kernel module has been removed. n");
}
为了编译hello.c,我们还得编写一个Makefile文件。
内核模块不是一个单独的可执行体,它只能作为一个二进制目标文件(相当于DOS的obj文件)被内核调用。经常在Linux下些程序的用户一定熟悉cc或gcc的用法,在此我们使用GNU gcc来编译hello.c文件,使用-c标志表示只将源文件编译成二进制目标文件。
同时,所有的内核模块在编译时都要使用__KERNEL__(注意,每一边都是两个半角的下划线)标示,只有这样才能告诉编译器这个程序将在内核模式下运行而不是一个 ǖ挠没Ы?獭?
此外,在编译时我们还得用到MODULE、LINUX两个标示:
MODULE — 告诉编译器给内核模块一个合适的定义;
LINUX —为了提高代码的可移植性,这里说明我们的代码是在Linux操作系统下编译。
所以,Makefile如下:
文件:Makefile
CC=gcc
MODCFLAGS := -Wall -DMODULE -D__KERNEL__ -DLINUX
hello.o: hello.c /usr/include/linux/version.h
$(CC) $(MODCFLAGS) -c hello.c
好了,接下来要做的事情就是以root的身份登录系统,或使用su来使自己成为root用户。然后,使用insmod hello和rmmod hello 命令来插入或卸载hello模块。执行insmod hello命令后,hello模块被初始化,于是console输出“Hello, this is the kernel speaking!”的字样;同样,执行rmsmod hello命令后,hello模块被卸载,于是系统要调用模块卸载函数来做清理工作,这时,console输出“This kernel module has been removed.”的字样。另外,一旦使用了insmod hello插入hello模块后,在/proc/modules目录下将会有hello模块的纪录。
顺便说一下,插入或卸载hello模块的动作不要在X-Windows下做,这是因为在程序中我们使用了printk函数,它能从内核直接向console控制台输出消息,如果您使用的Linux的内核是不稳定的版本的话,使用X-Windows下的虚拟终端输出可能会导致系统崩溃或重起。
上面所说的是单个文件的程序,如果程序比较大,则需要分解成多个源文件。下面就来看看多个源文件组成的内核模块程序是如何编写、编译的。
我们需要做以下三个工作:
1.在所有的源文件中除了一个没有,其他均要加上 #define __NO_VERSION__的宏定义。这一点非常重要,因为在module.h中已经包含了系统内核版本的定义,所有的内核模块都依照者这个已定义的包含了内核版本的全局变量来编译的。现在使用了#define __NO_VERSION__语句,可以在编译时避免使用上述的全局变量。但,此时您必须手工地包含version.h文件,因为有了__NO_VERSION__的定义,module.h就不会自动地包含version.h文件了;
2.和平时一样编译所有的源文件得到多个二进制目标文件;
3.连接(link)所有的二进制目标文件,在X86硬件环境下使用这样的命令: ld ?m elf_i386 ?r ?o $#@60;模块的名字$#@62;.o $#@60;源文件1的名字$#@62;.o $#@60;源文件2的名字$#@62;.o
好,下面就举一个例子。
文件:start.c
/* 此文件不包含__NO_VERSION__宏的定义 */
#include $#@60;linux/kernel.h$#@62; /* 说明我们在做一些关于内核的工作 */
#include $#@60;linux/module.h$#@62; /* 具体来说,是在写一个模块 */
#if CONFIG_MODVERSIONS==1 /* 如果需要指明模块的版本的话 */
#define MODVERSIONS
#include $#@60;linux/modversions.h$#@62;
#endif
int init_module() /* 模块初始化函数 */
{
printk("Hello, this is the kernel speaking n");
return 0;
}
文件:stop.c
#include $#@60;linux/kernel.h$#@62; /*说明我们在做一些关于内核的工作*/
#define __NO_VERSION__ /* 禁止module.h使用含有内核版本号的全局变量 */
/* 故,此宏的定义必须在包含module.h文件之前 */
#include $#@60;linux/module.h$#@62; /* 包含module.h文件 */
#include $#@60;linux/version.h$#@62; /* 由于__NO_VERSION__宏的定义,只能手工加入*/
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include $#@60;linux/modversions.h$#@62;
#endif
void cleanup_module() /* 模块卸载函数 */
{
printk("This kernel module has been removed. n");
}
接下来就是编写Makefile文件了。
文件:Makefile
CC=gcc
MODCFLAGS := -Wall -DMODULE -D__KERNEL__ -DLINUX
hello.o: start.o stop.o
ld -m elf_i386 -r -o hello.o start.o stop.o
start.o: start.c /usr/include/linux/version.h
$(CC) $(MODCFLAGS) -c start.c
stop.o: stop.c /usr/include/linux/version.h
$(CC) $(MODCFLAGS) -c stop.c.
下面的操作和使用单个源文件的一样,使用insmod hello和rmmod hello来装卸hello模块了。
关于Linux内核的编程有很多方面,涉及的知识点很多,需要程序员对C/C++甚至汇编语言很熟悉,同时对操作系统原理也有一定的了解。如果能读懂Linux的源程序那是更好了。虽然很辛苦,但是你一定会有收获的。