了解一下,Android 10中镜像文件的制作

时间:2024-05-21 12:11:52

缘起

写《深入理解Android Java虚拟机ART》一书的时候,我自己做了基于模拟器使用的系统镜像,供我学习ART使用。编译系统镜像的步骤是这样的:

  • 先创建一个avd设备,取个名字,比如innost-10_64。请从心底深切注意并认识到:这个虚拟设备和你自己买的那些用来做测试的真实手机差不多。因为不论是虚拟设备还是真实设备,上面跑的都是其实都是无色无味无形的软件。所以,模拟器是研究Android系统中那些和硬件无关模块的最好的试验场

  • 接下来,你要为这个设备选择系统。这个就是模拟同一个手机可以刷不同的系统。

avd manager中有下载镜像的地方。比如,API 29/Android 10.0/x86_64 非谷歌版镜像

了解一下,Android 10中镜像文件的制作

图中的Android 10.0(Goolge)是带GMS的镜像,不使用GMS那套东西的话我们是不需要的。

现在,avd manager中能看到innost-10_64这个设备,对应的系统信息。

了解一下,Android 10中镜像文件的制作

这个时候启动(点击右上角的绿色三角形),就能启动一个模拟设备。

作为一个搞机人,肯定必须必然要知道如何通过命令行启动这个设备。答案如下:

  • Android-Sdk/emulator/emulator -avd innost-10_64 -verbose

其中,加上-verbose选项后,emulator运行过程中(尤其是最开始的时候)会输出非常多的信息。

我们看看emulator支持的命令行选项,如下所示:

了解一下,Android 10中镜像文件的制作

上图中展示了emulator的很多选项。那我之前是如何用它的呢?

  • 在写Android ART一书的时候,我用Nougat源码,比如lunch选择innost_x86_64-userdebug

  • make systemimg后,在out/target/product/innost_x86_64下会生成一个system.img文件

  • emulator -avd innost-10-64       -system out/target/product/innost_x86_64/system.img 

通过这种方式,我让这个模拟器使用了自己搞出来的system.img文件。这样的话,我就可以魔改系统镜像,并随时用模拟设备来做测试了。

非常可惜且我奇怪了将近8个月的事情是,到了Android 10后,emulator -system选项貌似失效了。表现情况就是模拟器说无法加载system镜像。为这事,我还托人找谷歌AOSP开发者问了(对方给了一个搞法,但是和我的需求并不一致),我还一度怀疑是qemu或者kernel镜像哪里有问题.....

直到最近这两周,我偶然看了下init的代码,结合官方一些文档,才知道失败的原因。这里先说结论:

  • Android 10后,AOSP支持动态分区(dynamic partition)。即system分区大小不是固定的,而是可以动态调整。千万不要小看这个事情。在此之前,system分区大小是固定的,有些厂商把system分区搞得比较小,结果在从虚拟机从Dalvik切到ART的时候,发现system分区不够用。这个时候就得重新划分区,导致用户数据需要被清空,甚至有些设备根本就无法升级了解一下,Android 10中镜像文件的制作,而实际上存储空间还有很多空余。所以动态分区是为了解决这个问题。BTW,现在也不要高兴太早,动态分区需要kernel的支持,所以,老版本的设备可能依然不能升级...BTW,感觉现在windows/linux上好像都不支持随意调整分区大小吧...

  • Android 10后,AOSP内置了android verified boot(代码中叫AVB,或小写的avb),即启动校验。这个应该是配合动态分区来做的。启动校验肯定一直就有,但之前都是设备厂商/芯片厂商自己实现的。现在AOSP提供了AVB框架,貌似还支持Android Things相关系统。

Android 10中,make systemimage会多生成好几个image文件,我们看一下

了解一下,Android 10中镜像文件的制作

上图,多了什么super.img、vbmeta.img等一大堆东西,更奇怪的是,除了system.img之外,还有一个system-qemu.img...

