我們知道,在Android系統中,Dalvik虛擬機是運行Linux內核之上的。如果我們把Dalvik虛擬機看作是一臺機器,那麼它也有進程和線程的概念。事實上,我們的確是可以在Java代碼中創建進程和線程,也就是Dalvik虛擬機進程和線程。那麼,這些Dalvik虛擬機所創建的進程和線程與其宿主Linux內核的進程和線程有什麼關係呢?本文將通過Dalvik虛擬機進程和線程的創建過程來回答這個問題。
老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!
《Android系統源代碼情景分析》一書正在進擊的程序員網(http://0xcc0xcd.com)中連載,點擊進入!
此外,從前面Dalvik虛擬機的運行過程分析一文可以知道,Dalvik虛擬機除了可以執行Java代碼之外,還可以執行Native代碼,也就是C/C++函數。這些C/C++函數在執行的過程中,又可以通過本地操作系統提供的系統調用來創建本地操作系統進程或者線程,也就是Linux進程和線程。如果在Native代碼中創建出來的進程又加載有Dalvik虛擬機,那麼它實際上又可以看作是一個Dalvik虛擬機進程。另一方面,如果在Native代碼中創建出來的線程能夠執行Java代碼,那麼它實際上又可以看作是一個Dalvik虛擬機線程。
這樣事情看起來似乎很複雜,因爲既有Dalvik虛擬機進程和線程,又有Native操作系統進程和線程,而且它們又可以同時執行Java代碼和Native代碼。爲了理清它們之間的關係,我們將按照以下四個情景來組織本文:
1. Dalvik虛擬機進程的創建過程;
2. Dalvik虛擬機線程的創建過程;
3. 只執行C/C++代碼的Native線程的創建過程;
4. 能同時執行C/C++代碼和Java代碼的Native線程的創建過程。
對於上述進程和線程,Android系統都分別提供有接口來創建:
1. Dalvik虛擬機進程可以通過android.os.Process類的靜態成員函數start來創建;
2. Dalvik虛擬機線程可以通過java.lang.Thread類的成員函數start來創建;
3. 只執行C/C++代碼的Native線程可以通過C++類Thread的成員函數run來創建;
4. 能同時執行C/C++代碼和Java代碼的Native線程也可以通過C++類Thread的成員函數run來創建;
接下來,我們就按照上述四個情況來分析Dalvik虛擬機進程和線程和Native操作系統進程和線程的關係。
一. Dalvik虛擬機進程的創建過程
Dalvik虛擬機進程實際上就是通常我們所說的Android應用程序進程。從前面Android應用程序進程啓動過程的源代碼分析一文可以知道,Android應用程序進程是由ActivityManagerService服務通過android.os.Process類的靜態成員函數start來請求Zygote進程創建的,而Zyogte進程最終又是通過dalvik.system.Zygote類的靜態成員函數forkAndSpecialize來創建該Android應用程序進程的。因此,接下來我們就從dalvik.system.Zygote類的靜態成員函數forkAndSpecialize開始分析Dalvik虛擬機進程的創建過程,如圖1所示:
圖1 Dalvik虛擬機進程的創建過程
這個過程可以分爲3個步驟,接下來我們就詳細分析每一個步驟。
Step 1. Zygote.forkAndSpecialize
public class Zygote {
......
native public static int forkAndSpecialize(int uid, int gid, int[] gids,
int debugFlags, int[][] rlimits);
......
}
這個函數定義在文件libcore/dalvik/src/main/java/dalvik/system/Zygote.java中。
Zygote類的靜態成員函數forkAndSpecialize是一個JNI方法,它是由C++層的函數Dalvik_dalvik_system_Zygote_forkAndSpecialize來實現的,如下所示:
/* native public static int forkAndSpecialize(int uid, int gid,
* int[] gids, int debugFlags);
*/
static void Dalvik_dalvik_system_Zygote_forkAndSpecialize(const u4* args,
JValue* pResult)
{
pid_t pid;
pid = forkAndSpecializeCommon(args, false);
RETURN_INT(pid);
}
這個函數定義在文件dalvik/vm/native/dalvik_system_Zygote.c中。
注意,參數args指向的是一個u4數組,它裏面包含了所有從Java層傳遞進來的參數,這是由Dalvik虛擬機封裝的。另外一個參數pResult用來保存JNI方法調用結果,這是通過宏RETURN_INT來實現的。
函數Dalvik_dalvik_system_Zygote_forkAndSpecialize的實現很簡單,它通過調用另外一個函數forkAndSpecializeCommon來創建一個Dalvik虛擬機進程。
Step 2. forkAndSpecializeCommon
/*
* Utility routine to fork zygote and specialize the child process.
*/
static pid_t forkAndSpecializeCommon(const u4* args, bool isSystemServer)
{
pid_t pid;
uid_t uid = (uid_t) args[0];
gid_t gid = (gid_t) args[1];
ArrayObject* gids = (ArrayObject *)args[2];
u4 debugFlags = args[3];
ArrayObject *rlimits = (ArrayObject *)args[4];
int64_t permittedCapabilities, effectiveCapabilities;
if (isSystemServer) {
/*
* Don't use GET_ARG_LONG here for now. gcc is generating code
* that uses register d8 as a temporary, and that's coming out
* scrambled in the child process. b/3138621
*/
//permittedCapabilities = GET_ARG_LONG(args, 5);
//effectiveCapabilities = GET_ARG_LONG(args, 7);
permittedCapabilities = args[5] | (int64_t) args[6] << 32;
effectiveCapabilities = args[7] | (int64_t) args[8] << 32;
} else {
permittedCapabilities = effectiveCapabilities = 0;
}
if (!gDvm.zygote) {
......
return -1;
}
......
pid = fork();
if (pid == 0) {
int err;
......
err = setgroupsIntarray(gids);
......
err = setrlimitsFromArray(rlimits);
......
err = setgid(gid);
......
err = setuid(uid);
......
err = setCapabilities(permittedCapabilities, effectiveCapabilities);
......
enableDebugFeatures(debugFlags);
......
gDvm.zygote = false;
if (!dvmInitAfterZygote()) {
......
dvmAbort();
}
} else if (pid > 0) {
/* the parent process */
}
return pid;
}
這個函數定義在文件dalvik/vm/native/dalvik_system_Zygote.c中。
函數forkAndSpecializeCommon除了可以用來創建普通的Android應用程序進程之外,還用來創建System進程。Android系統中的System進程和普通的Android應用程序進程一樣,也是由Zygote進程負責創建的,具體可以參考前面Android系統進程Zygote啓動過程的源代碼分析一文。
當函數forkAndSpecializeCommon是調用來創建System進程的時候,參數isSystemServer的值就等於true,這時候在參數列表args就會包含兩個額外的參數permittedCapabilities和effectiveCapabilities。其中,permittedCapabilities表示System進程允許的特權,而effectiveCapabilities表示System進程當前的有效特權,這兩個參數的關係就類似於進程的uid和euid的關係一樣。
進程的特權是什麼概念呢?從Linux 2.2開始,Root用戶的權限被劃分成了一系列的子權限,每一個子權限都通過一個bit來表示,這些bit的定義可以參考CAPABILITIES(7)。每一個進程都關聯有一個u32整數,用來描述它所具有的Root用戶子權限,我們可以通過系統調用capset來進行設置。
參考前面Android系統進程Zygote啓動過程的源代碼分析一篇文章可以知道,Zygote進程在創建System進程的時候,給它指定的permittedCapabilities和effectiveCapabilities均爲130104352,因此,System進程在運行的時候,就可以獲得一些Root用戶特權。
當參數isSystemServer的值等於false的時候,變量permittedCapabilities和effectiveCapabilities的值被設置爲0,也就是說,由Zygote進程創建出來的Android應用程序進程是不具有任何的Root用戶特權的。
除了上述的permittedCapabilities和effectiveCapabilities之外,參數列表args還包含了其它的參數:
--uid:要創建的進程的用戶ID。一般來說,每一個應用程序進程都有一個唯一的用戶ID,用來將應用程序進程封閉在一個沙箱裏面運行。
--gid:要創建的進程的用戶組ID。一般來說,每一個應用程序進程都有一個唯一的用戶組ID,也是用來將應用程序進程封閉在一個沙箱裏面運行。
--gids:要創建的進程的額外用戶組ID。這個額外的用戶組ID實際上對應的就是應用程序所申請的資源訪權限。
--debugFlags:要創建的進程在運行時的調試選項。例如,我們可以將debugFlags的DEBUG_ENABLE_CHECKJNI位設置爲1,從而打開該進程中的Dalvik虛擬機的JNI檢查選項。
--rlimits:要創建的進程所受到的資源限制。例如,該進程所能打開文件的個數。
瞭解上述參數的含義之後,函數forkAndSpecializeCommon的實現就容易理解了,它主要就是調用系統調用fork來創建一個進程。我們知道系統調用fork執行完成之後,會有兩次返回,其中一次是返回當前進程中,另外一次是返回到新創建的進程中。當系統調用fork返回到新創建的進程的時候,它的返回值pid就會等於0,這時候就可以調用相應的函數來設置新創建的進程的uid、gid、gids、debugFlags和rlimits,從而將限制了新創建的進程的權限。
對於函數forkAndSpecializeCommon的實現,還有兩個地方是需要注意的。
第一個地方是隻有Zygote進程纔有權限創建System進程和Android應用程序進程。從前面Dalvik虛擬機的啓動過程分析一文可以知道,Zygote進程在啓動運行在它裏面的Dalvik虛擬機的時候,gDvm.zygote的值會等於true,這時候函數forkAndSpecializeCommon纔可以使用系統調用fork來創建一個新的進程。
第二個地方是在新創建出來的進程中,gDvm.zygote的值會被設置爲false,以表示它不是Zygote進程。我們知道,當一個進程使用系統調用fork來創建一個新進程的時候,前者就稱爲父進程,後者就稱爲子進程。這時候父進程和子進程共享的地址空間是一樣的,但是隻要某個地址被父進程或者子進程進行寫入操作的時候,這塊被寫入的地址空間纔會在父進程和子進程之間獨立開來,這種機制就稱爲COW(copy on write)。因此,當函數forkAndSpecializeCommon將新創建的進程中的gDvm.zygote的值設置爲false的時候, Zygote進程的gDvm.zygote的值仍然保持爲true。
從上述的第二點的描述還可以進一步看出,由Zygote進程創建出來的System進程和Android應用程序進程實際上是共享了很多東西,而且只要這些東西都是隻讀的時候,它們就會一直被共享着。從前面Dalvik虛擬機的啓動過程分析一文可以知道,Zygote進程在啓動的過程中,加載了很多東西,例如,Java和Android核心類庫(dex文件)及其JNI方法(so文件)。這些dex文件和so文件的只讀段,例如代碼段,都會一直在Zygote進程、System進程和Android應用程序進程中進行共享。這樣,我們在Zygote進程中進行的大量預加載行爲就獲得了價值,一方面是可以加快System進程和Android應用程序進程的啓動過程中,另外一方面也使得系統的整體內存消耗減少。
此外,運行在Zygote進程中的Dalvik虛擬機開始的時候也會與System進程和Android應用程序進程一起共享,但是由於上述的COW機制,在必要的時候,System進程和Android應用程序進程還是會複製一份出來的,從而使得它們都具有獨立的Dalvik虛擬機實例。
最後,函數forkAndSpecializeCommon還會調用函數dvmInitAfterZygote來進一步對在新創建的進程中運行的Dalvik虛擬機進行初始化,接下來我們就繼續分析它的實現。
Step 3. dvmInitAfterZygote
/*
* Do non-zygote-mode initialization. This is done during VM init for
* standard startup, or after a "zygote fork" when creating a new process.
*/
bool dvmInitAfterZygote(void)
{
......
/*
* Post-zygote heap initialization, including starting
* the HeapWorker thread.
*/
if (!dvmGcStartupAfterZygote())
return false;
......
/* start signal catcher thread that dumps stacks on SIGQUIT */
if (!gDvm.reduceSignals && !gDvm.noQuitHandler) {
if (!dvmSignalCatcherStartup())
return false;
}
/* start stdout/stderr copier, if requested */
if (gDvm.logStdio) {
if (!dvmStdioConverterStartup())
return false;
}
......
/*
* Start JDWP thread. If the command-line debugger flags specified
* "suspend=y", this will pause the VM. We probably want this to
* come last.
*/
if (!dvmInitJDWP()) {
LOGD("JDWP init failed; continuing anyway\n");
}
......
#ifdef WITH_JIT
if (gDvm.executionMode == kExecutionModeJit) {
if (!dvmCompilerStartup())
return false;
}
#endif
return true;
}
這個函數定義在文件dalvik/vm/Init.c中。
函數dvmInitAfterZygote執行的Dalvik虛擬機初始化操作包括:
1. 調用函數dvmGcStartupAfterZygote來進行一次GC。
2. 調用函數dvmSignalCatcherStartup來啓動一個Linux信號收集線程,主要是用來捕捉SIGQUIT信號,以便可以在進程退出前將各個線程的堆棧DUMP出來。
3. 調用函數dvmStdioConverterStartup來啓動一個標準輸出重定向線程,該線程負責將當前進程的標準輸出(stdout和stderr)重定向到日誌輸出系統中去,前提是設置了Dalvik虛擬機的啓動選項-Xlog-stdio。
4. 調用函數dvmInitJDWP來啓動一個JDWP線程,以便我們可以用DDMS工具來調試進程中的Dalvik虛擬機。
5. 調用函數dvmCompilerStartup來啓動JIT,前提是當前使用的Dalvik虛擬機在編譯時支持JIT,並且該Dalvik虛擬機在啓動時指定了-Xint:jit選項。
這一步執先完成之後,一個Dalvik虛擬機進程就創建完成了,從中我們就可以得出結論:一個Dalvik虛擬機進程實際上就是一個Linux進程。
二. Dalvik虛擬機線程的創建過程
在Java代碼中,我們可以通過java.lang.Thread類的成員函數start來創建一個Dalvik虛擬機線程,因此,接下來我們就從這個函數開始分析Dalvik虛擬機線程的創建過程,如圖2所示:
圖2 Dalvik虛擬機線程的創建過程
這個過程可以分爲10個步驟,接下來我們就詳細分析每一個步驟。
Step 1. Thread.start
public class Thread implements Runnable {
......
public synchronized void start() {
if (hasBeenStarted) {
throw new IllegalThreadStateException("Thread already started."); // TODO Externalize?
}
hasBeenStarted = true;
VMThread.create(this, stackSize);
}
......
}
這個函數定義在文件libcore/luni/src/main/java/java/lang/Thread.java中。
Thread類的成員函數start首先檢查成員變量hasBeenStarted的值是否等於true。如果等於true的話,那麼就說明當前正在處理的Thread對象所描述的Java線程已經啓動起來了。一個Java線程是不能重複啓動的,否則的話,Thread類的成員函數start就會拋出一個類型爲IllegalThreadStateException的異常。
通過了上面的檢查之後,Thread類的成員函數start接下來就繼續調用VMThread類的靜態成員函數create來創建一個線程。
Step 2. VMThread.create
class VMThread
{
......
native static void create(Thread t, long stacksize);
......
}
這個函數定義在文件luni/src/main/java/java/lang/VMThread.java中。
VMThread類的靜態成員函數create是一個JNI方法,它是C++層的函數Dalvik_java_lang_VMThread_create來實現的,如下所示:
/*
* static void create(Thread t, long stacksize)
*
* This is eventually called as a result of Thread.start().
*
* Throws an exception on failure.
*/
static void Dalvik_java_lang_VMThread_create(const u4* args, JValue* pResult)
{
Object* threadObj = (Object*) args[0];
s8 stackSize = GET_ARG_LONG(args, 1);
/* copying collector will pin threadObj for us since it was an argument */
dvmCreateInterpThread(threadObj, (int) stackSize);
RETURN_VOID();
}
這個函數定義在文件dalvik/vm/native/java_lang_VMThread.c中。
函數Dalvik_java_lang_VMThread_create的實現很簡單,它將Java層傳遞過來的參數獲取出來之後,就調用另外一個函數dvmCreateInterpThread來執行創建線程的工作。
Step 3. dvmCreateInterpThread
bool dvmCreateInterpThread(Object* threadObj, int reqStackSize)
{
pthread_attr_t threadAttr;
pthread_t threadHandle;
......
Thread* newThread = NULL;
......
int stackSize;
......
if (reqStackSize == 0)
stackSize = gDvm.stackSize;
else if (reqStackSize < kMinStackSize)
stackSize = kMinStackSize;
else if (reqStackSize > kMaxStackSize)
stackSize = kMaxStackSize;
else
stackSize = reqStackSize;
pthread_attr_init(&threadAttr);
pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);
......
newThread = allocThread(stackSize);
......
newThread->threadObj = threadObj;
......
int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart,
newThread);
......
while (newThread->status != THREAD_STARTING)
pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock);
......
newThread->next = gDvm.threadList->next;
if (newThread->next != NULL)
newThread->next->prev = newThread;
newThread->prev = gDvm.threadList;
gDvm.threadList->next = newThread;
......
newThread->status = THREAD_VMWAIT;
pthread_cond_broadcast(&gDvm.threadStartCond);
......
return true;
}
這個函數定義在文件dalvik/vm/Thread.c中。
參數reqStackSize表示要創建的Dalvik虛擬機線程的Java棧大小。Dalvik虛擬機線程實際上具有兩個棧,一個是Java棧,另一個是Native棧。Native棧是在調用Native代碼時使用的,它是由操作系統來管理的,而Java棧是由Dalvik虛擬機來管理的。從前面Dalvik虛擬機的運行過程分析一文可以知道,Dalvik虛擬機解釋器在執行Java代碼,每當遇到函數調用指令,就會在當前線程的Java棧上創建一個幀,用來保存當前函數的執行狀態,以便要調用的函數執行完成後可以返回到當前函數來。
當參數reqStackSize的值等於0的時候,那麼就會使用保存在gDvm.stackSize中的值作爲接下來要創建的線程的Java棧大小。我們可以通過Dalvik虛擬機的啓動選項-Xss來指定gDvm.stackSize的值。如果沒有指定,那麼gDvm.stackSize的值就設置爲kDefaultStackSize (12*1024) 個字節。
另外,如果參數reqStackSize的值不等於0,那麼它必須大於等於kMinStackSize(512+768),並且小於等於kMaxStackSize(256*1024+768),否則的話,它就會被修正。
參數threadObj描述的是Java層的一個Thread對象,它在Dalvik虛擬機中對應有一個Native層的Thread對象。這個Native層的Thread對象是通函數allocThread來分配的,並且與它對應的Java層的Thread對象會保存在它的成員變量threadObj中。
函數dvmCreateInterpThread是通過函數pthread_create來創建一個線程。函數pthread_create在創建一個線程的時候,需要知道該線程的屬性。這些屬性是通過一個pthread_attr_t結構體來描述的,而這個pthread_attr_t結構體可以調用函數pthread_attr_init來初始化,以及調用函數pthread_attr_setdetachstate來設置它的分離狀態。
函數pthread_create實際上是由pthread庫提供的一個函數,它最終是通過系統調用clone來請求內核創建一個線程的。由此就可以看出,Dalvik虛擬機線程實際上就是本地操作系統線程。
新創建的Dalvik虛擬機線程啓動完成之後,就會將自己的狀態設置爲THREAD_STARTING,而當前線程會通過一個while循環來等待新創建的Dalvik虛擬機線程的狀態被設置爲THREAD_STARTING之後再繼續往前執行,主要就是:
1. 將用來描述新創建的Dalvik虛擬機線程的Native層的Thread對象保存在gDvm.threadList所描述的一個線程列表中,這是因爲當前所有Dalvik虛擬機線程都保存在這個列表中。
2. 將新創建的Dalvik虛擬機線程的狀態設置爲THREAD_VMWAIT,使得新創建的Dalvik虛擬機線程繼續往前執行,這是因爲新創建的Dalvik虛擬機線程將自己的狀態設置爲THREAD_STARTING喚醒創建它的線程之後,又會等待創建它的線程通知它繼續往前執行。
注意,前面在調用函數pthread_create來創建新的Dalvik虛擬機線程時,指定該Dalvik虛擬機線程的入口點函數爲interpThreadStart,因此,接下來我們繼續分析函數interpThreadStart的實現,以便可以瞭解新創建的Dalvik虛擬機線程的啓動過程。
Step 4. interpThreadStart
static void* interpThreadStart(void* arg)
{
Thread* self = (Thread*) arg;
......
prepareThread(self);
......
self->status = THREAD_STARTING;
pthread_cond_broadcast(&gDvm.threadStartCond);
......
while (self->status != THREAD_VMWAIT)
pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock);
......
self->jniEnv = dvmCreateJNIEnv(self);
......
dvmChangeStatus(self, THREAD_RUNNING);
......
if (gDvm.debuggerConnected)
dvmDbgPostThreadStart(self);
......
int priority = dvmGetFieldInt(self->threadObj,
gDvm.offJavaLangThread_priority);
dvmChangeThreadPriority(self, priority);
......
Method* run = self->threadObj->clazz->vtable[gDvm.voffJavaLangThread_run];
JValue unused;
......
dvmCallMethod(self, run, self->threadObj, &unused);
......
dvmDetachCurrentThread();
return NULL;
}
這個函數定義在文件dalvik/vm/Thread.c中。
參數arg指向的是一個Native層的Thread對象,這個Thread對象最後會被保存在變量self,用來描述新創建的Dalvik虛擬機線程,也就是當前執行的線程。
函數interpThreadStart一方面是對新創建的Dalvik虛擬機線程進行初始化,另一方面是執行新創建的Dalvik虛擬機線程的主體函數,這些工作包括:
1. 調用函數prepareThread來初始化新創建的Dalvik虛擬機線程。
2. 將新創建的Dalvik虛擬機線程的狀態設置爲THREAD_STARTING,以便其父線程,也就是創建它的線程可以繼續往前執行。
3. 通過一個while循環來等待父線程通知自己繼續往前執行,也就是等待父線程將自己的狀態設置爲THREAD_VMWAIT。
4. 調用函數dvmCreateJNIEnv來爲新創建的Dalvik虛擬機線程創建一個JNI環境。
5. 調用函數dvmChangeStatus將新創建的Dalvik虛擬機線程的狀態設置爲THREAD_RUNNING,表示它正式進入運行狀態。
6. 如果此時gDvm.debuggerConnected的值等於true,那麼就說明有調試器連接到當前Dalvik虛擬機來了,這時候就調用函數dvmDbgPostThreadStart來通知調試器新創建了一個線程。
7. 調用函數dvmChangeThreadPriority來設置新創建的Dalvik虛擬機線程的優先級,這個優先級值保存在用來描述新創建的Dalvik虛擬機線程的一個Java層Thread對象的成員變量priority中。
8. 找到Java層的java.lang.Thread類的成員函數run,並且通過函數dvmCallMethod來交給Dalvik虛擬機解釋器執行,這個java.lang.Thread類的成員函數run即爲Dalvik虛擬機線程的Java代碼入口點函數。
9. 從函數dvmCallMethod返回來之後,新創建的Dalvik虛擬機線程就完成自己的使命了,這時候就可以調用函數dvmDetachCurrentThread來執行清理工作。
接下來,我們主要分析第1、4、8和第9個操作,也就是函數prepareThread、dvmCreateJNIEnv、dvmCallMethod和dvmDetachCurrentThread的實現,以便可以瞭解一個新創建的Dalvik虛擬機線程的初始化、運行和退出過程。
Step 5. prepareThread
static bool prepareThread(Thread* thread)
{
......
/*
* Initialize our reference tracking tables.
*
* Most threads won't use jniMonitorRefTable, so we clear out the
* structure but don't call the init function (which allocs storage).
*/
#ifdef USE_INDIRECT_REF
if (!dvmInitIndirectRefTable(&thread->jniLocalRefTable,
kJniLocalRefMin, kJniLocalRefMax, kIndirectKindLocal))
return false;
#else
/*
* The JNI local ref table *must* be fixed-size because we keep pointers
* into the table in our stack frames.
*/
if (!dvmInitReferenceTable(&thread->jniLocalRefTable,
kJniLocalRefMax, kJniLocalRefMax))
return false;
#endif
if (!dvmInitReferenceTable(&thread->internalLocalRefTable,
kInternalRefDefault, kInternalRefMax))
return false;
memset(&thread->jniMonitorRefTable, 0, sizeof(thread->jniMonitorRefTable));
......
return true;
}
這個函數定義在文件dalvik/vm/Thread.c中。
函數prepareThread最主要的就是對參數thread所描述的一個Dalvik虛擬機線程的三個引用表進行初始化。
第一個是JNI本地引用表。一個Dalvik虛擬機線程在執行JNI方法的時候,可能會需要訪問Java層的對象。這些Java層對象在被JNI方法訪問之前,需要往當前Dalvik虛擬機線程的JNI方法本地引用表添加一個引用,以便它們不會被GC回收。JNI方法本地引用表有兩種實現方式,一種是添加到它裏面的是間接引用,另一種是直接引用。兩者的區別在於,在進行GC的時候,間接引用的Java對象可以移動,而直接引用的Java對象不可以移動。在編譯Dalvik虛擬機的時候,可以通過宏USE_INDIRECT_REF來決定使用直接引用表,還是間接引用表。
第二個是Dalvik虛擬機內部引用表。有時候,我們需要在Dalvik虛擬機內部爲線程創建一些對象,這些對象需要添加到一個Dalvik虛擬機內部引用表中去,以便在線程退出時,可以對它們進行清理。
第三個是Monitor引用表。一個Dalvik虛擬機線程在執行JNI方法的時候,除了可能需要訪問Java層的對象之外,還可能需要進行一些同步操作,也就是進行MonitorEnter和MonitorExit操作。注意,MonitorEnter和MonitorExit是Java代碼中的同步指令,用來實現synchronized代碼塊或者函數的。Dalvik虛擬機需要跟蹤在JNI方法中所執行的MonitorEnter操作,也就是將被Monitor的對象添加到Monitor引用表中去,以便在JNI方法退出時,可以隱式地進行MonitorExit操作,避免出現不對稱的MonitorEnter和MonitorExit操作。
注意,Monitor引用表實際沒有被執行初始化, 函數prepareThread只是對它進行清0,這是因爲我們一般很少在JNI方法中執行同步操作的,因此,就最好在使用的時候再進行初始化。
這一步執行完成之後,回到前面的Step 4中,即函數interpThreadStart中,接下來它就會調用另外一個函數dvmCreateJNIEnv來爲新創建的Dalvik虛擬機線程創建一個JNI環境。
Step 6. dvmCreateJNIEnv
函數定義在文件dalvik/vm/Jni.c中,它的實現可以參考前面Dalvik虛擬機的啓動過程分析一文,主要就是用來爲新創建的Dalvik虛擬機線程創建一個JNI環境,也就是創建一個JNIEnvExt對象,並且爲該JNIEnvExt對象創建一個Java代碼訪問函數表。有了這個Java代碼訪問函數表之後,我們纔可以在JNI方法中訪問Java對象,以及執行Java代碼。
這一步執行完成之後,回到前面的Step 4中,即函數interpThreadStart中,接下來它就會調用另外一個函數dvmCallMethod來通知Dalvik虛擬機解釋器執行java.lang.Thread類的成員函數run。
Step 7. dvmCallMethod
void dvmCallMethod(Thread* self, const Method* method, Object* obj,
JValue* pResult, ...)
{
va_list args;
va_start(args, pResult);
dvmCallMethodV(self, method, obj, false, pResult, args);
va_end(args);
}
這個函數定義在文件dalvik/vm/interp/Stack.c中。
函數dvmCallMethod的實現很簡單,它通過調用另外一個函數dvmCallMethodV來通知Dalvik虛擬機解釋器執行參數method所描述的一個Java函數。在我們這個場景中,實際上就是執行java.lang.Thread類的成員函數run。
Step 8. dvmCallMethodV
這個函數定義在文件dalvik/vm/interp/Stack.c中。在前面Dalvik虛擬機的運行過程分析一文中,我們已經分析過函數dvmCallMethodV的實現了,它主要就是調用另外一個函數dvmInterpret來啓動Dalvik虛擬機解釋器,並且解釋執行指定的Java代碼。
如前所述,在我們這個場景中,接下來要執行的Java代碼便是java.lang.Thread類的成員函數run。接下來,我們就繼續分析java.lang.Thread類的成員函數run的實現,以便可以瞭解Dalvik虛擬機線程的Java代碼入口點。
Step 9. Thread.run
public class Thread implements Runnable {
......
Runnable target;
......
public void run() {
if (target != null) {
target.run();
}
}
......
}
這個函數定義在文件libcore/luni/src/main/java/java/lang/Thread.java中。
Thread類的成員函數run首先檢查成員變量target的值。如果不等於null的話,那麼就會調用它所指向的一個Runnable對象的成員函數run來作爲新創建的Dalvik虛擬機線程的執行主體,否則的話,就什麼也不做。
一般來說,我們都是通過從Thread繼承下來一個子類來創建一個Dalvik虛擬機線程的。在繼承下來的Thread子類中,重寫父類Thread的成員函數run,這時候這一步所執行的函數就是子類的成員函數run了。
這一步執行完成之後,回到前面的Step 4中,即函數interpThreadStart中,這時候新創建的Dalvik虛擬機線程就執行完成主體函數了,因此,接下來它就即將要退出。不過在退出之前,函數interpThreadStart會調用另外一個函數dvmDetachCurrentThread來執行清理工作。
接下來,我們就繼續分析函數dvmDetachCurrentThread的實現。
Step 10. dvmDetachCurrentThread
void dvmDetachCurrentThread(void)
{
Thread* self = dvmThreadSelf();
......
/*
* Release any held monitors. Since there are no interpreted stack
* frames, the only thing left are the monitors held by JNI MonitorEnter
* calls.
*/
dvmReleaseJniMonitors(self);
......
/*
* Do some thread-exit uncaught exception processing if necessary.
*/
if (dvmCheckException(self))
threadExitUncaughtException(self, group);
......
vmThread = dvmGetFieldObject(self->threadObj,
gDvm.offJavaLangThread_vmThread);
......
/*
* Tell the debugger & DDM. This may cause the current thread or all
* threads to suspend.
*
* The JDWP spec is somewhat vague about when this happens, other than
* that it's issued by the dying thread, which may still appear in
* an "all threads" listing.
*/
if (gDvm.debuggerConnected)
dvmDbgPostThreadDeath(self);
......
dvmObjectNotifyAll(self, vmThread);
......
/*
* Lose the JNI context.
*/
dvmDestroyJNIEnv(self->jniEnv);
self->jniEnv = NULL;
self->status = THREAD_ZOMBIE;
/*
* Remove ourselves from the internal thread list.
*/
unlinkThread(self);
......
freeThread(self);
}
這個函數定義在文件dalvik/vm/Thread.c中。
函數dvmDetachCurrentThread首先是調用函數dvmThreadSelf來獲得用來描述當前即將要退出的Dalvik虛擬機線程的Native層的Thread對象self。有了這個Native層的Thread對象self之後,就可以開始執行相應的清理工作了。這些清理工作主要包括:
1. 調用函數dvmReleaseJniMonitors來釋放那些在JNI方法中持有的Monitor,也就是MonitorExit那些被MonitorEnter了的對象。
2. 調用函數dvmCheckException來檢查線程是否有未處理異常。如果有的話,那麼就調用函數threadExitUncaughtException將它們交給thread-exit-uncaught-exception handler處理。
3. 檢查當前Dalvik虛擬機是否被調試器連接了,即檢查gDvm.debuggerConnected的值是否等於true。如果被調試器連接了的話,那麼就調用函數dvmDbgPostThreadDeath通知調試器當前線程要退出了。
4. 調用函數dvmObjectNotifyAll來向那些調用了Thread.join等待當前線程結束的線程發送通知。
5. 調用函數dvmDestroyJNIEnv來銷燬用來描述當前JNI上下文環境的一個JNIEnvExt對象。
6. 將當前線程的狀態設置爲殭屍狀態(THREAD_ZOMBIE),並且調用函數unlinkThread將當前線程從Dalvik虛擬機的線程列表中移除。
7. 調用函數freeThread來釋放當前線程的Java棧和各個引用表(即前面Step 5所描述的三個引用表)所佔用的內存,以及釋放Thread對象self所佔用的內存。
這一步執行完成之後,回到前面的Step 4中,即函數interpThreadStart中,這時候新創建的Dalvik虛擬機線程就結束了,整個Dalvik虛擬機線程的創建、運行和結束過程也分析完成了,從中我們就可以得出結論:一個Dalvik虛擬機線程實際上就是一個Linux線程。
三. 只執行C/C++代碼的Native線程的創建過程
我們這裏所說的Native線程就是指本地操作系統線程,它們是Dalvik虛擬機在執行C/C++代碼的過程中創建的,很顯然它們就是Linux線程。例如,在C/C++代碼中,我們可以通過C++類Thread的成員函數run來創建一個Native線程。接下來我們就從C++類Thread的成員函數run開始分析Dalvik虛擬機中的Native線程的創建過程,如圖3所示:
圖3 只執行C/C++代碼的Native線程的創建過程
這個過程可以分爲3個步驟,接下來我們就詳細分析每一個步驟。
Step 1. Thread.run
status_t Thread::run(const char* name, int32_t priority, size_t stack)
{
Mutex::Autolock _l(mLock);
......
bool res;
if (mCanCallJava) {
res = createThreadEtc(_threadLoop,
this, name, priority, stack, &mThread);
} else {
res = androidCreateRawThreadEtc(_threadLoop,
this, name, priority, stack, &mThread);
}
......
return NO_ERROR;
}
這個函數定義在文件frameworks/base/libs/utils/Threads.cpp中。
我們在創建一個Thread對象的時候,可以指定它所描述的Native線程是否可以調用Java代碼,它的構造函數原型如下所示:
class Thread : virtual public RefBase
{
public:
......
Thread(bool canCallJava = true);
......
};
這個函數定義在文件frameworks/base/include/utils/threads.h中。
回到Thread類的成員函數run中,如果當前正在處理的Thread對象所描述的Native線程可以調用Java代碼,那麼它的成員變量mCanCallJava的值就等於false,這時候就會調用另外一個函數androidCreateRawThreadEtc來創建這個Native線程。
Step 2. androidCreateRawThreadEtc
int androidCreateRawThreadEtc(android_thread_func_t entryFunction,
void *userData,
const char* threadName,
int32_t threadPriority,
size_t threadStackSize,
android_thread_id_t *threadId)
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
......
if (threadStackSize) {
pthread_attr_setstacksize(&attr, threadStackSize);
}
errno = 0;
pthread_t thread;
int result = pthread_create(&thread, &attr,
(android_pthread_entry)entryFunction, userData);
......
if (threadId != NULL) {
*threadId = (android_thread_id_t)thread; // XXX: this is not portable
}
return 1;
}
這個函數定義在文件frameworks/base/libs/utils/Threads.cpp中。
從函數androidCreateRawThreadEtc的定義就可以看出,在Android系統中,Native線程和Dalvik虛擬機線程一樣,都是通過pthread庫提供的函數pthread_create來創建的,其中,參數entryFunction指向的就是新創建的Native線程的入口點。
參數entryFunction是從前面的Step 1傳進來的,它指向的是Thread類的靜態成員函數_threadLoop,接下來我們就繼續分析它的實現,以便可以瞭解一個Native線程的運行過程。
Step 3. Thread._threadLoop
int Thread::_threadLoop(void* user)
{
Thread* const self = static_cast<Thread*>(user);
sp<Thread> strong(self->mHoldSelf);
wp<Thread> weak(strong);
......
bool first = true;
do {
bool result;
if (first) {
first = false;
self->mStatus = self->readyToRun();
result = (self->mStatus == NO_ERROR);
if (result && !self->mExitPending) {
......
result = self->threadLoop();
}
} else {
result = self->threadLoop();
}
if (result == false || self->mExitPending) {
......
break;
}
// Release our strong reference, to let a chance to the thread
// to die a peaceful death.
strong.clear();
// And immediately, re-acquire a strong reference for the next loop
strong = weak.promote();
} while(strong != 0);
return 0;
}
這個函數定義在文件frameworks/base/libs/utils/Threads.cpp中。
參數user指向的是一個Thread對象。這個Thread對象最終保存變量selft,用來描述前面所創建的Native線程。
Thread對象self的成員變量mHoldSelf是一個類型爲sp<Thread>的智能指針,它引用的就是Thread對象self本身。因此,Thread類的靜態成員函數_threadLoop一開始就首先獲得Thread對象self的一個強引用strong和弱引用weak,目的是爲了避免該Thread對象在線程運行的過程中被銷燬。關於Android系統中的智能指針,可以參考前面Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析一文。
Thread類的靜態成員函數_threadLoop的主體是一個while循環,這個while循環不斷地調用Thread對象self的成員函數threadLoop,來作爲前面所創建的Native線程的執行主體,直到滿足以下三個條件之一:
1. Thread對象self的成員函數threadLoop的返回值等於false。
2. Thread對象self的成員函數threadLoop在執行的過程中,調用了另外一個成員函數requestExit請求前面所創建的Native線程退出,這時候Thread對象self的成員變量mExitPending的值就會等於true。
3. Thread對象self被銷燬了,即強指針strong釋放了對Thread對象self的強引用之後,弱指針weak不能成功地提升成強指針。
注意,上述while循環第一次執行的時候,在調用Thread對象self的成員函數threadLoop之前,會首先調用另外一個成員函數readyToRun,以便前面所創建的Native線程在正式運行前有機會執行一些自定義的初始化工作。
我們一般都是使用Thread子類來創建Native線程的,這時候通過重寫父類Thread的成員函數readyToRun和threadLoop,就可以使得新創建的Native線程主動地執行自定義的任務。
至此,我們就分析完成只執行C/C++代碼的Native線程的創建和運行過程了,從中我們就可以得出結論:Native線程與Dalvik虛擬機線程一樣,也是一個Linux線程。
四. 能同時執行C/C++代碼和Java代碼的Native線程的創建過程
與只能執行C/C++代碼的Native線程一樣,能同時執行C/C++代碼和Java代碼的Native線程也是可以通過C++類Thread的成員函數run來創建的,因此,接下來我們就從這個函數開始分析能同時執行C/C++代碼和Java代碼的Native線程的創建過程,如圖4所示:
圖4 能同時執行C/C++代碼和Java代碼的Native線程的創建過程
這個過程可以分爲14個步驟,接下來我們就詳細分析每一個步驟。
Step 1. Thread.run
status_t Thread::run(const char* name, int32_t priority, size_t stack)
{
Mutex::Autolock _l(mLock);
......
bool res;
if (mCanCallJava) {
res = createThreadEtc(_threadLoop,
this, name, priority, stack, &mThread);
} else {
res = androidCreateRawThreadEtc(_threadLoop,
this, name, priority, stack, &mThread);
}
......
return NO_ERROR;
}
這個函數定義在文件frameworks/base/libs/utils/Threads.cpp中。
Thread類的成員函數run的實現可以參考前面只執行C/C++代碼的Native線程的創建過程的Step 1。不過,在我們這個場景中,當前正在處理的Thread對象的成員變量mCanCallJava的值就等於true,這時候Thread類的成員函數run就會調用另外一個函數createThreadEtc來執行創建線程的工作。因此,接下來我們就繼續分析函數createThreadEtc的實現。
Step 2. createThreadEtc
inline bool createThreadEtc(thread_func_t entryFunction,
void *userData,
const char* threadName = "android:unnamed_thread",
int32_t threadPriority = PRIORITY_DEFAULT,
size_t threadStackSize = 0,
thread_id_t *threadId = 0)
{
return androidCreateThreadEtc(entryFunction, userData, threadName,
threadPriority, threadStackSize, threadId) ? true : false;
}
這個函數定義在文件frameworks/base/include/utils/threads.h中。
函數createThreadEtc的實現很簡單,它通過調用函數androidCreateThreadEtc來執行創建線程的操作。
Step 3. androidCreateThreadEtc
static android_create_thread_fn gCreateThreadFn = androidCreateRawThreadEtc;
int androidCreateThreadEtc(android_thread_func_t entryFunction,
void *userData,
const char* threadName,
int32_t threadPriority,
size_t threadStackSize,
android_thread_id_t *threadId)
{
return gCreateThreadFn(entryFunction, userData, threadName,
threadPriority, threadStackSize, threadId);
}
這個函數定義在文件frameworks/base/libs/utils/Threads.cpp中。
函數createThreadEtc的實現也很簡單,它通調用函數指針gCreateThreadFn所指向的函數來執行創建線程的操作。注意,雖然函數指針gCreateThreadFn開始的時候指向的是函數androidCreateRawThreadEtc,但是在前面Dalvik虛擬機的啓動過程分析一文中提到,Zygote進程在啓動的過程中,會通過調用函數androidSetCreateThreadFunc將它重新指向函數javaCreateThreadEtc。因此,接下來實際上是調用的函數javaCreateThreadEtc來創建線程。
Step 4. javaCreateThreadEtc
/*static*/ int AndroidRuntime::javaCreateThreadEtc(
android_thread_func_t entryFunction,
void* userData,
const char* threadName,
int32_t threadPriority,
size_t threadStackSize,
android_thread_id_t* threadId)
{
void** args = (void**) malloc(3 * sizeof(void*)); // javaThreadShell must free
int result;
assert(threadName != NULL);
args[0] = (void*) entryFunction;
args[1] = userData;
args[2] = (void*) strdup(threadName); // javaThreadShell must free
result = androidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args,
threadName, threadPriority, threadStackSize, threadId);
return result;
}
這個函數定義在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
函數javaCreateThreadEtc首先是將entryFunction、userData和threadName三個參數封裝在一個void*數組args中,然後再以該數組爲參數,調用另外一個函數androidCreateRawThreadEtc來執行創建線程的工作。
Step 5. androidCreateRawThreadEtc
這個函數定義在文件frameworks/base/libs/utils/Threads.cpp中,它的具體實現可以參考前面只執行C/C++代碼的Native線程的創建過程中的Step 2。總體來說,它就是過pthread庫提供的函數pthread_create來創建一個線程,並且將從前面Step 4傳遞過來的函數AndroidRuntime類的靜態成員函數javaThreadShell作爲該新創建的線程的入口點函數。因此,接下來我們就繼續分析AndroidRuntime類的靜態成員函數javaThreadShell的實現。
Step 6. AndroidRuntime.javaThreadShell
/*static*/ int AndroidRuntime::javaThreadShell(void* args) {
void* start = ((void**)args)[0];
void* userData = ((void **)args)[1];
char* name = (char*) ((void **)args)[2]; // we own this storage
free(args);
JNIEnv* env;
int result;
/* hook us into the VM */
if (javaAttachThread(name, &env) != JNI_OK)
return -1;
/* start the thread running */
result = (*(android_thread_func_t)start)(userData);
/* unhook us */
javaDetachThread();
free(name);
return result;
}
這個函數定義在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
AndroidRuntime類的靜態成員函數javaThreadShell主要是執行以三個操作:
1. 調用函數javaAttachThread來將當前線程附加到在當前進程中運行的Dalvik虛擬機中去,使得當前線程不僅能夠執行 C/C++代碼,還可以執行Java代碼。
2. 調用函數指針start所指向的函數來作爲當前線程的執行主體。函數指針start指向的函數是在前面Step 4中封裝,而被封裝的函數是從前面的Step 1傳遞過來的,也就是Thread類的靜態成員函數_threadLoop。因此,當前新創建的線程是以Thread類的靜態成員函數_threadLoop作爲執行主體的。
3. 從Thread類的靜態成員函數_threadLoop返回來之後,當前線程就準備要退出了。在退出之前,需要調用函數javaDetachThread來將當前線程從當前進程中運行的Dalvik虛擬機中移除。
接下來,我們就分別分析上述三個函數的實現,以便可以瞭解一個能同時執行C/C++代碼和Java代碼的Native線程的初始化和運行過程。
Step 7. javaAttachThread
static int javaAttachThread(const char* threadName, JNIEnv** pEnv)
{
JavaVMAttachArgs args;
JavaVM* vm;
jint result;
vm = AndroidRuntime::getJavaVM();
assert(vm != NULL);
args.version = JNI_VERSION_1_4;
args.name = (char*) threadName;
args.group = NULL;
result = vm->AttachCurrentThread(pEnv, (void*) &args);
if (result != JNI_OK)
LOGI("NOTE: attach of thread '%s' failed\n", threadName);
return result;
}
這個函數定義在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
函數javaAttachThread首先調用AndroidRuntime類的靜態成員函數getJavaVM來獲得在當前進程中運行的Dalvik虛擬機實例。這個Dalvik虛擬機實例是在Zygote進程啓動的過程中創建的,具體可以參考前面Dalvik虛擬機的啓動過程分析一文。
獲得了運行在當前進程中的Dalvik虛擬機實例實例之後,函數javaAttachThread接下來就可以調用它的成員函數AttachCurrentThread來將當前線程附加到它裏去。同樣是從前面Dalvik虛擬機的啓動過程分析一文可以知道,這裏的變量vm指向的實際上是一個JavaVMExt對象,這個JavaVMExt對象的成員函數AttachCurrentThread實際上是一個函數指針,它指向的函數爲在Dalvik虛擬機內部定義的一個全局函數AttachCurrentThread。因此,接下來我們就繼續分析在Dalvik虛擬機內部定義的全局函數AttachCurrentThread的實現。
Step 8. AttachCurrentThread
/*
* Attach the current thread to the VM. If the thread is already attached,
* this is a no-op.
*/
static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args)
{
return attachThread(vm, p_env, thr_args, false);
}
這個函數定義在文件dalvik/vm/Jni.c中。
函數AttachCurrentThread的實現很簡單,它通過調用另外一個函數attachThread來將當前線程附加到在當前進程中運行的Dalvik虛擬機中去。
Step 9. attachThread
static jint attachThread(JavaVM* vm, JNIEnv** p_env, void* thr_args,
bool isDaemon)
{
JavaVMAttachArgs* args = (JavaVMAttachArgs*) thr_args;
Thread* self;
bool result = false;
/*
* Return immediately if we're already one with the VM.
*/
self = dvmThreadSelf();
if (self != NULL) {
*p_env = self->jniEnv;
return JNI_OK;
}
/*
* No threads allowed in zygote mode.
*/
if (gDvm.zygote) {
return JNI_ERR;
}
/* tweak the JavaVMAttachArgs as needed */
JavaVMAttachArgs argsCopy;
if (args == NULL) {
/* allow the v1.1 calling convention */
argsCopy.version = JNI_VERSION_1_2;
argsCopy.name = NULL;
argsCopy.group = dvmGetMainThreadGroup();
} else {
assert(args->version >= JNI_VERSION_1_2);
argsCopy.version = args->version;
argsCopy.name = args->name;
if (args->group != NULL)
argsCopy.group = args->group;
else
argsCopy.group = dvmGetMainThreadGroup();
}
result = dvmAttachCurrentThread(&argsCopy, isDaemon);
......
if (result) {
self = dvmThreadSelf();
assert(self != NULL);
dvmChangeStatus(self, THREAD_NATIVE);
*p_env = self->jniEnv;
return JNI_OK;
} else {
return JNI_ERR;
}
}
這個函數定義在文件dalvik/vm/Jni.c中。
函數attachThread首先是調用函數dvmThreadSelf來檢查在當前進程中運行的Dalvik虛擬機是否已經爲當前線程創建過一個Thread對象了。如果已經創建過的話,那麼就說明當前線程之前已經附加到當前進程中運行的Dalvik虛擬機去了。因此,這時候函數attachThread就什麼也不用做就返回了。
我們假設當前線程還沒有附加到當前進程中運行的Dalvik虛擬機中,接下來函數attachThread就繼續檢查gDvm.zygote的值是否等於true。如果等於true的話,那麼就說明當前進程的Zygote進程,這時候是不允許創建附加新的線程到Dalvik虛擬機中來的。因此,這時候函數attachThread就直接返回一個錯誤碼給調用者。
通過了前面的檢查之後,函數attachThread再將根據參數thr_args的值來初始化一個JavaVMAttachArgs結構體。這個JavaVMAttachArgs結構體用來描述當前線程附加到Dalvik虛擬機時的屬性,包括它所使用的JNI版本號、線程名稱和所屬的線程組。注意,當參數thr_args的值等於NULL的時候,當前線程所使用的JNI版本號默認爲JNI_VERSION_1_2,並且它的線程組被設置爲當前進程的主線程組。另一方面,如果參數thr_args的值不等於NULL,那麼就要求它所指定的JNI版本號大於等於JNI_VERSION_1_2,並且當它沒有指定線程組屬性性,將當前線程的線程組設置爲當前進程的主線程組。
初始化好上述的JavaVMAttachArgs結構體之後,函數attachThread就調用另外一個函數dvmAttachCurrentThread來爲當前線程創建一個JNI上下文環境。如果函數dvmAttachCurrentThread能成功爲當前線程創建一個JNI上下文環境,那麼它的返回值result就會等於ture。在這種情況下,函數dvmAttachCurrentThread同時還會爲當前線程一個Thread對象。因此,這時候調用函數dvmThreadSelf的返回值就不等於NULL,並且它的成員變量jniEnv指向了一個JNIEnvExt對象,用來描述當前線程的JNI上下文環境。
最後,函數attachThread就將當前線程的狀態設置爲THREAD_NATIVE,以表示它接下來都是在執行C/C++代碼,並且將前面所創建的JNI上下文環境保存在輸出參數p_env中返回給調用者使用。
接下來,我們就繼續分析函數dvmAttachCurrentThread的實現,以便可以瞭解Dalvik虛擬機爲當前線程創建JNI上下文環境的過程。
Step 10. dvmAttachCurrentThread
bool dvmAttachCurrentThread(const JavaVMAttachArgs* pArgs, bool isDaemon)
{
Thread* self = NULL;
......
bool ok, ret;
self = allocThread(gDvm.stackSize);
......
setThreadSelf(self);
......
ok = prepareThread(self);
......
self->jniEnv = dvmCreateJNIEnv(self);
......
self->next = gDvm.threadList->next;
if (self->next != NULL)
self->next->prev = self;
self->prev = gDvm.threadList;
gDvm.threadList->next = self;
......
/* tell the debugger & DDM */
if (gDvm.debuggerConnected)
dvmDbgPostThreadStart(self);
return ret;
......
}
這個函數定義在文件dalvik/vm/Thread.c中。
函數dvmAttachCurrentThread主要是執行以下五個操作:
1. 調用函數allocThread和setThreadSelf爲當前線程創建一個Thread對象。
2. 調用函數prepareThread來初始化當前線程所要使用到的引用表,它的具體實現可以參考前面Dalvik虛擬機線程的創建過程中的Step 5。
3. 調用函數dvmCreateJNIEnv來當前線程創建一個JNI上下文環境,它的具體實現可以參考前面Dalvik虛擬機的啓動過程分析一文。這個創建出來的JNI上下文環境使用一個JNIEnvExt結構體來描述。JNIEnvExt結構體有一個重要的成員變量funcTable,它指向的是一系列的Dalvik虛擬機回調函數。正是因爲有了這個Dalvik虛擬機回調函數表,當前線程纔可以訪問Dalvik虛擬機中的Java對象或者執行Dalvik虛擬機中的Java代碼。
4. 將當前線程添加到gDvm.threadList所描述的一個Dalvik虛擬機線程列表中去。從這裏就可以看出,可以執行Java代碼的Native線程和Dalvik虛擬機線程一樣,都是保存在gDvm.threadList所描述的一個列表中。
5. 如果在當前進程中運行的Dalvik虛擬機有調試器連接,即gDvm.debuggerConnected的值等於true,那麼就調用函數dvmDbgPostThreadStart來通知調試器在當前進程中運行的Dalvik虛擬機新增了一個線程。
這一步執行完成之後,當前線程就具有一個JNI上下文環境了。返回到前面的Step 6中,即AndroidRuntime類的靜態成員函數javaThreadShell中,接下來它就會調用Thread類的靜態成員函數_threadLoop來作爲當前線程的執行主體。
Step 11. Thread._threadLoop
這個函數定義在文件frameworks/base/libs/utils/Threads.cpp中,它的具體實現可以參考前面只執行C/C++代碼的Native線程的創建過程的Step 3。總的來說,Thread類的靜態成員函數_threadLoop就是在一個無限循環中不斷地調用用來描述當前線程的一個Thread對象的成員函數threadLoop,直到當前線程請求退出爲止。
這一步執行完成之後,當前線程就準備要退出了。返回前面的Step 6中,即AndroidRuntime類的靜態成員函數javaThreadShell中,接下來它就會調用函數javaDetachThread來將當前線程從在當前進程中運行的Dalvik虛擬機中移除。因此,接下來我們就繼續分析函數javaDetachThread的實現。
Step 12. javaDetachThread
/*
* Detach the current thread from the set visible to the VM.
*/
static int javaDetachThread(void)
{
JavaVM* vm;
jint result;
vm = AndroidRuntime::getJavaVM();
assert(vm != NULL);
result = vm->DetachCurrentThread();
if (result != JNI_OK)
LOGE("ERROR: thread detach failed\n");
return result;
}
這個函數定義在frameworks/base/core/jni/AndroidRuntime.cpp中。
函數javaDetachThread首先是調用AndroidRuntime類的靜態成員函數getJavaVM來獲得在當前進程中運行的Dalvik虛擬機實例,接着再調用這個Dalvik虛擬機實例的成員函數DetachCurrentThread來將當前線程將Dalvik虛擬機中移除。
在前面的Step 7中提到,AndroidRuntime類的靜態成員函數getJavaVM返回的實際上是一個JavaVMExt對象,這個JavaVMExt對象的成員函數DetachCurrentThread實際上與成員函數AttachCurrentThread一樣,也是一個函數指針,並且通過前面Dalvik虛擬機的啓動過程分析一文可以知道,這個函數指針指向的是在Dalvik虛擬機內部定義的函數DetachCurrentThread。
因此,接下來我們就繼續分在Dalvik虛擬機內部定義的函數DetachCurrentThread的實現,以便可以瞭解一個能同時執行C/C++代碼和Java代碼的Native線程脫離Dalvik虛擬機的過程。
Step 13. DetachCurrentThread
/*
* Dissociate the current thread from the VM.
*/
static jint DetachCurrentThread(JavaVM* vm)
{
......
/* detach the thread */
dvmDetachCurrentThread();
/* (no need to change status back -- we have no status) */
return JNI_OK;
}
這個函數定義在文件dalvik/vm/Jni.c中。
函數DetachCurrentThread主要是能通過調用另外一個函數dvmDetachCurrentThread來將當前線程從Dalvik虛擬機中移除,因此,接下來我們就繼續分析函數dvmDetachCurrentThread的實現。
Step 14. dvmDetachCurrentThread
這個函數定義在文件dalvik/vm/Thread.c中,它的具體實現可以參考前面Dalvik虛擬機線程的創建過程中的Step 10。總的來說,函數dvmDetachCurrentThread主要就是用來清理當前線程的JNI上下文環境,例如,清理當前線程還在引用的對象,以及清理當前線程所佔用的內存等。
至此,我們就分析完成能同時執行C/C++代碼和Java代碼的Native線程的創建和運行過程了,從中我們就可以得出結論:能同時執行C/C++代碼和Java代碼的Native線程與只能執行C/C++代碼的Native線程一樣,都是一個Linux線程,不過區別就在於前者會被附加到Dalvik虛擬機中去,並且具有一個JNI上下文環境,因而可以執行Java代碼。
這樣,Dalvik虛擬機進程和線程的創建過程分析就分析完成了,從中我們就可以得到它們與本地操作系統的進程和線程的關係:
1. Dalvik虛擬機進程就是本地操作系統進程,也就是Linux進程,區別在於前者運行有一個Dalvik虛擬機實例。
2. Dalvik虛擬機線程就是本地操作系統進程,也就是Linux線程,區別在於前者在創建的時候會自動附加到Dalvik虛擬機中去,而後者在需要執行Java代碼的時候纔會附加到Dalvik虛擬機中去。
我們可以思考一下:爲什麼Dalvik虛擬機要將自己的進程和線程使用本地操作系統的進程和線程來實現呢?我們知道,進程調度是一個很複雜的問題,特別是在多核的情況下,它要求高效地利用CPU資源,並且公平地調試各個進程,以達到系統吞吐量最大的目標。Linux內核本身已經實現了這種高效和公平的進程調度機制,因此,就完全沒有必要在Dalvik虛擬機內部再實現一套,這樣就要求Dalvik虛擬機使用本地操作系統的進程來作爲自己的進程。此外,Linux內核沒有線程的概念,不過它可以使用一種稱爲輕量級進程的概念來實現線程,這樣Dalvik虛擬機使用本地操作系統的線程來作爲自己的線程,就同樣可以利用Linux內核的進程調度機制來調度它的線程,從而也能實現高效和公平的原則。
至此,我們也完成了Dalvik虛擬機的學習了,重新學習請參考Dalvik虛擬機簡要介紹和學習計劃一文。
---------------------
作者:羅昇陽
來源:CSDN
原文:https://blog.csdn.net/Luoshengyang/article/details/8923484