Linux内核里的DebugFS

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;

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

时间: 2024-10-30 23:26:19

Linux内核里的DebugFS的相关文章

真正理解红黑树,真正的(Linux内核里大量用到的数据结构,且常被二货问到)

作为一种数据结构,红黑树可谓不算朴素,因为各种宣传让它过于神秘,网上搜罗了一大堆的关于红黑树的文章,不外乎千篇一律,介绍概念,分析性能,贴上代码,然后给上罪恶的一句话,它最坏情况怎么怎么地...              我们想,一棵二叉树怎么就是最坏情况,那就是它退化为一个链表,这样查找就成了遍历.问题是,平衡二叉树怎么会退回链表!它是怎么保持平衡的?能不能简单地阐述?当然可以!一般的讲述红黑树的资料都是直接给出黑节点相同,红节点不连续等来作为一个足够硬但是又不是太硬的约束来保证树的平衡,但事

Linux内核调试的方式以及工具集锦

CSDN GitHub Linux内核调试的方式以及工具集锦 LDD-LinuxDeviceDrivers/study/debug 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作 因本人技术水平和知识面有限, 内容如有纰漏或者需要修正的地方, 欢迎大家指正, 也欢迎大家提供一些其他好的调试工具以供收录, 鄙人在此谢谢啦 "调试难度本来就是写代码的两倍. 因此, 如果你写代码的时候聪明用尽, 根据定义, 你就没有能耐去调试它了.&qu

Linux内核中等待队列的几种用法

Linux内核里的等待队列机制在做驱动开发时用的非常多,多用来实现阻塞式访问,下面简单总结了等待队列的四种用法,希望对读者有所帮助. 1. 睡眠等待某个条件发生(条件为假时睡眠): 睡眠方式:wait_event, wait_event_interruptible 唤醒方式:wake_up (唤醒时要检测条件是否为真,如果还为假则继续睡眠,唤醒前一定要把条件变为真) 2. 手工休眠方式一: 1)建立并初始化一个等待队列项 DEFINE_WAIT(my_wait) <==> wait_queue

Linux 内核综述

一.什么是Linux内核: 内核->操作系统中最重要的部分,内核将在系统引导时被装载进RAM,其中包含了很多关键的例程,以操作系统.内核是OS最为关键的部分,人们常将OS(操作系统)与内核等同. 内核,是一个操作系统的核心.它负责管理系统的进程.内存.设备驱动程序.文件和网络系统,决定着系统的性能和稳定性. 想象一下,拥有了内核的源程序对你来说意味着什么?我们可以了解系统是如何工作的.通过通读源代码,我们就可以了解系统的工作原理,这在Windows下简直是天方夜谭.我们还可以针对自己的情况,量体

Linux内核抢占机制 - 简介

本文首发于 http://oliveryang.net,转载时请包含原文或者作者网站链接. 本文主要围绕 Linux 内核调度器 Preemption 的相关实现进行讨论.其中涉及的一般操作系统和 x86 处理器和硬件概念,可能也适用于其它操作系统. 1. 背景知识 要深入理解 Preemption 必须对操作系统的 Context Switch 做一个全面的梳理.最终可以了解 Preemption 和 Context Switch 概念上的区别与联系. 1.1 Context Switch C

成为 Linux 内核高手的四个方法

(之前我在 CUSEC 网站发表了关于内核并不可怕的一篇文章,本文是后续.) 我曾经问别人如何开始内核编程的学习,他们基本上都说:1. 如果你不需要了解内核是如何为你工作的,你为何要尝试呢?2. 你应该订阅Linux内核邮件列表,然后努力去理解.3. 如果你不去编写针对Linux内核的代码,你就是在浪费时间. 这些对我一点儿帮助都没有.所以我在这里列举了一些可行的方法,他们是有关操作系统和Linux内核是怎样在你的项目里工作的,而且还很有趣.虽然我知道得并不多,但至少比我做这些之前了解了更多.

Linux内核哈希表中的bucket桶

哈希表 哈希表(Hashtable)又称为“散列”,Hashtable是会根据索引键的哈希程序代码组织成的索引键(Key)和值(Value)配对的集合.Hashtable 对象是由包含集合中元素的哈希桶(Bucket)所组成的.而Bucket是Hashtable内元素的虚拟子群组,可以让大部分集合中的搜寻和获取工作更容易.更快速. 哈希函数(Hash Function)为根据索引键来返回数值哈希程序代码的算法.索引键(Key)是被存储对象的某些属性值(Value).当对象加入至 Hashtabl

Linux内核循环链表经典分析和移植

为什么说这个链表做的经典呢,哥哥我从Linux内核里边儿扣出来的,要么怎么说内核不是一般人能写的,这代码太TM优美了! 这里有一篇参考文章:http://isis.poly.edu/kulesh/stuff/src/klist/,下面的分析来自其他人的分析这里做了整理,使得它便于阅读. 在linux内核中,有大量的数据结构需要用到双循环链表,例如进程.文件.模块.页面等.若采用双循环链表的传统实现方式,需要为这些数据结构维护各自的链表,并且为每个链表都要设计插入.删除等操作函数.因为用来维持链表

Linux内核笔记:epoll实现原理

一.说明 针对的内核版本为4.4.10. 本文只是我自己看源码的简单笔记,如果想了解epoll的实现,强烈推荐下面的文章: The Implementation of epoll(1) The Implementation of epoll(2) The Implementation of epoll(3) The Implementation of epoll(4) 二.epoll_create() 系统调用epoll_create()会创建一个epoll实例并返回该实例对应的文件描述符fd.