LWN:优化kernel内联函数调试信息!

关注了就能看到更多这么棒的文章哦~

Better debugging information for inlined kernel functions

By Daroc Alden
April 30, 2025
LSFMM+BPF
Gemini-1.5-flash translation
https://lwn.net/Articles/1018475/

现代编译器会进行大量优化,这会使调试复杂化。Song Liu 和 Thierry Treyer 在 2025 年 Linux Storage, Filesystem, Memory-Management, and BPF Summit 会议上谈到了 BPF 类型格式 (BPF Type Format, BTF) 调试信息的一项潜在改进,这可以部分解决该问题。他们希望将选择性内联函数 (selectively inlined functions) 的信息添加到 BTF 中,以便更好地支持跟踪工具 (tracing tools)。Treyer 远程参与了会议。

最常见的编译器优化之一是内联 (inlining),它将一个函数的代码直接嵌入到其调用者中,从而避免函数调用的开销,并可能暴露其他隐藏的优化机会。然而,现代编译器并非总是内联对特定函数的每个调用,即使这些调用能从内联中受益。编译器使用启发式算法 (heuristics) 来决定哪些调用点 (call sites) 会从内联中受益。这意味着程序员很容易遇到这样的情况:函数仍然出现在二进制文件 (binary) 的符号表 (symbol table) 中(因为有些调用没有被内联),但跟踪该函数却不会显示对它的调用(因为热点调用 (hot calls) 被内联了,因此该函数的符号不再指向它们)。

Liu 说,这是一个问题。这不仅因为它使调试更加困难,还因为它促使开发者将重要的内核函数标记为不可内联 (not inlinable),以便他们能够依赖跟踪这些函数。从技术上讲,可以通过使用 DWARF 调试信息 (DWARF debugging information) 找到内联位置来跟踪选择性内联函数,一些跟踪工具会自动完成这一点。然而,DWARF 是一种庞大、复杂的格式,这使得将它用于此目的时速度较慢。其他方法,例如 tracepoint 和 Linux 安全模块 hook (Linux security module hook),也适用于选择性内联函数。然而,Liu 认为这些方法并不能完全替代常规跟踪,因为在调试内核问题时,在实际处理问题之前,通常不清楚需要跟踪哪些函数。

Liu 概述了两种改善这种情况的不同方案:一种是在内核的 BTF 中仅标记选择性内联函数,另一种是包含它们被内联位置的信息。第一种方案的好处在于其简洁性;可以轻松添加一个额外的函数属性,并将 pahole 等工具 转换为处理它。Liu 说,选择性内联函数之所以成为问题,主要是因为它们造成的困惑。仅仅能够警告开发者正在发生什么情况就有所帮助。

第二个想法将使其他工具更容易实现与 perf probe 类似的功能:在函数被内联的每个位置以及其非内联位置设置断点 (breakpoint)。=perf= 通过解析 DWARF 来实现这一点;而 Liu 的改动将允许其他工具使用 BTF 实现同样的目的。

然而,跟踪一个内联函数并非像在正确位置放置断点那么简单。当函数被内联时,参数 (argument) 和返回值 (return value) 可能由于其他优化而消失。BTF 需要一种方法来指示如何转换调用点 (callsite) 的机器状态 (machine state) 以恢复函数的参数。

Liu 和 Treyer 分析了内核现有的调试信息,以确定这是否可行。在 150,000 个不同的调用点上,内联函数的 228,000 个参数中,大约 83% 的位置可以表示为寄存器 (register) 的偏移量 (offset),通常是因为该参数存在于调用函数的栈 (stack) 上,或者作为指针 (pointer) 传递。Liu 警告说,这项分析不包括少数最常被内联的函数,因为它们会不成比例地扭曲数据。

一位听众询问这个数字是 per-argument 计算的还是 per-function 计算的。Liu 澄清说,这是 per-argument 计算的。该听众接着询问,有多少比例的函数的参数只能以这种方式表示。Liu 展示了一张幻灯片,表明他查看的大约一半的函数都可以使用这种方案获取所有参数。

知道这是可行的后,Liu 提出了一个关于如何在 BTF 中编码内联函数参数信息的方案:每个参数将由一个高度受限的虚拟机 (virtual machine) 中的一系列操作符 (operator) 描述。支持的操作包括加载常量 (constant)、解引用寄存器 (dereferencing registers)、应用偏移量等。因此,例如,一个存储在由寄存器指向的结构体 (structure) 成员中的参数,将表示为类似 "加载寄存器,添加偏移量,解引用,结束" 的序列。使用 Liu 和 Treyer 的原型实现 (prototype implementation),这将为内核的 BTF 增加约 10MB 的大小,不过这个数字是在进行去重 (deduplication) 之前计算的,去重可能会大幅降低大小。

最后一个问题是,BTF 只存在于未被完全内联的函数。在每个调用点都被内联的函数,比选择性内联函数造成的困惑要少,但支持它们会相当简单。添加这些函数将为内核的 BTF 增加额外的 32,000 个函数。

听众对编码参数信息的设计提出了许多问题,主要集中在如何简化它并移除冗余信息 (redundant information)。Alexei Starovoitov 说,他认为这种方法似乎 ""受到了 DWARF 状态机 (DWARF state machine) 的启发"",而且他们有机会采用更简单的方法。Liu 同意该方案还有些粗糙,并欢迎改进建议。

