Linux给应用程序提供了丰富的api,但是有时候我们需要跟硬件交互,访问一些特权级信息,所以可以使用编写内核模块这种方式。 另外Linux是宏内核结构,效率非常高,没有微内核那样各个模块之间的通讯损耗,但是又不能方便的对内核进行改动,可扩展性和可维护性比较差,内核模块提供了一种动态加载代码的方式,弥补了宏内核的不足。
步骤
首先需要xxx.c原文件存放代码,Makefile用来编译xxx.c文件。
编写内核模块源文件
代码语言:javascript复制// lkm_example.c
#include <linux/init.h> //必须包含,里面定义了__init和__exit两个宏,分别用来指定模块初始化函数和模块卸载函数
#include <linux/module.h> //必须包含,定义了动态加载内核模块所需的必要信息
#include <linux/kernel.h> //包含了内核常用API,比如内核打印函数printk()
//__init会将lkm_example_init函数标记为初始化函数,模块被装载到内核时会调用该函数。
static int __init lkm_example_init(void) {
printk(KERN_INFO "Hello, World!n"); //
return 0;
}
//模块被卸载时被调用
static void __exit lkm_example_exit(void) {
printk(KERN_INFO "Goodbye, World!n");
}
module_init(lkm_example_init); //引导内核加载模块
module_exit(lkm_example_exit); //引导内核卸载模块
MODULE_LICENSE("GPL"); //必选项 模块许可证,如果没有添加模块许可证,会收到内核被污染的警告
MODULE_AUTHOR("YIFEI"); //可选 模块作者
MODULE_DESCRIPTION("linux module"); //可选 模块描述
MODULE_VERSION("0.01"); //可选项 模块版本
编写Makefile文件
代码语言:javascript复制obj-m = lkm_example.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
#-C选项:此选项指定内核源码的位置,make在编译时将会进入内核源码目录,执行编译,编译完成时返回。
#这个build/目录是一个软连接,链接到源码头文件的安装位置。
#M=$(PWD):需要编译的模块源文件地址
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
执行make编译模块
代码语言:javascript复制$ make
装载模块
代码语言:javascript复制$ sudo insmod lkm_example.ko
查看装载的模块
代码语言:javascript复制$ lsmod
卸载模块
代码语言:javascript复制$ sudo rmmod lkm_example.ko
查看打印的日志
代码语言:javascript复制$ sudo dmesg
[75789.276382] Hello, World!
[75789.307013] Goodbye, World!
可以在Makefile最后添加以下代码,将测试流程自动化,每次只需执行 make test.
代码语言:javascript复制test:
sudo dmesg -C
sudo insmod lkm_example.ko
sudo rmmod lkm_example.ko
dmesg
其他知识点
往内核模块传参数
代码语言:javascript复制static int pid = -1;
module_param(pid,int,S_IRUGO);
/*
在内核模块中定义一个全局变量,然后用module_param声明一下
参数一:表示参数的名字;
参数二:表示参数的类型;
参数三:表示参数的访问权限,S_IRUGO表示参数可以被所有人读取, 但是不能改变。
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
当往模块传数组类型的参数时
module_param_array(name, type, num, perm);
name:表示数组的名字;
type:表示参数的类型;
num :表示数组中元素数量;
perm:表示参数的访问权限;
*/
模块间函数调用
ma.c
代码语言:javascript复制#include <linux/module.h>
int b=0;
void fun1(){
int a=0;
a ;
b ;
printk("%d %d n",a,b);
}
EXPORT_SYMBOL(fun1);
ma.c的Makefile
代码语言:javascript复制obj-m = ma.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
mb.c
代码语言:javascript复制#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
extern void fun1();
static int __init mb_init(void){
printk("hellon");
fun1();
}
static int __exit mb_exit(void){
printk("goodbyen");
}
module_init(mb_init);
module_exit(mb_exit);
MODULE_LICENSE("GPL");
mb.c的Makefile
代码语言:javascript复制-m = mb.o
KBUILD_EXTRA_SYMBOLS=/home/yifei/src/module_test/ma/Module.symvers #去该目录查找ma.ko的符号表
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
执行过程:
代码语言:javascript复制#要先插入ma.ko模块,再插入mb.ko.删除模块时顺序相反。
cd ma
make
insmod ma.ko
cd ../mb
make
insmod mb.ko
dmesg
rmmod mb.ko
rmmod ma.ko
Q&A
printk()使用方法。
代码语言:javascript复制printk相比printf来说还多了个:日志级别的设置,用来控制printk打印的这条信息是否在终端上显示的,当日志级别的数值小于控制台级别时,printk要打印的信息才会在控制台打印出来,否则不会显示在控制台!
在我们内核中一共有8种级别,他们分别为:
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
执行make编译内核模块时遇到签名验证失败时,在Makefile开始添加:
代码语言:javascript复制CONFIG_MODULE_SIG=n #关闭签名验证
根据pid获取可执行文件的绝对路径 https://www.cnblogs.com/ddk3000/p/5051111.html
参考
- 贺东升:编写简单的内核模块
- 如何编写简单的linux内核模块
- printk函数的用法
- linux modules 一个模块调用另一个模块的函数
欢迎与我分享你的看法。 转载请注明出处:http://taowusheng.cn/