Linux - 如何使用debugfs
这里,name是要创建的文件的名称,mode描述了该文件应该具有的访问权限,parent表示应该保存该文件的目录,data将被保存在产生的inode结构的i_private字段中,fops是一组实现该文件行为的文件操作函数。其他操作可以根据需要加入。同样,返回值将是一个指向所创建文件的dentry指针,出错时为ERR_PTR(-ERROR),如果缺少debugfs支持,则为ERR_PTR(-ENO
在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的指针,该目录下面的整个层次结构将被删除。
参考:
更多推荐
所有评论(0)