Dalvik名字来源于其作者祖先居住的小村庄,老外喜欢起这种名字,类似的还有ubuntu、Kali,虽说现在使用ART取代了Dalvik,但是感觉简单学习一下还是有用的。
一、.与java虚拟机对比
Java虚拟机解析class文件,Dalvik虚拟机解析dex(dalvik executable)文件
android SDK 的dx工具可以将java字节码转换为Dalvik字节码,对java类文件进行了压缩,去除了冗余信息,因此体积更小
架构不同,Java虚拟机基于栈结构,Dalvik基于寄存器架构,对于手机设备来说后者更适用,并且速度更快
下面以Hello.java 为示例文件,分析两种字节码的区别
public class Hello {
public int foo(int a,int b){
return (a+b)*(a-b);
}
public static void main(String[] argc){
Hello hello=new Hello();
System.out.println(hello.foo(5,3));
}
}
1. javac Hello.java 将其编译为Hello.class
2. ./dx --dex --output=Hello.dex Hello.class 将class文件转化为dex文件
3. javap -c -classpath . Hello得到反编译代码,这是jvm指令集,这里只列出foo函数对应代码。
java字节码一个指令为一个字节,PC计数器以字节为单位记录指令偏移量,图中PC对应的指令为iadd,前两行指令取出了函数两个参数并加载到求值栈
4.dexdump -d Hello.dex 得到的是dalvik指令集代码,这里只列出foo函数对应代码,左边的上半部分和下半部分分别对应指令的十六进制和助记符格式。Dalvik维护一个pc计数器和一个调用栈,但是这个调用栈维护的是一份寄存器列表。
二、安卓系统如何启动及dalvik虚拟机如何运行
1.系统架构以及运行流程
(1)Loader层:加载和运行引导程序
boot ROM:开机时,引导芯片从rom中预设的代码开始执行,然后将引导程序加载到ram。
boot loader:运行引导程序,主要是检查ram、初始化参数等。
(2)kernel层:Android内核层,在这里开机刚刚完成进入系统
启动swapper进程(pid=0的进程),这是系统初始化过程kernel创建的第一个进程,用于初始化进程管理、内存管理、加载驱动等工作
启动kthreadd进程,这是linux系统的内核进程,会创建内核工作线程kworkder、软中断进程ksoftirqd和thermal等内核守护进程,kthreadd是所有内核进程的父进程。
(3)native层(C++ Framework层):包含C++库、硬件抽象层(HAL)、Dalvik虚拟机或者安卓运行时(ART)
由init进程孵化出用户空间的守护进程、开机动画、hal层等。init是linux的守护进程,是所有用户空间进程的父进程.
init进程孵化出MediaServer进程,负责启动和管理C++Framework层,包含AudioFlinger,Camera Service等服务。
特别的,init进程会解析init.rc文件,然后孵化出zygote进程,zygote进程是Android系统的第一个java进程(虚拟机进程),zygote是所有java进程的父进程
(4)java Framework层(application Framework层):这一层由java语言编写
zygote进程负责加载ZygoteInit类、加载虚拟机、提前加载类preloadClasses、提前加载资源preloadResource
zygote进程孵化出System Server进程,它负责启动和管理整个Java Framework,包含ActivityManager、PackageManager、WindowManager等服务
(5)App层:这一层与用户直接交互,应用使用的是java语言开发
Zygote进程孵化出的第一个App进程是Luncher,就是桌面App,然后孵化出browser、phone、Email等基础的App进程,一个App至少运行在一个进程上。
所有的App进程都是由Zygote进程fork而来
(6)Syscall和JNI
Native和kernel之间是系统调用(Syscall)层
Java层与Native层之间的纽带是JNI(Java Native Interface)
2.dalvik虚拟机如何运行
上文已经介绍了Zygote进程,Zygote fork出其他进程之后,执行的工作就有Dalvik来进行。
它首先通过LoadClassFromDex()函数完成类的装载工作,类被解析之后有一个ClassObject类型的数据结构储存在运行时环境中,虚拟机使用gDvm.loadedClasses全局哈希表来储存和查询所有装载的类;
字节码验证器使用verifyCodeFlow()函数对装入的代码进行校验,随后虚拟机调用FindClass()函数查找和装载main方法类,随后调用dvmInterpret()函数初始化解释器并执行字节码流
3.Dalvik虚拟机JIT(just in time 即时编译)机制
JIT又称为动态编译,是一种在运行时将字节码翻译为机器码的技术,这使得程序执行速度更快
JIT有两种代码编译方式:Method方式和trace方式。分别以函数为单位和以trace为单位进行编译。简单说一下trace方式,函数的代码被分为许多条执行路径,根据执行的频繁与否被分为热路径和冷路径。trace方式能够快速的获取热路径,以更短的时间和更少的内存来编译代码
三、Dalik指令
Dalvik指令语言其实就是smali语言,指令这部分有点多,回头再研究
smali文件实例
.class public LHelloWorld; #定义类名
.super Ljava/lang/Object; #定义父类
.method public static main([Ljava/lang/String;)V
.registers 4 #程序中使用4个寄存器,v0,v1,v2和一个参数寄存器
.parameter #一个参数,有n个参数则有n行
.prologue #代码起始的指令
#空指令
nop
nop
nop
nop
#数据定义指令
const/16 v0, 0x8
const/4 v1, 0x5
const/4 v2, 0x3
#数据操作指令
move v1, v2
#数组操作指令
new-array v0, v0, [I
array-length v1, v0
#实例操作指令
new-instance v1, Ljava/lang/StringBuilder;
#方法调用指令
invoke-direct {v1}, Ljava/lang/StringBuilder;->()V
#跳转指令
if-nez v0, :cond_0
goto :goto_0
:cond_0
#数据转换指令
int-to-float v2, v2
#数据运算指令
add-float v2, v2, v2
#比较指令
cmpl-float v0, v2, v2
#字段操作指令
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Hello World" #构造字符串
#方法调用指令
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
#返回指令
:goto_0
return-void
.end method
编译smali文件,生成dex文件
java-jar smali.jar -o classes.dex Helloworld.smali
在真机或者虚拟机测试运行
先将classes.dex压缩为Helloworld.zip
adb push Helloworld.zip /data/local/tmp
adb shell dalvikvm -cp /data/local/tmp/Helloworld.zip HelloWorld
正常运行会打印helloworld字符串