DebugFS

版权所有 © 2009 Jonathan Corbet <corbet@lwn.net>

Debugfs 的存在是为了给内核开发者提供一种简单的方式,将信息提供给用户空间。与 /proc(仅用于进程信息)或 sysfs(具有严格的每个文件一个值的规则)不同,debugfs 没有规则。开发者可以将他们想要的任何信息放在那里。debugfs 文件系统也不打算作为用户空间的稳定 ABI;理论上,对导出到那里的文件没有稳定性约束。然而,现实世界并非总是如此简单 [1];即使是 debugfs 接口,最好也按照需要永久维护的理念进行设计。

Debugfs 通常使用类似这样的命令挂载

mount -t debugfs none /sys/kernel/debug

(或等效的 /etc/fstab 行)。默认情况下,debugfs 根目录仅对 root 用户可访问。要更改对该树的访问权限,可以使用 “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 指示应保存该文件的目录,data 将存储在生成的 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 文件提供一个新名称,可能位于不同的目录中。在调用之前,新名称必须不存在;返回值是带有更新信息的 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 指针,则将删除该目录下的整个层次结构。