用户空间与内核空间数据交换的方式(1)------debugfs

来源:互联网 发布:游戏数据爆炸 编辑:程序博客网 时间:2024/05/29 08:32

 内核开发者经常需要向用户空间应用输出一些调试信息,在稳定的系统中可能根本不需要这些调试信息,但是在开发过程中,为了搞清楚内核的行为,调试信息非常必要,printk可能是用的最多的,但它并不是最好的,调试信息只是在开发中用于调试,而printk将一直输出,因此开发完毕后需要清除不必要 的printk语句,另外如果开发者希望用户空间应用能够改变内核行为时,printk就无法实现。因此,需要一种新的机制,那只有在需要的时候使用,它在需要时通过在一个虚拟文件系统中创建一个或多个文件来向用户空间应用提供调试信息。
有几种方式可以实现上述要求:
 (1)使用procfs,在/proc创建文件输出调试信息,但是procfs对于大于一个内存页(对于x86是4K)的输出比较麻烦,而且速度慢,有时回出现一些意想不到的问题。
 (2)使用sysfs(2.6内核引入的新的虚拟文件系统),在很多情况下,调试信息可以存放在那里,但是sysfs主要用于系统管理,它希望每一个文件对应内核的一个变量,如果使用它输出复杂的数据结构或调试信息是非常困难的。
 (3)使用libfs创建一个新的文件系统,该方法极其灵活,开发者可以为新文件系统设置一些规则,使用libfs使得创建新文件系统更加简单,但是仍然超出了一个开发者的想象。
 (4)为了使得开发者更加容易使用这样的机制,Greg Kroah-Hartman开发了debugfs(在2.6.11中第一次引入),它是一个虚拟文件系统,专门用于输出调试信息,该文件系统非常小,很容易使用,可以在配置内核时选择是否构件到内核中,在不选择它的情况下,使用它提供的API的内核部分不需要做任何改动。
使用debugfs的开发者首先需要在文件系统中创建一个目录,下面函数用于在debugfs文件系统下创建一个目录:

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

参数name是要创建的目录名,参数parent指定创建目录的父目录的dentry,如果为NULL,目录将创建在debugfs文件系统的根目录下。如果返回为-ENODEV,表示内核没有把debugfs编译到其中,如果返回为NULL,表示其他类型的创建失败,如果创建目录成功,返回指向该 目录对应的dentry条目的指针。
下面函数用于在debugfs文件系统中创建一个文件: 

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为实现在该文件上进行文件操作的fiel_operations结构指针,在很多情况下,由seq_file提供的文件操作实现就足够了,因此使用debugfs很容易,当然,在一些情况下,开发者可能仅需要使用用户应用可以控制的变量来调试,debugfs也提供了4个这样的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);  
复制代码

 参数name和mode指定文件名和访问许可,参数value为需要让用户应用控制的内核变量指针。
当内核模块卸载时,Debugfs并不会自动清除该模块创建的目录或文件,因此对于创建的每一个文件或目录,开发者必须调用下面函数清除:
 

      void debugfs_remove(struct dentry *dentry);

参数dentry为上面创建文件和目录的函数返回的dentry指针。
在下面给出了一个使用debufs的示例模块debugfs_exam.c,为了保证该模块正确运行,必须让内核支持debugfs, debugfs是一个调试功能,因此它位于主菜单Kernel hacking,并且必须选择Kernel debugging选项才能选择,它的选项名称为Debug Filesystem。为了在用户态使用debugfs,用户必须mount它,下面是在作者系统上的使用输出:

复制代码
  $ mkdir -p  /debugfs  $ mount -t debugfs debugfs /debugfs  $ insmod  ./debugfs_exam.ko  $ ls /debugfs  debugfs-exam  $ ls /debugfs/debugfs-exam  u8_var         u16_var        u32_var        bool_var  $ cd /debugfs/debugfs-exam  $ cat u8_var  0  $ echo 200 > u8_var  $ cat u8_var  200  $ cat bool_var  N  $ echo 1 > bool_var  $ cat bool_var  Y
