前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何编写一个简单地内核模块

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

作者头像
yifei_
发布2022-11-14 14:40:55
1.3K0
发布2022-11-14 14:40:55
举报
文章被收录于专栏:yifei的专栏

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("hello\n");
        fun1();
}

static int __exit mb_exit(void){
        printk("goodbye\n");
}

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

参考

欢迎与我分享你的看法。 转载请注明出处:http://taowusheng.cn/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 步骤
  • 其他知识点
  • 模块间函数调用
  • Q&A
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档