【Linux驱动开发】第四天:dmesg日志全解+驱动加载失败极速排查

目录

  1. 什么是dmesg?大白话讲透本质
  2. printk日志级别全解(dmesg核心基础)
  3. 驱动开发必背的dmesg常用筛选命令
  4. 核心实战:用dmesg极速排查驱动加载失败(分场景)
  5. dmesg调试优化配置
  6. 核心总结+面试必背考点

1. 什么是dmesg?大白话讲透本质

dmesg = display message,是Linux内核环形日志缓冲区的专属查看工具,也是驱动开发的「第一调试神器」。

核心本质

你之前驱动里写的printk不会打印到用户态的stdout标准输出 ,而是直接写入内核专属的环形日志缓冲区dmesg就是用来读取、筛选、监控这个缓冲区的唯一工具。

大白话类比:

内核是一个封闭的工厂,驱动是工厂里的设备,printk是设备的报警/日志播报,dmesg就是工厂的监控室,所有设备的运行状态、报错、异常,全都会在这里留下记录,出问题第一时间来这里查。

关键特性

  1. 环形缓冲区:固定大小,满了会自动覆盖最旧的日志,出问题要第一时间查看;
  2. 纯内核态日志:只记录内核、驱动、硬件相关的日志,和用户态应用日志完全隔离;
  3. 临时存储:默认存在内存中,系统重启后会清空,需要持久化要额外配置。

2. printk日志级别全解(dmesg核心基础)

dmesg的筛选、过滤、控制台打印规则,全部基于printk的日志级别,这是所有调试的基础。

2.1 printk的标准格式

c 复制代码
printk(日志级别宏 "日志内容\n");

比如你之前驱动里的写法:

c 复制代码
printk(KERN_INFO "===== 驱动加载成功 =====\n");

必坑提醒:日志内容结尾必须加\n,否则会出现日志拼接、缓冲不刷新的问题。

2.2 8个日志级别全表

日志级别数字越小,优先级越高,严重程度越高:

日志级别宏 数字 严重程度 含义 驱动开发使用场景
KERN_EMERG 0 最高 紧急事件,系统即将崩溃 内核致命错误,驱动几乎不用
KERN_ALERT 1 极高 必须立即处理的告警 硬件严重故障,驱动完全无法工作
KERN_CRIT 2 严重错误 驱动初始化完全失败,硬件不可用
KERN_ERR 3 中高 普通错误 驱动常规错误,如资源申请失败、IO异常
KERN_WARNING 4 警告 不影响驱动运行,但有潜在风险,如参数非法
KERN_NOTICE 5 中低 重要提示 驱动加载/卸载成功等关键常规信息
KERN_INFO 6 普通信息 驱动调试常规日志,最常用
KERN_DEBUG 7 最低 调试信息 开发阶段的细节调试日志,正式版一般关闭

2.3 新手必懂的控制台打印规则

很多新手会遇到一个痛点:我写了printk,为什么控制台看不到,只能用dmesg看到?

核心原因是内核有个控制台日志级别阈值 :只有优先级高于等于这个阈值的日志,才会直接打印到串口/终端控制台。

  • 系统默认控制台级别一般是4,也就是只有0-3级(EMERGERR)的日志会打印到控制台;
  • 你常用的KERN_INFO(6)KERN_WARNING(4)级别低于阈值,不会打印到控制台,但会完整保存在内核缓冲区,用dmesg可以正常查看。

2.4 查看&修改日志级别

bash 复制代码
# 查看当前内核日志级别配置
cat /proc/sys/kernel/printk
# 输出示例:4       4       1       7
# 四个数字含义:
# 1. 当前控制台级别  2. 默认消息级别  3. 最小允许级别  4. 启动默认级别

# 临时修改(重启失效,调试专用):所有级别都打印到控制台
sudo echo 8 > /proc/sys/kernel/printk

3. 驱动开发必背的dmesg常用筛选命令

下面只列驱动开发高频使用的命令,过滤掉运维通用的无用参数,每个都带场景和示例,直接复制就能用。

3.1 基础查看命令

命令 作用 驱动开发使用场景
dmesg 直接查看所有内核日志 快速浏览全量日志
`dmesg less` 分页查看日志
dmesg -H 带人类可读的格式化时间戳 精准定位日志发生的时间,默认是开机秒数
dmesg -k 只看内核/驱动相关日志,过滤用户态消息 排除干扰,只看驱动相关内容

3.2 调试核心高频命令

  1. 实时监控日志(驱动调试第一常用)
bash 复制代码
dmesg -w
# 等价于 tail -f,实时刷新内核日志
# 用法:开一个终端执行这个命令,另一个终端加载驱动,实时看到打印日志
  1. 按关键字精准筛选(排查故障核心)
bash 复制代码
# 搜你的驱动名,精准过滤自己的驱动日志
dmesg | grep chr_dev

# 搜错误日志(忽略大小写)
dmesg | grep -i error

# 搜警告日志
dmesg | grep -i warn

# 多关键字联合筛选(同时搜错误、警告、你的驱动名)
dmesg | grep -E "error|warn|mychrdev"
  1. 按日志级别筛选
