1、启动流程概览:从按下电源键到控制台登录的完整旅程
各位同学,今天咱们聊聊嵌入式Linux的启动流程。说实话,我刚入行那会儿,觉得按下电源键到系统跑起来,不就是个黑盒吗?直到有一次,我调试一块板子,死活起不来,才逼着自己把整个流程啃了一遍。嗯,从那以后,我养成了一个习惯——每次拿到新板子,第一件事就是对着串口日志,把启动流程走一遍。
你想想看,一个嵌入式设备从冷启动到你能敲命令,中间经历了多少环节?我数了数,至少得经过这么几个大阶段:
核心启动阶段(按时间顺序):
- 上电复位 —— CPU从复位向量取第一条指令
- BootROM执行 —— 芯片内部固化的启动代码
- Bootloader加载 —— 比如U-Boot,负责初始化硬件、加载内核
- 内核解压与初始化 —— Linux内核开始接管系统
- 根文件系统挂载 —— 从存储介质挂载根目录
- init进程启动 —— 用户空间的第一个进程
- 系统服务与登录 —— 最终呈现控制台或图形界面
说白了,这就是一个接力赛。每一棒都有自己的任务,交接不好,系统就趴窝了。我个人习惯把整个流程画成一张图,这样脑子里有个全局观。来,我画个SVG流程图给你看看:
1.1 上电复位——一切从这里开始
按下电源键的那一刻,发生了什么?CPU的复位逻辑被触发,PC指针直接跳到一个固定的地址。对于ARM Cortex-A系列,通常是0x00000000或0xFFFF0000。这里存放的是芯片厂商固化的BootROM代码。
我记得第一次看某款芯片的手册,发现复位向量居然可以重映射,当时还纳闷为什么要这么设计。后来做项目才明白——为了支持多种启动介质,比如NAND Flash、eMMC、SD卡。BootROM会去检测启动引脚的电平,决定从哪儿加载下一级代码。
💡 小技巧: 调试启动问题时,先确认复位引脚电平是否正常。我曾经被一个虚焊的复位电阻坑了整整两天,板子偶尔能起偶尔不能,查到最后才发现是接触不良。
1.2 BootROM——芯片的“第一口奶”
BootROM是固化在芯片内部的一段只读代码,用户改不了。它的任务很简单:
- 初始化最基本的硬件:关闭看门狗、设置系统时钟、初始化DDR控制器
- 从启动介质(SD卡、NAND、SPI Flash等)读取Bootloader到DDR中
- 跳转到Bootloader的入口地址
这里有个关键点:BootROM通常很小,也就几十KB。它不会做复杂的初始化,比如网卡、USB这些外设,都留给Bootloader去处理。你想想看,如果BootROM什么都管,那芯片得有多大?
1.3 Bootloader——硬件的“管家”
Bootloader,最常见的就是U-Boot。它是个小型的操作系统,负责把硬件环境准备好,然后加载Linux内核。
U-Boot的主要工作流程:
- 第一阶段(汇编):设置异常向量、初始化CPU模式、关闭MMU和Cache
- 第二阶段(C语言):初始化串口、时钟、DDR、Flash等外设
- 加载内核:从存储介质读取内核镜像到DDR指定地址
- 传递参数:通过设备树(DTB)或ATAG方式告诉内核硬件信息
- 跳转执行:修改PC指针,跳转到内核入口
⚠️ 避坑指南: 我曾经遇到过U-Boot加载内核后,内核启动到一半就挂了的案例。查了半天,发现是U-Boot传给内核的设备树地址不对。内核从那个地址读到的全是乱码,自然就崩溃了。所以,设备树地址一定要和U-Boot中配置的一致。
1.4 内核解压与初始化——真正的“主角”登场
内核镜像通常是压缩过的(zImage或Image.gz)。U-Boot跳转到的第一个内核代码,其实是解压程序。它会:
- 自解压内核到内存中的另一个位置
- 重定位,修正符号地址
- 跳转到真正的内核入口
start_kernel()
start_kernel()是内核初始化的C语言入口,它干的事情可多了:
| 初始化模块 | 主要工作 |
|---|---|
| trap_init() | 设置中断向量表 |
| mm_init() | 初始化内存管理子系统 |
| sched_init() | 初始化进程调度器 |
| init_IRQ() | 初始化中断控制器 |
| console_init() | 初始化控制台(串口输出) |
| vfs_caches_init() | 初始化虚拟文件系统缓存 |
说白了,内核初始化就是把所有子系统都“唤醒”,让它们准备好干活。这个过程会打印很多日志,也就是你常看到的[ 0.000000]开头的那些信息。
1.5 根文件系统挂载——从“裸机”到“有家”
内核初始化完成后,最后一步是挂载根文件系统。根文件系统里放着什么呢?/sbin/init、/bin/sh、/etc配置……没有它,内核就是个光杆司令。
挂载方式取决于你的启动参数:
- initramfs:内核自带的临时根文件系统,常用于早期启动
- NFS挂载:开发阶段常用,通过网络挂载根文件系统
- 块设备挂载:从eMMC、SD卡、SATA硬盘等挂载
⚠️ 注意: 如果内核找不到根文件系统,会打印Kernel panic - not syncing: VFS: Unable to mount root fs。这是最常见的启动失败原因之一。我见过有人把内核编译选项里的文件系统驱动漏了,结果ext4分区挂不上,折腾了一下午。
1.6 init进程启动——用户空间的“一号进程”
根文件系统挂载成功后,内核会启动第一个用户空间进程——/sbin/init(PID=1)。这个进程是所有其他进程的祖先。
init进程根据配置文件(比如/etc/inittab或systemd的unit文件)启动各种服务:
- 网络服务(dhcp、sshd)
- 日志服务(syslogd)
- 图形界面(X11或Wayland)
- 登录管理器(getty)
最终,你会看到一个登录提示符:
Welcome to Embedded Linux
myboard login: _
到这一步,整个启动流程就走完了。从按下电源键到看到这个登录提示,中间经历了成千上万行代码的执行。嗯,是不是觉得挺神奇的?
💡 个人经验: 我建议你每次拿到新板子,都把完整的串口启动日志保存下来。从Booting Linux到login:,每一行都看看。时间长了,你一眼就能看出问题出在哪个阶段。这比任何调试工具都管用。
好了,这一章咱们把整个启动流程串了一遍。后面的章节,我会带你深入每一个环节,看看里面到底发生了什么。记住,理解启动流程,是嵌入式Linux开发的基石。
公众号:蓝海资料掘金营,微信deep3321