一、debugfs 介绍
1. debugfs是什么?
debugfs(Debug Filesystem)是一个基于内存的虚拟文件系统,专门为内核开发者和驱动开发者设计,用于在内核空间和用户空间之间交换调试信息。
2. 为什么用debugfs?
比 printk 更灵活:无需频繁修改代码、编译、重启。可以动态获取信息。
比 /proc 更纯粹:/proc 设计用于进程信息,而 debugfs 专为调试而生,API 更简洁。
比 sysfs 更自由:sysfs 有严格的一个值一个文件等规则,debugfs 无此限制,可以输出任意格式的数据(二进制、文本等)。
交互式调试:可以写入文件来触发内核中的特定动作或修改参数。
3. 基本原理
内核在启动时在 /sys/kernel/debug/ 挂载 debugfs 文件系统。
驱动调用 debugfs API 在该目录下创建自己的文件和目录。
用户通过 cat, echo, hexdump 等命令读写这些文件,内核中对应的回调函数会被触发。
4. 如何挂载?
如果系统没有自动挂载,可以手动挂载:
sudo mount -t debugfs none /sys/kernel/debug
二、debugfs 核心 API
创建/删除目录
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);voiddebugfs_remove(struct dentry *dentry); // 删除文件或目录
// 支持读写的整数文件struct dentry *debugfs_create_u8/u16/u32/u64(const char *name, umode_t mode, struct dentry *parent, u8 *value);// 类似的 API: debugfs_create_x8/x16/x32/x64 (十六进制显示)// 支持读写的布尔文件struct dentry *debugfs_create_bool(const char *name, umode_t mode, struct dentry *parent, bool *value);
struct dentry *debugfs_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops);
struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent, const char *target);
三、一个简单的驱动示例
在 /sys/kernel/debug/example_debugfs/ 下创建目录。
创建几个调试文件:
id:可读写的整数值(文本格式)。
counter:只读的递增值(每次读取+1)。
data:可读写任意文本数据的文件。
flag:可读写的布尔值。
代码:example_debugfs.c
#include<linux/module.h>#include<linux/kernel.h>#include<linux/init.h>#include<linux/debugfs.h>#include<linux/fs.h>#include<linux/uaccess.h>#include<linux/slab.h>#define DRIVER_NAME "example_debugfs"// 驱动私有数据结构struct example_data { struct dentry *dir; // debugfs 目录 u32 id; // 可读写整数 atomic_t counter; // 只读计数器 bool flag; // 布尔开关 char *data_buffer; // 自定义数据缓冲区 size_t data_size;};static struct example_data example;// ------------------------------------------------------------// 1. 自定义文件 "data" 的操作函数staticssize_tdata_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos){ ssize_t len; if (*ppos >= example.data_size) return 0; len = min(count, example.data_size - (size_t)*ppos); if (copy_to_user(user_buf, example.data_buffer + *ppos, len)) return -EFAULT; *ppos += len; return len;}staticssize_tdata_write(struct file *file, constchar __user *user_buf, size_t count, loff_t *ppos){ char *new_buf; new_buf = kmalloc(count + 1, GFP_KERNEL); if (!new_buf) return -ENOMEM; if (copy_from_user(new_buf, user_buf, count)) { kfree(new_buf); return -EFAULT; } new_buf[count] = '\0'; // 确保字符串结束 // 替换旧缓冲区 kfree(example.data_buffer); example.data_buffer = new_buf; example.data_size = count; return count;}static const struct file_operations data_fops = { .owner = THIS_MODULE, .read = data_read, .write = data_write,};// ------------------------------------------------------------// 2. 自定义文件 "counter" 的操作函数(只读,每次读自动递增)staticssize_tcounter_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos){ char buf[32]; int val; ssize_t len; if (*ppos > 0) // 支持多次读 return 0; val = atomic_inc_return(&example.counter); // 读取并自增 len = snprintf(buf, sizeof(buf), "%d\n", val); if (copy_to_user(user_buf, buf, len)) return -EFAULT; *ppos += len; return len;}static const struct file_operations counter_fops = { .owner = THIS_MODULE, .read = counter_read,};// ------------------------------------------------------------// 模块初始化和退出staticint __init example_init(void){ // 1. 创建 debugfs 目录 example.dir = debugfs_create_dir("example_debugfs", NULL); if (!example.dir) { printk(KERN_ERR "%s: 创建目录失败\n", DRIVER_NAME); return -ENOMEM; } // 2. 创建各种调试文件 // a) 可读写的32位整数(十进制显示) debugfs_create_u32("id", 0644, example.dir, &example.id); // b) 可读写的布尔值 debugfs_create_bool("flag", 0644, example.dir, &example.flag); // c) 自定义文件 "counter"(只读,每次读+1) debugfs_create_file("counter", 0444, example.dir, NULL, &counter_fops); // d) 自定义文件 "data"(可读写任意数据) debugfs_create_file("data", 0644, example.dir, NULL, &data_fops); // 3. 初始化数据 example.id = 100; atomic_set(&example.counter, 0); example.flag = true; example.data_buffer = kzalloc(1024, GFP_KERNEL); if (!example.data_buffer) { debugfs_remove_recursive(example.dir); return -ENOMEM; } strcpy(example.data_buffer, "Hello, debugfs!"); example.data_size = strlen(example.data_buffer); printk(KERN_INFO "%s: 初始化成功,目录在 /sys/kernel/debug/example_debugfs/\n", DRIVER_NAME); return 0;}staticvoid __exit example_exit(void){ // 递归删除目录及其下所有文件 debugfs_remove_recursive(example.dir); kfree(example.data_buffer); printk(KERN_INFO "%s: 退出\n", DRIVER_NAME);}module_init(example_init);module_exit(example_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Your Name");MODULE_DESCRIPTION("A simple example of using debugfs");
obj-m += example_debugfs.oKERNEL_DIR ?= /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)all: $(MAKE) -C $(KERNEL_DIR) M=$(PWD) modulesclean: $(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
四、编译、加载和测试
# 查看目录ls -la /sys/kernel/debug/example_debugfs/# 输出: counter data flag id# 1. 读写 id (整数)cat /sys/kernel/debug/example_debugfs/idecho 200 | sudo tee /sys/kernel/debug/example_debugfs/id# 2. 读写 flag (布尔)cat /sys/kernel/debug/example_debugfs/flagecho N | sudo tee /sys/kernel/debug/example_debugfs/flag # 写 0/N/n 为 false# 3. 读取 counter (每次读会自动+1)cat /sys/kernel/debug/example_debugfs/countercat /sys/kernel/debug/example_debugfs/counter # 再次读取,值会增加# 4. 读写 datacat /sys/kernel/debug/example_debugfs/dataecho "New debug data" | sudo tee /sys/kernel/debug/example_debugfs/data