编写内核模块

  内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的最大优点是效率高,因为所有的内容都集成在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。很多驱动程序都以模块的形式存在,用户可以有选择的加载需要的驱动程序。

 

一、 什么是模块

  模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。

 

二、 编写一个简单的模块

  模块和内核都在内核空间运行,模块编程在一定意义上说就是内核编程。因为内核版本的每次变化,其中的某些函数名也会相应地发生变化,因此模块编程与内核版本密切相关。以下例子针对2.6内核:

  tasklet.c文件

 1 #include <linux/module.h>
 2 #include <linux/init.h>
 3 #include <linux/kernel.h>
 4 #include <linux/interrupt.h>  
 5 
 6 static struct tasklet_struct my_tasklet;  
 7 
 8 static void tasklet_handler (unsigned long data)
 9 {
10         printk(KERN_ALERT "tasklet_handler is running.\n");
11 }  
12 
13 static int __init test_init(void)
14 {
15         tasklet_init(&my_tasklet, tasklet_handler, 0);
16         tasklet_schedule(&my_tasklet);
17         return 0;
18 }  
19 
20 static void __exit test_exit(void)
21 {
22         tasklet_kill(&my_tasklet);
23         printk(KERN_ALERT "test_exit running.\n");
24 }
25 MODULE_LICENSE("GPL");  
26 
27 module_init(test_init);
28 module_exit(test_exit);

  1.头文件说明:所有模块都要使用头文件module.h,此文件必须包含进来;头文件kernel.h包含了常用的内核函数;头文件init.h包含了宏_init和_exit,它们允许释放内核占用的内存。interrupt.h文件时编写tasklet(关于tasklet的说明可以参考linux中断与异常)必须要用的头文件。

  2.test_init是模块的初始化函数,它必需包含诸如要编译的代码、初始化数据结构等内容,其中的tasklet_init初始化我们自己创建的tasklet,tasklet_schedule函数则是将我们初始化后的tasklet挂到tasklet链表中,并激活软中断来执行我们的tasklet。

  3.test_exit是模块的退出和清理函数。此处可以做所有终止该内核模块时相关的清理工作。

  4.函数module_init()和module_exit()是模块编程中最基本也是必须的两个函数。module_init()向内核注册模块,并调用注册做模块的初始化工作的函数,而module_exit()注销模块并注册模块注销时执行的清理函数

  5.第10行使用了printk()函数,该函数是由内核定义的,功能与C库中的printf()类似,它把要打印的信息输出到终端或系统日志。字符串前面的KERN_ALERT是输出的级别,表示立即在终端输出,可以调用dmesg命令来查看输出。

  有了上面的各个组成,一个模块就算编写好了。

 

三、 编译模块

  编译模块不想编译普通的c文件,直接敲几个命令就可以了,编译模块必须用Makefile文件,而且Makefile文件必须按一定的规则编写,这个和平时编写的Makefile文件不一样。编写内核模块的Makefile文件内容几乎相同的。下面是模版:

obj-m += tasklet.o        # 产生tasklet 模块的目标文件
CURRENT_PATH := $(shell pwd)   #模块所在的当前路径
LINUX_KERNEL := $(shell uname -r)    #Linux内核源代码的当前版本
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL) #Linux内核源代码的绝对路径
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules   #编译模块了
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean    #清理

  除了第一行要把tasklet换成编写的目标模块的.c文件名外,其它都是一样的。

  一切都准备好之后,直接make一下就ok了。

 

四、 运行模块

  运行模块也不能像运行普通程序一样,直接./tasklet来运行,必须通过专门的命令来运行。

  使用$insmod tasklet.ko命令直接将我们的模块插入到内核中,模块被插入内核时就会调用我们注册的初始化函数test_init,用dmesg命令查看输出,可以看到tasklet_handler is running.消息被打印了。

  使用$lsmod命令来查看系统中的模块:

  使用$rmmod tasklet来卸载模块,卸载模块的同时会执行注册函数test_exit,用dmesg可以查看打印消息。

posted @ 2013-10-28 17:21  在于思考  阅读(2374)  评论(0编辑  收藏  举报