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;-><init>()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字符串