ART深入浅出3--了解Boot.art和boot-*.art

本文基于Android 7.1,不过因为从BSP拿到的版本略有区别,所以本文提到的源码未必与读者找到的源码完全一致。本文在提供源码片断时,将按照 <源码相对android工程的路径>:<行号> <类名> <函数名> 的方式,如果行号对不上,请参考类名和函数名来找到对应的源码。

在Android7.0之前,所有bootclasspath指定的库会被同一编译成为boot.oat和boot.art两个文件。其中boot.oat包含了编译后的机器码指令,而boot.art文件,则是一个类对象映像。boot.art内包含了所有framework/base/preloaded-classes文件列出的所有类。这些类会被一次性的载入到内存中,并可以被直接使用。
在Android7.0及以后,boot.art和boot.oat被分成了以boot-.oat/art方式的多个库。当有一个库被更新,不至于整个boot.art/boot.oat被更新。
当然,你有时也看不到这些被分开的boot.art/boot.oat库,这是因为,Android7.0可以通过参数来控制。
为了简便,我们只讨论所有bootclasspath包作成一个boot.art/boot.art的情况。
除此之外,如果在编译ROM时,选择打开ODEX,那么,编译程序就会生成boot.oat/boot.art,连同system/app, system/priv-app下面的程序,都会生成它们的oat文件。一般boot.oat/boot.art(可能是boot-.oat, boot- .art) 会放在 /system/framework/arm64 或者 /system/framework/arm下面。在系统初始化时,这些预编译好的oat/art文件会被拷贝到/data/dalvik-cache下面。在android7.0上,则会直接读取它们,不用再拷贝了。

boot.art文件的加载

