Magisk 启动机制全解:rootdir、SAR、2SI 与设备类型划分的源码级剖析
Magisk 能否在任意设备上稳定工作,取决于对 Android 启动流程的精确理解:rootdir 从哪来、system 分区何时挂载、init 进程如何被替换。本文以 docs/boot.md 为核心骨架,完整覆盖 rootdir / SAR / 2SI 等关键术语、三种启动方法(Method A/B/C)的判定标准与历史成因,以及 Type I–IV 设备分类模型,并结合 native/src/init/init.rs 等源码展示 Magisk 在不同启动路径下的实际劫持策略,帮助你准确判断设备属于哪一类,并理解对应刷入方案的底层原理。
核心术语:先统一语言
boot.md 开篇定义了一组贯穿全文的术语,理解它们是理解一切的基础:
- rootdir:根目录(
/)。所有文件/目录/文件系统都存储或挂载在 rootdir 之下。在 Android 上,文件系统可以是rootfs,也可以是system分区。 initramfs:Android boot 镜像中的一段内容,Linux 内核将其用作rootfs。人们也常互换使用 ramdisk 一词。recovery与boot分区:二者其实非常相似——都是包含 ramdisk 和 Linux 内核(以及一些别的东西)的 Android boot 镜像。唯一区别在于:启动boot分区会进入 Android,而recovery是一个用于维修与升级设备的极简自包含 Linux 环境。- SAR(System-as-Root):即设备使用
system分区作为 rootdir,而不是rootfs。 - A/B、A-only:支持 A/B 无缝系统更新的设备拥有所有只读分区的双 slot,称为 A/B 设备;与之相对,非 A/B 设备称为 A-only。
- 2SI(Two Stage Init):Android 10+ 的启动方式,后文详述。
此外,文档引入了两个参数来更精确地刻画一台设备的 Android 版本:
- LV(Launch Version):设备出厂时预装的 Android 版本。
- RV(Running Version):设备当前正在运行的 Android 版本。
文档使用 Android API level 来表示 LV 和 RV(API level 与 Android 版本的对应关系可查 AOSP 官方的平台版本表)。举例:Pixel XL 出厂搭载 Android 7.1(LV = 25),当前运行 Android 10(RV = 29),即 (LV = 25, RV = 29)。
三种启动方法:Method A、B、C
Android 的启动方式大致可归为三大类。文档给出了一张核心对照表,用“初始 rootdir / 最终 rootdir”两个维度区分:
| 方法 | 初始 rootdir | 最终 rootdir |
|---|---|---|
| A | rootfs |
rootfs |
| B | system |
system |
| C | rootfs |
system |
Method A - Legacy ramdisk
这是所有 Android 设备曾经使用的启动方式(“美好旧时光”):内核使用 initramfs 作为 rootdir,然后 exec /init 完成启动。
判定标准:不属于 Method B 和 C 任何条件的设备,都落入 Method A。
Method B - Legacy SAR
该方式最早出现在 Pixel 1 上:内核直接挂载 system 分区作为 rootdir,并 exec /init 启动。
判定标准:
(LV = 28)的设备;- Google:Pixel 1 和 2;Pixel 3 和 3a 在
(RV = 28)时; - OnePlus:6 – 7;
- 也许还有一些
(LV < 29)的 Android Go 设备?
Method C - 2SI ramdisk SAR
该方式最早出现在 Pixel 3 的 Android 10 developer preview 上:内核使用 initramfs 作为 rootdir 并 exec rootfs 中的 /init。这个 init 负责挂载 system 分区、将其设为新的 rootdir,最终 exec /system/bin/init 完成启动。
判定标准:
(LV >= 29)的设备;(LV < 28, RV >= 29)的设备,排除那些原本就使用 Method B 的;- Google:
(RV >= 29)的 Pixel 3 和 3a。
从源码看:Magisk 如何分流这三种路径
magiskinit 替换 /init 成为开机执行的第一进程。在 native/src/init/init.rs 的 MagiskInit::start() 中,可以看到文档所述三种方法与源码分支的一一对应:
if !argv1.is_null() && CStr::from_ptr(argv1) == c"selinux_setup" {
self.second_stage(); // Method C 的第二阶段
} else if CStr::from_ptr(self.config.boot_mode.as_ptr()) == c"charger" {
self.recovery_or_charger(); // 充电模式,直接放弃
} else if self.config.skip_initramfs {
self.legacy_system_as_root(); // Method B:内核直接用 system 作 rootdir
} else if self.config.force_normal_boot {
self.first_stage(); // Method C:USES_RECOVERY_AS_BOOT 设备
} else if cstr!("/sbin/recovery").exists()
|| cstr!("/system/bin/recovery").exists() {
self.recovery_or_charger(); // 当前实际在进 recovery,放弃注入
} else if self.check_two_stage() {
self.first_stage(); // Method C:2SI 设备
} else {
self.rootfs(); // Method A:传统 ramdisk 启动
}
这里有两个值得注意的判定细节:
- Method B 由内核参数标识:
skip_initramfs对应启动参数androidboot.skip_initramfs。当内核直接以system为 rootdir 启动时(Method B),boot ramdisk 根本没被用作根文件系统,因此 Magisk 走legacy_system_as_root()分支,把 Magisk 数据准备到/data并修补只读根。 - 2SI 的设备判定:
check_two_stage()(见 native/src/init/getinfo.rs)依次检查/first_stage_ramdisk、/second_stage_resources、/system/bin/init、/apex是否存在,若都无明确标志,则解析原始 init 二进制中是否包含selinux_setup字符串作为兜底——这正对应文档所说 2SI 是 Android 10+(LV/RV >= 29)的标志。 force_normal_boot与USES_RECOVERY_AS_BOOT:文档历史章节提到,A/B 设备的 boot ramdisk 是“混合”的,内核依据 bootloader 信息决定进 Android 还是 recovery。AOSP 为此引入USES_RECOVERY_AS_BOOT机制,设备启动 Android 时会设置androidboot.force_normal_boot=1,源码中它被归入first_stage()(2SI 处理路径)。
SAR 定义之争:Google 口径 vs Magisk 口径
从公开文档看,Google 对 SAR 的定义只考虑内核如何启动设备(即上表的 Initial rootdir),也就是说只有使用 Method B 的设备才官方被视为 SAR 设备。
但对 Magisk 而言,真正的差异在于设备完全启动后使用什么作为根(上表的 Final rootdir)。因此 在 Magisk 的关注范围内,Method B 和 C 都是 SAR 的一种形态,只是实现方式不同。boot.md 后续提到的每一处 SAR 均指 Magisk 的定义,除非特别说明。
Method C 的条件稍显复杂,通俗地说:要么你的设备足够新、出厂即为 Android 10+;要么你在原本使用 Method A 的设备上刷了 Android 10+ 的自定义 ROM:
- 任何运行 Android 10+ 的 Method A 设备都会自动变为 Method C;
- Method B 设备则被锁定在 Method B,唯一例外是 Pixel 3 和 3a——Google 对这两台设备做了改造以适配新方法。
SAR 是 Project Treble 的重要组成:rootdir 必须与平台(platform)绑定。这也是 Method B 和 C 都带有 (LV >= ver) 条件的原因——Google 每年强制所有 OEM 遵守更新后的要求。
历史演变:A/B 更新、Dynamic Partitions 与 2SI 的由来
理解三种方法为何存在,需要回到 Google 设计 A/B 时的历史语境:
-
A/B 无缝系统更新登场。当 Google 发布第一代 Pixel 时,同时引入了 A/B(Seamless)系统更新。出于存储容量考虑,A/B 与 A-only 存在若干差异,最相关的一点是:
recovery分区被移除,recovery ramdisk 被合并进boot。 -
Method B 催生了“合并”的巧思。如果采用 SAR(当时只有 Boot Method B),内核启动 Android 不需要
initramfs(因为 rootdir 在system里)。于是可以聪明地把 recovery ramdisk(极简 Linux 环境)塞进boot、移除recovery分区,让内核根据 bootloader 提供的信息选择使用哪个 rootdir(ramdisk 或system)。 -
Dynamic Partitions 成为 SAR 的“坏消息”。从 Android 7.1 演进到 Android 10 期间,Google 引入了动态分区(Dynamic Partitions)。这对 SAR 是坏消息:Linux 内核无法直接理解这种新的分区格式,因而无法直接把
system挂载为 rootdir。于是 Method C 应运而生:永远先进initramfs,把剩余启动工作交给用户空间处理——包括决定进 Android 还是 recovery,即 AOSP 官方所说的USES_RECOVERY_AS_BOOT。 -
部分现代设备的例外:一些采用 A/B + 2SI 的现代设备同时带有
recovery_a/recovery_b分区,这在 Google 标准中是官方支持的。这类设备只用 boot ramdisk 启动 Android,因为 recovery 存放在独立分区中。
汇总分类:Type I – IV 设备类型
掌握以上知识后,可以把所有 Android 设备归入以下类型。这些类型按首次出现的时间先后排序:
| 类型 | 启动方法 | 分区 | 2SI | boot 中的 Ramdisk |
|---|---|---|---|---|
| I | A | A-only | 否 | boot ramdisk |
| II | B | A/B | 任意 | recovery ramdisk |
| III | B | A-only | 任意 | N/A |
| IV | C | 任意 | 是 | Hybrid ramdisk |
各类型的定位:
- Type I:老式 legacy ramdisk 启动;
- Type II:传统 A/B 设备。Pixel 1 是第一款此类设备,它同时是首款 A/B 设备与首款 SAR 设备;
- Type III:2018 年底 – 2019 年的 A-only 设备。在 Magisk 看来,这是“史上最难伺候”的设备类型;
- Type IV:所有使用 Boot Method C 的设备。A/B 型 Type IV 的 ramdisk 可依据 bootloader 信息启动进 Android 或 recovery;A-only 型 Type IV 的 ramdisk 只能启动进 Android。
Type III 的特殊处境:Magisk 只能装进 recovery
Type III 设备之所以难缠,根源在于:Magisk 始终安装在 boot 镜像的 ramdisk 中。对其他所有设备类型,因为 boot 分区自带 ramdisk,Magisk 可以直接通过 Magisk App 修补 boot 镜像,或在自定义 recovery 中刷 zip 轻松安装。但 Type III 设备只能把 Magisk 装进 recovery 分区:正常开机时 Magisk 不生效,用户必须每次重启进 recovery 才能维持 Magisk 访问。
这一约束在仓库的 docs/install.md “Magisk in Recovery” 一节有对应说明:boot 镜像没有 ramdisk 的设备,只能走 hijack recovery 路径;且每次从关机状态启动时,需依靠厂商的“进 recovery 键位组合”快速松开按键 + 长按音量上键来区分“进 Magisk 管理的 recovery”还是“进真正的原厂 recovery”。
从源码侧看,这正是 native/src/init/init.rs 中这段检查的用武之地:
} else if cstr!("/sbin/recovery").exists()
|| cstr!("/system/bin/recovery").exists() {
// 当前 ramdisk 属于 recovery,恢复原始 init 并放弃注入
self.recovery_or_charger();
}
即 magiskinit 发现自己跑在 recovery ramdisk 里(说明这次是用户主动进的 recovery,而非正常启动),就恢复原始 /init 让原厂 recovery 正常跑起来——这正是文档所述的“快速松键进 Magisk、长按音量上进原厂 recovery”双模机制的内核侧实现。
此外文档还指出:部分 Type III 设备的 bootloader 仍然接受并会将手动塞入 boot 镜像的 initramfs 传递给内核(例如某些小米手机),但很多设备不接受(例如 Samsung S10、Note 10)。这完全取决于 OEM 对 bootloader 的实现方式。
实战映射:修补 boot 镜像时的设备类型参数
理解了上述分类,再看实际的修补流程就会清晰许多。Magisk 的 boot 镜像修补脚本 scripts/boot_patch.sh 在头部注释中明确列出可用环境变量:
# The following environment variables can configure the installation:
# KEEPVERITY, KEEPFORCEENCRYPT, PATCHVBMETAFLAG, RECOVERYMODE, LEGACYSAR
其中与本文主题直接相关的有两个:
LEGACYSAR:对应 Method B(Legacy SAR / Type II、III 设备)的修补模式,指示修补逻辑按“内核直接以 system 为 rootdir”的场景处理 ramdisk——这解释了为何文档强调 Method B 设备“被锁定在 Method B”时,修补策略也必须与之匹配;RECOVERYMODE:对应 Type III 设备“Magisk 装进 recovery 分区”的情形,即 docs/install.md 中所说的“如果你修补的是 recovery 镜像,勾选 Recovery Mode 选项”。
脚本注释还说明,boot_patch.sh 需要与同目录下的 magiskinit(替换 /init 的二进制)、magisk、magiskboot(操作 boot 镜像的工具)、init-ld(作为 /init 的 LD_PRELOAD 库)、stub.apk 等文件配套工作——这份文件清单本身就是文档所述“Magisk 始终安装在 boot 镜像 ramdisk 中”这一事实的直接落证。
2SI 劫持原理:Method C 设备的第二阶段接管
Method C 设备的两段式 init 是 Magisk 启动链中最精巧的部分。magiskinit 在第一阶段(first_stage())中调用 native/src/init/twostage.rs 的 hijack_init_with_switch_root(),利用原始 init 的 SwitchRoot 行为完成对第二阶段 init 的劫持。源码注释完整解释了这一技巧:
- 两个关于 2SI 的重要假设:第二阶段 init 永远是
/system/bin/init;SwitchRoot之后/sdcard必然是指向/storage/self/primary的符号链接; SwitchRoot会做两件事:把/下的所有挂载递归移动到/system下,再 chroot 到/system;- Magisk 的 trick:在第一阶段把
magiskinit挂载到/sdcard,并创建/storage/self/primary -> /system/system/bin/init的符号链接。原始 init 执行SwitchRoot时,会把/sdcard(即 magiskinit)移动到/system/sdcard,经过符号链接链最终落到/system/bin/init——等效于强制原始 init 把 magiskinit bind mount 到/system/bin/init上,成功劫持第二阶段。
对于某些 2SI 但不执行 switch_root 的设备(如魅族),源码会检测 /sdcard 是否已存在于 ramfs 中,若已存在则回退到 hexpatch 方案:hexpatch_init_for_second_stage()(native/src/init/twostage.rs)直接在 /init 二进制内把字符串 /system/bin/init 替换为 /data/magiskinit,若 /init 不可写,则把补丁后的副本写入 /data/init 并 bind mount 覆盖。
完成劫持后,magiskinit 还会通过 native/src/init/rootdir.rs 的 inject_magisk_rc() 向 init.rc 追加服务定义,把 Magisk 的四个生命周期钩子挂进系统启动流:
on post-fs-data → magisk --post-fs-data
on property:vold.decrypt=trigger_restart_framework → magisk --service
on nonencrypted → magisk --service
on property:sys.boot_completed=1 → magisk --boot-complete
这与 docs/details.md “Magisk Booting Process” 一节描述的 Pre-Init / post-fs-data / late_start 三阶段流程完全吻合:Magisk 的注入时机严格贴着 Android 自身的启动里程碑。
小结
docs/boot.md 的核心脉络可以浓缩为三句话:
- 用 LV/RV 两个 API level 参数 + 启动方法 A/B/C 的判定条件,可以推断设备大致使用哪种启动方式;
- 按启动方法 × 分区形态 × 2SI 与否,设备被划分为 Type I–IV 四类,其中 Type III(A-only + Method B + 无 boot ramdisk)是唯一需要把 Magisk 装进 recovery 分区的类型;
- Magisk 源码在 native/src/init/init.rs 中为每条路径提供了明确的分支:
rootfs()对应 Method A,legacy_system_as_root()对应 Method B,first_stage()+ 2SI 劫持(native/src/init/twostage.rs)对应 Method C,recovery 检测则服务于 Type III 的双模 recovery 机制。
掌握了这套分类法与对应的源码分支,你就能在刷入前准确判断自己的设备类型、选择正确的镜像(boot.img / init_boot.img / recovery.img)与修补参数(LEGACYSAR / RECOVERYMODE),而不是盲刷盲试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00