前言
内核开发者经常需要导出一些信息到用户空间,用于分析内核运行逻辑。最常见的方法是使用 printk(),不过在嵌入式中,printk() 往往直接打印到 console,一旦 printk() 被频繁调用的话,console 就会被刷屏,此时输入命令都是件困难的事情。
有时我们只想偶尔看一下某个内核变量的值,但是一旦使用 printk(),它就会无休止地循环打印;另一方面,使用 printk() 只能打印,而不能从用户空间去修改内核变量的值。为了应对这种情况,我们可以使用 procfs 和 sysfs 这两个虚拟文件系统来实现上述需求。不过通过 procfs 和 sysfs 创建一个文件,来读写某个变量的值,从编码角度看,略微复杂了些。
debugfs
为了让开发人员更轻松地实现调试,内核提供了 debugfs,这是一个致力于调试信息的虚拟文件系统。debugfs 旨在成为一个相对简单和轻量级的子系统。开发人员只需要寥寥几行代码就可以实现调试,并且只需要引用一个头文件 linux/debugfs.h。
示例(调试内核里面一个 u32 类型的变量 count):
debugfs_int.c
#include <linux/debugfs.h>
#include <linux/kernel.h>
#include <linux/module.h>
static struct dentry *hello_root;
static u32 count;
static int __init hello_init(void)
{
hello_root = debugfs_create_dir("hello", NULL);
if (hello_root == NULL) {
printk("%s: create debugfs dir failed\n", __func__);
return -1;
}
count = 100;
debugfs_create_u32("count", 0644, hello_root, &count);
return 0;
}
static void __exit hello_exit(void)
{
if (hello_root) {
debugfs_remove_recursive(hello_root);
}
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
Makefile
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
obj-m :=debugfs_int.o
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.* .tmp_versions *.mod *.order *.symvers *.dwo
# make && insmod debugfs_int.ko
# cat /sys/kernel/debug/hello/count
100
# echo 30 > /sys/kernel/debug/hello/count
# cat /sys/kernel/debug/hello/count
30
使用起来是不是非常方便,除了u32,debugfs 还有很多类型接口
debugfs_create_u8()
debugfs_create_u16()
debugfs_create_u64()
debugfs_create_x8()
debugfs_create_x16()
...
debugfs_create_size_t()
debugfs_create_bool()
...
debugfs_create_file()
如果想一次性打印多个变量的值,可以使用 debugfs_create_file(),它可以自定义 read() 函数,我们可以在其中输入多个变量的值。
示例:
debugfs_file.c
#include <linux/debugfs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
static struct dentry *hello_root;
int count_1 = 20;
int count_2 = 30;
static ssize_t hello_read(struct file *filp, char __user *user_buf, size_t count, loff_t *ppos)
{
char *buf;
u32 len = 0, size = 4096;
size_t retval;
buf = kzalloc(size, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
len = scnprintf(buf, size, "count_1: %d\n", count_1);
len += scnprintf(buf + len, size - len, "count_2: %d\n", count_2);
retval = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return retval;
}
static ssize_t hello_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
return count;
}
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.read = hello_read,
.write = hello_write,
};
static int __init hello_init(void)
{
hello_root = debugfs_create_dir("hello", NULL);
if (hello_root == NULL) {
printk("%s: create debugfs dir failed\n", __func__);
return -1;
}
debugfs_create_file("hello_file", S_IWUGO, hello_root, NULL, &hello_fops);
return 0;
}
static void __exit hello_exit(void)
{
if (hello_root) {
debugfs_remove_recursive(hello_root);
}
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
obj-m :=debugfs_file.o
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.* .tmp_versions *.mod *.order *.symvers *.dwo
# make && insmod debugfs_file.ko
# cat /sys/kernel/debug/hello/hello_file
count_1: 20
count_2: 30
1. 前言
Debugfs虚拟文档系统是一种内核空间与用户空间的接口,基于libfs库实现,专用于开发人员调试,便于向用户空间导出内核空间数据。
内核开发者经常需要向用户空间应用输出一些调试信息,在稳定的系统中可能根本不需要这些调试信息,但是在开发过程中,为了搞清楚内核的行为,调试信息非常必要。
2. 使用
2.1 自动挂载
要使用debugfs,需要在内核编译配置中配置 CONFIG_DEBUG_FS=y选项,并且将其自动挂载到默认的目录 /sys/kernel/debug。
2.2 手动挂载
mkdir tmp //新建目录(手动挂载点)
mount -t debugfs debugfs tmp //挂载到tmp/ 目录
3. Debugfs的API
//创建和撤销目录及文件
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);
struct dentry *debugfs_create_file(const char *name, mode_t mode, struct dentry *parent, void *data, const struct file_operations *fops);
void debugfs_remove(struct dentry *dentry);
void debugfs_remove_recursive(struct dentry *dentry);
//······
4. 应用
4.1 查看GPIO使用情况
在实际的开发任务中,有时候不知道GPIO的状态,也不知道在内核中GPIO是否申请成功。
可以通过/sys/kernel/debug/gpio这个文件来查看。这个文件显示了申请成功的GPIO的输入输出状态和电平。
//1.去默认挂载点查看gpio的使用情况
cat sys/kernel/debug/gpio
//2.去手动挂载点(/system/tmp)查看gpio的使用情况
mount -rw -o remount /system //创建tmp目录时提示文件系统只读时重新mount一下该目录
cd system
mkdir tmp //新建目录(手动挂载点)
mount -t debugfs debugfs tmp/ //挂载到/system/tmp 目录
cat /system/tmp/gpio
//cat 之后就会显示gpio的使用情况
GPIOs 0-31, platform/209c000.gpio, 209c000.gpio:
gpio-1 (2190000.usdhc cd ) in lo
gpio-26 (sysfs ) in hi
GPIOs 32-63, platform/20a0000.gpio, 20a0000.gpio:
gpio-34 (OTG_CHARGE_EN ) out lo
gpio-35 (otg-mode ) out lo
GPIOs 64-95, platform/20a4000.gpio, 20a4000.gpio:
gpio-84 (spi_imx ) out lo
GPIOs 96-127, platform/20a8000.gpio, 20a8000.gpio:
GPIOs 128-159, platform/20ac000.gpio, 20ac000.gpio:
······
linux DebugFS介绍
Debugfs 是内核开发人员向用户空间提供信息的一种简单方法。 /proc 仅用于提供有关进程的信息,或者 sysfs 具有严格的每个文件一个值的规则,而 debugfs 则完全没有规则。开发人员可以将他们想要的任何信息放在那里。 debugfs 文件系统也不能作为用户空间的稳定 ABI;理论上,在那里导出的文件没有稳定性限制。现实世界并不总是那么简单,即使是 debugfs 接口,最好的设计也是考虑到它们需要永远维护。
Debugfs 通常使用如下命令安装:
mount -t debugfs none /sys/kernel/debug
(或等效的 /etc/fstab 行)。默认情况下,只有 root 用户可以访问 debugfs 根目录。要更改对树的访问,可以使用“uid”、“gid”和“mode”挂载选项。
请注意,debugfs API 仅以 GPL 格式导出到模块。
使用 debugfs 的代码应该包括 <linux/debugfs.h>。然后,首要任务是创建至少一个目录来保存一组 debugfs 文件:
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);
如果成功,此调用将在指定的父目录下创建一个名为 name 的目录。如果 parent 为 NULL,则将在 debugfs 根目录中创建该目录。成功时,返回值是一个 struct dentry 指针,可用于在目录中创建文件(并在最后清理它)。 ERR_PTR(-ERROR) 返回值表示出现问题。如果返回 ERR_PTR(-ENODEV),这表明内核是在没有 debugfs 支持的情况下构建的,并且下面描述的任何功能都将不起作用。
在 debugfs 目录中创建文件的最常用方法是:
struct dentry *debugfs_create_file(const char *name, umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops);
这里,name 是要创建的文件的名称,mode 描述文件应具有的访问权限,parent 表示应保存文件的目录,数据将存储在生成的 inode 结构的 i_private 字段中,fops 是一组实现文件行为的文件操作。至少应该提供 read() 和/或 write() 操作;其他的可以根据需要加入。同样,返回值将是指向已创建文件的 dentry 指针,错误时为 ERR_PTR(-ERROR),如果缺少 debugfs 支持,则返回 ERR_PTR(-ENODEV)。
创建具有初始大小的文件,可以使用以下函数代替:
void debugfs_create_file_size(const char *name, umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops,
loff_t file_size);
file_size 是初始文件大小。其他参数与函数 debugfs_create_file 相同。
在许多情况下,实际上并不需要创建一组文件操作。 debugfs 代码为简单的情况提供了许多帮助函数。可以使用以下任何一种创建包含单个整数值的文件:
void debugfs_create_u8(const char *name, umode_t mode,
struct dentry *parent, u8 *value);
void debugfs_create_u16(const char *name, umode_t mode,
struct dentry *parent, u16 *value);
void debugfs_create_u32(const char *name, umode_t mode,
struct dentry *parent, u32 *value);
void debugfs_create_u64(const char *name, umode_t mode,
struct dentry *parent, u64 *value)
这些文件支持读取和写入给定值;如果不应写入特定文件,只需相应地设置模式位。这些文件中的值是十进制的;如果十六进制更合适,可以使用以下函数代替:
void debugfs_create_x8(const char *name, umode_t mode,
struct dentry *parent, u8 *value);
void debugfs_create_x16(const char *name, umode_t mode,
struct dentry *parent, u16 *value);
void debugfs_create_x32(const char *name, umode_t mode,
struct dentry *parent, u32 *value);
void debugfs_create_x64(const char *name, umode_t mode,
struct dentry *parent, u64 *value);
只要开发人员知道要导出的值的大小,这些函数就很有用。但是,某些类型在不同的架构上可能具有不同的宽度,这使情况有些复杂。在这种特殊情况下,有一些功能可以提供帮助:
void debugfs_create_size_t(const char *name, umode_t mode,
struct dentry *parent, size_t *value);
正如所料,此函数将创建一个 debugfs 文件来表示 size_t 类型的变量。
同样,对于 unsigned long 类型的变量,有十进制和十六进制的助手:
struct dentry *debugfs_create_ulong(const char *name, umode_t mode,
struct dentry *parent,
unsigned long *value);
void debugfs_create_xul(const char *name, umode_t mode,
struct dentry *parent, unsigned long *value);
布尔值可以放在 debugfs 中:
void debugfs_create_bool(const char *name, umode_t mode,
struct dentry *parent, bool *value);
对结果文件的读取将产生 Y(对于非零值)或 N,后跟换行符。如果写入,它将接受大写或小写值,或者 1 或 0。任何其他输入都将被忽略。
此外, atomic_t 值可以放在 debugfs 中:
void debugfs_create_atomic_t(const char *name, umode_t mode,
struct dentry *parent, atomic_t *value)
读取此文件将获得 atomic_t 值,写入此文件将设置 atomic_t 值。 另一种选择是导出具有以下结构和功能的任意二进制数据块:
struct debugfs_blob_wrapper {
void *data;
unsigned long size;
};
struct dentry *debugfs_create_blob(const char *name, umode_t mode,
struct dentry *parent,
struct debugfs_blob_wrapper *blob);
读取此文件将返回 debugfs_blob_wrapper 结构指向的数据。一些驱动程序使用“blob”作为返回多行(静态)格式化文本输出的简单方法。此函数可用于导出二进制信息,但在主线中似乎没有任何代码这样做。请注意,使用 debugfs_create_blob() 创建的所有文件都是只读的。
如果你想转储一个寄存器块(在开发过程中经常发生的事情,即使很少有这样的代码到达主线。Debugfs 提供两个功能:一个是创建一个仅寄存器文件,另一个是在中间插入一个寄存器块另一个顺序文件:
struct debugfs_reg32 {
char *name;
unsigned long offset;
};
struct debugfs_regset32 {
const struct debugfs_reg32 *regs;
int nregs;
void __iomem *base;
struct device *dev; /* Optional device for Runtime PM */
};
debugfs_create_regset32(const char *name, umode_t mode,
struct dentry *parent,
struct debugfs_regset32 *regset);
void debugfs_print_regs32(struct seq_file *s, const struct debugfs_reg32 *regs,
int nregs, void __iomem *base, char *prefix);
“base”参数可能为 0,但您可能希望使用 __stringify 构建 reg32 数组,并且许多寄存器名称(宏)实际上是寄存器块基数上的字节偏移量。
如果要在 debugfs 中转储 u32 数组,可以使用以下命令创建文件:
struct debugfs_u32_array {
u32 *array;
u32 n_elements;
};
void debugfs_create_u32_array(const char *name, umode_t mode,
struct dentry *parent,
struct debugfs_u32_array *array);
“array”参数包含一个指向数组数据及其元素数量的指针。注意:一旦创建数组,它的大小就不能改变。
有一个辅助函数来创建设备相关的 seq_file:
void debugfs_create_devm_seqfile(struct device *dev,
const char *name,
struct dentry *parent,
int (*read_fn)(struct seq_file *s,
void *data));
“dev”参数是与这个debugfs文件相关的设备,“read_fn”是一个函数指针,被调用来打印seq_file的内容。
还有其他几个面向目录的帮助函数:
struct dentry *debugfs_rename(struct dentry *old_dir,
struct dentry *old_dentry,
struct dentry *new_dir,
const char *new_name);
struct dentry *debugfs_create_symlink(const char *name,
struct dentry *parent,
const char *target);
调用 debugfs_rename() 将为现有的 debugfs 文件提供一个新名称,可能位于不同的目录中。 new_name 在调用之前不能存在;返回值是带有更新信息的 old_dentry。可以使用 debugfs_create_symlink() 创建符号链接。
所有 debugfs 用户都必须考虑一件重要的事情:不会自动清理在 debugfs 中创建的任何目录。如果一个模块在没有明确删除 debugfs 条目的情况下被卸载,结果将是很多过时的指针,并且没有结束高度反社会的行为。因此,所有 debugfs 用户——至少是那些可以构建为模块的用户——必须准备好删除他们在那里创建的所有文件和目录。可以通过以下方式删除文件:
void debugfs_remove(struct dentry *dentry);
dentry 值可以是 NULL 或错误值,在这种情况下不会删除任何内容。
曾几何时,debugfs 用户需要记住他们创建的每个 debugfs 文件的 dentry 指针,以便清理所有文件。不过,我们现在生活在更加文明的时代,debugfs 用户可以调用
void debugfs_remove_recursive(struct dentry *dentry);
如果这个函数被传递了一个指向顶层目录对应的dentry的指针,那么该目录下的整个层次结构将被删除。
-
Linux内核空间-用户空间通信之debugfs
一、debugfs文件系统简介
debugfs虚拟文件系统是一种内核空间与用户空间的接口,基于libfs库实现,专用于开发人员调试,便于向用户空间导出内核空间数据(当然,反方向也可以)。debugfs在linux内核版本2.6.10引入,作者是Greg Kroah-Hartman。
与procfs和sysfs不同,前者主要提供进程信息(当然后来又加入设备、内存、网络等信息,比较杂乱),后者主要提供设备信息,且有一个文件提供一个值的“规则”,是Linux通用设备模型的影射。debugfs没有类似的限制,开发者可以放入任何信息。
二、debugfs的使用
说明:函数接口请参见:Linux Kernel: fs/debugfs/file.c File Reference
1. 挂载debugfs文件系统
要使用debugfs,需要在内核编译配置中配置CONFIG_DEBUG_FS选项,一般的发行版都会默认编译进了内核,并且将其自动挂载默认的目录(/sys/kernel/debug),可通过以下命令查看:
也可手动挂载到其它位置:$ mkdir /debugfs $ mount -t debugfs none /debugfs
2. 利用debugfs导出基本数据类型的变量
debugfs可以将内核中基本整数类型的变量导出为单个文件,在用户空间中可以直接对其读写(如使用cat、echo命令),只要权限允许即可。支持的类型有:u8, u16, u32, u64, size_t和 bool。其中bool类型在内核中要定义为u32类型,在用户空间中对应的文件内容则显示为Y或N。示例代码如下:static struct dentry *root_d = debugfs_create_dir("exam_debugfs", NULL); //在debugfs根目录下创建新目录exam_debugfs,然会新建目录的目录项指针 static u8 var8; debugfs_create_u8("var-u8", 0664, root_d, &var8); //在exam_debugfs中创建变量var8对应的文件,名为var-u8,权限为0664 static u32 varbool; debugfs_create_bool("var-bool", 0664, root_d, &varbool); //bool变量
3. 利用debugfs导出数据块(只读)
debugfs提供的debugfs_create_blob函数可导出数据块,示例代码如下:
char buf[] = "Hello debugfs! "; b.data = buf; b.size = strlen(buf) + 1; debugfs_create_blob("blob", 0644, root_d, &b); // blob is readonly, even if 0644
没错,debugfs提供的debugfs_blob_wrapper结构所导出的数据块只能读取,不能修改。4. 利用debugfs导出u32数组(只读)
debugfs提供的debugfs_create_u32_array函数可导出内核中u32类型的数组。示例代码如下:
u32 arr[] = {1,2,3,4,5}; debugfs_create_u32_array("array", 0664, root_d, arr, sizeof(arr)/sizeof(u32));
5. 实现可读写数据块的导出
实现并不难,只需实现struct file_operations的open、write、read方法即可。然后调用debugfs提供的debugfs_create_file即可。该方法的原型如下:struct dentry* debugfs_create_file ( const char * name, umode_t mode, struct dentry * parent, void * data, // 传入的data指针会被赋值给新建文件对应inode的i_private字段 const struct file_operations * fops )
下面,仿照struct debugfs_blob_wrapper的实现,实现struct my_blob_wrapper和my_blob_wrapper_ops,提供可读写的“blob”:/** 自定义可读写blob **/ struct my_blob_wrapper{ void *data; unsigned long size; // data缓冲区长度 }; static int my_blob_wrapper_open(struct inode *inode, struct file *filp) { filp->private_data = inode->i_private; // inode->i_private被设置为debugfs_create_file传入的data参数 return 0; } static ssize_t my_blob_wrapper_read(struct file *filp, char __user *user_buf, size_t count, loff_t *ppos) { struct my_blob_wrapper *blob = filp->private_data; return simple_read_from_buffer(user_buf, count, ppos, blob->data, blob->size);//此函数有libfs提供,与下面逻辑等价 // if (*ppos >= blob->size) { // return 0; // } // if (*ppos + count > blob->size) { // count = blob->size - *ppos; // } // if (copy_to_user(user_buf, blob->data + *ppos, count) != 0) { // return -EFAULT; // } // *ppos += count; // return count; } static ssize_t my_blob_wrapper_write(struct file *filp, const char __user *user_buf, size_t count, loff_t *ppos) { struct my_blob_wrapper *blob = filp->private_data; return simple_write_to_buffer(blob->data, blob->size, ppos, user_buf, count);//此函数由libfs提供,与下面逻辑等价 // if (*ppos >= blob->size) { // return 0; // } // if (*ppos + count > blob->size) { // count = blob->size - *ppos; // } // if (copy_from_user(blob->data + *ppos, user_buf, count) != 0) { // return -EFAULT; // } // *ppos += count; // return count; } static struct file_operations my_blob_wrapper_ops = { .owner = THIS_MODULE, .open = my_blob_wrapper_open, .read = my_blob_wrapper_read, .write = my_blob_wrapper_write, .llseek = default_llseek,//VFS提供 }; struct dentry *my_create_blob(const char *name, umode_t mode, struct dentry *parent, struct my_blob_wrapper *blob) { return debugfs_create_file(name, mode, parent, blob, &my_blob_wrapper_ops); } /* done */
6. 注意:
① 在debugfs中创建的文件和目录在模块退出时,并不会自动删除,可调用debugfs_remove_recursive删除整个目录,或调用debugfs_remove删除单个文件。
② u32_array在用户空间文件中显示为空格隔开的元素
③ debugfs_create_x{8,16,32,64}与debugfs_create_u{8,16,32,64}的不同在于前者显示为16进制,后者显示为10进制三、debugfs示例完整代码
#include <linux/module.h> #include <linux/debugfs.h> #include <linux/fs.h> // for libfs #include <asm-generic/uaccess.h> /** 自定义可读写blob **/ struct my_blob_wrapper{ void *data; unsigned long size; }; static int my_blob_wrapper_open(struct inode *inode, struct file *filp) { filp->private_data = inode->i_private; return 0; } static ssize_t my_blob_wrapper_read(struct file *filp, char __user *user_buf, size_t count, loff_t *ppos) { struct my_blob_wrapper *blob = filp->private_data; return simple_read_from_buffer(user_buf, count, ppos, blob->data, blob->size); // from libfs } static ssize_t my_blob_wrapper_write(struct file *filp, const char __user *user_buf, size_t count, loff_t *ppos) { struct my_blob_wrapper *blob = filp->private_data; return simple_write_to_buffer(blob->data, blob->size, ppos, user_buf, count); } static struct file_operations my_blob_wrapper_ops = { .owner = THIS_MODULE, .open = my_blob_wrapper_open, .read = my_blob_wrapper_read, .write = my_blob_wrapper_write, .llseek = default_llseek, // from vfs }; /* 接口函数 */ struct dentry *my_create_blob(const char *name, umode_t mode, struct dentry *parent, struct my_blob_wrapper *blob) { return debugfs_create_file(name, mode, parent, blob, &my_blob_wrapper_ops); } /** my_clob implementation end **/ static struct dentry *root_d; static u8 var8; static u16 var16; static u32 var32; static u32 varbool; static char buf[] = "Hello debugfs! "; static struct debugfs_blob_wrapper b; static struct my_blob_wrapper b2; static u32 arr[] = {1,2,3,4,5}; int __init mod_init(void) { printk(KERN_INFO "exam_debugfs: initialing... "); root_d = debugfs_create_dir("exam_debugfs", NULL); if (!root_d) { printk(KERN_INFO "exam_debugfs: error create root dir "); return 1; } /* u{8,16,32}, bool */ debugfs_create_u8("var-u8", 0664, root_d, &var8); debugfs_create_u16("var-u16", 0664, root_d, &var16); debugfs_create_u32("var-u32", 0664, root_d, &var32); debugfs_create_bool("var-bool", 0664, root_d, &varbool); /* u32_array */ debugfs_create_u32_array("array", 0664, root_d, arr, sizeof(arr)/sizeof(u32)); /* blob_wrapper */ b.data = buf; b.size = strlen(buf) + 1; debugfs_create_blob("blob", 0644, root_d, &b); // blob is readonly, even if 0644 /* my_blob_wrapper */ b2.data = buf; b2.size = strlen(buf) + 1; my_create_blob("myblob", 0644, root_d, &b2); return 0; } void __exit mod_exit(void) { debugfs_remove_recursive(root_d); printk(KERN_INFO "exam_debugfs: exiting... "); } module_init(mod_init); module_exit(mod_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("a demo for debugfs"); MODULE_AUTHOR("rsljdkt");
四、补充:debugfs bool的实现
static ssize_t read_file_bool(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { char buf[3]; u32 *val = file->private_data; if (*val) buf[0] = 'Y'; else buf[0] = 'N'; buf[1] = ' '; buf[2] = 0x00; return simple_read_from_buffer(user_buf, count, ppos, buf, 2); } static ssize_t write_file_bool(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { char buf[32]; size_t buf_size; bool bv; u32 *val = file->private_data; buf_size = min(count, (sizeof(buf)-1)); if (copy_from_user(buf, user_buf, buf_size)) return -EFAULT; if (strtobool(buf, &bv) == 0) *val = bv; return count; }
-
DebugFS,顾名思义,是一种用于内核调试的虚拟文件系统,内核开发者通过debugfs和用户空间交换数据。类似的虚拟文件系统还有procfs和sysfs等,这几种虚拟文件系统都并不实际存储在硬盘上,而是Linux内核运行起来后才建立起来。
通常情况下,最常用的内核调试手段是printk。但printk并不是所有情况都好用,比如打印的数据可能过多,我们真正关心的数据在大量的输出里不是那么一目了然;或者我们在调试时可能需要修改某些内核变量,这种情况下printk就无能为力,而如果为了修改某个值重新编译内核或者驱动又过于低效,此时就需要一个临时的文件系统可以把我们需要关心的数据映射到用户空间。在过去,procfs可以实现这个目的,到了2.6时代,新引入的sysfs也同样可以实现,但不论是procfs或是sysfs,用它们来实现某些debug的需求,似乎偏离了它们创建的本意。比如procfs,其目的是反映进程的状态信息;而sysfs主要用于Linux设备模型。不论是procfs或是sysfs的接口应该保持相对稳定,因为用户态程序很可能会依赖它们。当然,如果我们只是临时借用procfs或者sysfs来作debug之用,在代码发布之前将相关调试代码删除也无不可。但如果相关的调试借口要在相当长的一段时间内存在于内核之中,就不太适合放在procfs和sysfs里了。故此,debugfs应运而生。
默认情况下,debugfs会被挂载在目录/sys/kernel/debug之下,如果您的发行版里没有自动挂载,可以用如下命令手动完成:
# mount -t debugfs none /your/debugfs/dir
Linux内核为debugfs提供了非常简洁的API,本文接下来将以一个实作为例来介绍,sample code可以从这里下载。
这个实作会在debugfs中建立如下的目录结构:
其中,a对应模块中的一个u8类型的变量,b和subdir下面的c都是对应模块里的一个字符数组,只是它们的实现方式不同。
在module_init里,我们首先要建立根目录mydebug:
my_debugfs_root = debugfs_create_dir("mydebug", NULL);
第一个参数是目录的名称,第二个参数用来指定这个目录的上级目录,如果是NULL,则表示是放在debugfs的根目录里。
子目录也是用debugfs_create_dir来实现:
sub_dir = debugfs_create_dir("subdir", my_debugfs_root);
建立文件a的代码非常简单:
debugfs_create_u8("a", 0644, my_debugfs_root, &a);
这表示文件名为“a”,文件属性是0644,父目录是上面建立的“mydebug”,对应的变量是模块中的a。
Linux内核还提供了其他一些创建debugfs文件的API,请参考本文的附录。
b是一个32-bytes的字符数组,在debugfs里,数组可以用blob wrapper来实现。
char hello[32] = "Hello world!\n";
struct debugfs_blob_wrapper b;
b.data = (void *)hello;
b.size = strlen(hello) + 1;
debugfs_create_blob("b", 0644, my_debugfs_root, &b);
这里需要注意的是,blob wrapper定义的数据只能是只读的。在本例中,虽然我们把文件b的权限设定为0644,但实际这个文件还是只读的,如果试图改写这个文件,系统将提示出错。
如果需要对内核数组进行写的动作,blob wrapper就无法满足要求,我们只能通过自己定义文件操作来实现。在这个实作里,可以参考文件c的实现。c和b在模块里对应着同一块字符数组,不同的是,b是只读的,而c通过自定义的文件操作同时实现了读和写。
staTIc int c_open(struct inode *inode, struct file *filp)
{
filp->private_data = inode->i_private;
return 0;
}
staTIc ssize_t c_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
if (*ppos >= 32)
return 0;
if (*ppos + count > 32)
count = 32 - *ppos;
if (copy_to_user(buffer, hello + *ppos, count))
return -EFAULT;
*ppos += count;
return count;
}
staTIc ssize_t c_write(struct file *filp, const char __user *buffer,
size_t count, loff_t *ppos)
{
if (*ppos >= 32)
return 0;
if (*ppos + count > 32)
count = 32 - *ppos;
if (copy_from_user(hello + *ppos, buffer, count))
return -EFAULT;
*ppos += count;
return count;
}
struct file_operaTIons c_fops = {
.owner = THIS_MODULE,
.open = c_open,
.read = c_read,
.write = c_write,
};
debugfs_create_file("c", 0644, sub_dir, NULL, &c_fops);
注:代码里,c_open其实并没有任何用处,因为c_read和c_write直接引用了全局变量hello。这里,我们也可以换一种写法,在read/write函数里用filp->private_data来引用字符数组hello。
到这里,三个文件和子目录已经创建完毕。在module_exit中,我们要记得释放创建的数据。
debugfs_remove_recursive(my_debugfs_root);
debugfs_remove_recursive可以帮我们逐步移除每个分配