如何编写一个简单地内核模块

2022-11-14 14:40:55 浏览数 (1)

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/

0 人点赞