RISC-V 64 Android/Linux Bringup 深挖:first stage / second stage 与 PID 1 之前发生了什么
| 字段 | 内容 |
|---|---|
| 文档类型 | 技术调研报告 |
| 版本 | v1.0 |
| 状态 | 已定稿 |
| 密级 | 内部 |
| 作者 | 技术调研助手 |
| 生成日期 | 2026-10-09 |
| 研究截点 | 2026-10-09 |
| 目标读者 | 从事 RISC-V 平台 bringup、Android/Linux 系统集成的工程师与架构师 |
| 调研问题 | ①bringup 为什么 bug 多;②first stage / second stage 指什么;③没有 init 进程时内核在做什么;④两个 stage 之间在做什么;⑤第一个 stage 之前在做什么 |
| 覆盖范围 | Linux 内核入口到 PID 1 的源码路径;Android init 三段机制(first_stage→selinux_setup→second_stage)与 AOSP 源码;initramfs 与 switch_root;AVB/dm-verity、SELinux 在启动路径上的作用;RISC-V 交接约定;Android RISC-V 公开现状 |
| 排除范围 | 芯片内部 ROM 实现与未公开 blob 的逆向细节;具体发行版/整机的兼容性矩阵;虚拟化与容器启动;性能调优 |
版本历史
| 版本 | 日期 | 变更说明 | 作者 |
|---|---|---|---|
| v1.0 | 2026-10-09 | 首版:内核入口到 PID 1 的源码级梳理、Android init 三段机制、bringup bug 根因矩阵 | 技术调研助手 |
术语与缩略语
| 术语 | 含义 |
|---|---|
| SPL / FSBL | Second Program Loader / First Stage Boot Loader,一级装载器;在 DRAM 就绪前的小体积引导代码,负责初始化 DDR 并载入下一级 |
| SBI / OpenSBI | RISC-V Supervisor Binary Interface 及其参考实现;M 态固件向上提供 TIME/IPI/RFENCE/HSM 等服务 |
| FDT / DT | Flattened Device Tree / Device Tree,描述硬件的扁平化数据;由前级经寄存器传给内核 |
| PMD | Page Middle Directory,rv64 下 2MB 对齐的镜像边界约束 |
| initcall | 内核 init 段的函数调用层级机制,按 pure→early→core→…→late 分层执行 |
| initramfs | 内核内嵌或外部加载的 cpio 归档内存文件系统,解包后其中的 /init 可作 PID 1 |
| switch_root | 从 initramfs 切换到真实根文件系统的动作(overmount / 并 exec 新 init) |
| PID 1 / kernel_init | 内核在 rest_init 阶段创建的第一个用户态进程;PID 2 为 kthreadd |
| first stage | 多义术语:①SPL(引导器语境);②Android init 第一段(first_stage);③initramfs 早期用户态(Linux 语境) |
| second stage | 多义术语:①U-Boot proper(引导器语境);②Android init 第三段(second_stage);③换根后的常驻 init |
| first_stage_init | AOSP init 的 first stage 实现文件,负责最挂载与分区挂载 |
| selinux_setup | Android init 的中间态子命令,只做 SELinux 策略装载与 enforcement 切换 |
| AVB | Android Verified Boot,启动镜像验证链,校验失败设备无法启动 |
| dm-verity | 分区运行时完整性校验,损坏时以 EIO 错误报告读取 |
| SELinux | 强制访问控制;Android 上策略在 init 中间态装载,之后所有操作受策略约束 |
| ueventd | 设备节点管理器:接收内核 uevent、按规则建 /dev 节点并设权限;coldboot 遍历 /sys 回放设备 |
| bootconfig / androidboot.* | Android 引入的键值参数机制,与 cmdline 一起由 bootloader 传给内核与 init |
| coldboot | init 中发起的一次性设备事件回放,确保服务启动前 /dev 就绪 |
| GKI | Generic Kernel Image,Android 通用内核镜像机制 |
| VTS / CTS | Vendor / Compatibility Test Suite,兼容性测试套件 |
可靠性标记
| 标记 | 含义 |
|---|---|
| 【C】 | 官方文档、规范或源码直接支持 |
| 【C*】 | 多份一手来源交叉一致 |
| 【S】 | 厂商、社区材料与第三方报道 |
| 【P】 | 由既有证据推导的合理判断 |
| 【?】 | 尚未确定,需后续验证 |
1. 核心判断(TL;DR)
- 【C】“没有 init 进程”的时刻并非空白:内核在 rest_init() 中先派生 kernel_init(即 PID 1 的内核线程形态)与 kthreadd(PID 2),跑步 initcall 各层、解包 initramfs、再 exec 用户态 init;引导 CPU 则直接进入 idle [S01][S02][S04]。
- 【C】“first stage / second stage”至少存在三种语境:引导器的 SPL→U-Boot proper、Android init 的 first_stage→selinux_setup→second_stage、Linux 用户态的 initramfs→switch_root→真根 init;混用这些名词是理解混乱的主要来源 [S08][S16][S20]。
- 【C】Android 两个 stage 之间夹着一个独立中间态 selinux_setup:它只做“装载 SELinux 策略并切换 enforcement”一件事,因为策略必须在任何受策略约束的动作(restorecon、服务启动)之前生效;三段之间用 execv 带着不同 argv 重入同一个 /system/bin/init 二进制 [S10][S16]。
- 【S】bringup bug 多的结构性原因:启动链长且层间只有少数寄存器/指针做交接、早期无日志、厂商 blob 与上游状态不齐、AVB/SELinux 等强制机制在启动路径上“失败即致命”,错误往往在数层之后才爆发 [S05][S07][S12][S13]。
- 【P】RISC-V 放大了上述问题:Android 官方尚未定义 riscv64 的 NDK ABI,社区验证主要依赖 Cuttlefish 虚拟设备与 QEMU 版本组合,真机 Android bringup 的公开样本远少于 arm64 [S13][S14][S15]。
2. 问题、范围与方法
本报告回答五个具体问题:第一,bringup 过程为什么容易出大量 bug;第二,first stage 与 second stage 到底指什么;第三,常被说成“没有 init 进程”的时段里,内核在做什么;第四,Android 两个 init 阶段中间在做什么;第五,Android 的第一个 init stage 之前,系统经历哪些阶段。
研究方法以源码与官方文档为第一证据:Linux 侧逐段阅读 torvalds/linux 的 init/main.c、include/linux/init.h、kernel/exit.c,并核对 kernel.org 的 initramfs 与 init 引导文档;Android 侧逐文件阅读 AOSP system/core/init 的 main.cpp、first_stage_init.cpp、selinux.cpp、init.cpp、README.md,并核对 source.android.com 的 Verified Boot 文档;引导链背景复用本工作区前一版 bringup 报告 [S20],国内生态事实交叉引用本工作区 Android 17 报告 [S19]。所有外部引用编号见“参考来源”台账,图注与正文均标注来源编号。
范围边界:只覆盖“固件交接→内核→用户态 init”的启动路径,不深入芯片 ROM 实现、不评估具体商业固件质量;文中出现的错误字符串均来自对应版本源码或官方文档,用于定位参考而非穷举。
3. 全球技术基线
Linux 内核侧的主线在源码层面非常清晰。RISC-V 平台下前级通过寄存器完成交接:a0 为 hartid、a1 为 FDT 物理地址、satp=0 表示 MMU 关闭,rv64 镜像须按 PMD(2MB)边界对齐,EFI stub 场景允许物理重定位 [S07]。进入 _start 后依次执行 start_kernel() 的架构与基础设施初始化;随后 rest_init() 用 kernel_clone 派生 kernel_init(PID 1)与 kthreadd(PID 2),而引导 CPU 经 cpu_startup_entry(CPUHP_ONLINE) 沉入 idle——这正是“还没有 init 进程”时系统的真实形态 [S01]。kernel_init 先在内核态完成 kernel_init_freeable():do_pre_smp_initcalls、smp_init、do_basic_setup(其内部 do_initcalls 逐层执行 initcall)、wait_for_initramfs 与 console_on_rootfs 等 [S01];initcall 分层为 pure、early、core、postcore、arch、subsys、fs、rootfs、device、late(另有特殊 console 层),同层内顺序由链接顺序决定,“early initcalls run before initializing SMP” [S02]。之后才尝试用户态:优先 ramdisk_execute_command(默认 /init,可被 rdinit= 覆盖),其次 execute_command(init=),再回退 /sbin/init、/etc/init、/bin/init、/bin/sh,全部失败则以 “No working init found” panic,内核文档给出了成套排查手段(debug 参数、builtin 驱动核对、console= 更换、readelf -d 查依赖、先用 /bin/sh 验证)[S01][S04][S05]。initramfs 本身是内核内嵌的 gzip cpio 归档,解包进 rootfs 后若发现 /init 即作为 PID 1 执行;没有 /init 则回落到旧路径去寻找并挂载真实根分区;从 initramfs 切到真实根由 switch_root 完成 [S04]。systemd 官方把整体启动归纳为四阶段:固件、boot loader、内核(可带 initrd)、system manager,其中换根由 initrd-switch-root.service 执行 [S06]。PID 1 还有一条特殊保护:init 若退出,do_exit 直接 panic(Attempted to kill init!),因为 PID 1 承担孤儿进程收养职责 [S03]。
Android 侧则在同一条内核路径之上叠加了三段式用户态 init。AOSP init 的 main.cpp 先按 argv[1] 分发:无参数默认进入 FirstStageMain,selinux_setup 进入 SetupSelinux,second_stage 进入 SecondStageMain [S08]。first stage(first_stage_init.cpp)只搭建最小可运行环境并挂载系统分区:umask(0)、挂 tmpfs /dev(MS_NOSUID)、devpts、/proc(hidepid=2)、/sys,创建 kmsg/random/null 节点,建立 /mnt、/debug_ramdisk、/second_stage_resources 等 staging 挂载,读取 /proc/cmdline 与 /proc/bootconfig 解析 androidboot.* 参数,FirstStageMount 执行 AVB/dm-verity 校验并挂载必需分区(失败直接 LOG(FATAL) “Failed to mount required partitions early”),随后 LoadKernelModules、FreeRamdisk,最后把 stdout/stderr 重定向到 /dev/kmsg 并 execv 自身带参 “selinux_setup” [S09][S12]。中间态(selinux.cpp)只做策略:LoadSelinuxPolicyAndroid 装载策略、SelinuxSetEnforcement 决定 enforce/permissive(androidboot.selinux=permissive 可降级,用于区分“策略问题”与“普通 bug”)、restorecon /system/bin/init、SetupOverlays,然后 execv 自身带参 “second_stage” [S10][S16]。second stage(init.cpp)打日志 “init second stage started!”,建立属性服务、触发 ueventd 冷启动、解析 init.rc 并启动 zygote 与核心服务,部分触发条件等待 ueventd coldboot 完成 [S11][S16][S17]。AOSP 官方 README 对这套名词的定义是:“First stage init is responsible for setting up the bare minimum requirements to load the rest of the system”,随后“execs /system/bin/init again with the second_stage argument”,再“continues the boot process via the init.rc scripts” [S16]。生态层面:谷歌 2023-10-30 官方博客确认“we have begun to mature support for RISC-V in Android”,并给出 Cuttlefish 虚拟设备路径与 NDK ABI 定型预告 [S15];google/android-riscv64 仓库自我定位为“solely for issue tracking/discussion/documentation purposes”,构建目标为 aosp_cf_riscv64_phone-trunk_staging-userdebug,2025Q2 起用 Cuttlefish 跑 ART 至首屏,要求 QEMU 至少 8.1(9.0 含 V 扩展重要修复),并明确“We have not defined the Android NDK ABI for riscv64 yet” [S13];DeepComputing 在 FOSDEM 2026 复盘中强调真机启动进展与 upstream-first 策略 [S14]。综上,全局基线可概括为:内核与 initramfs 机制三十年成熟且文档完备,Android init 三段机制设计清晰但依赖安全链与参数链正确,RISC-V 侧的共同变量是“上游化程度”与“真机可用样本”仍在演进中。
4. 国内发展、差距与机会
国内 RISC-V 的 Android 与 Linux bringup 生态以“上游优先”为主线。公开资料显示,阿里巴巴达摩院/玄铁团队自 2022 年起持续推进 AOSP RISC-V 上游化,配合 RISC-V International sig-android 邮件列表开展社区协作;GitHub 的 XUANTIE-RV/riscv-aosp 仓库提供面向玄铁平台的构建脚本与补丁集,其 README 明确目标是向上游贡献补丁而非长期维护独立分支 [S19]。DeepComputing 在 FOSDEM 2026 的复盘中展示了在真实 RISC-V 硬件上启动 Android 的进展,并强调 Cuttlefish 虚拟设备仍是当前最稳定的开发验证环境,这与 AOSP 官方口径一致 [S14][S13]。平台侧(进迭时空 K1、赛昉 JH7110、算能 SG2042 等)的 Linux 主线化程度明显高于 Android:Linux 发行版可依赖主线内核与 U-Boot/OpenSBI 的完整组合,而 Android 受 GKI 移除 riscv64 影响,厂商需要基于 Android Common Kernel 自维护 defconfig、模块与 OTA 签名链,ART 后端(向量 regalloc、intrinsics)的公开阻塞项会直接压低头层性能 [S19]。差距体现在三个层面:其一,公开的“真机 Android bringup 全流程”样本少,多数团队可复用的是 Cuttlefish 与 QEMU 经验;其二,调试工具链与日志预算缺乏国内厂商的公开沉淀(如早期 console 方案、AVB 诊断流程);其三,兼容性验证(CTS/VTS 子集)尚无公开的 RISC-V 结果基线 [S13][S14][S19]。机会同样清晰:内核维护与 BSP 绑定是厂商差异化交付的抓手;ART 与 LLVM 后端的投入可以直接反馈上游并换取话语权;把 QEMU/Cuttlefish 的“基线对照法”固化成内部 bringup 流程,可以把大量低级错误挡在真机之前;selinux_setup、initcall 分层、AVB 校验链这些机制在 arm64 上有大量文档与经验,国产平台团队以“机制等价、参数与描述适配”的思路迁移,可比从零摸索节省数周到数月的周期 [S05][S16][S19]。
5. 关键机制深挖
5.1 内核早段:从入口到 PID 1——“没有 init 进程”时内核在做什么
前级交出控制权时,内核拿到的全部“世界描述”只有 a0(hartid)、a1(FDT 指针)与一块符合 PMD 对齐约束的镜像;这是整个启动链中交接面最窄、错误传播最远的一段 [S07]。_start 进入 start_kernel() 后完成架构探测、分页与常量映射、命令行解析与早期 console 建立;rest_init() 随即用 kernel_clone 派生 kernel_init(PID 1)与 kthreadd(PID 2),引导 CPU 自身经 cpu_startup_entry(CPUHP_ONLINE) 沉入 idle——从此刻起到 exec 用户态之前,系统里“没有用户态 init 进程”的答案是:PID 1 还是内核线程,正忙于跑完内核剩余初始化 [S01]。kernel_init_freeable() 的顺序包含 do_pre_smp_initcalls、smp_init、do_basic_setup(其中 do_initcalls 执行全部 initcall)、wait_for_initramfs 与 console_on_rootfs 等步骤 [S01];initcall 按 pure、early、core、postcore、arch、subsys、fs、rootfs、device、late 分层,另有 console 特殊层,同层顺序取决于链接顺序,early initcalls 在 SMP 初始化之前运行 [S02]。初始化完成后 kernel_init 尝试用户态:ramdisk_execute_command(默认 “/init”,rdinit= 覆盖)→ execute_command(init=)→ 回退链 /sbin/init、/etc/init、/bin/init、/bin/sh,全部失败则 panic “No working init found”;官方文档专设章节指导排查(debug 参数、确认存储与文件系统 builtin、换 console= 设备、readelf -d 查依赖、先验证 /bin/sh 这类非脚本二进制)[S01][S04][S05]。initramfs 是内核内嵌的 gzip cpio 归档:解包进 rootfs 后若有 /init 即以 PID 1 执行,rdinit=/bin/sh 是常用调试入口,无 /init 时回落寻找真实根分区,换根由 switch_root 完成 [S04]。PID 1 本身受特殊保护:一旦退出,do_exit 直接 panic(Attempted to kill init!),原因是它还负责孤儿进程收养,内核用这条 panic 换取可用的 coredump [S03]。整条主链机制见图 1。
接口与实现依据:本段所有行为均来自 Linux 主线源码 [S01][S02][S03] 与内核文档 [S04][S05];RISC-V 交接约定出自官方 arch/riscv 引导文档(a0/a1/satp 与 PMD 对齐属 ABI 级约束)[S07]。失效边界:console 建立之前的所有故障表现为“静默挂起”;FDT 指针或对齐错误会让启动在最早的异常处丢失上下文;init 缺失或不可执行则以固定 panic 字符串收场 [S01][S05][S07]。验证条件:QEMU virt 上先验证自带基线,再用 rdinit=/bin/sh 复现“无 init”场景、以 initcall_debug 与 debug 参数观察各层 initcall 时序 [S05]。成本与权衡:initcall 分层把依赖正确性转为链接顺序约束,代价是改动链接布局可能悄然改变启动时序;wait_for_initramfs 用启动延迟换取 initramfs 解包的确定性,两者都是“正确性优先”的取舍 [S01][S02]。
图 1:RISC-V 64 内核从入口到 PID 1 的主链与三处机制注记。来源:依据 Linux 内核源码 [S01][S02][S03] 与内核文档 [S04][S05][S06] 整理绘制;数据口径为 2026-10 主线源码与文档快照。
5.2 init 的三种“first stage / second stage”语境与 Android 三段机制
同一个词在不同栈层含义不同,这是理解混乱的根源。语境一(引导器):SPL/FSBL 是第一级装载(DRAM 就绪前运行),U-Boot proper 是第二级;OpenSBI 作为 M 态固件夹在两者与内核之间,向上提供 SBI 服务,多核有序启动依赖 HSM 扩展 [S07][S20]。语境二(Android init):AOSP init 是同一二进制 /system/bin/init 的三段接力——main.cpp 按 argv[1] 分发 selinux_setup 与 second_stage,缺省进入 FirstStageMain [S08]。first stage 只搭“最小可运行环境”:挂 tmpfs /dev、devpts、/proc、/sys,建设备节点,建立 staging 挂载,解析 /proc/cmdline 与 /proc/bootconfig,FirstStageMount 做 AVB/dm-verity 校验并挂载必需分区(失败 LOG(FATAL)),加载内核模块后 FreeRamdisk,再 execv 自身带参“selinux_setup” [S09][S12][S16]。AOSP README 的官方定义是“First stage init is responsible for setting up the bare minimum requirements to load the rest of the system”,随后“execs /system/bin/init again with the second_stage argument”,“continues the boot process via the init.rc scripts” [S16]。语境三(Linux 用户态):initramfs 内的 /init 是早期用户态,switch_root 换根后 exec 真实根的常驻 init(如 systemd),systemd 官方把这段描述为“initrd 向宿主 system manager 移交” [S04][S06]。三种语境的对照与 Android 三段细节见图 2。回到用户问题的前半句:“第一个 stage 之前”在 Android 语境下即固件与引导器段——BootROM 选介质、SPL 初始化 DDR、OpenSBI 接管 M 态、U-Boot 准备 a0/a1 并跳入内核 [S07][S20]。
接口与实现依据:三段接力依赖 execv 重入同一二进制与 argv 约定(selinux_setup、second_stage 为进程内协议字符串),参数来自 /proc/cmdline 与 /proc/bootconfig 的 androidboot.* 键值(ABI 级契约)[S08][S09][S10][S18]。失效边界:first stage 挂载失败即 LOG(FATAL) 终止;分区描述错误、AVB 元数据不匹配会让设备无法启动;second stage 的服务问题则表现为启动卡住或循环重启 [S09][S11][S12]。验证条件:在 Cuttlefish 上按官方目标构建(aosp_cf_riscv64_phone-trunk_staging-userdebug)验证三段日志次序;真机上以 /proc/bootconfig 与 init 日志核对参数与阶段 [S13][S16]。成本与权衡:三段式以两次 execv 与状态重建为代价换来清晰的边界——first stage 保持极简以尽快挂载分区,selinux_setup 把安全切换隔离成单职责步骤,second stage 才在完整约束下起服务 [S09][S10][S16]。
图 2:Android init 三段接力(first stage → selinux_setup → second stage)与 Linux initramfs 对照链,附“first/second stage”三种语境澄清。来源:依据 AOSP 源码与官方 README [S08][S09][S10][S11][S16] 整理绘制;数据口径为 AOSP main 分支 2026-10 快照。
5.3 两个 stage 之间:selinux_setup 中间态为什么单独存在
Android 三段机制中最容易被忽略的是夹在中间的 selinux_setup:它既不是“挂载阶段”,也不是“服务阶段”,而是一个只做安全切换的短命进程 [S10][S16]。其内容按源码为五步:LoadSelinuxPolicyAndroid 装载策略文件、SelinuxSetEnforcement 决定 enforcing/permissive(permissive 判定来自内核命令行 androidboot.selinux=permissive 或属性状态)、对 /system/bin/init 执行 restorecon 确保自身执行上下文正确、SetupOverlays 处理 system 分区叠加、最后 execv 自身带参“second_stage” [S10]。它必须独立成段的原因有二:其一,策略装载需要有 /dev、/proc、/sys 与已挂载的 system/vendor 分区作前提,而这正是 first stage 完成的工作;其二,策略生效必须早于一切受策略约束的动作——restorecon、服务创建、属性写入都要在 enforcing 之下执行,若把策略装载塞进 first stage 会与“最小环境”目标冲突,塞进 second stage 则服务启动已先于策略 [S10][S16]。AOSP README 对这一中间态的描述是系统进入“SELinux is optionally compiled and loaded”的状态后再进入主阶段 [S16]。排障含义:该阶段失败的错误特征非常明确——“Unable to open SELinux policy”“Failed to read policy file” 等 LOG(FATAL) 字符串直接指向策略路径问题;androidboot.selinux=permissive 是区分“策略配置问题”与“普通功能 bug”的最快开关 [S10]。
接口与实现依据:本机制的接口面是 init 二进制的子命令协议与环境(argv 字符串、/proc/cmdline、属性服务),实现依据为 selinux.cpp 与官方 README 的对应段落 [S10][S16]。失效边界:策略文件缺失/损坏、restorecon 或 overlay 失败都会让进程在进入 second stage 之前终止,设备停在黑屏或 bootloader 循环;permissive 模式只改变拦截行为,不修复根因 [S10][S12]。验证条件:以 androidboot.selinux=permissive 启动对比观察是否越过该阶段,再以 enforcing 复现并记录 AVC 拒绝日志定位策略缺口 [S10]。成本与权衡:单独中间态增加了进程重建与 execv 次数(启动开销很小),换取的是“安全边界与挂载边界”解耦,这一取舍在 bringup 排障时收益显著 [S10][S16]。
5.4 为什么 bringup bug 多:五类结构性根因
把上面机制合起来看,bringup bug 多不是偶然,而是结构决定的,可归纳为五类根因。第一类,交接面窄而错误延迟爆发:固件→内核只有 a0/a1/satp 三个寄存器的约定,FDT 少一个节点或对齐错位,可能到某个驱动 probe 或挂载分区时才爆 [S07]。第二类,早期无日志:console 建立之前一切故障都是静默挂起,initcall 层级顺序问题也常在特定组合下才显现 [S01][S02][S05]。第三类,描述与实现漂移:FDT、defconfig、bootconfig 与驱动/固件的版本矩阵庞大,内核文档把“驱动 builtin 与否”“console= 设备”列为首要排查项正是这个原因 [S05][S07]。第四类,时序与并发:同层 initcall 由链接顺序决定、SMP 在部分 initcall 之后才启动、ueventd 冷启动与服务启动存在等待关系,任何一处假设错位都会变成偶发卡死 [S02][S16]。第五类,强制安全链“失败即致命”:AVB 校验失败设备无法启动、dm-verity 损坏以 EIO 报告、SELinux 策略缺失直接 LOG(FATAL),这些机制不提供降级路径,bringup 阶段未配好的安全元数据会直接表现为“起不来” [S10][S12]。再叠加 RISC-V 侧的特有变量:Android 尚未定义 riscv64 NDK ABI、GKI 不再提供 riscv64 支持需要厂商自维护内核、DDR 训练等厂商 blob 不透明、SBI 扩展与 PMD 对齐约束 [S07][S13][S19]。五类根因与阶段的对应排查手段见图 3。
接口与实现依据:五类根因均以源码与官方文档为据——交接约定与对齐出自 RISC-V 引导文档,initcall 顺序出自内核源码注释,安全链行为出自 Verified Boot 文档与 selinux.cpp,参数链出自 init 与 bootconfig 文档 [S02][S05][S07][S10][S12][S18]。失效边界:该分类用于定位“最早可能引入错误”的阶段,但同一现象(如挂载失败)可能由上游多层分别导致,分类不能替代逐层验证 [S05][S09]。验证条件:对任一 bug 按图 3 从现象出现的最早阶段入手,先用 QEMU/Cuttlefish 基线对照复现,再逐层替换变量(镜像、FDT、参数、策略)收敛 [S13][S05]。成本与权衡:逐层验证比“直改代码试错”单轮更慢,但能把跨层错误收敛为单层问题,总体成本更低;反之在无日志的早期阶段盲目试错,代价是大量无效真机烧写 [S05][S13]。
图 3:bringup bug 根因 × 阶段排查矩阵(行=阶段,列=现象/定位/根因)。来源:综合本报告来源台账 [S01][S05][S07][S09][S10][S11][S14] 与公开社区实践整理;数据口径为源码与官方文档 2026-10 快照。
能力成熟度对比矩阵
| 阶段 | 调试可见性 | 典型失败模式 | 工具与文档成熟度 |
|---|---|---|---|
| 固件 / BootROM / SPL(含 DDR 训练) | 低:常无可用串口输出 | 上电静默、DDR 初始化失败、启动介质选错 | 厂商闭源工具链为主,公开文档少 [S14] |
| 引导器 / OpenSBI / 内核交接 | 中:引导器 console 可用 | 镜像装载失败、单核起跑、hart 卡住 | OpenSBI/U-Boot 上游文档完备,SBI 可 probe [S07][S20] |
| 内核早段(→initcalls) | 低→中:earlycon 可用后有改善 | 静默挂起、initcall 时序异常、DT 缺节点 | 内核文档与 debug 参数成套 [S01][S02][S05] |
| initramfs / 根切换 | 高:shell 可进 | panic “No working init found”、依赖库缺失 | 机制文档详实,rdinit=/bin/sh 复现方便 [S04][S05] |
| Android first stage | 中高:/dev/kmsg 可读 | 分区挂载 LOG(FATAL)、AVB/dm-verity 失败 | AOSP 源码可读,AVB 工具链成熟 [S09][S12] |
| SELinux 中间态 | 中:kmsg 与 permissive 开关 | 策略装载 FATAL、restorecon/overlay 失败 | AOSP 源码 + 官方 README 描述清楚 [S10][S16] |
| Android second stage | 高:init/ueventd/属性日志完整 | 服务循环重启、SELinux 拒绝、coldboot 竞态 | 日志丰富,但相对第二手文档偏少 [S11][S16][S17] |
该矩阵的接口依据为各阶段公开接口(console、earlycon、/dev/kmsg、permissive 开关、属性 ctl.)与 AOSP/内核源码 [S01][S10][S16];失效边界体现为“可见性越低、越接近硬件”的单调关系,固件段与内核早段的排障成本最高 [S05][S14];验证条件即表中“工具/手段”列的可执行动作,可在 QEMU 基线中逐一复现 [S05][S13];成本考量是:把调试资源优先投向可见性最低的两段(固件、内核早段)的一次性工具建设(串口、earlycon、kmsg 收集),能换取后续所有阶段的排障速度提升 [S05]。
6. 演进方向与情景
短期(6–12 个月)情景:Android on RISC-V 的主战场仍是虚拟设备与少数真机样板。google/android-riscv64 以 issue 跟踪方式推进,Cuttlefish 上 ART 已能进首屏,QEMU 版本(8.1 基线、9.0 的 V 扩展修复、9.2 的提速)构成实际门槛;NDK ABI 一旦定型并出现在公开 CI,应用生态才有规模化前提 [S13][S15]。同期 Linux 侧(发行版+主线内核+U-Boot/OpenSBI)的 bringup 会继续最成熟,多数教训可平移 [S20]。中期(12–24 个月)情景:国内厂商以“Android Common Kernel 自维护 + 厂商 BSP + 上游补丁”模式交付,bringup 的核心难点从“能不能起”转向“CTS/VTS 子集与安全链验收”;ART 后端阻塞项(向量 regalloc、intrinsics)逐步被上游或厂商补丁覆盖 [S19]。长期情景:若 Android 官方恢复 GKI 对 riscv64 的支持并给出 ABI 承诺,国内平台可直接复用标准内核链,把投入集中到差异化硬件(NPU、安全)与体验优化;否则“自维护内核 + 上游化补丁”将长期并行,bringup 团队需要把内核维护能力当作核心资产 [S13][S19]。可观察指标:android-riscv64 仓库 issue 关闭节奏、Cuttlefish 镜像的 ART/服务覆盖变化、NDK 公开包中 riscv64 的发行状态、国内平台公开的 Android 真机演示与测试结果 [S13][S14]。
7. 建议与验收
面向 bringup 团队的建议分四组。第一组,建立分层定位基线:任何功能引入前先在 QEMU virt 与 Cuttlefish(若为 Android)跑通“官方镜像基线”,再逐层替换为自研组件(bootloader、内核、DT、分区表),每一步都记录可启动状态,把“哪一层引入首次失败”变成可查询事实 [S05][S13]。第二组,日志预算前置:串口与 earlycon 方案在原理样机阶段就要可用,Android 侧确保 kmsg 可读;把“静默挂起”视为一级故障并立即补日志能力,而不是反复烧写试错 [S05][S09]。第三组,参数与描述清单化:a0/a1/satp 约定、PMD 对齐、androidboot.* 键值、AVB/dm-verity 元数据、fstab 与分区表全部进入版本化清单,变更走评审;对 SELinux 用 permissive 快速二分“策略问题 vs 功能问题”,但交付前必须回到 enforcing [S07][S10][S12][S18]。第四组,验收按阶段设门:固件与引导器段验收“交接后可进内核并打印早期 console”;内核段验收“initcall 完整、initramfs 可交互”;Android 段验收“三段日志次序完整、zygote 起、关键服务稳定”;最后以 CTS/VTS 子集与一次冷启动/热重启压力作为放行条件 [S05][S11][S13][S16]。
建议—验收矩阵
| 建议 | 验收条件 | 对应证据 |
|---|---|---|
| QEMU/Cuttlefish 基线对照先行 | 官方基线可重复启动;替换自研组件后“首次失败层”可被明确指认 | QEMU 与 Cuttlefish 官方路径 [S13][S20] |
| 日志预算前置(串口/earlycon/kmsg) | 从上电到 init 之间任意故障均有可读输出,无“静默挂起”盲区 | 内核 debug 文档与 first stage 日志机制 [S05][S09] |
| 参数与描述清单化(a0/a1、DT、androidboot.*、AVB) | 清单与镜像一一对应;任一变更可在清单中回溯并复现启动 | RISC-V 引导文档、bootconfig 与 Verified Boot 文档 [S07][S12][S18] |
| SELinux 二分法(permissive 定位) | 策略问题可在一次启动内与功能问题分离;交付镜像回到 enforcing | selinux.cpp 与 README [S10][S16] |
| 分阶段放行门 | 每阶段最小可证明目标达成后才进入下一段;三段日志次序完整 | init 三段机制与日志字符串 [S08][S11][S16] |
8. 不确定性与研究限制
第一,AOSP main 分支的 init 目录仍在演进:second_stage.cpp 已从当前分支消失,第二阶段的实现入口分散在 init.cpp 与相关文件,本报告对 second stage 步骤的编号以当前可读源码为准,未来重构可能改变文件布局 [S08][S11]。第二,部分外部页面的语言版本与快照时间不一(如 Verified Boot 文档抓取到的是非英语版本),要点虽然一致,但引用时以机制描述为准 [S12]。第三,Android RISC-V 支持状态变化快:NDK ABI 未定义、GKI 状态、Cuttlefish 目标名均可能在下一次发布变化,本报告截点为 2026-10 [S13][S15]。第四,国内厂商进展透明度有限,本报告仅基于公开仓库、会议资料与本工作区前序报告交叉验证,未包含任何非公开信息 [S14][S19]。第五,本报告未做真机复现实验,所有错误字符串与阶段顺序均取自源码与官方文档;读者在具体平台上的现象可能被厂商定制(额外分区、特殊 boot mode)叠加 [S05][S09]。
主张—证据矩阵
| 主张 | 证据 | 置信度与反例风险 |
|---|---|---|
| “没有 init 进程”时内核仍以 PID 1 内核线程跑完 initcall、解包 initramfs 后才 exec 用户态 init | init/main.c 的 rest_init、kernel_init 与 kernel_init_freeable 序列 [S01];initcall 分层注释 [S02] | 高;不同内核版本函数细节会调整,但阶段结构稳定 |
| Android 两个 stage 之间的中间态是 selinux_setup,职责单一 | selinux.cpp 的 SetupSelinux 五步与 AOSP README 表述 [S10][S16] | 高;未来若策略装载前置到 first stage,需重读源码 |
| “first/second stage”存在三种语境且混用导致理解偏差 | 引导器、Android init、Linux 用户态三套来源并列 [S07][S16][S20] | 高;术语本身无权威统一定义,以语境区分即可 |
| bringup bug 多源于五类结构性原因(交接面窄、无日志、描述漂移、时序、强制安全链) | 内核文档排障清单、Verified Boot 行为、initcall 顺序、ueventd coldboot [S02][S05][S12][S16] | 中高;分类是解释性框架,具体项目权重不同 |
| RISC-V Android 的公开门槛是 NDK ABI 未定与真机样本少 | android-riscv64 仓库声明与 FOSDEM 复盘 [S13][S14] | 中;官方节奏可能快于预期,需持续跟踪 |
参考来源
| ID | 来源 | 链接/位置 | 可靠性 |
|---|---|---|---|
| [S01] | Linux 内核源码 init/main.c(rest_init、kernel_init、kernel_init_freeable、回退链与 panic 文案) | https://raw.githubusercontent.com/torvalds/linux/master/init/main.c | C |
| [S02] | Linux 内核源码 include/linux/init.h(initcall 分层与顺序注释) | https://raw.githubusercontent.com/torvalds/linux/master/include/linux/init.h | C |
| [S03] | Linux 内核源码 kernel/exit.c(do_exit 对 PID 1 的 panic) | https://raw.githubusercontent.com/torvalds/linux/master/kernel/exit.c | C |
| [S04] | 内核文档 ramfs-rootfs-initramfs(initramfs 解包、/init、rdinit=、switch_root) | https://www.kernel.org/doc/html/latest/filesystems/ramfs-rootfs-initramfs.html | C |
| [S05] | 内核文档 admin-guide/init(No working init found 排查清单) | https://www.kernel.org/doc/html/latest/admin-guide/init.html | C |
| [S06] | systemd 手册 bootup(7)(四阶段与 initrd 移交) | https://www.freedesktop.org/software/systemd/man/latest/bootup.html | C |
| [S07] | 内核文档 arch/riscv/boot(a0/a1/satp、PMD 对齐、EFI stub、HSM 有序启动) | https://docs.kernel.org/arch/riscv/boot.html | C |
| [S08] | AOSP init/main.cpp(argv 分发) | https://android.googlesource.com/platform/system/core/+/refs/heads/main/init/main.cpp | C |
| [S09] | AOSP init/first_stage_init.cpp(FirstStageMain 全步骤) | https://android.googlesource.com/platform/system/core/+/refs/heads/main/init/first_stage_init.cpp | C |
| [S10] | AOSP init/selinux.cpp(SetupSelinux 五步与 permissive 判定) | https://android.googlesource.com/platform/system/core/+/refs/heads/main/init/selinux.cpp | C |
| [S11] | AOSP init/init.cpp(second stage 日志与子上下文) | https://android.googlesource.com/platform/system/core/+/refs/heads/main/init/init.cpp | C |
| [S12] | Android Verified Boot 文档(AVB 失败不可启动、dm-verity 以 EIO 报告损坏) | https://source.android.com/docs/security/features/verifiedboot | C |
| [S13] | google/android-riscv64 仓库(用途声明、构建目标、Cuttlefish 2025Q2、QEMU 版本、NDK ABI 未定义) | https://github.com/google/android-riscv64 | S |
| [S14] | DeepComputing FOSDEM 2026 复盘(真机启动进展与 upstream-first) | https://deepcomputing.io/fosdem-2026-recap-bringing-android-aosp-to-risc-v/ | S |
| [S15] | Google 开源博客(2023-10-30,Cuttlefish 路径与 NDK ABI 预告) | https://opensource.googleblog.com/2023/10/android-and-risc-v-what-you-need-to-know.html | S |
| [S16] | AOSP init/README.md(first stage 定义、second_stage 重入、ueventd coldboot、SELinux 中间态) | https://android.googlesource.com/platform/system/core/+/refs/heads/main/init/README.md | C |
| [S17] | AOSP init/ueventd.cpp(设备节点与 coldboot 实现) | https://android.googlesource.com/platform/system/core/+/refs/heads/main/init/ueventd.cpp | C |
| [S18] | 内核文档 bootconfig(/proc/bootconfig 与键值参数链) | https://www.kernel.org/doc/html/latest/admin-guide/bootconfig.html | C |
| [S19] | 内部前序报告《Android 17 RISC-V 64 机遇》(国内生态、GKI 与 ART 阻塞项) | android17-riscv64-opportunities.md(本工作区) | B |
| [S20] | 内部前序报告《RISC-V 64 CPU Bringup:从加电到 init》(引导链与 OpenSBI/U-Boot 分工) | riscv64-bringup-uboot-opensbi-init-research.md(本工作区) | B |
附录:研究方法
- 确定五个问题为检索目标,优先定位一手材料:Linux 内核源码(torvalds/linux 原始文件)、AOSP system/core/init 源码、内核与 systemd 官方文档、source.android.com 安全文档。
- 逐文件阅读源码并摘录关键函数序列与错误字符串(rest_init、kernel_init_freeable、FirstStageMain、SetupSelinux、main 分发等),凡涉阶段顺序的表述均以源码为准。
- 用官方文档补足机制背景:initramfs/switch_root、引导排障清单、bootconfig、RISC-V 交接约定、systemd 四阶段。
- 交叉验证生态现状:google/android-riscv64 仓库声明、DeepComputing FOSDEM 2026 复盘、Google 官方博客,标注快照时间与本工作区前序报告。
- 将证据固化为三张图与三个矩阵,并用工作区校验器做结构与来源闭环检查,渲染为可浏览 HTML 后交付。
复现与检查命令示例(在报告工作区执行):
# 1) 结构、来源闭环与图表约束校验
python3 skills/document/scripts/validate_research_report.py \
riscv64-android-linux-init-bringup-research.md --profile technical-research
# 2) 重新生成三张图(SVG + PNG)
python3 riscv64-android-linux-figs.py
# 3) 渲染发布为可浏览 HTML(内嵌 SVG、去除脚本)
python3 render_init_bringup_report.py