Linux内核debugfs介绍

前言

内核开发者经常需要导出一些信息到用户空间,用于分析内核运行逻辑。最常见的方法是使用 printk(),不过在嵌入式中,printk() 往往直接打印到 console,一旦 printk() 被频繁调用的话,console 就会被刷屏,此时输入命令都是件困难的事情。

有时我们只想偶尔看一下某个内核变量的值,但是一旦使用 printk(),它就会无休止地循环打印;另一方面,使用 printk() 只能打印,而不能从用户空间去修改内核变量的值。为了应对这种情况,我们可以使用 procfs 和 sysfs 这两个虚拟文件系统来实现上述需求。不过通过 procfs 和 sysfs 创建一个文件,来读写某个变量的值,从编码角度看,略微复杂了些。

Debugs

Debugfs 作为内核开发人员向用户空间提供信息的一种简单方法而存在。与/proc 不同,它只用于处理有关进程的信息,而 sysfs 有严格的每个文件一个值的规则,debug fs 根本没有规则。开发人员可以将他们想要的任何信息放在那里。Debug 文件系统还不能作为用户空间的稳定 ABI; 理论上,导出的文件没有稳定性约束。现实世界并不总是那么简单,尽管如此; 即使是调试接口的最佳设计思想也是需要永久维护的。

调试通常使用以下命令安装,或等效的/etc/fstab写入要挂载的文件系统:
mount -t debugfs none /sys/kernel/debug

具体来说,命令mount -t debugfs none /sys/kernel/debug的含义是将debugfs文件系统挂载到/sys/kernel/debug目录下。-t debugfs指定了文件系统类型为debugfs,none表示没有指定设备,/sys/kernel/debug是挂载点目录。

默认情况下,只有root用户才能访问 debugfs 根目录。要更改对树的访问,可以使用“ uid”、“ gid”和“ mode”挂载选项。

宏开关

CONFIG_DEBUG_FS=y

使用debugfs的内核模块

很多低功耗相关的模块都使用了debugfs

  • clk
  • regulator
  • pinctrl
  • gpio
  • devfreq
  • pm
  • etc

debugfs的使用

创建目录

使用debugfs的代码应该包括<linux/debugfs.h>。然后,第一步是创建至少一个目录来保存一组debugfs文件。

如果调用成功,将在指定的父目录下创建一个名为name的目录。如果父目录为NULL,则将在debugfs根目录中创建该目录。成功后,返回值是一个struct-dentry指针,可用于在目录中创建文件(并在最后清理)。ERR_PTR(-ERROR)返回值表示失败。如果返回ERR_PTR(-ENODEV),则表明内核是在没有debugfs支持的情况下构建的,并且下面描述的函数都无法工作。

struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);

创建文件节点

这里,name是要创建的文件的名称,mode描述文件应该具有的访问权限,parent表示应该保存文件的目录,数据将存储在生成的inode结构的i_private字段中,fops是一组实现文件行为的文件操作。至少应该提供read()和/或write()操作;可以根据需要包括其他的。同样,返回值将是指向创建文件的dentry指针,错误时为ERR_PTR(-ERROR),如果缺少debugfs支持,则为ERR_PTR(-ENODEV)。在debugfs目录中创建文件的最常见方法是:

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

创建整数值文件

在许多情况下,实际上并不需要创建一组文件操作;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);

与体系结构相关的大小

一般情况开发人员都知道要导出的值的大小,上面函数就很有用。然而,某些类型在不同的体系结构上可能具有不同的宽度,这在一定程度上使情况复杂化。在这种特殊情况下,有一些功能可以提供帮助

  • size_t
  • long
  • xul
  • bool
void debugfs_create_size_t(const char *name, umode_t mode,
                           struct dentry *parent, size_t *value);

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);

void debugfs_create_bool(const char *name, umode_t mode,
                         struct dentry *parent, bool *value);

导出二进制块

读取此文件将返回debugfs_blob_wrapper结构所指向的数据。一些驱动程序使用“blobs”作为返回几行(静态)格式化文本输出的简单方法。这个函数可以用来导出二进制信息,但在主线中似乎没有任何代码可以这样做。请注意,使用debugfs_create_blob()创建的所有文件都是只读的。

导出任意二进制数据块,其结构和功能如下:

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);

导出数组

“array”参数包装一个指向数组数据及其元素数的指针。注意:一旦创建了数组,其大小就无法更改。

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);

文件相关

命名

对 debug fs _ rename()的调用将为现有的 debug 文件提供一个新名称,可能位于不同的目录中。New _ name 在调用之前不能存在; 返回值为 old _ dentry,其中包含更新的信息。可以使用 degugfs _ create _ symlink ()创建符号链接。

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用户都必须考虑一件重要的事情:在debugfs中创建的任何目录都不会自动清理。如果卸载模块时没有明确删除debugfs条目,那么结果将是大量过时的指针,并且会出现高度反社会的行为。因此,所有的debugfs用户——至少是那些可以作为模块构建的用户——都必须准备好删除他们在那里创建的所有文件和目录。可以使用以下方法删除文件:
void debugfs_remove(struct dentry *dentry);
debugfs用户需要记住他们创建的每个debugfs文件的dentry指针,以便可以清理所有文件。不过,我们现在生活在一个更加文明的时代,debugfs用户可以调用:如果向该函数传递与顶级目录对应的dentry的指针,则该目录下的整个层次结构将被删除。
void debugfs_remove_recursive(struct dentry *dentry);

Ref

https://docs.kernel.org/filesystems/debugfs.html

posted @ 2023-05-15 15:50  zephyr~  阅读(304)  评论(0编辑  收藏  举报