40、Linux内核调试技巧与最佳实践

Linux内核调试技巧与最佳实践

1. 看门狗设备驱动

在用户空间,我们可以使用看门狗的sysfs接口来操作相关参数。例如,检查是否可以选择预超时调节器:

# cat /sys/class/watchdog/watchdog0/pretimeout_governor
panic
# echo -n noop > /sys/class/watchdog/watchdog0/pretimeout_governor
# cat /sys/class/watchdog/watchdog0/pretimeout_governor
noop

检查预超时值:

# cat /sys/class/watchdog/watchdog0/pretimeout
10

通过这些操作,我们可以在用户空间利用整个看门狗框架,灵活调整看门狗参数。

2. Linux内核调试基础

Linux内核调试具有一定挑战性,因为它是运行在操作系统最底层的独立软件。不过,大多数内核调试工具是内核自带的,无需额外工具。

2.1 技术要求
  • 具备高级计算机架构知识和C编程技能。
  • 拥有Linux内核v4.19.X源码,可从https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/refs/tags 获取。
2.2 理解Linux内核发布过程

Linux内核发布模型包含主线版本、稳定版本和长期支持(LTS)版本三种活跃内核发布类型。
- 主线版本 :子系统维护者收集和准备错误修复和新特性,提交给Linus Torvalds,集成到他的主线Linux树(即主Git仓库),这是所有稳定版本的源头。
- 稳定版本 :新内核版本发布前,会通过候选版本标签提交给社区,开发者测试并反馈。Linus根据反馈决定最终版本是否发布。稳定版本基于主线版本,由Greg Kroah - Hartman维护linux - stable树。
- 长期支持(LTS)版本 :提供长期的稳定性和安全更新。

版本类型 特点 维护者
主线版本 新特性和修复的源头 Linus Torvalds
稳定版本 基于主线版本,接收错误修复 Greg Kroah - Hartman
长期支持(LTS)版本 长期稳定性和安全更新 特定开发者

新主线内核通常每2 - 3个月发布一次。稳定版本的错误修复必须先在主线仓库中应用,再反向移植到稳定树。例如,4.9内核发布后,基于它的稳定版本编号为4.9.1、4.9.2等。

以下是Linux内核发布过程的mermaid流程图:

graph LR
    A[子系统维护者收集修复和特性] --> B[提交给Linus Torvalds]
    B --> C[集成到主线Linux树]
    C --> D[发布候选版本标签]
    D --> E[开发者测试和反馈]
    E --> F{Linus决定发布最终版本?}
    F -- 是 --> G[发布稳定版本]
    F -- 否 --> D
    G --> H[错误修复反向移植到稳定树]

重要链接:
- https://www.kernel.org/ :可下载内核归档文件。
- https://www.kernel.org/category/releases.html :获取最新LTS内核版本及支持时间线。
- https://patchwork.kernel.org/ :按子系统跟踪内核补丁提交情况。

3. Linux内核开发技巧

最佳的Linux内核开发实践源于现有内核代码。本章重点关注调试,常用的调试方法是日志记录和打印。

3.1 消息打印

在Linux内核中, printk() 函数是事实上的内核消息打印函数,类似于C库中的 printf() ,但有日志级别概念。使用示例:

printk(<LOG_LEVEL> "printf like formatted message\n");

<LOG_LEVEL> 是在 include/linux/kern_levels.h 中定义的八种不同日志级别之一,用于指定错误消息的严重程度。

3.2 内核日志级别
日志级别 定义 描述
KERN_EMERG “0” 紧急消息,系统即将崩溃或不稳定
KERN_ALERT “1” 发生严重情况,需立即采取行动
KERN_CRIT “2” 关键条件发生,如严重硬件/软件故障
KERN_ERR “3” 错误条件,常用于驱动程序表示硬件问题或子系统交互失败
KERN_WARNING “4” 警告信息,本身不严重,但可能暗示问题
KERN_NOTICE “5” 不严重但值得注意,常用于报告安全事件
KERN_INFO “6” 信息性消息,如驱动初始化时的启动信息
KERN_DEBUG “7” 调试信息,只有在启用DEBUG内核选项时才有效

如果消息未指定日志级别,默认使用 DEFAULT_MESSAGE_LOGLEVEL (通常为 “4” = KERN_WARNING),可通过 CONFIG_DEFAULT_MESSAGE_LOGLEVEL 内核配置选项设置。