bash 复制代码
# 只看错误及以上级别的致命日志
dmesg -l err,crit,alert,emerg

# 只看警告+错误
dmesg -l warn,err

# 只看调试级别的日志
dmesg -l debug
  1. 调试专用命令
bash 复制代码
# 清空所有内核日志(调试前清空,只看本次操作的新日志,必用)
sudo dmesg -C

# 查看最近N行日志(加载驱动后第一时间执行,看最新报错)
dmesg | tail -30

# 把日志保存到文件,方便复盘/分享
dmesg > driver_debug.log

4. 核心实战:用dmesg极速排查驱动加载失败

这是本文的核心内容,整理了驱动开发新手99%会遇到的加载失败场景,给你一套通用排查SOP +分场景定位方案,看完就能解决绝大多数问题。

4.1 驱动加载失败通用排查SOP(必背)

不管什么报错,先按这个步骤走,10秒定位问题方向:

  1. 清空旧日志sudo dmesg -C(排除旧日志干扰,只看本次操作的报错)
  2. 重新执行加载sudo insmod 你的驱动.ko
  3. 查看最新报错dmesg | tail -30dmesg | grep -i error
  4. 根据报错关键词匹配场景,对应解决

4.2 6大高频故障场景+排查+解决方案

场景1:insmod直接报错 cannot insert module xxx.ko: Invalid module format
  • 现象:insmod执行后直接返回该错误,驱动完全无法加载

  • dmesg报错特征

    复制代码
    module verification failed: signature and/or required key missing - tainting kernel
    disagrees about version of symbol module_layout
  • 根因

    1. 驱动编译用的内核源码版本,和当前运行的内核版本不匹配;
    2. 系统开启了Secure Boot/内核锁定,禁止加载未签名的模块。
  • 排查命令

    bash 复制代码
    # 查看当前运行内核版本
    uname -r
    # 查看dmesg里的版本/签名报错
    dmesg | grep -E "module layout|signature|lockdown"
  • 解决方案

    1. 必须在当前运行的系统环境下编译驱动,不要用其他系统/版本编译的ko文件;
    2. 调试阶段在BIOS中关闭Secure Boot,关闭内核锁定;
    3. 生产环境给驱动做签名,符合内核安全要求。

场景2:insmod报错 cannot insert module xxx.ko: Unknown symbol in module
  • 现象:insmod直接报「未知符号」,加载失败(新手90%会踩的坑)

  • dmesg报错特征 :会明确打印缺失的符号,比如:

    复制代码
    chr_dev: Unknown symbol class_create (err 0)
    chr_dev: Unknown symbol device_create (err 0)
  • 根因

    1. 最常见:没加MODULE_LICENSE("GPL");,无法使用内核GPL协议导出的函数;
    2. 调用的函数名拼写错误;
    3. 调用了内核没有导出的私有函数。
  • 排查命令

    bash 复制代码
    # 查看缺失的符号
    dmesg | grep -i "Unknown symbol"
    # 确认内核是否导出了该函数(有T标记就是导出的全局函数)
    grep "class_create" /proc/kallsyms
  • 解决方案

    1. 驱动代码第一优先级检查:必须添加MODULE_LICENSE("GPL");
    2. 核对函数名拼写,和内核API定义完全一致;
    3. 只能使用内核导出的函数,不能调用内核未导出的私有函数。

场景3:insmod执行无报错,但lsmod看不到模块,驱动没生效
  • 现象 :insmod没有返回任何错误,但lsmod | grep 驱动名找不到模块,/dev/下也没有生成设备文件

  • dmesg报错特征 :能看到你驱动里的错误打印,比如:

    复制代码
    alloc dev num failed
  • 根因 :驱动的module_init初始化函数执行失败,返回了非0值,内核会自动回滚,把刚加载的模块卸载掉。

  • 排查命令

    bash 复制代码
    # 查看你的驱动初始化日志,定位哪一步失败了
    dmesg | grep 你的驱动名
  • 解决方案

    1. 给初始化函数的每一步资源申请都加错误判断+KERN_ERR级别打印,精准定位失败步骤;
    2. 常见失败原因:设备号被占用、GPIO/中断号冲突、内存分配失败、硬件未就绪;
    3. 修复失败逻辑,确保初始化成功时返回0,失败时返回负的错误码。

场景4:insmod报错 cannot insert module xxx.ko: Operation not permitted
  • 现象:加了sudo还是报权限不足,无法加载

  • dmesg报错特征

    复制代码
    Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7
  • 根因 :Ubuntu等发行版开启了内核锁定(Kernel Lockdown),Secure Boot开启状态下,禁止加载未签名的内核模块。

  • 排查命令

    bash 复制代码
    dmesg | grep -i lockdown
  • 解决方案

    1. 调试阶段:重启进入BIOS,关闭Secure Boot;
    2. 生产环境:按照内核要求给驱动模块做签名。

