Dalvik學習筆記--啟動過程
阿新 • • 發佈:2019-01-24
學習老羅的部落格,順便記點筆記,強化記憶
程式碼用的4.3,與老羅不一樣的地方會標註出來
從AndroidRuntime.start開始
GetStaticMethodID函式用於獲取方法id,一個引數為目標類,第二個為方法名,第三個是引數描述(是不是很像smali程式碼)void AndroidRuntime::start(const char* className, const char* options) { ...... /* start the virtual machine */ JNIEnv* env; if (startVm(&mJavaVM, &env) != 0) { return; } onVmCreated(env);<span style="white-space:pre"> </span>//此函式為空函式,原文註釋:If AndroidRuntime had anything to do here, we'd have done it in 'start'. /* * Register android functions. */ if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); return; } /* * We want to call main() with a String array with arguments in it. * At present we have two arguments, the class name and an option string. * Create an array to hold them. */ jclass stringClass; jobjectArray strArray; jstring classNameStr; jstring optionsStr; stringClass = env->FindClass("java/lang/String");<span style="white-space:pre"> assert(stringClass != NULL); strArray = env->NewObjectArray(2, stringClass, NULL); assert(strArray != NULL); classNameStr = env->NewStringUTF(className); assert(classNameStr != NULL); env->SetObjectArrayElement(strArray, 0, classNameStr);<span style="white-space:pre"> </span>//構造2個元素的String陣列 optionsStr = env->NewStringUTF(options);<span style="white-space:pre"> </span>//第一個元素為類名 env->SetObjectArrayElement(strArray, 1, optionsStr);<span style="white-space:pre"> </span>//第二個為設定 /* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */ char* slashClassName = toSlashClassName(className);<span style="white-space:pre"> </span>//將字串中的 . 替換為 / jclass startClass = env->FindClass(slashClassName); if (startClass == NULL) { ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V");<span style="white-space:pre"> </span>//獲取main方法的id if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in '%s'\n", className); /* keep going */ } else { env->CallStaticVoidMethod(startClass, startMeth, strArray);<span style="white-space:pre"> </span>//呼叫main方法 ...... }
CallStaticVoidMethod函式第一個引數為目標類,第二個為方法id,第三個是可變引數
這裡有個小小的變化就是toSlashClassName將原來的幾行程式碼封裝了一下。
例項建立startVm函式
該函式很長,但是大致可以分為三部分
先宣告字串用於存放配置資訊,宣告標誌變數int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) { int result = -1; JavaVMInitArgs initArgs; JavaVMOption opt; char propBuf[PROPERTY_VALUE_MAX]; char stackTraceFileBuf[PROPERTY_VALUE_MAX]; char dexoptFlagsBuf[PROPERTY_VALUE_MAX]; char enableAssertBuf[sizeof("-ea:")-1 + PROPERTY_VALUE_MAX]; char jniOptsBuf[sizeof("-Xjniopts:")-1 + PROPERTY_VALUE_MAX]; char heapstartsizeOptsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX]; char heapsizeOptsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; char heapgrowthlimitOptsBuf[sizeof("-XX:HeapGrowthLimit=")-1 + PROPERTY_VALUE_MAX]; char heapminfreeOptsBuf[sizeof("-XX:HeapMinFree=")-1 + PROPERTY_VALUE_MAX]; char heapmaxfreeOptsBuf[sizeof("-XX:HeapMaxFree=")-1 + PROPERTY_VALUE_MAX]; char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX]; char extraOptsBuf[PROPERTY_VALUE_MAX]; char* stackTraceFile = NULL; bool checkJni = false; bool checkDexSum = false; bool logStdio = false; enum { kEMDefault, kEMIntPortable, kEMIntFast, kEMJitCompiler, } executionMode = kEMDefault;
第二部分大多都是上述這種形式,先用property_get函式,在之前宣告的字串中存放配置資訊,比較後設置對應的標誌變數property_get("dalvik.vm.checkjni", propBuf, ""); if (strcmp(propBuf, "true") == 0) { checkJni = true; } else if (strcmp(propBuf, "false") != 0) { /* property is neither true nor false; fall back on kernel parameter */ property_get("ro.kernel.android.checkjni", propBuf, ""); if (propBuf[0] == '1') { checkJni = true; } } ...... /* Force interpreter-only mode for selected methods */ char jitMethodBuf[sizeof("-Xjitmethod:") + PROPERTY_VALUE_MAX]; property_get("dalvik.vm.jit.method", propBuf, ""); if (strlen(propBuf) > 0) { strcpy(jitMethodBuf, "-Xjitmethod:"); strcat(jitMethodBuf, propBuf); opt.optionString = jitMethodBuf; mOptions.add(opt); }
比如在propBuf中存放dalvik.vm.checkjni的資訊,如果propBuf字串為“true”,就將checkJni設定為true
if (executionMode == kEMIntPortable) {
opt.optionString = "-Xint:portable";
mOptions.add(opt);
} else if (executionMode == kEMIntFast) {
opt.optionString = "-Xint:fast";
mOptions.add(opt);
} else if (executionMode == kEMJitCompiler) {
opt.optionString = "-Xint:jit";
mOptions.add(opt);
}
if (checkDexSum) {
/* perform additional DEX checksum tests */
opt.optionString = "-Xcheckdexsum";
mOptions.add(opt);
}
......
再根據這些標誌變數進行設定。
/*
* Initialize the VM.
*
* The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
* If this call succeeds, the VM is ready, and we can start issuing
* JNI calls.
*/
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
ALOGE("JNI_CreateJavaVM failed\n");
goto bail;
}
當然最後呼叫JNI_CreateJavaVM函式。
/*
* Create a new VM instance.
*
* The current thread becomes the main VM thread. We return immediately,
* which effectively means the caller is executing in a native method.
*/
jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
const JavaVMInitArgs* args = (JavaVMInitArgs*) vm_args;
if (args->version < JNI_VERSION_1_2) {
return JNI_EVERSION;
}
// TODO: don't allow creation of multiple VMs -- one per customer for now
/* zero globals; not strictly necessary the first time a VM is started */
memset(&gDvm, 0, sizeof(gDvm));
/*
* Set up structures for JNIEnv and VM.
*/
JavaVMExt* pVM = (JavaVMExt*) calloc(1, sizeof(JavaVMExt));
pVM->funcTable = &gInvokeInterface;
pVM->envList = NULL;
dvmInitMutex(&pVM->envListLock);
UniquePtr<const char*[]> argv(new const char*[args->nOptions]);
memset(argv.get(), 0, sizeof(char*) * (args->nOptions));
/*
* Convert JNI args to argv.
*
* We have to pull out vfprintf/exit/abort, because they use the
* "extraInfo" field to pass function pointer "hooks" in. We also
* look for the -Xcheck:jni stuff here.
*/
int argc = 0;
for (int i = 0; i < args->nOptions; i++) {<span style="white-space:pre"> </span>//收集虛擬機器相關資訊
const char* optStr = args->options[i].optionString;
if (optStr == NULL) {
dvmFprintf(stderr, "ERROR: CreateJavaVM failed: argument %d was NULL\n", i);
return JNI_ERR;
} else if (strcmp(optStr, "vfprintf") == 0) {
gDvm.vfprintfHook = (int (*)(FILE *, const char*, va_list))args->options[i].extraInfo;
} else if (strcmp(optStr, "exit") == 0) {
gDvm.exitHook = (void (*)(int)) args->options[i].extraInfo;
} else if (strcmp(optStr, "abort") == 0) {
gDvm.abortHook = (void (*)(void))args->options[i].extraInfo;
} else if (strcmp(optStr, "sensitiveThread") == 0) {
gDvm.isSensitiveThreadHook = (bool (*)(void))args->options[i].extraInfo;
} else if (strcmp(optStr, "-Xcheck:jni") == 0) {
gDvmJni.useCheckJni = true;
} else if (strncmp(optStr, "-Xjniopts:", 10) == 0) {
char* jniOpts = strdup(optStr + 10);
size_t jniOptCount = 1;
for (char* p = jniOpts; *p != 0; ++p) {
if (*p == ',') {
++jniOptCount;
*p = 0;
}
}
char* jniOpt = jniOpts;
for (size_t i = 0; i < jniOptCount; ++i) {
if (strcmp(jniOpt, "warnonly") == 0) {
gDvmJni.warnOnly = true;
} else if (strcmp(jniOpt, "forcecopy") == 0) {
gDvmJni.forceCopy = true;
} else if (strcmp(jniOpt, "logThirdPartyJni") == 0) {
gDvmJni.logThirdPartyJni = true;
} else {
dvmFprintf(stderr, "ERROR: CreateJavaVM failed: unknown -Xjniopts option '%s'\n",
jniOpt);
return JNI_ERR;
}
jniOpt += strlen(jniOpt) + 1;
}
free(jniOpts);
} else {
/* regular option */
argv[argc++] = optStr;
}
}
if (gDvmJni.useCheckJni) {
dvmUseCheckedJniVm(pVM);
}
if (gDvmJni.jniVm != NULL) {
dvmFprintf(stderr, "ERROR: Dalvik only supports one VM per process\n");
return JNI_ERR;
}
gDvmJni.jniVm = (JavaVM*) pVM;
/*
* Create a JNIEnv for the main thread. We need to have something set up
* here because some of the class initialization we do when starting
* up the VM will call into native code.
*/
JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
/* Initialize VM. */
gDvm.initializing = true;
std::string status =
dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);<span style="white-space:pre"> </span>//初始化虛擬機器,關鍵函式,其他程式碼都是為他的引數做準備
gDvm.initializing = false;
if (!status.empty()) {
free(pEnv);
free(pVM);
ALOGW("CreateJavaVM failed: %s", status.c_str());
return JNI_ERR;
}
/*
* Success! Return stuff to caller.
*/
dvmChangeStatus(NULL, THREAD_NATIVE);
*p_env = (JNIEnv*) pEnv;
*p_vm = (JavaVM*) pVM;
ALOGV("CreateJavaVM succeeded");
return JNI_OK;
}
程式碼註釋很全,簡單說一下。先給一個虛擬機器例項分配空間,初始化需要切換當前執行緒狀態,需要儲存設定和建立一個執行環境,gDvm就是用來收集虛擬機器資訊的全域性變數(給例項分配的空間也儲存在當中),用於在不同執行緒狀態間傳遞虛擬機器例項,argv儲存從vm_args傳遞過來的引數,通過這個幾個變數就可以切換執行緒狀態建立虛擬機器例項。建立完成後再通過dvmChangeStatus切換回去(Return stuff to caller)。然後將例項(pVM)和環境(pEnv)傳遞給呼叫者。
上述提到的DvmGlobals結構體定義檔案dalvik/vm/Globals.h中,JNIInvokeInterface結構體定義在檔案dalvik/libnativehelper/include/nativehelper/jni.h中,JavaVMExt和JNIEnvExt結構體定義在檔案dalvik/vm/JniInternal.h中。
接下來是dvmCreateJNIEnv函式
JNIEnv* dvmCreateJNIEnv(Thread* self) {
JavaVMExt* vm = (JavaVMExt*) gDvmJni.jniVm;
//if (self != NULL)
// ALOGI("Ent CreateJNIEnv: threadid=%d %p", self->threadId, self);
assert(vm != NULL);
JNIEnvExt* newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt));<span style="white-space:pre"> </span>//建立一個JNIEnvExt物件
newEnv->funcTable = &gNativeInterface;<span style="white-space:pre"> </span>//設定本地藉口表
if (self != NULL) {<span style="white-space:pre"> </span>//self表示所要關聯的執行緒
dvmSetJniEnvThreadId((JNIEnv*) newEnv, self);<span style="white-space:pre"> </span>//設定關聯的函式
assert(newEnv->envThreadId != 0);
} else {
/* make it obvious if we fail to initialize these later */
newEnv->envThreadId = 0x77777775;<span style="white-space:pre"> </span>//表示還未與執行緒關聯
newEnv->self = (Thread*) 0x77777779;
}
if (gDvmJni.useCheckJni) {
dvmUseCheckedJniEnv(newEnv);
}
ScopedPthreadMutexLock lock(&vm->envListLock);
/* insert at head of list */
newEnv->next = vm->envList;<span style="white-space:pre"> </span>//newEnv的宿主虛擬機器是vm,也就是之前建立的例項
assert(newEnv->prev == NULL);
if (vm->envList == NULL) {<span style="white-space:pre"> </span>//將newEnv插入到vm->envList連結串列中
// rare, but possible
vm->envList = newEnv;
} else {
vm->envList->prev = newEnv;
}
vm->envList = newEnv;
//if (self != NULL)
// ALOGI("Xit CreateJNIEnv: threadid=%d %p", self->threadId, self);
return (JNIEnv*) newEnv;
}
在一個Dalvik虛擬機器裡面,可以執行多個執行緒。所有關聯有JNI環境的執行緒都有一個對應的JNIEnvExt物件,這些JNIEnvExt物件相互連線在一起儲存在用來描述其宿主Dalvik虛擬機器的一個JavaVMExt物件的成員變數envList中。因此,前面建立的JNIEnvExt物件需要連線到其宿主Dalvik虛擬機器的JavaVMExt連結串列中去。再看dvmStartup函式
std::string dvmStartup(int argc, const char* const argv[],
bool ignoreUnrecognized, JNIEnv* pEnv)
{
ScopedShutdown scopedShutdown;
assert(gDvm.initializing);
ALOGV("VM init args (%d):", argc);
for (int i = 0; i < argc; i++) {
ALOGV(" %d: '%s'", i, argv[i]);
}
setCommandLineDefaults();<span style="white-space:pre"> </span>//設定預設項
/*
* Process the option flags (if any).
*/
int cc = processOptions(argc, argv, ignoreUnrecognized);<span style="white-space:pre"> </span>//處理啟動選項
if (cc != 0) {
if (cc < 0) {
dvmFprintf(stderr, "\n");
usage("dalvikvm");
}
return "syntax error";
}
與老羅程式碼相比沒有了dvmPropertiesStartup來分配空間 /*
* Initialize components.
*/
dvmQuasiAtomicsStartup();
if (!dvmAllocTrackerStartup()) {<span style="white-space:pre"> </span>//物件分配記錄子模組
return "dvmAllocTrackerStartup failed";
}
if (!dvmGcStartup()) {<span style="white-space:pre"> </span>//GC子模組
return "dvmGcStartup failed";
}
if (!dvmThreadStartup()) {<span style="white-space:pre"> </span>//執行緒列表
return "dvmThreadStartup failed";
}
if (!dvmInlineNativeStartup()) {<span style="white-space:pre"> </span>//內建Native函式表
return "dvmInlineNativeStartup";
}
if (!dvmRegisterMapStartup()) {<span style="white-space:pre"> </span>//暫存器對映集
return "dvmRegisterMapStartup failed";
}
if (!dvmInstanceofStartup()) {<span style="white-space:pre"> </span>//例項操作符子模組
return "dvmInstanceofStartup failed";
}
if (!dvmClassStartup()) {<span style="white-space:pre"> </span>//啟動類載入器
return "dvmClassStartup failed";
}
/*
* At this point, the system is guaranteed to be sufficiently
* initialized that we can look up classes and class members. This
* call populates the gDvm instance with all the class and member
* references that the VM wants to use directly.
*/
if (!dvmFindRequiredClassesAndMembers()) {<span style="white-space:pre"> </span>//重要類和函式
return "dvmFindRequiredClassesAndMembers failed";
}
if (!dvmStringInternStartup()) {<span style="white-space:pre"> </span>//字串池
return "dvmStringInternStartup failed";
}
if (!dvmNativeStartup()) {<span style="white-space:pre"> </span>//so庫載入表
return "dvmNativeStartup failed";
}
if (!dvmInternalNativeStartup()) {<span style="white-space:pre"> </span>//內部Native函式表
return "dvmInternalNativeStartup failed";
}
if (!dvmJniStartup()) {<span style="white-space:pre"> </span>//全域性引用表
return "dvmJniStartup failed";
}
if (!dvmProfilingStartup()) {<span style="white-space:pre"> </span>//效能分析子模組
return "dvmProfilingStartup failed";
}
/*
* Create a table of methods for which we will substitute an "inline"
* version for performance.
*/
if (!dvmCreateInlineSubsTable()) {<span style="white-space:pre"> </span>//行內函數表
return "dvmCreateInlineSubsTable failed";
}
/*
* Miscellaneous class library validation.
*/
if (!dvmValidateBoxClasses()) {<span style="white-space:pre"> </span>//驗證虛擬機器中相應的裝箱類
return "dvmValidateBoxClasses failed";
}
/*
* Do the last bits of Thread struct initialization we need to allow
* JNI calls to work.
*/
if (!dvmPrepMainForJni(pEnv)) {<span style="white-space:pre"> </span>//準備主執行緒JNI環境
return "dvmPrepMainForJni failed";
}
/*
* Explicitly initialize java.lang.Class. This doesn't happen
* automatically because it's allocated specially (it's an instance
* of itself). Must happen before registration of system natives,
* which make some calls that throw assertions if the classes they
* operate on aren't initialized.
*/
if (!dvmInitClass(gDvm.classJavaLangClass)) {//確保目標類初始化
return "couldn't initialized java.lang.Class";
}
/*
* Register the system native methods, which are registered through JNI.
*/
if (!registerSystemNatives(pEnv)) {<span style="white-space:pre"> </span>//為JAVA核心類註冊JNI方法
return "couldn't register system natives";
}
/*
* Do some "late" initialization for the memory allocator. This may
* allocate storage and initialize classes.
*/
if (!dvmCreateStockExceptions()) {<span style="white-space:pre"> </span>//預建立與記憶體相關的異樣物件
return "dvmCreateStockExceptions failed";
}
/*
* At this point, the VM is in a pretty good state. Finish prep on
* the main thread (specifically, create a java.lang.Thread object to go
* along with our Thread struct). Note we will probably be executing
* some interpreted class initializer code in here.
*/
if (!dvmPrepMainThread()) {<span style="white-space:pre"> </span>//為主執行緒建立ThreadGroup物件
return "dvmPrepMainThread failed";
}
/*
* Make sure we haven't accumulated any tracked references. The main
* thread should be starting with a clean slate.
*/
if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)<span style="white-space:pre"> </span>//確保主執行緒當前不應用JAVA物件,保證一個乾淨的入口
{
ALOGW("Warning: tracked references remain post-initialization");
dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, "MAIN");
}
/* general debugging setup */
if (!dvmDebuggerStartup()) {<span style="white-space:pre"> </span>//初始化除錯環境
return "dvmDebuggerStartup failed";
}
if (!dvmGcStartupClasses()) {<span style="white-space:pre"> </span>// GC class
return "dvmGcStartupClasses failed";
}
初始化各項子模組 /*
* Init for either zygote mode or non-zygote mode. The key difference
* is that we don't start any additional threads in Zygote mode.
*/
if (gDvm.zygote) {
if (!initZygote()) {
return "initZygote failed";
}
} else {
if (!dvmInitAfterZygote()) {
return "dvmInitAfterZygote failed";
}
}
判斷是否在zygote中啟動虛擬機器,注意是initZygote不是dvmInitZygote
/*
* Do zygote-mode-only initialization.
*/
static bool initZygote()
{
/* zygote goes into its own process group */
setpgid(0,0);
// See storage config details at http://source.android.com/tech/storage/
// Create private mount namespace shared by all children
if (unshare(CLONE_NEWNS) == -1) {
SLOGE("Failed to unshare(): %s", strerror(errno));
return -1;
}
// Mark rootfs as being a slave so that changes from default
// namespace only flow into our children.
if (mount("rootfs", "/", NULL, (MS_SLAVE | MS_REC), NULL) == -1) {
SLOGE("Failed to mount() rootfs as MS_SLAVE: %s", strerror(errno));
return -1;
}
// Create a staging tmpfs that is shared by our children; they will
// bind mount storage into their respective private namespaces, which
// are isolated from each other.
const char* target_base = getenv("EMULATED_STORAGE_TARGET");
if (target_base != NULL) {
if (mount("tmpfs", target_base, "tmpfs", MS_NOSUID | MS_NODEV,
"uid=0,gid=1028,mode=0050") == -1) {
SLOGE("Failed to mount tmpfs to %s: %s", target_base, strerror(errno));
return -1;
}
}
// Mark /system as NOSUID | NODEV
const char* android_root = getenv("ANDROID_ROOT");
if (android_root == NULL) {
SLOGE("environment variable ANDROID_ROOT does not exist?!?!");
return -1;
}
std::string mountDev(getMountsDevDir(android_root));
if (mountDev.empty()) {
SLOGE("Unable to find mount point for %s", android_root);
return -1;
}
if (mount(mountDev.c_str(), android_root, "none",
MS_REMOUNT | MS_NOSUID | MS_NODEV | MS_RDONLY | MS_BIND, NULL) == -1) {
SLOGE("Remount of %s failed: %s", android_root, strerror(errno));
return -1;
}
#ifdef HAVE_ANDROID_OS
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
if (errno == EINVAL) {
SLOGW("PR_SET_NO_NEW_PRIVS failed. "
"Is your kernel compiled correctly?: %s", strerror(errno));
// Don't return -1 here, since it's expected that not all
// kernels will support this option.
} else {
SLOGW("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
return -1;
}
}
#endif
return true;
}
註釋比較全,主要是父子程序共享資源的問題。
到這裡例項的建立和初始化工作就算完成了,dvmStartup函式也算結束了,要注意的是dvmStartup函式反回的是一個字串。
JNI_CreateJavaVM函式中是這樣呼叫的
std::string status =
dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);
if (!status.empty()) {
free(pEnv);
free(pVM);
ALOGW("CreateJavaVM failed: %s", status.c_str());
return JNI_ERR;
}
返回到AndroidRuntime::start,之後執行的函式是startReg,註冊Android核心類的JNI方法
int AndroidRuntime::startReg(JNIEnv* env)
{
/*
* This hook causes all future threads created in this process to be
* attached to the JavaVM. (This needs to go away in favor of JNI
* Attach calls.)
*/
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
ALOGV("--- registering native functions ---\n");
/*
* Every "register" function calls one or more things that return
* a local reference (e.g. FindClass). Because we haven't really
* started the VM yet, they're all getting stored in the base frame
* and never released. Use Push/Pop to manage the storage.
*/
env->PushLocalFrame(200);
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
//createJavaThread("fubar", quickTest, (void*) "hello");
return 0;
}
總結:建立例項,處理配置資訊 ——> 收集配置資訊,建立環境 ——> 切換執行緒狀態,初始化虛擬機器例項 ——> 註冊核心方法 ——> 啟動main方法
最後附上老羅的圖