LWN:GDB 与 io_uring!

当使用GDB调试使用io_uring的程序时,由于内核线程的变化导致调试器陷入死循环。为解决此问题,开发者提出隐藏io_uring线程,但这一方案影响了诊断工具的使用。最终,通过允许io_uring线程接收信号,使其行为类似于普通线程,问题得到解决,该方案在Linux 5.12-rc5中被合并。

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

GDB and io_uring

By Jake Edge
March 31, 2021
DeepL assisted translation
https://lwn.net/Articles/851076/

在用 GDB 来调试那些使用了 io_uring 的程序时,出现了一个问题,从而引出了后续的一系列可能的解决方案,并且最终有一个方案被合并到 Linux 5.12-rc5 中。这个问题来源于 5.12 合并窗口中改变了 io_uring 线程的创建方式,它们跟使用 io_uring 的进程关联了起来。这些 "I/O 线程" 在内核中得到特殊的处理,这就导致了 GDB 这边出现问题(很可能其他使用 ptrace()的程序也会出错)。解决方法就是按照跟其他线程一样的方式来对待它们,因为事实已经证明,对它们进行特殊化处理所引起的问题要比所解决的问题更加多。

Stefan Metzmacher 在 3 月 20 日向 io-uring 邮件列表报告了这个问题。他希望将 GDB 连到(attach to)一个使用了 io_uring 的程序进程,但 GDB 调试器 "进入了一个死循环,因为它无法 attach 到 io_threads"。PF_IO_WORKER 线程是 io_uring 中用在可能会发生阻塞操作的情况下。他在 bug 报告之后提出了两个 patch set,来通过不同的方式隐藏这些线程。之所以要隐藏掉它们,是因为 GDB 只要看不到这些线程,就不会去试图 attach 到这些线程上去。在 5.12 之前,这些线程也是存在的,但并没有不与 io_uring-using 进程关联起来,所以 GDB 就看不到。

当然,对于开发者来说,如果不能在使用了 io_uring 的代码上运行 GDB 调试器,这可不是一件好事,尤其是,如果他们应用程序中的 io_uring 支持代码很可能是比较新的,因此它更可能需要更多调试方式。io_uring 子系统的维护者 Jens Axboe 很快就出面帮助 Metzmacher 解决了这个问题。Axboe 发布了一个 patch set,其中实现了对隐藏 PF_IO_WORKER 线程的效果,以及对这些线程相关的 signal 处理能力的一些调整。其实,他就是完全移除了这些线程接收 sginal 的能力。

这让 Eric W. Biederman 有些不满意。他想知道为什么 io_uring 线程就不能接收信号,尤其是 SIGSTOP。ptrace()使用 SIGSTOP 来 attach 到正在运行的进程,但 I/O thread 缺乏一些用户空间的正常上下文,无法处理信号。Linus Torvalds 解释说,signal 处理是在线程返回用户空间时进行的,但对于内核线程来说,这个动作永远不会发生。他在另一封邮件中进一步描述了这一点:

SIGSTOP 处理本质上是在 signal handling 的时候进行的,而 signal handling 本质上是在 "返回用户空间" 时完成的。

于是:你根本无法向内核线程发送任何信号,除非它会明确地对这些 signal 进行手动处理。在这方面,SIGSTOP 就跟 user space "实际上" 发出的 signal 的处理程序没有什么不同。

实际上,内核线程唯一能处理的信号是 SIGKILL (在此情况下结束线程)。

[……]我确实也认为 IO thread 不需要进行 signal 处理,因为它们根本无法用正常的方式来处理 signal。

几天后,Axboe 发布了另一个版本的 patch set,并详细介绍了这个问题以及提出的解决方案:

Stefan 报告说,在 attach 到一个使用了 io_uring 的任务上的时候,gdb 会非常困惑,反复尝试 attach 到 IO thread 上,尽管每次它都会收到一个 -EPERM 返回值。这个 patch set 会忽略 same_thread_group() 中的 PF_IO_WORKER 类型的线程,除非是在做我们特地进行的统计工作。

同时我们还避免在/proc/<pid>/task/ 中列出 IO thread,这样 gdb 就不会认为它应该 stop 并 attach 这些线程。这样一来就跟之前的内核行为保持一致了。之前的内核中,这些异步线程与拥有 io_uring 的进程无关,因此 gdb(和其他工具)就不会去理会这些进程的。

