1:Kettle Bell Exercises with Pocket PT
2:Our Radio-Pro
3:Mike McCoy & VU
4:RaceMe
5:Droid
6:SMS at Time (SMS a Tempo) free
7:My Radio 70s 80s 90s
8:Brothers Baseball
9:Insomnia
10:性感的女孩愛性感
11:Angry Tank
12:VIP Stanje
13:成人谜语大全
14:Swedish-English Dictionary
15:Fuzzball Pro: A multiplayer Billiards / Soccer strategy game against online friends over 3G internet
16:Matches
17:190 Mobile
18:The Daily Press
19:智能呼吸分析儀
20:iKnow What You_re Really Thinking
21:童畫:海底總動員
22:Randroid: Random xkcd
23:Top 100 Guitar Song Lessons
24:AutoMobile
25:Tim Gunn Motivator!
26:MirrorOp Receiver Free
27:WebSMS: GMX Connector
28:Reddit News Free
29:Cs Weapons
30:PHP GETter

1:Ikemen Counter
2:マンガ無料アプリ★無料で読める小説コミックアニメ電子書籍
3:[無料占い]スマホの無料占いなら「占いステーション」
4:通販コスメ姫 ~激安カラコン編~
5:車・バイクアプリ
6:ビジネスホームスクリーン
7:[無料相性占い] 恋愛占い・復縁占い ☆Love Happy
8:EiWeight2
9:HBO GO Slovenia
10:HBO GO Serbia
11:HBO GO Hungary
12:HBO
13:HealthLog
14:Mobile TV German
15:Mobile TV French
16:The Best Movies Download
17:Mobile TV International
18:My-Cast Weather for Europe
19:TV za van
20:Accroids
21:Crackle for Sony Tablet
22:Girl's Secret
23:МТС Вторая память
24:링크모아 영화
25:凤凰星座
26:TUT.BY
27:MP3 Notes for Tablet
28:MP3 Notes
29:Task Alerts
30:Mobile Banking
maper

Android筆記 – Dalvik的漫談

Android筆記 – Dalvik的漫談

hlchou@mail2000.com.tw
by loda

由於Dalvik所涉及的範圍不少,從JNI介面,Trace-JIT的實作,到最佳化的技巧,筆者在本文只會針對自己挑選的區塊以Android 2.2 Source Code加以說明.同樣的,所有涉及的內容,都會隨著Android程式碼的改版而有所差異,還請以最新取得的Package為主.

在此引用侯捷曾說過的 "源碼之前了無秘密",對有志於深入探究Dalvik運作原理的人而言,Android所釋出的Source Code,就是最好的Handbook.

