5、内核与 Bootloader:Linux 内核在 Android 中的角色、Bootloader(U-Boot/LK)启动流程
这一章,我们来聊聊 Android 设备上电后,在系统界面出现之前,到底发生了什么。说白了,就是 Bootloader 和 Linux 内核的“交接仪式”。
很多做上层应用开发的同事,可能一辈子都不用碰这块。但如果你想定制 ROM,或者调试底层驱动,不理解这个启动链路,遇到问题就会一头雾水。我自己刚入行那会儿,就吃过这个亏——板子死活起不来,最后发现是 Bootloader 传参传错了。
5.1 Linux 内核在 Android 中的角色
先说说内核。Android 用的 Linux 内核,跟桌面 Linux 内核是同源的,但做了大量定制。我习惯把它理解为“硬件资源的大管家”。
- 进程管理:决定哪个 App 能跑,哪个得等着。
- 内存管理:给每个进程划分沙盒,防止互相踩踏。
- 驱动框架:屏幕、触摸、Wi-Fi、摄像头……所有硬件都得通过内核驱动来操作。
- Binder 驱动:这是 Android 独有的,用于进程间通信。没有它,App 之间没法互相调用。
- 电源管理:wake lock、suspend/resume,这些机制都在内核里。
你想想看,Android 系统里跑着那么多 App,如果每个 App 都能直接操作硬件,那不乱套了?内核就是那个“守门员”,所有硬件访问都得经过它。
核心要点:Android 的 Linux 内核,本质上是一个“增强版”的 Linux 内核。它加入了 Android 特有的驱动和机制,比如 Binder、ashmem(匿名共享内存)、ion(内存分配器)等。这些在主线 Linux 内核里是没有的。
我记得有一次,我在移植一个老版本的内核到新板子上,结果触摸屏怎么都不响应。查了两天,最后发现是内核配置里忘了打开多点触控协议。嗯,这种细节问题,真的会让人抓狂。
5.2 Bootloader 是什么?
Bootloader,直译就是“启动加载器”。它的任务很简单:初始化硬件,加载内核到内存,然后跳转过去执行。
在 Android 世界里,最常见的 Bootloader 有两个:
- U-Boot:通用 Bootloader,主要用于嵌入式 Linux 设备,功能强大,支持网络启动、文件系统加载等。
- LK (Little Kernel):高通平台常用的 Bootloader,轻量级,专为 Android 设计。
我个人更习惯用 U-Boot,因为它调试手段多。但 LK 在手机领域更普遍,毕竟高通芯片占了半壁江山。
5.3 Bootloader 启动流程
不管用哪个 Bootloader,核心流程都差不多。我画了一张图,帮你理清思路:
这张图展示了从按下电源键到内核启动的完整链路。下面我拆开来讲。
5.3.1 第一阶段:Boot ROM
芯片上电后,CPU 会从固定的物理地址开始执行代码。这个地址指向的是芯片内部固化的 Boot ROM。Boot ROM 很小,通常只有几十 KB。它的任务就是初始化最基本的硬件(比如时钟、DDR 控制器),然后从存储设备(eMMC、NAND Flash)中加载 Bootloader 的第一阶段代码到内部 SRAM 中。
为什么会这样设计?因为 DDR 内存还没初始化,CPU 只能用内部的小 SRAM。Boot ROM 就是那个“先遣队”,把大部队(Bootloader)请进来。
5.3.2 第二阶段:SPL / PBL
SPL(Secondary Program Loader)或 PBL(Primary Boot Loader)是 Bootloader 的第一阶段。它比 Boot ROM 大一些,但依然很小。它的主要工作是:
- 初始化 DDR 内存
- 从存储设备加载完整的 Bootloader(U-Boot 或 LK)到 DDR
- 跳转到 Bootloader 的入口点
我曾经在一个项目里,SPL 总是加载 U-Boot 失败。后来发现是 eMMC 的时序配置不对,导致读取数据出错。这种问题,没有示波器还真不好查。
5.3.3 第三阶段:U-Boot / LK
完整的 Bootloader 开始运行。这时候,我们有了完整的控制权。它会做以下几件事:
- 初始化更多外设:比如 USB、网络、显示控制器。
- 读取分区表:找到 boot 分区、system 分区等。
- 加载内核镜像:从 boot 分区读取 zImage/Image 和 dtb(设备树)到内存。
- 设置启动参数:比如内核命令行、内存大小、串口地址等。
- 跳转到内核:通过一条指令,把 CPU 控制权交给内核。
调试技巧:在 U-Boot 命令行下,你可以用 printenv 查看所有环境变量,用 bootm 手动启动内核。如果板子起不来,先在这里停住,检查内核加载地址是否正确。
5.4 内核启动参数
Bootloader 跳转到内核之前,会通过寄存器传递一些参数。最常见的是 ATAG(老方式)或 设备树(Device Tree)(新方式)。
设备树现在几乎是标配了。它用文本文件描述硬件信息,比如:
/dts-v1/;
/ {
model = "Qualcomm MSM8916";
compatible = "qcom,msm8916";
memory {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB 内存
};
chosen {
bootargs = "console=ttyMSM0,115200 root=/dev/mmcblk0p5";
};
};
内核启动时,会解析这个设备树,知道内存在哪里、串口用哪个、根文件系统在哪个分区。如果没有设备树,内核就不知道硬件长什么样,自然起不来。
避坑指南:我曾经遇到过一个问题——换了新版本的设备树,但 Bootloader 没更新,导致内核启动时崩溃。原因是设备树里引用的某个外设地址变了,但内核驱动还是按老地址去访问。所以,设备树和内核版本一定要匹配。
5.5 内核启动后的交接
当 Bootloader 把控制权交给内核后,内核会做以下事情:
- 解压自身:如果内核是压缩的(zImage),先解压。
- 初始化架构相关代码:设置页表、中断向量、MMU。
- 启动内核线程:创建 init 进程(PID 1)。
- 挂载根文件系统:通常是 ramdisk 或 system 分区。
- 执行 init 程序:init 会启动 Android 的各个服务,比如 Zygote、SurfaceFlinger。
从这一刻起,Android 系统才算真正“活”了过来。
5.6 小结
这一章我们聊了 Bootloader 和内核的启动流程。说白了,就是一条从硬件到软件的“接力赛”。Bootloader 是第一棒,内核是第二棒,然后才是 Android 框架。
理解这个流程,对调试启动问题特别有帮助。比如板子黑屏,你得先判断是 Bootloader 没跑完,还是内核崩溃了,还是 Android 服务没起来。每个阶段都有不同的调试手段。
我个人建议,如果你刚开始接触这块,可以先在 QEMU 或模拟器上跑一遍 U-Boot 和内核的启动流程。不用硬件,纯软件模拟,能帮你快速建立整体认知。
公众号:蓝海资料掘金营,微信deep3321