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
  • 上一部执行完后:
  1. 如果所有apex文件都解析失败,就不创建loop device,loop设备创建个数size为0
  2. 如果每个apex文件都解析成功,且每个apex文件都是faltter形式,也不创建loop device,loop设备创建个数size为0
  3. 如果有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
posted @ 2023-02-19 15:23  leo21sun  阅读(1144)  评论(0编辑  收藏  举报