对于新驱动,建议使用更方便的打印API,如 pr_emerg pr_alert 等。示例:

#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__

这样可以为每个 pr_*() 消息添加模块和函数名前缀。

对于设备驱动,应使用与设备相关的辅助函数,如 dev_emerg dev_alert 等,这些函数会接受设备结构作为参数,并打印相关设备名称。

3.3 控制台日志级别

内核根据消息的日志级别和 console_loglevel 内核变量决定是否立即将消息打印到控制台。默认内核日志级别通常为 “4”,所以 pr_info() pr_notice() pr_warn() 等消息可能不会显示在控制台。

  • 查看当前控制台日志级别
$ cat /proc/sys/kernel/printk
4    4    1    7

第一个数字是当前控制台日志级别,第二个是默认值,第三个是可设置的最小级别,第四个是启动时的默认级别。
- 更改控制台日志级别

# echo 8 > /proc/sys/kernel/printk

或者使用 dmesg -n 参数:

# dmesg -n 5

也可以在启动时使用 loglevel 启动参数指定日志级别。

此外,还有特殊的 KERN_CONT pr_cont ,用于表示连续消息,仅在早期启动时的核心/架构代码中使用。

3.4 内核日志缓冲区

每个内核消息都会记录在一个固定大小的循环缓冲区中。如果缓冲区满了,可能会丢失消息。可以通过 LOG_BUF_SHIFT 选项或 log_buf_len 内核启动参数更改缓冲区大小。

3.5 添加时间信息

通过 CONFIG_PRINTK_TIME 选项启用 printk 时间功能,可为打印的消息添加时间戳。时间戳格式为秒和微秒,是自机器启动(或内核计时开始)以来的绝对时间。
可以通过向 /sys/module/printk/parameters/time 文件写入值来控制时间戳的打印:

# echo 1 >/sys/module/printk/parameters/time
# cat /sys/module/printk/parameters/time
Y
4. Linux内核跟踪与性能分析

虽然打印调试能满足大部分需求,但有时需要在运行时监控Linux内核,以跟踪异常行为。Ftrace是Linux内核内部的跟踪工具,是本节的重点。

4.1 使用Ftrace进行代码插桩

Ftrace自2008年的2.6.27版本开始包含在Linux内核中,提供调试环形缓冲区来记录数据。它基于debugfs文件系统,通常挂载在 /sys/kernel/debug/tracing/ 目录。

启用Ftrace需要开启以下内核选项:
- CONFIG_FUNCTION_TRACER
- CONFIG_FUNCTION_GRAPH_TRACER
- CONFIG_STACK_TRACER
- CONFIG_DYNAMIC_FTRACE

这些选项依赖于架构支持相关的跟踪特性选项。

可以通过以下方式挂载tracefs目录:
- 在 /etc/fstab 文件中添加:

tracefs   /sys/kernel/debug/tracing   tracefs defaults   0   0
  • 运行时挂载:
mount -t tracefs nodev /sys/kernel/debug/tracing

Ftrace目录下的部分重要文件及其功能如下:
- available_tracers :可用的跟踪程序列表。
- tracing_cpumask :指定要跟踪的CPU,以十六进制字符串格式表示。
- current_tracer :当前运行的跟踪程序。
- tracing_on :启用或禁用向环形缓冲区写入数据。
- trace :以人类可读格式保存跟踪数据的文件。

4.2 可用的跟踪程序

通过以下命令查看可用的跟踪程序:

# cat /sys/kernel/debug/tracing/available_tracers 
blk function_graph wakeup_dl wakeup_rt wakeup irqsoff function nop

各跟踪程序特点如下:
| 跟踪程序 | 特点 |
| ---- | ---- |
| function | 无参数的函数调用跟踪器 |
| function_graph | 带子调用的函数调用跟踪器 |
| blk | 与块设备I/O操作相关的调用和事件跟踪器 |
| mmiotrace | 内存映射I/O操作跟踪器 |
| irqsoff | 跟踪禁用中断的区域,并保存最长延迟的跟踪信息 |
| preemptoff | 跟踪并记录禁用抢占的时间 |
| preemtirqsoff | 跟踪并记录禁用中断和/或抢占的最长时间 |
| wakeup | 跟踪最高优先级任务唤醒后调度的最大延迟 |
| wakeup_rt | 跟踪实时(RT)任务唤醒后调度的最大延迟 |
| nop | 最简单的跟踪器,仅显示 trace_printk() 调用的输出 |

