Windbg+Rotor:Managed Process中的各種Special Threads分析
這幾天Oracle培訓(xùn),數(shù)據(jù)庫功力倒是沒太大長進,倒是Debug,Windows架構(gòu)和實現(xiàn)還有CLR的覺悟突飛猛進。
開篇前首先3ks下rick,他把他寫的一票經(jīng)典的文章都發(fā)到sscli.cnblogs.com團隊里面來了。Rick可是我在看雪bbs上面久仰的大牛…由于寫的文章時間在創(chuàng)建團隊的時間之前,故需要翻到第一頁才能看到rick的文章。
首先就從sscli中TLS預(yù)先定義的一個結(jié)構(gòu)體說起了:
enum TlsThreadTypeFlag // flag used for thread type in Tls data
{
ThreadType_GC = 0x00000001,
ThreadType_Timer = 0x00000002,
ThreadType_Gate = 0x00000004,
ThreadType_DbgHelper = 0x00000008,
ThreadType_Shutdown = 0x00000010,
ThreadType_DynamicSuspendEE = 0x00000020,
ThreadType_Finalizer = 0x00000040,
ThreadType_ADUnloadHelper = 0x00000200,
ThreadType_ShutdownHelper = 0x00000400,
ThreadType_Threadpool_IOCompletion = 0x00000800,
ThreadType_Threadpool_Worker = 0x00001000,
ThreadType_Wait = 0x00002000,
};
這個枚舉類型的數(shù)據(jù)結(jié)構(gòu),是表示的TLS初始化參數(shù)中的Thread的類型。
首先不說一個User App可以創(chuàng)建多少個不同類型的Thread,CLR中,當(dāng)一個托管Process在啟動之后,至少需要創(chuàng)建三種類型的Process:一個Main Thread用來啟動CLR和執(zhí)行托管代碼。一個CLR Debugger Helper thread。主要用來提供調(diào)試services。For interop debuggers,just as visual studio和windbg之類。另外就是一個finalizer thread,用來完成對各種可達和不可達的Object的析構(gòu)。
這三種thread,是一個托管Process啟動之后最少需要創(chuàng)建的。另外,根據(jù)這個Process是干嘛的不同,還可能需要創(chuàng)建不同的threads來適應(yīng)不同的功能。例如webform和winform,涉及到I/O,lock,synchronization,GC,timer以及ThreadPool的時候,都分別需要創(chuàng)建不同的threads。
Well,對于上面的thread,下面先來個簡單的敘述先:
Finalizer Thread:ThreadType_Finalizer = 0x00000040
對于Finalizer Thread,主要是在EE在啟動的時候,GC Heap啟動的時候,由GC來創(chuàng)建的這個線程。
等等,找一小白鼠演示下先,這里,選擇一個webform作為小白鼠,方便Threadpool的介紹和其余的一些Thread的演示:
namespace TestWebApp
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write("test for web app");
}
}
}
Windbg Attach上aspnet_wp.exe這個Process:
0:007> !threads
ThreadCount: 9
UnstartedThread: 0
BackgroundThread: 8
PendingThread: 0
DeadThread: 1
Hosted Runtime: no
ID GC Domain APT Exception
1 1 Enabled 0016ab98 Ukn (Threadpool Completion Port)
7 2 Enabled 0016ab98 MTA (Finalizer)
8 3 Enabled 0016ab98 MTA (Threadpool Completion Port)
10 4 Enabled 0016ab98 Ukn
XX 6 Enabled 0016ab98 Ukn (Threadpool Worker)
12 5 Enabled 0016ab98 MTA (Threadpool Worker)
13 7 Enabled 0016ab98 MTA (Threadpool Completion Port)
16 8 Enabled 0016ab98 MTA (Threadpool Completion Port)
11 9 Enabled 0016ab98 MTA (Threadpool Worker)
這里為了顯示方便,去掉了一些列。上面顯示的托管Thread里面,第七個就是Finalizer Thread,用來析構(gòu)some Objects用的。來看看其堆棧調(diào)用情況:
0:007> k
ChildEBP RetAddr
0132fcd8 7c92e9ab ntdll!KiFastSystemCallRet
0132fcdc 7c8094e2 ntdll!ZwWaitForMultipleObjects+0xc
0132fd78 7c80a075 kernel32!WaitForMultipleObjectsEx+0x12c
0132fd94 79f60b6a kernel32!WaitForMultipleObjects+0x18
0132fdb4 79f34fb4 mscorwks!SVR::WaitForFinalizerEvent+0x7a
0132fdc8 79ecb4a4 mscorwks!SVR::GCHeap::FinalizerThreadWorker+0x75
0132fdd8 79ecb442 mscorwks!Thread::UserResumeThread+0xfb
0132fe6c 79ecb364 mscorwks!Thread::DoADCallBack+0x355
0132fea8 79ed5e8b mscorwks!Thread::DoADCallBack+0x541
0132fed0 79ed5e56 mscorwks!ManagedThreadBase_NoADTransition+0x32
0132fedc 79f6fd87 mscorwks!ManagedThreadBase::FinalizerBase+0xb
0132ff14 79ecb00b mscorwks!SVR::GCHeap::FinalizerThreadStart+0xbb
0132ffb4 7c80b683 mscorwks!Thread::intermediateThreadProc+0x49
0132ffec 00000000 kernel32!BaseThreadStart+0x37
啊哈,從下往上看,第一個是調(diào)用OS的base Thread的初始化方法。在托管Process的創(chuàng)建中,任何一個thread的創(chuàng)建都是基于一個base OS Thread的。關(guān)于soft thread,hard thread和!threads的區(qū)別,可以參見我以前的文章。還是有很大區(qū)別的。Then,第二行調(diào)用的是intermediateThreadProc,這個是任何一個soft thread在創(chuàng)建之前都需要調(diào)用的一個方法:
DWORD __stdcall Thread::intermediateThreadProc(PVOID arg)
{
WRAPPER_CONTRACT;
m_offset_counter++;
if (m_offset_counter * offset_multiplier > PAGE_SIZE)
m_offset_counter = 0;
_alloca(m_offset_counter * offset_multiplier);
intermediateThreadParam* param = (intermediateThreadParam*)arg;
LPTHREAD_START_ROUTINE ThreadFcnPtr = param->lpThreadFunction;
PVOID args = param->lpArg;
delete param;
return ThreadFcnPtr(args);
}
這個是Thread類中的intermediateThreadProc方法,是一個softThread創(chuàng)建的時候需要調(diào)用的。注意,這里的Thread不是由ThreadPool來創(chuàng)建的。對于Thread Pool創(chuàng)建一個Hard thread的時候,ThreadPoolMgr也有一個相同的intermediateThreadProc方法:
DWORD __stdcall ThreadpoolMgr::intermediateThreadProc(PVOID arg)
{
WRAPPER_CONTRACT;
//多調(diào)用了一組宏,來進行完整性的Contract的檢驗的。
STATIC_CONTRACT_SO_INTOLERANT;
offset_counter++;
if (offset_counter * offset_multiplier > PAGE_SIZE)
offset_counter = 0;
_alloca(offset_counter * offset_multiplier);
intermediateThreadParam* param = (intermediateThreadParam*)arg;
LPTHREAD_START_ROUTINE ThreadFcnPtr = param->lpThreadFunction;
PVOID args = param->lpArg;
delete param;
return ThreadFcnPtr(args);
}
這個方法,主要是用來避免P4 cpu 上面的64kb/1mb的命名問題。在Enable了HyperThreading的時候,這個可以非常影響app的性能。
GCHeap在啟動的時候,就啟動了Finalizer Thread:
DWORD __stdcall GCHeap::FinalizerThreadStart(void *args)
這個方法的實現(xiàn),就不具體說了,比較麻煩的說。亂七八糟的檢查啊,初始化的東西比較多。同時也不是這里的重點。
Debugger Helper Thread(ThreadType_DbgHelper = 0x00000008)
還是上面的小白鼠,由于Thread太多了,一個一個的找起來不方便,就先用~*k命令把調(diào)用堆棧先都列了出來,找到了第四個線程是Debugger Helper Thread:
0:004> k
ChildEBP RetAddr
00dbfe38 7c92e9ab ntdll!KiFastSystemCallRet
00dbfe3c 7c8094e2 ntdll!ZwWaitForMultipleObjects+0xc
00dbfed8 7c80a075 kernel32!WaitForMultipleObjectsEx+0x12c
00dbfef4 79ed4b06 kernel32!WaitForMultipleObjects+0x18
00dbff54 79ed4a63 mscorwks!DebuggerRCThread::MainLoop+0xcf
00dbff84 79ed49a6 mscorwks!DebuggerRCThread::ThreadProc+0xca
00dbffb4 7c80b683 mscorwks!DebuggerRCThread::ThreadProcStatic+0x82
00dbffec 00000000 kernel32!BaseThreadStart+0x37
從這個地方,可以看到,是mscorwks這個模塊啟動的這個Thread。這種在Managed Process種植入調(diào)試線程的方式,就有一些good tips和一些bad tips了。
00dbff54 79ed4a63 mscorwks!DebuggerRCThread::MainLoop+0xcf
這一句表明,在這個helper Thread的大部分時間,是在執(zhí)行一個loop循環(huán)來獲取外部debugger的request的。當(dāng)這個helper thread得到一個requets的時候,它就direct進入CLR的內(nèi)部data structure,得到一系列的結(jié)果,然后返回結(jié)果到debugger。
Since是由托管代碼啟動的,這個debugger helper thread只能提供對于mixed mode或者是managed debugger的支持。而對于windbg這樣的native debugger是不支持的。不過,如果如果windbg加載了sos或者是SIEExtPub或者是外部托管擴展調(diào)試模塊的話,那就另行討論了。
另外提一下調(diào)試線程的“in-process model”和“out-process model”兩種調(diào)試模型的比較:
在CLR的Debug Service里面“in-Process Model”就意味著這個debugger help thread和ee thread一樣運行在managed Process里面了,當(dāng)然,ee Thread也是提供一部分調(diào)試信息的。
這樣的好處是顯而易見的,因為EE Thread也是提供一部分調(diào)試信息的,這樣以來,helper Thread就可以重用EE的這一部分代碼了。這樣比out-of-process調(diào)試模型更加容易得獲取了CLR的內(nèi)部的data stracture。性能的提升也是不用說了的。還有一個好處,就是可以于GC Thread在一些問題的處理上面更加好的協(xié)作和共享信息了,譬如,在對lock和在對synchronization objects的操作上面。同一個process里面的兩個thread總比不同process里面的線程交互起來方便吧。
當(dāng)然,也是有bad tips的。由于一個helper debug thread只能存活在一個live process里面,這樣,在使用dump分析問題的時候,helper debugger的功能特性代碼就用不上鳥。What a pity..還有一個非常棘手的問題,由于inprocessmode的調(diào)試thread對于獲取托管代碼中的信息用起來比較方便,這樣,在interop的時候,托管代碼和native code交互起來之后,這整個調(diào)試模型就被破壞掉了,這也是為什么在vs 2003的時候,調(diào)試interop的時候經(jīng)常死鎖和特別慢的原因。
還有一個問題,在需要完全調(diào)試這個process,下了一個斷點,所有的執(zhí)行都中斷的時候,這個thread還是運行著的。這樣,在進行壓力測試的時候,就帶來了麻煩了….
額,還有一些問題,羅嗦起來就麻煩了扯遠了沒完沒了了,打住打住。
在Rotor里面,對于這個helper debugger thread的實現(xiàn),是藏在一個陰暗的角落了(debug/ee/rcthread.cpp):
/*static*/ DWORD WINAPI DebuggerRCThread::ThreadProcStatic(LPVOID)
{
// We just wrap the instance method DebuggerRCThread::ThreadProc
//為了保持一致性的宏的合同檢查
WRAPPER_CONTRACT;
BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO();
//這個地方是上面的枚舉類型了,設(shè)置clr的這個thread flag,標(biāo)識其類型為調(diào)試線程。
ClrFlsSetThreadType(ThreadType_DbgHelper);
//寫入調(diào)試日志。
LOG((LF_CORDB, LL_EVERYTHING, "ThreadProcStatic called\n"));
DebuggerRCThread* t = (DebuggerRCThread*)g_pRCThread;
//這句才是重點,又調(diào)用別的地方去了,不繼續(xù)找了,查看堆棧
t->ThreadProc(); // this thread is local, go and become the helper
END_SO_INTOLERANT_CODE;
return 0;
}
ThreadPool threads and relative threads
這個是一個比較大的部分。ThreadPool Thread和一些由ThreadPool啟動的相關(guān)的Threads。這些thread,并不是一個托管Process必須有的Thread,這取決于一個Host到宿主的進程如何使用CLR的特性和功能了。
還有,some of 這些thread type啟動之后,更加CPU模式,所處理的工作和一些用戶的配置文件,這些同類型的thread,可以只有一個,也可以有多個。例如以前提到的在多cpu的時候,如果GC運行在Server mode的話,一個cpu對應(yīng)一個gc thread的,管理一個GC Heap和一個LOH。
啊哈,剛才在在代碼里面找到了一個枚舉類型的結(jié)構(gòu)體:
enum ThreadpoolThreadType
{
WorkerThread,
CompletionPortThread,
WaitThread,
TimerMgrThread
};
這個枚舉類型的結(jié)構(gòu)體還是表明了很多信息的。^_^
這些由ThreadPool啟動的thread的type就多了,主要有上面的四種類型:包括wait Thread,對應(yīng)上面的Thread Type wait。Wait Thread用來處理同步等待,這種類型的Thread可以有多個。WorkerThread,這些worker thread是用來執(zhí)行托管代碼的,根據(jù)需要執(zhí)行的功能的不同,可以有多個。Completion port threads,這些線程用來處理I/O和network Port相關(guān)的功能。Ms在Rotor1.1里面是沒有這個東西的..2.0里面有不用說。
另外,和worker thread匹配的,還有一個叫做gate thread的thread。這個thread本身意義上面講,也是ThreadPool創(chuàng)建的,但是卻不屬于ThreadPoolThreadType。因為,worker thread并不是由ThreadPool直接創(chuàng)建管理的,worker thread是由Gate Thread創(chuàng)建并且管理的。因為根據(jù)程序的功能不同,worker thread可以多種多樣而且有很多。來個分層設(shè)計,我喜歡。最后一個就是timer thread了。這個是用來控制timer queue的,只能有一個。
具體的,就不一一介紹這些線程的實現(xiàn)和功能了,太多了。按照上面講解的思路和方法,可以得到的挺清楚。如有疑問可以follow commets。
AppDomain Unload Helper Thread (ThreadType_ADUnloadHelper = 0x00000200)
這個Thread,是幫助AppDomain卸載用的。在CLR1.0中,如果需要將一個用戶啟動的AppDomain卸載的話,就會在System Domain中創(chuàng)建一個Worker Thread,用這個Thread來完成AppDomain的卸載工作。當(dāng)目標(biāo)AppDomain被卸載了之后,這個Thread就死掉了。而在Rotor里面,則是專門的用一個AppDomain Unload Helper Thread來完成這個工作的。關(guān)于AD的卸載步驟和細(xì)節(jié),以后在研究這個問題。
GC Thread
根據(jù)托管Process是運行的workstation模式的gc還是server模式的gc,還有一種用叫做concurrent模式的GC,這種模式的GC在Rotor里面是沒有的。
另外,還有一些RPC thread和一些LRPC threads。
最后的剩下的,很大一部分就是COM Threads。
額,到吃飯的點了,催吃飯去了,收尾收的有點急,虎頭蛇尾的感覺…………后續(xù)吧。
lbq1221119@4/12/2008 11:52 AM
首發(fā):sscli.cnblogs.com
posted on 2008-04-12 18:12 lbq1221119 閱讀(3513) 評論(11) 收藏 舉報
浙公網(wǎng)安備 33010602011771號