Linux Kernel —— DebugFS介绍

本文介绍了DebugFS,一种用于内核调试的虚拟文件系统,它允许内核开发者通过debugfs和用户空间交换数据。文章详细解释了如何在DebugFS中创建目录、文件以及子目录,以及使用不同的API来实现读写操作。此外,还提供了创建和撤销目录及文件的API示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);
<think>好的,用户之前问了Linux内核的roadmap,现在又问如何学习内核,显然是想深入这个领域。首先,我需要考虑用户可能的背景。可能是学生或者刚入行的开发者,对内核有兴趣但不知如何入手。 用户之前的问题关于roadmap,说明他可能已经有一定的了解,想进一步学习。但学习内核的门槛较高,需要分步骤引导。我应该先强调基础的重要性,比如操作系统原理和C语言,因为内核开发离不开这些。 接下来,用户可能没有明确说明自己的学习目标,是出于兴趣还是职业需求。所以回答中需要涵盖不同的学习路径,比如开发、调试、源码阅读等,同时推荐实践方法,比如搭建实验环境,动手写代码或模块。 还要考虑用户的资源获取途径,推荐书籍和在线资料,比如经典的《Linux内核设计与实现》和在线文档。同时,社区资源也很重要,比如邮件列表和论坛,用户可能需要知道如何参与社区讨论。 另外,用户可能对调试和工具不熟悉,需要介绍常用的工具如QEMU、GDB,以及调试技巧。强调实践的重要性,通过实际项目来巩固知识,比如参与开源项目或自己写驱动。 最后,鼓励用户保持耐心,持续学习,因为内核学习是一个长期过程。可能还需要提醒他们关注最新动态,比如通过LWN.net或者内核邮件列表,保持知识的更新。 需要避免过于技术性的术语,用易懂的语言分步骤说明,结构清晰,让用户有明确的路线图可循。同时结合之前的回答,保持一致性,比如提到社区协作和版本管理,让用户感觉到连贯性。</think>学习 Linux 内核需要系统性方法,以下是分阶段学习路径建议: 一、基础准备阶段 1. **必备知识储备** - 操作系统原理:重点掌握进程管理、内存管理、文件系统、设备驱动等核心概念 - C语言进阶:深入理解指针操作、结构体对齐、内存屏障等特性(推荐《C和指针》) - 计算机体系结构:了解CPU工作模式(实模式/保护模式)、中断机制、DMA原理 2. **环境搭建** ```bash # 推荐开发环境配置 $ sudo apt-get install build-essential libncurses-dev flex bison libssl-dev $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git $ make menuconfig # 配置精简内核(建议初次编译选择X86架构) ``` 二、渐进式学习路径 1. **入门实践** - 通过/proc文件系统实现简单信息查询(如《Linux设备驱动程序》中的示例) - 编写基础字符设备驱动(实现open/read/write等基本操作) - 使用ftrace跟踪系统调用路径:`echo function > /sys/kernel/debug/tracing/current_tracer` 2. **核心子系统学习顺序建议** ① 进程调度(kernel/sched/) ② 内存管理(mm/) ③ 文件系统(fs/) ④ 网络协议栈(net/) ⑤ 设备模型(drivers/base/) 三、高效学习工具 1. **调试分析工具链** ```bash # QEMU+GDB调试内核 $ qemu-system-x86_64 -kernel bzImage -append "nokaslr" -s -S $ gdb vmlinux -ex "target remote :1234" ``` - 性能分析:perf(`perf record -g --`)、ebpf - 动态追踪:systemtap、trace-cmd 2. **代码阅读技巧** - 使用LXR交叉索引工具:https://elixir.bootlin.com/ - 关注核心数据结构: ```c struct task_struct { // 进程描述符 volatile long state; struct mm_struct *mm; /* ... */ }; ``` 四、学习资源推荐 1. **经典书籍** - 《Linux内核设计与实现》(Robert Love著)—— 最佳入门 - 《深入理解Linux内核》—— 核心机制详解 - 《Linux设备驱动程序》—— 驱动开发必读 2. **在线资源** - 官方文档:Documentation/ 目录(内核源码中) - KernelNewbies:https://kernelnewbies.org/ - LWN内核专栏:https://lwn.net/Kernel/ 五、实践建议 1. **从真实问题出发** - 复现/修复内核bug(可从LKML找简单问题) - 参与Eudyptula挑战(小型内核开发任务) - 使用syzkaller进行模糊测试 2. **进阶方向选择** - 驱动开发:学习设备树(DT)、PCIe协议、GPIO子系统 - 性能优化:掌握cgroup、numa架构、锁优化 - 安全研究:学习SELinux、漏洞利用防御机制 学习曲线提示: 1. 建议从最新LTS内核版本(如6.6.x)开始研究 2. 每天精读100行核心代码胜过泛读千行 3. 结合git历史记录(`git blame`)理解代码演进 4. 参与内核邮件列表(LKML)讨论,注意遵守社区规范 关键突破点:通过`printk`调试理解代码执行流,使用GDB单步跟踪关键函数,分析`/proc/<pid>/maps`等调试接口验证理论认知。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值