Linux内核模块编程
这是Linux实践课程系列第二篇
内容要求
- 设计一个模块,要求列出系统中所有内核线程的程序名、PID号、进程状态及进程优先级。
- 设计一个带参数的模块,其参数为某个进程的PID号,该模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号。
开发平台
- Linux环境 gcc vim
具体步骤
- 无参模块
- 代码如下:
-
- 编译
make
- 编译
-
- 验证是否成功
- 有参模块
- 代码如下:
-
- 编译
make
- 编译
内核模块编程补充知识
-
Linux内核模块简介
模块基本概念
Linux内核是单体是结构,即宏内核,相对微内核结构而言,运行效率更高,但系统的可维护性和可扩展性较差。为此,Linux提供了内核模块(module)机制,它不仅可以弥补单体式内核相对于微内核的一些不足,而且对系统性能没有影响。内核模块的全程是动态可加载内核模块(Loadable Kernel Module,KLM),简称模块。模块是一个目标文件,能完成某种独立的功能,但其自身不是一个独立的进程,不能单独运行,可以动态载入内核,使其成为内核代码的一部分,与其他内核代码地位完全相同。当不需要某模块的功能时,可以动态卸载。Linux中大多数设备驱动程序或文件系统都是以模块的方式实现的,因为它们数量多、体积大,不适合直接编译在内核中,而是通过模块机制,需要时临时加载。另外使用模块的好处还有是,修改模块代码后只需要重新编译和加载模块,并不需要重新编译内核和引导系统。一个模块通常由一组函数和数据结构组成,用来实现某种功能,如实现一种文件系统、一个驱动程序或其他内核层上的功能。模块自身不是一个独立的进程,当前进程运行是调用到模块代码时,可以认为是该段代码就代表当前进程在核心态运行。内核符号表
模块编程可以使用内核的一些全局变量和函数,内核符号表是用来存放所有模块都可以访问的符号以及相应地址的表,位置在/proc/kallsyms
。一个模块如果只需实现自己的功能,不需导出任何符号;如果其他模块要调用这个模块的函数或数据结构时,该模块可以导出符号,其他模块可以使用,这种技术称为模块层叠技术。
模块导出符号,可以使用下面的宏:EXPORT_SYMBOL(symbol_name)
EXPORT_SYMOBL_GPL(symbol_name)
这两个宏可以将给定的符号导出到模块外部。_GPL
版本使要导出的符号只能被GPL许可证下的模块使用。另外符号必须在模块的全局部分导出,不能在函数里。
-
内核模块编程基础
- 模块代码结构
- 模块的编译和加载
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
static int hello_init(void)
{
printk(KERN_ALERT"hello world\n");
return 0;
}
static int hello_exit(void)
{
printk(KERN_ALERT"goodbye\n");
return 0;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
如上代码所示,每个模块程序都没有main函数,每个模块必须有定义两个函数:用于初始化和退出时释放资源。Linux用宏module_init()
和 module_exit()
来注册这两个函数。需要注意的是初始化函数和清理函数必须在module_init()
和 module_exit()
前定义,否则会出现编译错误。