Linux内核开发-编写一个proc文件

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. 其他

3.1 根据内核版本条件编译

在上面代码中,可以看到有如下的条件编译指令:

#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

在实际开发中,一份代码可能需要运行在各种机器上,机器使用的内核版本有差异,而不同的内核版本各个接口、结构体定义可能会有差异,这时候就可以用这种条件编译指令通过判断内核版本来编译不同的代码分支,减小代码维护的复杂度。

3.2 带参数insmod

有时候需要在insmod *.ko时传入参数,可以按如下方式实现:

static int g_insmod_with_param = 0;
module_param(g_insmod_with_param, int, 0400);

static int __init km_init(void)              /* 模块初始化 */
{
    int ret = 0;
    printk("g_insmod_with_param = %d\n", g_insmod_with_param);
    return ret;
}
insmod km.ko g_insmod_with_param=2

在这里插入图片描述

4.下一章:基于2层、3层netfilter实现一个简单的内核流量统计模块

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值