内核
Linux 内核是几乎所有的 Android 设备上极其重要的软件组成部分。本部分介绍了 Linux 内核开发和版本模型(如下)、稳定的长期支持 (LTS) 内核(包括所有 Android 设备都应使用稳定版本而非择优挑选补丁程序的原因)、内核配置和加固、接口和模块化内核要求(在 Android O 中推出)、内核调试和网络测试以及 SquashFS。
Linux 内核开发
Linux 内核是迄今为止最大的协同软件项目。在 2016 年,超过 450 家不同公司的 4000 多名开发者对该项目做出了贡献。该项目共有 6 个版本,每个版本都包含 12000 到 16000 项不同的更改。在 2016 年底,Linux 内核的规模刚好超过 56000 个文件,其中包括 2200 万行代码、编译脚本和文档(内核版本 4.9)。(要查看完整的 Linux 开发统计信息,请参阅 https://kernelnewbies.org/DevelopmentStatistics。)
虽然 Linux 内核包含其支持的所有不同芯片架构和硬件驱动程序的代码,但各个系统仅运行一小部分代码库。一台普通的笔记本电脑需要使用来自 5000 个文件的大约 200 万行内核代码才能正常运行;而 Pixel 手机需要使用来自 6000 个文件的 320 万行内核代码才能正常运行(因为 SoC 的复杂性有所增加)。
Linux 内核版本
Linux 内核使用的版本模型与标准 AOSP 版本大不相同。随着 2.6 内核在 2003 年 12 月发布,内核开发者社区从之前具有单独开发和稳定内核分支的模型迁移到“仅限稳定”的分支模型。在此模型中,每 2-3 个月发布一次新版本,内核开发者社区声明该版本“稳定”,并建议所有用户运行该版本。开发模型出现这种变化的原因在于:2.6 版本内核之前的版本周期非常长(将近 3 年),且同时维护两个不同的代码库分支难度很高。
内核版本的编号从 2.6.x 开始,其中 x 是个数字,会在每次发布新版本时递增(除了表示此版本比上一内核版本更新之外,该数字的值不具有任何意义)。自那时起,内核版本现在已发展到 4.x,其中发生了 2 次重大版本变更。维护人员采用这些版本号只是为了避免更高的次要版本号令用户感到困惑。
稳定的内核版本和更新
由于之前的内核开发模型(每 2-3 个月发布一次新版本)被认为无法满足大多数用户的需求,Linux 内核稳定版模型于 2005 年随之诞生。用户希望实际用到在 2-3 个月内提交的错误修复程序,但 Linux 发行方发现,如果没有内核社区的反馈,很难确保内核保持最新状态。一般情况下,努力确保各个内核的安全并及时集成最新的错误修复程序对各方而言不仅任务艰巨,而且令人颇感困惑。
稳定的内核版本直接基于 Linus Torvalds 的版本,一般每周发布一次,具体取决于各种外部因素(处于一年中的哪个时间、可用的补丁程序、维护人员的工作量等)。稳定版本的编号开头为内核版本编号,末尾再添加一个数字。例如,Linus 发布了 4.4 内核,则基于此内核的稳定内核版本编号为 4.4.1、4.4.2、4.4.3,依此类推。表示稳定内核版本树时,该序列号通常简写为 4.4.y。每个稳定的内核版本树由一位内核开发者维护,该开发者负责为该版本挑选所需的补丁程序以及管理审核/发布流程。
稳定内核的维护期限为当前开发周期。Linus 发布新内核后,上一个稳定内核版本树就会停止维护,用户必须转为使用新发布的内核。
长期稳定的内核
这种新的稳定版本流程在采用一年之后,很多不同的 Linux 用户都表示希望延长对内核的支持,而不只是几个月时间。因此,长期支持 (LTS) 内核版本应运而生,第一个 LTS 内核 (2.6.16) 在 2006 年诞生。从那时起,每年都会选择一个新的 LTS 内核,并且内核社区会为该内核提供最少 2 年的维护支持。
在撰写本文之时,LTS 内核版本为 4.4.y、4.9.y 和 4.14.y,并且每周发布一个新内核。为了满足某些用户和发行方的需求,内核开发者会额外维护一些较旧的内核,但会延长发布周期。如需有关所有长期稳定内核、内核负责方以及维护时长的信息,请访问 kernel.org 版本页面。
LTS 内核版本每天平均会接纳 6-8 个补丁程序,而常规稳定内核版本每天则接纳 10-15 个补丁程序。考虑到相应开发内核版本的当前时间和其他外部不定因素,每个版本的补丁程序数量也会有所不同。LTS 内核的版本越旧,对应的补丁程序数量就越少,这是因为很多最新的错误修复程序与旧版内核无关。不过,内核版本越旧,向后移植那些需要采纳的更改就越难,这是因为代码库发生了变化。因此,虽然采纳的总体补丁程序数量比较少,但维护 LTS 内核所需的工作量要高于维护常规稳定内核所需的工作量。
稳定内核补丁程序规则
对于哪些内容可添加到稳定内核版本方面,相关规则自稳定内核版本模型推出后几乎没有发生任何变化,这些规则可总结为以下几点:
- 必须明显正确无疑且经过测试。
- 不得超过 100 行。
- 必须只修复一个问题。
- 必须修复已得到报告的问题。
- 可以是新的设备 ID 或硬件 quirk,但不可以添加主要新功能。
- 必须已合并到 Linus Torvalds 树中。
Documentation/process/stable_kernel_rules.rst
内核文件。
最后一条规则“必须已合并到 Linus Torvalds 树中”可防止内核社区遗漏修复程序。该社区不希望那些不在 Linus Torvalds 树中的修复程序进入稳定内核版本,以使升级的任何用户都绝不会遇到回归问题。这样可避免给维护稳定开发分支的其他项目人员带来诸多问题。
内核更新
Linux 内核社区以前曾向其用户群承诺,任何升级都不会破坏当前在上一个版本中运行正常的任何功能。这个承诺如今依然有效。回归确实会发生,但属于优先级最高的错误,要么快速对其进行修复,要么从 Linux 内核树快速还原那些导致回归的更改。
这一承诺既适用于稳定内核的渐增式更新,也适用于每 3 个月进行一次的大规模重要更新。不过,内核社区只能对已合并到 Linux 内核树的代码做出此承诺。如果在合并到设备内核的任何代码中,有任何代码不在 kernel.org 版本中,则均属于未知代码,社区无法规划(甚至考虑)与这些代码的互动。
由于各版本之间发生了大量更改(每个版本有 10000-14000 项更改),因此包含大型补丁程序集的基于 Linux 的设备在更新到新版内核时可能会遇到严重问题。由于 SoC 补丁程序集的体积大、对架构专用内核代码(有时甚至是核心内核代码)的修改较多,在更新到新版内核时尤其容易出现问题。因此,大多数 SoC 供应商开始使用 LTS 版本对其设备进行标准化,使这些设备能够直接从 Linux 内核社区接收错误和安全更新。
安全
在发布内核时,Linux 内核社区几乎从不将具体更改声明为“安全修复程序”。这是因为存在一个基本问题,那就是在开发错误修复程序时难以确定其是否为安全修复程序。此外,很多错误修复程序要经过很长时间之后才能确定为与安全相关,因此内核社区强烈建议始终接纳已发布的所有错误修复程序。
注意:要详细了解 Linus Torvalds 关于安全修复程序的声明,请参阅相关电子邮件会话。内核社区收到安全问题的报告时,便会尽快进行修复并将相应代码公开推送到开发树和稳定版本。如上所述,这些更改几乎从来都不会被描述为“安全修复程序”,而是看起来与内核的其他错误修复程序别无二致。这样做是为了让相关方能够在问题报告者将相应问题公诸于世之前更新其系统。
要详细了解如何向内核社区报告安全错误以使其尽快得到解决和修复,请参阅《Linux 内核用户和管理员指南》(www.kernel.org) 中的“安全错误”部分。
由于内核团队不会公布安全错误,因此 Linux 内核相关问题的 CVE 编号通常在修复程序合并到稳定开发分支后的几周、几个月甚或几年后发布。
确保系统安全
部署使用 Linux 的设备时,强烈建议制造商采纳所有 LTS 内核更新,在适当的测试表明更新没有什么问题后,将其推送给用户。这样做有多项优势:
- 内核开发者已整体审核发布的版本,而不是单独审核各个组件。
- 很难(即使能够)确定哪些补丁程序修复的是“安全”问题,哪些修复的不是安全问题。几乎所有 LTS 版本都至少包含一个已知的安全修复程序,以及很多暂时“未知”的安全修复程序。
- 如果测试表明有问题,则内核开发者社区会迅速采取应对措施以解决问题。
- 如果在更改的代码中,尝试仅过滤出您运行的那些代码,会导致内核树无法与未来的上游版本正确合并。
Android 通用内核
AOSP 通用内核是长期支持 (LTS) 内核的下游,包含与 Android 社区相关但尚未合并到 LTS 的补丁程序。这些补丁程序可能包括:
- 针对 Android 需求定制的功能(例如交互式
cpufreq
调节器)。 - 由于实现方面的问题而被上游拒绝的功能(例如 MTP/PTP、Paranoid Networking)。
- 可供 Android 设备使用但仍处于开发上游阶段的功能(例如 Energy Aware Scheduling/EAS)。
- 对其他方有用的供应商/OEM 功能(例如
sdcardfs
)。
通用内核列表
要查看 Android 通用内核列表,请访问 https://android.googlesource.com/kernel/common/(如下所示)。
与 LTS 的区别
与 LTS (4.14.0) 相比,Android 通用内核更改了 355 行,插入了 32266 行,并删除了 1546 行(截至 2018 年 2 月)。
最大的特性包括:
- 19.8% Energy Aware Scheduling (kernel/sched)
- 13.8% 网络 (net/netfilter)
- 13.5% Sdcardfs (fs/sdcardfs)
- 9.4% USB (drivers/usb)
- 7.2% SoC (arch/arm64, arch/x86)
- 6.2% f2fs(fs/f2fs - 从上游向后移植)
- 6.1% 输入 (drivers/input/misc)
- 5.4% FIQ 调试程序 (drivers/staging/android/fiq_debugger)
- 3.6% Goldfish 模拟器 (drivers/platform/goldfish)
- 3.4% Verity (drivers/md)
- 11.6% 其他
要求
所有 AOSP 通用内核必须提供以下各项:
- 用于下游合作伙伴及时获取最新更新(包括所有 LTS 补丁程序)的方法。
- 用于确保新功能开发不会影响从 AOSP 通用内核合并的机制(即使之前的 Android 版本也不受影响)。
- 用于下游合作伙伴轻松识别属于 Android 安全公告 (ASB) 范围内的安全补丁程序的方法。如果 OEM 尝试包含公告中未列出的补丁程序,则满足运营商有关全面重新认证的要求。
此外,必须在 AOSP 通用内核上定期执行测试,并且在分支通过测试时对其进行标记。
LTS 合并
为确保下游合作伙伴能够及时获取最新更新(包括所有 LTS 补丁程序),android-X.Y 将从 LTS 获取定期合并,并通过自动 VTS、CTS 和编译/启动测试进行验证。
Android-dessert 版本分支
为确保新功能开发不会影响从 AOSP 通用内核合并(即使之前的 Android 版本也不受影响),android-X.Y-androidRel 是从 Android-dessert 最初版本之前的 android-X.Y 克隆的,它会从 LTS 获取定期合并,并基于相关的 Android 版本进行测试。例如,android-4.4-n 分支从 LTS 4.4.y 分支获取合并。
Android-release 版本分支
为确保下游合作伙伴能够轻松识别属于 ASB 范围内的安全补丁程序,android-X.Y-androidRel-type 是从 Android 版本发布时的 android-X.Y-androidRel 克隆的,并且只能获取公告中列出的补丁程序。
在确认与公告相关的补丁程序合并到版本分支之后,相应分支将用 ASB 级别进行标记。例如,标记 ASB-2017-10-05 表示相应版本分支包含 2017 年 10 月 5 日发布的 Android 安全公告中的补丁程序。父分支包含这些安全补丁程序,因此,如果 android-4.4-o-release 分支标记为 ASB-2017-10-01,则 android-4.4-o 和 android-4.4 也是用相应公告中的补丁程序进行更新的最新版本。例如:
- 在发布 Android N MR1 之前,android-4.4-n-mr1 是从 android-4.4-n 克隆的。
- 只有 ASB 中列出的补丁程序才会进行合并,允许 OEM(运营商对其有严格的要求,旨在避免对安全更新进行全面重新认证)查找公告中列出的补丁程序。
- android-4.4-n-mr2 将由 android-4.4-n-mr1 以及这两个版本之间合并的 LTS 补丁程序构成。
- 每个月 ASB 公开发布时,相应版本分支都会使用公告中提及的所有上游补丁程序进行更新(公告中提及的设备特定补丁程序不会应用于通用内核)。
定期测试
所有 AOSP 通用内核都会定期接受测试,并且测试结果将公开发布。具体而言:
- 在 LTS 更新或其他补丁程序合并之后,将会运行 VTS 以及一部分 CTS,相关结果将在 https://qa-reports.linaro.org/lkft 上公布。
- 为持续测试各种架构和版本中的编译/启动中断,将会运行
kernelci
,相关结果将在 https://kernelci.org/job/android 上公布。
分支层次结构 (android-4.4)
android-4.4 内核的分支层次结构使用了以下结构:
准则
Android 实现应使用以下内核准则:
- 将新的 AOSP 通用内核用作上游合并源。
- 要从 LTS 获取补丁程序,需从 android-X.Y 合并。
- 在开发阶段定期合并。
- 将设备更新至新的 Android 版本时,需从 android-X.Y 分支或目标版本的版本分支合并(例如,要更新至 Nougat MR2,需从 android-4.4-n-mr2 分支合并)。
- 如果运营商对安全更新补丁程序有限制,则需从版本分支合并,以便进行安全更新。
- 要从 LTS 获取补丁程序,需从 android-X.Y 合并。
- 将修复程序发送至上游主线内核、LTS 内核或 AOSP 通用内核。
模块化内核要求
Android 8.0 中引入了模块化内核,将设备内核分为系统芯片 (SoC)、设备和板专属组件。这一变化使得原始设计制造商 (ODM) 和原始设备制造商 (OEM) 可以在独立的板专属树中使用板专属功能、驱动程序等,使他们可以替换通用的内核配置、以内核模块的形式添加新的驱动程序等。
Android 中的模块化内核支持包括:
- 对于独立 SoC 和 OEM/ODM 内核开发的平台支持。Android 9 及更高版本建议以设备中的内核模块的形式编译和推出所有板专属代码。因此:
- 所有平台都应支持设备树或高级配置与电源接口 (ACPI),以便描述所有不可发现的设备。
- 所有基于设备树的平台都应将板专属设备节点作为叠加层添加到内核设备树中。
- 在供应商测试套件 (VTS) 中支持应用二进制接口 (ABI)/应用编程接口 (API) 测试。这些测试能够确保指定内核可以运行 Android 开源项目 (AOSP) 框架。
- 每个 Android 版本的最低内核版本。
- 对于生成 Android 供应商接口 (VINTF) 内核对象的支持。
可加载的内核模块
Android 8.0 中引入的模块化内核要求规定,所有系统芯片 (SoC) 内核都必须支持可加载的内核模块。
内核配置选项
为了支持可加载的内核模块,所有常见内核中的 android-base.cfg 都包含以下内核配置选项(或其内核版本等效选项):
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODVERSIONS=y
所有设备内核都必须启用这些选项。内核模块还应尽可能支持卸载和重新加载。
注意:CONFIG_MODULE_SRCVERSION_ALL
是可选的,并且未经过测试。
模块签名
原始设计制造商 (ODM) 可以选择性启用以下内核配置选项,以在其内核配置中启用模块签名:
CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y
在需要支持验证启动的设备上,Android 要求内核模块位于启用 dm-verity 的分区中。模块签名并非强制性要求,也不会进行测试;不过,如有需要,ODM 也可以启用模块签名,但前提是 ODM 拥有所需的密钥签名及其他基础架构,以确保未来可进行独立的内核和文件系统 OTA 更新。
文件位置
Android 7.x 及更低版本对内核模块(包括对 insmod
和 rmmod
的支持)没有强制要求,而 Android 8.x 及更高版本建议在生态系统中使用内核模块。下表显示了 Android 的 3 种启动模式所需的潜在板专属外设支持:
启动模式 | 存储 | 显示 | 拨号键盘 | 电池 | PMIC | 触摸屏 | NFC、WLAN、 蓝牙 | 传感器 | 相机 |
---|---|---|---|---|---|---|---|---|---|
恢复 | |||||||||
充电 | |||||||||
Android |
除了按 Android 启动模式的可用情况对内核模块进行分类之外,还可以按照所有者(SoC 供应商或 ODM)进行分类。如果使用了内核模块,则它们在文件系统中的放置位置的要求如下:
- 所有内核都应内置对启动和装载分区的支持。
- 必须从只读分区加载内核模块。
- 对于需要支持验证启动的设备,应从验证分区加载内核模块。
- 内核模块不应位于
/system
中。 - 完整 Android 模式或充电模式所需的 SoC 供应商内核模块应该位于
/vendor/lib/modules
中。 - 如果存在 ODM 分区,则完整 Android 模式或充电模式所需的 ODM 内核模块应该位于
/odm/lib/modules
中。如果不存在,则这些模块应该位于/vendor/lib/modules
中。 - 来自 SoC 供应商和 ODM 且恢复模式所需的内核模块应该位于
/lib/modules
下的恢复ramfs
中。 - 恢复模式和完整 Android/充电模式所需的内核模块应同时位于恢复
rootfs
以及/vendor
或/odm
分区中(如上所述)。 - 恢复模式所用的内核模块不应依赖仅位于
/vendor
或/odm
中的模块,因为这些分区在恢复模式下没有装载。 - SoC 供应商内核模块不应依赖 ODM 内核模块。
在 Android 7.x 及更低版本中,/vendor
和 /odm
分区不会提前装载。在 Android 8.x 及更高版本中,为使模块能够从这些分区加载,已进行相关配置,以便为非 A/B 和 A/B 设备提前装载分区。这还确保了在 Android 和充电模式下均装载分区。
Android 编译系统支持
在 BoardConfig.mk
中,Android 编译系统定义了 BOARD_VENDOR_KERNEL_MODULES
变量,此变量提供了用于供应商映像的内核模块的完整列表。此变量中列出的模块会被复制到位于 /lib/modules/
的供应商映像中,在 Android 中装载后会显示在 /vendor/lib/modules
中(根据上述要求)。下面是一个供应商内核模块的配置示例:
vendor_lkm_dir := device/$(vendor)/lkm-4.x
BOARD_VENDOR_KERNEL_MODULES := \
$(vendor_lkm_dir)/vendor_module_a.ko \
$(vendor_lkm_dir)/vendor_module_b.ko \
$(vendor_lkm_dir)/vendor_module_c.ko
在此示例中,供应商内核模块预编译代码库会映射到 Android 编译系统中的上述位置。
恢复映像可能包含供应商模块的子集。Android 编译系统定义了这些模块的变量 BOARD_RECOVERY_KERNEL_MODULES
。示例:
vendor_lkm_dir := device/$(vendor)/lkm-4.x
BOARD_RECOVERY_KERNEL_MODULES := \
$(vendor_lkm_dir)/vendor_module_a.ko \
$(vendor_lkm_dir)/vendor_module_b.ko
Android 编译系统负责运行 depmod
以在 /vendor/lib/modules
和 /lib/modules
(recovery ramfs
) 中生成所需的 modules.dep
文件。
模块加载和版本管理
通过调用 modprobe -a
从 init.rc*
一次加载所有内核模块。这样可以避免重复初始化 modprobe
二进制文件的 C 运行时环境产生的开销。您可以修改 early-init
事件来调用 modprobe
:
on early-init
exec u:r:modprobe:s0 -- /vendor/bin/modprobe -a -d \
/vendor/lib/modules module_a module_b module_c ...
通常,内核模块必须使用将与此模块结合使用的内核进行编译,否则,内核会拒绝加载此模块。CONFIG_MODVERSIONS
通过检测应用二进制接口 (ABI) 中的损坏情况提供一种解决方法。此功能会为内核中导出的每个符号的原型计算循环冗余校验 (CRC) 值,并将这些值作为内核的一部分存储起来;对于内核模块所使用的符号,相应的值也会存储在内核模块中。模块加载完成后,模块所用符号的值将与内核中的相应值进行比较。如果这些值相互匹配,则加载模块;如果不匹配,则模块加载会失败。
要使内核映像的更新独立于供应商映像,请启用 CONFIG_MODVERSIONS
。这样做可以在确保与供应商映像中的现有内核模块保持兼容的同时,对内核进行小幅度更新(例如 LTS 提供的问题修复)。不过,CONFIG_MODVERSIONS
本身并不会修复 ABI 损坏。如果内核中某个导出的符号的原型由于源代码的修改或内核配置更改而发生变化,则会破坏与使用此符号的内核模块的兼容性。在此类情况下,必须重新编译内核模块。
例如,内核中的 task_struct
结构(在 include/linux/sched.h
中定义)包含很多字段,具体包含的字段根据相关条件取决于内核配置。sched_info
字段仅在 CONFIG_SCHED_INFO
启用(启用 CONFIG_SCHEDSTATS
或 CONFIG_TASK_DELAY_ACCT
时发生)时才显示。如果这些配置选项的状态发生变化,task_struct
结构的布局也将发生变化,同时,从内核导出的使用 task_struct
的所有接口都会发生变化(如 kernel/sched/core.c
中的 set_cpus_allowed_ptr
)。与使用这些接口的之前编译的内核模块的兼容性将会被破坏,这就需要使用新的内核配置重新编译这些模块。
要详细了解 CONFIG_MODVERSIONS
,请参阅位于 Documentation/kbuild/modules.txt
的内核树中的相关文档。
提前装载分区
支持 Treble 的设备必须启用第一阶段装载,以确保 init
可以加载分布在 system
和 vendor
分区的安全增强型 Linux (SELinux) 政策片段。此访问权限还可实现在内核启动后尽快加载内核模块。
要执行提前装载,Android 必须有权访问模块所在的文件系统。Android 8.x 及更高版本支持在 init
的第一阶段(即初始化 SELinux 之前)装载 /system
、/vendor
或 /odm
。设备制造商可以使用设备树叠加层为提前装载的分区指定 fstab
条目。
AOSP 提前装载更改摘要:
提前装载分区,VBoot 1.0
使用 VBoot 1.0 提前装载分区的要求包括:
- 设备节点路径必须在
fstab
和设备树条目中使用其by-name
符号链接。例如,确保对分区进行命名且设备节点为/dev/block/…./by-name/{system,vendor,odm}
,而不是使用/dev/block/mmcblk0pX
指定分区。 - 在产品的设备配置中(即
device/oem/project/device.mk
中)为PRODUCT_{SYSTEM,VENDOR}_VERITY_PARTITION
和CUSTOM_IMAGE_VERITY_BLOCK_DEVICE
指定的路径必须与fstab
/设备树条目中相应块设备节点指定的by-name
相匹配。例如:PRODUCT_SYSTEM_VERITY_PARTITION := /dev/block/…./by-name/system
PRODUCT_VENDOR_VERITY_PARTITION := /dev/block/…./by-name/vendor
CUSTOM_IMAGE_VERITY_BLOCK_DEVICE := /dev/block/…./by-name/odm - 通过设备树叠加层提供的条目不得在
fstab
文件片段中出现重复。例如,指定某个条目以在设备树中装载/vendor
时,fstab
文件不得重复该条目。 - 不得提前装载需要
verifyatboot
的分区(此操作不受支持)。 - 必须在
kernel_cmdline
中使用androidboot.veritymode
选项指定验证分区的真实模式/状态(现有要求)。
提前装载设备树,VBoot 1.0
在 Android 8.x 及更高版本中,init
会解析设备树并创建 fstab
条目,以在其第一阶段提前装载分区。fstab
条目采取以下形式:
src mnt_point type mnt_flags fs_mgr_flags
定义设备树属性以模拟该格式:
fstab
条目必须在设备树中的/firmware/android/fstab
下,且必须将兼容字符串集设为android,fstab
。/firmware/android/fstab
下的每个节点都被视为单个提前装载fstab
条目。节点必须定义以下属性:dev
必须指向代表by-name
分区的设备节点。type
必须是文件系统类型(如在fstab
文件中一样)。mnt_flags
必须是装载标记的逗号分隔列表(如在fstab
文件中一样)。fsmgr_flags
必须是 Androidfs_mgr flags
列表(如在fstab
文件中一样)。
- A/B 分区必须具有
slotselect fs_mgr
选项。 - 已启用 dm-verity 的分区必须具有
verify fs_mgr
选项。
示例:N6P 上的 /system 和 /vendor
下面的示例显示的是在 Nexus 6P 上为 system
和 vendor
分区提前装载设备树:
/ {
firmware {
android {
compatible = "android,firmware";
fstab {
compatible = "android,fstab";
system {
compatible = "android,system";
dev = "/dev/block/platform/soc.0/f9824900.sdhci/by-name/system";
type = "ext4";
mnt_flags = "ro,barrier=1,inode_readahead_blks=8";
fsmgr_flags = "wait,verify";
};
vendor {
compatible = "android,vendor";
dev = "/dev/block/platform/soc.0/f9824900.sdhci/by-name/vendor";
type = "ext4";
mnt_flags = "ro,barrier=1,inode_readahead_blks=8";
fsmgr_flags = "wait";
};
};
};
};
};
示例:Pixel 上的 /vendor
下面的示例显示的是在 Pixel 上为 /vendor
提前装载设备树(请务必为 A/B 分区添加 slotselect
):
/ {
firmware {
android {
compatible = "android,firmware";
fstab {
compatible = "android,fstab";
vendor {
compatible = "android,vendor";
dev = "/dev/block/platform/soc/624000.ufshc/by-name/vendor";
type = "ext4";
mnt_flags = "ro,barrier=1,discard";
fsmgr_flags = "wait,slotselect,verify";
};
};
};
};
};
提前装载分区,VBoot 2.0
VBoot 2.0 是 Android 验证启动 (AVB)。使用 VBoot 2.0 提前装载分区的要求如下:
- 设备节点路径必须在
fstab
和设备树条目中使用其by-name
符号链接。例如,确保对分区进行命名且设备节点为/dev/block/…./by-name/{system,vendor,odm}
,而不是使用/dev/block/mmcblk0pX
指定分区。 - VBoot 1.0 所用的编译系统变量(如
PRODUCT_{SYSTEM,VENDOR}_VERITY_PARTITION
和CUSTOM_IMAGE_VERITY_BLOCK_DEVICE
)对 VBoot 2.0 而言并不是必需的。您应定义 VBoot 2.0 中引入的编译变量(包括BOARD_AVB_ENABLE := true
);有关完整配置,请参阅适用于 AVB 的编译系统集成。 - 通过设备树叠加层提供的条目不得在
fstab
文件片段中出现重复。例如,如果您指定某个条目以在设备树中装载/vendor
,则fstab
文件不得重复该条目。 - VBoot 2.0 不支持
verifyatboot
,无论是否启用了提前装载。 - 必须在
kernel_cmdline
中使用androidboot.veritymode
选项指定验证分区的真实模式/状态(现有要求)。确保包含以下 AVB 修复程序:
提前装载设备树,VBoot 2.0
VBoot 2.0 设备树中的配置与 VBoot 1.0 中的大致相同,但还有以下几项不同之处:
fsmgr_flag
由verify
变为avb
。- 包含 AVB 元数据的所有分区都必须位于设备树的 VBMeta 条目中,即使相应的分区并非提前装载的分区(如
/boot
)也是如此。
示例:N5X 上的 /system 和 /vendor
下面的示例显示的是在 Nexus 5X 上为 system
和 vendor
分区提前装载设备树。请注意:
/system
使用 AVB 进行装载,且/vendor
的装载不需要进行完整性验证。- 由于 Nexus 5X 没有
/vbmeta
分区,因此顶级 vbmeta 位于/boot
分区的末端(有关详情,请参阅 AOSP 变更列表)。/ {
firmware {
android {
compatible = "android,firmware";
vbmeta {
compatible = "android,vbmeta";
parts = "boot,system,vendor";
};
fstab {
compatible = "android,fstab";
system {
compatible = "android,system";
dev = "/dev/block/platform/soc.0/f9824900.sdhci/by-name/system";
type = "ext4";
mnt_flags = "ro,barrier=1,inode_readahead_blks=8";
fsmgr_flags = "wait,avb";
};
vendor {
compatible = "android,vendor";
dev = "/dev/block/platform/soc.0/f9824900.sdhci/by-name/vendor";
type = "ext4";
mnt_flags = "ro,barrier=1,inode_readahead_blks=8";
fsmgr_flags = "wait";
};
};
};
};
};
示例:Pixel 上的 /vendor
下面的示例显示的是在 Pixel 上提前装载 /vendor
。请注意:
- 很多分区都是在 vbmeta 条目中指定的,因为这些分区受 AVB 保护。
- 请务必包含所有 AVB 分区,即使仅提前装载了
/vendor
也是如此。 - 请务必为 A/B 分区添加
slotselect
。/ {
vbmeta {
compatible = "android,vbmeta";
parts = "vbmeta,boot,system,vendor,dtbo";
};
firmware {
android {
compatible = "android,firmware";
fstab {
compatible = "android,fstab";
vendor {
compatible = "android,vendor";
dev = "/dev/block/platform/soc/624000.ufshc/by-name/vendor";
type = "ext4";
mnt_flags = "ro,barrier=1,discard";
fsmgr_flags = "wait,slotselect,avb";
};
};
};
};
};
DTO 支持
设备树叠加层 (DTO) 扩展了现有的扁平化设备树 (FDT) 实现,让用户空间(在运行时)可以通过加载用于修改原始数据的额外叠加层 FDT 来修改内核中的初始设备树数据。Android 不需要在运行时更新用户空间的 DT Blob,而是建议供应商借助 libfdt
或 libufdt
在引导加载程序中添加设备树补丁程序。
Android DTO 支持
Android 对 DTO 的支持因 Android 版本而异:
- Android 7.x 及更早版本不要求提供设备树支持,同时,没有针对供应商如何将 DT Blob 传递给内核或在何处存储这些 Blob 提供建议。
- Android 8.x 建议提供设备树支持,以将内核的板专属部分和仅限 SoC 访问的部分区分开来。
- Android 9 及更高版本要求必须存在设备树 Blob 叠加层 (DTBO) 分区并且至少应用一个 DTO。
DTO 分区要求
大多数 Android 设备都在编译时将 DT Blob 附加到内核中,并且引导加载程序会从内核加载 Blob。但是,由于 DT Blob 被视为系统芯片 (SoC) 内核的一部分,因此 Android 对如何编译或存储 DT Blob 没有特定要求。设备可以将 DT Blob 附加到内核,也可以将 Blob 存储在单独的分区中;唯一的要求是引导加载程序应该知道如何加载以及从何处加载 DT Blob。
注意:要详细了解分区格式,请参阅 DTB/DTBO 分区。要支持 DTO,设备应具备以下条件:
- 每个适用于板专属 DT 叠加层的内核映像都应具有一个 DTBO 分区,并且引导加载程序必须知道从何处以及如何加载 SoC 专属 DTB。分区大小取决于编译 SoC 内核所需的更改数量;选择分区大小时需要为未来更新留出空间,通常,8 MB 大小的分区已绰绰有余。
- 针对 A/B 设备更新了 DTO 分区。恢复内核与 Android 内核相同,但分区必须针对 A/B 设备设置,因为这样才可以通过无线下载 (GOTA) 更新进行更新。分区大小取决于设备以及主 SoC 内核 DT Blob 上所需的更改数量。
DTO 引导加载程序要求
要支持 DTO,引导加载程序应具备以下条件:
- 知道如何以及从何处(考虑使用 A/B 设备的启动槽)以供应商独有的方式加载 SoC 专属 DT Blob(通常是从内核映像的末端提取,因为 Blob 已附加到内核中)。
- 知道如何以及从何处以供应商独有的方式加载叠加层 DT Blob。
- 将组合设备树传递给内核之前,使用叠加层修补主 DT Blob。
要详细了解如何在引导加载程序中增加对 DTO 的支持,请参阅设备树叠加层。
核心内核要求
Android 8.0 及更高版本指定了最低内核版本和内核配置,并由供应商测试套件 (VTS) 和无线下载 (OTA) 更新对其进行验证。Android 设备内核必须启用内核 .config
支持以及在运行时通过 procfs
读取内核配置的选项。
内核 .config 支持
所有设备内核都必须完整启用 android-base.cfg,其中必须包含以下内核配置选项(或其内核版本等效选项):
CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y
内核版本
对于 Android 9,要求的最低长期支持 (LTS) 内核版本是 4.4.107、4.9.84 和 4.14.42。
- 2018 年生产的所有系统芯片 (SoC) 在推出时都必须采用 4.9.84 版或更高版本的内核。
- 所有其他 SoC 在推出搭载 Android 9 的 Android 设备时,都必须使用 4.4.107 版或更高版本的内核。
- 基于 4.14 的设备内核必须包含 4.14.42 或更高版本的 LTS。
- 发布搭载 Android 8.0 及更高版本的设备时,所有 SoC 仍然需要遵循启用 Treble 所需的内核更改要求,无论发布日期是什么时候。
- 要升级到 Android 8.0 或更高版本的早期 Android 设备可以继续使用原来的基础内核版本。
要详细了解 LTS 内核,请参阅长期稳定的内核和 Android 通用内核
设备树支持
如果平台不支持高级配置与电源接口 (ACPI),则内核中必须启用设备树支持,且引导加载程序必须将硬件描述以设备树的形式传递给内核。设备树还必须可供 Android 读取,且能够将供应商和 ODM 特有的参数传递给 Android。CONFIG_OF
以及所有其他设备和子系统专属的 CONFIG_OF_*
内核配置选项都是强制性的选项。
DebugFS
供应商接口的实现不应依赖 debugfs
,您可以启用 debugfs,但在不装载 debugfs
的情况下也可以完成 VTS 测试。
未来的 Android 版本
当前的 Android 版本建议以设备中的内核模块的形式编译和推出所有板专属代码。对 Android 而言,内核的其余部分为一个整体(无论它是单片内核,还是其中的一部分是作为内核模块编译的)。
该单片内核是可以在 SoC 供应商的参考硬件上启动的 SoC 内核,但仅限于此。如今,对 SoC 内核的处理方式与通用内核类似;SoC 内核在板专属的代码库中会有大量副本。这种分发模型会导致,针对每个分支中的同一错误,系统会采取极为不同的方式修复 SoC 内核;这样一来,由于会在不同的时间择优挑选或修复同一错误的方式不同,未来的内核更新会有延迟。要解决此问题,必须单独提供 SoC 内核,以便使用 SoC 的每个人都可以为同一 SoC 内核做贡献。
单片内核碎片
SoC 内核会在一段时间内在各个 Android 版本以及 ODM 之间逐渐碎片化。
图 1. 设备内核副本此示例说明了以下事实:
- 每个人都需要花费大量的时间和精力对板专属分支/标签进行交叉合并。
- 等待交叉合并的同时,Android 设备制造商会修补他们自己的内核以获取错误/安全漏洞修复程序。
- 与父级的偏离导致未来的升级/合并变得很困难。
通用 SoC 内核
针对通用 SoC 内核提议的模型可解决上行合并更改(如 SoC 专属错误修复程序、LTS 升级和安全漏洞修复程序)导致的问题。例如,按 SoC 和内核进行过统一的理想场景具有以下工作流程。
图 2. Android 8.x 及更高版本的设备内核此工作流程旨在通过与设备制造商展开协作以采用最新的通用 SoC 内核,解决内核代码库碎片化的问题。Android 8.x 及更高版本为 ODM 提供了各种可能的选项,使 ODM 不需要维护自己的 SoC 内核,而是依赖通用 SoC 内核来进行升级,例如获取 LTS 升级、问题修复程序和安全漏洞补丁程序。
我们初步的计划是推动所有 ODM/供应商都使用单一的 SoC 内核源。未来,我们计划朝着每个 SoC 分发单个内核二进制文件的方向发展。
上游内核变更
为了更轻松且更自动化地更新为较新的内核版本,并为 ODM 提供更安全可靠的平台来开发产品,我们强烈建议 SoC 供应商在上游更改其内核,并让 kernel.org 主代码库接受这些更改。一开始,这样做需要付出额外的努力和工程资源,但从长远的角度来看,这样可以节省时间和资金。与未经社区审核的代码相比,合并后的代码质量高很多,错误和安全漏洞问题也更少(这些错误和安全漏洞问题的严重程度通常更低)。
如果将对 SoC 的全面支持合并到上游,社区便可以在内部的内核 API 随时间不断发展的同时,做出必要的 API 更改,从而延长平台的使用寿命。通过将硬件平台添加到诸多由社区管理的内核测试平台中的一个(如 kernelci.org
),还可以在开发版本和稳定版本中自动对内核进行回归测试。
要获取与 Linux 内核社区协作以将您的代码上游化的相关帮助,请参阅以下资源:
Documentation/process
(在 4.9 版及更低版本中为Documentation/development-process
)Documentation/CodingStyle
Documentation/SubmittingPatches
社区会稍作审核就接受独立的驱动程序和文件系统,并将其纳入内核的暂存区,然后在其中努力提高代码质量