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

news2024/12/24 11:28:34

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

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

4.下一章:待计划

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1861788.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[word] Word如何删除所有的空行? #职场发展#学习方法

Word如何删除所有的空行&#xff1f; 很多网友从网页复制文字粘贴到word文档后发现段落之间有空行&#xff0c;如果文字不多&#xff0c;手动删除这些空行也没有多少工作量&#xff0c;但是如果文字的字数达到成千上万&#xff0c;一个个手动删除这些空行还是很繁琐的。那么&a…

礼让,不是一昧地退让,而是表达我们的素养、品德

礼 / 让&#xff0c;发心是文明相处&#xff0c;互助互让&#xff0c;是君子之交

鸿蒙北向开发 ubuntu20.04 gn + ninja环境傻瓜式搭建闭坑指南

ninja跟gn都是比较时髦的东西,由歪果仁维护,如果走下载源码并编译这种流程的话需要走github跟google官网下载,国内的用网环境相信各位傻瓜都知道,github跟google这几年基本是属于连不上的状态,好在你看的鸿蒙项目跟国内的一些软件大厂已经帮你爬过梯子了,ninja工具跟gn工具已经…

【应用开发一】LED开发

文章目录 1应用层控制外设的两种方式2 sysfs和/sys关系3 LED控制方式3.1 基本情况3.2 LED属性文件介绍3.3 命令行属性测试3.4 led程序3.5 开发板上测试 1应用层控制外设的两种方式 使用设备文件控制 在Linux系统下&#xff0c;一切皆是文件。应用层控制底层硬件同样也是通过文…

堆的实现详解

目录 1. 堆的概念和特点2. 堆的实现2.1 堆向下调整算法2.2堆的创建2.3 建堆时间复杂度2.4 堆的插入2.5 堆的删除2.6 堆的代码实现2.6.1 结构体2.6.2 初始化2.6.3 销毁2.6.4 插入2.6.5 删除2.6.6 获取堆顶2.6.7 判空2.6.8 个数2.6.9 向上调整2.6.10 向下调整3. 堆的实现测试测试…

SQL连接与筛选:解析left join on和where的区别及典型案例分析

文章目录 前言一、left join on和where条件的定义和作用left join on条件where条件 二、left join on和where条件的区别原理不同left join原理&#xff1a;where原理&#xff1a; 应用场景不同执行顺序不同&#xff08;作用阶段不同&#xff09;结果集不同 三、实际案例理解lef…

深入JVM:详解JIT即时编译器

文章目录 深入JVM&#xff1a;详解JIT即时编译器一、序言二、基础概念1、何为JIT即时编译2、热点代码 三、HotSpot内置的即时编译器1、C1编译器2、C2编译器3、分层编译3.1 协作流程 四、常见JIT优化技术1、方法内联2、逃逸分析&#xff08;1&#xff09;同步锁消除&#xff08;…

高考填报志愿,找准自己的真兴趣来选择专业

又是一年一度的高考填报志愿的时间了&#xff0c;毕业生们要根据自己的分数&#xff0c;在很短的时间内确定自己的专业。专业千万条&#xff0c;兴趣第一条。专业的选择很大程度上决定着大学的学习生活是否顺利&#xff0c;甚至决定着以后的职业生涯。在纷繁复杂的专业中&#…

全球首个数字人开源了

DUIX&#xff08;Dialogue User Interface System&#xff09;是硅基智能打造的AI数字人智能交互平台。通过将数字人交互能力开源&#xff0c;开发者可自行接入多方大模型、语音识别&#xff08;ASR&#xff09;、语音合成&#xff08;TTS&#xff09;能力&#xff0c;实现数字…

【AI落地应用实战】如何高效检索与阅读论文——302.AI学术论文工具评测

一、引言 作为一名学术领域的探索者&#xff0c;我们都知道&#xff0c;检索和阅读论文是我们获取知识、启发思考、验证假设的基石&#xff0c;也是日常学习中必不可少的基本功之一。然而在浩瀚的学术海洋中&#xff0c;如何快速、准确地找到我们需要的论文&#xff0c;就像是…