为了探究boot.art文件的格式,我们首先要大致了解下它的加载过程。
boot.art文件是在创建heap的时候加载的。在构造函数Heap::Heap,我们可以看到如下的代码:
art/gc/heap.cc:278 Heap::Heap

  // Load image space(s).
  if (!image_file_name.empty()) {
   ....
    for (size_t index = 0; index < image_file_names.size(); ++index) {
      std::string& image_name = image_file_names[index];
      std::string error_msg;
      space::ImageSpace* boot_image_space = space::ImageSpace::CreateBootImage(
          image_name.c_str(),
          image_instruction_set,
          index > 0,
          &error_msg);
....
  }

我把其中很多代码都省略了,我们只看我们关心的代码space::ImageSpace::CreateBootImage 函数。
heap把内存分成了若干的space,每个space管理一大块内存,每个space下面都有自己的内存分配和回收算法,这样可以应对不同的内存需求。比如,有些内存一但分配,就不释放的,如为class,method, field这些元对象分配的内存;一些大内存对象,如图片的像素数组等,一个对象就占用很多内存;或者是那些频繁创建但是很快丢弃的对象。等等。
Boot.art内部保存的,主要是一些class,method,field,还有一些非常基础的类对象,比如用于管理dex数据的dexcache对象等,这些对象,一旦分配,就不会释放,不仅是在一个应用的生命周期,而是在整个系统的周期内。也就是说,只有手机关机了,它们才会被丢弃掉。故此,ART将这些数据做成映像,系统一启动就把它们载入到内存,而且是zygote进程的内存,这样所有zygote的子进程就可以直接享用这些数据了。
下面
art/runtime/gc/space/image_space.cc:1149 ImageSpace::Init

ImageSpace* ImageSpace::CreateBootImage(const char* image_location,
                                        const InstructionSet image_isa,
                                        bool secondary_image,
                                        std::string* error_msg) {
....
  bool found_image = FindImageFilename(image_location, image_isa, &system_filename,
                                       &has_system, &cache_filename, &dalvik_cache_exists,
                                       &has_cache, &is_global_cache);

....
  ImageSpace* space;
  bool relocate = Runtime::Current()->ShouldRelocate();
  bool can_compile = Runtime::Current()->IsImageDex2OatEnabled();
  if (found_image) {
....
      space = ImageSpace::Init(image_filename->c_str(),
                               image_location,
                               !(is_system || relocated_version_used),
                               /* oat_file */nullptr,
                               error_msg);
   ....
    return space;
  }
}

去除一些逻辑,可以看到一个关键函数ImageSpace::Init 其他的逻辑我们现在不关心。

ImageSpace* ImageSpace::Init(const char* image_filename,
                             const char* image_location,
                             bool validate_oat_file,
                             const OatFile* oat_file,
                             std::string* error_msg) {
....
  ImageHeader temp_image_header;
  ImageHeader* image_header = &temp_image_header;
  {
    TimingLogger::ScopedTiming timing("ReadImageHeader", &logger);
    bool success = file->ReadFully(image_header, sizeof(*image_header));
    if (!success || !image_header->IsValid()) {
      *error_msg = StringPrintf("Invalid image header in '%s'", image_filename);
      return nullptr;
    }
  }
  ...

  std::vector addresses(1, image_header->GetImageBegin());
  if (image_header->IsPic()) {
    // Can also map at a random low_4gb address since we can relocate in-place.
    addresses.push_back(nullptr);
  }

  // Note: The image header is part of the image due to mmap page alignment required of offset.
  std::unique_ptr map;
  std::string temp_error_msg;
  for (uint8_t* address : addresses) {
    TimingLogger::ScopedTiming timing("MapImageFile", &logger);
    // Only care about the error message for the last address in addresses. We want to avoid the
    // overhead of printing the process maps if we can relocate.
    std::string* out_error_msg = (address == addresses.back()) ? &temp_error_msg : nullptr;
    const ImageHeader::StorageMode storage_mode = image_header->GetStorageMode();
    if (storage_mode == ImageHeader::kStorageModeUncompressed) {
      map.reset(MemMap::MapFileAtAddress(address,
                                         image_header->GetImageSize(),
                                         PROT_READ | PROT_WRITE,
                                         MAP_PRIVATE,
                                         file->Fd(),
                                         0,
                                         /*low_4gb*/true,
                                         /*reuse*/false,
                                         image_filename,
                                         /*out*/out_error_msg));
    } else {
      ....//解压再映射部分的代码,不是核心功能,不去管他。
    }
    if (map != nullptr) {
      break;
    }
  }

....
  {
    TimingLogger::ScopedTiming timing("RelocateImage", &logger);
    if (!RelocateInPlace(*image_header,
                         map->Begin(),
                         bitmap.get(),
                         oat_file,
                         error_msg)) {
      return nullptr;
    }
  }
  // We only want the mirror object, not the ArtFields and ArtMethods.
  std::unique_ptr space(new ImageSpace(image_filename,
                                                   image_location,
                                                   map.release(),
                                                   bitmap.release(),
                                                   image_end));

  ....
  return space.release();
}

我去除了很多逻辑判断的代码,以及解压压缩image的代码。去掉这些后,我们发现,它实际上只有很简单的两个步骤:

  1. 解析ImageHeader,拿到基本信息
  2. 将image映射到内存中,映射的时候需要映射到ImageHeader存好的地址。
    Android N 支持PIC模式,即可重定向Image的位置。这是个可选项。我们暂时不讨论PIC模式,先了解固定地址映射的情况后,再去了解它。

boot.art的文件格式

art/runtime/image.h:73

class PACKED(4) ImageHeader {
....
private:
  static const uint8_t kImageMagic[4];
  static const uint8_t kImageVersion[4];

  uint8_t magic_[4];
  uint8_t version_[4];

  // Required base address for mapping the image.
  uint32_t image_begin_;

  // Image size, not page aligned.
  uint32_t image_size_;

  // Checksum of the oat file we link to for load time sanity check.
  uint32_t oat_checksum_;

  // Start address for oat file. Will be before oat_data_begin_ for .so files.
  uint32_t oat_file_begin_;

  // Required oat address expected by image Method::GetCode() pointers.
  uint32_t oat_data_begin_;

  // End of oat data address range for this image file.
  uint32_t oat_data_end_;

  // End of oat file address range. will be after oat_data_end_ for
  // .so files. Used for positioning a following alloc spaces.
  uint32_t oat_file_end_;

  // Boot image begin and end (app image headers only).
  uint32_t boot_image_begin_;
  uint32_t boot_image_size_;

  // Boot oat begin and end (app image headers only).
  uint32_t boot_oat_begin_;
  uint32_t boot_oat_size_;

  // TODO: We should probably insert a boot image checksum for app images.

  // The total delta that this image has been patched.
  int32_t patch_delta_;

  // Absolute address of an Object[] of objects needed to reinitialize from an image.
  uint32_t image_roots_;

  // Pointer size, this affects the size of the ArtMethods.
  uint32_t pointer_size_;

  // Boolean (0 or 1) to denote if the image was compiled with --compile-pic option
  const uint32_t compile_pic_;

  // Boolean (0 or 1) to denote if the image can be mapped at a random address, this only refers to
  // the .art file. Currently, app oat files do not depend on their app image. There are no pointers
  // from the app oat code to the app image.
  const uint32_t is_pic_;

  // Image section sizes/offsets correspond to the uncompressed form.
  ImageSection sections_[kSectionCount];

  // Image methods, may be inside of the boot image for app images.
  uint64_t image_methods_[kImageMethodsCount];

  // Storage method for the image, the image may be compressed.
  StorageMode storage_mode_;

  // Data size for the image data excluding the bitmap and the header. For compressed images, this
  // is the compressed size in the file.
  uint32_t data_size_;
....
};

内容虽然多,但是我们可以分组了解

  uint8_t magic_[4];
  uint8_t version_[4];

这个没什么好说的,Android N上固定是 “art\n” 和 “030\0”。

  // Required base address for mapping the image.
  uint32_t image_begin_;

  // Image size, not page aligned.
  uint32_t image_size_;

这是Image本身的大小和映射的开始地址。Android N支持PIC,在是PIC模式下,ART试图加载image到image_begin_ 处,如果失败,就会把image加载到任意地址,然后在进行重定位。
当然,这个“任意”地址也是有限制的。在ART内部,ART直接用Object的指针访问它们,即Object的引用值就是这个Object在内存中的地址。不过,按照java规范的要求,一个Object的引用只能是4字节,所以,在64位系统中,Object在内存中只能存储在高32为0的地址内,这也就是说,image以及所有的java对象都只能在这个范围内分配

  // Checksum of the oat file we link to for load time sanity check.
  uint32_t oat_checksum_;

  // Start address for oat file. Will be before oat_data_begin_ for .so files.
  uint32_t oat_file_begin_;

  // Required oat address expected by image Method::GetCode() pointers.
  uint32_t oat_data_begin_;

  // End of oat data address range for this image file.
  uint32_t oat_data_end_;

  // End of oat file address range. will be after oat_data_end_ for
  // .so files. Used for positioning a following alloc spaces.
  uint32_t oat_file_end_;

这4个值和boot.art对应的boot.oat的信息。boot.art和boot.oat必须是对应的,否则就会出现问题。为此,boot.art中存储了它对应的boot.oat的校验和。每次加载时必须进行校验,通过后才可以加载,否则就会退出系统。
oat_file_begin的地址和oat_data_begin是有一定区别的。oat file借用了elf文件的格式,它可以看作一个标准的elf文件。oat_file_begin是这个elf文件的加载地址。oat真正的数据,是保存在elf文件的”.oatdata”以及和它挨着的”.oattext”段, oat_data_begin,就是oatdata的地址。一般oat_file_begin和oat_data_begin相差0x1000,这个空间映射的是elf文件的头信息。

  // Boot image begin and end (app image headers only).
  uint32_t boot_image_begin_;
  uint32_t boot_image_size_;

  // Boot oat begin and end (app image headers only).
  uint32_t boot_oat_begin_;
  uint32_t boot_oat_size_;

在只有app有art的情况下才使用。实际上,只有从Android N开始才正式支持app有image,不过,我还没有看到过实际产品中开启app的image。所以不用管他了。

  // The total delta that this image has been patched.
  int32_t patch_delta_;

这个值有patchoat命令修改,在Android N上用的少了。没有太大意义。

  uint32_t image_roots_;

  // Pointer size, this affects the size of the ArtMethods.
  uint32_t pointer_size_;

image_roots_的绝对地址,这是一个Object[]数组,里面包含了最基本的roots对象,后面详细介绍。

c++
// Image section sizes/offsets correspond to the uncompressed form.
ImageSection sections_[kSectionCount];

ImageSection保存了一些可以优化的东西,不是关键。
c++
// Image methods, may be inside of the boot image for app images.
uint64_t image_methods_[kImageMethodsCount];

这是保存一些运行时的method。所谓运行时的method,指不是由用户代码生成,而是在art内部定义,用于完成一些特殊任务的方法。
c++
enum ImageMethod {
kResolutionMethod,
kImtConflictMethod,
kImtUnimplementedMethod,
kCalleeSaveMethod,
kRefsOnlySaveMethod,
kRefsAndArgsSaveMethod,
kImageMethodsCount, // Number of elements in enum.
};

比如ResolutionMethod,用于解析一个方法。当加载一个dex文件或者class的时候,dex文件内引用到的method的初值都被设置为该运行时函数。这个函数被调用时,会先解析出真正的函数,然后在转入进去。
CalleeSaveMethod是用于保存当前调用的java函数信息。比如,java代码抛出了一个异常,该异常对象需要保存抛出位置的调用栈列表。这需要ART遍历当前的调用栈。于是,ART就会生成一个CalleeSaveMethod方法,将当前正在使用的寄存器全部保存到这个CalleeSaveMethod函数的栈上,然后再进行遍历。这是Android M以后才加入的特性。

image_roots

image_roots_是一个Object[]数组,它的成员定义是
c++
enum ImageRoot {
kDexCaches,
kClassRoots,
kImageRootsMax,
};

这两个成员指出的都是数组对象。

DexCaches

通过 ImageHeader::GetImageRoot(ImageHeader::kDexCaches)得到DexCache[] 。
DexCache是定义在 libcore/libart/src/main/java/lang/DexCache.java中的一个核心对象。它是Dex文件的内存对象,将Dex文件内常用数据缓存成可以直接使用的各种对象。ART用这些对象加载类、访问代码等。

ClassRoots

通过ImageHeader::GetImageRoot(ImageHeader::kClassRoots)获得,保存在ClassLinker的class_roots_成员内。
这是一个Class[] 数组对象,它的成员都是java/lang下最基本的类,比如Class, Object, String, Reference,以及一些特殊的数组类,Class[], Object[], 和基础对象,boolean.class, int.class,void.class等等。

Image的主体内容

Image剩下的部分,都是一些class了。这些内容是从framework/base/preloaded-classes文件中列出的那些类。这些内容加载到内存后,就可以直接被访问了。它们的结构和class对象的结构是完全一样的,所不同的就是它们的数据全部被压缩到了一起。

你可能感兴趣的:(ART深入浅出)