分类
谈 WinDbg 之 AppDomain 的创建过程‖
AppDomain 是介于操作系统
使用者可以通过 AppDomain
单还是蛋生鸡的问题,这个
AppDomain 类型的 AppDoma
AppDomain.CreateDomain
层面进程和线程概念之间,同时
.CreateDomain 创建新的 AppDo
AppDomain.CreateDomain 方法
in 里面被调用的,但这个 AppD
方法创建的呢?呵呵
具有线程的轻便和进程的封闭性,
main。这样一来就出现了一个鸡生
肯定是要在一个载入了
omain 又是谁调用
| 我们可以使用 WinDbg 当前运行的 AppDomain 情 | + SOS 的 EEHeap 命令ü?br>况。我们以下面这段代码为例 | 出 CLR 执行引擎的堆信息,获取 |
| 以下内容为程序代码: |
| // |
| // AppDomain.cs |
| // |
| using System; |
| public class EntryPoint |
| { |
| public static void Main(string[] args) |
| { |
| Console.Out.WriteLin | e("Hello AppDomain!"[img]/im | ages/wink.gif[/img]; |
| Console.In.ReadLine(); |
| } |
| } |
| 这个典型的 CLR 程序的输出如下: |
| 以下为引用: |
| 0:003> !EEHeap |
| succeeded |
| Loaded Son of Strike data table SMicrosoft.NETFrameworkv1.1.4322msco | version 5 from "E:WINDOW rwks.dll" |
| Loader Heap: |
| -------------------------------------- |
| System Domain: 793e6fc8 |
| LowFrequencyHeap:00960000(2000:0 | 0001000) |
| Size: 0x00001000(4096) bytes. |
| HighFrequencyHeap:00 | 962000(8000:00001000) |
| Size: 0x00001000(4096) bytes. |
| StubHeap:0096a000(2000:00001000) |
| Size: 0x00001000(4096) bytes. |
| Total size: 0x3000(12288)bytes |
| -------------------------------------- |
| Shared Domain: 793e83f8 |
| LowFrequencyHeap:00990000(2000) | 06c40000(10000:00007000) |
| Size: 0x00009000(36864) bytes. |
| HighFrequencyHeap:00 | 992000(8000:00001000) |
| Size: 0x00001000(4096) bytes. |
| StubHeap:0099a000(2000:00001000) |
| Size: 0x00001000(4096) bytes. |
| Total size: 0xb000(45056)bytes |
| -------------------------------------- |
| Domain 0: 147330 |
| LowFrequencyHeap:009 | 70000(2000) 06c60000(10000:0 | 0004000) |
| Size: 0x00006000(24576) bytes. |
| HighFrequencyHeap:00 | 972000(8000:00004000) |
| Size: 0x00004000(16384) bytes. |
| StubHeap:0097a000(2000:00001000) |
| Size: 0x00001000(4096) bytes. |
| Total size: 0xb000(45056)bytes |
| -------------------------------------- |
| Jit code heap: |
| Normal Jit:06c80000(10000:00002000) |
| Size: 0x00002000(8192) bytes. |
| Total size 0x00002000(8192)bytes. |
| -------------------------------------- |
| Total LoaderHeap size: 0x1b000(1 | 10592)bytes |
| ======================================= |
| generation 0 starts at 0x04aa1040 |
| generation 1 starts at 0x04aa1034 |
| generation 2 starts at 0x04aa1028 |
| segment begin allocated size |
| 04aa0000 04aa1028 04aa4000 0000 | 2fd8(12248) |
| Large object heap starts at 0x05aa1028 |
| segment begin allocated size |
| 05aa0000 05aa1028 0 | 5aa6000 0x00004fd8(20440) |
| Total Size 0x7fb0(32688) |
| ------------------------------ |
| GC Heap Size 0x7fb0(32688) |
| 我们可以看到,虽然这 已经有了三个 AppDomain: 用 DumpDomain 命令查看三 | 个程序非常简单,没有自己创建 "System Domain", "Shared Dom 个 AppDomain: | 任何 AppDomain,但实际上 CLR ain" 和 "Domain 0"。而进一步使 |
| 以下为引用: |
| 0:003> !DumpDomain 793e6fc8 |
| Domain: 793e6fc8 |
| LowFrequencyHeap: 793e702c |
| HighFrequencyHeap: 793e7080 |
| StubHeap: 793e70d4 |
| Name: |
| Assembly: 00158e48 [mscorlib] |
| ClassLoader: 00158f20 |
| Module Name |
| 79b66000 e:windowsmicrosoft.net | rameworkv1.1.4322mscorlib.dll |
| 0:003> !DumpDomain 793e83f8 |
| Domain: 793e83f8 |
| LowFrequencyHeap: 793e845c |
| HighFrequencyHeap: 793e84b0 |
| StubHeap: 793e8504 |
| Name: |
| 0:003> !DumpDomain 147330 |
| Domain: 00147330 |
| LowFrequencyHeap: 00147394 |
| HighFrequencyHeap: 001473e8 |
| StubHeap: 0014743c |
| Name: appdomain.exe |
| Assembly: 0015c2c0 [appdomain] |
| ClassLoader: 00161008 |
| Module Name |
| 00161d50 d: empappdomain.exe |
| 我们可以看到,System 的;Shared Domain 暂时没 以猜测 System Domain 是 mscorlib 创建其他 AppDom 代码,是否能够予以印证。 | Domain 实际上是专门用于载入 有使用;而 Domain 0 则负责运 CLR 专门用来载入系统基础库的 ain 以运行用户目标 Assembly | mscorlib.dll 这个 BCL 基础库 行我们的目标 Assembly。我们可 ,而系统将进一步使用此 。我们接下来看看 Rotor 的相关 |
| 在 CLR 启动时负责加 此函数首先在进行基础性初 SystemDomain,然后加载并 SystemDomain::Init 函数 | 载执行引擎的 EEStartup 函数( 始化工作后,调用 SystemDomai 初始化异常处理、JITer等等支 完成初始化 SystemDomain 等等 | vmceemain.cpp:206)中,可以发现 n::Attach 函数载入 持代码,最后会调用 工作。 |
| SystemDomain::Attach 函数(vmappd stub 管理器和 SystemDomain 的静态成 的内存区,构造并初始化 SystemDomain 中,用于以后判断 SystemDomain 是否被 SharedDomain。函数的简要功能代码如下 | omain.cpp:912)主要完成四部分工作:初始化系统 员变量;以全局静态数组 g_pSystemDomainMemory 对象,并将指针保存到 m_pSystemDomain 静态变量 构造等功能使用;构造缺省的 AppDomain;构造 : |
| 以下内容为程序代码: |
| SystemDomain* | SystemDomain::m_pSystemDomai | n = NULL; |
| static BYTE g_pSystemDom | ainMemory[sizeof(SystemDomain)]; |
| HRESULT SystemDomain::Attach() |
| { |
| // 判断 SystemDomain 是否已经构造 |
| _ASSERTE(m_pSystemDomain == NULL); |
| if(m_pSystemDomain != NULL) |
| return COR_E_EXECUTIONENGINE; |
| // 跏蓟低?stub 管理器和 SystemDomain 的静态成员变量 |
| // ... |
| // 构造 SystemDomain 对象 |
| m_pSystemDomain = ne | w (&g_pSystemDomainMemory) S | ystemDomain(); |
| if(m_pSystemDomain = | = NULL) return COR_E_OUTOFME | MORY; |
| // 初始化 SystemDomain 对象 |
| HRESULT hr = m_pSystemDomain->BaseDomain::Init(); // Setup the memory heaps |
| if(FAILED(hr)) return hr; |
| m_pSystemDomain->GetInterfaceVTableMapMgr().SetShared(); |
| // 构造缺省的 AppDomain |
| hr = m_pSystemDomain->CreateDefaultDomain(); |
| if(FAILED(hr)) return hr; |
| // 构造 SharedDomain |
| hr = SharedDomain::Attach(); |
| return hr; |
| } |
| 值得注意的是,为了让 SystemDomai BaseDomain 的构造函数都为空,而初始 码都使用类似的模式将构造和初始化分离 SystemDomain::Attach 中直接被调用以 SystemDomain::Init 函数则在上面提到 。 | n 的构造不会失败,SystemDomain 及其基类 化代码放到 Init 方法中完成,CLR 中很多类型的代 以保障构造成功。BaseDomain::Init 函数在 初始化 SystemDomain 的父类; 的 EEStartup 函数末尾才被调用,待会再详细讨论 |
| BaseDomain::Init 函 一大堆成员变量外,主要负 中存在的,这也是为什么我 BaseDomain 之后,会将 Sy SystemDomain 负责载入的 | 数(vmappdomain.cpp:310)除了 担堆和缓存的初始化。CLR 中的 们刚刚可以使用 EEHeap 命令列 stemDomain 的接口 VTable 映 mscorlib 中类型实际上是所以 | 要负责初始化 BaseDomain 对象的 堆,实际上是在每个 AppDomain 举 AppDomain 的原因。在初始化 射表设置为共享,这是因为 AppDomain 中夹枰褂玫降摹?br> |
| 接着 SystemDomain::Attach 会调用 (vmappdomain.cpp:2522)构造缺省的 App 入用户指定 Assembly 执行。此函数只是 Managed 方式构造新的 AppDomain 实例 | SystemDomain::CreateDefaultDomain 函数 Domain,也就是前面试验中的 "Domain 0",用作载 简单地调用 SystemDomain::NewDomain 函数以非 ;然后将此 AppDomain 设置为缺省的 AppDomain。 |
| 以下内容为程序代码: |
| HRESULT SystemDomain::CreateDefa | ultDomain() |
| { |
| HRESULT hr = S_OK; |
| // 防止多次初始化 |
| if (m_pDefaultDomain != NULL) |
| return S_OK; |
| // 以非 Managed 方式构造新的 AppDomain 实例 |
| AppDomain* pDomain = NULL; |
| if (FAILED(hr = NewDomain(&pDomain))) |
| return hr; |
| // 将此 AppDomain 设置为缺省的 AppDomain |
| pDomain->GetSecurityDescriptor()->SetDefaultAppDomainProperty(); |
| m_pDefaultDomain = pDomain; |
| // ... |
| } |
| SystemDomain::NewDom 例后,通知此 AppDomain tupSharedStatics 函数(vm ystem.SharedStatics。这 .Runtime.Remoting.Identi | ain 函数(vmappdomain.cpp:248 其载入的 Assembly;最后会调 appdomain.cpp:4583) 构造并初 个类被用于生成全局唯一的 GUI ty.ProcessIDGuid 以及安全相 | 0)比较简单,构造 AppDomain 实 用 AppDomain::Se 始化一个内部类 S D,在诸如 System 关类型中被用到。 |
| 在 SystemDomain::Att 化 SharedDomain。此 Shar 前写的一篇文章《.Net平台 略,有兴趣的朋友可以仔细 | ach 函数的末尾,会调用 Share edDomain 负责载入 Appdomain- 下CLR程序载入原理分析》刑?br>看看,这儿摘抄一段: | dDomain::Attach 函数构造并初始 neutral 的共享 Assembly。我以 论了载入 Assembly 进行共享的策 |
| 以下为引用: |
| 以下三个参数用于指定配件载入优化策略.我们等会详细讨论. |
| STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN = 0x1 << 1, |
| STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN = 0x2 << 1, |
| STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST = 0x3 << 1, |
| ... |
| CLR在执行一个配件时,会新建一个应 使用到一个配件,就要涉及到前面提到的 STARTUP_LOADER_OPTIMIZATION_SINGLE_D 这样速度最快,管理最方便,但占用内存较 STARTUP_LOADER_OPTIMIZATION_MULTI_DO 等数据时,因为要保证每个应用域有独立 用(使用STARTUP_LOADER_OPTIMIZATION_M Name的配件才会被多个应用域共享. | 用域,将此配件放入新的应用域.如果多个应用域同时 配件载入优化策略了.最简单的方法是使用 OMAIN标志,每个应用域拥有一份独立的配件的镜像, 多.相对的是所有应用域共享一份配件的镜像,(使用 MAIN标志)这样节约内存,但在此配件中存在静态变量 的数据,所以会一定程度上影响效率.折中的方案是使 ULTI_DOMAIN_HOST标志)此时,只有那些有Strong |
| SharedDomain::Attach 函数(vmappd SystemDomain::Attach 类似,其也是在 SharedDomain 对象,并饔?SharedDom 函数(vmappdomain.cpp:6475)则首先调用 Assembly 映射表。 | omain.cpp:6440)的实现比较简单,与 g_pSharedDomainMemory 分配的全局静态内存区构造 ain::Init 函数初始化之。而 SharedDomain::Init 基类的初始化函数 BaseDomain::Init,然后初始化 |
| 在完成 SystemDomain: SystemDomain::Init 函数 | :Attach 函数调用和异常等初始 完成 SystemDomain 的初始化工 | 化工作后,EEStartup 函数会调用 作。 |
| SystemDomain::Init 数;然后获取 Windows 系 库所在 Assembly (mscorli | 函数(vmappdomain.cpp:1074)首 统目录等配置信息;接着分别完 b.dll);构造预分配异常对象; | 先初始化 fusion 系统关闭回调?br>成最重要的三项工作:载入 BCL 构造并初始化全局字符串常量表。 |
| SystemDomain::LoadBa SystemDomain::LoadSystem der::StartupMscorlib 函 初始化工作;最后从 mscor g_pArrayClass等等。 | seSystemClasses 函数(vmappdo Assembly 函数载入 mscorlib.d 数间接调用 g_Mscorlib.Init ( lib 中载入常用的一些类型,如 | main.cpp:1263)首先调用 ll;然后通过 Bin Binder::Init) 完成 mscorlib 的 g_pValueTypeClass、 |
| SystemDomain::Create 刚获取的类型定义,构造预 ackOverflowException 和 堆栈和堆可能已经被破坏或 Framework 2.0 中对此类问 念,确保局部构造的确定性 [2] Whidbey 中的改进》 | PreallocatedExceptions 函数( 分配的三个异常对象:OutOfMem ExecutionEngineException。因 溢出,不能再通过传统的内存分 题更是进一步提出了 CER(Const 等等。有兴趣的朋友可以参考我 | vmappdomain.cpp:1019)则使用刚 oryException、St 为这三种异常被引发的时候,CLR 配方式进行构造。而 .NET rained Execution Regions)等概 另外一篇文章《Finalization |
| 对全局字符串常量表的 以字符串为值的全局 HashM 率等等。有兴趣的朋友可以 | 初始化就比较简单,实际上是初 ap。用于优化字符串性能,保障 参考我另外一篇文章《CLR中字 | 始化了一个以字符串Hash值为键, 跨 AppDomain 字符串传递的高效 符串不变性的优化》。 |
| 至此,CLR 在运行用户程序之前,启 Domain 的流程基本上已经介绍完毕,下 能够在空间和效率上达到最优。 | 动 System Domain、Shared Domain 和 Default 一节将介绍这三者如何搭配使用,使 CLR 在运行时 |