1、启动流程概览:从按下电源键到控制台登录的完整旅程

各位同学,今天咱们聊聊嵌入式Linux的启动流程。说实话,我刚入行那会儿,觉得按下电源键到系统跑起来,不就是个黑盒吗?直到有一次,我调试一块板子,死活起不来,才逼着自己把整个流程啃了一遍。嗯,从那以后,我养成了一个习惯——每次拿到新板子,第一件事就是对着串口日志,把启动流程走一遍。

你想想看,一个嵌入式设备从冷启动到你能敲命令,中间经历了多少环节?我数了数,至少得经过这么几个大阶段:

核心启动阶段(按时间顺序):

  1. 上电复位 —— CPU从复位向量取第一条指令
  2. BootROM执行 —— 芯片内部固化的启动代码
  3. Bootloader加载 —— 比如U-Boot,负责初始化硬件、加载内核
  4. 内核解压与初始化 —— Linux内核开始接管系统
  5. 根文件系统挂载 —— 从存储介质挂载根目录
  6. init进程启动 —— 用户空间的第一个进程
  7. 系统服务与登录 —— 最终呈现控制台或图形界面

说白了,这就是一个接力赛。每一棒都有自己的任务,交接不好,系统就趴窝了。我个人习惯把整个流程画成一张图,这样脑子里有个全局观。来,我画个SVG流程图给你看看:

嵌入式Linux启动流程概览 ① 上电复位 CPU从0x0000取指令 ② BootROM 初始化时钟、DDR ③ Bootloader U-Boot加载内核 ④ 内核解压 自解压、重定位 ⑤ 内核初始化 start_kernel() ⑥ 挂载根文件系统 mount rootfs ⑦ init进程启动 /sbin/init → 控制台 硬件初始化阶段 内核启动阶段 用户空间阶段 时间 →

1.1 上电复位——一切从这里开始

按下电源键的那一刻,发生了什么?CPU的复位逻辑被触发,PC指针直接跳到一个固定的地址。对于ARM Cortex-A系列,通常是0x000000000xFFFF0000。这里存放的是芯片厂商固化的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的主要工作流程:

  1. 第一阶段(汇编):设置异常向量、初始化CPU模式、关闭MMU和Cache
  2. 第二阶段(C语言):初始化串口、时钟、DDR、Flash等外设
  3. 加载内核:从存储介质读取内核镜像到DDR指定地址
  4. 传递参数:通过设备树(DTB)或ATAG方式告诉内核硬件信息
  5. 跳转执行:修改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 Linuxlogin:,每一行都看看。时间长了,你一眼就能看出问题出在哪个阶段。这比任何调试工具都管用。

好了,这一章咱们把整个启动流程串了一遍。后面的章节,我会带你深入每一个环节,看看里面到底发生了什么。记住,理解启动流程,是嵌入式Linux开发的基石。


公众号:蓝海资料掘金营,微信deep3321