Linux是一个单内核的结构,单内核的模块的可维护性比较差,当我们想增加一个新的功能,比如说要写一个驱动程序要融入到内核里头,Linux内核现在非常庞大,编译一次内核花费的时间很长。所幸的是Linux提供了一种机制(可加载的Linux内核模块),我们所写的代码不需要完全和内核捆绑在一起。在Linux内核运行的过程中,可以把模块加载进Linux内核,当不需要这个模块的时候,即使Linux内核在运行,也可以进行卸载。
Linux内核模块编程
printk 函数包含在linux/kernel.h ,linux/module.h 和linux/init.h 是必须包含的头文件。
- 模块的初始化函数
__init 是用于初始化的编译修饰符,当通过insmod命令加载内核模块时,模块的初始化函数会自动被内核执行,完成本模块相关初始化工作。
#include <linux/module.h>
#include <linux/kernrl.h>
#include <linux/init.h>
static int __init lkp_init(void)
{
printk("Hello, World! from the kernel space...\n");
return 0;
}
- 模块的退出和清理函数
当通过rmmod命令卸载模块时,模块的退出函数会自动被内核执行,完成退出清理工作。
static void __exit lkp_exit(void)
{
printk("Goodbye, World! leaving kernel space...\n");
}
- module_init和module_exit
module_init表示要用来调用这个初始化函数的宏,它的参数是一个函数名,这个宏定义它放在linux/module.h 。 module_init(lkp_init); module_exit(lkp_exit); - 模块许可证申明
MODULE_LICENSE("GPL");
-
模块的参数 块参数是模块被加载的时候可以被传递给他的值,它本身对应模块内部的全局变量。 -
模块的导出符号 内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本模块中的变量或函数。
EXPORT_SYMBOL(variable);
EXPORT_SYMBOL(FunctionName);
模块的编译
Linux内核模块不是一个独立的可执行文件,是在内核运行期间可以插入到内核中的,只有超级用户才能加载和卸载模块。 编译内核模块不能用一般的编译方法,必须写一个Makefile文件,下面这个格式是在ubuntu下的格式。 obj-m := 这个赋值语句的含义是说明要使用目标文件module_helloworld.o建立一个模块,最后生成的模块名为module_helloworld.ko。.o文件是经过编译和汇编,而没有经过链接的中间文件。
obj-m:module_helloworld.o # 产生module_helloworld模块的目标文件
CURRENT_PATH := $(shell pwd) # 模块所在的当前路径
LINUX_KERNEL := $(shell uname -r) # linux内核源码的当前版本
LINUX_KERNEL := /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 # 清理模块
模块插入命令insmod module_helloworld.ko 模块卸载命令rmmod module_helloworld
| C应用程序 | 内核模块程序 |
---|
使用函数 | Libc库 | 内核函数 | 运行空间 | 用户空间 | 内核空间 | 运行权限 | 普通用户 | 超级用户 | 入口函数 | main() | module_init() | 出口函数 | exit() | module_exit() | 编译 | gcc-c | make | 链接 | gcc | insmod | 运行 | 直接运行 | insmod | 调试 | gdb | kgdb |
Linux内核模块与C应用的对比
|