Android 9.0 ART编译分析(二)-Installd触发dex2oat编译流程

原创内容,转载请注明出处,多谢配合。

这个通路是经过PMS,最终由installd触发的主apk编译。

一、Installd介绍

Installd是Android native层的服务进程,在init阶段通过init.rc对应的配置服务启动的。

#frameworks/native/cmds/installd/Android.bp

cc_binary {
...
    init_rc: ["installd.rc"],
}

#frameworks/native/cmds/installd/installd.rc
service installd /system/bin/installd
    class main

Android 中提供了PMS来进行包管理工作,对上层交付的内容包括应用安装、卸载、以及Pakcage信息的管理。但是这个过程中牵涉到的目录创建、安装包copy、dex优化等内容,最终是交给installd去执行的,为什么?因为从上面我们知道了installd是由init孵化的(拥有root权限),而PMS是由zygote孵化的(只拥有system权限),显然installd拥有的权限远远高于PMS,因此它主要负责处理需要root权限的操作。

二、通路介绍

通过Installd触发dex2oat执行编译,上层都是通过PMS来执行的。简单打几个调用栈看看:

1) install
08-15 13:17:22.435 1548 1688 I PackageManager.DexOptimizer: ZHT Running dexopt (dexoptNeeded=1) on: /data/app/com.ss.android.article.news-UcwHwLxdQxcoHjQhpmsTkA==/base.apk pkg=com.ss.android.article.news isa=arm dexoptFlags=boot_complete,public,enable_hidden_api_checks targetFilter=quicken oatDir=/data/app/com.ss.android.article.news-UcwHwLxdQxcoHjQhpmsTkA==/oat classLoaderContext=PCL[/system/framework/org.apache.http.legacy.boot.jar]
dex2oatpath=
com.android.server.pm.PackageDexOptimizer.performDexOptLI:253
 com.android.server.pm.PackageDexOptimizer.performDexOpt:149
 com.android.server.pm.PackageManagerService.installPackageLI:18215
com.android.server.pm.PackageManagerService.installPackageTracedLI:17635
 com.android.server.pm.PackageManagerService.access$3300:407
 com.android.server.pm.PackageManagerService$10.run:15465
android.os.Handler.handleCallback:873
android.os.Handler.dispatchMessage:99
android.os.Looper.loop:201
android.os.HandlerThread.run:65
com.android.server.ServiceThread.run:45
2) post boot
08-15 13:29:22.200  1450  6499 I PackageManager.DexOptimizer: ZHT Running dexopt (dexoptNeeded=1) on: /data/app/com.ss.android.article.news-UcwHwLxdQxcoHjQhpmsTkA==/base.apk pkg=com.ss.android.article.news isa=arm dexoptFlags=boot_complete,profile_guided,enable_hidden_api_checks targetFilter=verify oatDir=/data/app/com.ss.android.article.news-UcwHwLxdQxcoHjQhpmsTkA==/oat classLoaderContext=PCL[/system/framework/org.apache.http.legacy.boot.jar] dex2oatpath=
com.android.server.pm.PackageDexOptimizer.performDexOptLI:253
com.android.server.pm.PackageDexOptimizer.performDexOpt:149
com.android.server.pm.PackageManagerService.performDexOptInternalWithDependenciesLI:9723
com.android.server.pm.PackageManagerService.performDexOptInternal:9674
com.android.server.pm.PackageManagerService.performDexOptTraced:9652
com.android.server.pm.PackageManagerService.performDexOptWithStatus:9637
com.android.server.pm.BackgroundDexOptService.postBootUpdate:233
com.android.server.pm.BackgroundDexOptService.access$000:52
com.android.server.pm.BackgroundDexOptService$1.run:183

另外oat 、idle就不一一例举了,获取打印log比较麻烦一点,但是基本上流程也差不多,最终都汇集到com.android.server.pm.PackageDexOptimizer.dexOptPath()方法

三、编译流程
frameworks/base/services/core/java/com/android/server/pm/PackageDexOptimizer.java
 
private int dexOptPath(PackageParser.Package pkg, String path, String isa,
 String compilerFilter, boolean profileUpdated, String classLoaderContext,
 int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
 String profileName, String dexMetadataPath, int compilationReason) {
    //判断是否主要做dex2oat编译
    int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
 profileUpdated, downgrade);
 if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
        return DEX_OPT_SKIPPED;
 }
 ...
 //通过installd走dex2oat编译
 mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
 compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
 false /* downgrade*/, pkg.applicationInfo.targetSdkVersion,
 profileName, dexMetadataPath,
 getAugmentedReasonName(compilationReason, dexMetadataPath != null));
...
}

这个方法主要就干了两件事:判断是否需要做dex2oat 和 通过Installer binder call给installd(7.0及之前它与Installer是进行socket通信) 去执行dexopt操作。

下面先来看一张整体流程图:

经PMS由Installd触发的dex2oat编译流程
2.1 判断是否需要做dex2oat的逻辑:

从时序图看,最终逻辑在oat_file_assisatant.cc
这里不跟代码了,提炼下核心逻辑要点:

1)是否需要编译的类型分类:
class OatFileAssistant {
//是否需要编译
enum DexOptNeeded {
 kNoDexOptNeeded = 0, //已经编译过,不需要再编译
 kDex2OatFromScratch = 1, //有dex文件,但还没编过
 kDex2OatForBootImage = 2,//oat文件不能匹配boot image(系统升级 boot image会变化)
 kDex2OatForFilter = 3,//oat文件不能匹配compiler filter
 kDex2OatForRelocation = 4, //还是oat文件与boot image不匹配,但是没有深刻理解relocation是什么场景
 }

