AndroidApex技术分析调研1-apexd-bootstrap执行流程分析
分析代码基线 android10-release
APEX: Android Pony EXpress
安卓运行环境Android Runtime(ART)将会在安卓12中,添加到Project Mainline当中,这意味着可以通过Google Play商店对其进行更新,而无需完整的系统升级。Google还指出,Project Mainline将会推动更多系统关键组件模块独立更新。
apexd执行流程分析
开机阶段,init先启动apexd-bootstrap服务, 再启动apexd服务
system\core\rootdir\init.rc
# Cgroups are mounted right before early-init using list from /etc/cgroups.json
on early-init
# Run apexd-bootstrap so that APEXes that provide critical libraries
# become available. Note that this is executed as exec_start to ensure that
# the libraries are available to the processes started after this statement.
exec_start apexd-bootstrap
on post-fs-data
# /data/apex is now available. Start apexd to scan and activate APEXes.
mkdir /data/apex 0750 root system
mkdir /data/apex/active 0750 root system
mkdir /data/apex/backup 0700 root system
mkdir /data/apex/sessions 0700 root system
mkdir /data/app-staging 0750 system system
start apexd
system\apex\apexd\apexd.rc
service apexd /system/bin/apexd
class core
critical
user root
group system
shutdown critical
service apexd-bootstrap /system/bin/apexd --bootstrap
critical
user root
group system
oneshot
disabled
apexd-bootstrap执行流程分析
apexd源码入口system\apex\apexd\apexd_main.cpp
int main(int /*argc*/, char** argv) {
// Use CombinedLogger to also log to the kernel log.
android::base::InitLogging(argv, CombinedLogger());
if (argv[1] != nullptr) {
return HandleSubcommand(argv);
}
int HandleSubcommand(char** argv) {
if (strcmp("--pre-install", argv[1]) == 0) {
LOG(INFO) << "Preinstall subcommand detected";
return android::apex::RunPreInstall(argv);
}
if (strcmp("--post-install", argv[1]) == 0) {
LOG(INFO) << "Postinstall subcommand detected";
return android::apex::RunPostInstall(argv);
}
if (strcmp("--bootstrap", argv[1]) == 0) {
LOG(INFO) << "Bootstrap subcommand detected";
return android::apex::onBootstrap();
}
LOG(ERROR) << "Unknown subcommand: " << argv[1];
return 1;
}
apexd-bootstrap基本就是只执行了android::apex::onBootstrap
system\apex\apexd\apexd.cpp
int onBootstrap() {
gBootstrap = true;
Status preAllocate = preAllocateLoopDevices();
if (!preAllocate.Ok()) {
LOG(ERROR) << "Failed to pre-allocate loop devices : "
<< preAllocate.ErrorMessage();
}
Status status = collectApexKeys();
if (!status.Ok()) {
LOG(ERROR) << "Failed to collect APEX keys : " << status.ErrorMessage();
return 1;
}
// Activate built-in APEXes for processes launched before /data is mounted.
status = scanPackagesDirAndActivate(kApexPackageSystemDir);
if (!status.Ok()) {
LOG(ERROR) << "Failed to activate APEX files in " << kApexPackageSystemDir
<< " : " << status.ErrorMessage();
return 1;
}
LOG(INFO) << "Bootstrapping done";
return 0;
}
preAllocateLoopDevices中:
- 调用FindApexes, 递归找出kApexPackageBuiltinDirs(/system/apex, /product/apex)下所有后缀名为kApexPackageSuffix(.apex)的文件
- 对每个apex文件, 调用ApexFile::Open读取解析,保存返回值到apexFile
- 上一部执行完后:
- 如果所有apex文件都解析失败,就不创建loop device,loop设备创建个数size为0
- 如果每个apex文件都解析成功,且每个apex文件都是faltter形式,也不创建loop device,loop设备创建个数size为0
- 如果有apex文件解析成功,且此apex文件都是faltter形式, 统计这种情况的个数记为size. 如果此apex文件包名为com.android.runtime或com.android.tzdata,再给size+1
- 如果size为0, 或者设备不支持更新apex(ro.apex.updatable is not true),返回成功
- 如果size不为0,调用loop::preAllocateLoopDevices,在其中调用带LOOP_CTL_ADD的ioctl,创建出loop device
ApexFile::Open中:
- 先检查模块apex文件在 /ststem/apex/下是以目录存储
如果不是,就对每个apex文件: - 调用libziparchive的OpenArchive打开apex文件
- 得到apex文件内部的kImageFilename(apex_payload.img)的偏移和大小image_offset, image_size
- 找到apex内部的kManifestFilename(apex_manifest.json),将其内容保存到manifest_content
- 找到apex内部的kBundledPublicKeyFilename(apex_pubkey),将其内容保存到pubkey
- 将manifest_content传给ParseManifest,解析apex中的manifest,保存返回值到manifest
最后将image_offset, image_size, manifest, pubkey封装为ApexFile类型变量并返回
ParseManifest中:
- 检查kManifestFilename中是否缺少表示包名的name字段
- 检查kManifestFilename中是否缺少表示包版本号的version字段
- 将kManifestFilename内容转为ApexManifest类型变量并返回
collectApexKeys
- 调用collectEmbedddedApexKeysFromDir, 解析 kApexPackageBuiltinDirs 下每个apex文件信息, 将其manifest中包名作为key,公钥作为value,保存到一组k,v数组key_pairs
- 调用updateScannedApexKeys, 将key_pairs再保存一份到gScannedApexKeys
scanPackagesDirAndActivate
只扫描并激活kApexPackageSystemDir(/system/apex)下的apex,用于/data挂载之前的进程启动
- 首先还是获取kApexPackageSystemDir下的apex文件到scan
- 调用GetActivePackagesMap()
- 对scan中每个apex循环处理
- 如果此apex同包名已挂载,且挂载的版本更高,就不激活
- 如果ro.apex.updatable为false且此apex不是目录形式,就不激活
- 其余情况都调用activatePackageImpl激活此apex
GetActivePackagesMap
GetActivePackagesMap中主要是调用GetActivePackages
- 调用GetActivePackages(),取得目前已挂载的apex
- 将这些apex按 包名 到 版本号的映射保存到ret并返回ret
GetActivePackages中:
- 调用类MountedApexDatabase的对象gMountedApexes的ForallMountedApexes方法
[&](const std::string&, const MountedApexData& data, bool latest) {
if (!latest) {
return;
}
StatusOr<ApexFile> apexFile = ApexFile::Open(data.full_path);
if (!apexFile.Ok()) {
// TODO: Fail?
return;
}
ret.emplace_back(std::move(*apexFile));
}
类MountedApexDatabase的函数ForallMountedApexes接收一个函数指针作为参数,再里面执行此函数
template <typename T>
inline void ForallMountedApexes(const T& handler) const {
for (const auto& pkg : mounted_apexes_) {
for (const auto& pair : pkg.second) {
handler(pkg.first, pair.first, pair.second);
}
}
}
gMountedApexes的mounted_apexes_保存的是 已挂载的apex的包名 到 此apex的 映射
private:
// A map from package name to mounted apexes.
// Note: using std::maps to
// a) so we do not have to worry about iterator invalidation.
// b) do not have to const_cast (over std::set)
// TODO: Eventually this structure (and functions) need to be guarded by
// locks.
std::map<std::string, std::map<MountedApexData, bool>> mounted_apexes_;
所以这里就是先判断mounted_apexes_中每个成员的std::map<MountedApexData, bool>的bool类型的value是否为false,
如果是false就从函数指针[&]代表的函数直接return, 再继续执行ForallMountedApexes for中下一个循环
如果为true, 就打开mounted_apexes_中此成员的std::map<MountedApexData保存的apex, 将解析结果保存到getActivePackages中的ret变量, 再return继续执行ForallMountedApexes for中下一个循环
ForallMountedApexes执行完后,getActivePackages的ret就保存了目前已挂载的apex,然后直接返回ret
activatePackageImpl
activatePackageImpl中:
- 如果执行的是apexd-bootstrap服务, 对当前不是kBootstrapApexes(com.android.runtime,com.android.tzdata)的apex,直接返回成功
- 如果执行的是不是apexd-bootstrap服务, 且系统ro.apex.updatable为false, 对当前是kBootstrapApexes(com.android.runtime,com.android.tzdata)的apex,直接返回成功,因为这些apex在apexd-bootstrap服务已激活
- 执行gMountedApexes.ForallMountedApexes
gMountedApexes.ForallMountedApexes(
manifest.name(), [&](const MountedApexData& data, bool latest) {
StatusOr<ApexFile> otherApex = ApexFile::Open(data.full_path);
if (!otherApex.Ok()) {
return;
}
found_other_version = true;
if (static_cast<uint64_t>(otherApex->GetManifest().version()) ==
new_version) {
version_found_mounted = true;
version_found_active = latest;
}
if (static_cast<uint64_t>(otherApex->GetManifest().version()) >
new_version) {
is_newest_version = false;
}
});
template <typename T>
inline void ForallMountedApexes(const std::string& package,
const T& handler) const {
auto it = mounted_apexes_.find(package);
if (it == mounted_apexes_.end()) {
return;
}
for (auto& pair : it->second) {
handler(pair.first, pair.second);
}
}
这里对activatePackageImpl中当前apex,先判断是否已在登记的挂载的apex中, 如果没有就从ForallMountedApexes返回继续
如果在登记的挂载的apex中, 说明此apex之前挂载过, 就提取出std::map<std::string, std::map<MountedApexData, bool>> mounted_apexes_中std::map<MountedApexData, bool>的key和value, 作为ForallMountedApexes第二个函数指针的参数, 其中传给函数指针的第二个参数含义为mounted_apexes_中此apex是否是最新的已挂载版本(bool latest)
函数指针流程中:
- 先打开此apex之前已挂载版本的文件,
- 如果之前已挂载的版本号,与activatePackageImpl当前apex版本号相同,就将version_found_mounted设为true,version_found_active设为latest
- 如果之前已挂载的版本号,大于activatePackageImpl当前apex版本号,就将is_newest_version设为false
函数指针执行完后退出ForallMountedApexes继续activatePackageImpl:
- version_found_active为true,说明说明此apex之前已挂载,且当时挂载的是最新版本,返回成功
- 设置字符串mountPoint为"/apex/包名@包版本号"
- 如果version_found_mounted为false, 就先调用apexd_private::MountPackage挂载该apex
- 如果is_newest_version为true,说明当前apex版本号>=mounted_apexes_之前已挂载apex, 调用apexd_private::BindMount绑定挂载
- 最后,如果刚才activatePackageImpl中挂载的是最新版本,就将mounted_apexes_中可能同包名的apex的latest设为flase