Linux 就是通常所说的单内核(monolithic kernel),即操作系统的大部分功能都被称为内核,并在特权模式下运行。通过 Linux 内核模块(LKM)可以在运行时动态地更改 Linux。Linux可加载内核模块(从内核的 1.2 版本开始引入)是 Linux 内核的最重要创新之一。它们提供了可伸缩的、动态的内核。探索隐藏在可加载模块后面的原理,并学习这些独立的对象如何动态地转换成 Linux 内核的一部分。
(1) linux 内核模块的创建
LKM 包含 entry 和 exit 函数。当向内核插入模块时,调用 entry 函数,从内核删除模块时则调用 exit 函数。因为 entry 和 exit 函数是用户定义的,所以存在 module_init
和 module_exit
宏,用于定义这些函数属于哪种函数。LKM 还包含一组必要的宏和一组可选的宏,用于定义模块的许可证、模块的作者、模块的描述等等,见下例。
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> static int __init hello_3_init(void) { printk(KERN_ALERT "Hello, world n"); return 0; } static void __exit hello_3_exit(void) { printk(KERN_ALERT "Goodbye, world " ); } MODULE_LICENSE("GPL"); module_init(hello_3_init); module_exit(hello_3_exit);
(2)构建linux 内核模块
编译模块的make file 必须是Makefile,不能是makefile,并且要生成模块必须先编译通过。 内核模块的makefile如下
ifneq ($(KERNELRELEASE),) obj-m := mytest.o mytest-objs := file1.o else
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions endif
1> KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义。所以执行else之后内容。
2> 当执行make 时候 执行默认目标defalult。
$(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 指示路径;表明然后返回到当前目录继续读入、执行当前的Makefile。
$(KVER) 要生成的模块内核版本 当前系统版本
3> 当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。
mytest-objs := file1.o 表示mytest.o 由file1.o 连接生成
obj-m := mytest.o 表示编译连接后将生成mytest.o模块 mytest.ko。
(3) 编译 安装 卸载
1> 编译 执行make 生成mytest.ko内核模块
2> 安装内核模块 sudo insmod mytest.ko
3> 查看内核模块输出 dmesg -c
4> 卸载内核模块 sudo rmmod mytest.ko
注意:Linux命令dmesg用来显示开机信息,kernel会将开机信息存储在ring buffer中。您若是开机时来不及查看信息,可利用dmesg来查看。开机信息亦保存在/var/log目录中,名称为dmesg的文件里。
(4)内核模块的启动参数
像我们普通的应用程序一样,有main函数启动可以有参数,内核模块启动时候也可以使用参数。这点很重要,如在视频压缩中通过最优参数达到不同压缩效率。先看一个例子 我们在解释其中的代码。
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/kernel.h> #define MAX_ARRAY 66 static int int_var = 0; static char *str_var = "default"; static int int_array[6]; int narr; module_param(int_var, int, 0644); MODULE_PARM_DESC(int_var, "A integer variable"); module_param(str_var,charp,0644); MODULE_PARM_DESC(str_var, "A string variable"); module_param_array(int_array, int, &narr, 0644); MODULE_PARM_DESC(int_array, "A integer array"); static int __init hello_init(void) { int i; printk(KERN_ALERT "Hello, my LKM. "); printk(KERN_ALERT "int_var %d. ", int_var); printk(KERN_ALERT "str_var %s. ", str_var); for(i = 0; i < narr; i ++) { printk("int_array[%d] = %d ", i, int_array[i]); } return 0; } static void __exit hello_exit(void) { printk(KERN_ALERT "Bye, my LKM. "); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("This module is a example.");
方式一:在内核模块里,参数的用法如同全局变量。
module_param(name, type, perm);
name 用户看到的参数名,又是模块内接受参数的变量
type 表示参数的数据类型,下列之一:byte, short, ushort, int, uint, long, ulong, charp, bool, invbool
perm 指定了在sysfs中相应文件的访问权限
eg:
static unsigned int int_var = 0;
module_param(int_var, uint, S_IRUGO);
方式二:模块源文件内部的变量名与外部的参数名有不同的名
module_param_named(name, variable, type, perm);
name 外部可见的参数名
variable 源文件内部的全局变量名
方式三:字符串
static char *name;
module_param(name, charp, 0);
或者
static char species[BUF_LEN];
module_param_string(name, string, len, perm);
方式四:数组
static int finsh[MAX_FISH];
static int nr_fish;
module_param_array(fish, int, &nr_fish, 0444); //最终传递数组元素个数存在nr_fish中
编译生成内核模块
装载内核模块 sudo insmod parm.ko int_var=100 str_var=hello int_array=100,200