37、Linux内核调试:挂起任务、工作队列停滞检测与KGDB使用指南

Linux内核调试:挂起任务、工作队列停滞检测与KGDB使用指南

1. 挂起任务和工作队列停滞检测

在Linux系统中,挂起任务(hung task)和工作队列停滞(workqueue stall)是可能影响系统性能和稳定性的重要问题。下面将详细介绍如何利用内核的相关功能来检测这些问题。

1.1 利用内核挂起任务检测器

通过常规的 make menuconfig 界面进行内核配置,在 Kernel hacking | Debug Oops, Lockups and Hangs 菜单下,可以找到以下相关配置项:
- [*] Detect Hung Tasks :启用挂起任务检测功能。
- (120) Default timeout for hung task detection (in seconds) :挂起任务检测的默认超时时间为120秒。此值可通过以下两种方式调整:
- 更改 CONFIG_DEFAULT_HUNG_TASK_TIMEOUT 的值。
- 修改 kernel.hung_task_timeout_secs sysctl的值,将其设置为0可禁用检测。
- [ ] Panic (Reboot) On Hung Tasks :当检测到挂起任务时,是否触发内核恐慌(panic)。该配置对应 CONFIG_BOOTPARAM_HUNG_TASK_PANIC ,默认关闭,也可通过 kernel.hung_task_panic sysctl进行设置。

检测挂起任务由内核线程 khungtaskd 持续扫描实现。以下是与挂起任务检测相关的内核sysctl可调参数:
| 参数 | 描述 |
| — | — |
| hung_task_all_cpu_backtrace | 若设置为1(默认0),检测到挂起任务时,内核向所有核心发送NMI中断,触发堆栈回溯。需启用 CONFIG_DETECT_HUNG_TASK CONFIG_SMP 。 |
| hung_task_check_count | 检查任务数量的上限。在资源受限的嵌入式系统中,可适当降低此值。该值因架构而异,如ARM - 32编译的树莓派上为32,768,x86_64上为4,194,304。 |
| hung_task_check_interval_secs | 通常为0,表示挂起任务的超时时间由 kernel.hung_task_timeout_secs sysctl决定。若为正值,则覆盖该值,按此间隔(秒)检查挂起任务。合法范围为 {0:LONG_MAX/HZ} 。 |
| hung_task_timeout_secs | 关键的挂起任务检测参数。当任务处于不可中断睡眠状态( ps -l 显示为D状态)超过此时间(秒)时,触发内核警告(可能触发恐慌)。合法范围为 {0:LONG_MAX/HZ} 。 |
| hung_task_panic | 若设置为1(默认0),检测到挂起任务时内核恐慌;若为0,任务保持挂起(D)状态。 |
| hung_task_warnings | 报告警告的最大数量,默认10。检测到挂起任务时减1,值为 -1 表示可无限报告警告。 |

以下是在x86_64 guest VM上查看这些默认值的示例:

$ sudo sysctl -a|grep hung_task
kernel.hung_task_all_cpu_backtrace = 0
kernel.hung_task_check_count = 4194304
kernel.hung_task_check_interval_secs = 0
kernel.hung_task_panic = 0
kernel.hung_task_timeout_secs = 120
kernel.hung_task_warnings = 10
$
1.2 检测工作队列停滞

内核工作队列基础设施对驱动程序和其他开发者非常有用,但工作有时会出现不可接受的延迟,影响性能。内核提供了检测工作队列停滞的功能。

通过选择 CONFIG_WQ_WATCHDOG=y 内核配置启用该功能,可在 make menuconfig 界面的 Kernel hacking | Debug Oops, Lockups and Hangs | Detect Workqueue Stalls 中找到。启用后,若工作队列的工作池无法处理工作项,内核日志将发出 KERN_WARN 级别的警告信息,并包含工作队列的内部状态信息。

工作队列停滞检测的超时时间由 workqueue.watchdog_thresh 内核启动参数和相应的sysfs文件控制,默认30秒。将其设置为0可禁用工作队列停滞检查。

要触发工作队列停滞,可以在 kernel-default 工作队列的工作函数中插入一些CPU密集型代码进行测试。示例代码可参考 此处 。插入的有问题代码通常会导致CPU被锁定,内核的工作队列停滞检测代码会感知到问题并发出紧急级别的打印信息。此时运行 top -i 工具,会发现内核工作线程几乎占用了100%的CPU资源。

2. 使用内核GDB(KGDB)

KGDB是用于Linux内核(和模块)的源级调试工具,允许像调试应用程序一样设置断点、单步执行代码、查看变量和检查内存。

