0.前言
上一章(点击返回上一章)完成了一个内核模块的编写,实现了在内核运行时的动态加载和卸载。
在模块的开发调测过程中或者模块运行过程中,可能需要打印内核模块的变量的值或者想要动态开关模块的运行日志打印,那么就需要一个与内核交互的接口来实现这个目的。
1.什么是/proc文件
proc 文件系统是一个虚拟文件系统(类似的还有sys文件系统),对内核模块中的全局变量,我们都可以为其生成一个/proc文件,在控制台(即应用层)对其进行读写操作。
2.编写一个/proc文件
需求:某个模块在客户现场出问题了,需要记录内核模块的日志用于记录运行情况。
分析:在函数被调用不频繁的情况下,可以直接使用printk来打印函数运行的分支情况。但是printk操作很消耗性能,在会产生大量打印的情况下,很可能会由于一直在print,cpu得不到调度而出发系统重启。因此需要实现一个能根据需求动态调整打印的方式。
if (debug_enable)
{
printk("[%s:%d], come here\n", __func__, __LINE__);
}
如上的代码片段,我们只要能控制debug_enable这个变量就能控制日志打印的输出。
2.1 代码实现
2.1.1 km_proc文件
一般开发中,会有多个proc文件创建的需求,对于这些创建、读、写逻辑都统一放到一个文件中进行,本项目就放在km_proc.c和km_proc.h中,其中最重要的km_proc.c文件内容如下:
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include "km_proc.h"
/* 我们本次就是为此变量创建一个proc文件,并在控制台读写它 */
extern int km_debug_enable;
struct proc_dir_entry *km_proc_dir = NULL; /* km模块文件夹,用于存放模块的所有proc文件 */
struct proc_dir_entry *km_debug_proc_file = NULL; /* km_debug对应的proc文件 */
static int km_debug_proc_show(struct seq_file* file, void* v)
{
seq_printf(file, "km_debug_enable:%d\n", km_debug_enable);
return 0;
}
static int km_debug_proc_open(struct inode* inode, struct file* file)
{
return single_open(file, km_debug_proc_show, NULL);
}
static ssize_t km_debug_proc_write(struct file* file, const char __user *buffer, size_t count, loff_t *pos)
{
char buf[2] = {0};
if (count > 2)
{
printk("error:please input 0 or 1");
return -ENOSPC;
}
if (copy_from_user(buf, buffer, count))
{
return -EFAULT;
}
sscanf(buf, "%d", &km_debug_enable);
printk("km_debug_enable:%d\n", km_debug_enable);
return count;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,6,0)
static const struct proc_ops km_debug_proc_fops = {
.proc_open = km_debug_proc_open,
.proc_read = seq_read,
.proc_release = single_release,
.proc_write = km_debug_proc_write,
};
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,0,0)
static const struct file_operations km_debug_proc_fops = {
.owner = THIS_MODULE,
.open = km_debug_proc_open,
.read = seq_read,
.release = single_release,
.write = km_debug_proc_write,
};
#else
#error "Please make sure your kernel vision" /* proc文件相关结构体和接口与内核版本有关,需要单独适配 */
#endif
int km_init_proc(void)
{
int ret = 0;
/* 创建/proc/km文件夹 */
km_proc_dir = proc_mkdir(KM_PROC_DIR, NULL);
if (NULL == km_proc_dir) /* 创建失败,退出 */
{
ret = -1;
printk("proc_mkdir fail!\n");
goto err;
}
/* 创建/proc/km/km_debug文件 */
km_debug_proc_file = proc_create(KM_DEBUG_FILE, 0644, km_proc_dir, &km_debug_proc_fops);
if (NULL == km_debug_proc_file)
{
ret = -1;
printk("create /proc/%s/%s fail!\n", KM_PROC_DIR, KM_DEBUG_FILE);
goto err1;
}
printk("proc init success!\n");
return ret;
err1:
/* 失败,销毁已经创建的文件 */
remove_proc_entry(KM_PROC_DIR, NULL);
err:
return ret;
}
void km_exit_proc(void)
{
/* 按反向顺序一个个销毁,即先销毁文件、再销毁文件夹 */
if (km_debug_proc_file)
{
remove_proc_entry(KM_DEBUG_FILE, km_proc_dir);
km_debug_proc_file = NULL;
}
if (km_proc_dir)
{
remove_proc_entry(KM_PROC_DIR, NULL);
km_proc_dir = NULL;
}
printk("proc exit complete!\n");
}
其余变更见:github:kernel_module:为自己的内核模块添加一个proc文件
2.2.2 编译、运行、测试
# 源码目录下编译
make
# 插入模块
insmod km.ko
# 查看插入结果
lsmod
查看内核打印信息:
dmesg
可以看到:
查看变量km_debug_enable的值:
cat /proc/km/km_debug
输出:
修改变量km_debug_enable的值:
sudo sh -c "echo 1 > /proc/km/km_debug"
可以看到修改已经生效了。
注意:修改km_debug_enable值时,如果直接输入echo,会提示权限不够,原因可见:https://blog.csdn.net/change_can/article/details/115128218
3. 其他
在上面代码中,可以看到有如下的条件编译指令:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,6,0)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,0,0)
#else
#error "Please make sure your kernel vision" /* proc文件相关结构体和接口与内核版本有关,需要单独适配 */
#endif
在实际开发中,一份代码可能需要运行在各种机器上,机器使用的内核版本有差异,而不同的内核版本各个接口、结构体定义可能会有差异,这时候就可以用这种条件编译指令通过判断内核版本来编译不同的代码分支,减小代码维护的复杂度。