このページではAndroidアプリ開発関連のメモを書いています。
エミュレータ関連
エミュレータ操作のためのショートカットキー
Emulated Device Key | Keyboard Key |
Home | HOME (Mac: fn + left key) |
Menu (left softkey) | F2 or Page-up button |
Star (right softkey) | Shift-F2 or Page Down |
Back | ESC |
Call/dial button | F3 |
Hangup/end call button | F4 |
Search | F5 |
Power button | F7 |
Audio volume up button | KEYPAD_PLUS, Ctrl-F5 |
Audio volume down button | KEYPAD_MINUS, Ctrl-F6 |
Camera button | Ctrl-KEYPAD_5, Ctrl-F3 |
Switch to previous layout orientation (for example, portrait, landscape) | KEYPAD_7, Ctrl-F11 |
Switch to next layout orientation (for example, portrait, landscape) | KEYPAD_9, Ctrl-F12 |
Toggle cell networking on/off | F8 |
Toggle code profiling | F9 (only with -trace startup option) |
Toggle fullscreen mode | Alt-Enter |
Toggle trackball mode | F6 |
Enter trackball mode temporarily (while key is pressed) | Delete |
DPad left/up/right/down | KEYPAD_4/8/6/2 |
DPad center click | KEYPAD_5 |
Onion alpha increase/decrease | KEYPAD_MULTIPLY(*) / KEYPAD_DIVIDE(/) |
ネットワークアドレス
10.0.2.1 | Router/gateway address |
10.0.2.2 | Special alias to your host loopback interface (i.e., 127.0.0.1 on your development machine) |
10.0.2.3 | First DNS server |
10.0.2.4 / 10.0.2.5 / 10.0.2.6 | Optional second, third and fourth DNS server (if any) |
10.0.2.15 | The emulated device's own network/ethernet interface |
127.0.0.1 | The emulated device's own loopback interface |
エミュレータを動作させている開発環境とネットワーク通信を行いたい場合は上記の通り''10.0.2.2''を参照すれば良いです。
NDK(JNI)関連
JNIHelp
JNIで呼び出される関数名はパッケージ名、クラス名、メソッド名を連結した名前を付ける必要があり可読性を著しく損なう原因となっています。そこで、JNIから呼び出される関数をパッケージ単位で登録できる ''jniRegisterNativeMethods'' 関数を使うと便利です(以下抜粋)。
1 | /* |
2 | * Register native JNI-callable methods. |
3 | * |
4 | * "className" looks like "java/lang/String". |
5 | */ |
6 | int jniRegisterNativeMethods(JNIEnv* env, const char* className, |
7 | const JNINativeMethod* gMethods, int numMethods) |
8 | { |
9 | jclass clazz; |
10 | |
11 | LOGV("Registering %s natives\n", className); |
12 | clazz = (*env)->FindClass(env, className); |
13 | if (clazz == NULL) { |
14 | LOGE("Native registration unable to find class '%s'\n", className); |
15 | return -1; |
16 | } |
17 | if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { |
18 | LOGE("RegisterNatives failed for '%s'\n", className); |
19 | return -1; |
20 | } |
21 | return 0; |
22 | } |
23 | |
24 | jint addVals(JNIEnv* env, jobject thiz, jint a, jint b) { |
25 | return a + b; |
26 | } |
27 | jint subVals(JNIEnv* env, jobject thiz, jint a, jint b) { |
28 | return a - b; |
29 | } |
30 | jint mulVals(JNIEnv* env, jobject thiz, jint a, jint b) { |
31 | return a * b; |
32 | } |
33 | jint divVals(JNIEnv* env, jobject thiz, jint a, jint b) { |
34 | return a / b; |
35 | } |
36 | |
37 | static JNINativeMethod methods[] = { |
38 | /* name, signature, func pointer */ |
39 | {"addVals", "(II)I", (void*)addVals}, |
40 | {"subVals", "(II)I", (void*)subVals}, |
41 | {"mulVals", "(II)I", (void*)mulVals}, |
42 | {"divVals", "(II)I", (void*)divVals}, |
43 | }; |
44 | |
45 | EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { |
46 | JNIEnv* env = NULL; |
47 | if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { |
48 | return -1; |
49 | } |
50 | jniRegisterNativeMethods(env, "{パッケージ/クラス名}", methods, NELEM(methods)); |
51 | return JNI_VERSION_1_6; |
52 | } |
popup | print | ? |
最適化
コンパイル時に指定できる最適化関連のオプション
$ arm-linux-androideabi-g++ --target-help The following options are target specific: -mabi= Specify an ABI -mabort-on-noreturn Generate a call to abort if a noreturn function returns -mandroid Generate code for the Android platform. -mapcs-float Pass FP arguments in FP registers -mapcs-frame Generate APCS conformant stack frames -mapcs-reentrant Generate re-entrant, PIC code -march= Specify the name of the target architecture -mbig-endian Assume target CPU is configured as big endian -mbionic Use Bionic C library -mcallee-super-interworking Thumb: Assume non-static functions may be called from ARM code -mcaller-super-interworking Thumb: Assume function pointers may go to non- Thumb aware code -mcirrus-fix-invalid-insns Cirrus: Place NOPs to avoid invalid instruction combinations -mcpu= Specify the name of the target CPU -mfix-cortex-m3-ldrd Avoid overlapping destination and address registers on LDRD instructions that may trigger Cortex-M3 errata. -mfloat-abi= Specify if floating point hardware should be used -mfp16-format= Specify the __fp16 floating-point format -mfpu= Specify the name of the target floating point hardware/format -mglibc Use GNU C library -mhard-float Alias for -mfloat-abi=hard -mlittle-endian Assume target CPU is configured as little endian -mlong-calls Generate call insns as indirect calls, if necessary -mpic-register= Specify the register to be used for PIC addressing -mpoke-function-name Store function names in object code -msched-prolog Permit scheduling of a function's prologue sequence -msingle-pic-base Do not load the PIC register in function prologues -msoft-float Alias for -mfloat-abi=soft -mstructure-size-boundary= Specify the minimum bit alignment of structures -mthumb Compile for the Thumb not the ARM -mthumb-interwork Support calls between Thumb and ARM instruction sets -mtp= Specify how to access the thread pointer -mtpcs-frame Thumb: Generate (non-leaf) stack frames even if not needed -mtpcs-leaf-frame Thumb: Generate (leaf) stack frames even if not needed -mtune= Tune code for the given processor -muclibc Use uClibc C library -mvectorize-with-neon-quad Use Neon quad-word (rather than double-word) registers for vectorization -mword-relocations Only generate absolute relocations on word sized values. -mwords-little-endian Assume big endian bytes, little endian words Known ARM CPUs (for use with the -mcpu= and -mtune= options): cortex-m0, cortex-m1, cortex-m3, cortex-m4, cortex-r4f, cortex-r4, cortex-a15, cortex-a9, cortex-a8, cortex-a5, arm1156t2f-s, arm1156t2-s, mpcore, mpcorenovfp, arm1176jzf-s, arm1176jz-s, arm1136jf-s, arm1136j-s, arm1026ej-s, arm926ej-s, fa726te, fmp626, fa626te, fa606te, iwmmxt2, iwmmxt, xscale, arm1022e, arm1020e, arm10e, arm968e-s, arm966e-s, arm946e-s, arm9e, arm1020t, arm10tdmi, ep9312, arm940t, arm922t, arm920t, arm920, arm9tdmi, arm9, arm740t, arm720t, arm710t, arm7tdmi-s, arm7tdmi, fa626, fa526, strongarm1110, strongarm1100, strongarm110, strongarm, arm810, arm8, arm7dmi, arm7dm, arm7m, arm7500fe, arm7500, arm7100, arm710c, arm720, arm710, arm700i, arm700, arm70, arm7di, arm7d, arm7, arm620, arm610, arm600, arm60, arm6, arm3, arm250, arm2 Known ARM architectures (for use with the -march= option): iwmmxt2, iwmmxt, ep9312, armv7e-m, armv7-m, armv7-r, armv7-a, armv7, armv6-m, armv6t2, armv6zk, armv6z, armv6k, armv6j, armv6, armv5te, armv5e, armv5t, armv5, armv4t, armv4, armv3m, armv3, armv2a, armv2 Assembler options ================= Use "-Wa,OPTION" to pass "OPTION" to the assembler. ARM-specific assembler options: -k generate PIC code -mthumb assemble Thumb code -mthumb-interwork support ARM/Thumb interworking -mapcs-32 code uses 32-bit program counter -mapcs-26 code uses 26-bit program counter -mapcs-float floating point args are in fp regs -mapcs-reentrant re-entrant code -matpcs code is ATPCS conformant -mbig-endian assemble for big-endian -mlittle-endian assemble for little-endian -mapcs-frame use frame pointer -mapcs-stack-check use stack size checking -mno-warn-deprecated do not warn on use of deprecated feature -mcpu=<cpu name> assemble for CPU <cpu name> -march=<arch name> assemble for architecture <arch name> -mfpu=<fpu name> assemble for FPU architecture <fpu name> -mfloat-abi=<abi> assemble for floating point ABI <abi> -meabi=<ver> assemble for eabi version <ver> -mimplicit-it=<mode> controls implicit insertion of IT instructions -EB assemble code for a big-endian cpu -EL assemble code for a little-endian cpu --fix-v4bx Allow BX in ARMv4 code
FPU(浮動小数点演算ユニット)関連のオプション
VFP/NEON関連のオプションです。
- ''ハードウェアFPUを利用''
mfloat-abi オプションで指定します。-mfloat-abi=softfp
を指定してコンパイルすると.fpu vfp ... fmsr s15, r3 @int fsitos s0, s15
このようにVFP(Vector Floating-Point, ARMv6)命令を使います。
- ''FPUの指定''
mfpu オプションで利用するFPUを指定できます。-mfloat-abi=softfp -mfpu=vfpv3-d16
このようにFPU名を明示します。.fpu vfpv3-d16 ... fmsr s15, r3 @int fsitos s0, s15
- ''NEONを利用したベクトル化''
ベクトル化のオプションを追加指定します。-mfloat-abi=softfp -mfpu=neon -ftree-vectorize -mvectorize-with-neon-quad
mfpu オプションでNEONを指定し、ftree-vectorize オプションでループのベクトル化を、mvectorize-with-neon-quad オプションでQレジスタ(128bitレジスタ)を利用するように指定します。.fpu neon ... vmla.i32 q8, q10, q9 vst1.32 {q8}, [ip]!
これでNEON(ARMv7)命令を使うようになります。-mfloat-abi=softfp は値の名前で勘違いしそうですが、浮動小数点演算をエミュレーション実行しているわけではなくハードウェア(FPU)で演算してくれます。このオプションはABI(Application Binary Interface)を指定するもので、関数呼び出し時のレジスタの使われ方が異なります。FPUがない(soft)場合との互換性が取れるように、ハードウェアFPUで関数によって演算が実行される場合でも浮動小数点引数は整数レジスタ(r)に渡されます。
参考: ハードウェア VFP 命令の使用
数学演算関連のオプション
- ffast-math オプション
これはAndroid特有のものではなくGCC一般の最適化オプションです。このオプションを指定すると次のオプションを一度に指定したことになります。-fno-math-errno -funsafe-math-optimizations -fno-trapping-math -ffinite-math-only -fno-signaling-nans
実行速度最適化のためANSIやIEEEの規則や仕様を破ることをGCCに許します。例えば、sqrt 関数の引数が負にならないと仮定したりとか。結果として浮動小数点演算の精度が変わってしまうような最適化でも行うようになります。精度を重視する場合は指定しないようにしましょう。O3オプションを併せて指定されることが多いです。
ndk-buildコマンド
ndk-buildをコマンドラインから実行する際にコンパイルオプションなどを標準出力で確認したい場合は
$ ndk-build V=1
のように Vオプション を指定すれば確認できます。
開発での注意点、SDKバグ関連
XMLパーサー
Android 4.0 (Ice Cream Sandwich) から''XmlPullParser''のコア部分が変更となったため(バグの多いExpatPullParserが削除されKXmlParserに統一)、Android 3.2以前のプロジェクトで動作していたXMLを扱うプログラムが4.0以降では動作しなくなる可能性があります。
参考: Watch out for XmlPullParser.nextText()
画像処理/カメラ関連
画像処理を行うAndroidアプリ開発はメモリとの戦いです。とにかく OutOfMemory エラーが多発します。。
- android.graphics.BitmapFactory
画像のデコードに合計32KBしか使えない模様。ただし、tempStorage の方は任意に指定できる(?
1 public class BitmapFactory { 2 private static final int DECODE_BUFFER_SIZE = 16 * 1024; // デコード用バッファが16KB 3 ... 省略 4 if (!is.markSupported()) { 5 is = new BufferedInputStream(is, DECODE_BUFFER_SIZE); 6 } 7 if (tempStorage == null) tempStorage = new byte[16 * 1024]; // テンポラリストレージが16KB 8 ... 省略 9 bm = nativeDecodeStream(is, tempStorage, outPadding, opts, true, scale); popup | print | ?
- カメラデバイスオープン時の注意点
カメラデバイスを開くときに(android.hardware.Camera#open)、API Level 11(Android 3.0)未満ではカメラ処理の種類を明示的に指定(android.view.SurfaceHolder#setType)しておかないとアプリが落ちてしまいます。
1 | final SurfaceHolder holder = getHolder(); |
2 | ## 以下の処理を忘れないように、これを忘れると2.x系ではカメラデバイスを開くときにアプリが落ちます |
3 | holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); |
popup | print | ? |
SURFACE_TYPE_PUSH_BUFFERS を指定すると「バッファに入れられた画像を画面表示する(カメラプレビュー)」という処理になります。
OpenCV for Android
OpenCV for Androidについては別ページに。
Androidソースコードリーディング
まだ殴り書きのメモ段階です、コードリーディングを進めながら整理していきます
Androidのソースコードリーディングを進めています。
Welcome to Android
- 構成 platform
bionic glibc互換 Cライブラリ bootable ブート関連 build ビルドツール cts 互換性確認ツール dalvik 仮想マシン(DalvikVM) development 開発ツール(エミュレータとか) device デバイス関連ファイル external 外部で開発されたソフトなど frameworks Android フレームワーク hardware ハードウェア制御ライブラリ ndk Native Development Kit packages 標準インストールアプリ prebuilt ビルドツール(ビルド済) sdk Android SDK system デバイスドライバのソース
- 名前の由来
「GoogleのDan Bornsteinが自作の仮想マシンに付けたDalvikという名前は、祖先がむかし住んでいたアイスランドの漁村の名に由来している。」 とのこと。
- きっかけ
主にNDK(JNI)を用いた開発中、スタックトレースに見慣れない単語(Zygoteなど)が頻発するため中身を知りたいと思ったから。at com.android.internal.os.ZygoteInit.main at dalvik.system.NativeStart.main
とか、あとは画像処理アプリを作っているときはよくメモリが足りなくてつまづくことが多いです。ERROR/dalvikvm-heap(187): 2457600-byte external allocation too large for this process. ERROR/(187): VM won't let us allocate 2457600 bytes… ERROR/AndroidRuntime(187): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
などのエラー
Zygote
Androidアプリケーションはマルチタスクで実行することができますが、ここではその仕組みを調べます。
Androidを起動すると、まずZygote(ザイゴウト)というプロセスが立ち上がります(いわゆるinitプロセス)。このZygoteはすべてのAndroidアプリケーションの親プロセスになっています。アプリケーションを起動するには下図のようにZygoteから fork して子プロセスを作ります。
Zygoteは多くのライブラリを含んでいるため起動には時間がかかりますが、その後の子プロセス生成は非常に高速に行うことができます。また、子プロセスは親プロセスとメモリ領域を共有するためメモリ消費量も節約できます。
ここで、Zygoteプロセス起動からのフローを整理します。
- init.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
initでサービスを起動します。エントリーポイントは以下のようになっています。 - frameworks/base/cmds/app_process/app_main.cpp
Zygoteを起動する AppRuntime#start の中身は以下のようになっています。JNI経由でJava側の static void main(String[] args) を呼び出しています。
1 int main(int argc, const char* const argv[]) 2 { 3 // ProcessState.cpp で定義されているグローバル変数 4 mArgC = argc; 5 mArgV = argv; 6 7 mArgLen = 0; 8 for (int i=0; i<argc; i++) { 9 mArgLen += strlen(argv[i]) + 1; 10 } 11 mArgLen--; 12 // ランタイム本体, 実装は base/core/jni/AndroidRuntime.cpp 13 AppRuntime runtime; 14 const char* argv0 = argv[0]; 15 16 argc--; 17 argv++; 18 19 int i = runtime.addVmArguments(argc, argv); 20 21 // コマンドライン引数のパース 22 bool zygote = false; 23 bool startSystemServer = false; 24 bool application = false; 25 const char* parentDir = NULL; 26 const char* niceName = NULL; 27 const char* className = NULL; 28 while (i < argc) { 29 const char* arg = argv[i++]; 30 if (!parentDir) { 31 parentDir = arg; 32 } else if (strcmp(arg, "--zygote") == 0) { 33 zygote = true; 34 niceName = "zygote"; 35 } else if (strcmp(arg, "--start-system-server") == 0) { 36 startSystemServer = true; 37 } else if (strcmp(arg, "--application") == 0) { 38 application = true; 39 } else if (strncmp(arg, "--nice-name=", 12) == 0) { 40 niceName = arg + 12; 41 } else { 42 className = arg; 43 break; 44 } 45 } 46 47 if (niceName && *niceName) { 48 setArgv0(argv0, niceName); 49 set_process_name(niceName); 50 } 51 52 runtime.mParentDir = parentDir; 53 if (zygote) { 54 // --zygote --start-system-server の場合、ここでZygoteを起動 55 runtime.start("com.android.internal.os.ZygoteInit", 56 startSystemServer ? "start-system-server" : ""); 57 } else if (className) { 58 runtime.mClassName = className; 59 runtime.mArgC = argc - i; 60 runtime.mArgV = argv + i; 61 runtime.start("com.android.internal.os.RuntimeInit", 62 application ? "application" : "tool"); 63 } else { 64 fprintf(stderr, "Error: no class name or --zygote supplied.\n"); 65 app_usage(); 66 LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); 67 return 10; 68 } 69 } popup | print | ? - frameworks/base/core/jni/AndroidRuntime.cpp
続きは調べつつ書きます。
1 // Androidランタイムの開始 2 // VMを起動してJava側の main メソッドを呼び出す 3 void AndroidRuntime::start(const char* className, const char* options) 4 { 5 ALOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n", 6 className != NULL ? className : "(unknown)"); 7 8 blockSigpipe(); 9 10 /* 11 * 'startSystemServer == true' means runtime is obsolete and not run from 12 * init.rc anymore, so we print out the boot start event here. 13 */ 14 if (strcmp(options, "start-system-server") == 0) { 15 /* track our progress through the boot sequence */ 16 const int LOG_BOOT_PROGRESS_START = 3000; 17 LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, 18 ns2ms(systemTime(SYSTEM_TIME_MONOTONIC))); 19 } 20 21 const char* rootDir = getenv("ANDROID_ROOT"); 22 if (rootDir == NULL) { 23 rootDir = "/system"; 24 if (!hasDir("/system")) { 25 LOG_FATAL("No root directory specified, and /android does not exist."); 26 return; 27 } 28 setenv("ANDROID_ROOT", rootDir, 1); 29 } 30 31 //const char* kernelHack = getenv("LD_ASSUME_KERNEL"); 32 //ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack); 33 34 // VMを起動する 35 JNIEnv* env; 36 if (startVm(&mJavaVM, &env) != 0) { 37 return; 38 } 39 onVmCreated(env); 40 41 // 関数の登録 42 if (startReg(env) < 0) { 43 ALOGE("Unable to register all android natives\n"); 44 return; 45 } 46 47 // Java側の main メソッドを呼び出す準備 48 jclass stringClass; 49 jobjectArray strArray; 50 jstring classNameStr; 51 jstring optionsStr; 52 53 stringClass = env->FindClass("java/lang/String"); 54 assert(stringClass != NULL); 55 strArray = env->NewObjectArray(2, stringClass, NULL); 56 assert(strArray != NULL); 57 classNameStr = env->NewStringUTF(className); 58 assert(classNameStr != NULL); 59 env->SetObjectArrayElement(strArray, 0, classNameStr); 60 optionsStr = env->NewStringUTF(options); 61 env->SetObjectArrayElement(strArray, 1, optionsStr); 62 63 // このスレッドはVMのメインスレッドになる、VMが終了するまで戻らない 64 char* slashClassName = toSlashClassName(className); 65 jclass startClass = env->FindClass(slashClassName); 66 if (startClass == NULL) { 67 ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); 68 /* keep going */ 69 } else { 70 // 呼び出すメソッドのIDを取得 (main メソッド) 71 jmethodID startMeth = env->GetStaticMethodID(startClass, "main", 72 "([Ljava/lang/String;)V"); // String型の配列を表す 73 if (startMeth == NULL) { 74 ALOGE("JavaVM unable to find main() in '%s'\n", className); 75 /* keep going */ 76 } else { 77 // JNIでJavaの static void main(String[] args) を呼び出す 78 env->CallStaticVoidMethod(startClass, startMeth, strArray); 79 #if 0 80 if (env->ExceptionCheck()) 81 threadExitUncaughtException(env); 82 #endif 83 } 84 } 85 free(slashClassName); 86 87 ALOGD("Shutting down VM\n"); 88 if (mJavaVM->DetachCurrentThread() != JNI_OK) 89 ALOGW("Warning: unable to detach main thread\n"); 90 if (mJavaVM->DestroyJavaVM() != 0) 91 ALOGW("Warning: VM did not shut down cleanly\n"); 92 } popup | print | ?
ashmem (Anonymouse Shared memory Subsystem)
Androidにおける共有メモリ用デバイスで、Linuxカーネルに組み込まれています。ashmem からメモリを確保するには /dev/ashmem をオープンして mmap を使います。mmap を行うと ashmem デバイスを通じて最終的に tmpfs からデータを取得します。
ashmem API
ashmemの機能は libcutils.so に含まれています。
- system/core/include/cutils/ashmem.h
ashmemの提供するAPIは以下の5つ。内部では ioctl でデバイスドライバに情報を渡しているだけ。共有メモリ領域生成には以下のように ashmem_create_region で返ってくるファイルディスクリプタを mmap に渡します。1 // ASHMEM_SET_NAME, ASHMEM_SET_SIZE (/dev/ashmem リクエストコード) 2 int ashmem_create_region(const char *name, size_t size); 3 // ASHMEM_SET_PROT_MASK 4 int ashmem_set_prot_region(int fd, int prot); 5 // ASHMEM_PIN 6 int ashmem_pin_region(int fd, size_t offset, size_t len); 7 // ASHMEM_UPIN 8 int ashmem_unpin_region(int fd, size_t offset, size_t len); 9 // ASHMEM_GET_SIZE 10 int ashmem_get_size_region(int fd); popup | print | ? 他のプロセスから共有メモリを使うには binder (Android独自のデバイスドライバ)経由で使うようです。要調査。1 // int ashmem_create_region(const char *name, size_t size); 2 // 新しく共有メモリ領域を生成してファイルディスクリプタを返す 3 // name: 領域に付けるラベル名、size: 領域のサイズ 4 5 int fd = ashmem_create_region("my_shm_region", size); 6 if(fd < 0) { 7 //無効なファイルディスクリプタ 8 } 9 char* data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 10 if(data == MAP_FAILED) { 11 // マッピング失敗 12 } popup | print | ?
Android SDKではJavaから ashmem を利用するために、''android.os.MemoryFile'' クラスが提供されています。アプリケーションキャッシュなどに利用すると良いです。また、 ashmem はグラフィックメモリのバッファなどでも利用されているとのことです。
binder
プロセス間通信
atodekaku
bionic
bionic はAndroidのCライブラリです。 glibc は組み込み向けには大きすぎるため BSD libc をAndroid向けに改良したライブラリが用いられています。
dlmalloc
(ここではv2.8.6を対象としています)
bionic にはDoug Leaという方が作った通称 dlmalloc と呼ばれるメモリ確保機構が含まれています。dlmalloc は速度と空間効率を両立したアルゴリズムを採用しており、フラグメンテーションの問題を最小限にします。スマートフォンデバイスのような組み込み環境に適しているとのことです。
- platform/bionic/libc/upstream-dlmalloc
以下のようにメモリ確保関数を #define して使うことができます。
1 /* 2 mspace is an opaque type representing an independent 3 region of space that supports mspace_malloc, etc. 4 */ 5 typedef void* mspace; 6 7 /* 8 create_mspace creates and returns a new independent space with the 9 given initial capacity, or, if 0, the default granularity size. It 10 returns null if there is no system memory available to create the 11 space. If argument locked is non-zero, the space uses a separate 12 lock to control access. The capacity of the space will grow 13 dynamically as needed to service mspace_malloc requests. You can 14 control the sizes of incremental increases of this space by 15 compiling with a different DEFAULT_GRANULARITY or dynamically 16 setting with mallopt(M_GRANULARITY, value). 17 */ 18 DLMALLOC_EXPORT mspace create_mspace(size_t capacity, int locked); 19 20 /* 21 mspace_malloc behaves as malloc, but operates within 22 the given space. 23 */ 24 DLMALLOC_EXPORT void* mspace_malloc(mspace msp, size_t bytes); popup | print | ? まず create_mspace() で指定したサイズ(capacity, 0ならデフォルトの粒度)の領域をあらかじめ確保し、そこから必要なサイズの領域を mspace_malloc() で取得します。デフォルトで確保される領域については以下の通りです。1 /* 2 * MSPACES 3 If MSPACES is defined, then in addition to malloc, free, etc., 4 this file also defines mspace_malloc, mspace_free, etc. These 5 are versions of malloc routines that take an "mspace" argument 6 obtained using create_mspace, to control all internal bookkeeping. 7 If ONLY_MSPACES is defined, only these versions are compiled. 8 So if you would like to use this allocator for only some allocations, 9 and your system malloc for others, you can compile with 10 ONLY_MSPACES and then do something like... 11 */ 12 13 static mspace mymspace = create_mspace(0,0); // for example 14 #define mymalloc(bytes) mspace_malloc(mymspace, bytes) popup | print | ? フリーリストは''循環双方向リンクリスト''あるいは''トライ木(trie)''(内部では bin と呼ばれている)で管理されています。チャンク(リストのノード)のサイズは、フリーチャンクの最後の4バイトにも記録されています。これを活用して隣接するフリーチャンクを結合してメモリの断片化を避けています。各binはサイズ別にチャンクを管理しており、求めるサイズのチャンクをすばやく探せるようになっています。チャンクを解放するとき、可能であれば隣り合うフリーチャンクと結合させます(断片化防止)。解放するチャンクの直前または直後に位置するチャンクがフリーであれば、そのフリーチャンクはbinから外され、解放しようとしているチャンクと結合して適切なbinに配置されます。チャンクの構造は以下のようになっています。1 DEFAULT_GRANULARITY default: page size if MORECORE_CONTIGUOUS, 2 system_info.dwAllocationGranularity in WIN32, 3 otherwise 64K. 4 Also settable using mallopt(M_GRANULARITY, x) 5 The unit for allocating and deallocating memory from the system. On 6 most systems with contiguous MORECORE, there is no reason to 7 make this more than a page. However, systems with MMAP tend to 8 either require or encourage larger granularities. You can increase 9 this value to prevent system allocation functions to be called so 10 often, especially if they are slow. The value must be at least one 11 page and must be a power of two. Setting to 0 causes initialization 12 to either page size or win32 region size. (Note: In previous 13 versions of malloc, the equivalent of this option was called 14 "TOP_PAD") 15 16 // MORECORE_CONTIGUOUS では size_t が4bytesの場合 17 // (size_t)64U * (size_t)1024U = 4224bytes 18 #else /* MORECORE_CONTIGUOUS */ 19 #define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U) popup | print | ? 実は glibc malloc は dlmalloc から派生したもの、歴史はとても古いのです。シンプルな仕組みなのに効率が良く、現在でも広く利用されています。詳細は本家サイトにて、図付きでわかりやすいです。1 // 小さいチャンクは循環双方向リンクリストで管理 2 "Small" chunks are stored in circular doubly-linked lists, and look 3 like this: 4 5 chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 6 | Size of previous chunk | 7 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 8 `head:' | Size of chunk, in bytes |P| 9 mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | Forward pointer to next chunk in list | 11 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | Back pointer to previous chunk in list | 13 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | Unused space (may be 0 bytes long) . 15 . . 16 . | 17 nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 `foot:' | Size of chunk, in bytes | 19 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 // P は PINUE_BIT と呼ばれ、前のチャンクが割り当て済みかを示すビット(0:フリー, 1:割り当て済み) 21 22 // 大きいチャンク(256bytes以上)はトライ木で管理 23 Larger chunks are kept in a form of bitwise digital trees (aka 24 tries) keyed on chunksizes. Because malloc_tree_chunks are only for 25 free chunks greater than 256 bytes, their size doesn't impose any 26 constraints on user chunk sizes. Each node looks like: 27 28 chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 29 | Size of previous chunk | 30 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 31 `head:' | Size of chunk, in bytes |P| 32 mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 33 | Forward pointer to next chunk of same size | 34 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 35 | Back pointer to previous chunk of same size | 36 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 37 | Pointer to left child (child[0]) | 38 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 39 | Pointer to right child (child[1]) | 40 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 41 | Pointer to parent | 42 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 43 | bin index of this chunk | 44 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 45 | Unused space . 46 . | 47 nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 48 `foot:' | Size of chunk, in bytes | 49 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 50 51 // チャンクは malloc_chunk/malloc_tree_chunk 構造体として定義されている 52 // 循環双方向リンクリスト 53 struct malloc_chunk { 54 size_t prev_foot; /* Size of previous chunk (if free). */ 55 size_t head; /* Size and inuse bits. */ 56 struct malloc_chunk* fd; /* double links -- used only if free. */ 57 struct malloc_chunk* bk; 58 }; 59 // トライ木(trie) 60 struct malloc_tree_chunk { 61 /* The first four fields must be compatible with malloc_chunk */ 62 size_t prev_foot; 63 size_t head; 64 struct malloc_tree_chunk* fd; 65 struct malloc_tree_chunk* bk; 66 67 struct malloc_tree_chunk* child[2]; 68 struct malloc_tree_chunk* parent; 69 bindex_t index; 70 }; popup | print | ?
A Memory Allocator
DEXファイルフォーマット
Androidアプリは、それぞれが独自のLinuxプロセス内で実行され、それらのプロセスが個々にDalvik VMのインスタンスをホストしています。Dalvikは、デバイスが複数の仮想マシンを同時に効率よく実行できるように設計されています。効率が良い理由は、主にDalvikがDEX(Dalvik Executable)ベースのファイルを実行するからです。DEXはメモリの占有を最小限に抑えるよう最適化されたフォーマットです。
.dex — Dalvik Executable Format
データ型
DEXファイルフォーマットで扱えるデータ型は以下の11種類です。また、バイトオーダーはリトルエンディアンとなっています。
Name | Description |
byte | 8ビット符号付き整数 |
ubyte | 8ビット符号無し整数 |
short | 16ビット符号付き整数 |
ushort | 16ビット符号無し整数 |
int | 32ビット符号付き整数 |
uint | 32ビット符号無し整数 |
long | 64ビット符号付き整数 |
ulong | 64ビット符号無し整数 |
sleb128 | 符号付きLEB128 (後述) |
uleb128 | 符号無しLEB128 (後述) |
uleb128p1 | 符号無しLEB128 + 1 |
LEB128(Little-Endian Base 128)では可変長整数を扱います。これは DWARF3 フォーマットを参考にしたもので以下のような構造(2バイトの場合)になっています。
| Bitwise diagram of a two-byte LEB128 value | | First byte | Second byte | | 1 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 | 0 | bit13 | bit12 | bit11 | bit10 | bit9 | bit8 | bit7 |
各バイトの7ビットがデータで最上位ビットを使ってデータの終端を示しています。上の例だと2バイト目の最上位ビットが落ちているため、ここでこのデータは終了ということになります。値の大きさによってデータ長も変化するので、小さな整数値だとデータ長も短くなります。LEB128のような可変長整数の扱い方は特に珍しくはなく、バイナリネットワークプロトコルだとよく見かけます(thriftとか)。
ファイルレイアウト
内部のセクションは以下のようになっています。
header | ヘッダ |
string_ids | 文字列(UTF-16)を格納 |
type_ids | データ型を格納 |
proto_ids | メソッドのプロトタイプ情報(戻り値の型とか)を格納 |
field_ids | クラスのフィールド情報を格納 |
method_ids | クラスのメソッド情報を格納 |
class_defs | クラス情報(継承関係とか)を格納 |
data | データを格納、アノテーション情報も含む |
link_data | 静的リンクされたファイルのデータを格納 |
型のシグネチャはJNIを利用するときにもよく目にします。以下にシグネチャと型の対応を載せます。
V | void; only valid for return types |
Z | boolean |
B | byte |
S | short |
C | char |
I | int |
J | long |
F | float |
D | double |
Lfully/qualified/Name; | the class fully.qualified.Name |
[descriptor | array of descriptor, usable recursively for arrays-of-arrays, though it is invalid to have more than 255 dimensions. |
最後の配列型は例えば "[I" ならint型配列、"[D" ならdouble型配列を表します。
''Dalvik VMはJavaバイトコードを実行しません。''コンパイルされたJavaのクラスファイル(.class)をDEXファイル(.dex)に変換して生成されたバイトコードが実行されます。
- ''dx''
classファイルをdexファイルに変換できるコマンド。Android SDKに入っています。$ dx usage: dx --dex [--debug] [--verbose] [--positions=<style>] [--no-locals] [--no-optimize] [--statistics] [--[no-]optimize-list=<file>] [--no-strict] [--keep-classes] [--output=<file>] [--dump-to=<file>] [--dump-width=<n>] [--dump-method=<name>[*]] [--verbose-dump] [--no-files] [--core-library] [--num-threads=<n>] [--incremental] [--force-jumbo] [<file>.class | <file>.{zip,jar,apk} | <directory>] ... Convert a set of classfiles into a dex file, optionally embedded in a jar/zip. Output name must end with one of: .dex .jar .zip .apk. Positions options: none, important, lines. dx --annotool --annotation=<class> [--element=<element types>] [--print=<print types>] dx --dump [--debug] [--strict] [--bytes] [--optimize] [--basic-blocks | --rop-blocks | --ssa-blocks | --dot] [--ssa-step=<step>] [--width=<n>] [<file>.class | <file>.txt] ... Dump classfiles, or transformations thereof, in a human-oriented format. dx --find-usages <file.dex> <declaring type> <member> Find references and declarations to a field or method. declaring type: a class name in internal form, like Ljava/lang/Object; member: a field or method name, like hashCode dx -J<option> ... <arguments, in one of the above forms> Pass VM-specific options to the virtual machine that runs dx. dx --version Print the version of this tool (1.7). dx --help Print this message.
実際にAndroidプロジェクトディレクトリで以下のようにコマンドを実行してみます。--output オプションで出力ファイル名を指定し、その後に変換対象のclassファイルを含んだディレクトリを指定します。$ dx --dex --verbose --output=classes.dex bin/classes ignored resource bin/classes/./.DS_Store ignored resource bin/classes/./com/.DS_Store ignored resource bin/classes/./com/example/.DS_Store processing bin/classes/./com/example/hellojni/BuildConfig.class... processing bin/classes/./com/example/hellojni/MainActivity$1.class... processing bin/classes/./com/example/hellojni/MainActivity$2.class... processing bin/classes/./com/example/hellojni/MainActivity.class... processing bin/classes/./com/example/hellojni/R$attr.class... processing bin/classes/./com/example/hellojni/R$drawable.class... processing bin/classes/./com/example/hellojni/R$id.class... processing bin/classes/./com/example/hellojni/R$layout.class... processing bin/classes/./com/example/hellojni/R$menu.class... processing bin/classes/./com/example/hellojni/R$string.class... processing bin/classes/./com/example/hellojni/R$style.class... processing bin/classes/./com/example/hellojni/R.class...
カレントディレクトリに classes.dex ファイルが生成されていたら成功です。このdexファイルは後述の dexdump コマンドで中身を覗くことができます。
Dalvik VMバイトコード
Dalvik VMは''レジスタマシン''として実装されています(JVMはスタックマシン)。Dalvik VMバイトコードの特徴を整理します。
- オペコードは8ビットで、これに必要な分だけオペランドが追加される
- 命令は16ビット単位でアライメントされる(16-bit code units)
- 各命令毎に指定できるレジスタ番号の異なり、4/8/16ビットの範囲の番号で指定する
Dalvik VMで実行されるバイトコードについて、以下の公式リファレンスを見ながら dexdump コマンドを使って整理していきます。
Bytecode for the Dalvik VM
- ''dexdump''
Unix系OSでELFバイナリを読むときに使う objdump のようなもの。 前述の dx コマンドと同様にこれもAndroid SDKに入っています。$ dexdump dexdump: no file specified Copyright (C) 2007 The Android Open Source Project dexdump: [-c] [-d] [-f] [-h] [-i] [-l layout] [-m] [-t tempfile] dexfile... -c : verify checksum and exit -d : disassemble code sections -f : display summary information from file header -h : display file header details -i : ignore checksum failures -l : output layout, either 'plain' or 'xml' -m : dump register maps (and nothing else) -t : temp file name (defaults to /sdcard/dex-temp-*)
以下のような簡単なJavaコードをコンパイルしてclassファイルを作り、それを dx コマンドでdexファイルに変換。 dexdump コマンドを使ってその中身を覗いてみます。
1 | package hello; |
2 | |
3 | public class HelloWorld { |
4 | public static void main(String[] args) { |
5 | System.out.println("Hello, World!"); |
6 | } |
7 | public int add(int a, int b) { |
8 | return a + b; |
9 | } |
10 | public int sub(int a, int b) { |
11 | return a - b; |
12 | } |
13 | public int mul(int a, int b) { |
14 | return a * b; |
15 | } |
16 | public int div(int a, int b) { |
17 | return a / b; |
18 | } |
19 | } |
popup | print | ? |
コードをディスアセンブルするには -d オプションを付けて実行します。
1 | ## dexファイルに変換 |
2 | $ dx --dex --output=hello.dex . |
3 | ## dexファイルをディスアセンブル |
4 | $ dexdump -d hello.dex |
5 | Processing 'hello.dex'... |
6 | Opened 'hello.dex', DEX version '035' |
7 | Class #0 - |
8 | Class descriptor : 'Lhello/HelloWorld;' |
9 | Access flags : 0x0001 (PUBLIC) ## アクセスフラグ(スコープ) |
10 | Superclass : 'Ljava/lang/Object;' ## 継承情報 |
11 | Interfaces - |
12 | Static fields - |
13 | Instance fields - |
14 | Direct methods - ## コンストラクタ |
15 | #0 : (in Lhello/HelloWorld;) |
16 | name : '<init>' |
17 | type : '()V' |
18 | access : 0x10001 (PUBLIC CONSTRUCTOR) |
19 | code - |
20 | registers : 1 ## レジスタ操作(参照)回数 |
21 | ins : 1 ## 入力レジスタ |
22 | outs : 1 ## 出力レジスタ |
23 | insns size : 4 16-bit code units ## 命令は16bits単位、4x16=64bits (8bytes) |
24 | 000178: |[000178] hello.HelloWorld.<init>:()V |
25 | 000188: 7010 0700 0000 |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // method@0007 |
26 | 00018e: 0e00 |0003: return-void |
27 | catches : (none) |
28 | positions : |
29 | 0x0000 line=3 |
30 | locals : |
31 | 0x0000 - 0x0004 reg=0 this Lhello/HelloWorld; |
32 | |
33 | #1 : (in Lhello/HelloWorld;) ## main メソッド |
34 | name : 'main' |
35 | type : '([Ljava/lang/String;)V' ## 型情報、String型配列を引数に取る |
36 | access : 0x0009 (PUBLIC STATIC) |
37 | code - |
38 | registers : 3 |
39 | ins : 1 |
40 | outs : 2 |
41 | insns size : 8 16-bit code units |
42 | ## sget-object でスタティックメソッド参照を取得してレジスタ(v0)に格納する |
43 | ## const-string で文字列をレジスタ(v1)に格納する |
44 | ## invoke-virtual でメソッド呼び出し(v0, v1)を行う |
45 | 000190: |[000190] hello.HelloWorld.main:([Ljava/lang/String;)V |
46 | 0001a0: 6200 0000 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000 |
47 | 0001a4: 1a01 0100 |0002: const-string v1, "Hello, World!" // string@0001 |
48 | 0001a8: 6e20 0600 1000 |0004: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0006 |
49 | 0001ae: 0e00 |0007: return-void |
50 | catches : (none) |
51 | positions : |
52 | 0x0000 line=5 |
53 | 0x0007 line=6 |
54 | locals : |
55 | ## 以下インスタンスメソッド情報が続く |
56 | Virtual methods - |
57 | #0 : (in Lhello/HelloWorld;) ## add メソッド (return a + b) |
58 | name : 'add' |
59 | type : '(II)I' ## int型の引数2つを取り、int型の戻り値を返す |
60 | access : 0x0001 (PUBLIC) |
61 | code - |
62 | registers : 4 |
63 | ins : 3 |
64 | outs : 0 |
65 | insns size : 3 16-bit code units ## 3x16=48bytes (6bytes) |
66 | ## add-int で2つのレジスタ(v2,v3)に格納された32ビット整数値を加算してレジスタ(v0)に格納 |
67 | ## return でレジスタに格納された値を返す |
68 | 0001b0: |[0001b0] hello.HelloWorld.add:(II)I |
69 | 0001c0: 9000 0203 |0000: add-int v0, v2, v3 |
70 | 0001c4: 0f00 |0002: return v0 |
71 | catches : (none) |
72 | positions : |
73 | 0x0000 line=8 |
74 | locals : |
75 | 0x0000 - 0x0003 reg=1 this Lhello/HelloWorld; |
76 | |
77 | #1 : (in Lhello/HelloWorld;) ## div メソッド (return a /b) |
78 | name : 'div' |
79 | type : '(II)I' |
80 | access : 0x0001 (PUBLIC) |
81 | code - |
82 | registers : 4 |
83 | ins : 3 |
84 | outs : 0 |
85 | insns size : 3 16-bit code units |
86 | ## div-int で2つのレジスタ(v2,v3)に格納された32ビット整数値を除算してレジスタ(v0)に格納 |
87 | ## return でレジスタに格納された値を返す |
88 | 0001c8: |[0001c8] hello.HelloWorld.div:(II)I |
89 | 0001d8: 9300 0203 |0000: div-int v0, v2, v3 |
90 | 0001dc: 0f00 |0002: return v0 |
91 | catches : (none) |
92 | positions : |
93 | 0x0000 line=17 |
94 | locals : |
95 | 0x0000 - 0x0003 reg=1 this Lhello/HelloWorld; |
96 | |
97 | #2 : (in Lhello/HelloWorld;) ## mul メソッド (return a * b) |
98 | name : 'mul' |
99 | type : '(II)I' |
100 | access : 0x0001 (PUBLIC) |
101 | code - |
102 | registers : 4 |
103 | ins : 3 |
104 | outs : 0 |
105 | insns size : 3 16-bit code units |
106 | ## mul-int で2つのレジスタ(v2,v3)に格納された32ビット整数値を乗算してレジスタ(v0)に格納 |
107 | ## return でレジスタに格納された値を返す |
108 | 0001e0: |[0001e0] hello.HelloWorld.mul:(II)I |
109 | 0001f0: 9200 0203 |0000: mul-int v0, v2, v3 |
110 | 0001f4: 0f00 |0002: return v0 |
111 | catches : (none) |
112 | positions : |
113 | 0x0000 line=14 |
114 | locals : |
115 | 0x0000 - 0x0003 reg=1 this Lhello/HelloWorld; |
116 | |
117 | #3 : (in Lhello/HelloWorld;) ## sub メソッド (return a - b) |
118 | name : 'sub' |
119 | type : '(II)I' |
120 | access : 0x0001 (PUBLIC) |
121 | code - |
122 | registers : 4 |
123 | ins : 3 |
124 | outs : 0 |
125 | insns size : 3 16-bit code units |
126 | ## sub-int で2つのレジスタ(v2,v3)に格納された32ビット整数値を減算してレジスタ(v0)に格納 |
127 | ## return でレジスタに格納された値を返す |
128 | 0001f8: |[0001f8] hello.HelloWorld.sub:(II)I |
129 | 000208: 9100 0203 |0000: sub-int v0, v2, v3 |
130 | 00020c: 0f00 |0002: return v0 |
131 | catches : (none) |
132 | positions : |
133 | 0x0000 line=11 |
134 | locals : |
135 | 0x0000 - 0x0003 reg=1 this Lhello/HelloWorld; |
136 | |
137 | source_file_idx : 2 (HelloWorld.java) |
popup | print | ? |
Helloworldクラスでは単純な演算命令しか使っていないのでバイトコードを読むのも楽だと思います。
/dalvik/vm/mterp/out 以下にアーキテクチャ毎のバイトコードインタプリタのコードが入っています。
$ ls dalvik/vm/mterp InterpAsm-allstubs.S InterpAsm-armv7-a-neon.S InterpAsm-x86.S InterpC-armv5te.cpp InterpC-mips.cpp InterpAsm-armv5te-vfp.S InterpAsm-armv7-a.S InterpC-allstubs.cpp InterpC-armv7-a-neon.cpp InterpC-portable.cpp InterpAsm-armv5te.S InterpAsm-mips.S InterpC-armv5te-vfp.cpp InterpC-armv7-a.cpp InterpC-x86.cpp
ガベージコレクション
Dalvik VMのGCには''Mark & Sweep(ビットマップマーキング)''アルゴリズムを採用しています。通常のMark & Sweep方式はLinuxのCopy On Write(データの書き換えが発生した時にコピーを行う)と相性が悪いです。Mark & Sweepは生きているオブジェクトのヘッダにマークビットを立てますが、これを行うと本来は必要のないコピーが行われてしまうからです。なので、Dalvik VMではビットマップマーキングという手法を併用してこの問題に対処しています。この手法は各オブジェクトのマークビットだけを集めたテーブル(ビットマップテーブル)を用意し、オブジェクトとは別の場所で管理する手法です。
- dalvik/vm/alloc
$ ls Alloc.cpp Copying.cpp DlMalloc.h HeapBitmap.h HeapInternal.h MarkSweep.h Visit.cpp Alloc.h DdmHeap.cpp Heap.cpp HeapBitmapInlines.h HeapSource.cpp TEST/ Visit.h CardTable.cpp DdmHeap.h Heap.h HeapDebug.cpp HeapSource.h Verify.cpp VisitInlines.h CardTable.h DlMalloc.cpp HeapBitmap.cpp HeapDebug.h MarkSweep.cpp Verify.h WriteBarrier.h $ du -sh *.cpp 12K Alloc.cpp 16K CardTable.cpp 72K Copying.cpp 16K DdmHeap.cpp 4.0K DlMalloc.cpp 24K Heap.cpp 8.0K HeapBitmap.cpp 4.0K HeapDebug.cpp 40K HeapSource.cpp 28K MarkSweep.cpp 4.0K Verify.cpp 12K Visit.cpp
Dalvik VMのヒープ
Dalvik VMのオブジェクトは専用のヒープに保持されます。これには2種類あり、1つは前述の Zygote 用のヒープ、もう1つはAndroidアプリケーションで使用するヒープです。ヒープの中身は以下のようになっています。
- dalvik/vm/alloc/HeapSource.cpp
前述の dlmalloc の項で紹介した mspace をメンバとして持っています。オブジェクトはこの中にアロケーションされます。
1 struct Heap { 2 /* The mspace to allocate from. 3 */ 4 mspace msp; 5 6 /* The largest size that this heap is allowed to grow to. 7 */ 8 size_t maximumSize; 9 10 /* Number of bytes allocated from this mspace for objects, 11 * including any overhead. This value is NOT exact, and 12 * should only be used as an input for certain heuristics. 13 */ 14 size_t bytesAllocated; 15 16 /* Number of bytes allocated from this mspace at which a 17 * concurrent garbage collection will be started. 18 */ 19 size_t concurrentStartBytes; 20 21 /* Number of objects currently allocated from this mspace. 22 */ 23 size_t objectsAllocated; 24 25 /* 26 * The lowest address of this heap, inclusive. 27 */ 28 char *base; 29 30 /* 31 * The highest address of this heap, exclusive. 32 */ 33 char *limit; 34 35 /* 36 * If the heap has an mspace, the current high water mark in 37 * allocations requested via dvmHeapSourceMorecore. 38 */ 39 char *brk; 40 }; popup | print | ? - dalvik/vm/alloc/HeapBitmap.h
マーキングで用いるビットマップは以下のように定義されています。実態は unsigned long の配列のようです。処理の詳細については調べながら追記していきます。1 struct HeapBitmap { 2 /* The bitmap data, which points to an mmap()ed area of zeroed 3 * anonymous memory. 4 */ 5 unsigned long *bits; 6 7 /* The size of the used memory pointed to by bits, in bytes. This 8 * value changes when the bitmap is shrunk. 9 */ 10 size_t bitsLen; 11 12 /* The real size of the memory pointed to by bits. This is the 13 * number of bytes we requested from the allocator and does not 14 * change. 15 */ 16 size_t allocLen; 17 18 /* The base address, which corresponds to the first bit in 19 * the bitmap. 20 */ 21 uintptr_t base; 22 23 /* The highest pointer value ever returned by an allocation 24 * from this heap. I.e., the highest address that may correspond 25 * to a set bit. If there are no bits set, (max < base). 26 */ 27 uintptr_t max; 28 }; popup | print | ?