做内核启动调试时,最容易犯的一个错就是:
屏幕一黑,串口一静,就下结论说“系统挂了”。
但实际情况经常不是这样。
很多时候,系统根本没死,只是日志链路没打通。内核还在跑,只是它没法把启动信息送到你眼前。
这也是为什么 console=、earlycon=、loglevel= 这几个参数看起来很小,却特别实用。把它们搞明白,很多“黑屏疑案”会简单很多。
一、为什么会出现“系统没死,只是没日志”
Linux 启动是分阶段进行的。
在最早期,CPU 刚进入内核,很多基础能力都还没完全准备好。这个时候即使内核代码已经开始执行,也不代表日志一定能正常输出。
常见现象是:
1. Bootloader 阶段串口有输出,一进内核就安静了。
2. 后面能看到登录提示,但前面的启动日志完全没有。
3. 板子其实还能 ping 通,但串口没任何信息。
4. 明明程序在跑,看起来却像“死机”。
问题的关键不是有没有 printk,而是当前有没有一条可用的日志输出通道。
没有通道,你就会误判。
二、console= 和 earlycon= 到底差在哪
这两个参数经常一起出现,但作用完全不同。
console= 是正式控制台。
比如:
它的意思是,等正式串口驱动起来之后,把日志输出到 ttyS0,波特率是 115200。
注意重点:等驱动起来之后。
所以只配 console=,通常只能看到“后半段”日志。真正最早那一段,你还是看不到。
earlycon= 是早期控制台。
比如:
或者:
earlycon=uart8250,mmio,0xfe660000
它的作用是,在正式控制台还没建立之前,先临时把早期日志打出来。
一句话总结:
earlycon= 解决“最开始能不能看到”
console= 解决“后面稳定输出到哪里”
这俩不是二选一,很多时候是要一起配的。
三、为什么建议两个一起用
最常见、也最稳的做法就是:
earlycon console=ttyS0,115200 loglevel=8
或者平台相关一点:
earlycon=uart8250,mmio,0xfe660000 console=ttyS0,115200 loglevel=8
启动过程可以理解成接力:
1. earlycon 先负责最早期输出
2. 正式串口驱动初始化
3. console= 指定的控制台接手
4. 整段日志尽量连续
如果你只写 console=,前面有一大段时间其实还是黑的。
四、串口名写错,系统可能是好的,但你就是看不到
这是特别常见的坑。
很多人以为 Bootloader 用的是某个串口,Linux 肯定也是同一个名字。其实不一定。
不同平台常见的串口名可能是:
ttyS0
ttyAMA0
ttymxc0
ttyMSM0
如果 console= 里的名字写错了,会发生什么?
很简单:系统可能正常启动了,但日志没打到你监听的那个口上。你这边一片安静,于是误以为内核挂了。
所以看到“没日志”,先别急着判断死机,先确认串口名对不对。
五、波特率不对,常见表现不是没日志,而是乱码
比如你串口工具开的是 115200,内核实际按别的波特率在发,那你看到的往往不是“完全没输出”,而是一堆乱码。
这时候很容易误判成内存踩坏、程序跑飞、异常打印。
实际上,问题可能只是串口参数没对上。
要确认的点很简单:
● 串口工具波特率是否正确
● Bootloader 和内核是否一致
● 当前监听的是不是对的物理串口
● 早期日志阶段本来就脆弱,参数不准,看到的东西就不可信。
六、MMIO 地址为什么会影响 early log
earlycon 经常会带一个 MMIO 地址:
earlycon=uart8250,mmio,0xfe660000
这个地址不是随便填的,它是 UART 控制器寄存器基地址。
因为 early console 工作得非常早,那时候很多完整驱动机制还没起来,所以它需要你直接告诉它:“去这个寄存器地址发字符。”
如果地址写错了,后果通常是:
1. 完全没日志
2. 日志乱码
3. 更糟时直接访问异常
所以 earlycon 这件事,最重要的不只是“写了没有”,而是“类型和地址对不对”。
七、loglevel= 控制的是日志多少,不是日志往哪打
这个点也很容易混。
loglevel= 的作用不是选输出设备,而是决定哪些级别的日志允许打印到控制台。
调试时常见写法:
有时还会搭配:
它们的作用是尽量把更多日志放出来,方便排查问题。
但要注意,如果 console 或 earlycon 根本没打通,那你把 loglevel 调再高也没用。
因为它只决定“打不打”,不决定“打到哪”。
所以这三个参数要分开理解:
● earlycon=:最早期能不能看见
● console=:正式日志往哪去
●loglevel=:日志放多少出来
八、怎么让启动过程更“可见”
如果你的目标是排障,而不是碰运气,建议这样配:
console=ttyS0,115200 earlycon=uart8250,mmio,0xfe660000 loglevel=8 ignore_loglevel
如果还想更强一点,可以临时加:
它很适合看内核卡在哪个初始化调用附近。
调试阶段的核心思路就一句话:
先让系统尽可能多说话,再去分析它说了什么。
九、一个非常实用的判断思路
启动日志最有价值的地方,不只是“有没有”,而是“断在哪”。
比如:
● 连 Linux version 都看不到:说明问题非常早
● 能看到 early log,后面没了:可能是 early console 结束后,正式 console 没接上
● 能看到大量驱动初始化:说明系统其实已经跑到很后面了
所以启动调试时,千万别一看到没输出就说“系统死了”。
更准确的问法应该是:
系统是真的停住了,还是只是从这里开始我看不见了?
这两个问题,差别非常大。
十、最容易踩的几个坑
1. console= 设备名写错
2. earlycon= 串口类型写错
3. MMIO 地址写错
4. 波特率不匹配
5. 只配 console= 不配 earlycon=
6. 日志一断就直接断言系统挂了
真正有经验的人,遇到启动黑屏时,第一反应通常不是“死机”,而是:
先查日志链路。
结尾
console=、earlycon=、loglevel= 这几个参数虽然小,但它们解决的是启动调试里最关键的一件事:可见性。
很多启动问题,不是代码没跑,而是你没看见它跑到哪了。
可以把它们记成三句话:
● earlycon= 把最早期照亮
● console= 把正式输出接稳
● loglevel= 把该看的日志尽量放出来
做内核 bring-up 的时候,最怕的不是报错太多,最怕的是什么都看不见。
启动调试的第一步,不是证明系统死了,而是先确认:它是不是只是没有机会开口说话。