复制代码
复制代码
//kernel module: debugfs_exam.c#include <linux/config.h>#include <linux/module.h>#include <linux/debugfs.h>#include <linux/types.h>/*dentry:目录项,是Linux文件系统中某个索引节点(inode)的链接。这个索引节点可以是文件,也可以是目录。Linux用数据结构dentry来描述fs中和某个文件索引节点相链接的一个目录项(能是文件,也能是目录)。  (1)未使用(unused)状态:该dentry对象的引用计数d_count的值为0,但其d_inode指针仍然指向相关
的的索引节点。该目录项仍然包含有效的信息,只是当前没有人引用他。这种dentry对象在回收内存时可能会被释放。  (2)正在使用(inuse)状态:处于该状态下的dentry对象的引用计数d_count大于0,且其d_inode指向相关
的inode对象。这种dentry对象不能被释放。  (3)负(negative)状态:和目录项相关的inode对象不复存在(相应的磁盘索引节点可能已被删除),dentry
对象的d_inode指针为NULL。但这种dentry对象仍然保存在dcache中,以便后续对同一文件名的查找能够快速完成。
这种dentry对象在回收内存时将首先被释放。*/static struct dentry *root_entry, *u8_entry, *u16_entry, *u32_entry, *bool_entry;static u8 var8;static u16 var16;static u32 var32;static u32 varbool;static int __init exam_debugfs_init(void){ root_entry = debugfs_create_dir("debugfs-exam", NULL); if (!root_entry) { printk("Fail to create proc dir: debugfs-exam\n"); return 1; } u8_entry = debugfs_create_u8("u8-var", 0644, root_entry, &var8); u16_entry = debugfs_create_u16("u16-var", 0644, root_entry, &var16); u32_entry = debugfs_create_u32("u32-var", 0644, root_entry, &var32); bool_entry = debugfs_create_bool("bool-var", 0644, root_entry, &varbool); return 0;}static void __exit exam_debugfs_exit(void){ debugfs_remove(u8_entry); debugfs_remove(u16_entry); debugfs_remove(u32_entry); debugfs_remove(bool_entry); debugfs_remove(root_entry);}module_init(exam_debugfs_init);module_exit(exam_debugfs_exit);MODULE_LICENSE("GPL");
复制代码


Linux内核里的DebugFS

2011-01-17 23:52 by wwang, 5279 阅读, 5 评论, 收藏, 编辑

DebugFS,顾名思义,是一种用于内核调试的虚拟文件系统,内核开发者通过debugfs和用户空间交换数据。类似的虚拟文件系统还有procfs和sysfs等,这几种虚拟文件系统都并不实际存储在硬盘上,而是Linux内核运行起来后才建立起来。

通常情况下,最常用的内核调试手段是printk。但printk并不是所有情况都好用,比如打印的数据可能过多,我们真正关心的数据在大量的输出里不是那么一目了然;或者我们在调试时可能需要修改某些内核变量,这种情况下printk就无能为力,而如果为了修改某个值重新编译内核或者驱动又过于低效,此时就需要一个临时的文件系统可以把我们需要关心的数据映射到用户空间。在过去,procfs可以实现这个目的,到了2.6时代,新引入的sysfs也同样可以实现,但不论是procfs或是sysfs,用它们来实现某些debug的需求,似乎偏离了它们创建的本意。比如procfs,其目的是反映进程的状态信息;而sysfs主要用于Linux设备模型。不论是procfs或是sysfs的接口应该保持相对稳定,因为用户态程序很可能会依赖它们。当然,如果我们只是临时借用procfs或者sysfs来作debug之用,在代码发布之前将相关调试代码删除也无不可。但如果相关的调试借口要在相当长的一段时间内存在于内核之中,就不太适合放在procfs和sysfs里了。故此,debugfs应运而生。

默认情况下,debugfs会被挂载在目录/sys/kernel/debug之下,如果您的发行版里没有自动挂载,可以用如下命令手动完成:

?
# mount -t debugfs none /your/debugfs/dir

Linux内核为debugfs提供了非常简洁的API,本文接下来将以一个实作为例来介绍,sample code可以从这里下载。

这个实作会在debugfs中建立如下的目录结构:

其中,a对应模块中的一个u8类型的变量,b和subdir下面的c都是对应模块里的一个字符数组,只是它们的实现方式不同。

在module_init里,我们首先要建立根目录mydebug:

?
1
my_debugfs_root = debugfs_create_dir("mydebug", NULL);

第一个参数是目录的名称,第二个参数用来指定这个目录的上级目录,如果是NULL,则表示是放在debugfs的根目录里。

子目录也是用debugfs_create_dir来实现:

?
1
sub_dir = debugfs_create_dir("subdir", my_debugfs_root);