git提交新仓库代码,提示无权限,但用户名已修改

目录 1 用户名无权限 2 删除用户凭据 2.1 打开控制面板 2.2 找到“凭据管理器” 2.3 删除git历史 3 npm工具库添加git仓库指引 1 用户名无权限 之前因为时间的原因&#xff0c;js-tool-big-box工具库没有提交到github上去&#xff0c;今天想着往上提交一下&#xff0c;但…

python03——文件操作(new)

“变量”open&#xff08;‘文件路径’&#xff0c;‘模式’&#xff09; //注意加引号 “变量”.write( ) //write函数是写的是字符串&#xff0c;如果你写的东西不是字符串&#xff0c;要写成write&#xff08;str&#xff08;。。&#xff09;&#xff09; “变量”.read…

【Containerd】Containerd接入Harbor仓库

说明 在日常使用容器时&#xff0c;安全方便起见一般都会使用到私有仓库&#xff0c;一般都是采用 harbor 作为私有仓库&#xff0c;docker 对接 harbor 仓库非常简单&#xff0c;那么 containerd 如何对接 harbor 呢&#xff1f; 在内网使用 harbor 根据个人习惯&#xff0c…

快速上手golang(持续更新)

由于项目需要&#xff0c;我不得不快速掌握go语言来帮助我进行项目的开发。时间紧迫到我来不及去了解语言的特性就直接项目上手了。我决定就先熟悉一个主流的go框架和go语言的一些日常用法&#xff0c;之后就得滑着这只破船摇摇晃晃上路了。 1 基础命令 就说几个用的多的&…

QT拖放事件之八:通过全局剪切板中的接口QClipboard::mimeData()来获取MIME类型数据

1、演示效果 首先向剪切板写入数据,然后点击paste按钮进行从全局剪切板中 获取 MIME数据。。。 2、核心代码 void Widget::on_pasteBtn_clicked() {const QClipboard* clipBoard = QGuiApplication::clipboard()

理解和处理不同类型的上下文的新型的语言模型ReALM

在人类的日常交流中&#xff0c;模糊的代词如“他们”或“那个”常常出现&#xff0c;它们的意义通常依赖于上下文才能明确。这种上下文的理解对于对话助手来说至关重要&#xff0c;因为它们旨在提供一种自然的交流体验。然而&#xff0c;现有的对话助手在处理这类模糊引用时往…

Java HashMap 简介

HashMap 简介 HashMap 主要用来存放键值对&#xff0c;它基于哈希表的 Map 接口实现&#xff0c;是常用的 Java 集合之一&#xff0c;是线程不安全的。 HashMap&#xff1b;可以存储 null 的 key 和 value &#xff0c;但 null 作为 key 只能有一个&#xff0c;null 作为值可以…

非强化学习的对齐方法

在文章《LLM对齐“3H原则”》和《深入理解RLHF技术》中&#xff0c;我们介绍了大语言模型与人类对齐的“3H原则”&#xff0c;以及基于人类反馈的强化学习方法&#xff08;RLHF&#xff09;&#xff0c;本文将继续介绍另外一种非强化学习的对齐方法&#xff1a;直接偏好优化&am…

深度学习语义分割算法之基础知识

文章目录 前言一、图像分割介绍1.语义分割2.实例分割3.全景分割 二、常见数据集格式1.PASCAL VOC2.MS COCO 三、语义分割结果四、语义分割常见评价指标1.Pixel Accuracy2.mean Accuracy3.mean IoU 五、语义分割标注工具结束语 &#x1f482; 个人主页:风间琉璃&#x1f91f; 版…

FireFox 编译指南2024 Windows10篇-环境准备(一)

1. 引言 在开源浏览器项目中&#xff0c;Firefox因其高性能和灵活性而备受开发者青睐。为了在本地环境中编译和定制Firefox&#xff0c;开发者需要做好充分的环境准备工作。这不仅是编译成功的基础&#xff0c;也是后续调试、优化和二次开发的关键步骤。 编译Firefox是一个复…