Android Threading
From Jollen's Wiki
目錄 |
[編輯]
Thread Structure
- Android thread 都是 native thread、即 pthread
- 裡面包含一個 pthread 的 binding
typedef struct Thread {
/* small unique integer; useful for "thin" locks and debug messages */
u4 threadId;
/*
* Thread's current status. Can only be changed by the thread itself
* (i.e. don't mess with this from other threads).
*/
ThreadStatus status;
/*
* This is the number of times the thread has been suspended. When the
* count drops to zero, the thread resumes.
*
* "dbgSuspendCount" is the portion of the suspend count that the
* debugger is responsible for. This has to be tracked separately so
* that we can recover correctly if the debugger abruptly disconnects
* (suspendCount -= dbgSuspendCount). The debugger should not be able
* to resume GC-suspended threads, because we ignore the debugger while
* a GC is in progress.
*
* Both of these are guarded by gDvm.threadSuspendCountLock.
*
* (We could store both of these in the same 32-bit, using 16-bit
* halves, to make atomic ops possible. In practice, you only need
* to read suspendCount, and we need to hold a mutex when making
* changes, so there's no need to merge them. Note the non-debug
* component will rarely be other than 1 or 0 -- not sure it's even
* possible with the way mutexes are currently used.)
*/
int suspendCount;
int dbgSuspendCount;
/*
* Set to true when the thread suspends itself, false when it wakes up.
* This is only expected to be set when status==THREAD_RUNNING.
*/
bool isSuspended;
/* thread handle, as reported by pthread_self() */
pthread_t handle;
/* thread ID, only useful under Linux */
pid_t systemTid;
/* start (high addr) of interp stack (subtract size to get malloc addr) */
u1* interpStackStart;
/* current limit of stack; flexes for StackOverflowError */
const u1* interpStackEnd;
/* interpreter stack size; our stacks are fixed-length */
int interpStackSize;
bool stackOverflowed;
/* FP of bottom-most (currently executing) stack frame on interp stack */
void* curFrame;
/* current exception, or NULL if nothing pending */
Object* exception;
/* the java/lang/Thread that we are associated with */
Object* threadObj;
/* the JNIEnv pointer associated with this thread */
JNIEnv* jniEnv;
/* internal reference tracking */
ReferenceTable internalLocalRefTable;
/* JNI local reference tracking */
ReferenceTable jniLocalRefTable;
/* JNI native monitor reference tracking (initialized on first use) */
ReferenceTable jniMonitorRefTable;
/* hack to make JNI_OnLoad work right */
Object* classLoaderOverride;
/* pointer to the monitor lock we're currently waiting on */
/* (do not set or clear unless the Monitor itself is held) */
/* TODO: consider changing this to Object* for better JDWP interaction */
Monitor* waitMonitor;
/* set when we confirm that the thread must be interrupted from a wait */
bool interruptingWait;
/* thread "interrupted" status; stays raised until queried or thrown */
bool interrupted;
/*
* Set to true when the thread is in the process of throwing an
* OutOfMemoryError.
*/
bool throwingOOME;
/* links to rest of thread list; grab global lock before traversing */
struct Thread* prev;
struct Thread* next;
/* JDWP invoke-during-breakpoint support */
DebugInvokeReq invokeReq;
#ifdef WITH_MONITOR_TRACKING
/* objects locked by this thread; most recent is at head of list */
struct LockedObjectData* pLockedObjects;
#endif
#ifdef WITH_ALLOC_LIMITS
/* allocation limit, for Debug.setAllocationLimit() regression testing */
int allocLimit;
#endif
#ifdef WITH_PROFILER
/* base time for per-thread CPU timing */
bool cpuClockBaseSet;
u8 cpuClockBase;
/* memory allocation profiling state */
AllocProfState allocProf;
#endif
#ifdef WITH_JNI_STACK_CHECK
u4 stackCrc;
#endif
} Thread;
[編輯]
Thread Initialization
- bool dvmThreadStartup(void);
- bool dvmThreadObjStartup(void);
- void dvmThreadShutdown(void);
- void dvmSlayDaemons(void);
- bool dvmPrepMainThread(void);
[編輯]
dvmPrepMainThread
/*
* Finish preparing the main thread, allocating some objects to represent
* it. As part of doing so, we finish initializing Thread and ThreadGroup.
* This will execute some interpreted code (e.g. class initializers).
*/
bool dvmPrepMainThread(void)
{
Thread* thread;
Object* groupObj;
Object* threadObj;
Object* vmThreadObj;
StringObject* threadNameStr;
Method* init;
JValue unused;
LOGV("+++ finishing prep on main VM thread\n");
/* main thread is always first in list at this point */
thread = gDvm.threadList;
assert(thread->threadId == kMainThreadId);
/*
* Make sure the classes are initialized. We have to do this before
* we create an instance of them.
*/
if (!dvmInitClass(gDvm.classJavaLangClass)) {
LOGE("'Class' class failed to initialize\n");
return false;
}
if (!dvmInitClass(gDvm.classJavaLangThreadGroup) ||
!dvmInitClass(gDvm.classJavaLangThread) ||
!dvmInitClass(gDvm.classJavaLangVMThread))
{
LOGE("thread classes failed to initialize\n");
return false;
}
groupObj = dvmGetMainThreadGroup();
if (groupObj == NULL)
return false;
/*
* Allocate and construct a Thread with the internal-creation
* constructor.
*/
threadObj = dvmAllocObject(gDvm.classJavaLangThread, ALLOC_DEFAULT);
if (threadObj == NULL) {
LOGE("unable to allocate main thread object\n");
return false;
}
dvmReleaseTrackedAlloc(threadObj, NULL);
threadNameStr = dvmCreateStringFromCstr("main", ALLOC_DEFAULT);
if (threadNameStr == NULL)
return false;
dvmReleaseTrackedAlloc((Object*)threadNameStr, NULL);
init = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangThread, "<init>",
"(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V");
assert(init != NULL);
dvmCallMethod(thread, init, threadObj, &unused, groupObj, threadNameStr,
THREAD_NORM_PRIORITY, false);
if (dvmCheckException(thread)) {
LOGE("exception thrown while constructing main thread object\n");
return false;
}
/*
* Allocate and construct a VMThread.
*/
vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT);
if (vmThreadObj == NULL) {
LOGE("unable to allocate main vmthread object\n");
return false;
}
dvmReleaseTrackedAlloc(vmThreadObj, NULL);
init = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangVMThread, "<init>",
"(Ljava/lang/Thread;)V");
dvmCallMethod(thread, init, vmThreadObj, &unused, threadObj);
if (dvmCheckException(thread)) {
LOGE("exception thrown while constructing main vmthread object\n");
return false;
}
/* set the VMThread.vmData field to our Thread struct */
assert(gDvm.offJavaLangVMThread_vmData != 0);
dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)thread);
/*
* Stuff the VMThread back into the Thread. From this point on, other
* Threads will see that this Thread is running (at least, they would,
* if there were any).
*/
dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread,
vmThreadObj);
thread->threadObj = threadObj;
/*
* Set the context class loader. This invokes a ClassLoader method,
* which could conceivably call Thread.currentThread(), so we want the
* Thread to be fully configured before we do this.
*/
Object* systemLoader = dvmGetSystemClassLoader();
if (systemLoader == NULL) {
LOGW("WARNING: system class loader is NULL (setting main ctxt)\n");
/* keep going */
}
int ctxtClassLoaderOffset = dvmFindFieldOffset(gDvm.classJavaLangThread,
"contextClassLoader", "Ljava/lang/ClassLoader;");
if (ctxtClassLoaderOffset < 0) {
LOGE("Unable to find contextClassLoader field in Thread\n");
return false;
}
dvmSetFieldObject(threadObj, ctxtClassLoaderOffset, systemLoader);
/*
* Finish our thread prep.
*/
/* include self in non-daemon threads (mainly for AttachCurrentThread) */
gDvm.nonDaemonThreadCount++;
return true;
}
[編輯]
allocThread
- 配置並初始化 Thread struct
/*
* Alloc and initialize a Thread struct.
*
* "threadObj" is the java.lang.Thread object. It will be NULL for the
* main VM thread, but non-NULL for everything else.
*
* Does not create any objects, just stuff on the system (malloc) heap. (If
* this changes, we need to use ALLOC_NO_GC. And also verify that we're
* ready to load classes at the time this is called.)
*/
static Thread* allocThread(int interpStackSize)
{
Thread* thread;
u1* stackBottom;
thread = calloc(1, sizeof(Thread));
if (thread == NULL)
return NULL;
assert(interpStackSize >= kMinStackSize && interpStackSize <=kMaxStackSize);
thread->status = THREAD_INITIALIZING;
thread->suspendCount = 0;
#ifdef WITH_ALLOC_LIMITS
thread->allocLimit = -1;
#endif
/*
* Allocate and initialize the interpreted code stack. We essentially
* "lose" the alloc pointer, which points at the bottom of the stack,
* but we can get it back later because we know how big the stack is.
*
* The stack must be aligned on a 4-byte boundary.
*/
#ifdef MALLOC_INTERP_STACK
stackBottom = malloc(interpStackSize);
if (stackBottom == NULL) {
free(thread);
return NULL;
}
memset(stackBottom, 0xc5, interpStackSize); // stop valgrind complaints
#else
stackBottom = mmap(NULL, interpStackSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON, -1, 0);
if (stackBottom == MAP_FAILED) {
free(thread);
return NULL;
}
#endif
assert(((u4)stackBottom & 0x03) == 0); // looks like our malloc ensures this
thread->interpStackSize = interpStackSize;
thread->interpStackStart = stackBottom + interpStackSize;
thread->interpStackEnd = stackBottom + STACK_OVERFLOW_RESERVE;
/* give the thread code a chance to set things up */
dvmInitInterpStack(thread, interpStackSize);
return thread;
}
[編輯]
Symbol: dvmThreadStartup
bool dvmThreadStartup(void)
{
Thread* thread;
/* allocate a TLS slot */
if (pthread_key_create(&gDvm.pthreadKeySelf, threadExitCheck) != 0) {
LOGE("ERROR: pthread_key_create failed\n");
return false;
}
/* test our pthread lib */
if (pthread_getspecific(gDvm.pthreadKeySelf) != NULL)
LOGW("WARNING: newly-created pthread TLS slot is not NULL\n");
/* prep thread-related locks and conditions */
dvmInitMutex(&gDvm.threadListLock);
pthread_cond_init(&gDvm.threadStartCond, NULL);
//dvmInitMutex(&gDvm.vmExitLock);
pthread_cond_init(&gDvm.vmExitCond, NULL);
dvmInitMutex(&gDvm._threadSuspendLock);
dvmInitMutex(&gDvm.threadSuspendCountLock);
pthread_cond_init(&gDvm.threadSuspendCountCond, NULL);
#ifdef WITH_DEADLOCK_PREDICTION
dvmInitMutex(&gDvm.deadlockHistoryLock);
#endif
/*
* Dedicated monitor for Thread.sleep().
* TODO: change this to an Object* so we don't have to expose this
* call, and we interact better with JDWP monitor calls. Requires
* deferring the object creation to much later (e.g. final "main"
* thread prep) or until first use.
*/
gDvm.threadSleepMon = dvmCreateMonitor(NULL);
gDvm.threadIdMap = dvmAllocBitVector(kMaxThreadId, true);
thread = allocThread(gDvm.stackSize);
if (thread == NULL)
return false;
/* switch mode for when we run initializers */
thread->status = THREAD_RUNNING;
/*
* We need to assign the threadId early so we can lock/notify
* object monitors. We'll set the "threadObj" field later.
*/
prepareThread(thread);
gDvm.threadList = thread;
return true;
}
[編輯]
Symbol: dvmThreadObjStartup
/*
* We're a little farther up now, and can load some basic classes.
*
* We're far enough along that we can poke at java.lang.Thread and friends,
* but should not assume that static initializers have run (or cause them
* to do so). That means no object allocations yet.
*/
bool dvmThreadObjStartup(void)
{
/*
* Cache the locations of these classes. It's likely that we're the
* first to reference them, so they're being loaded now.
*/
gDvm.classJavaLangThread =
dvmFindSystemClassNoInit("Ljava/lang/Thread;");
gDvm.classJavaLangVMThread =
dvmFindSystemClassNoInit("Ljava/lang/VMThread;");
gDvm.classJavaLangThreadGroup =
dvmFindSystemClassNoInit("Ljava/lang/ThreadGroup;");
if (gDvm.classJavaLangThread == NULL ||
gDvm.classJavaLangThreadGroup == NULL ||
gDvm.classJavaLangThreadGroup == NULL)
{
LOGE("Could not find one or more essential thread classes\n");
return false;
}
/*
* Cache field offsets. This makes things a little faster, at the
* expense of hard-coding non-public field names into the VM.
*/
gDvm.offJavaLangThread_vmThread =
dvmFindFieldOffset(gDvm.classJavaLangThread,
"vmThread", "Ljava/lang/VMThread;");
gDvm.offJavaLangThread_group =
dvmFindFieldOffset(gDvm.classJavaLangThread,
"group", "Ljava/lang/ThreadGroup;");
gDvm.offJavaLangThread_daemon =
dvmFindFieldOffset(gDvm.classJavaLangThread, "daemon", "Z");
gDvm.offJavaLangThread_name =
dvmFindFieldOffset(gDvm.classJavaLangThread,
"name", "Ljava/lang/String;");
gDvm.offJavaLangThread_priority =
dvmFindFieldOffset(gDvm.classJavaLangThread, "priority", "I");
if (gDvm.offJavaLangThread_vmThread < 0 ||
gDvm.offJavaLangThread_group < 0 ||
gDvm.offJavaLangThread_daemon < 0 ||
gDvm.offJavaLangThread_name < 0 ||
gDvm.offJavaLangThread_priority < 0)
{
LOGE("Unable to find all fields in java.lang.Thread\n");
return false;
}
gDvm.offJavaLangVMThread_thread =
dvmFindFieldOffset(gDvm.classJavaLangVMThread,
"thread", "Ljava/lang/Thread;");
gDvm.offJavaLangVMThread_vmData =
dvmFindFieldOffset(gDvm.classJavaLangVMThread, "vmData", "I");
if (gDvm.offJavaLangVMThread_thread < 0 ||
gDvm.offJavaLangVMThread_vmData < 0)
{
LOGE("Unable to find all fields in java.lang.VMThread\n");
return false;
}
/*
* Cache the vtable offset for "run()".
*
* We don't want to keep the Method* because then we won't find see
* methods defined in subclasses.
*/
Method* meth;
meth = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangThread, "run", "()V");
if (meth == NULL) {
LOGE("Unable to find run() in java.lang.Thread\n");
return false;
}
gDvm.voffJavaLangThread_run = meth->methodIndex;
/*
* Cache vtable offsets for ThreadGroup methods.
*/
meth = dvmFindVirtualMethodByDescriptor(gDvm.classJavaLangThreadGroup,
"removeThread", "(Ljava/lang/Thread;)V");
if (meth == NULL) {
LOGE("Unable to find removeThread(Thread) in java.lang.ThreadGroup\n");
return false;
}
gDvm.voffJavaLangThreadGroup_removeThread = meth->methodIndex;
return true;
}