建立文件a的代码非常简单:

?
1
debugfs_create_u8("a", 0644, my_debugfs_root, &a);

这表示文件名为“a”,文件属性是0644,父目录是上面建立的“mydebug”,对应的变量是模块中的a。

Linux内核还提供了其他一些创建debugfs文件的API,请参考本文的附录。

b是一个32-bytes的字符数组,在debugfs里,数组可以用blob wrapper来实现。

?
1
2
3
4
5
6
char hello[32] = "Hello world!\n";
struct debugfs_blob_wrapper b;
 
b.data = (void *)hello;
b.size = strlen(hello) + 1;
debugfs_create_blob("b", 0644, my_debugfs_root, &b);

这里需要注意的是,blob wrapper定义的数据只能是只读的。在本例中,虽然我们把文件b的权限设定为0644,但实际这个文件还是只读的,如果试图改写这个文件,系统将提示出错。

如果需要对内核数组进行写的动作,blob wrapper就无法满足要求,我们只能通过自己定义文件操作来实现。在这个实作里,可以参考文件c的实现。c和b在模块里对应着同一块字符数组,不同的是,b是只读的,而c通过自定义的文件操作同时实现了读和写。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static int c_open(struct inode *inode, struct file *filp)
{
    filp->private_data = inode->i_private;
    return 0;
}
 
static ssize_t c_read(struct file *filp, char __user *buffer,
        size_t count, loff_t *ppos)
{
    if (*ppos >= 32)
        return 0;
    if (*ppos + count > 32)
        count = 32 - *ppos;
 
    if (copy_to_user(buffer, hello + *ppos, count))
        return -EFAULT;
 
    *ppos += count;
 
    return count;
}
 
static ssize_t c_write(struct file *filp, const char __user *buffer,
        size_t count, loff_t *ppos)
{
    if (*ppos >= 32)
        return 0;
    if (*ppos + count > 32)
        count = 32 - *ppos;
 
    if (copy_from_user(hello + *ppos, buffer, count))
        return -EFAULT;
 
    *ppos += count;
 
    return count;
}
 
struct file_operations c_fops = {
    .owner = THIS_MODULE,
    .open = c_open,
    .read = c_read,
    .write = c_write,
};
 
 
debugfs_create_file("c", 0644, sub_dir, NULL, &c_fops);

注:代码里,c_open其实并没有任何用处,因为c_read和c_write直接引用了全局变量hello。这里,我们也可以换一种写法,在read/write函数里用filp->private_data来引用字符数组hello。

到这里,三个文件和子目录已经创建完毕。在module_exit中,我们要记得释放创建的数据。

?
1
debugfs_remove_recursive(my_debugfs_root);

debugfs_remove_recursive可以帮我们逐步移除每个分配的dentry,如果您想一个一个手动的移除,也可以直接调用debugfs_remove。

 

附录:

创建和撤销目录及文件

?
1
2
3
4
5
6
struct dentry *debugfs_create_dir(const char *name,struct dentry *parent);
struct dentry *debugfs_create_file(const char *name, mode_t mode,
        struct dentry *parent, void *data,
        const struct file_operations *fops);
void debugfs_remove(struct dentry *dentry);
void debugfs_remove_recursive(struct dentry *dentry);

创建单值文件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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_u64(const char *name, mode_t mode,
        struct dentry *parent, u64 *value);
 
struct dentry *debugfs_create_x8(const char *name, mode_t mode,
        struct dentry *parent, u8 *value);
struct dentry *debugfs_create_x16(const char *name, mode_t mode,
        struct dentry *parent, u16 *value);
struct dentry *debugfs_create_x32(const char *name, mode_t mode,
        struct dentry *parent, u32 *value);
 
struct dentry *debugfs_create_size_t(const char *name, mode_t mode,
        struct dentry *parent, size_t *value);
struct dentry *debugfs_create_bool(const char *name, mode_t mode,
        struct dentry *parent, u32 *value);

其中,后缀为x8、x16、x32的这三个函数是指debugfs中的数据用十六进制表示。

创建BLOB文件

?
1
2
3
4
5
6
7
struct debugfs_blob_wrapper {
    void *data;
    unsignedlong size;
};
 
struct dentry *debugfs_create_blob(const char *name, mode_t mode,
         struct dentry *parent, struct debugfs_blob_wrapper *blob);

其它

?
1
2
3
4
5
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);

 


原创粉丝点击