事出反常必有妖。从这个角度看,Android 10的镜像文件的生成看来有重大变化。恩,是时候开始一次折腾之旅了,毕竟,人呐,不能闲死(此话的来历,见上篇公众号文章"了解一下,Android 10 Build系统"

Makefile,了解一下

首先,我们从m systemimage开始。但需要先学习下Makefile。依然是根据"了解一下,Android 10 Build系统"一文的知识,现在,我们必须将Makefile或其它.mk等文件看做是源码了(对,它属于Domain Specific Lanaguage。非常复杂)。

要真正看懂Makefile的话,需要了解Makefile的语法规则。它其实非常复杂,完整的语言详情可阅读官网https://www.gnu.org/software/make/manual/make.html

大体上说,Make是以rule为“单位”来驱动干活的。下面是一个rule的表示:

了解一下,Android 10中镜像文件的制作

其中:

  • target:prerequistes一行是一个rule的目标以及依赖

  • recipe等行以tab开头。表示为了要达到一个target所需要的动作。动作可以是shell命令,也可以是调用Makefile里定义的函数。

所以,要分析Makfile,标准步骤如下:

  1. 先找到对应的rule以及它的依赖

  2. 然后确定其该rule的recipes,

这样我们就能知道为了达到某个target,我们需要准备哪些东西,做哪些事情。

现在看我写的一个Makefile,其中涉及到看懂AOSP中makefile文件的一些关键知识。

了解一下,Android 10中镜像文件的制作

上图中:

  • 我们通过define定义了一个functest函数(Makefile中,它其实叫cannied recipe,译为"罐装配方"),但其实我感觉和一个函数没什么区别。

  • @echo:echo是shell命令。前面加上@号之后,make将不会打印recipe的内容——意思是,如果没有@符号的话,make将打印每个recipe的内容,然后再执行这recipe。

cannied recipe可以被调用,调用方式有两种:

  • $(functest):直接调用这个罐装配方,貌似不能传参数

  • $(call functest, param1, param2):通过内置的call函数调用。可以传参数

在functest内部,[email protected]代表调用它的target名,$^代表该target对应的依赖条件,而具体传过来的参数则是$1,$2表示。

做一个小测试,创建1.o,2.o,3.o,4.o文件后,执行make clean。clean将打印下面的内容:

了解一下,Android 10中镜像文件的制作

另外,Makfile内置了很多命令,有操作文件的,操作字符串的,有支持for-each循环的,等等等等,在上面的官方文档中都可以找到说明

到此,Makfile相关基础知识就有了(很奇怪为何没有什么工具能解析Makefile,android build系统里定义了超多的变量、rule,target,现在要了解AOSP build系统,还只能靠最暴力的搜索...)

追踪systemimage目标

systemimage是一个编译目标。build目录下是整个AOSP编译系统的源码。mgrep syystemimage,提取相关的内容如下:

了解一下,Android 10中镜像文件的制作

上面列了七个步骤。就是按前面提到的Makefile解析两步骤来找的(彻头彻尾的暴力搜索,毫无技术含量..)。

多说一下:Android中$(hide)的取值为

build/make/core/config.mk:180:hide := @

这样,make就不会打印recipe的内容了。

上面图片中,systemimage的制作是由python脚本build_image.py来实现的,并且依赖一个叫system_image_info.txt的文件。

我们马上来运行下这个脚本。不得不说,AOSP build工作真的不是写写配置就完事。上篇文章(了解一下,Android 10 Build系统)说了要懂go,这次大家也看到了,python也要懂...

了解一下,Android 10中镜像文件的制作

上图展示了system_image_info.txt,原来它包含了生成镜像文件的配置。我们这里就不讨论怎么生成这个txt文件了。按上面的makefile查找方法,很容易找到生成这个文件的Rule。

在build_img.py中,我们发现真正的镜像制作工具是mkuserimg_mke2fs

这个工具由AOSP提供,源码为system/extras/ext4_utils/。

注意,lunch后,mkuserimg_mke2fs已经在PATH变量中了,大家可以执行下玩玩。

追踪system-qemu.img目标

上面介绍了system.img生成方法,现在看看system-qemu.img的规则,通过mgrep system-qemu.img,得到下图:

了解一下,Android 10中镜像文件的制作

上图中有几个变量需要特别注意:

  • SGDISK => out/soong/host/linux-x86/bin/sgdisk  源码:external/gptfdisk。它是一个第三方开源库,其说明是“Command-line GUID partition table (GPT) manipulator for Linux and Unix”,也就是专用于Linux的硬盘分区工具

  • SIMG2IMG => out/soong/host/linux-x86/bin/simg2img 源码:system/core/libsparse。这个是将sparse镜像文件转换为非sparse镜像文件的工具,由AOSP提供。

  • MK_COMBINE_QEMU_IMAGE_SH => device/generic/goldfish/tools/mk_combined_img.py,一个工具

  • INSTALLED_SYSTEM_QEMU_CONFIG => out/target/product/generic_x86_64/system-qemu-config.txt。生成system-qemu.image的配置文件

老套路,python脚本加一个配置文件。所以,生成system-qemu.img的配置文件是system-qemu-config.txt,其内容如下,就两行文本

  • out/target/product/generic_x86_64/vbmeta.img vbmeta 1

  • out/target/product/generic_x86_64/super.img super 2

mk_combined_img.py其实内部调用的就是sgdisk。执行sgdisk --print system-qemu.img,可显示这个镜像文件的信息。其中有两个分区,分别叫vbmeta以及super。如下图所示:

了解一下,Android 10中镜像文件的制作

mk_combined_img并不复杂,核心还是利用sgdisk命令对vbmeta和super两个镜像文件进行合并处理。具体怎么玩sgdisk,这里就不说了。看了下,并不是很复杂(提示:不要想着去看sgdisk的实现,关键是怎么用它)

那么,vbmeta和super分别是什么呢?分别了解一下。

vbmeta.img,了解一下

生成vbmeta.img的罐装配方如下:

了解一下,Android 10中镜像文件的制作

其中:

  • INSTALLED_VBMETAIMG_TARGET => out/target/product/generic_x86_64/vbmeta.img

  • AVB_CHAIN_KEY_DIR => 一个存储key的文件夹,但是命令执行完后又删除了

  • AVBTOOL => out/host/linux-x86/bin/avbtool 。源码在external/avb下。avb就是谷歌android verified boot的全部代码。

  • 其他参数,不说了

avbtool这个工具是供host生成vbmeta镜像时用的。它会用到私钥进行数字签名和加密。如果没定义下面两个变量的话,默认

BOARD_AVB_ALGORITHM := SHA256_RSA4096

BOARD_AVB_KEY_PATH :=  external/avb/test/data/testkey_rsa4096.pem

使用上述指定的加密和签名方式。

testkey_rsa4096.pem是个私钥文件,内容如下:

了解一下,Android 10中镜像文件的制作

avbtool是android启动校验安全的核心组成部分,详细代码在external/avbtool下:

  • 生成镜像时使用工具avbtool

  • 设备上的系统启动时,使用libavb(由init使用)进行校验

关于avb,external/avb/README.md是详细了解它的关键文档,下面截个图看看:

了解一下,Android 10中镜像文件的制作

使用avbtool工具,看下我们vbmeta.img的内容

了解一下,Android 10中镜像文件的制作

如前所述,verified boot设备/芯片厂商早就有了。所以,文档里说,android verified boot只是verified boot的一种实现。另外,avb支持回退,A/B系统等各种看起来比较高级的功能。感兴趣的可以深入学习一把。BTW,Android Things(IoT)方面也可以使用它。

super.img,了解一下

生成super.img的罐装配方如下:

了解一下,Android 10中镜像文件的制作

  • lpmake:command-line tool for creating Android Logical Partition images 源码在system/extras/partition_tools。

  • build_super_image.py:内部就是调用lpmake生成最终的super.img文件

生成super.img用到了一个名为misc_info.txt的配置文件,其内容如下:

了解一下,Android 10中镜像文件的制作

我用这个配置文件,手动调用build_super_image.py生成一个dfptest/dfptest.img,输出如下。这个文件的md5和build生成的super.image一样。

了解一下,Android 10中镜像文件的制作

super.img到底干啥自用的呢?它是实现动态分区的关键。来看官网说明:

https://source.android.com/devices/tech/ota/dynamic_partitions/implement

了解一下,Android 10中镜像文件的制作

说白了,就是通过super+动态分区,system可以调整大小了。也就是说,system分区并不是真实的物理分区,而是逻辑分区。super分区远比system分区大。这样,system分区才能动态调整。另外,要说明的是,只有read-only的分区才能这么搞。/data分区就不适合,因为谁知道用户会存储多少内容呢....

到此,我们对Android 10如何生成镜像文件做了一些了解。涉及到相关知识有:

  • Makefile的一些语法,尤其是罐装配方

  • system.img和生成它的工具以及依赖。核心工具是mkuserimg_mke2fs

  • system-qemu.img和生成它的工具以及步骤。核心工具是sgdisk

  • vbmeta.img和生成它的相关工具以及步骤。核心工具是avbtool

  •  super.img和生成它的相关工具以及步骤。核心工具是lpmake

  • avb的相关知识以及super(动态分区)相关知识   

后续的安排

AOSP 10源码撸了大概五天,发现其中有一些需要了解的知识,比如APEX、ART等。接下来会对这些东西做一系列的“了解”。在此也欢迎大家提供一些目标,好让我们的"了解Android 10”系列飞得更远一点。

最后的最后

  • 我期望的结果不是朋友们从我的书、文章、博客后学会了什么知识,干成了什么,而应该是说,神农,我可是踩在你的肩膀上的喔

  • 关于学习方面的问题,我已经讨论完了。后面这个公众号将对一些基础的技术,新技术做一些学习和分享。也欢迎你的投稿。不过,正如我在公众号“联系方式”里说的那样——郑渊洁在童话大王《智齿》里有一句话令我印象深刻,大意是“我有权保持沉默,但你说的每一句话都可能成为我灵感的源泉”。所以,影响不是单向的,很可能我从你那学到的东西更多

了解一下,Android 10中镜像文件的制作

神农和朋友们的杂文集

长按识别二维码关注我们