🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习
🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发
❄️作者主页:一个平凡而乐于分享的小比特的个人主页
✨收录专栏:Linux,本专栏目的在于,记录学习Linux操作系统的总结
欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

CPU上电启动到程序运行全流程详解
一、启动概览全景图
┌─────────────────────────────────────────────────────────────────────────────┐
│ CPU启动全流程 │
├─────────────────┬─────────────┬────────────┬─────────────┬─────────────┤
│ 上电瞬间 │ Boot阶段 │ UBoot阶段│ Linux内核 │ 用户空间 │
├─────────────────┼─────────────┼────────────┼─────────────┼─────────────┤
│ Boot引脚配置 │ ROM Code │ UBoot SPL │ 内核解压 │ systemd │
│ 启动介质选择 │ (片上固件) │ (第一阶段) │ 驱动初始化 │ 或init │
│ USB/自启动 │ │ UBoot │ 挂载根文件 │ 启动服务 │
│ │ │ (第二阶段) │ 系统 │ 用户程序 │
└─────────────────┴─────────────┴────────────┴─────────────┴─────────────┘
二、启动方式选择:Boot引脚配置
对比表格:不同CPU的启动差异
| 特性 | ARMv7 (imx6ull) | ARMv8 (imx8mp) | 类比场景 |
|---|---|---|---|
| ATF功能 | 不带ATF 直接启动Bootloader | 带ATF 安全启动框架 | 如大楼保安系统 : ARMv7:普通门锁 ARMv8:高级安保系统+指纹识别 |
| 启动流程 | 简单直接 | 多层安全验证 | |
| USB烧录模式 | BOOT_MODE[1:0]=10 | 类似,但通过ATF验证 | 如手机刷机 : 普通模式:正常开机 下载模式:刷入新系统 |
| 自启动模式 | BOOT_MODE[1:0]=00 | BOOT_MODE[1:0]=00 |
Boot引脚工作原理图
┌─────────────────────────────────────┐
│ BOOT引脚配置电路 │
├─────────────────────────────────────┤
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │引脚1│ │引脚2│ │引脚n│ ← PCB上的拨码开关或电阻 │
│ └─────┘ └─────┘ └─────┘ │
│ │ │ │ │
│ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐ │
│ │ 高/ │ │ 高/ │ │ 高/ │ │
│ │ 低电平│ │ 低电平│ │ 低电平│ │
│ └─────┘ └─────┘ └─────┘ │
│ │
│ ↓ 上电时CPU读取引脚状态 │
│ ↓ 确定启动介质和模式 │
└─────────────────────────────────────┘
三、第一阶段启动流程(芯片厂商固件)
详细流程示意图
┌─────────────────────────────────────────────────────┐
│ 第一阶段:ROM Code执行 │
├─────────────┬───────────────────────────────────────┤
│ 步骤 │ 功能说明 │
├─────────────┼───────────────────────────────────────┤
│ 1.上电复位 │ CPU从固定地址(0x00000000)开始执行 │
│ │ ↓ │
│ 2.读取Boot引脚│ 确定启动介质: │
│ │ • SD卡 → 读取第一个扇区 │
│ │ • eMMC → 读取boot分区 │
│ │ • NAND → 读取前几KB │
│ │ • USB → 等待主机连接 │
│ │ ↓ │
│ 3.加载SPL │ 从启动介质加载SPL(Secondary Program │
│ │ Loader)到内部SRAM │
│ │ ↓ │
│ 4.设置栈指针 │ 为C语言运行准备栈空间 │
│ │ ↓ │
│ 5.跳转到SPL │ 开始执行第二阶段引导 │
└─────────────┴───────────────────────────────────────┘
ATF与非ATF对比
不带ATF的ARMv7 (imx6ull):
┌─────────┐ ┌─────────┐ ┌─────────┐
│ ROM │────▶│ SPL │────▶│ UBoot │
│ Code │ │ (DDR初始化)│ │ │
└─────────┘ └─────────┘ └─────────┘
带ATF的ARMv8 (imx8mp):
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ ROM │────▶│ ATF │────▶│ SPL │────▶│ UBoot │
│ Code │ │(安全监控)│ │ │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
安全检查层 DDR初始化层 完整引导层
四、第二阶段:UBoot引导
UBoot两阶段架构图
┌─────────────────────────────────────────────────────────┐
│ UBoot两阶段架构 │
├──────────────┬──────────────────────────────────────────┤
│ 第一阶段(SPL) │ 第二阶段(完整UBoot) │
├──────────────┼──────────────────────────────────────────┤
│ 位置:内部SRAM │ 位置:外部DDR │
│ 大小:~64KB │ 大小:几百KB~几MB │
│ 功能: │ 功能: │
│ 1.初始化时钟 │ 1.初始化更多外设 │
│ 2.初始化DDR │ 2.检测内存映射 │
│ 3.初始化串口 │ 3.加载内核到内存 │
│ 4.加载第二阶段 │ 4.设置启动参数 │
│ 代码到DDR │ 5.启动内核 │
│ 5.跳转 │ │
└──────────────┴──────────────────────────────────────────┘
第二阶段详细任务表
| 步骤 | 具体操作 | 类比场景 |
|---|---|---|
| ① 初始化硬件 | • 初始化网络(Ethernet) • 初始化存储设备(SD/eMMC) • 初始化显示设备(可选) | 如装修房子:先通水电网络,再搬家具 |
| ② 检测内存映射 | 扫描DDR控制器,确定: • 内存起始地址 • 内存大小 • 内存区域划分 | 如仓库管理员:清点仓库分区和容量 |
| ③ 加载内核 | 从存储设备读取: uImage(压缩内核)或Image 到DDR的特定地址(如0x80800000) |
如搬家:从仓库(Flash)把家具(内核)搬到新家(DDR) |
| ④ 设置启动参数 | 准备bootargs: • 控制台参数console= • 根文件系统root= • 文件系统类型rootfstype= |
如旅行指南:告诉司机(内核)目的地和路线 |
| ⑤ 调用内核 | 跳转到内核入口地址,并传递: • 机器ID • ATAGs或DTB地址 • bootargs参数 | 如交接工作:把任务清单交给下一任负责人 |
五、内核启动参数详解
bootargs参数配置示例表
| 参数 | 示例值 | 说明 |
|---|---|---|
| console | console=ttymxc0,115200 |
指定控制台串口和波特率 |
| root | root=/dev/mmcblk1p2 root=/dev/nfs |
根文件系统位置: • MMC第1设备第2分区 • NFS网络文件系统 |
| rootfstype | rootfstype=ext4 rootfstype=squashfs |
文件系统类型 |
| ip | ip=192.168.1.100::::eth0:dhcp |
网络配置(网络根文件系统时) |
| init | init=/linuxrc init=/sbin/init |
指定init程序 |
| 其他 | rw earlyprintk mem=512M |
读写模式、早期打印、内存大小 |
两种常见启动方式对比
从本地存储启动:
┌─────────┐ ┌────────────┐ ┌────────────┐
│ UBoot │────▶│ Linux内核 │────▶│ 根文件系统 │
│ │ │ │ │ (eMMC/SD) │
└─────────┘ └────────────┘ └────────────┘
传递bootargs: mount /
root=/dev/mmcblk1p2
rootfstype=ext4
从网络启动(NFS):
┌─────────┐ ┌────────────┐ ┌────────────┐
│ UBoot │────▶│ Linux内核 │────▶│ NFS服务器 │
│ │ │ │ │ 根文件系统 │
└─────────┘ └────────────┘ └────────────┘
传递bootargs: mount -t nfs
root=/dev/nfs 192.168.1.10:/nfsroot
ip=192.168.1.100
六、Linux内核初始化
内核启动流程图
┌─────────────────────────────────────────────────────┐
│ Linux内核初始化阶段 │
├──────────────┬──────────────────────────────────────┤
│ 阶段 │ 主要操作 │
├──────────────┼──────────────────────────────────────┤
│ 1.解压内核 │ • 如果是zImage/uImage,先解压 │
│ │ • 如果是Image,直接加载 │
│ │ ↓ │
│ 2.早期初始化 │ • 初始化CPU和内存基本功能 │
│ │ • 建立初始内存页表 │
│ │ • 开启MMU(内存管理单元) │
│ │ ↓ │
│ 3.串口初始化 │ • 初始化控制台串口 │
│ │ • 启用printk输出 │
│ │ ↓ │
│ 4.驱动初始化 │ • 按顺序初始化所有驱动: │
│ │ 1) 平台设备驱动 │
│ │ 2) 子系统驱动 │
│ │ 3) 文件系统驱动 │
│ │ ↓ │
│ 5.挂载根文件系统│ • 根据root=参数找到根设备 │
│ │ • 挂载根文件系统 │
│ │ • 切换到根文件系统 │
│ │ ↓ │
│ 6.启动init进程│ • 执行init=指定的程序 │
│ │ • 通常是/sbin/init │
└──────────────┴──────────────────────────────────────┘
MMU启用前后对比
启用MMU前: 启用MMU后:
┌─────────────────┐ ┌─────────────────┐
│ 物理地址空间 │ │ 虚拟地址空间 │
│ (1:1映射) │ │ (页表映射) │
│ │ │ │
│ 0x80000000 ─────┼─▶物理内存 │ 0xC0000000 ─────┼─▶物理内存
│ 0x90000000 ─────┼─▶外设寄存器 │ 0xF0000000 ─────┼─▶外设寄存器
│ │ │ │
│ 直接访问硬件 │ │ 通过页表转换 │
│ 无内存保护 │ │ 有内存保护 │
└─────────────────┘ └─────────────────┘
七、用户空间初始化:init系统
SystemV init vs systemd对比
| 方面 | SystemV init (传统) | systemd (现代) | 类比 |
|---|---|---|---|
| 配置文件位置 | /etc/inittab /etc/init.d/ |
/etc/systemd/system/ /lib/systemd/system/ |
如食谱 : 旧:纸质菜谱本 新:电子菜单系统 |
| 配置文件格式 | Shell脚本 | .service单元文件 |
|
| 启动方式 | 顺序串行启动 | 并行启动 | 如餐厅开业 : 旧:一个一个准备工作 新:同时准备所有事项 |
| 服务管理 | service xxx start /etc/init.d/xxx start |
systemctl start xxx systemctl enable xxx |
|
| 日志管理 | 分散的日志文件 | journalctl统一管理 |
systemd服务文件示例
ini
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target # 在网络服务之后启动
[Service]
Type=simple
ExecStart=/usr/bin/myapp --daemon
Restart=on-failure # 失败时自动重启
User=appuser # 以指定用户运行
Group=appgroup
[Install]
WantedBy=multi-user.target # 在哪个运行级别启用
SystemV启动脚本示例
bash
#!/bin/bash
# /etc/init.d/myapp
case "$1" in
start)
echo "Starting myapp..."
/usr/bin/myapp --daemon
;;
stop)
echo "Stopping myapp..."
killall myapp
;;
restart)
$0 stop
sleep 1
$0 start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
八、完整启动时间线图
时间轴:0ms → 100ms → 200ms → 500ms → 1s → 3s → 完成
│ │ │ │ │ │
┌───────┼────────┼────────┼────────┼──────┼──────┼─────────────┐
│阶段 │ROM Code│ SPL │ UBoot │内核 │init │ 用户程序 │
│ │ 执行 │ 执行 │ 执行 │初始化│系统 │ │
├───────┼────────┼────────┼────────┼──────┼──────┼─────────────┤
│关键 │读取Boot│初始化 │加载 │挂载 │启动 │应用服务 │
│操作 │引脚 │DDR │内核 │根文件│服务 │运行 │
│ │加载SPL │跳转到 │传递参数│系统 │ │ │
│ │ │UBoot │ │ │ │ │
└───────┴────────┴────────┴────────┴──────┴──────┴─────────────┘
实际时间: 1-10ms 10-50ms 50-200ms 200-500ms 500ms-2s 2s+
九、常见启动问题排查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 无任何输出 | 1. Boot引脚配置错误 2. 电源问题 3. 晶振不起振 | 1. 检查Boot引脚电压 2. 测量各路电源 3. 示波器测时钟 |
| 停在ROM Code | 1. 启动介质无有效程序 2. 存储设备通信失败 | 1. 检查SPL是否烧写 2. 测量SD卡/eMMC时钟和数据线 |
| UBoot启动失败 | 1. DDR初始化失败 2. SPL加载UBoot出错 | 1. 检查DDR配置参数 2. 验证UBoot镜像CRC |
| 内核panic | 1. bootargs错误 2. 根文件系统错误 3. 驱动问题 | 1. 检查console参数 2. 验证根文件系统 3. 查看panic信息 |
| 无法挂载根文件系统 | 1. root=参数错误 2. 文件系统损坏 3. 驱动缺失 | 1. 检查设备节点 2. fsck检查文件系统 3. 确认内核包含对应驱动 |
十、总结:从硬件到软件的完整旅程
整个启动过程就像建造和启动一座现代化城市:
- 奠基阶段(Boot引脚+ROM Code):选址规划,确定城市类型和基础
- 基础设施建设(SPL):修建道路、铺设水电网络(DDR初始化)
- 城市框架搭建(UBoot):建立政府机构,制定法规(bootargs)
- 核心系统启动(Linux内核):启动警察、消防、医疗等核心服务(驱动)
- 民生服务启动(init):开设学校、商店、公交等公共服务(服务进程)
- 市民生活开始(用户程序):市民开始工作生活,城市正常运转
每个阶段都依赖前一阶段的正确完成,任何环节出错都会导致启动失败。理解这个完整流程,有助于在开发调试时快速定位问题所在。