目录
- 什么是dmesg?大白话讲透本质
- printk日志级别全解(dmesg核心基础)
- 驱动开发必背的dmesg常用筛选命令
- 核心实战:用dmesg极速排查驱动加载失败(分场景)
- dmesg调试优化配置
- 核心总结+面试必背考点
1. 什么是dmesg?大白话讲透本质
dmesg = display message,是Linux内核环形日志缓冲区的专属查看工具,也是驱动开发的「第一调试神器」。
核心本质
你之前驱动里写的printk,不会打印到用户态的stdout标准输出 ,而是直接写入内核专属的环形日志缓冲区 ,dmesg就是用来读取、筛选、监控这个缓冲区的唯一工具。
大白话类比:
内核是一个封闭的工厂,驱动是工厂里的设备,
printk是设备的报警/日志播报,dmesg就是工厂的监控室,所有设备的运行状态、报错、异常,全都会在这里留下记录,出问题第一时间来这里查。
关键特性
- 环形缓冲区:固定大小,满了会自动覆盖最旧的日志,出问题要第一时间查看;
- 纯内核态日志:只记录内核、驱动、硬件相关的日志,和用户态应用日志完全隔离;
- 临时存储:默认存在内存中,系统重启后会清空,需要持久化要额外配置。
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级(EMERG到ERR)的日志会打印到控制台; - 你常用的
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 调试核心高频命令
- 实时监控日志(驱动调试第一常用)
bash
dmesg -w
# 等价于 tail -f,实时刷新内核日志
# 用法:开一个终端执行这个命令,另一个终端加载驱动,实时看到打印日志
- 按关键字精准筛选(排查故障核心)
bash
# 搜你的驱动名,精准过滤自己的驱动日志
dmesg | grep chr_dev
# 搜错误日志(忽略大小写)
dmesg | grep -i error
# 搜警告日志
dmesg | grep -i warn
# 多关键字联合筛选(同时搜错误、警告、你的驱动名)
dmesg | grep -E "error|warn|mychrdev"
- 按日志级别筛选
bash
# 只看错误及以上级别的致命日志
dmesg -l err,crit,alert,emerg
# 只看警告+错误
dmesg -l warn,err
# 只看调试级别的日志
dmesg -l debug
- 调试专用命令
bash
# 清空所有内核日志(调试前清空,只看本次操作的新日志,必用)
sudo dmesg -C
# 查看最近N行日志(加载驱动后第一时间执行,看最新报错)
dmesg | tail -30
# 把日志保存到文件,方便复盘/分享
dmesg > driver_debug.log
4. 核心实战:用dmesg极速排查驱动加载失败
这是本文的核心内容,整理了驱动开发新手99%会遇到的加载失败场景,给你一套通用排查SOP +分场景定位方案,看完就能解决绝大多数问题。
4.1 驱动加载失败通用排查SOP(必背)
不管什么报错,先按这个步骤走,10秒定位问题方向:
- 清空旧日志 :
sudo dmesg -C(排除旧日志干扰,只看本次操作的报错) - 重新执行加载 :
sudo insmod 你的驱动.ko - 查看最新报错 :
dmesg | tail -30或dmesg | grep -i error - 根据报错关键词匹配场景,对应解决
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 -
根因 :
- 驱动编译用的内核源码版本,和当前运行的内核版本不匹配;
- 系统开启了Secure Boot/内核锁定,禁止加载未签名的模块。
-
排查命令 :
bash# 查看当前运行内核版本 uname -r # 查看dmesg里的版本/签名报错 dmesg | grep -E "module layout|signature|lockdown" -
解决方案 :
- 必须在当前运行的系统环境下编译驱动,不要用其他系统/版本编译的ko文件;
- 调试阶段在BIOS中关闭Secure Boot,关闭内核锁定;
- 生产环境给驱动做签名,符合内核安全要求。
场景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) -
根因 :
- 最常见:没加
MODULE_LICENSE("GPL");,无法使用内核GPL协议导出的函数; - 调用的函数名拼写错误;
- 调用了内核没有导出的私有函数。
- 最常见:没加
-
排查命令 :
bash# 查看缺失的符号 dmesg | grep -i "Unknown symbol" # 确认内核是否导出了该函数(有T标记就是导出的全局函数) grep "class_create" /proc/kallsyms -
解决方案 :
- 驱动代码第一优先级检查:必须添加
MODULE_LICENSE("GPL");; - 核对函数名拼写,和内核API定义完全一致;
- 只能使用内核导出的函数,不能调用内核未导出的私有函数。
- 驱动代码第一优先级检查:必须添加
场景3:insmod执行无报错,但lsmod看不到模块,驱动没生效
-
现象 :insmod没有返回任何错误,但
lsmod | grep 驱动名找不到模块,/dev/下也没有生成设备文件 -
dmesg报错特征 :能看到你驱动里的错误打印,比如:
alloc dev num failed -
根因 :驱动的
module_init初始化函数执行失败,返回了非0值,内核会自动回滚,把刚加载的模块卸载掉。 -
排查命令 :
bash# 查看你的驱动初始化日志,定位哪一步失败了 dmesg | grep 你的驱动名 -
解决方案 :
- 给初始化函数的每一步资源申请都加错误判断+
KERN_ERR级别打印,精准定位失败步骤; - 常见失败原因:设备号被占用、GPIO/中断号冲突、内存分配失败、硬件未就绪;
- 修复失败逻辑,确保初始化成功时返回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开启状态下,禁止加载未签名的内核模块。
-
排查命令 :
bashdmesg | grep -i lockdown -
解决方案 :
- 调试阶段:重启进入BIOS,关闭Secure Boot;
- 生产环境:按照内核要求给驱动模块做签名。
场景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" -
解决方案 :
- 检查所有指针是否为NULL,内存分配后必须判断返回值;
- 核对
copy_to_user/copy_from_user的用法,必须用__user标记用户态缓冲区; - 检查中断上下文里是否有睡眠函数、耗时操作。
场景6:驱动加载成功,但dmesg里看不到printk日志
-
现象:驱动正常工作,但dmesg里找不到你写的printk日志
-
根因 :
- 日志级别太低,被内核过滤了(比如用了
KERN_DEBUG); - printk结尾没加
\n,行缓冲没刷新; - 日志被环形缓冲区的新内容覆盖了。
- 日志级别太低,被内核过滤了(比如用了
-
排查命令 :
bash# 查看所有级别日志,包括调试级 dmesg -l debug | grep 你的驱动名 -
解决方案 :
- printk日志结尾必须加
\n; - 调试阶段优先用
KERN_INFO级别,不要用KERN_DEBUG; - 调试前用
sudo dmesg -C清空日志,避免被覆盖。
- printk日志结尾必须加
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. 核心总结+面试必背考点
核心总结
dmesg是读取内核环形日志缓冲区的工具,是驱动调试的第一神器,所有printk日志都在这里;printk有8个日志级别,数字越小优先级越高,只有高于控制台阈值的日志才会打印到终端;- 驱动开发必背命令:
dmesg -w(实时监控)、dmesg | grep 关键字、dmesg -l err、dmesg -C(清空重测); - 驱动加载失败通用SOP:清空日志→重新加载→查看dmesg报错→定位问题→修复;
- 90%的新手驱动加载失败,都能通过dmesg精准定位,最常见的坑是没加GPL协议、内核版本不匹配、初始化函数返回非0。
面试必背考点
printk和printf的核心区别是什么?printk的日志级别有哪些?分别代表什么含义?- 为什么
printk的日志在控制台看不到,只能用dmesg看到? - 驱动模块加载失败,你的排查流程是什么?
- dmesg的日志存储在哪里?系统重启后还在吗?
- 什么是内核Oops?怎么通过dmesg排查Oops问题?