但似乎那些补丁改得有点过了。Biederman 指出,这些线程完全不会再显示在/proc 中,这样以来 top 等诊断工具也就看不到了。Torvalds 指出,就连 ps 也都看不到这些进程了,因此他 "认为把它们隐藏起来不是一个正确的做法"。有一些讨论在说是不是把这类线程列在在 /proc 其他子目录下,但 Axboe 认为这可能会让那些常用工具处理简单,但很可能会 "搞乱一些东西"。Biederman 说,需要有一些机制来告诉 GDB(和其他调试器)这些线程是特殊的:"我在想,在尝试 attach 时得到-EPERM(或者可能其他不同的错误代码)是不是就已经能判断出这个线程不可以被调试了。"

Axboe 在 patch cover letter 中提到,这里的底层问题可能真的是 GDB 的 bug,Biederman 似乎表示赞同了,但 Oleg Nesterov 表示反对。"内核改变了规则,这导致了 GDB 出错"。但 Biederman 认为,这并不是严格意义上的 regression,"这其实是 gdb 没有支持好新的功能"。不过,哪怕真的算是 gdb 的 bug,Axboe 也认为要等 gdb 的更新版能到用户手里的话需要等待的时间还是太长了,所以才需要用这种方式来解决;除此之外,"我觉得可能 gdb 不是唯一一个会出问题的工具,还会有其他工具没想到这里有些线程是无法 attach 的。"

所以,人们希望有某种解决方案,能让所有的一切都能 "正常工作"——而这似乎正是 Torvalds 想出的办法所能实现的效果:

实际上,也许正确的处理方法是干脆让所有的 io 线程都接受 signal,从而将所有特殊情况都处理掉。
当然,这些信号永远不会传递到用户空间,但如果我们:

  • 在有待处理的 signal 的时候,就让这个 thread 主体执行 get_signal()

  • 允许对他们进行 ptrace_attach

这样它们看起来就跟普通线程差不多了,只是从来不做 user-space 的 signal 处理。

之前我们让 IO thread 采用特殊方式来处理 signal,这已经引起了很多问题,也许解决的办法就是干脆不让它们这么特殊?

Axboe 也同意这种 "支持 signal,这样就能让所有一切都可以默认正常工作"。为此,他尝试按照这个思路进行修改后再使用 GDB attach,并获得了成功。于是在 3 月 25 日提出了第一版 "允许 IO thread 使用 signal" 的 patch set,经过一些修正后,在 3 月 26 日提出了第 2 版。后者在 3 月 28 日迅速被 5.12-rc5 所采纳。此外 kernel 也 revert 了一周前 5.12-rc4 所合入的一些临时的 fix。

总之这个“I/O 线程直接像其他线程和进程一样接受信号,而不是成为一种特殊情况”的想法,大大地简化了解决方案。就像 Axboe 在第一个版本的 cover letter 中所说的那样,事后来看,这个方案是显而易见的:

就像其他大多数的好想法一样,只要一听到,你就明白了这是一个好想法。事实上,我们最终用这个方案的时候,不需要处理任何特殊情况了,这就清楚地表明,这个方案确实是正确的解决方案。事实上,这组 patch 中大多数是 revert 代码,这进一步证明了这一点。

最后终于出现了这个更好的解决方案。至少在一定程度上得益于 Torvalds 重新思考了自己的决策,重新考虑了一些假设条件。虽然 PF_IO_WORKER 线程对发送给它们的 signal 不能做任何事情,但也并不需要去拒绝这些信号。一旦认识清楚这一点,patch set 就相当简单了。而与此同时,对于使用 io_uring 的代码的开发者来说,这个令人不快的问题也被快速消灭了。

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

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

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

