序言:
Linux内核模块全称为“动态可加载内核模块(LoadableKernelModule,LKM)”,是系统内核向外部提供的功能接口。作为宏内核结构,Linux内核具有效率高的特征linux内存管理,但也有可扩充性和可维护性相对较差的不足linux服务器代维,Linux提供模块机制正是填补这一缺陷。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。模块在运行时被链接到内核作为内核的一部份在内核空间运行,这与运行在用户控件的进程是不同的。模块一般有一组函数和数据结构组成,拿来实现某种文件系统、驱动程序或其它内核下层功能。
本文将介绍怎么编撰一个简单的内核模块以及怎样传递参数给此模块。
一、编写一个简单的内核模块1.编撰模块程序
编撰如下简单代码,本示例中代码文件命名“hello_module.c”。
//hello_module.c
#include
#include
#include
static int __init hello_init(void){
printk("This is hello_module, welcome to Linux kernel n");
return 0;
}
static void __exit hello_exit(void){
printk("see you next time!n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mr Q");
MODULE_DESCRIPTION("hello kernel module");
MODULE_ALIAS("hello");
以上代码解释如下:
(1)#include:必须。module.h头文件包含了对模块的结构定义以及模块的版本控制,任何模块程序的编撰都要包含这个头文件;
(2)#include:kernel.h包含了常用的内核函数,如以上程序中的printk()函数;
(3)#include:必须。init.h包含了module_init()和module_exit()函数的申明;
(4)module_init():必须。模块加载函数,加载模块式该函数手动执行linux内核编译步骤,进行初始化操作;
(5)module_exit():必须。模块卸载函数,卸载模块时函数手动执行,进行清除操作;
(6)MODULE_LICENSE():表示模块代码接受的软件许可合同。Linux内核是使用GPLV2的开源项目,其要求所有使用和更改了Linux内核代码的个人或组织都有义务把更改后的源代码公开,这是一个强制的开源合同,所以通常编撰驱动代码都须要显示的申明和遵守本合同,否则内核UI发出被污染的警告;
(7)MODULE_AUTHOR():描述模块的作者信息;
(8)MODULE_DESCRIPTION():简单描述模块的用途、功能介绍等;
(9)MODULE_ALIAS():为用户控件提供的别称;
(10)printk():内核输出函数,默认复印系统文件“/var/log/kern.log”的内容。
2.编译内核模块
编撰Makefile文件,文件名必须为“Makefile”
obj-m := hello_module.o
KERNELBUILD := /lib/modules/$(shell uname -r)/build
CURRENT_PATH := $(shell pwd)
all:
make -C $(KERNELBUILD) M=$(CURRENT_PATH) modules
clean:
make -C $(KERNELBUILD) M=$(CURRENT_PATH) clean
以上代码解释如下:
(1)obj-m:=.o:定义要生成的模块名称
(2)KERNELBUILD:=/lib/modules/$(shelluname-r)/build:
KERNELBUILD为自定义名称,用于指向正在运行Linux的内核编译目录,其中“uname-r”标识显示对应的内核版本;
(3)CURRENT_PATH:=$(shellpwd):CURRENT_PATH为自定义名称,用于指向当前当前目录;
(4)all:编译执行的动作
(5)clean:zhixingmakeclean须要的动作。“makeclean”用于去除先前的make命令所形成的object文件(后缀为“.o”的文件)及可执行文件。
3.编译
将以上两个文件(hello_module.c和Makefile)保存于同一目录下linux内核编译步骤,将上文中代码储存在路径为“/code/hellomodule/”,编译需在文件保存目录中进行。
编译成功后,可见到生成的hello_module.ko目标文件
4.检测编译模块
可通过file命令检测编译的模块是否正确,可以看见x86-64构架的elf文件,说明编译成功:
也可通过modinfo命令进一步检测:
5.插入模块
通过insmod命令插入模块,完成插入后可通过lsmod命令查看当前模块是否早已被加载到系统中:
系统加载模块后,也会在“/sys/module”目录下新建以模块名命名的目录:
6.查看输出
因本演示中prink()采用默认输出等级,可通过“dmesg”或“tail/var/log/kern.log”命令查看输出结果。
“tail/var/log/kern.log”命令查看输出结果:
7.卸载模块
卸载模块,可通过“rmmod模块名”实现,通过“tail/var/log/kern.log”命令查看输出结果。
二、模块参数1.说明
Linux内核提供一个宏来实现模块的参数传递
#define module_param(name, type, perm)
module_param_named(name, name, type, perm)
#define MODULE_PARM_DESC(_parm, desc)
_MODULE_INFO(parm, _parm, #_parm ":" desc);
module_param()宏由3个参数组成,name表示参数名,type表示参数类型,perm表示参数读写权限。MODULE_PARM_DESC()宏提供参数的简单说明,参数类型可为byte、short、int、long、char、bool等类型;perm指定在sysfs中相应文件的访问权限,如设置为0则不会出现在sysfs文件系统中,设置为0644标示root用户可更改本参数。
static int debug = 1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debugging information");
#define dprintk(args...) if(debug){printk(KERN_DEBUG args);}
如上述实际代码所示(driver/misc/altera-stap1/altera.c),实际定义模块参数debug,类型是int,访问权限是0644。参数用途是大约调试信息,实际内核编程中常用此方式进行内核调试。
2.开始动手
更改上文中的“hello_module.c”文件,改为以下内容:
//hello_module.c
#include
#include
#include
static int debug = 1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "debugging information");
#define dprintk(args...) if(debug){printk(KERN_DEBUG args);}
static int myparm = 10;
module_param(myparm, int, 0644);
MODULE_PARM_DESC(myparm, "kernel module parameter experiment.");
static int __init parm_init(void){
dprintk("my linux kernel module init.n");
dprintk("module parameter = %dn", myparm);
return 0;
}
static void __exit parm_exit(void){
printk("see you next time!n");
}
module_init(parm_init);
module_exit(parm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mr Q");
MODULE_DESCRIPTION("kernel module paramter experiment");
MODULE_ALIAS("myparm");
make编译,装载模块,并查看输出:
通过查看日志信息,可发觉输出以上程序中参数的默认值。
卸载模块,形参重新加载模块,更改参数myparm值为116:
insmod parm_module.ko myparm=116
通过查看日志信息,可发觉参数值早已改变。
头条对markdown太不友好了~辛苦改了格式分享给你们,可能还是有点乱,你们可以点下方了解更多看我的博客。