第 7 章:虚拟机的创建
本文最后更新于:9 个月前
笔记侧重于了解模块/类的功能,尽量省略了代码的实现细节。
ART 虚拟机的启动流程:
Android 中所有 Java 进程都是由 zygote 进程 fork 而来,而 zygote 进程自己又是 Linux 系统上 init 进程通过解析配置脚本对于 32 位 CPU,配置脚本文件是 system/core/rootdir/init.zygote32.rc
来启动的:
- service zygote:告诉 init 进程——配置名为 zygote 的服务。
- /system/bin/app_process:指定 zygote 服务对应的二进制文件路径
init 进程会 fork 子进程来运行这个程序
。 - -Xzygote /system/bin --zygote --start-system-server:传递给 app_process 的启动参数。
从 /system/bin/app_process/app_main.cpp -> main 开始:
- 调用 AndroidRuntime 的 start 函数。
调用 JniInvocation 的 Init 函数
加载 ART 虚拟机的核心动态库。调用 AndroidRuntime 的 startVm 函数
启动 ART 虚拟机。调用 AndroidRuntime 的 JNI_CreateJavaVM
最终调用到 libart.sojava_vm_ext.cc
的 JNI_CreateJavaVM。
- java_vm_ext.cc -> JNI_CreateJavaVM:
- 调用 Runtime 的 Create 函数。
调用 ParseOptions 函数
将外部调用者传入的参数信息保存到 RuntimeArgumentMap。调用 Create 函数(参数携带了传入的参数信息)
采用单例方式创建 Runtime 对象ART 虚拟机的化身
。- 调用 Init 函数。
- 调用 MemMap
管理内存映射
的 Init 函数。 - 创建 OatFileManager
管理打开的 oat 文件
对象。 - 调用 Monitor
实现线程同步
的 Init 函数。 - 创建 MonitorList
维护了一组 Monitor 对象
对象。 - 调用 MonitorPool
创建 Monitor 对象
的 Create 函数。 - 创建 ThreadList
管理 ART 中的线程对象
对象。 - 创建 InternTable
字符串常量池
对象。 - 创建 Heap
和虚拟机的内存管理相关
对象。 - 创建 ArenaPool
管理多个内存单元
对象。 调用 CreateLinearAlloc 函数
创建 LinearAlloc可在 ArenaPool 上分配任意大小的内存
。- 调用 FaultManager
处理 SIGSEGV 信号
的 Init 函数。 - 创建 JavaVMExt
JNI 中代表 Java 虚拟机的对象
对象。 调用 Thread 的 Startup 函数、Attach 函数
初始化虚拟机主线程。- 创建 ClassLinker
处理和 Class 有关的工作
对象。 - 调用 MethodVerifier
用于校验 Java 方法
的 Init 函数。
- 调用 MemMap
- 调用 Init 函数。
调用 runtime 对象的 Start 函数
启动 Java 虚拟机。
- 调用 Runtime 的 Create 函数。
.oat 文件和 .art 文件:
一个包含 classes.dex 项的 jar 或 apk 文件经由 dex2oat 编译处理后会生成两个结果文件:
- .oat 文件:
- 本质是 ELF 格式的文件。
- boot oat 文件
boot image
:zygote 进程第一个 Java 进程
会首先加载一些基础与核心的 Oat 文件,这些 Oat 文件里包含了 Android 系统中所有 Java 程序所依赖的基础功能类比如 Java 标准类,也叫预加载类
。 - app image:其他 APP 进程
通过 zygote fork 得到,会继承预加载类
将加载 APK 包经过 dex2oat 得到的 Oat 文件。 - Oat 文件格式:
- OatHeader
该信息不存储在 Oat 文件的头部
。 - 每一个 OatDexFile 对应一个 DexFile,OatDexFile 指明了 DexFile 在 Oat 文件的位置
offset
、其他信息在 Oat 文件中的位置offset
。 - DexFile 包含一个 Dex 文件的全部内容。
- OatHeader
- .art 文件
ART 虚拟机代码里常提到的 Image 文件
:- boot image:来源于 /system/framework 下的核心 jar 包
core-oj.jar、framework.jar、org.apache.http.legacy.jar、okhttp.jar 等
的 Art 文件。 - app image:来源于某个 apk 的 Art 文件。
- Art 文件格式:
数据类型 名称 长度 描述 数组 magic_ 4 魔数 art
+\n
数组 version_ 4 版本号 029
+\0
image_begin_ 内存的起始位置 该 art 文件期望自己被映射到内存的什么位置
image_size_ 映射多大空间到内存 ImageSection 数组 sections_ kSectionCount = 9 ImageSection 用于描述一个 section 存储了不同的信息
在内存中的位置基于 image_begin_ 的偏移量
和大小。
ImageHeader 的内存被包括在第 1 个元素内。
image_size_ 只覆盖到 sections_[kSectionImageBitmap -1],最后一个元素 sections_[kSectionImageBitmap] 从 image_size_ 之后的某个按页大小对齐的位置开始storage_mode_ 文件内容是否为压缩存储 data_size_
- boot image:来源于 /system/framework 下的核心 jar 包
关键模块:
MemMap:
- 封装了和内存映射有关的操作
mmap、msync
,可以设置内存读写权限mprotect
,可创建基于文件的内存映射MapFile、MapFileAtAddress
以及匿名内存映射MapAnonymous
。 - mmap 系统调用的返回值只是一个代表地址的指针,而 MemMap 提供了更多的成员变量以便更好地使用 mmap 得到的这块映射内存。
OatFileManager:
- 用于管理虚拟机加载的 Oat 文件。
FaultManager:
- 信号处理管控类,管理了一系列的 FaultHandler 对象。
- 当虚拟机收到来自操作系统的信号时,FaultManager 将调用相关 FaultHandler
虚基类,实际由派生类处理
进行处理。 - fault_handler.cc -> FaultManager::Init() :
调用 SetUpArtAction 函数
为 action 对象设置 ART 虚拟机指定的信号处理函数。调用 sigaction 函数
将 action 对象设置为 SIGSEGV 信号的信号处理结构体。调用 ClaimSignalChain 函数
声明信号被使用。
- 信号处理:
- Linux 系统中,一个进程可以接受来自操作系统或其它进程发送的信号
分为标准和实时两大类信号,ART 只处理标准信号
。 - 信号由信号 ID 唯一标识,每一个信号都对应一个信号处理方法,如果进程不为信号设置特定的处理方法,操作系统将使用预定的方法来处理信号。
- fault_handler.cc -> FaultManager::HandleFault 函数处理 SIGSEGV 信号的流程:先判断该信号是否发生在编译时所生成的机器码
generated code
里。如果是,则交给 generated_code_handlers_ 中的成员处理;否则,则交给 other_handlers_ 中的成员处理。
- Linux 系统中,一个进程可以接受来自操作系统或其它进程发送的信号
Thread:
- Android 平台上线程的栈空间的创建过程:
- mmap 得到一块内存,其返回值为该内存的低地址
malloc 或 mmap 返回的都是某块内存的低地址
。 - 设置该内存从低地址开始的某段区域为不可访问。
- 得到该内存段的高地址,将其作为线程栈的栈底位置
Linux 系统上线程栈由高地址往低地址拓展
传递给 clone 系统调用。
- mmap 得到一块内存,其返回值为该内存的低地址
- thread.cc -> Thread::Startup():
调用 pthread_key_create 函数
创建 TLSThread Local Storage,一块调用线程特有的数据区域
。
- thread.cc -> Thread::Attach():
- 创建 Thread 对象。
- 初始化 tlsPtr_ 里的一些成员变量。
- 调用 Thread 的 Init 函数。
调用 InitStackHwm 函数
设置线程栈。调用 InitCpu 函数
【X86】让每个线程将自己的地址设置到 GDT 中,同时 GDT 关联 FS 寄存器,以便实现跳转前提是操作系统实现了 FS 的内容会随着线程切换而切换,因为 FS 只有一个
;【ARM】无实际操作。调用 InitTlsEntryPoints 函数
初始化 tlsPtr_ 里的 jni_entrypoints 和 quick_entrypointsgenerated code 和 ART 虚拟机交互的通道
。调用 Interpreter 的 InitInterpreterTls 函数
初始化解释执行模块的入口地址。
调用 Thread 的 InitStringEntryPoints 函数
设置 tlsPtr_.quick_entrypoints 中的某些成员。
- 创建 Thread 对象。
Heap:
- 位图:减少指针本身所占据的内存空间
将对象的指针转换成位图里的一个索引
。- 位图:0 或 1 表示对象是否存在
单位是字节
。- 如果指针未存储,就计算偏移量 -> 计算字节 -> 计算字节中的比特位,保存指针,同时将比特位设置为 1。
- 内存块 1:存储对象的指针
根据 CPU 架构,单位长度是 32 位或 64 位
。 - 内存块 2:存储更大块的空间
单位长度是 4k
。
- 位图:0 或 1 表示对象是否存在
JavaVmExt 和 JNIEnvExt:
- JavaVmExt 是 JavaVM 在 ART 虚拟机中的实际代表类。
- JavaVM 在 JNI 层中表示 Java 虚拟机,是 JNI 规范里指定的数据结构,不同虚拟机有不同的实现
比如 ART 虚拟机中是 Runtime
。 - 一个 Java 进程只有一个 JavaVM 实例。
- JavaVM 在 JNI 层中表示 Java 虚拟机,是 JNI 规范里指定的数据结构,不同虚拟机有不同的实现
- JNIEnvExt 是 JNIEnv 在 ART 虚拟机中的实际代表类。
- JNIEnv 表示 JNI 环境,是 JNI 规范里指定的数据结构,不同虚拟机有不同的实现。
- 每一个需要和 Java 交互的线程都有一个独立的 JNIEnv 对象。
ClassLinker:
- 类的连接器:关联和管理类。
- 入口函数:runtime.cc -> Runtime::Init:
- 创建 ClassLinker 对象。
调用 InitFromBootImage 函数
根据 boot image 初始化 ClassLinker 对象。
第 7 章:虚拟机的创建
https://weichao.io/411da2cc8d27/