2. debugfs 基础:挂载、基本操作与读写回调

debugfs 是 Linux 内核开发者向用户空间暴露调试信息的最轻量级文件系统。与 procfs 和 sysfs 不同,debugfs 不承诺任何稳定的 ABI,专为开发和调试阶段设计。内核调度器(scheduler)的许多内部状态——例如 runqueue 统计、调度类切换计数、负载跟踪细节——都通过 debugfs 暴露给性能分析工具。

本章将围绕 debugfs 的挂载方法、目录/文件创建、以及核心的读写回调函数展开,为后续调度器调试接口的编写打下基础。

2.1 debugfs 文件系统挂载

debugfs 通常挂载在 /sys/kernel/debug 路径下。系统启动后,若内核编译时启用了 CONFIG_DEBUG_FS,则可通过以下命令手动挂载:

mount -t debugfs none /sys/kernel/debug

大多数现代发行版(如 Ubuntu、CentOS、Fedora)会在 init 阶段自动挂载 debugfs。验证挂载是否成功:

mount | grep debugfs
# 输出示例:none on /sys/kernel/debug type debugfs (rw,relatime)
权限提示: 普通用户通常无法直接访问 /sys/kernel/debug。分析调度器性能时,建议使用 root 或通过 sudo 执行。也可通过 chmod 临时调整目录权限,但生产环境不推荐。

2.2 基本操作:创建目录与文件

内核模块或调度器代码中,使用 <linux/debugfs.h> 提供的 API 操作 debugfs。核心数据结构为 struct dentry *,用于持有目录和文件的引用。

2.2.1 创建根目录

每个子系统通常先在 debugfs 下创建自己的目录,避免文件散落。调度器相关代码常创建 sched 目录:

#include <linux/debugfs.h>

static struct dentry *sched_debugfs_dir;

static int __init sched_debugfs_init(void)
{
    sched_debugfs_dir = debugfs_create_dir("sched", NULL);
    if (IS_ERR(sched_debugfs_dir))
        return PTR_ERR(sched_debugfs_dir);
    return 0;
}

2.2.2 创建文件

debugfs 提供多种文件创建函数,最常用的是 debugfs_create_file(),它允许自定义读写回调:

struct dentry *debugfs_create_file(const char *name, umode_t mode,
                                   struct dentry *parent, void *data,
                                   const struct file_operations *fops);

参数说明:

参数 含义
name 文件名,如 "runqueue_stats"
mode 访问权限,如 0444(只读)或 0644(读写)
parent 父目录 dentry,如 sched_debugfs_dir
data 私有数据指针,回调函数可通过 filp->private_data 获取
fops 文件操作结构体,包含 .read 和 .write 回调

创建只读文件的简化方式:

debugfs_create_u32("nr_running", 0444, sched_debugfs_dir, &nr_running_var);

对于简单数值类型,debugfs 提供了 debugfs_create_u8/u16/u32/u64、debugfs_create_bool 等辅助函数,无需手动实现回调。

2.3 读写回调函数

当需要输出复杂格式(如多行统计信息)或处理写入命令时,必须自定义 file_operations。调度器调试接口几乎全部使用自定义回调。

2.3.1 回调结构体定义

static const struct file_operations sched_stats_fops = {
    .open    = sched_stats_open,
    .read    = seq_read,
    .write   = sched_stats_write,
    .llseek  = seq_lseek,
    .release = single_release,
};

debugfs 强烈推荐与 seq_file 机制配合使用。通过 single_open() 或 seq_open(),可以优雅地输出多行格式化文本。

2.3.2 只读回调示例(seq_file 方式)

static int sched_stats_show(struct seq_file *m, void *v)
{
    struct rq *rq = m->private;

    seq_printf(m, "CPU%d:\n", cpu_of(rq));
    seq_printf(m, "  nr_running  : %d\n", rq->nr_running);
    seq_printf(m, "  nr_switches : %lu\n", rq->nr_switches);
    seq_printf(m, "  curr        : %s\n", rq->curr->comm);
    return 0;
}

static int sched_stats_open(struct inode *inode, struct file *file)
{
    return single_open(file, sched_stats_show, inode->i_private);
}

2.3.3 可写回调示例(写入触发动作)

调度器调试接口常通过写入特定字符串来触发重置统计、切换调度策略等动作:

static ssize_t sched_stats_write(struct file *file, const char __user *buf,
                                 size_t len, loff_t *off)
{
    char cmd[32];

    if (len > sizeof(cmd) - 1)
        return -EINVAL;

    if (copy_from_user(cmd, buf, len))
        return -EFAULT;

    cmd[len] = '\0';

    if (strncmp(cmd, "reset", 5) == 0) {
        // 重置当前 CPU 的调度统计
        this_cpu_ptr(&runqueues)->nr_switches = 0;
        pr_info("sched: stats reset on CPU%d\n", smp_processor_id());
    }

    return len;
}
安全注意: 写入回调运行在进程上下文,但可能被并发访问。操作共享数据(如 per-CPU 变量)时,必须使用适当的同步机制(如 this_cpu_ptr、raw_spin_lock)。debugfs 写入接口应仅限调试用途,切勿在生产系统中暴露危险操作。

2.4 debugfs 目录与文件结构总览

下图展示了调度器模块在 debugfs 下的典型布局,以及用户空间通过 cat/echo 与内核回调的交互流程:

debugfs 调度器调试接口结构 用户空间 cat /sys/kernel/debug/sched/stats echo reset > sched/ctrl 系统调用 VFS 层 file_operations debugfs 核心 回调 sched debugfs 目录树示例 /sys/kernel/debug/ └── sched/ ├── stats (只读) ├── ctrl (读写) ├── nr_running (u32) └── load_avg (u64) 回调函数调用流程 1. open() → single_open() 绑定 show() 函数 2. read() → seq_read() 循环调用 show() 3. write() → 解析用户输入,执行调试动作 4. release() → 释放 seq_file 缓冲区 关键:show() 中可安全访问 rq、cfs_rq 等调度器内部结构

2.5 清理与模块卸载

模块卸载或子系统停用时,必须递归删除 debugfs 目录。使用 debugfs_remove_recursive() 一次性清理:

static void __exit sched_debugfs_exit(void)
{
    debugfs_remove_recursive(sched_debugfs_dir);
}
最佳实践: 始终使用 debugfs_remove_recursive() 而非逐个删除文件。该函数会递归遍历并清理所有子目录和文件,避免内存泄漏。在调度器调试模块的退出路径中,这是标准做法。

2.6 本章小结

  • 挂载: mount -t debugfs none /sys/kernel/debug,内核需开启 CONFIG_DEBUG_FS。
  • 目录创建: debugfs_create_dir("sched", NULL) 返回目录 dentry。
  • 文件创建: 简单数值用 debugfs_create_u32/u64/bool;复杂输出用 debugfs_create_file() + 自定义 file_operations。
  • 读写回调: 推荐 seq_file 模式(single_open + seq_printf)输出多行统计;写入回调用于触发重置、切换等调试动作。
  • 清理: 模块卸载时调用 debugfs_remove_recursive()。