4.3 使用function跟踪器

以下是使用function跟踪器的示例脚本:

# cd /sys/kernel/debug/tracing
# echo function > current_tracer
# echo 1 > tracing_on
# sleep 1
# echo 0 > tracing_on
# less trace

运行脚本后,输出包含缓冲区中的条目数量、写入的总条目数量,以及每个跟踪函数的信息,包括进程名、进程ID、CPU编号、函数开始时间、被跟踪函数名和调用它的父函数名。

4.4 使用function_graph跟踪器

function_graph跟踪器比function跟踪器更详细,会显示每个函数的入口和出口点,并能测量函数执行时间。
修改前面的脚本:

# cd /sys/kernel/debug/tracing
# echo function_graph > current_tracer
# echo 1 > tracing_on
# sleep 1
# echo 0 > tracing_on
# less trace

输出中, DURATION 显示函数运行时间, + 表示函数执行时间超过10微秒, ! 表示超过100微秒。函数调用使用C语言中的花括号 {} 表示开始和结束,无子调用的叶函数用分号 ; 标记。

Ftrace还支持通过 tracing_thresh 选项限制只跟踪执行时间超过一定阈值的函数。可以在启动时通过内核命令行设置阈值:

tracing_thresh=200 ftrace=function_graph

也可以在运行时设置:

echo 200 > tracing_thresh
4.5 函数过滤

可以使用过滤器简化Ftrace的输出,只显示感兴趣的函数信息。
- 设置过滤函数

# echo kfree > set_ftrace_filter
  • 禁用过滤器
# echo  > set_ftrace_filter
  • 排除特定函数
# echo kfree > set_ftrace_notrace

还可以使用 set_ftrace_pid 工具跟踪特定进程调用的函数。更多过滤选项可参考官方文档https://www.kernel.org/doc/Documentation/trace/ftrace.txt 。

4.6 跟踪事件

在介绍跟踪事件之前,先了解跟踪点。跟踪点是触发系统事件的特殊代码插入点,分为动态和静态两种。静态跟踪点对系统无影响,仅在被检测函数末尾添加少量函数调用字节和一个数据结构;动态跟踪点在相关代码片段执行时调用跟踪函数,并将跟踪数据写入环形缓冲区。

Linux内核提供了从用户空间操作跟踪点的特殊API,在 /sys/kernel/debug/tracing/events 目录中保存了系统事件。

  • 查看可用事件列表
# cat /sys/kernel/debug/tracing/available_events
  • 以结构化方式查看事件
# ls /sys/kernel/debug/tracing/events

以跟踪hrtimer相关内核函数为例,使用 nop 跟踪器:

# cd /sys/kernel/debug/tracing/
# echo 0 > tracing_on
# echo > trace
# echo nop > current_tracer 
# echo 1 > events/timer/enable 
# echo 1 > tracing_on;
# sleep 1;
# echo 0 > tracing_on; 
# echo 0 > events/timer/enable 
# less trace

更多关于事件跟踪配置的详细信息可参考https://www.kernel.org/doc/Documentation/trace/events.txt 。

Linux内核调试技巧与最佳实践

5. 内核调试实战案例分析

为了更好地理解上述的内核调试技巧,下面通过几个实战案例进行详细分析。

5.1 设备驱动调试案例

假设我们正在开发一个新的设备驱动,在设备初始化过程中遇到了问题。我们可以使用内核日志打印来定位问题。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

static struct device *my_device;

static int __init my_driver_init(void) {
    pr_info("My driver is initializing...\n");

    my_device = device_create(NULL, NULL, 0, NULL, "my_device");
    if (IS_ERR(my_device)) {
        pr_err("Failed to create device\n");
        return PTR_ERR(my_device);
    }

    pr_info("Device created successfully\n");
    return 0;
}