Starovoitov 还指出,Liu 提出的编码对于 16 字节的值无法正常工作,而内核中使用了相当多的此类值。Andrii Nakryiko 询问问题是否在于这些值有时存储在一对寄存器中,而不是内存 (memory) 中。Starovoitov 同意有时是这样,但概述了小型结构体可能遇到的其他一些边缘情况 (edge-cases)。Liu 同意研究 16 字节值的编码问题。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

<think>好的,我现在需要帮助用户了解Linux内核驱动的学习路径。用户明确提到了学习路线、图书、资源和教程。首先,我得先整理一下已有的信息,结合提供的三个引用内容,这些引用提到了学习Linux内核的书籍、内核与驱动程序的关系,以及内核开发的特点。 首先,引用[1]推荐了五本书和学习视频教程,这可能对用户有帮助。引用[2]讨论了内核与驱动的关系,区分了用户态和内核态,以及设备驱动开发的思路。引用[3]提到了编译内核和内核开发的特点,并推荐了一些图书链接。这些信息可以作为回答的基础。 接下来,我需要构建一个结构清晰的学习路线。通常,学习内核驱动开发需要先掌握操作系统原理,特别是Linux内核的基础知识。然后,深入理解设备驱动的工作原理,包括字符设备、块设备、网络设备等。之后是实践,比如编写简单的驱动模块,学习内核的编译和调试方法。最后,可能需要进一步优化和参与实际项目。 根据引用内容,用户可能需要先学习操作系统理论,然后结合内核源码分析。引用[1]中的课程可能适合实践部分。引用[2]提到的用户态和内核态的区别,以及驱动开发的具体思路,应该在学习路径中强调。引用[3]中的编译内核和开发特点,也是关键步骤。 关于推荐资源,引用中的书籍如《Linux设备驱动程序》和《深入理解Linux内核》是经典,必须包含在内。视频教程如腾讯课堂的课程能提供实践指导。此外,官方文档和社区资源也很重要,比如kernel.org和LWN.net。 需要注意用户可能缺乏基础,所以学习路线需要分阶段,从基础到进阶,逐步深入。同时,要强调理论与实践结合,特别是通过编写和调试驱动来巩固知识。另外,要指出内核开发与应用程序开发的不同,比如引用[2]提到的内核开发没有标准库,需要处理并发和硬件差异。 最后,生成的相关问题需要围绕用户可能遇到的下一步疑问,比如如何开始实际项目、调试技巧、内核模块机制等。确保问题覆盖用户可能感兴趣的其他方面。</think>### Linux内核驱动开发学习路径分步指南 #### 一、前置知识准备 1. **操作系统原理** - 必须掌握进程调度/内存管理/文件系统/中断机制等核心概念 - 推荐学习《现代操作系统》(Andrew S. Tanenbaum著) 2. **C语言深度掌握** - 重点:指针操作/结构体嵌套/内存对齐/内联汇编 - 需要理解`volatile`关键字的作用:$volatile\ int *p$防止编译器优化[^2] 3. **Linux系统编程** - 掌握系统调用接口:`open/read/ioctl`等函数使用 - 推荐实践:通过$strace$工具跟踪系统调用 #### 二、内核基础专项训练 1. **内核源码结构解析** ```bash linux-5.x/ ├── arch/ # 体系架构相关代码 ├── drivers/ # 设备驱动代码(重点区域) ├── fs/ # 文件系统实现 └── include/ # 内核头文件 ``` 建议从$drivers/char$目录开始研究简单字符设备驱动[^3] 2. **内核构建与调试** - 编译配置:`make menuconfig`图形化配置界面 - QEMU调试方案: ```bash qemu-system-x86_64 -kernel arch/x86/boot/bzImage -s -S gdb vmlinux ``` #### 三、驱动开发核心技术栈 1. **模块化开发基础** ```c #include <linux/init.h> #include <linux/module.h> static int __init demo_init(void) { printk(KERN_INFO "Driver loaded\n"); return 0; } module_init(demo_init); MODULE_LICENSE("GPL"); ``` 编译使用$make\ -C\ /lib/modules/$(uname -r)/build\ M=$(pwd)$ 2. **设备类型精讲** - **字符设备**:实现`file_operations`结构体 - **Platform设备**:分离设备信息与驱动代码 - **中断处理**:使用`request_irq()`注册中断服务程序 #### 四、学习资源矩阵 | 资源类型 | 推荐内容 | 实践指数 | |----------|----------|----------| | 经典书籍 | 《Linux设备驱动程序》 | ★★★★☆ | | 视频课程 | 腾讯课堂内核专题课[^1] | ★★★★☆ | | 社区资源 | [elixir.bootlin.com](https://elixir.bootlin.com)在线源码 | ★★★★★ | | 实验平台 | Raspberry Pi + LED模块 | ★★★★☆ | #### 五、能力提升路线图 1. 第一阶段(1-2月):完成5个基础字符驱动开发 2. 第二阶段(3-4月):实现USB/PCIe设备驱动 3. 第三阶段(5-6月):参与主线内核驱动维护
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值