 //对应的几种状态
enum OatStatus {
 kOatCannotOpen, //oat文件不存在
 kOatDexOutOfDate, //oat文件过期,与dex文件不匹配
 kOatBootImageOutOfDate, //对应kDex2OatForBootImage,oat文件与boot image不匹配
 kOatRelocationOutOfDate,//对应kDex2OatForRelocation oat文件与boot image不匹配
 kOatUpToDate,//oat文件与dex文件和 boot image都匹配
 };
}

核心逻辑:

art/runtime/oat_file_assistant.cc
 
int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target,
                                      bool profile_changed,
                                      bool downgrade,
                                      ClassLoaderContext* class_loader_context) {
  OatFileInfo& info = GetBestInfo();//获取OatFileInfo对应实例
  DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target,
                                                    profile_changed,
                                                    downgrade,
                                                    class_loader_context);
  if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) {
    return dexopt_needed;
  }
  return -dexopt_needed;
}

这里有两个概念需要了解:

  • oat location 与odex location 分别是什么?
    app的安装系统目录data/app和system/app,这个路径下每个应用都会生成一个类似包名+乱码的一个文件夹,里面存放主apk以及编译文件。
    oat location对应的是oat文件夹路径
    odex location对应的是oat/arm or arm64/odex文件路径
    如果有odex优先用odex。

  • 正负数是指的什么?
    正数对应in_odex_path ,负数对应out_oat_path

2)DexOptNeeded各类型赋值

这里主要是看看这几个判断类型是在哪赋值的,这样就知道编译的触发条件有哪些了

if (!oat_file_assistant.IsUpToDate()) { 
 switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/false, /*out*/ &error_msg)) {
...
}

过期逻辑一般是先IsUpToDate判断是否过期,然后MakeUpToDate做过期操作,很明显这个部分还是在oat_file_assistant.cc做的

art/runtime/oat_file_assistant.cc

bool OatFileAssistant::IsUpToDate() {
  return GetBestInfo().Status() == kOatUpToDate;//是不是已经编过了
}

没有编过就通过MakeUpToDate来置DexOptNeeded编译类型

OatFileAssistant::MakeUpToDate(bool profile_changed, std::string* error_msg) {
  CompilerFilter::Filter target;
  if (!GetRuntimeCompilerFilterOption(&target, error_msg)) {
    return kUpdateNotAttempted; //We wanted to update the code, but determined we should not make the attempt.
  }
  OatFileInfo& info = GetBestInfo();
  switch (info.GetDexOptNeeded(target, profile_changed)) { //这里有各种条件来赋值DexOptNeeded,条件跟之前的描述差不多

    case kNoDexOptNeeded:
      return kUpdateSucceeded;//We successfully made the code up to date (possibly by doing nothing).

    // TODO: For now, don't bother with all the different ways we can call
 // dex2oat to generate the oat file. Always generate the oat file as if it
 // were kDex2OatFromScratch.
    case kDex2OatFromScratch:
    case kDex2OatForBootImage:
    case kDex2OatForRelocation:
    case kDex2OatForFilter:
      return GenerateOatFileNoChecks(info, target, error_msg);//mark the odex file has changed and we should try to reload.

  }
  UNREACHABLE();
}

主要赋值在GetBestInfo()

OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() {
  // TODO(calin): Document the side effects of class loading when
  // running dalvikvm command line.
  if (dex_parent_writable_) {
    // If the parent of the dex file is writable it means that we can
    // create the odex file. In this case we unconditionally pick the odex
    // as the best oat file. This corresponds to the regular use case when
    // apps gets installed or when they load private, secondary dex file.
    // For apps on the system partition the odex location will not be
    // writable and thus the oat location might be more up to date.
    return odex_;
  }

  // We cannot write to the odex location. This must be a system app.

  // If the oat location is usable take it.
  if (oat_.IsUseable()) {
    return oat_;
  }

  // The oat file is not usable but the odex file might be up to date.
  // This is an indication that we are dealing with an up to date prebuilt
  // (that doesn't need relocation).
  if (odex_.Status() == kOatUpToDate) {
    return odex_;
  }

  // The oat file is not usable and the odex file is not up to date.
  // However we have access to the original dex file which means we can make
  // the oat location up to date.
  if (HasOriginalDexFiles()) {
    return oat_;
  }

  // We got into the worst situation here:
  // - the oat location is not usable
  // - the prebuild odex location is not up to date
  // - and we don't have the original dex file anymore (stripped).
  // Pick the odex if it exists, or the oat if not.
  return (odex_.Status() == kOatCannotOpen) ? oat_ : odex_;
}

这里注释也很明显,不赘述了。

2.2 installd执行dexopt

8.0之后除了socket换成了binder call ,另外调用也发送了变化,也不像7.0的时候在installd.cpp中通过cmds命令对应depot了 { "dexopt", , do_dexopt },现在操作是在dexopt.cpp中进行。

frameworks/native/cmds/installd/dexopt.cpp
int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* instruction_set,
 int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
 const char* volume_uuid, const char* shared_libraries, const char* se_info) {
 ...
 run_dex2oat(input_fd.get(),
            out_oat_fd.get(),
            in_vdex_fd.get(),
            out_vdex_fd.get(),
            image_fd.get(),
            dex_path,
            out_oat_path,
            swap_fd.get(),
            instruction_set,
            compiler_filter,
            debuggable,
            boot_complete,
            reference_profile_fd.get(),
            shared_libraries);

 ...
 return 0;
}

这里主要就是向dex2oat可执行文件传参并执行编译操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容