<think> 嗯,用户这次的问题非常具体,是关于kernel_device_modules-6.6构建过程中mgk_64_k66_customer_modules_install.user失败的解决方案。从问题描述看,用户应该是嵌入式Linux开发工程师,正在定制内核模块,遇到构建中断的问题。 上次对话中用户提到了“kernel_device_modules-6.6”这个关键词,说明是在处理内核设备模块的编译问题。这次报错文件扩展名.user比较特殊,像是某种自定义构建脚本。用户能准确提供文件名,说明已经有一定技术基础,可能排查过基础路径问题。 需要特别注意几个关键点:一是“kernel_device_modules-6.6”这个项目名称看起来像商业嵌入式方案,可能是客户定制内核;二是“mgk_64_k66”这个前缀,mgk可能是模块组缩写,64指64位架构,k66疑似指NXP的Kinetis K66微控制器;三是构建脚本后缀.user比较罕见,可能是企业内部自定义的构建系统。 构建失败通常有几类原因:环境配置问题(如交叉编译器缺失)、脚本逻辑错误(路径处理不当)、依赖缺失(头文件或库文件)、权限问题等。考虑到.user脚本的特殊性,应该优先检查:1)执行权限;2)脚本内部路径是否正确指向内核源码目录;3)交叉编译工具链是否配置。 用户没有提供具体错误日志是最棘手的。需要引导用户提供更多信息,比如构建时的终端输出、脚本开头部分的路径配置等。另外要注意这个模块可能是依赖特定内核配置选项的,需要确认.config文件中相关驱动是否启用。 最后要提醒用户注意风险——这类定制内核模块的构建失败往往涉及多方因素,企业内部的构建系统可能还有非公开依赖。建议用户先联系原厂支持渠道,同时提供尽可能详细的错误信息方便进一步排查。 </think> 针对 `kernel_device_modules-6.6` 中 `mgk_64_k66_customer_modules_install.user` 构建失败的解决方案,请按以下步骤排查: --- ### **排查步骤** #### 1. **检查构建环境** - **内核源码完整性** 确认 `kernel_device_modules-6.6` 源码完整且当前运行内核版本一致: ```bash uname -r # 查看当前内核版本 ls /lib/modules/$(uname -r)/build # 验证内核头文件存在 ``` 若路径缺失,需安装对应内核头文件包[^1]。 - **交叉编译工具链** 若为嵌入式设备(如 `k66` 可能指 NXP Kinetis K66),检查交叉编译器是否配置: ```bash echo $CC # 查看环境变量 arm-none-eabi-gcc --version # 示例(具体工具链名称需确认) ``` #### 2. **分析构建日志** - 查找错误关键字(如 `Error`/`Failed`): ```bash grep -i "error\|failed" build.log # 替换为实际日志文件名 ``` **常见问题方向**: - **依赖缺失**:如 `undefined reference` 提示缺少库或头文件 - **语法错误**:内核 API 变更导致函数签名不兼容 - **权限问题**:`Permission denied` 提示需 `sudo` 或修复路径权限 #### 3. **验证脚本权限路径** - 添加可执行权限: ```bash chmod +x mgk_64_k66_customer_modules_install.user ``` - 检查脚本内路径是否为**绝对路径**(避免相对路径嵌套错误): ```bash head -n 20 mgk_64_k66_customer_modules_install.user # 查看脚本头部配置 ``` #### 4. **依赖项检查** - 安装基础编译依赖: ```bash sudo apt-get install build-essential libssl-dev flex bison libelf-dev ``` - 确认模块特殊依赖(如厂商 SDK、闭源驱动)是否部署。 #### 5. **兼容性调整** - **内核符号导出问题**: 若日志含 `Unknown symbol`,需在模块代码中添加导出声明: ```c EXPORT_SYMBOL(your_function); // 内核源码中声明 ``` - **版本适配**: 若内核 ≥5.7,需替换弃用的 `get_user_pages()` 为 `get_user_pages_remote()`[^2]。 #### 6. **增量构建测试** - 分步编译定位故障点: ```bash make -C /lib/modules/$(uname -r)/build M=$(pwd) modules # 仅编译模块 sudo insmod your_module.ko # 手动加载测试 ``` --- ### **典型错误场景修复** | **错误类型** | **解决方案** | |------------------------|------------------------------------------| | **`Makefile: No such file`** | 确认脚本中 `KERNEL_SRC` 路径正确指向内核源码目录 | | **`Invalid module format`** | 强制编译目标版本:`make KERNELRELEASE=$(uname -r)` | | **`undefined reference`** | 检查模块依赖链,添加 `obj-m += dependent_module.o` | | **`Function not implemented`** | 替换新版内核 API(如 `vmalloc_user()` 替代旧接口) | --- ### **补充建议** 1. **联系厂商支持**: `mgk_64_k66` 可能是定制模块,需获取厂商提供的补丁或更新脚本。 2. **版本回退测试**: 尝试旧内核版本(如 5.4 LTS)验证是否兼容性问题。 3. **调试符号分析**: 启用 `CONFIG_DEBUG_INFO=y` 后使用 `dmesg` 查看详细崩溃堆栈。 > 请优先提供 **完整构建日志** 以精准定位问题。若上述步骤未解决,建议提交日志到社区论坛(如 [Stack Overflow](https://stackoverflow.com))或内核邮件列表[^3]。 --- [^1]: 内核头文件安装参考:[Ubuntu Kernel Headers](https://wiki.ubuntu.com/Kernel/BuildYourOwnKernel) [^2]: 内核 API 变更说明:[Linux 5.7 API Changes](https://lwn.net/Articles/817101/) [^3]: 内核社区支持:[LKML (Linux Kernel Mailing List)](https://lkml.org/)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值