场景5:驱动加载成功,但操作设备时系统崩溃/卡住
  • 现象 :insmod成功,/dev/下有设备文件,但cat /dev/xxx或读写设备时,系统卡住、崩溃、重启

  • dmesg报错特征 :会出现内核Oops崩溃日志,关键词:

    复制代码
    BUG: unable to handle kernel NULL pointer dereference
    Oops: 0000 [#1] PREEMPT SMP
  • 根因 :驱动代码有致命bug,比如空指针访问、内存越界、copy_to_user用法错误、中断上下文里执行了睡眠函数。

  • 排查命令

    bash 复制代码
    # 查看内核崩溃Oops日志,看调用栈定位出错的函数
    dmesg | grep -i -A 20 "Oops|BUG"
  • 解决方案

    1. 检查所有指针是否为NULL,内存分配后必须判断返回值;
    2. 核对copy_to_user/copy_from_user的用法,必须用__user标记用户态缓冲区;
    3. 检查中断上下文里是否有睡眠函数、耗时操作。

场景6:驱动加载成功,但dmesg里看不到printk日志
  • 现象:驱动正常工作,但dmesg里找不到你写的printk日志

  • 根因

    1. 日志级别太低,被内核过滤了(比如用了KERN_DEBUG);
    2. printk结尾没加\n,行缓冲没刷新;
    3. 日志被环形缓冲区的新内容覆盖了。
  • 排查命令

    bash 复制代码
    # 查看所有级别日志,包括调试级
    dmesg -l debug | grep 你的驱动名
  • 解决方案

    1. printk日志结尾必须加\n
    2. 调试阶段优先用KERN_INFO级别,不要用KERN_DEBUG
    3. 调试前用sudo dmesg -C清空日志,避免被覆盖。

5. dmesg调试优化配置

5.1 给驱动日志加统一前缀,方便筛选

定义一个宏,给所有日志加上你的驱动名前缀,再也不会和其他内核日志混在一起:

c 复制代码
// 放在驱动代码开头
#define DEBUG_INFO(fmt, args...) printk(KERN_INFO "[mychrdev] " fmt "\n", ##args)
#define DEBUG_ERR(fmt, args...) printk(KERN_ERR "[mychrdev] " fmt "\n", ##args)

// 使用示例
DEBUG_INFO("驱动加载成功");
DEBUG_ERR("设备号申请失败,ret=%d", ret);

筛选日志时,直接执行dmesg | grep [mychrdev],就能精准过滤出你的驱动所有日志。

5.2 持久化保存内核日志

默认dmesg日志存在内存中,重启后会清空,想要持久化查看:

bash 复制代码
# Ubuntu/Debian系统,内核日志自动持久化到/var/log/kern.log
cat /var/log/kern.log | grep mychrdev

# 用journalctl查看本次开机的所有内核日志
journalctl -k -b

# 查看上一次开机的内核日志(需要系统开启了持久化)
journalctl -k -b -1

5.3 增大内核环形缓冲区大小

如果驱动日志量很大,默认缓冲区(一般4M)不够用,会被覆盖,可以在内核启动参数里添加:

复制代码
log_buf_len=16M

把缓冲区增大到16M,满足大日志量的调试需求。


6. 核心总结+面试必背考点

核心总结

  1. dmesg是读取内核环形日志缓冲区的工具,是驱动调试的第一神器,所有printk日志都在这里;
  2. printk有8个日志级别,数字越小优先级越高,只有高于控制台阈值的日志才会打印到终端;
  3. 驱动开发必背命令:dmesg -w(实时监控)、dmesg | grep 关键字dmesg -l errdmesg -C(清空重测);
  4. 驱动加载失败通用SOP:清空日志→重新加载→查看dmesg报错→定位问题→修复;
  5. 90%的新手驱动加载失败,都能通过dmesg精准定位,最常见的坑是没加GPL协议、内核版本不匹配、初始化函数返回非0。

面试必背考点

  1. printkprintf的核心区别是什么?
  2. printk的日志级别有哪些?分别代表什么含义?
  3. 为什么printk的日志在控制台看不到,只能用dmesg看到?
  4. 驱动模块加载失败,你的排查流程是什么?
  5. dmesg的日志存储在哪里?系统重启后还在吗?
  6. 什么是内核Oops?怎么通过dmesg排查Oops问题?
相关推荐
武超杰2 小时前
Nginx从入门到精通
运维·nginx
wdfk_prog2 小时前
正常关闭虚拟机时,不要点“关机”,而要点“关闭客户机”
linux·c语言·网络·ide·vscode
weixin_704266052 小时前
Nginx 反向代理 + 6 种负载均衡策略
运维·nginx
fish_xk3 小时前
Linux开方工具
linux·运维·服务器
中科三方5 小时前
输入域名后无法访问?教你快速区分域名解析问题与服务器问题
运维·服务器
internet Boy5 小时前
桌面运维面试常见问题及标准答案(完整版)
运维
用户2367829801685 小时前
Linux find 命令深度解析:从递归遍历到性能优化的完整实现
linux
ascarl20106 小时前
Linux.do 帖子整理:AI 调用 Chrome DevTools 调试前端页面
linux·前端·人工智能