static void __exit my_driver_exit(void) {
    pr_info("My driver is exiting...\n");
    device_destroy(NULL, 0);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");

在这个案例中,我们使用 pr_info pr_err 来打印初始化过程中的信息。如果设备创建失败,会打印错误信息,方便我们定位问题。

5.2 性能问题调试案例

假设系统出现了性能问题,CPU使用率过高。我们可以使用Ftrace来分析是哪些函数占用了大量时间。

# cd /sys/kernel/debug/tracing
# echo function_graph > current_tracer
# echo 1 > tracing_on
# sleep 10  # 运行一段时间来收集数据
# echo 0 > tracing_on
# less trace

通过分析 trace 文件的输出,我们可以找到执行时间较长的函数,例如带有 + ! 标记的函数。然后根据这些信息对代码进行优化。

6. 内核调试的注意事项

在进行内核调试时,需要注意以下几点:
- 编译选项 :在调试时,建议启用 DEBUG 选项,这样可以让 pr_debug 等调试信息生效。
- 日志级别 :合理设置日志级别,避免过多的日志信息影响系统性能。可以根据需要调整 console_loglevel
- 缓冲区大小 :如果需要记录大量的日志信息,适当增大内核日志缓冲区的大小,避免日志丢失。
- 性能影响 :一些调试工具,如Ftrace,可能会对系统性能产生一定影响。在生产环境中使用时,需要谨慎评估。

7. 总结

本文详细介绍了Linux内核调试的相关技巧和最佳实践,包括看门狗设备驱动的操作、内核发布过程、开发技巧、跟踪与性能分析等方面。通过掌握这些知识,我们可以更高效地进行Linux内核的开发和调试。

以下是本文内容的总结表格:
| 主题 | 主要内容 |
| ---- | ---- |
| 看门狗设备驱动 | 通过sysfs接口操作看门狗参数,如检查预超时调节器和预超时值 |
| 内核发布过程 | 包括主线版本、稳定版本和长期支持版本,介绍了发布流程和相关链接 |
| 内核开发技巧 | 消息打印、日志级别、控制台日志级别、日志缓冲区和时间信息的处理 |
| 内核跟踪与性能分析 | 使用Ftrace进行代码插桩、函数跟踪、事件跟踪和性能分析 |
| 调试实战案例 | 设备驱动调试和性能问题调试的案例分析 |
| 注意事项 | 编译选项、日志级别、缓冲区大小和性能影响等方面的注意事项 |

最后,为了更清晰地展示整个内核调试的流程,以下是一个mermaid流程图:

graph LR
    A[开始调试] --> B{选择调试方法}
    B -- 打印调试 --> C[使用printk或pr_*系列函数]
    B -- 跟踪调试 --> D[使用Ftrace]
    C --> E[设置日志级别和缓冲区]
    D --> F[选择跟踪程序和过滤器]
    E --> G[分析日志信息]
    F --> H[分析跟踪数据]
    G --> I{问题解决?}
    H --> I
    I -- 是 --> J[结束调试]
    I -- 否 --> B

通过这个流程图,我们可以看到内核调试的基本流程,根据问题的特点选择合适的调试方法,然后进行分析和解决。希望这些内容能帮助你更好地进行Linux内核的开发和调试工作。

源码地址: https://pan.quark.cn/s/d1f41682e390 miyoubiAuto 米游社每日米游币自动化Python脚本(务必使用Python3) 8更新:更换cookie的获取地址 注意:禁止在B站、贴吧、或各大论坛大肆传播! 作者已退游,项目不维护了。 如果有能力的可以pr修复。 小引一波 推荐关注几个非常可爱有趣的女孩! 欢迎B站搜索: @嘉然今天吃什么 @向晚大魔王 @乃琳Queen @贝拉kira 第三方库 食用方法 下载源码 在Global.py中设置米游社Cookie 运行myb.py 本地第一次运行时会自动生产一个文件储存cookie,请勿删除 当前仅支持单个账号! 获取Cookie方法 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 按刷新页面,按下图复制 Cookie: How to get mys cookie 当触发时,可尝试按关闭,然后再次刷新页面,最后复制 Cookie。 也可以使用另一种方法: 复制代码 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 控制台粘贴代码并运行,获得类似的输出信息 部分即为所需复制的 Cookie,点击确定复制 部署方法--腾讯云函数版(推荐! ) 下载项目源码和压缩包 进入项目文件夹打开命令行执行以下命令 xxxxxxx为通过上面方式或取得米游社cookie 一定要用双引号包裹!! 例如: png 复制返回内容(包括括号) 例如: QQ截图20210505031552.png 登录腾讯云函数官网 选择函数服务-新建-自定义创建 函数名称随意-地区随意-运行环境Python3....
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值