本文基于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-
当然,你有时也看不到这些被分开的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文件是在创建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的代码。去掉这些后,我们发现,它实际上只有很简单的两个步骤:
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_是一个Object[]数组,它的成员定义是
c++
enum ImageRoot {
kDexCaches,
kClassRoots,
kImageRootsMax,
};
这两个成员指出的都是数组对象。
通过 ImageHeader::GetImageRoot(ImageHeader::kDexCaches)得到DexCache[] 。
DexCache是定义在 libcore/libart/src/main/java/lang/DexCache.java中的一个核心对象。它是Dex文件的内存对象,将Dex文件内常用数据缓存成可以直接使用的各种对象。ART用这些对象加载类、访问代码等。
通过ImageHeader::GetImageRoot(ImageHeader::kClassRoots)获得,保存在ClassLinker的class_roots_成员内。
这是一个Class[] 数组对象,它的成员都是java/lang下最基本的类,比如Class, Object, String, Reference,以及一些特殊的数组类,Class[], Object[], 和基础对象,boolean.class, int.class,void.class等等。
Image剩下的部分,都是一些class了。这些内容是从framework/base/preloaded-classes文件中列出的那些类。这些内容加载到内存后,就可以直接被访问了。它们的结构和class对象的结构是完全一样的,所不同的就是它们的数据全部被压缩到了一起。