Windbg+Rotor:Managed Process中的各种Special Threads分析

这几天Oracle培训,数据库功力倒是没太大长进,倒是DebugWindows架构和实现还有CLR的觉悟突飞猛进。

开篇前首先3ksrick,他把他写的一票经典的文章都发到sscli.cnblogs.com团队里面来了。Rick可是我在看雪bbs上面久仰的大牛由于写的文章时间在创建团队的时间之前,故需要翻到第一页才能看到rick的文章。

 

       首先就从sscliTLS预先定义的一个结构体说起了:

 

    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,

};

 

这个枚举类型的数据结构,是表示的TLS初始化参数中的Thread的类型。

       首先不说一个User App可以创建多少个不同类型的ThreadCLR中,当一个托管Process在启动之后,至少需要创建三种类型的Process:一个Main Thread用来启动CLR和执行托管代码。一个CLR Debugger Helper thread。主要用来提供调试servicesFor interop debuggersjust as visual studiowindbg之类。另外就是一个finalizer thread,用来完成对各种可达和不可达的Object的析构。

       这三种thread,是一个托管Process启动之后最少需要创建的。另外,根据这个Process是干嘛的不同,还可能需要创建不同的threads来适应不同的功能。例如webformwinform,涉及到I/OlocksynchronizationGCtimer以及ThreadPool的时候,都分别需要创建不同的threads

       Well,对于上面的thread,下面先来个简单的叙述先:

 

Finalizer ThreadThreadType_Finalizer = 0x00000040

对于Finalizer Thread,主要是在EE在启动的时候,GC Heap启动的时候,由GC来创建的这个线程。

等等,找一小白鼠演示下先,这里,选择一个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 Attachaspnet_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,用来析构some Objects用的。来看看其堆栈调用情况:

 

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

 

啊哈,从下往上看,第一个是调用OSbase Thread的初始化方法。在托管Process的创建中,任何一个thread的创建都是基于一个base OS Thread的。关于soft threadhard thread!threads的区别,可以参见我以前的文章。还是有很大区别的。Then,第二行调用的是intermediateThreadProc,这个是任何一个soft thread在创建之前都需要调用的一个方法:

 

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创建的时候需要调用的。注意,这里的Thread不是由ThreadPool来创建的。对于Thread Pool创建一个Hard thread的时候,ThreadPoolMgr也有一个相同的intermediateThreadProc方法:

 

DWORD __stdcall ThreadpoolMgr::intermediateThreadProc(PVOID arg)