2.1 技术要求

除了常规的技术要求和工作空间外,还需要安装一些软件包和下载一个压缩的根文件系统映像:
1. 安装QEMU ARM和x86模拟器应用程序以及一些杂项软件包:

sudo apt install qemu-system-arm qemu-system-x86 lzop libncursesw5 libncursesw5-dev p7zip-full
  1. 导航到GitHub仓库的 ch11/ 目录,下载压缩的根文件系统映像:
cd <book_src>/ch11
wget https://github.com/PacktPublishing/Linux-Kernel-Debugging/raw/main/ch11/rootfs_deb.img.7z

由于该文件较大,下载后可能会自动命名为 rootfs_deb.img.7z.1 ,需要删除原始(虚拟)文件并将实际文件重命名为正确的名称:

rm rootfs_deb.img.7z
mv rootfs_deb.img.7z.1 rootfs_deb.img.7z
2.2 理解KGDB的工作原理

KGDB采用客户端 - 服务器架构,由两台机器配合使用。一台是运行客户端GDB程序的主机系统,另一台是将GDB服务器组件嵌入内核的目标系统。客户端和服务器通常通过TCP/IP(默认端口1234)进行通信,客户端将用户输入的GDB命令发送到服务器,服务器在目标系统上执行命令并将结果返回给客户端显示,实现远程调试。

以下是KGDB工作原理的流程图:

graph LR
    A[客户端GDB] -- 发送GDB命令 --> B[目标系统内核(GDB服务器)]
    B -- 执行命令并返回结果 --> A

JTAG调试器(如流行的BDI2000/3000)也使用gdb - server组件,使用带有嵌入式GDB服务器的JTAG调试器通常能更轻松、稳定地调试内核,还可避免在调试会话和Linux控制台之间复用串口。

2.3 为KGDB设置ARM目标系统和内核

当构建Linux内核时,会生成两个特定架构的内核映像文件:未压缩的内核映像文件 vmlinux 和压缩的内核映像文件(如 bzImage zImage )。为了使用KGDB进行内核调试,需要启用 CONFIG_DEBUG_INFO 内核配置选项,以在 vmlinux 文件中嵌入调试符号信息。

为了方便构建自定义的ARM Linux系统,可以利用Simple Embedded ARM Linux System(SEALS)项目。该项目允许配置平台和内核,构建内核、设备树Blob(DTB)和基于BusyBox的根文件系统映像。

任何工作的Linux系统至少需要三个(或四个,取决于CPU)组件:
- 引导加载程序(在本例中,QEMU充当引导加载程序)。
- 对于ARM32/AArch64/PPC系统,需要设备树Blob(DTB)二进制映像。
- 内核映像。
- 根文件系统。

使用SEALS项目需要安装QEMU ARM模拟器、完整的x86_64 - ARM32工具链和一些杂项软件包。若想了解如何配置和使用SEALS项目,可参考其维基页面:
- Welcome to the SEALS wiki
- HOWTO Install required packages on the Host for SEALS
- Detailed step - by - step instructions to use SEALS: SEALs HOWTO

2.4 为KGDB配置内核

配置内核时,通常将其配置为调试内核。以下是KGDB支持的强制配置项:
- DEBUG_KERNEL=y :选择 Kernel Hacking | Kernel debugging 布尔菜单选项时启用。
- DEBUG_INFO=y :选择 Kernel Hacking | Compile-time checks and compiler options | Compile the kernel with debug info 选项,通过使用 -g 编译器开关编译内核和模块,将内核符号和调试符号信息嵌入 vmlinux 文件。
- MAGIC_SYSRQ=y :选择 Kernel Hacking | Generic Kernel Debugging Instruments | Magic SysRq key ,通常用于在运行的系统中通过向 /proc/sysrq-trigger 写入 g 来发出进入调试器的命令。建议通过向 /proc/sys/kernel/sysrq 写入 1 来启用所有魔法SysRq功能。
- CONFIG_KGDB=y :启用使用GDB调试内核的支持,内核将包含服务器端GDB(gdbserver)代码,允许GDB客户端远程连接并发送GDB命令。
- KGDB_SERIAL_CONSOLE=y :启用与KGDB共享串行控制台,这是远程GDB客户端连接到服务器(目标内核)的一种方式。
- KGDB_HONOUR_BLOCKLIST=y :建议保持此配置开启,防止递归陷阱,禁止将某些无法进行kprobe的例程设置为断点。

