1、概述
内核开发者经常需要向用户空间应用输出一些调试信息,在稳定的系统中可能根本不需要这些调试信息,但是在开发过程中,为了搞清楚内核的行为,调试信息非常必要,printk可能是用的最多的,但它并不是最好的,调试信息只是在开发中用于调试,而printk将一直输出,因此开发完毕后需要清除不必要的printk语句,另外如果开发者希望用户空间应用能够改变内核行为时,printk就无法实现。因此,需要一种新的机制,那只有在需要的时候使用,它在需要时通过在一个虚拟文件系统中创建一个或多个文件来向用户空间应用提供调试信息。
有几种方式可以实现上述要求:
使用procfs,在/proc创建文件输出调试信息,但是procfs对于大于一个内存页(对于x86是4K)的输出比较麻烦,而且速度慢,有时回出现一些意想不到的问题。
使用sysfs(2.6内核引入的新的虚拟文件系统),在很多情况下,调试信息可以存放在那里,但是sysfs主要用于系统管理,它希望每一个文件对应内核的一个变量,如果使用它输出复杂的数据结构或调试信息是非常困难的。
使用libfs创建一个新的文件系统,该方法极其灵活,开发者可以为新文件系统设置一些规则,使用libfs使得创建新文件系统更加简单,但是仍然超出了一个开发者的想象。
为了使得开发者更加容易使用这样的机制,Greg Kroah-Hartman开发了debugfs(在2.6.11中第一次引入),它是一个虚拟文件系统,专门用于输出调试信息,该文件系统非常小,很容易使用,可以在配置内核时选择是否构件到内核中,在不选择它的情况下,使用它提供的API的内核部分不需要做任何改动。
使能debugfs功能,需要在.config中配置CONFIG_DEBUG_FS
默认情况下,debugfs会被挂载在目录/sys/kernel/debug之下,如果您的发行版里没有自动挂载,可以用如下命令手动完成:
# mount -t debugfs none /your/debugfs/dir
2、debugfs API
debugfs的API很简单,我们来看一下
2.1、创建目录
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);
参数:name:创建的目录名字
parent:父目录,为NULL表示在/sys/kernel/debug下
返回值:创建成功 返回指向该目录对应的dentry条目的指针,NULL表示创建失败。
2.2、创建文件
struct dentry *debugfs_create_file(const char *name, mode_t mode,
struct dentry *parent, void *data,
struct file_operations *fops);
参数:name,创建的文件的名称
mode,访问许可,如可读可写
parent,父目录.
data:传入的私有数据
fops:操作函数
返回值:创建成功返回该文件对应的dentry条目的指针,NULL表示创建失败
2.3、创建一个变量debug信息
上面创建文件的API主要用于debug大量信息的时候,如果只是debug某个变量,可以用如下的API
struct dentry *debugfs_create_u8(const char *name, mode_t mode,
struct dentry *parent, u8 *value);
struct dentry *debugfs_create_u16(const char *name, mode_t mode,
struct dentry *parent, u16 *value);
struct dentry *debugfs_create_u32(const char *name, mode_t mode,
struct dentry *parent, u32 *value);
struct dentry *debugfs_create_bool(const char *name, mode_t mode,
struct dentry *parent, u32 *value);
2.4、清除debugfs模块
当内核模块卸载时,Debugfs并不会自动清除该模块创建的目录或文件,因此对于创建的每一个文件或目录,开发者必须调用下面函数清除:
void debugfs_remove(struct dentry *dentry);
3、示例
对于debug一个变量的信息,比较简单,这里就不介绍了,我们主要来看看有大量debug信息的时候,创建一个debugfs文件的方式。
struct dentry *debugfs_create_file(const char *name, mode_t mode,
struct dentry *parent, void *data,
struct file_operations *fops);
最后一个参数是文件操作集合,在debugfs中我们使用seq_file提供的文件操作实现就足够了,seq_file的只是不做介绍了,我们只看如何使用即可。
struct file_operations exam_single_seq_file_operations = {
.open = exam_single_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
上面的方法集合中,我们只用完成open的即可,其余的是固定的。
int exam_show(struct seq_file *p, void *v)
{
…
}
int exam_single_open(struct inode *inode, struct file *file)
{
return(single_open(file, exam_show, NULL));
}
exam_show就是我们要打印的信息。
为了使用方便,我们可以对这些函数封装一下,
struct exa_debugfs_info {
const char *name;
int (*show)(struct seq_file*, void*); /** show callback */
};
上面这个结构体表示debugfs文件的名称和show函数。
struct exa_info_node {
struct list_head list;
struct exa_dev *exa_dev;
const struct exa_debugfs_info *info_ent;
struct dentry *dent;
};
上面这个结构体表示一个节点的信息,
链表,用于遍历一个目录下的dubugfs文件
设备,驱动设备
设备debugfs信息,
dentry
int exa_debugfs_create_files(const struct exa_debugfs_info *files, int count,
struct dentry *root, struct exa_dev *dev)
{
struct dentry *ent;
struct exa_info_node *tmp;
int i, ret;
for (i = 0; i < count; i++) {
tmp = kmalloc(sizeof(struct exa_info_node), GFP_KERNEL);
if (tmp == NULL) {
ret = -1;
goto fail;
}
ent = debugfs_create_file(files[i].name, S_IFREG | S_IRUGO,
root, tmp, &cnn_debugfs_fops);
if (!ent) {
pr_err("Cannot create /sys/kernel/debug/dri/%pd/%s\n",
root, files[i].name);
kfree(tmp);
ret = -1;
goto fail;
}
tmp->exa_dev = dev;
tmp->dent = ent;
tmp->info_ent = &files[i];
mutex_lock(&dev->debugfs_lock);
list_add(&tmp->list, &dev->debugfs_list);
mutex_unlock(&dev->debugfs_lock);
}
return 0;
fail:
exa_debugfs_remove_files(files, count, dev);
return ret;
}
在上面的代码中,通过count,循环添加debugfs文件。
我们还需要创建目录,可以将这个步骤和上面的创建文件合并成一个
int exa_debugfs_init(struct exa_dev *dev, struct dentry *root)
{
char name[8];
int ret;
INIT_LIST_HEAD(&dev->debugfs_list);
mutex_init(&dev->debugfs_lock);
snprintf(name, 5, "%s", g_chrdev_name);
dev->debugfs_root = debugfs_create_dir(name, root);
if (!dev->debugfs_root) {
pr_err("Cannot create /sys/kernel/debug/%s\n", name);
return -1;
}
ret = exa_debugfs_create_files(exa_debugfs_list, EXA_DEBUGFS_ENTRIES,
dev->debugfs_root, dev);
if (ret) {
debugfs_remove(dev->debugfs_root);
dev->debugfs_root = NULL;
pr_err("Failed to create core drm debugfs files\n");
return ret;
}
return 0;
}
int exa_debugfs_remove_files(const struct exa_debugfs_info *files, int count,
struct exa_dev *dev)
{
struct list_head *pos, *q;
struct exa_info_node *tmp;
int i;
mutex_lock(&dev->debugfs_lock);
for (i = 0; i < count; i++) {
list_for_each_safe(pos, q, &dev->debugfs_list) {
tmp = list_entry(pos, struct exa_info_node, list);
if (tmp->info_ent == &files[i]) {
debugfs_remove(tmp->dent);
list_del(pos);
kfree(tmp);
}
}
}
mutex_unlock(&dev->debugfs_lock);
return 0;
}
在remove debugfs模块时,遍历整个链表,依次remove
使用过程如下
int debug1_show(struct seq_file *m, void *data)
{
....
}
int debug2_show(struct seq_file *m, void *data)
{
....
}
int debug3_show(struct seq_file *m, void *data)
{
....
}
static struct exa_debugfs_info exa_debugfs_list[] = {
{"debug1", debug1_show},
{"debug2", debug2_show},
{"debug3", debug3_show},
};
#define EXA_DEBUGFS_ENTRIES ARRAY_SIZE(exa_debugfs_list)
int exa_debugfs_cleanup(struct exa_dev *dev)
{
if (!dev->debugfs_root)
return 0;
exa_debugfs_remove_files(exa_debugfs_list, EXA_DEBUGFS_ENTRIES, dev);
debugfs_remove_recursive(dev->debugfs_root);
dev->debugfs_root = NULL;
return 0;
}
static int __init exa_init(void)
{
.....
exa_debugfs_init();
}
static int __init exa_exit(void)
{
.....
exa_debugfs_cleanup();
}