參考Google的文件,我們知道Dalvik是Google在Android手機上所提供的ByteCode虛擬器,所預定的目標是要能運作在效能需求不高,較少的記憶體,與使用慢速的內部Flash儲存,作業系統要能支援虛擬記憶體,執行行程與執行緒(Process/Thread),與具備使用者帳號安全管理機制(UID-based security mechanisms),並能透過GNU C編譯器編譯後運作在包括Linux,BSD與Mac OS X等Unix環境下,並支援Little-endian與Big-endian處理器執行環境. 核心的函式庫主要是承襲Open Source的Java SE實作Apache Harmony而來(網址:http://harmony.apache.org/ ),並基於Open Source中相關OpenSSL, zlib與ICU(International Components for Unicode) 的計畫成果.

一般虛擬器的設計,會在應用程式啟動時,動態的從儲存裝置讀取並解壓縮個別的Classes到記憶體中,在沒有經過適度優化的架構下,每個獨立的虛擬器行程,都會包含自己一份的相關Classes記憶體空間,對於程式載入的固定成本與執行效率上,不容易有較好的表現.

因此,參考Google文件,Dalvik設計之初,基於上述的特徵與限制,主要著重在以下的目標,

1,Class Data尤其是共用的ByteCode,要能跨行程共用,節省系統整體記憶體需求(參考Process Memory Map (in /proc/xxxx/maps),目前包括相關的 jar與所包含的Bytecode Dex檔案,都能跨行程在不同的Dalvik行程中共用).

2,減少Dalvik應用程式載入啟動的成本,加速應用程式的反應時間

3,Class Data儲存在個別的檔案中,導致額外的儲存成本,針對儲存空間的需求需要多加注意.

4,要從Class Data中讀取出資料數值( 例如:整數或是字串)會增加不必要的成本,評估如何採用C 的形式存取,會是有必要的.

5,ByteCode的驗證雖耗時但卻是必要的,應該試著在程式執行前完成驗證.

6,透過快速指令集與機制的優化進行ByteCode的最佳化對於執行效率與電池壽命是相當重要的.

7,為了安全需求,執行中的行程不能修改共享的程式碼.

基於上述目標,Dalvik在設計時,作了以下的決定

1,可以把多個Classes檔案整合到一個 DEX檔案中

2,DEX檔案會以唯讀方案載入到記憶體中,並跨行程共享

3,因應支援的系統架構,調整Byte Ordering與Word Alignment.

4,ByteCode驗證對所有的Classes都是必要的,且會儘可能進行事先的驗證加速系統效率.

5,對於需修改ByteCode的最佳化動作,會在執行前完成.

在dalvik上通常會以.apk的方式來提供應用程式的封裝,或以.jar的方式來提供Framework函式庫,這些包裝格式主要是以zip的形式壓縮內容並加上相關的參考資訊,儲存在檔案系統中,並達到節省儲存空間的目的.也因此,在執行前必須要進行解壓縮的動作,把DEX檔案載入到記憶體中執行. DEX檔案可包括多個Classes檔案,並以 classes.dex 檔名結尾.

DEX的驗證與最佳化

系統可以在VM進行JIT(Just in time),第一次安裝或在系統編譯時,進行最佳化DEX檔案的動作(ODEX – Optimized DEX),如果是在裝置上進行的最佳化動作,會把最佳化後的檔案存放到 dalvik-cache目錄下(例如:/data/dalvik-cache/system@framework@ext.jar@classes.dex),若是在編譯階段的最佳化動作,則是把最佳化後的DEX檔案存放在jar/apk格式的ZIP壓縮檔案中,可成為產品出貨時預設System Image的一部分.

執行時期的dalvik-cache目錄權限為0771並屬於system使用者與system群組(例如:drwxrwx–x system   system),最佳化後的DEX檔案權限為0644並屬於system使用者與使用者群組(例如:-rw-r–r– system   app_29),而有被DRM-locked保護的應用程式最佳化後的DEX檔案權限會設定為0640以避免其他應用程式取得檔案資料.

Android SDK中的System.img 所包含的init.rc,在啟動過程中會進行如下的配置

# create dalvik-cache and double-check the perms

mkdir /data/dalvik-cache 0771 system system

chown system system /data/dalvik-cache

chmod 0771 /data/dalvik-cache

最佳化DEX的動作會產生一個可供快速載入執行的classes.dex檔案,並會進行包括byte-swapping,structure realigning與basic structure checks,更新ODEX(Optimized DEX)header ,為了確保產生ODEX流程的正確性,Android提供了一個dexopt工具(原始碼在dalvik/dexopt),用來作為一個Dalvik虛擬器的輔助工具,可以在系統啟動時,透過Dalvik虛擬器對載入的DEX檔案執行最佳化的動作.

工具dexopt在Android系統中有兩種使用的時機,

1,由Dalvik虛擬器來執行,在有多個Dalvik虛擬器執行的環境中,會透過dexopt locks確保同一個DEX檔案只會執行到一次.

2,在安裝應用程式時, 會把檔案從ZIP壓縮中解開,再透過dexopt進行驗證與最佳化
針對DEX檔案所進行的驗證與最佳化程序內容,如下簡介

1,驗證:

會包括DEX檔案中所包括的每個Classes集合以及每個指令集,確保不會在Run-Time階段遇到不適當的指令集,一般而言,Dalvik只有針對被驗證過的Classes進行依據平台的最佳化流程(這條件是可修改的,甚至也可忽略驗證階段只進行最佳化),如果呼叫了一個被驗證失敗的Class所提供的呼叫,就會導致Dalvik虛擬器發生Exception例外, DEX中的Class一旦被驗證過,就會被記錄在ODEX格式的欄位中(包含32-bit check-sum),如此可以避免下次載入時,還要重複進行驗證的工作.

2,最佳化:

會包括把數值資料的定義,透過指標對應到內部的資料結構,把一定成功或是特定行為的指令流程置換為比較簡單的形式,除了必須要在Run-Time才能取得的資訊外,會把可以先決定的資訊先靜態處理完畢.

A, 以vtable Index置換虛擬函式(virtual method calls)Index

B, 變數的存取,會以實際資料的Byte Offset置換,並且會把資料型別為boolean / byte / char / short 等變數,轉為32bits的形式儲存

C,置換大量調用的函式,例如 String.length() 透過 Inline的置換,直接由Intepreter呼叫到原生函式的實作,減輕函式呼叫的成本,

D,移除空函式的實作,例如Class物件的init空函式,在每次物件被配置時,都還是會被呼叫,就會以nop指令集(0×00)取代.

E,將可以預先計算的資料,進行預處理.

F,會以Dalvik規格中沒有制定的OpCode指令集來進行最佳化,這部份會交由dexopt根據Dalvik虛擬器的版本去決定哪些部分要置換.

G.最佳化的流程,也可以看做是跟ELF執行檔與動態函式庫間解決Symbol Resolve的問題,

上述的最佳化動作,也會使用到來自其他DEX檔案中的Class,如果所參考到的Class資料的內容或是函式有所變動,就會有相依性的問題要被處理,並且要針對變動的部份重新進行最佳化的動作.

DEX 相依性.

由於,最佳化時,會把系統相依的問題進行解決,以加速應用程式載入的效率,也因此,這些在最佳化時所仰賴的相關DEX檔案如果有變動的話,就有機會導致原本最佳化的結果可能造成因為版本差異的所導致的系統問題,也因此,最佳化過的ODEX(Optimized DEX)檔案中會包括所相依的DEX檔案清單以及其CRC-32,時間資訊,對應到dalvik-cache的完整路徑,SHA-1簽名,Dalvik虛擬器版本編號. 基於此,如果啟動目錄下的DEX檔案變動,也就象徵著會導致系統上一次相依到該DEX檔案的驗證與最佳化流程,需要被重新執行. 如果使用者自定的Class命名與啟動路目錄下的Class名稱重複,系統會對該Class標註並且該參考將不會被驗證與最佳化流程解析. 如果DEX檔案在安裝時就是以ODEX的形式安裝,且他所相依的DEX檔案被更新過,這將會導致dexopt沒有辦法對該DEX檔案依據目前系統的狀況進行最佳化產生ODEX,此時Dalvik虛擬器將會拒絕該DEX檔案安裝.

Dalvik雜談

Dalvik虛擬器支援大約230多個指令集OpCode,其中也包含部分由Dexopt插入到ByteCode執行檔中但目前尚未在Android文件”Bytecode for the Dalvik VM” 說明的OpCode指令.

主要的Dalvik虛擬器實作,都是基於C Code,並具有平台移植性,除了JIT(Byte-Code Compiler to ARM/x86 Code)與JNI(Java Native Interface) Call Bridge有牽涉到跟平台有關的組語優化實作,這會牽涉到包括如何把Byte-Code針對ARMv5,ARMv7,VFP與 NEON指令集進行動態的產生與優化,以及由Java端呼叫到Native函式時,要如何根據平台的差異去針對例如C語言的函式參數傳遞進行優化(例如:x86的函式參數傳遞,是由右往左推到Stack中,而ARM的函式參數傳遞,是由左到右依序放到R0,R1,R2,R3通用暫存器),與函式呼叫與傳回值(例如:x86會根據傳回值的Type決定要用EAX或加上EDX暫存器,或是ARM會決定要用到R0或加上R1暫存器). 若你所使用的平台,並不在Dalvik JNI Call Bridge支援中,Google文件中也建議可以參考 open-source FFI library (A Portable Foreign Function Interface Library,網址:http://sourceware.org/libffi/ ),裡面有關於不同平台的可移植函式呼叫實作.對應到Dalvik中這部分的實作位於Source Code的dalvik/vm/arch/目錄下,裡面有關於Java透過JNI機制呼叫到Native Code的實作函式void dvmPlatformInvoke(void* pEnv, ClassObject* clazz, int argInfo, int argc, const u4* argv, const char* signature, void* func, JValue* pReturn) (例如:在x86目錄下實作是在Call386ABI.S 與Hints386ABI.c,arm目錄下實作是CallEABI.S,CallOldABI.S 與HintsEABI.c,或generic目錄下的實作Call.c 與 Hints.c),其中,也有包括關於SH處理器Call Bridge的實作,這是由Hitachi與 Renesas公司實作後,貢獻到Android計畫中.(ㄟ….那MIPS也應該要這樣子做吧…@_@)

函式 dvmPlatformInvoke主要目的是會用來把我們在Java端呼叫JNI函式時所帶入的參數,用C函式參數傳遞的原則進行調整,與處理C函式的回傳值對應回Java的世界中.

Dalvik支援兩種Java虛擬器的直譯器,一個是最早的版本,在文件中稱為Portabl Interpreter,所在原始碼路徑為dalvik/vm/mterp/portable,這個all-in-one-function的C實作,可以用來編譯到不同的平台上,並支援Profiling與除錯機制,根據Config的設定,所在編譯環境會納入編譯的Java虛擬器的直譯器實作會把Source Code放到dalvik/vm/mterp/out目錄下.

Dalvik也支援根據不同平台透過組語優化後的直譯器版本,在文件中稱為Fast Interpreter,可以藉由優化後的平台組語實作,得到更好的Java虛擬器執行效能,在dalvik/vm/mterp/out目錄下可以看到依據平台不同而命名的InterpC-<arch>.c, InterpAsm-<arch>.S原始碼,並可針對不同平台的差異採用不同的優化指令集,例如:在ARM11(ARMv6)架構下可以使用 PLD 指令,與在ARM7(ARMv4T)架構下要避免使用CLZ指令.

組語版本的優化程式碼,會以64bytes Memory Alignment配置來實作每一個對應的Java指令(也就是說每個Java指令最多可以用16個ARM 32bits指令集實作),如果在Dalvik虛擬器啟動時,發現有對應實作的Java指令超過定義的大小,就會由虛擬器產生錯誤(Abort),參考InterpAsm-armv5te.S,如下指令集實作

.balign 64

.L_OP_MOVE_16: /* 0×03 */

/* File: armv5te/OP_MOVE_16.S */

/* for: move/16, move-object/16 */

/* op vAAAA, vBBBB */

FETCH(r1, 2)                        @ r1<- BBBB

FETCH(r0, 1)                        @ r0<- AAAA

FETCH_ADVANCE_INST(3)               @ advance rPC, load rINST

GET_VREG(r2, r1)                    @ r2<- fp[BBBB]

GET_INST_OPCODE(ip)                 @ extract opcode from rINST

SET_VREG(r2, r0)                    @ fp[AAAA]<- r2

GOTO_OPCODE(ip)                     @ jump to next instruction

.balign 64

.L_OP_MOVE_WIDE: /* 0×04 */

/* File: armv5te/OP_MOVE_WIDE.S */

/* move-wide vA, vB */

/* NOTE: regs can overlap, e.g. "move v6,v7″ or "move v7,v6″ */

mov     r2, rINST, lsr #8           @ r2<- A(+)

mov     r3, rINST, lsr #12          @ r3<- B

and     r2, r2, #1

add     r3, rFP, r3, lsl #2         @ r3<- &fp[B]

add     r2, rFP, r2, lsl #2         @ r2<- &fp[A]

ldmia   r3, {r0-r1}                 @ r0/r1<- fp[B]

FETCH_ADVANCE_INST(1)               @ advance rPC, load rINST

GET_INST_OPCODE(ip)                 @ extract opcode from rINST

stmia   r2, {r0-r1}                 @ fp[A]<- r0/r1

GOTO_OPCODE(ip)                     @ jump to next instruction

也可以參考網頁http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html ,有整理好的Dalvik Java OpCode, 對比Sun Java的OpCode (可以參考網頁:http://java.sun.com/docs/books/jvms/second_edition/html/Mnemonics.doc.html 或是比較完整的文件http://java.sun.com/docs/books/jvms/second_edition/Java5-Instructions2.pdf ),對比之後可以知道雖然都是透過JDK編譯的程式碼,透過Google Android Dex轉換後,就會變成Dalvik專屬的OpCode在Dalvik自己的Java虛擬器上執行.

針對使用者所採用平台的組語優化程式碼,可以透過dalvik/vm/mterp/config-<Arch>相關檔案來決定,其中包括要import哪一部分的實作,也規避掉了ARM在不同架構上支援指令集的差異. 以dalvik/vm/mterp/config-armv4t為例,擷取其中關於opcode 的部分來說明如下

# opcode list; argument to op-start is default directory

op-start armv5te

op OP_AGET_WIDE armv4t

op OP_APUT_WIDE armv4t

op OP_IGET_WIDE armv4t

op OP_IGET_WIDE_QUICK armv4t

op OP_IPUT_WIDE armv4t

op OP_IPUT_WIDE_QUICK armv4t

op OP_SGET_WIDE armv4t

op OP_SPUT_WIDE armv4t

op-end

上述宣告,最後產生的dalvik/vm/mterp/out/InterpAsm-armv4t.S除上述AGET_WIDE,APUT_WIDE,IGET_WIDE,IGET_WIDE_QUICK,IPUT_WIDE,IPUT_WIDE_QUICK,SGET_WIDE與SPUT_WIDE指令集會用armv4t的實作外,其他指令集會用armv5te的指令集架構實現. 如果有修改上述指令集的實作,會需要執行dalvik/vm/mterp/rebuild.sh,重新產生dalvik/vm/mterp/out/ 下對應到相關處理器架構的Java指令集實作.並重新編譯dalvik目錄,以便產生對應這次修改的 libdvm.so .

Fast Interpreter會是預設的Dalvik虛擬器中的直譯器,如果你所在的處理器沒有Dalvik的組語實作支援,可以由使用者在啟動時選擇要用C Code版本的Portable Interpreter,只要在啟動時(例如在init.rc中)執行 “echo dalvik.vm.execution-mode = int:portable >> /data/local.prop “,就可以選擇採用Portable Interpreter.

一般Java虛擬器直譯器的實作,最直覺的寫法就是用一個很大的Switch-Case,依序對應每個進來的指令集OpCode,做出對應OpCode的行為,在每個指令集OpCode執行完畢後,就回到迴圈的頭,重新Fetch下一次的指令,繼續Siwtch-Case的行為. Google文件中提到的另一種改善的技巧就是透過”threaded execution”,在每個指令集OpCode執行結束後,進行下一個指令集的fetch 與 dispatch,省去要回到迴圈啟始點(Branch)的固定成本.

Dalvik直譯器的實作,採用預先算好的Goto位址,基於每個直譯器處理的指令集OpCode實作,都固定以64bytes為Memory Alignment,取代得到一個指令集OpCode後,要進行查表的成本,只要取得OpCode後乘上64bytes(等於2的6次方,可以透過Shift的方式運算),就可以跳到對應指令集OpCode的實作.Android之所以選擇64bytes為每個指令集OpCode實作的最大空間,並沒有特別的理由,主要是這樣的值可以完全適用於目前Android對於ARM與x86上實作的結果.

參考Source Code dalvik/libdex/OpCode.h中的DEFINE_GOTO_TABLE的宣告如下,

#define DEFINE_GOTO_TABLE(_name) \

static const void* _name[kNumDalvikInstructions] = {                    \

/* 00..0f */                                                        \

H(OP_NOP),                                                          \

H(OP_MOVE),                                                         \

H(OP_MOVE_FROM16),                                                  \

H(OP_MOVE_16),                                                      \

H(OP_MOVE_WIDE),                                                    \

H(OP_MOVE_WIDE_FROM16),                                             \

H(OP_MOVE_WIDE_16),                                                 \

H(OP_MOVE_OBJECT),                                                  \

H(OP_MOVE_OBJECT_FROM16),                                           \

………………………..

…………………………………etc

再參考Source Code dalvik/vm/mterp/cstubs/entry.c,有如下宣告

/*

* Handler function table, one entry per opcode.

*/

#undef H

#define H(_op) dvmMterp_##_op

DEFINE_GOTO_TABLE(gDvmMterpHandlers)

#undef H

#define H(_op) #_op

DEFINE_GOTO_TABLE(gDvmMterpHandlerNames)

定義 gDvmMterpHandlers對應到每個OpCode的處理實作,與 gDvmMterpHandlerNames對應到每個OpCode的名稱.

再來追蹤函式bool dvmMterpStdRun(MterpGlue* glue) ,可以看到如下的Busy Loop

while (true) {

typedef void (*Handler)(MterpGlue* glue);

u2 inst = /*glue->*/pc[0];

Handler handler = (Handler) gDvmMterpHandlers[inst & 0xff];

LOGVV("handler %p %s\n",

handler, (const char*) gDvmMterpHandlerNames[inst & 0xff]);

(*handler)(glue);

}

會每次取一個Java指令集OpCode,並透過 gDvmMterpHandlers 對應0×00-0xff範圍的Java指令集OpCode的對應處理函式. 在dalvik/vm/mterp/out/InterpAsm-<Arch>.S中,配合Fast Interpreter也有對應依據平台差異的函式dvmMterpStdRun 組語版本實作,跟C語言版本實作行為類似,但最大的差異在於,不是透過一個 while(true)的Busy Loop去逐一抓取給Dalvik虛擬器的指令集,而是先在函式dvmMterpStdRun中執行如下巨集 (以InterpAsm-armv5te.S實作為例)

FETCH_INST()                        @ load rINST from rPC

GET_INST_OPCODE(ip)         @ extract opcode from rINST

GOTO_OPCODE(ip)               @ jump to next instruction

抓取第一個Dalvik OpCode指令並執行,同時在每個指令執行結束後,再透過FETCH_ADVANCE_INST與GET_INST_OPCODE抓取下一個Dalvik OpCode指令,最後再透過GOTO_OPCODE執行該指令集的實作,如此持續運作下去,藉此得到比用C版本Busy Loop更高的執行效率.

如果因為進行記憶體的Garbage Collection,進行除錯或是轉換到Native Code執行,而讓執行中的Dalvik行程處於Suspend狀態,Dalvik直譯器會提供一個安全機制,確保行程可以被正確的回復執行. 如下所示,目前Dalvik主要支援Portable/Fast Interpreter,而Trace-JIT主要是屬於Fast-Interpreter實作中的一部分.

Trace-JIT/Compiler

(For Hot Fragments)

Fast Interpreter

(For Cold Fargments)

Portable Interpreter
(C Code)
Fast Interpreter (ASM)
Dalvik VM

Dalvik上每一個應用程式不管是SDK或是NDK都會基於一個Java Based的應用程式為主體(NDK為Java+ELF .so),也因此要了解Android的應用程式Framework,最適當的途徑就是把Dalvik的運作原理與基礎做一個分析,相對這會對於在Android架構下,不管是開發應用程式或是進行系統的效能優化都會有相當的助益.

Dalvik的控制與範例

如果是透過PC上的系統參數設定操作,可以透過adb設定如下的指令

#設定系統參數

adb shell setprop <name> <value>

#取得系統參數

adb shell getprop <name>

相關的Dalvik系統參數設定,是在Zygote行程載入時進行初始化的,一旦相關的系統參數被更動到,就必須要重啟Dalvik Run-Time環境,透過Zygote載入流程讓相關系統參數發揮作用.

在開發階段,有測試系統參數的需求時,就可以藉由Android的Shell,透過Stop/Start指令,終止與重啟Dalvik Run-Time環境,讓系統參數發揮作用.(Stop會依序設定系統參數 ctl.stop=runtime與ctl.stop=zygote,Start會依序設定系統參數ctl.start=zygote與ctl.start=runtime)

系統參數 說明
dalvik.vm.stack-trace-file /data/anr/traces.txt Stack Dumps
範例: setprop dalvik.vm.stack-trace-file /tmp/stack-traces.txt

Dalvik虛擬器在收到 Linux Signal SIGQUIT (也可透過kill -3觸發)就會把目標行程所有執行緒的Stack Traces內容(可以知道每個執行緒函式呼叫的Call Stack),根據這個參數指定的路徑把資料寫入,供開發者分析問題時參考之用.

dalvik.vm.dexopt-flags m=y Bytecode Verification and Optimization

範例:setprop dalvik.vm.dexopt-flags v=a,o=v

用來決定dexopt所進行執行前的驗證與最佳化動作的行為,在實際的裝置上,dexopt會在Dalvik啟動一個應用或是應用程式安裝時調用(會透過dexlock確保同時只有一個被執行到),

進一步說明參數如下

v=a,o=v =>驗證所有的Dex檔案,並且只最佳化被驗證過的Dex檔案,如果驗證失敗,該Dex檔案就不會被執行

v=n,o=v =>關閉驗證Dex的動作,只針對有被驗證過的Dex進行最佳化,沒被驗證過的Dex,就會直接執行(也不會被最佳化)

根據Android的文件,第一次執行的Dex檔案,進行驗證的動作,可能會讓執行時間慢了40%左右,但只要該Dex檔案有驗證過,並且對應放在dalvik-cache下,只要該檔案沒有變動且所相依的其他Dex檔案也沒有更動過,之後執行就可省去驗證的流程,並可透過最佳化機制,加速應用程式的啟動執行.

Enabling type-precise GC results in larger optimized DEX files.  The additional storage requirements for ".odex" files can cause /system to overflow on some devices, so this is configured separately for each product.

dalvik.vm.lockprof.threshold 500 Enable Dalvik lock contention logging for userdebug builds.
dalvik.vm.checkjni true/false Extended JNI Checks

範例:setprop dalvik.vm.checkjni true

如果系統沒有設置 dalvik.vm.checkjni,就會以 ro.kernel.android.checkjni為主,如果有設置 dalvik.vm.checkjni(不論是true或false),則以 dalvik.vm.checkjni為依據來決定CheckJNI的開或關

另一個對應的參數為 ro.kernel.android.checkjni,可在編譯時期透過build/core/main.mk決定.

這參數的設置,會讓JNI函式呼叫前,執行相關的稽核動作,其中包括

1,查核NULL Pointer

2,查核函式參數的正確性 (jclass is a class object, jfieldID points to field data, jstring is a java.lang.String)

3,確認資料寫入動作與變數宣告一致. (例如: don’t store a HashMap in a String field.)

4,確認是否有不允許的例外處理有因為函式呼叫正在等待例外的情況.

5,確認關鍵的Get/Release呼叫沒有不適當的函式存在

6,確認JNIEnv不會被跨執行緒(Thread)分享

7,確認Local變數的參考不會超過該變數所能支援的變數壽命週期.

8,UTF-8字串只會包含有效修改的UTF-8資料.

dalvik.vm.jniopts forcecopy Extended JNI Checks

範例:setprop dalvik.vm.jniopts forcecopy

dalvik.vm.enableassertions You can provide a class name, a package name (followed by "…"), or the special value "all" Assertions

範例: setprop dalvik.vm.enableassertions all

Dalvik虛擬器預設對於Assertion是關閉的,可以透過設定這個參數來開關Dalvik上Java程式碼Assertion的動作,

dalvik.vm.execution-mode int:portable/int:fast/int:jit Execution Mode

範例:

setprop dalvik.vm.execution-mode int:portable (C版本的Intepreter)

setprop dalvik.vm.execution-mode int:fast (組語版本的Intepreter)

setprop dalvik.vm.execution-mode int:jit (Just-in Time加速)

如果有啟動Profiling或是接上Java除錯器,就會切到Debug Mode(我理解Debug Mode是在 C版本的Portable Intepreter中支援的),當除錯階段結束,或是Profiling結束,就會重新回復原本執行的Java模式.

如果在AndroidManifest.xml中把android:vmSafeMode設定為true,就會預設關閉JIT選項,開發者可借此釐清應用程式所造成的問題,是否為JIT所導致的.

dalvik.vm.deadlock-predict err off/warn/err Deadlock Prediction

範例:setprop dalvik.vm.deadlock-predict err

編譯Dalvik時必須要加上 WITH_DEADLOCK_PREDICTION便能支援這個功能,參考Android文件,這機制主要用來進行Dalvik上Deadlock的預測,而非偵測

如果設定為off就是關閉,warn是記錄這個問題,但程式繼續執行,如果是err就是在Dalvik OpCpde OP_MONITOR_ENTER(0x1d)指令執行完畢後,立刻觸發Exception,終止Dalvik虛擬器的執行.

dalvik.vm.check-dex-sum DEX File Checksums
範例:setprop dalvik.vm.check-dex-sum true

目前Dalvik虛擬器預設對載入ODEX(Optimized DEX)的流程,是不做Checksum確保的,參考dalvik-cache,跟系統與Framework有關的ODEX,所Owner與Group會是root或system,只有一般應用執行的ODEX,會把Group設定為對應的AP Group,基於此,Android預設透過系統存取權限的保護,就可以防止ODEX檔案被修改的可能.

如果所在的儲存媒體不可靠,容易有資料損毀的問題發生,就會有必要加入Checksum的機制(其實以目前很多SmartPhone用 MLC NAND+FTL Controller的組合,只要Wear-Leveling有作用,要遇到儲存裝置的”不可靠”,對一般使用者而言,應該是很不容易遇到). 開發階段,也可以透過dexdump工具,手動的確認DEX檔案的Checksum.

dalvik.vm.jit.profile true/false JIT Profile
範例:setprop dalvik.vm.jit.profile true

可以用來查看Dalvik JIT對於熱門執行區塊的統計資訊.

有關Dalvik行程與執行時期介紹

如下所示,為Android系統應用程式啟動的父子行程關係,我們可以看到init會把包括Service Manager,Netd,Rild,MediaServer,BootAnimation這些原生行程載入,而Dalvik的第一個初始化程式會透過app_process載入後,並命名為Zygote(ㄟ …Google翻譯查詢是 "受精卵"),再由Zygote帶起system_server與後續的Dalvik應用程式.

Kernel(PID=0)
/init(PID=1) /system/bin/sh(PID=27)

/system/bin/servicemanager(PID=28)

/system/bin/vold(PID=29)

/system/bin/netd(PID=30)

/system/bin/debuggerd(PID=31)

/system/bin/rild(PID=32)

/system/bin/mediaserver(PID=34)

/system/bin/dbus-daemon(PID=35)

/system/bin/installd(PID=36)

/system/bin/keystore(PID=37)

/system/bin/sh(PID=38)

/system/bin/qemud(PID=39)

/sbin/adbd(PID=41)

/system/bin/bootanimation(PID=81)

zygote(PID=33) system_server (pid:74)
com.android.inputmethod.latin (pid:146)
com.android.phone (pid:150)
android.process.acore (pid:189)
com.android.launcher (pid:191)
com.android.quicksearchbox (pid:229)
android.process.media (pid:259)
com.android.bluetooth (pid:273)
com.android.providers.calendar (pid:280)
com.android.email (pid:292)
com.android.mms (pid:305)
com.android.protips (pid:323)
com.android.music (pid:329)
com.cooliris.media (pid:335)
kthreadd(PID=2) ksoftirqd/0(PID=3)

events/0(PID=4)

khelper(PID=5)

suspend(PID=6)

kblockd/0(PID=7)

cqueue(PID=8)

kseriod(PID=9)

kmmcd(PID=10)

pdflush(PID=11)

pdflush(PID=12)

kswapd0(PID=13)

aio/0(PID=14)

mtdblockd(PID=22)

kstriped(PID=23)

hid_compat(PID=24)

rpciod/0(PID=25)

mmcqd(PID=26)

以目前筆者環境,列舉一個Dalvik應用程式CalendarProvider的記憶體Mapping資訊. 對應到其他Dalvik應用程式,我們可以看到基於Apriori prelink機制,在共用原生碼動態函式庫.so的部份,不同的Dalvik應用程式,也會對應到同樣的記憶體位置. 而在Dalvik OpCpde編碼動態函式庫有共用的.jar (會被解開為 classes.dex)部分,在跨不同的Dalvik應用程式,會被配置到同樣的記憶體位址.

記憶體位址 屬性 對應的執行檔或是函式庫名稱
00008000-00009000 r-xp /system/bin/app_process
00009000-0000a000 rwxp /system/bin/app_process
0000a000-001f9000 rwxp [heap]
40000000-40008000 r-xs /dev/ashmem/system_properties
40008000-40009000 r-xp
40009000-4024a000 rwxp /dev/ashmem/mspace/dalvik-heap/zygote/0
4024a000-41009000 —p /dev/ashmem/mspace/dalvik-heap/zygote/0
41009000-41038000 r-xs /system/fonts/DroidSans.ttf
41038000-4104c000 rwxp
4104c000-4104d000 —p /dev/ashmem/dalvik-LinearAlloc
4104d000-41254000 rwxp /dev/ashmem/dalvik-LinearAlloc
41254000-4154c000 —p /dev/ashmem/dalvik-LinearAlloc
4154c000-4175b000 r-xs /system/framework/core.jar
4175b000-41c23000 r-xp /data/dalvik-cache/system@framework@core.jar@classes.dex
41c23000-41c68000 rwxp
41c68000-41ca2000 r-xs /system/framework/ext.jar
41ca2000-41d2e000 r-xp /data/dalvik-cache/system@framework@ext.jar@classes.dex
41d2e000-41fc5000 r-xs /system/framework/framework.jar
41fc5000-425f2000 r-xp /data/dalvik-cache/system@framework@framework.jar@classes.dex
425f2000-42676000 rwxp
42676000-4268b000 r-xs /system/framework/android.policy.jar
4268b000-426b9000 r-xp /data/dalvik-cache/system@framework@android.policy.jar@classes.dex
426b9000-42752000 r-xs /system/framework/services.jar
42752000-428a0000 r-xp /data/dalvik-cache/system@framework@services.jar@classes.dex
428a0000-428a3000 rwxp
428a3000-428d9000 rwxp /dev/ashmem/dalvik-heap-bitmap/objects
428d9000-428df000 rwxp
428df000-428e0000 r-xs /dev/ashmem/SurfaceFlinger read-only heap
428e0000-42907000 rwxp
42907000-4290c000 r-xs /system/app/CalendarProvider.apk
4291a000-4294b000 rwxp
4294b000-42982000 rwxp /dev/ashmem/dalvik-heap-bitmap/mark/0
42982000-429c2000 rwxp /dev/ashmem/dalvik-heap-bitmap/mark/1
429c2000-42a16000 r-xs /system/app/CalendarProvider.apk
42a16000-42a29000 r-xs /system/framework/android.test.runner.jar
42a29000-42a55000 r-xp /data/dalvik-cache/system@framework@android.test.runner.jar@classes.dex
42a70000-42e23000 r-xs /system/framework/framework-res.apk
42e23000-4304e000 r-xs /system/framework/framework-res.apk
4304e000-4308f000 rwxp /dev/ashmem/mspace/dalvik-heap/zygote/1
4308f000-43e0e000 —p /dev/ashmem/mspace/dalvik-heap/zygote/1
43e0e000-43e4f000 rwxp /dev/ashmem/mspace/dalvik-heap/2
43e4f000-44b8e000 —p /dev/ashmem/mspace/dalvik-heap/2
44b8e000-44b8f000 —p
44b8f000-44c8e000 rwxp
44c8e000-44c8f000 —p
44c8f000-44d8e000 rwxp
44d8e000-44e8c000 r-xp /dev/binder
44e8c000-44e8d000 —p
44e8d000-44f8c000 rwxp
44f8c000-44f8d000 —p
44f8d000-4508c000 rwxp
4508c000-450e0000 r-xs /system/app/CalendarProvider.apk
450e0000-451ab000 r-xp /data/dalvik-cache/system@app@CalendarProvider.apk@classes.dex
80000000-801d0000 r-xp /system/lib/libicudata.so
801d0000-801d1000 rwxp /system/lib/libicudata.so
80200000-80286000 r-xp /system/lib/libdvm.so
80286000-80289000 rwxp /system/lib/libdvm.so
80289000-8028a000 rwxp
9d100000-9d139000 r-xp /system/lib/libstlport.so
9d139000-9d13b000 rwxp /system/lib/libstlport.so
9d700000-9d736000 r-xp /system/lib/libjpeg.so
9d736000-9d737000 rwxp /system/lib/libjpeg.so
9ea00000-9ea08000 r-xp /system/lib/libdrm1.so
9ea08000-9ea09000 rwxp /system/lib/libdrm1.so
a2f00000-a2fa9000 r-xp /system/lib/libstagefright.so
a2fa9000-a2fac000 rwxp /system/lib/libstagefright.so
a3500000-a3503000 r-xp /system/lib/libstagefright_color_conversion.so
a3503000-a3504000 rwxp /system/lib/libstagefright_color_conversion.so
a3600000-a3605000 r-xp /system/lib/libstagefright_avc_common.so
a3605000-a3606000 rwxp /system/lib/libstagefright_avc_common.so
a3700000-a370c000 r-xp /system/lib/libstagefright_amrnb_common.so
a370c000-a370d000 rwxp /system/lib/libstagefright_amrnb_common.so
a3900000-a39c7000 r-xp /system/lib/libopencore_common.so
a39c7000-a39cd000 rwxp /system/lib/libopencore_common.so
a4800000-a48b6000 r-xp /system/lib/libopencore_player.so
a48b6000-a48be000 rwxp /system/lib/libopencore_player.so
a5900000-a5917000 r-xp /system/lib/libomx_amrenc_sharedlibrary.so
a5917000-a5918000 rwxp /system/lib/libomx_amrenc_sharedlibrary.so
a6800000-a682f000 r-xp /system/lib/libopencore_net_support.so
a682f000-a6832000 rwxp /system/lib/libopencore_net_support.so
a6d00000-a6d14000 r-xp /system/lib/libomx_sharedlibrary.so
a6d14000-a6d15000 rwxp /system/lib/libomx_sharedlibrary.so
a7500000-a7502000 r-xp /system/lib/libemoji.so
a7502000-a7503000 rwxp /system/lib/libemoji.so
a7e00000-a7e05000 r-xp /system/lib/libhardware_legacy.so
a7e05000-a7e06000 rwxp /system/lib/libhardware_legacy.so
a7f00000-a7f01000 r-xp /system/lib/libhardware.so
a7f01000-a7f02000 rwxp /system/lib/libhardware.so
a8100000-a8124000 r-xp /system/lib/libutils.so
a8124000-a8125000 rwxp /system/lib/libutils.so
a8200000-a821f000 r-xp /system/lib/libbinder.so
a821f000-a8225000 rwxp /system/lib/libbinder.so
a8300000-a86ec000 r-xp /system/lib/libwebcore.so
a86ec000-a8747000 rwxp /system/lib/libwebcore.so
a8747000-a8749000 rwxp
a8a00000-a8a14000 r-xp /system/lib/libexpat.so
a8a14000-a8a16000 rwxp /system/lib/libexpat.so
a8b00000-a8b4d000 r-xp /system/lib/libsqlite.so
a8b4d000-a8b4f000 rwxp /system/lib/libsqlite.so
a9000000-a9051000 r-xp /system/lib/libmedia.so
a9051000-a905d000 rwxp /system/lib/libmedia.so
a9300000-a930c000 r-xp /system/lib/libmedia_jni.so
a930c000-a930d000 rwxp /system/lib/libmedia_jni.so
a9400000-a941c000 r-xp /system/lib/libvorbisidec.so
a941c000-a941d000 rwxp /system/lib/libvorbisidec.so
a9500000-a9552000 r-xp /system/lib/libsonivox.so
a9552000-a9553000 rwxp /system/lib/libsonivox.so
a9553000-a9554000 rwxp
a9c00000-a9c0a000 r-xp /system/lib/libskiagl.so
a9c0a000-a9c0b000 rwxp /system/lib/libskiagl.so
ab100000-ab211000 r-xp /system/lib/libskia.so
ab211000-ab215000 rwxp /system/lib/libskia.so
ab215000-ab218000 rwxp
ab900000-ab912000 r-xp /system/lib/libui.so
ab912000-ab914000 rwxp /system/lib/libui.so
aba80000-aba90000 r-xp /system/lib/libcamera_client.so
aba90000-aba93000 rwxp /system/lib/libcamera_client.so
abb00000-abb09000 r-xp /system/lib/libexif.so
abb09000-abb0a000 rwxp /system/lib/libexif.so
abb0a000-abb0c000 rwxp
abd00000-abd02000 r-xp /system/lib/libETC1.so
abd02000-abd03000 rwxp /system/lib/libETC1.so
abe00000-abe08000 r-xp /system/lib/libEGL.so
abe08000-abe09000 rwxp /system/lib/libEGL.so
abe09000-abe0b000 rwxp
ac100000-ac104000 r-xp /system/lib/libGLESv2.so
ac104000-ac105000 rwxp /system/lib/libGLESv2.so
ac200000-ac205000 r-xp /system/lib/libGLESv1_CM.so
ac205000-ac206000 rwxp /system/lib/libGLESv1_CM.so
ac700000-ac715000 r-xp /system/lib/libsurfaceflinger_client.so
ac715000-ac718000 rwxp /system/lib/libsurfaceflinger_client.so
ac900000-ac919000 r-xp /system/lib/libpixelflinger.so
ac919000-ac91b000 rwxp /system/lib/libpixelflinger.so
ad100000-ad12f000 r-xp /system/lib/libnativehelper.so
ad12f000-ad132000 rwxp /system/lib/libnativehelper.so
ad300000-ad36d000 r-xp /system/lib/libandroid_runtime.so
ad36d000-ad375000 rwxp /system/lib/libandroid_runtime.so
ad375000-ad37a000 rwxp
ad900000-ad9e0000 r-xp /system/lib/libicui18n.so
ad9e0000-ad9e4000 rwxp /system/lib/libicui18n.so
ad9e4000-ad9e5000 rwxp
ade00000-aded0000 r-xp /system/lib/libicuuc.so
aded0000-aded8000 rwxp /system/lib/libicuuc.so
aded8000-adeda000 rwxp
ae300000-ae304000 r-xp /system/lib/libnetutils.so
ae304000-ae305000 rwxp /system/lib/libnetutils.so
ae400000-ae402000 r-xp /system/lib/libwpa_client.so
ae402000-ae403000 rwxp /system/lib/libwpa_client.so
af000000-af08d000 r-xp /system/lib/libcrypto.so
af08d000-af09f000 rwxp /system/lib/libcrypto.so
af09f000-af0a1000 rwxp
af400000-af424000 r-xp /system/lib/libssl.so
af424000-af427000 rwxp /system/lib/libssl.so
af700000-af713000 r-xp /system/lib/libz.so
af713000-af714000 rwxp /system/lib/libz.so
af900000-af90e000 r-xp /system/lib/libcutils.so
af90e000-af90f000 rwxp /system/lib/libcutils.so
af90f000-af91e000 rwxp
afa00000-afa03000 r-xp /system/lib/liblog.so
afa03000-afa04000 rwxp /system/lib/liblog.so
afb00000-afb20000 r-xp /system/lib/libm.so
afb20000-afb21000 rwxp /system/lib/libm.so
afc00000-afc01000 r-xp /system/lib/libstdc++.so
afc01000-afc02000 rwxp /system/lib/libstdc++.so
afd00000-afd3f000 r-xp /system/lib/libc.so
afd3f000-afd42000 rwxp /system/lib/libc.so
afd42000-afd4d000 rwxp
b0001000-b000c000 r-xp /system/bin/linker
b000c000-b000d000 rwxp /system/bin/linker
b000d000-b0016000 rwxp
bea87000-bea9c000 rwxp [stack]
0xc0000000——— LINUX KERNEL

使用 ANT 包裝NDK應用程式為APK

Java的應用,通常可以透過Ant來包裝動態函式庫.so檔案與Java應用程式為.apk,Ant是一個Apache計畫的產物,官方網站為http://ant.apache.org/ ,下載JUnit https://github.com/KentBeck/junit/downloads ( 筆者下載的路徑是http://cloud.github.com/downloads/KentBeck/junit/junit-4.9b2.jar),把 junit-4.9b2.jar複製到 ant解開後的路徑  lib/optional下,然後如下設定環境變數

(http://ant.apache.org/manual/index.html)

export ANT_HOME=/usr/local/ant

export JAVA_HOME=/android/jdk1.5.0_22 (如果是Cygwin就設定到JDK安裝的路徑,例如 export JAVA_HOME=/cygdrive/c/"Program Files"/Java/jdk1.6.0_23)

export PATH=${PATH}:${ANT_HOME}/bin

NDK=/android-ndk-r5b-windows

接下來,執行 ./build.sh

就可以產生ANT執行的環境,以NDK為例,可以進到範例目錄

透過 Android SDK tools下的 android.bat產生讓ANT參考的build.xml

snna@snna-PC /android-ndk-r5b-windows/samples/hello-jni

$ android.bat update project -p . -s

Updated local.properties

Added file C:\cygwin\android-ndk-r5b-windows\samples\hello-jni\build.xml

Added file C:\cygwin\android-ndk-r5b-windows\samples\hello-jni\proguard.cfg

Updated local.properties

Added file C:\cygwin\android-ndk-r5b-windows\samples\hello-jni\tests\build.xml

Added file C:\cygwin\android-ndk-r5b-windows\samples\hello-jni\tests\proguard.cfg

透過 ndk-build進行編譯

snna@snna-PC /android-ndk-r5b-windows/samples/hello-jni

$ $NDK/ndk-build

(要編譯 Debug 版本就是加上 NDK_LOG=1 NDK_DEBUG=1)

Gdbserver      : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver

Gdbsetup       : libs/armeabi/gdb.setup

Install        : libhello-jni.so => libs/armeabi/libhello-jni.so

透過ANT產生 APK

snna@snna-PC /android-ndk-r5b-windows/samples/hello-jni

$ ant debug

Buildfile: C:\cygwin\android-ndk-r5b-windows\samples\hello-jni\build.xml

[setup] Android SDK Tools Revision 8

[setup] Project Target: Android 2.2

……………………..

debug:

[echo] Running zip align on final apk…

[echo] Debug Package: C:\cygwin\android-ndk-r5b-windows\samples\hello-jni\bin\HelloJni-debug.apk

BUILD SUCCESSFUL

Total time: 5 seconds

安裝 APK 到android手機環境

snna@snna-PC /android-ndk-r5b-windows/samples/hello-jni

$ adb install bin/HelloJni-debug.apk (如果之前有安裝過,可以加上 -r,重新安裝)

396 KB/s (79155 bytes in 0.195s)

pkg: /data/local/tmp/HelloJni-debug.apk

Success

安裝完畢後,如果希望透過ndk-gdb除錯時,可以在程式啟動後,立刻停住等待除錯器啟動,不要就直接執行下去,可以修改Java程式碼加入函式呼叫 android.os.Debug.waitForDebugger() , 如下所示,修改 /android-ndk-r5b-windows/samples/hello-jni/src/com/example/hellojni/HelloJni.java

package com.example.hellojni;

import android.app.Activity;

import android.widget.TextView;

import android.os.Bundle;

public class HelloJni extends Activity

{

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState)

{

android.os.Debug.waitForDebugger();

super.onCreate(savedInstanceState);

snna@snna-PC /android-ndk-r5b-windows/samples/hello-jni

$ $NDK/ndk-gdb –start

GNU gdb 6.6

Copyright (C) 2006 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB.  Type "show warranty" for details.

This GDB was configured as "–host=i586-mingw32msvc –target=arm-elf-linux".

(no debugging symbols found)

Android Runtime

包括工具 dexopt,或是app_process(zygote)與dalvikvm這些在Android目錄下可以使用的執行檔,其實最主要的實作都是基於 /system/lib/libdvm.so,並且透過JNI串起原生程式碼與Java程式碼的介面,這些工具主要是把要傳遞給 libdvm.so處理的資料,進行前段的處理,如下以app_process為例說明Zygote程序的初始化流程.

Dalvik 第一個Java應用程式 Zygote,就由此而來, app_process Source Code所在路徑為frameworks/base/cmds/app_process.系統在初始化Zygote時,會帶入如下的參數

/system/bin/app_process -Xzygote /system/bin –zygote –start-system-server

由於最終實作的內容是在Java Framework中的,com.android.internal.os.ZygoteInit (原始碼路徑:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java),在app_process的C實作中,主要是初始化Dalvik虛擬器,並且把要帶入Java的資訊加以處理,我們簡要說明如下.

1,在C程式碼中產生 AndroidRuntime  (AndroidRuntime實作在frameworks/base/core/jni/AndroidRuntime.cpp)

2,呼叫AndroidRuntime::addVmArguments把C參數的個數與內容扣掉屬於app_process的部份(個數減一與字串往前移動一).傳給AndroidRuntime

3,如果參數等於 –zygote 與隨後參數等於 –start-system-server 就把變數startSystemServer設為true,並呼叫set_process_name設定行程名稱為 zygote,再來執行AndroidRuntime::start(const char* className, const bool startSystemServer)函式,該函式會初始化Dalvik虛擬器(由AndroidRuntime::startVm呼叫函式JNI_CreateJavaVM(原始碼在:dalvik/vm/Jni.c)),並由C透過JNI介面(FindClass,GetStaticMethodID and CallStaticVoidMethod)執行Java的 com.android.internal.os.ZygoteInit Class(原始碼路徑:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java)的 ZygoteInit::main函式,執行如下行為 (只列舉筆者覺得重要的)

3.a,註冊ZygoteSocket

3.b,Preload所需的DEX/Class檔案,例如:會透過dvmJarFileOpen(原始碼在:dalvik/vm/JarFile.c),解開ZIP壓縮的JAR檔案,先確認是否有odex結尾的檔案(表示已經做過最佳化的動作),如果有,就會確認該odex檔案的Class相依性(如果有相依的Class被更新,就要重新進行最佳化),接下來搜尋是否有classes.dex結尾的檔案(尚未被最佳化過),並呼叫dvmOptimizeDexFile(原始碼在:dalvik/vm/analysis/DexOptimize.c)進行驗證與最佳化DEX的流程.

3.c,進行初始化後的記憶體回收動作 (GC,Memory Garbage Collection)

3.d,如果startSystemServer為true,呼叫startSystemServer產生System Server行程.

3.e,Zygote進入一個Socket 等待Client連結的無窮迴圈. 至此即完成zygote行程的初始化動作.

4,如果參數不為 –zygote, 就會把Class名稱與C參數的個數與內容扣掉屬於app_process的部份(個數減一與字串往前移動一).傳給AndroidRuntime. 並呼叫set_process_name設定行程為該Class名稱,再來執行AndroidRuntime::start(const char* className, const bool startSystemServer)函式,該函式會初始化Dalvik虛擬器,並由C透過JNI介面執行Java的 com.android.internal.os.RuntimeInit Class(原始碼路徑:frameworks/base/core/java/com/android/internal/os/RuntimeInit.java)的 RuntimeInit::main函式

JNI (Java Native Interface)

JNI的介面是原本Java環境中就有的機制,讓Java的應用程式可以跟其他語言實作的動態函式庫進行互通,更進一步來說,就是可以透過Native Code執行的高效率,優化Java應用程式執行上表現.

有兩份關於JNI的文件,是Google推薦開發者可以參考的

1,Introduction and Tutorial
(http://java.sun.com/docs/books/jni/html/jniTOC.html)

2, Java Native Interface Specification for J2SE 1.6
(http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html)

JNI的C函式可以存取 "JavaVM" 或"JNIEnv" 的資料結構,一個JNI C函式的第一個函式參數會是JNIEnv (例如:HelloWorld(JNIEnv * env, jobject jobj)),並且以Dalvik的設計而言,是以一個Java AP對應到一個Linux Process並對應到一個Dalvik VM的實體,也就是說,Dalvik VM本身並不會進行MultiTask Java AP的動作,同時,每個Java AP與其對應的JNI C 動態函式庫,就會以一個Linux  Process行程空間(每個都是一個獨立的4GB記憶體空間)為單位來運作.

JNI可以透過FindClass取得Java世界所宣告的Class,透過GetFieldID取得Java世界的變數(並且也可以改變,設定值與使用該變數),與可以透過GetMethodID取得Java世界的Class函式呼叫.有關字元部分需要注意的是,Java世界採用的字元是以Unicode(UTF-16 LE)編碼,而在JNI的C世界中是採用UTF-8的方式編碼.

以c為例,在實作JNI的函式時,所面對的資料型態,主要是以j 開頭加上對應的Java端的資料型態,例如

Java中的型態 JNI 中的型態 對應到C實作中的型態
boolean jboolean unsigned char  ( unsigned 8 bits)
byte jbyte signed char (signed 8 bits)
char jchar unsigned short  (unsigned 16 bits)
short jshort short (signed 16 bits)
int jint int (signed 32 bits)
long jlong long long (signed 64 bits)
float jfloat float (32-bit IEEE 754)
double jdouble double ( 64-bit IEEE 754)
size jsize int (signed 32 bits)
void void void
String jstring void *
object jobject void *
class jclass void *
array jarray void *
jobjectArray/jbooleanArray/jbyteArray/jcharArray/jshortArray/jintArray/jlongArray/jfloatArray/jdoubleArray/jthrowable/jweak void *

需要注意的是,在Java中字元是以Unicode的方式編碼,所以jchar對應一個Java字元,才會等於一個unsigned short 16bits字元,但是在JNI這邊C語言的實作則是以UTF-8的方式來實現,也就是說ASCII字元固定為1個bytes,但中文或其他語系的編碼就會有2-6bytes不等的編碼長度(可以參考http://en.wikipedia.org/wiki/UTF-8 ),中文等亞洲語系通常為3bytes.

如下,提供一個簡單沒有傳入參數與傳回值的JNI範例 libtest8.c

#include <jni.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#define LOG_TAG "C LOG DEMO"

#undef LOG

#include <utils/Log.h>

JNIEXPORT void JNICALL Java_loda_Test8_Test8_HelloWorld(JNIEnv * env, jobject jobj)

{

printf("Hello World in Console.\n");

LOGV("Hello World! This color is for Verbose.");

LOGD("Hello World! This color is for Debug.");

LOGI("Hello World! This color is for Information");

LOGW("Hello World! This color is for Warnning.");

LOGE("Hello World! This color is for Error.");

}

透過如下內容的Android.mk

LOCAL_PATH := $(my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES := libtest8.c

LOCAL_SHARED_LIBRARIES := liblog

LOCAL_MODULE := libtest8

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)

之後執行

[root@localhost froyo]# make libtest8

============================================

PLATFORM_VERSION_CODENAME=REL

PLATFORM_VERSION=2.2.1

TARGET_PRODUCT=generic

TARGET_BUILD_VARIANT=eng

TARGET_SIMULATOR=

TARGET_BUILD_TYPE=release

TARGET_BUILD_APPS=

TARGET_ARCH=arm

HOST_ARCH=x86

HOST_OS=linux

HOST_BUILD_TYPE=release

BUILD_ID=MASTER

============================================

target thumb C: libtest8 <= system/core/libtest8/libtest8.c

target SharedLib: libtest8 (out/target/product/generic/obj/SHARED_LIBRARIES/libt

est8_intermediates/LINKED/libtest8.so)

target Non-prelinked: libtest8 (out/target/product/generic/symbols/system/lib/li

btest8.so)

target Strip: libtest8 (out/target/product/generic/obj/lib/libtest8.so)

Install: out/target/product/generic/system/lib/libtest8.so

之後,筆者把libtest.so放到Windows環境下,首先原本的system.img载入是唯讀的,必須要透過

adb reomunt

讓system.img可以寫入資料 (/dev/block/mtdblock0 /system yaffs2 rw 0 0)

之後,透過 adb push libtest8.so /system/lib

616 KB/s (25248 bytes in 0.040s)

把函式庫放到目標主機上,然後透過Eclipse執行下面的程式碼

package loda.Test8;
import android.app.Activity;
import android.util.Log;
import android.os.Bundle;
import java.io.IOException;
import java.io.InputStream;
import java.lang.IllegalStateException;

public class Test8 extends Activity {
private native void HelloWorld();
static {
System.loadLibrary("test8″);
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("loda", "libtest8 Before Call JNI HelloWorld");
HelloWorld();
Log.i("loda", "libtest8 After Call JNI HelloWorld");
setContentView(R.layout.main);
}

}

就可以看到在Eclipse的環境中,的logcat有來自Java程式透過JNI介面呼叫HelloWorld C函式庫的結果了.

接下來,我們把這個JNI範例加上傳入參數與傳回值,如下範例C程式

#include <jni.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#define LOG_TAG "C LOG DEMO"

#undef LOG

#include <utils/Log.h>

char gTest8Buffer[]="Unicode based Global Sring Reference from JNI-C";

JNIEXPORT jstring JNICALL Java_loda_Test8d_Test8d_HelloWorld(JNIEnv * env, jobject jobj,jint test_int,jstring test_string,jdouble test_double)

{

const jbyte *jni_string;

jni_string=(*env)->GetStringUTFChars(env,test_string,0);

if(jni_string==NULL)

{

return NULL;

}

printf("int:%d string:%s double:%f\n",test_int,jni_string,test_double);

LOGI("int:%d string:%s double:%f\n",test_int,jni_string,test_double);

(*env)->ReleaseStringUTFChars(env,test_string,jni_string);

return (*env)->NewStringUTF(env, gTest8Buffer);

}

我們額外加入, int,String與double型態的參數,由Java端傳入給JNI的C程式,編譯後,同樣的透過adb push libtest8d.so /system/lib

61 KB/s (5164 bytes in 0.082s)

傳到Android Target上,然後再Eclipse上編譯如下的Java程式

package loda.Test8d;

import android.app.Activity;
import android.util.Log;
import android.os.Bundle;

public class Test8d extends Activity {

private native String HelloWorld(int a,String b,double c);

static {
System.loadLibrary("test8d");
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("loda", "libtest8d Before Call JNI HelloWorld");
String vStr="It is a string from Java";
String vRet= HelloWorld(5799232,vStr,123.456);
Log.i("loda", vRet);
Log.i("loda", "libtest8d After Call JNI HelloWorld");
setContentView(R.layout.main);
}
}

就可以示範如何從Java端傳遞整數,字串與浮點數給JNI C程式,然後再從C程式中傳遞結果字串給Java應用程式.

接下來,我們再舉另一個例子,示範如何從JNI C程式中,去呼叫Java端的應用函式,如此我們就可以達成C與Java端彼此可以呼叫的結果,並且可以驗證這些JNI的行為在Android架構下確實可以運作無誤.

(參考:http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/method.html )

Signature Java Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L fully-qualified-class;
[ type type[]
( arg-types ) ret-type method type

如下所示,我們在JNI C函式中透過 GetMethodID取得Java函式String CallFromJNI(int a,String b,double c) 的c函式與參數宣告 “(ILjava/lang/String;D)Ljava/lang/String;”,

#include <jni.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#define LOG_TAG "C LOG DEMO"

#undef LOG

#include <utils/Log.h>

char gTest8Buffer[]="Unicode based Global Sring Reference from JNI-C";

JNIEXPORT jstring JNICALL Java_loda_Test8d_Test8d_HelloWorld(JNIEnv * env, jobject jobj,jint test_int,jstring test_string,jdouble test_double)

{

jclass vClass=(*env)->GetObjectClass(env,jobj);

jmethodID vMethodID=(*env)->GetMethodID(env,vClass,"CallFromJNI","(ILjava/lang/String;D)Ljava/lang/String;");

if(vMethodID==NULL)

{

LOGI("Falied to load Java Func CallFromJNI");

return NULL;

}

jstring vStr=(*env)->NewStringUTF(env, gTest8Buffer);

jstring vRetStr=(*env)->CallObjectMethod(env,jobj,vMethodID,123,vStr,4321.1234);

const jbyte *jni_string=(*env)->GetStringUTFChars(env,vRetStr,0);

if(jni_string==NULL)

{

return NULL;

}

LOGI("Java Method Return String:%s",jni_string);

(*env)->ReleaseStringUTFChars(env,vRetStr,jni_string);

return vStr;

透過如下命令傳到手機上

adb push libtest8d.so /system/lib

419 KB/s (5160 bytes in 0.012s)

再透過如下的Java 程式碼,就可以完成從Java呼叫JNI C函式,到由JNI C函式呼叫回Java函式的兩者互通的函式呼叫機制,並可在Android環境中驗證無誤,如下範例程式碼

package loda.Test8d;

import android.app.Activity;
import android.util.Log;
import android.os.Bundle;

public class Test8d extends Activity {

private native String HelloWorld(int a,String b,double c);

static {
System.loadLibrary("test8d");
}
private String CallFromJNI(int a,String b,double c)
{
Log.i("loda", "int:"+a+"_String:"+b+"_double:"+c);
return "From CallFromJNI in Java";
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("loda", "libtest8d Before Call JNI HelloWorld");
String vStr="It is a string from Java";
String vRet= HelloWorld(5799232,vStr,123.456);
Log.i("loda", vRet);
Log.i("loda", "libtest8d After Call JNI HelloWorld");
setContentView(R.layout.main);
}
}

最後,再示範如何透過JNI C函式,去取得Java的Class變數的機制,雖然一般模組化的設計,跨模塊的變數存取最好都是透過函式實作來達成,直接去存取變數並不是一個好的實作方式,但本文的目的主要是演示這些JNI操作的實例作為相關設計的參考之用,所以,還請僅供各位參考,實作上還是建議可以透過函式來使用跨模組的變數,而不是直接自行存取造成後續維護或是問題收斂上的困難.

如下為JNI C程式碼的範例,由C程式碼去參考Java程式碼中宣告的int,String與double變數

#include <jni.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#define LOG_TAG "C LOG DEMO"

#undef LOG

#include <utils/Log.h>

char gTest8Buffer[]="Sring from JNI-C";

JNIEXPORT jstring JNICALL Java_loda_Test8e_Test8e_HelloWorld(JNIEnv * env, jobject jobj)

{

jfieldID vFieldID;

jint jInt;

jstring jStr;

jdouble jDouble;

const char *vpTemp;

jclass vClass=(*env)->GetObjectClass(env,jobj);

/////////////////////////////////////////////

//Get and Set Integer from Java

//

vFieldID=(*env)->GetFieldID(env,vClass,"IntInJava","I");

if(vFieldID==NULL)

{

LOGI("Falied to load Java Integer Variable");

return NULL;

}

jInt=(*env)->GetIntField(env,jobj,vFieldID);

if(jInt==0)

{

LOGI("Falied to get jInt Object");

return NULL;

}

LOGI("jInt:%d",jInt);

jInt=987654321;

(*env)->SetIntField(env,jobj,vFieldID,jInt);

/////////////////////////////////////////////

/////////////////////////////////////////////

//Get and Set String from Java

//

vFieldID=(*env)->GetFieldID(env,vClass,"StringInJava","Ljava/lang/String;");

if(vFieldID==NULL)

{

LOGI("Falied to load Java String Variable");

return NULL;

}

jStr=(*env)->GetObjectField(env,jobj,vFieldID);

if(jStr==NULL)

{

LOGI("Falied to get jStr Object");

return NULL;

}

vpTemp=(*env)->GetStringUTFChars(env,jStr,NULL);

if(vpTemp==NULL)

{

LOGI("Falied to get UTF8 String Variable");

return NULL;

}

LOGI("jStr:%s",vpTemp);

(*env)->ReleaseStringUTFChars(env,jStr,vpTemp);

jStr=(*env)->NewStringUTF(env,"JNI C Copy to Java String Variable");

if(jStr==NULL)

{

LOGI("Falied to allocate UTF8 String Variable");

return NULL;

}

(*env)->SetObjectField(env,jobj,vFieldID,jStr);

////////////////////////////////////////////////

/////////////////////////////////////////////

//Get and Set Double from Java

//

vFieldID=(*env)->GetFieldID(env,vClass,"DoubleInJava","D");

if(vFieldID==NULL)

{

LOGI("Falied to load Java Double Variable");

return NULL;

}

jDouble=(*env)->GetDoubleField(env,jobj,vFieldID);

if(jDouble==0)

{

LOGI("Falied to get jDouble Object");

return NULL;

}

LOGI("jDouble:%f",jDouble);

jDouble=9876.5432;

(*env)->SetDoubleField(env,jobj,vFieldID,jDouble);

/////////////////////////////////////////////

jstring vStr=(*env)->NewStringUTF(env, gTest8Buffer);

return vStr;

}

透過如下命令傳到手機上

adb push libtest8e.so /system/lib

334 KB/s (5136 bytes in 0.015s)

如下所示,為搭配上述JNI C程式碼參考到Java中變數,所配套的Java 程式碼

package loda.Test8e;

import android.app.Activity;
import android.util.Log;
import android.os.Bundle;

public class Test8e extends Activity {

private native String HelloWorld();
private int IntInJava;
private String StringInJava;
private double DoubleInJava;

static {
System.loadLibrary("test8e");
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IntInJava=99999;
StringInJava="String from Java";
DoubleInJava=7777.6666;
Log.i("loda", "libtest8e Before Call JNI HelloWorld");
String vRet= HelloWorld();
Log.i("loda:", "IntInJava:" + IntInJava);
Log.i("loda:","StringInJava:" + StringInJava);
Log.i("loda:","DoubleInJava:" + DoubleInJava);
Log.i("Return String:", vRet);
Log.i("loda", "libtest8e After Call JNI HelloWorld");
setContentView(R.layout.main);
}
}
接下來,讓我們示範由JNI C程式碼中,透過初始化要使用的Java Class與使用所包含的Method

#include <jni.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#define LOG_TAG "C LOG DEMO"

#undef LOG

#include <utils/Log.h>

char gTest8Buffer[]="Sring from JNI-C";

JNIEXPORT jstring JNICALL Java_loda_Test8f_Test8f_HelloWorld(JNIEnv * env, jobject jobj)

{

jmethodID vMethodID;

jobject vNewObj;

jclass vClass=(*env)->FindClass(env,"loda/Test8f/MyJavaClass");

if(vClass==NULL)

{

LOGI("Falied to FindClass MyJavaClass");

return NULL;

}

/////

vMethodID=(*env)->GetMethodID(env,vClass,"<init>","()V");

if(vMethodID==NULL)

{

LOGI("Falied to Get init of MyJavaClass");

return NULL;

}

vNewObj=(*env)->NewObject(env,vClass, vMethodID, NULL);

/////

vMethodID=(*env)->GetMethodID(env,vClass,"MyRun","(Ljava/lang/String;)Ljava/lang/String;");

if(vMethodID==NULL)

{

LOGI("Falied to Get MyRun of MyJavaClass");

return NULL;

}

LOGI("Get MyJavaClass Method MyRun");

jstring vStr=(*env)->NewStringUTF(env, gTest8Buffer);

jstring vRetStr=(*env)->CallObjectMethod(env,vNewObj,vMethodID,vStr);

const jbyte *jni_string=(*env)->GetStringUTFChars(env,vRetStr,0);

if(jni_string==NULL)

{

LOGI("Falied to GetStringUTFChars");

return NULL;

}

LOGI("Java Method Return String:%s",jni_string);

(*env)->ReleaseStringUTFChars(env,vRetStr,jni_string);

(*env)->DeleteLocalRef(env,vClass);

return vStr;

}

透過如下命令傳到手機上

adb push libtest8f.so /system/lib

417 KB/s (5132 bytes in 0.012s)

如下所示,為搭配上述JNI C程式碼參考到Java中變數,所配套的Java 程式碼

package loda.Test8f;

import android.app.Activity;

import android.util.Log;

import android.os.Bundle;

public class Test8f extends Activity {

private native String HelloWorld();

static {

System.loadLibrary("test8f");

}

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

String vRet= HelloWorld();

Log.i("Return String:", vRet);

setContentView(R.layout.main);

}

}

class MyJavaClass implements Runnable

{

public void run()

{

}

public String MyRun(String StringFromJNIC)

{

try

{

Log.i("loda:", "in MyJavaClass:" + StringFromJNIC);

}

catch(Exception e)

{

e.printStackTrace();

}

Log.i("loda:","End of MyRun");

return "From MyJavaClass/MyRun in Java";

}

}

透過C程式要啟動Dalvik虛擬器,會透過呼叫函式 JNI_CreateJavaVM (實作在dalvik/vm/Jni.c中),之後透過函式dvmStartup(實作在dalvik/vm/Init.c),再呼叫函式dvmPrepMainThread(實作在dalvik/vm/Thread.c)產生Dalvik虛擬器的Main Thread.

Google從2.2版Foryo開始釋出NDK r4的版本,支援JNI單步除錯的機制.除了可以直接透過JNI介面開發 Java與Native Code的整合應用外,Google當然也提供了NDK的Framework,讓有這需求的開發者可以有一定程度的規範.

接下來,我們就針對JNI的介面進一步的追蹤系統運作的原理,

以 hello-jni範例提供的JNI C函式Java_com_example_hellojni_HelloJni_stringFromJNI為例,進入到 Java_com_example_hellojni_HelloJni_stringFromJNI函式後,此時的Call Stack為

(gdb) bt full

#0  Java_com_example_hellojni_HelloJni_stringFromJNI (env=0xaa50,thiz=0x43e1d078)

at C:/cygwin/android-ndk-r5b-windows/samples/hello-jni/jni/hello-jni.c:30

No locals.

#1  0×80213978 in ?? ()

確認Registers LR與PC

lr             0×80213978       2149661048

pc             0x803002e4       0x803002e4 <Java_com_example_hellojni_HelloJni_stringFromJNI+8>

進一步確認上一層記憶體0×80213974中的程式邏輯

0x8021396c:     ldmia   r9, {r2, r3}

0×80213970:     ldr     r12, [r4, #8]

0×80213974:     blx     r12 =>由此跳到Java_com_example_hellojni_HelloJni_stringFromJNI 中執行C函式庫

0×80213978:     cmp     r5, #0  ; 0×0

0x8021397c:     ldrne   r12, [r4, #12]

0×80213980:     stmneia r12, {r0, r1}

0×80213984:     ldmdb   r4, {r4, r5, r6, r7, r8, r9, sp, pc}

0×80213988:     mov     r5, r2, lsr #28

0x8021398c:     ldr     r6, [r4, #4]

0×80213990:     mov     r2, #0  ; 0×0

0×80213994:     ldrb    r12, [r6], #1

0×80213998:     cmp     r12, #0 ; 0×0

0x8021399c:     beq     0x802139c0

0x802139a0:     add     r2, r2, #1      ; 0×1

0x802139a4:     cmp     r12, #68        ; 0×44

0x802139a8:     cmpne   r12, #74        ; 0x4a

0x802139ac:     bne     0×80213994

由上述的程式碼,我們可以對應到JNI呼叫到Native Code中的實作代碼是在dalvik/vm/arch/arm/CallEABI.S 原始碼中的Lcopy_done程式碼,負責由Java World呼叫到JNI Native Code

.Lcopy_done:

……………….

ldmia   r9, {r2-r3}                 @ r2/r3<- argv[0]/argv[1]

@ call the method

ldr     ip, [r4, #8]                @ func

#ifdef __ARM_HAVE_BLX

blx     ip

#else

mov     lr, pc

bx      ip

#endif

由此溯源,我們得知函式dvmPlatformInvoke是在Dalvik Vm中負責將Java World與Native World做一個呼叫轉換的函式. 參考dalvik/vm/arch中的實作,Android本身有提供對 arm/sh/x86/x86-atom JNI Calling Convention的組語優化,如果不是上述平台,就會透過FFI (Foreign Function Interface)(網址:http://sourceware.org/libffi/ )函式庫來實作函式dvmPlatformInvoke,達成JNI介面在不同平台上的支援. FFI機制,提供一個完整的介面,讓從Interpreter端可以呼叫到原生碼的實作,包括參數的傳遞,與對不同平台的支持,參考網頁,除了Dalvik JNI介面有採用外,包括FireFox 3.6/Mac OSX/iPhone JavaScript,Ruby,Python ..etc 都有使用這個介面達成由Interpreter呼叫到原生碼的機制.

Just-In-Time Compiler

有關JIT把ByteCode轉成原生指令集的概念,前兩天也聽到同事在聊LLVM(Low level virtual machine, 網址http://llvm.org/ ),這概念是把C/C++/Fortran的程式語言透過llvm-gcc轉成中介語言,之後透過LLVM轉換成執行平台原生的指令集,有關程式碼與中介語言的產生,可以透過該網站的Demo網頁http://llvm.org/demo/index.cgi 實際的操刀體驗看看.

Dalvik系統除了有Portable與Fast Intepreter實作外,還可以透過JIT 的機制,實現把Dalvik OpCode轉成原生代碼的機制,讓運作的效率可以變得更高,參考dvmMterpStdRun的實作,我們可以知道JIT在Dalvik中的入口是透過Fast Interpreter,如果你所在平台用的是Portable Interpreter,那JIT的機制也就沒有作用了.(ㄟ 也是啦,,,,Fast Interpreter就是Dalvik用組語去刻出來的加速Dalvik OpCode直譯器,如果你不是用ARM或是x86的指令集平台,或其他第三方有支援Fast Interpreter組語實作的Dalvik版本,只能採用C版本的實作,自然也不可能會有JIT機制的實作了.).

可以參考如下的新聞Android 2.2 Froyo 450% Faster Than Eclair(網頁:http://www.mobiletopsoft.com/board/7646/android-2-2-froyo-450-faster-than-eclair.html ),在有開啟JIT的Android 2.2 Froyo版本中,可以得到比沒有開啟JIT的2.1 Eclair大約4.5倍的Dalvik應用執行效率.(分別為37.5 MFLOPS 與 6.5-7 MFLOPS).

參考Google的文件,JIT的技術,有很多種施行的方式,包括在載入時期編譯,安裝應用程式時編譯,函式被呼叫時編譯整個函式,或是Byte Code指令在被擷取時編譯,而編譯時,可以選擇把整個應用程式編譯,動態函式庫,針對Class的Method編譯,Trace-JIT(選擇熱點編譯),或一次只編譯一個Byte Code指令.Android在選擇JIT技術時,主要希望以符合移動,透過電池供電,最小的記憶體需求(可以參考Jazelle網頁http://www.arm.com/products/processors/technologies/jazelle.php,Java Byte Code to Native Code記憶體會膨脹4-8倍),要跟既有的Dalvik安全機制共存,快速達到效能的提升,能在既有的直譯器(Intepreter)與JIT技術間平順的過渡.一般常見的Java虛擬器(例如 SUN  Java HotSpot Virtual Machine)採用的是Method-JIT,而Dalvik採用的是Trace-JIT,兩者的差別如下

Method JIT 1,目前最常見的JIT機制

2,透過直譯器(Interpreter)去統計出熱門的Class Method

3,編譯與最佳化以Java Class Method為單位

4,有關虛擬器對應到直譯器的資訊,只需要考慮到所走過的Method為主.

5,缺點在於,所最佳化的Mehtod中,可能會包含幾乎用不到的程式碼片段,由於優化的範圍大,因此對記憶體需求較高,在函式一開始執行時,呼叫到熱門Mehtod,需要有比較長的延遲時間(等待最佳化為機械碼)

Trace JIT 1,透過直譯器去統計熱門的執行路徑(Execution Path)

2,會把編譯的程式碼片段串(Fragment Chain)放到轉譯的Cache中(Dalvik-jit-code-cache)

3,只有最熱門的程式碼執行路徑(片段)會被編譯到,可以節省記憶體的需求,並且可以跟直譯器更緊密的結合,擁有較快速的編譯與執行效能(因為不用根據整個Method去編譯)

4,每個Dalvik行程都有獨立的Dalvik虛擬器,並擁有一個Translation Cache(用來儲存編譯為機械碼的Trace段落).

5,缺點在於,優化的範圍較小,會跟直譯器互動的頻率高,並且,較難以把最佳化過的程式碼片段跨行程分享.(由於不是以Method為單位,只根據每個Mehtod中的程式碼片段優化,每個應用程式的行為差異不小,不過這有一篇交大碩士的論文http://www.cis.nctu.edu.tw/~wuuyang/papers/File-Based%20Sharing%20For%20Dynamically%20Compiled%20Code%20On%20Dalvik%20Virtual%20Machine.pdf ,有提到如何在Dalvik中跨行程分享這優化過的程式碼片段)

6,Android建議把JIT視為直譯器能力的延伸

7,確保Trace段落,只會包含有成功執行過的Byte Code

8,會根據使用的平台(通常為ARM),優化暫存器的使用,Load/Store指令的使用(減少需要去外部記憶體存取的成本),消除不必要的Null-Check.

9,優化Byte Code中Loop迴圈的執行.

10,通常會以一個Branch的目標位址作為一個Trace區塊的頭.(以此為Trace區塊也合理,便於在Translation Cache與Interpreter之間進行跳躍).

參考這份Android文件http://www.android-app-developer.co.uk/android-app-development-docs/android-jit-compiler-androids-dalvik-vm.pdf ,Android Dalvik基於記憶體需求,功耗(For 移動裝置)與應用程式反應時間,選擇了Trace-JIT為實作的技術,運作的機制為,應用程式先透過Fast Interpreter載入,依據執行的位置,更新程式執行統計表格,,如果該位置沒有達到Threshold,直譯器繼續執行,直到到達下一個可能的Trace段落,再度確認統計表格,是否有達到Threshold,若達到就確認該Trace段落,是否已經有對應編譯的結果,如果有就直接跳過去執行,如果沒有就編譯該Trace段落,並加入到JIT轉譯快取(Translation Cache)中. 位於Translation中的Trace段落編譯結果,可以依據應用程式的行為彼此呼叫,如果該編譯段落的下一段Trace Fragment並不在Translation Cache中,就直接呼叫回直譯器執行該Trace Fragment,繼續Dalvik應用程式的執行.

ARMv5te架構下 gDvmJit.threshold = 200 (in Sorce Code compiler/codegen/arm/armv5te/ArchVariant.c),而ARMv7a架構下 gDvmJit.threshold = 40 ( compiler/codegen/arm/armv7-a/ArchVariant.c),以此來看,處理器的時脈越高,Android對於Trace區塊Threshold的要求就越低,也就是會盡可能都透過JIT編譯讓運作環境更快.(當然也表示對同一Android區塊,JIT的編譯時間越短).

參考Android文件(抱歉,,我就不自己OProfile了…@_@),Dalvik JIT對Trace段落熱區的統計,可以達到有97%是落在Translation Cache(Dalvik-jit-code-cache)中(在Cache中的Trace段落可以彼此呼叫,直到遇到不在Cache中的Trace段落才需要回到Interpreter),只有3%需要透過Fast Interpreter (Libdvm.so)執行.

承上,以system_server為例,在開機後20分鐘,大約有9898個Trace段落被編譯,總共佔Byte Code大小約103966bytes(平均每個Trace段落Byte Code約11bytes),編譯需要時間約6.024秒,編譯後的機械碼大小為796264 bytes (平均每個Trace段落轉成機械碼約80bytes),所涉及的Method Byte Code程式碼大小為396230 bytes(如果用Trace-JIT可以省去編譯2.8倍的ByteCode範圍),相對於省去的JIT機械碼大小就更可觀了(Method Byte Code程式碼大小:396230 bytes 乘上7倍(約80/11)=2773610bytes,約可省下編譯後約1.977364MB).

Dalvik在選擇JIT Trace區塊的時候,會在以下OpCode中進行Profile的統計,找出熱門的執行Trace區塊(參考 dalvik/vm/mterp/out/InterpAsm-armv5te.S實作),作為Trace區塊的Head.

指令集名稱 編碼
OP_GOTO 0×28
OP_GOTO_16 0×29
OP_GOTO_32 0x2a
OP_PACKED_SWITCH 0x2b
OP_SPARSE_SWITCH 0x2c
OP_IF_EQ 0×32
OP_IF_NE 0×33
OP_IF_LT 0×34
OP_IF_GE 0×35
OP_IF_GT 0×36
OP_IF_LE 0×37
OP_IF_EQZ 0×38
OP_IF_NEZ 0×39
OP_IF_LTZ 0x3a
OP_IF_GEZ 0x3b
OP_IF_GTZ 0x3c
OP_IF_LEZ 0x3d

我們舉實際的例子來說明,不過首先定義一下,

在ARM EABI GCC編譯器中,對於ARM暫存器的識別名稱

暫存器 識別名稱 說明
r10 sl seems to be generally available
r11 fp is used by gcc (unless -fomit-frame-pointer is set)
r12 ip is scratch — not preserved across method calls
r13 sp should be managed carefully in case a signal arrives
r14 lr must be preserved
r15 pc can be tinkered with directly

在Fast Interpreter中,對於ARM暫存器的使用與識別名稱

暫存器 識別名稱 說明
r4 rPC interpreted program counter, used for fetching instructions
r5 rFP interpreted frame pointer, used for accessing locals and args
r6 rGLUE MterpGlue pointer
r7 rINST first 16-bit code unit of current instruction
r8 rIBASE interpreted instruction base pointer, used for computed goto

還有參考dalvik/vm/Globals.h 中對於gDvmJit的宣告 “extern struct DvmJitGlobals gDvmJit;”,其中 DvmJitGlobals定義了JIT運作時,所需的相關變數與狀態資訊. 以及dalvik/vm/Thread.h中對於Thread的宣告, 建議可以一併理解.

參考 dalvik/vm/mterp/common/asm-constants.h中,關於組語中會用到的變數定義,同時參考dalvik/vm/mterp/Mterp.h 中的宣告 "typedef InterpState MterpGlue;” 與在原始碼 dalvik/vm/interp/InterpDefs.h中關於 “typedef struct InterpState”的原型,我定義幾個在後續說明時,會用到的MterpGlue struct參數名稱與說明

ARM 組語中的名稱 C程式碼中的變數名稱 說明
offGlue_pJitProfTable pJitProfTable Array of profile threshold counters
offGlue_jitThreshold jitThreshold 為判斷該目標位置,是否已達可進行JIT編譯為原生碼的要求,以筆者手中的版本來說,armv5te-vfp/armv5te定義為200,而armv7-a-neon/armv7-a定義為40即達可進行JIT編譯的門檻.
pJitEntryTable 會透過JIT Hash機制,用來儲存在Fast Interpret中有被編譯為原生碼的目標位置Trace段落.
offThread_inJitCodeCache inJitCodeCache 相對於每個Dalvik Thread,用來儲存對應的Trace區塊編譯後的原生碼記憶體位址
offGlue_jitState jitState 設定Debug或要取Trace區塊時,Interpreter所在的狀態,可以包括以下的屬性

kJitNot = 0,               // Non-JIT related reasons */

kJitTSelectRequest = 1,    // Request a trace (subject to filtering)

kJitTSelectRequestHot = 2, // Request a hot trace (bypass the filter)

kJitSelfVerification = 3,  // Self Verification Mode

/* Operational states in the debug interpreter */

kJitTSelect = 4,           // Actively selecting a trace

kJitTSelectEnd = 5,        // Done with the trace – wrap it up

kJitSingleStep = 6,        // Single step interpretation

kJitSingleStepEnd = 7,     // Done with single step, ready return to mterp

kJitDone = 8,              // Ready to leave the debug interpreter

InterpEntry 組語的宣告

MTERP_CONSTANT(kInterpEntryInstr,   0)

MTERP_CONSTANT(kInterpEntryReturn,  1)

MTERP_CONSTANT(kInterpEntryThrow,   2)

MTERP_CONSTANT(kInterpEntryResume,  3)

C的宣告

kInterpEntryInstr = 0,      // continue to next instruction

kInterpEntryReturn = 1,     // jump to method return

kInterpEntryThrow = 2,      // jump to exception throw

kInterpEntryResume = 3,     // Resume after single-step

offGlue_entryPoint entryPoint 用來儲存所要去的Interpreter

接下來,進行流程上的說明,在OP_GOTO中,如果有加入JIT的支援就會,加入這段Code (我會把認為流程上可以忽略的Code “….”掉)

mov     r0, rINST, lsl #16          @ r0<- AAxx0000

movs    r9, r0, asr #24             @ r9<- ssssssAA (sign-extended)

mov     r9, r9, lsl #1              @ r9<- byte offset=>r9儲存Goto要跳去的Offset

=>如果r9為負數,表示會往低位址跳,會跳到函式 common_backwardBranch中執行

bmi     common_backwardBranch       @ backward branch, do periodic checks

=>如果r9為正數,就繼續執行

#if defined(WITH_JIT)

GET_JIT_PROF_TABLE(r0)

FETCH_ADVANCE_INST_RB(r9)           @ update rPC, load rINST

cmp     r0,#0

bne     common_updateProfile

GET_INST_OPCODE(ip)                 @ extract opcode from rINST

GOTO_OPCODE(ip)                     @ jump to next instruction

#else

……

#endif

其中函式common_backwardBranch的實作如下,

common_backwardBranch:

mov     r0, #kInterpEntryInstr

……

#if defined(WITH_JIT)

GET_JIT_PROF_TABLE(r0)

FETCH_ADVANCE_INST_RB(r9)           @ update rPC, load rINST

cmp     r0,#0

bne     common_updateProfile

GET_INST_OPCODE(ip)

GOTO_OPCODE(ip)

#else

……

#endif

巨集 FETCH_ADVANCE_INST_RB(宣告為#define FETCH_ADVANCE_INST_RB(_reg) ldrh    rINST, [rPC, _reg]!),會把帶入的GOTO目標偏移植(Offset)當做rPC(rPC為Fast Interpreter中的Program Counter)的偏移植,並把指令存在rINST,與更新下一個指令位置到rPC中. 巨集GET_JIT_PROF_TABLE (宣告為 #define GET_JIT_PROF_TABLE(_reg)    ldr     _reg,[rGLUE,#offGlue_pJitProfTable]), 會傳回目前Profile Threshold Counters表格的記憶體位置,如果該值為0,表示目前並沒有啟動指令Profiling Threshold的機制,就執行原本Fast Interpreter的流程,如果該值不為0,就表示JIT的Profiling Threshold機制有開啟,就跳到函式common_updateProfile進行處理. 如下所示

common_updateProfile:

=>rPC儲存的是GOTO要去執行的目標位置

eor     r3,rPC,rPC,lsr #12 @ cheap, but fast hash function

=>在筆者ARM_ARCH_5TE組態中,JIT_PROF_SIZE_LOG_2=9,其它平台為11

lsl     r3,r3,#(32 – JIT_PROF_SIZE_LOG_2)          @ shift out excess bits

=>以在Fast Interpreter中的Program Counter對Profile Threshold Counters表格(位置在r0)做Hash,然後把目前目標位置的Threshold值,放到r1

ldrb    r1,[r0,r3,lsr #(32 - JIT_PROF_SIZE_LOG_2)] @ get counter

=>把rINST & 0xff 存到 r12中,成為一個 Dalvik OpCode

GET_INST_OPCODE(ip)

subs    r1,r1,#1           @ decrement counter

strb    r1,[r0,r3,lsr #(32 - JIT_PROF_SIZE_LOG_2)] @ and store it

=>如果r1不為0,就直接跳到該Dalvik OpCode所在的位址,表示該目標位置尚未達Threshold的要求,還是執行Fast Interpreter的原本機制,不編譯為原生碼.

GOTO_OPCODE_IFNE(ip)       @ if not threshold, fallthrough otherwise */ (CPSR.Z=0)
=>如果r1為0,就繼續執行,根據Trace區塊,進行編譯的流程.
………………………..

=>取得目前平台預設的Threshold數值

GET_JIT_THRESHOLD(r1)

ldr     r10, [rGLUE, #offGlue_self] @ callee saved r10 <- glue->self

=>重設該目標位置的Threshold Counter,ARMv5預設是200,ARMv7預設是40

strb    r1,[r0,r3,lsr #(32 - JIT_PROF_SIZE_LOG_2)] @ reset counter

=>把Fast Interpreter要去執行的目標位置,存到Interpreted Frame(Stack)

EXPORT_PC()

mov     r0,rPC

bl      dvmJitGetCodeAddr           @ r0<- dvmJitGetCodeAddr(rPC)

=>呼叫函式dvmJitGetCodeAddr ,如果位於Fast Interpreter的Program Counter已經有被編譯過,並儲存在Translation Cache中,就傳回對應的記憶體位置

str     r0, [r10, #offThread_inJitCodeCache] @ set the inJitCodeCache flag

=>把編譯過的Code Cache記憶體位址儲存在#offGlue_self中的#offThread_inJitCodeCache欄位中

mov     r1, rPC                     @ arg1 of translation may need this

mov     lr, #0                      @  in case target is HANDLER_INTERPRET

cmp     r0,#0

=>如果r0為0,表示尚未編譯過,反之表示編譯過,存在Translation Cache

#if !defined(WITH_SELF_VERIFICATION)

……………

#else

=>如果目前Fast Interpreter要去的Program Counter尚未編譯過,就設定r2為selectTrace Request然後呼叫函式common_selectTrace

moveq   r2,#kJitTSelectRequest      @ ask for trace selection

beq     common_selectTrace

/*

* At this point, we have a target translation.  However, if

* that translation is actually the interpret-only pseudo-translation

* we want to treat it the same as no translation.

*/

mov     r10, r0                     @ save target

bl      dvmCompilerGetInterpretTemplate

cmp     r0, r10                     @ special case?

bne     jitSVShadowRunStart         @ set up self verification shadow space

GET_INST_OPCODE(ip)

GOTO_OPCODE(ip)

/* no return */

#endif

巨集GET_JIT_THRESHOLD (宣告為#define GET_JIT_THRESHOLD(_reg)     ldr     _reg,[rGLUE,#offGlue_jitThreshold]),巨集 EXPORT_PC(宣告為 #define EXPORT_PC()  str     rPC, [rFP, #(-sizeofStackSaveArea + offStackSaveArea_currentPc)]),由函式common_selectTrace會先回到函式dvmMterpStd中(為標準的mterp進入點,實作位置在 dalvik/vm/mterp/Mterp.c),並設定glue->nextMode = INTERP_DBG與傳回true,之後回到函式dvmInterpret(實作的位置在dalvik/vm/interp/Interp.c),因為選擇了Debug版本的Interpreter,呼叫進入dvmInterpretDbg(定義為#define INTERP_FUNC_NAME dvmInterpretDbg ),在函式INTERP_FUNC_NAME(實作在dalvik/vm/mterp/out/InterpC-portdbg.c),再呼叫函式dvmJitCheckTraceRequest(實作在dalvik/vm/interp/Jit.c),再透過函式setTraceConstruction進行Trace的建置,最後呼叫函式dvmCheckJit(實作在dalvik/vm/interp/Jit.c)會一次Interpreter切換週期(由Fast Interpreter切到Debug Interpreter),增加一個指令集的方式進行Trace流程的追蹤,目前一個Trace流程最多可以有64個TraceRun(MAX_JIT_RUN_LEN=64),也就是說如果你是在函式內GOTO到不同位置的話,這些路徑就會被當做個別的TraceRun,最後,放到同一個Trace流程中. 如果是在有Switch操作的函式中,TraceRun的選擇會以一個Switch內的處理指令流程為主,如果離開所在的Switch判斷,就會作為一個Trace流程的結束. 在dvmCheckJit內部會有以下的判斷依據

1,如果目前已執行Trace指令個數長度不為0,且上一次處理的指令為OP_PACKED_SWITCH 或OP_SPARSE_SWITCH,就會將Trace流程結束.

2,如果上一個指令不是GOTO,也不為INVOKE_DIRECT_EMPTY ,而屬於像是IF/Switch/Return/Invoke類的指令,也會作為一個Trace流程的結束.

3,如果上一個指令是一個Exception或是一個Self-Loop,也會作為一個Trace Run的結束 .

4,如果Trace的指令個數超過100個(JIT_MAX_TRACE_LEN=100),也會當做一個Trace流程的結束.

5,如果前一個指令為Return,也會當做一個Trace流程的結束.

當上述Trace流程結束就會把interpState->jitState 設定為 kJitTSelectEnd,或是直接進入到switch  kJitTSelectEnd中,結束Trace流程.

在函式dvmCheckJit中會判斷,如果是第一個Trace區塊的指令,會把當前的Program Counter記錄在interpState->lastPC,然後,直接結束. 第二次進來時,會呼叫dexDecodeInstruction(實作在dalvik/libdex/InstrUtils.c)把上一次的Program Counter進行指令集的Decode,如果解碼後的指令為OP_PACKED_SWITCH或是OP_SPARSE_SWITCH,就會設定interpState->jitState = kJitTSelectEnd把Trace區塊做一個結束. 若繼續執行,就會透過函式dexGetInstrFlags(Inline函式宣告在libdex/InstrUtils.h中),並依據指令集的OpCode決定Flags為以下的值之一

Dalvik 指令 對應到的Flags狀態
OP_NOP,OP_MOVE,OP_MOVE_FROM16,OP_MOVE_16,OP_MOVE_WIDE,

OP_MOVE_WIDE_FROM16,OP_MOVE_WIDE_16,OP_MOVE_OBJECT,

OP_MOVE_OBJECT_FROM16,OP_MOVE_OBJECT_16,OP_MOVE_RESULT,

OP_MOVE_RESULT_WIDE,OP_MOVE_RESULT_OBJECT,

OP_MOVE_EXCEPTION,OP_CONST_4,OP_CONST_16,OP_CONST,

OP_CONST_HIGH16,OP_CONST_WIDE_16,OP_CONST_WIDE_32,

OP_CONST_WIDE,OP_CONST_WIDE_HIGH16,OP_FILL_ARRAY_DATA,

OP_CMPL_FLOAT,OP_CMPG_FLOAT,OP_CMPL_DOUBLE,

OP_CMPG_DOUBLE,OP_CMP_LONG,OP_NEG_INT,OP_NOT_INT,

OP_NEG_LONG,OP_NOT_LONG,OP_NEG_FLOAT,OP_NEG_DOUBLE,

OP_INT_TO_LONG,OP_INT_TO_FLOAT,OP_INT_TO_DOUBLE,

OP_LONG_TO_INT,OP_LONG_TO_FLOAT,OP_LONG_TO_DOUBLE,

OP_FLOAT_TO_INT,OP_FLOAT_TO_LONG,OP_FLOAT_TO_DOUBLE,

OP_DOUBLE_TO_INT,OP_DOUBLE_TO_LONG,OP_DOUBLE_TO_FLOAT,

OP_INT_TO_BYTE,OP_INT_TO_CHAR,OP_INT_TO_SHORT,OP_ADD_INT,

OP_SUB_INT,OP_MUL_INT,OP_AND_INT,OP_OR_INT,OP_XOR_INT,

OP_SHL_INT,OP_SHR_INT,OP_USHR_INT,OP_ADD_LONG,OP_SUB_LONG,

OP_MUL_LONG,OP_AND_LONG,OP_OR_LONG,OP_XOR_LONG,

OP_SHL_LONG,OP_SHR_LONG,OP_USHR_LONG,OP_ADD_FLOAT,

OP_SUB_FLOAT,OP_MUL_FLOAT,OP_DIV_FLOAT,OP_REM_FLOAT

,OP_ADD_DOUBLE,OP_SUB_DOUBLE,OP_MUL_DOUBLE,OP_DIV_DOUBLE,

OP_REM_DOUBLE,OP_ADD_INT_2ADDR,OP_SUB_INT_2ADDR,

OP_MUL_INT_2ADDR,OP_AND_INT_2ADDR,OP_OR_INT_2ADDR,

OP_XOR_INT_2ADDR,OP_SHL_INT_2ADDR,OP_SHR_INT_2ADDR,

OP_USHR_INT_2ADDR,OP_ADD_LONG_2ADDR,OP_SUB_LONG_2ADDR,

OP_MUL_LONG_2ADDR,OP_AND_LONG_2ADDR,OP_OR_LONG_2ADDR,

OP_XOR_LONG_2ADDR,OP_SHL_LONG_2ADDR,OP_SHR_LONG_2ADDR,

OP_USHR_LONG_2ADDR,OP_ADD_FLOAT_2ADDR,OP_SUB_FLOAT_2ADDR,

OP_MUL_FLOAT_2ADDR,OP_DIV_FLOAT_2ADDR,OP_REM_FLOAT_2ADDR,

OP_ADD_DOUBLE_2ADDR,OP_SUB_DOUBLE_2ADDR,

OP_MUL_DOUBLE_2ADDR,OP_DIV_DOUBLE_2ADDR,

OP_REM_DOUBLE_2ADDR,OP_ADD_INT_LIT16,OP_RSUB_INT,

OP_MUL_INT_LIT16,OP_AND_INT_LIT16,OP_OR_INT_LIT16,

OP_XOR_INT_LIT16,OP_ADD_INT_LIT8,OP_RSUB_INT_LIT8,

OP_MUL_INT_LIT8,OP_AND_INT_LIT8,OP_OR_INT_LIT8,OP_XOR_INT_LIT8,

OP_SHL_INT_LIT8,OP_SHR_INT_LIT8,OP_USHR_INT_LIT8

flags = kInstrCanContinue;

表示這類指令會加入Trace區塊,並不會造成Exception

OP_CONST_STRING,OP_CONST_STRING_JUMBO,OP_CONST_CLASS,

OP_MONITOR_ENTER,OP_MONITOR_EXIT,OP_CHECK_CAST,

OP_INSTANCE_OF,OP_ARRAY_LENGTH,OP_NEW_INSTANCE,

OP_NEW_ARRAY,OP_FILLED_NEW_ARRAY,OP_FILLED_NEW_ARRAY_RANGE

,OP_AGET,OP_AGET_BOOLEAN,OP_AGET_BYTE,OP_AGET_CHAR,

OP_AGET_SHORT,OP_AGET_WIDE,OP_AGET_OBJECT,OP_APUT,

OP_APUT_BOOLEAN,OP_APUT_BYTE,OP_APUT_CHAR,OP_APUT_SHORT,

OP_APUT_WIDE,OP_APUT_OBJECT,OP_IGET,OP_IGET_BOOLEAN,

OP_IGET_BYTE,OP_IGET_CHAR,OP_IGET_SHORT,OP_IGET_WIDE,

OP_IGET_OBJECT,OP_IPUT,OP_IPUT_BOOLEAN,OP_IPUT_BYTE,

OP_IPUT_CHAR,OP_IPUT_SHORT,OP_IPUT_WIDE,OP_IPUT_OBJECT,

OP_SGET,OP_SGET_BOOLEAN,OP_SGET_BYTE,OP_SGET_CHAR,

OP_SGET_SHORT,OP_SGET_WIDE,OP_SGET_OBJECT,OP_SPUT,

OP_SPUT_BOOLEAN,OP_SPUT_BYTE,OP_SPUT_CHAR,OP_SPUT_SHORT,

OP_SPUT_WIDE,OP_SPUT_OBJECT,OP_DIV_INT,OP_REM_INT,

OP_DIV_LONG,OP_REM_LONG,OP_DIV_INT_2ADDR,OP_REM_INT_2ADDR,

OP_DIV_LONG_2ADDR,OP_REM_LONG_2ADDR,OP_DIV_INT_LIT16,

OP_REM_INT_LIT16,OP_DIV_INT_LIT8,OP_REM_INT_LIT8,

OP_EXECUTE_INLINE,OP_EXECUTE_INLINE_RANGE,OP_IGET_QUICK,

OP_IGET_WIDE_QUICK,OP_IGET_OBJECT_QUICK,OP_IPUT_QUICK,

OP_IPUT_WIDE_QUICK,OP_IPUT_OBJECT_QUICK

flags = kInstrCanContinue | kInstrCanThrow;

表示這類指令會加入Trace區塊,但有可能造成Exception

OP_INVOKE_VIRTUAL,OP_INVOKE_VIRTUAL_RANGE,OP_INVOKE_SUPER,

OP_INVOKE_SUPER_RANGE,OP_INVOKE_DIRECT,

OP_INVOKE_DIRECT_RANGE,OP_INVOKE_STATIC,

OP_INVOKE_STATIC_RANGE,OP_INVOKE_INTERFACE,

OP_INVOKE_INTERFACE_RANGE,OP_INVOKE_VIRTUAL_QUICK,

OP_INVOKE_VIRTUAL_QUICK_RANGE,OP_INVOKE_SUPER_QUICK,

OP_INVOKE_SUPER_QUICK_RANGE,OP_INVOKE_DIRECT_EMPTY

flags = kInstrCanContinue | kInstrCanThrow | kInstrInvoke;

表示這類指令會加入Trace區塊,有機會發生Exception或是進行外部呼叫

OP_RETURN_VOID,OP_RETURN,OP_RETURN_WIDE,OP_RETURN_OBJECT flags = kInstrCanReturn;

表示為Return指令

OP_THROW,OP_THROW_VERIFICATION_ERROR flags = kInstrCanThrow;

會觸發Exception

OP_GOTO,OP_GOTO_16,OP_GOTO_32 flags = kInstrCanBranch | kInstrUnconditional;

表示一個無條件的Branch動作

OP_IF_EQ,OP_IF_NE,OP_IF_LT,OP_IF_GE,OP_IF_GT,OP_IF_LE,OP_IF_EQZ,

OP_IF_NEZ,OP_IF_LTZ,OP_IF_GEZ,OP_IF_GTZ,OP_IF_LEZ

flags = kInstrCanBranch | kInstrCanContinue;

表示一個搭配條件判斷的Branch動作

OP_PACKED_SWITCH,OP_SPARSE_SWITCH flags = kInstrCanSwitch | kInstrCanContinue;

表示一個Switch判斷,如果該值不在這Switch中,會加入Trace區塊

OP_UNUSED_3E,OP_UNUSED_3F,OP_UNUSED_40,OP_UNUSED_41,

OP_UNUSED_42,OP_UNUSED_43,OP_UNUSED_73,OP_UNUSED_79,

OP_UNUSED_7A,OP_UNUSED_E3,OP_UNUSED_E4,OP_UNUSED_E5,

OP_UNUSED_E6,OP_UNUSED_E7,OP_UNUSED_E8,OP_UNUSED_E9,

OP_UNUSED_EA,OP_UNUSED_EB,OP_BREAKPOINT,OP_UNUSED_F1,

OP_UNUSED_FC,OP_UNUSED_FD,OP_UNUSED_FE,OP_UNUSED_FF

these should never appear when scanning code

flags=0

後續,呼叫dexGetInstrOrTableWidthAbs(實作在libdex/InstrUtils.c)判斷所在位置指令或是資料(PackedSwitchSignature/SparseSwitchSignature/ArrayDataSignature)的長度(指令長度的Table可以透過函式dexCreateInstrWidthTable建立),以及距離所在Class Method起始點的位置(lastPC – interpState->method->insns),如果超過該Class Method的實際實作的大小,就會觸發Assertion.

等到完成一個Trace流程段落,會呼叫函式dvmCompilerWorkEnqueue,把這個Trace流程段落放到JIT編譯器的Queue中,之後就會由Compiler 的thread透過函式dvmCompileTrace(實作在dalvik/vm/compiler/Frontend.c)進行編譯為原生碼的動作.並重新回到函式dvmMterpStd,繼續下一個指令集的抓取與執行,直到又有指令Threshold被滿足,而進行下一次的Trace追蹤的流程. 在進行Trace追蹤的流程時,相關指令都還是會以Fast Interpreter的實作被執行.

要在 Android環境中支援JIT機制,必須在Dalvik編譯時加入WITH_JIT參數,可以參考dalvik/vm/Android.mk,如果所選擇的架構是armv5te (TARGET_ARCH_VARIANT:=armv5te),WITH_JIT預設會是關掉的.然後修改build/target/board/generic/system.prop加入dalvik.vm.execution-mode=int:jit (這個系統參數設定會被加入到執行時期的檔案/system/build.prop), ,就可以開始編譯支援Dalvik JIT的虛擬器.

支援JIT的Dalvik環境除了會編譯出 libdvm.so外,參考dalvik/vm/Android.mk,在編譯Dalvik函式庫時,還會額外產生以下三個函式庫

libdvm_sv.so (開啟Assertion與self-verification)

libdvm_assert.so (開啟Assertion與JIT Tuning)

libdvm_interp.so (這是把WITH_JIT關閉的版本)

可根據自己開發上的除錯,決定要採用的版本.

要確認所編譯的Dalvik虛擬器有無啟動JIT機制,可以在啟動後,執行dalvikvm -h 查看是否有下列參數設定

-Xjitop:hexopvalue[-endvalue][,hexopvalue[-endvalue]]*

-Xincludeselectedmethod

-Xjitthreshold:decimalvalue

-Xjitblocking

-Xjitmethod:signature[,signature]* (eg Ljava/lang/String\;replace)

-Xjitcheckcg

-Xjitverbose

-Xjitprofile

-Xjitdisableopt

簡述ADB,EmulatorNDK-GDB運作的機制

在非直接使用實體手機裝置透過USB開發的環境下,我們可以透過電腦上的模擬器達到便利開發的目的,根據上述的流程,我們可以看到包括abd或是ndk-gdb這些輔助開發,安裝與除錯的工具,都可以跟Emulator互通. 目前ADB,Emulator或是NDK-GDB都是透過網路TCP連線的方式進行互通. 如果使用者使用Eclipse加上ADT的環境,一開始會產生一個ADB的Daemon,監聽port 5037,而Eclipse上的ADT套件,會產生連線到ADT的port 5037. 如果有執行Emulator的話,就會由ADT連到Emulator所監聽的Port5555,與Eclipse連到Emulator所監聽的Port 5554. Emulator的Port訊息也會透過ADB Port 5037交換,如下所示

Receive Side Port:5037

==>Return Code: 0×00000000

001Chost:transport:emulator-5554

Send Size Port:40280

==> Return Code: 0×00000000

OKAY

如果使用者透過ndk-gdb – -start啟動對 JNI應用除錯機制的話,就會由arm-linux-androideabi-gdb.連到adb所監聽的Port 5039進行GDB的操作動作.

(Receive Port:5037 Send Port:49288)

Receive: Return Code: 0×00000000

0052host:forward:tcp:5039;localfilesystem:/data/data/com.example.hellojni/debug-socket

Send: Return Code: 0×00000000

OKAYOKAY

(Receive Port:5037 Send Port:49290)

Receive: Return Code: 0×00000000

0012host:transport-any

Send: Return Code: 0×00000000

OKAY

Receive: Return Code: 0×00000000

004ashell:run-as com.example.hellojni lib/gdbserver +debug-socket –attach 348

Send: Return Code: 0×00000000

OKAY

Send: Return Code: 0×00000000

Attached; pid = 348

Send: Return Code: 0×00000000

Listening on sockaddr socket debug-socket

(Receive Port:5039 Send Port:49295)

Receive: Return Code: 0×00000000

$qSupported#37

Send: Return Code: 0×00000000

+$PacketSize=7cf;qXfer:auxv:read+#70

Receive: Return Code: 0×00000000

++$Hc-1#09

Send: Return Code: 0×00000000

+$E01#a6

Receive: Return Code: 0×00000000

+$qC#b4

Send: Return Code: 0×00000000

+$#00

Receive: Return Code: 0×00000000

+$qOffsets#4b

Send: Return Code: 0×00000000

+$#00

Receive: Return Code: 0×00000000

+$?#3f

Send: Return Code: 0×00000000

+$T050b:0*"00;0d:9838a0be;0f:08ebd0af;#6e

Receive: Return Code: 0×00000000

+$m9040,108#ff

Send: Return Code: 0×00000000

+$010*"c4ca00b0010*"24cf00b0010*"3cd000b0010*"6cd200b0010*"84d300b0010*"dccb00b0010*"f4cc00b0010*"0cce00b020*%90*!210*"080*"190*"0890*!1b0*"080*"1a0*"1090*!1c0*"080*"040*"08810*!50*"ec840*!60*"4c820*!a0*"e5030*!b0*"10*"0150*"a8c400b0030*"48910*!20*"b0*"0140*"110*"170*"fc880* 110*"d4880* 120*"280*"130*"080*}0*!#a8

Receive: Return Code: 0×00000000

+$mb000c4ac,4#1a

Send: Return Code: 0×00000000

+$b0ca00b0#48

Receive: Return Code: 0×00000000

+$mb000cab0,14#46

Send: Return Code: 0×00000000

+$0*"00db3da0be0*"00e04901b0*%#b1

Receive: Return Code: 0×00000000

+$mb00149e0,14#f3

Send: Return Code: 0×00000000

+$0*"00dc4801b0*%e0cc00b0b0ca00b0#e6

結語

本文謹涉及Trace JIT,JNI與有關Dalvik基礎的知識,後續,會視時間從系統運作的角度,來分析Dalvik在效能加速上的議題.

雖盡可能確保資訊的正確性,然若仍有所遺漏,還請不吝告知. 感謝!!


1 comment to Android筆記 – Dalvik的漫談

  • heartburn no more ebook

    I like what you guys are up too. This sort of clever work and exposure! Keep up the wonderful works guys I’ve incorporated you guys to blogroll.

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>