此外,还可以选择一些可选的内核配置选项:
- FRAME_POINTER=y :选择 Kernel hacking | Compile-time checks and compiler options | Compile the kernel with frame pointers ,虽然标记为可选,但非常有用。
- DEBUG_INFO_SPLIT=y :通过拆分和重用 .dwo 调试信息文件,显著减小文件大小。
- DEBUG_INFO_BTF=y :生成去重的BPF类型信息(BTF),可能对未来运行eBPF有用。
- GDB_SCRIPTS=y :设置指向基于Python的GDB辅助脚本( lx - <foo> )的链接,有助于调试内核或模块。
- DEBUG_FS=y :通常建议启用 debugfs 伪文件系统。

在使用KGDB调试内核(或模块)时,建议禁用硬件或软件看门狗,避免干扰调试过程。

如果使用SEALS项目,可以在项目板的 build.config 文件中启用KGDB模式选项,默认关闭:

$ grep KGDB build.config
KGDB_MODE=0 # make '1' to have qemu run with the '-s -S' 
            # switch (waits for client GDB to 'connect')

在构建内核时,如果被询问是否启用GCC插件,建议选择否。

3. 使用KGDB调试内核和内核模块

3.1 调试内核

在完成了ARM目标系统和内核的KGDB配置后,就可以开始使用KGDB调试内核了。以下是具体的操作步骤:
1. 启动目标系统 :使用QEMU启动配置好的ARM目标系统,并开启KGDB模式。如果使用SEALS项目,可在 build.config 文件中将 KGDB_MODE 设置为1,这样QEMU会以 -s -S 开关启动,等待客户端GDB连接。
2. 启动客户端GDB :在主机系统上启动GDB,并加载未压缩的内核映像文件 vmlinux

gdb vmlinux
  1. 连接到目标系统 :在GDB中使用 target remote 命令连接到目标系统的GDB服务器。默认情况下,GDB服务器使用TCP/IP的1234端口。
(gdb) target remote localhost:1234
  1. 设置断点 :在需要调试的内核代码位置设置断点。例如,要在某个内核函数 my_kernel_function 处设置断点,可以使用以下命令:
(gdb) break my_kernel_function
  1. 单步执行和查看变量 :使用GDB的单步执行命令(如 next step )逐行执行代码,并使用 print 命令查看变量的值。
(gdb) next
(gdb) print my_variable
3.2 调试内核模块

调试内核模块与调试内核类似,但需要额外加载模块的符号信息。以下是调试内核模块的步骤:
1. 插入内核模块 :在目标系统上插入需要调试的内核模块。

insmod my_module.ko
  1. 加载模块符号信息 :在客户端GDB中,使用 add-symbol-file 命令加载模块的符号信息。需要指定模块的二进制文件路径和加载地址。
(gdb) add-symbol-file /path/to/my_module.ko <load_address>

其中, <load_address> 可以通过 lsmod 命令在目标系统上查看模块的加载地址。
3. 设置断点和调试 :与调试内核一样,在模块代码中设置断点,然后单步执行代码、查看变量。

3.3 [K]GDB的一些技巧和提示
  • 使用GDB脚本 :启用 GDB_SCRIPTS=y 内核配置选项后,GDB会设置指向基于Python的GDB辅助脚本( lx - <foo> )的链接。这些脚本可以简化调试过程,例如 lx-dmesg 可以查看内核日志。
(gdb) lx-dmesg
  • 硬件断点 :如果目标系统支持,建议使用硬件断点。硬件断点比软件断点更高效,且不受 CONFIG_STRICT_KERNEL_RWX CONFIG_STRICT_MODULE_RWX 配置的限制。
  • 禁用看门狗 :在调试过程中,确保禁用硬件或软件看门狗,避免它们干扰调试。

4. 总结

通过本文,我们详细介绍了Linux内核调试中的两个重要方面:挂起任务和工作队列停滞检测以及使用内核GDB(KGDB)进行调试。

在挂起任务和工作队列停滞检测方面,我们了解了如何通过内核配置选项和sysctl参数来检测和处理这些问题。通过检测挂起任务和工作队列停滞,可以及时发现系统中的性能瓶颈和潜在问题,并通过查看内核日志中的警告信息和CPU回溯来定位问题所在。

在使用KGDB进行调试方面,我们学习了KGDB的工作原理、技术要求、目标系统和内核的配置方法,以及如何使用KGDB调试内核和内核模块。KGDB为开发者提供了强大的调试功能,使得我们能够像调试应用程序一样调试内核代码,大大提高了内核开发和调试的效率。

希望本文的内容能够帮助你更好地理解和掌握Linux内核调试的相关技术,在实际开发中能够更加高效地定位和解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值