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;
}
this_cpu_ptr、raw_spin_lock)。debugfs 写入接口应仅限调试用途,切勿在生产系统中暴露危险操作。
2.4 debugfs 目录与文件结构总览
下图展示了调度器模块在 debugfs 下的典型布局,以及用户空间通过 cat/echo 与内核回调的交互流程:
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()。