在Linux内核中,许多驱动程序和子系统都支持一个叫做debugfs的特殊文件系统。Debugfs与其他虚拟文件系统非常相似,比如sysfs和procfs,但它除了提供调试信息外没有任何功能用途,方便内核开发人员将内核信息在user space中可见。这个文件系统中的文件是由内核生成的,其内容会因驱动/子系统的不同而不同。

/proc文件系统只表示一个进程的有关信息;sysfs有严格的一个文件一个值的规则;而debugfs并没有什么规则约束,开发者可以把信息放在想要放的地方。而且debugfs也并不是作为一个稳定的user space的ABI所存在的,并不存在一个文件系统规范。

当编写新的驱动程序时,debugfs比printk语句更受欢迎,因为它更容易启用/禁用,并且提供了一个更标准化的接口。本文档提供了关于使用和实现debugfs文件的基本信息。

先决条件 / Prerequisites

使用以下配置选项构建内核:

CONFIG_DEBUG_FS=y

Mounting debugfs

Debugfs可以像其他虚拟文件系统一样使用mount命令来挂载。

1,创建一个挂载点。默认的是/sys/kernel/debug/,它可能已经存在。

# mkdir /sys/kernel/debug

2,使用mount命令挂载文件系统。

# mount -t debugfs none /sys/kernel/debug

3,Use the cat command to view the contents of the file system:

# cat /sys/kernel/debug/gpio

我的板子,ingenic x2000的输出结果:

gpiochip0: GPIOs 0-31, parent: platform/10010000.pinctrl, GPA:

gpio-1   (                    |pwdn                ) out hi ACTIVE LOW

gpiochip1: GPIOs 32-63, parent: platform/10010000.pinctrl, GPB:

gpio-32  (                    |vcc-en              ) out lo

gpio-36  (                    |i2c-pow             ) out lo

gpio-51  (                    |Trigger             ) in  hi IRQ ACTIVE LOW

gpiochip2: GPIOs 64-95, parent: platform/10010000.pinctrl, GPC:

gpio-64  (                    |pwm0                ) in  lo

gpio-74  (                    |pwm10               ) in  lo

gpio-75  (                    |pwm11               ) in  lo

gpiochip3: GPIOs 96-127, parent: platform/10010000.pinctrl, GPD:

gpio-100 (                    |BT-Reset            ) in  lo IRQ ACTIVE LOW

gpiochip4: GPIOs 128-159, parent: platform/10010000.pinctrl, GPE:

#

Adding debugfs Support to a Driver 

debugfs API在内核源文件fs/debugfs/inode.c和fs/debugfs/file.c中有注释描述。

为一个给定的驱动或子系统创建debugfs文件是相对容易的。它有三个主要步骤:

- 确定目录结构

- 为每个文件创建文件操作函数

- 在debugfs文件系统中注册文件。

* Determine directory structure

* Create file operation functions for each file.

* Register files with debugfs filesystem.

Debugfs Directory Structure

你可以把你的debugfs条目分成多个文件,甚至可以把它们组织成一个分层的目录结构。单个文件可以用于记录寄存器的值,或用于读取缓冲区的内容,或者是你想要的任何内容。最终,试着选择一种对正在调试问题的人最有帮助的结构。

层次结构是由debugfs_create_dir和debugfs_create_file函数决定的,这些函数在文档和内核源代码中会有更详细的描述。

File Operation Functions 

在大多数情况下,你需要定义文件操作的函数:

- open

- release

- llseek

- read

你要为每个文件定义操作。如果通用的文件操作适合你的文件,你也许可以使用。

一旦函数在驱动程序中被定义,它们必须被分配到file_operations结构中相应的函数指针中。每个文件都应该有一个该结构。

参见Linux Device Drivers 3rd的第三章:Linux Device Drivers, 3rd Edition | Timesys LinuxLink

Registering Files

函数 debugfs_create_file 用来在 debugfs 文件系统中注册一个文件。这个函数需要一个文件名、文件权限掩码和file_operations结构(以及其他东西)作为参数。这个函数应该在驱动/子系统与内核初始化时为每个文件调用一次。

要在debugfs文件系统中创建子目录,使用函数debugfs_create_dir。

这些函数在内核源代码中的fs/debugfs目录中都有记录。

Code Samples

在Linux内核中,有非常多的代码实例,使用了debugfs。比如说:

* drivers/gpio/gpiolib.c - GPIOLIB subsystem

* drivers/usb/gadget/atmel_ubsa_udc.c - Atmel USBA Gadget Ethernet Driver

具体操作

内核代码中使用debugfs功能,需要包含头文件:<linux/debugfs.h>。

第一件事,就是要创建一个文件夹,用来存放文件:

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

这个调用,如果成功,将在指定的父目录下创建一个名为name的目录。如果父目录是NULL,目录将在debugfs根目录下创建。如果成功,返回值是一个结构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表示文件的初始化大小,其他的参数和上面一样。

在有些情况下,有些文件的创建不用这么麻烦,提供一些helper函数,来方便处理简单的情况,比如一个文件内容只包含一个整数的话:

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

这些文件支持读取和写入给定的值;如果某个文件不可写,只需修改mode参数的相关bits。这些文件中的数值是十进制的;如果十六进制更合适,可以用以下函数代替:

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的helper函数,包括十进制和十六进制的:

void 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,后面是一个新行符。如果写入文件,它将接受大写或小写的字母(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 "作为返回几行(静态)格式化文本输出的简单方法。这个函数可以用来输出二进制信息,但在linux mainline上似乎没有代码这样用过。注意,所有用debugfs_create_blob()创建的文件都是只读的。

如果你想转储(dump)一个寄存器块(这在开发过程中经常发生,即使很少有这样的代码提交到mainline。Debugfs提供了两个函数:一个是制作一个只包含寄存器的文件,另一个是在另一个序列(sequential)文件的中间插入一个寄存器块:

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中转储(dump)一个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的指针,该目录下面的整个层次结构将被删除。

参考:

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

How To Use debugfs | Timesys LinuxLink

Logo

更多推荐