{

WRAPPER_CONTRACT;

//多调用了一组宏,来进行完整性的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的命名问题。在EnableHyperThreading的时候,这个可以非常影响app的性能。

GCHeap在启动的时候,就启动了Finalizer Thread

DWORD __stdcall GCHeap::FinalizerThreadStart(void *args)

这个方法的实现,就不具体说了,比较麻烦的说。乱七八糟的检查啊,初始化的东西比较多。同时也不是这里的重点。

 

Debugger Helper ThreadThreadType_DbgHelper = 0x00000008

还是上面的小白鼠,由于Thread太多了,一个一个的找起来不方便,就先用~*k命令把调用堆栈先都列了出来,找到了第四个线程是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种植入调试线程的方式,就有一些good tips和一些bad tips了。

00dbff54 79ed4a63 mscorwks!DebuggerRCThread::MainLoop+0xcf

这一句表明,在这个helper Thread的大部分时间,是在执行一个loop循环来获取外部debuggerrequest的。当这个helper thread得到一个requets的时候,它就direct进入CLR的内部data structure,得到一系列的结果,然后返回结果到debugger

Since是由托管代码启动的,这个debugger helper thread只能提供对于mixed mode或者是managed debugger的支持。而对于windbg这样的native debugger是不支持的。不过,如果如果windbg加载了sos或者是SIEExtPub或者是外部托管扩展调试模块的话,那就另行讨论了。

另外提一下调试线程的“in-process model”和“out-process model”两种调试模型的比较:

CLRDebug Service里面“in-Process Model”就意味着这个debugger help threadee thread一样运行在managed Process里面了,当然,ee Thread也是提供一部分调试信息的。

这样的好处是显而易见的,因为EE Thread也是提供一部分调试信息的,这样以来,helper Thread就可以重用EE的这一部分代码了。这样比out-of-process调试模型更加容易得获取了CLR的内部的data stracture。性能的提升也是不用说了的。还有一个好处,就是可以于GC Thread在一些问题的处理上面更加好的协作和共享信息了,譬如,在对lock和在对synchronization objects的操作上面。同一个process里面的两个thread总比不同process里面的线程交互起来方便吧。

当然,也是有bad tips的。由于一个helper debug thread只能存活在一个live process里面,这样,在使用dump分析问题的时候,helper debugger的功能特性代码就用不上鸟。What a pity..还有一个非常棘手的问题,由于inprocessmode的调试thread对于获取托管代码中的信息用起来比较方便,这样,在interop的时候,托管代码和native code交互起来之后,这整个调试模型就被破坏掉了,这也是为什么在vs 2003的时候,调试interop的时候经常死锁和特别慢的原因。

还有一个问题,在需要完全调试这个process,下了一个断点,所有的执行都中断的时候,这个thread还是运行着的。这样,在进行压力测试的时候,就带来了麻烦了….

额,还有一些问题,罗嗦起来就麻烦了扯远了没完没了了,打住打住。

 

Rotor里面,对于这个helper debugger thread的实现,是藏在一个阴暗的角落了(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();

      

       //这个地方是上面的枚举类型了,设置clr的这个thread flag,标识其类型为调试线程。

ClrFlsSetThreadType(ThreadType_DbgHelper);

//写入调试日志。

    LOG((LF_CORDB, LL_EVERYTHING, "ThreadProcStatic called\n"));

      

DebuggerRCThread* t = (DebuggerRCThread*)g_pRCThread;

       //这句才是重点,又调用别的地方去了,不继续找了,查看堆栈

    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启动的相关的Threads。这些thread,并不是一个托管Process必须有的Thread,这取决于一个Host到宿主的进程如何使用CLR的特性和功能了。

还有,some of 这些thread type启动之后,更加CPU模式,所处理的工作和一些用户的配置文件,这些同类型的thread,可以只有一个,也可以有多个。例如以前提到的在多cpu的时候,如果GC运行在Server mode的话,一个cpu对应一个gc thread的,管理一个GC Heap和一个LOH

啊哈,刚才在在代码里面找到了一个枚举类型的结构体:

 

enum ThreadpoolThreadType

{

    WorkerThread,

    CompletionPortThread,

    WaitThread,

    TimerMgrThread

};

 

这个枚举类型的结构体还是表明了很多信息的。^_^

 

这些由ThreadPool启动的threadtype就多了,主要有上面的四种类型:包括wait Thread,对应上面的Thread Type waitWait Thread用来处理同步等待,这种类型的Thread可以有多个。WorkerThread,这些worker thread是用来执行托管代码的,根据需要执行的功能的不同,可以有多个。Completion port threads,这些线程用来处理I/Onetwork Port相关的功能。MsRotor1.1里面是没有这个东西的..2.0里面有不用说。

另外,和worker thread匹配的,还有一个叫做gate threadthread。这个thread本身意义上面讲,也是ThreadPool创建的,但是却不属于ThreadPoolThreadType。因为,worker thread并不是由ThreadPool直接创建管理的,worker thread是由Gate Thread创建并且管理的。因为根据程序的功能不同,worker thread可以多种多样而且有很多。来个分层设计,我喜欢。最后一个就是timer thread了。这个是用来控制timer queue的,只能有一个。

具体的,就不一一介绍这些线程的实现和功能了,太多了。按照上面讲解的思路和方法,可以得到的挺清楚。如有疑问可以follow commets

 

AppDomain Unload Helper Thread (ThreadType_ADUnloadHelper = 0x00000200)

这个Thread,是帮助AppDomain卸载用的。在CLR1.0中,如果需要将一个用户启动的AppDomain卸载的话,就会在System Domain中创建一个Worker Thread,用这个Thread来完成AppDomain的卸载工作。当目标AppDomain被卸载了之后,这个Thread就死掉了。而在Rotor里面,则是专门的用一个AppDomain Unload Helper Thread来完成这个工作的。关于AD的卸载步骤和细节,以后在研究这个问题。

 

GC Thread

根据托管Process是运行的workstation模式的gc还是server模式的gc,还有一种用叫做concurrent模式的GC,这种模式的GCRotor里面是没有的。

 

另外,还有一些RPC thread和一些LRPC threads

最后的剩下的,很大一部分就是COM Threads 

       额,到吃饭的点了,催吃饭去了,收尾收的有点急,虎头蛇尾的感觉…………后续吧。 

       lbq1221119@4/12/2008 11:52 AM

       首发:sscli.cnblogs.com

posted on 2008-04-12 18:12  lbq1221119  阅读(3476)  评论(11编辑  收藏  举报

导航