分享
 
 
 

用WinDbg探索CLR世界 [6] AppDomain 的创建过程

王朝system·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

原文:http://www.blogcn.com/User8/flier_lu/index.html?id=3024651

我们知道 CLR 中 Assembly 是在名为 AppDomain 的逻辑空间中被载入运行的,而 AppDomain 是介于操作系统层面进程和线程概念之间,同时具有线程的轻便和进程的封闭性,使用者可以通过 AppDomain.CreateDomain 创建新的 AppDomain。这样一来就出现了一个鸡生单还是蛋生鸡的问题,这个 AppDomain.CreateDomain 方法肯定是要在一个载入了 AppDomain 类型的 AppDomain 里面被调用的,但这个 AppDomain 又是谁调用 AppDomain.CreateDomain 方法创建的呢?呵呵

我们可以使用 WinDbg + SOS 的 EEHeap 命令,通过列出 CLR 执行引擎的堆信息,获取当前运行的 AppDomain 情况。我们以下面这段代码为例

以下内容为程序代码:

//

// AppDomain.cs

//

using System;

public class EntryPoint

{

public static void Main(string[] args)

{

Console.Out.WriteLine("Hello AppDomain!"

;

Console.In.ReadLine();

}

}

这个典型的 CLR 程序的输出如下:

以下为引用:

0:003> !EEHeap

succeeded

Loaded Son of Strike data table version 5 from "E:WINDOWSMicrosoft.NETFrameworkv1.1.4322mscorwks.dll"

Loader Heap:

--------------------------------------

System Domain: 793e6fc8

LowFrequencyHeap:00960000(2000:00001000)

Size: 0x00001000(4096) bytes.

HighFrequencyHeap:00962000(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:00992000(8000:00001000)

Size: 0x00001000(4096) bytes.

StubHeap:0099a000(2000:00001000)

Size: 0x00001000(4096) bytes.

Total size: 0xb000(45056)bytes

--------------------------------------

Domain 0: 147330

LowFrequencyHeap:00970000(2000) 06c60000(10000:00004000)

Size: 0x00006000(24576) bytes.

HighFrequencyHeap:00972000(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(110592)bytes

=======================================

generation 0 starts at 0x04aa1040

generation 1 starts at 0x04aa1034

generation 2 starts at 0x04aa1028

segment begin allocated size

04aa0000 04aa1028 04aa4000 00002fd8(12248)

Large object heap starts at 0x05aa1028

segment begin allocated size

05aa0000 05aa1028 05aa6000 0x00004fd8(20440)

Total Size 0x7fb0(32688)

------------------------------

GC Heap Size 0x7fb0(32688)

我们可以看到,虽然这个程序非常简单,没有自己创建任何 AppDomain,但实际上 CLR 已经有了三个 AppDomain:"System Domain", "Shared Domain" 和 "Domain 0"。而进一步使用 DumpDomain 命令查看三个 AppDomain:

以下为引用:

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 Domain 实际上是专门用于载入 mscorlib.dll 这个 BCL 基础库的;Shared Domain 暂时没有使用;而 Domain 0 则负责运行我们的目标 Assembly。我们可以猜测 System Domain 是 CLR 专门用来载入系统基础库的,而系统将进一步使用此 mscorlib 创建其他 AppDomain 以运行用户目标 Assembly。我们接下来看看 Rotor 的相关代码,是否能够予以印证。

在 CLR 启动时负责加载执行引擎的 EEStartup 函数(vmceemain.cpp:206)中,可以发现此函数首先在进行基础性初始化工作后,调用 SystemDomain::Attach 函数载入 SystemDomain,然后加载并初始化异常处理、JITer等等支持代码,最后会调用 SystemDomain::Init 函数完成初始化 SystemDomain 等等工作。

SystemDomain::Attach 函数(vmappdomain.cpp:912)主要完成四部分工作:初始化系统 stub 管理器和 SystemDomain 的静态成员变量;以全局静态数组 g_pSystemDomainMemory 的内存区,构造并初始化 SystemDomain 对象,并将指针保存到 m_pSystemDomain 静态变量中,用于以后判断 SystemDomain 是否被构造等功能使用;构造缺省的 AppDomain;构造 SharedDomain。函数的简要功能代码如下:

以下内容为程序代码:

SystemDomain* SystemDomain::m_pSystemDomain = NULL;

static BYTE g_pSystemDomainMemory[sizeof(SystemDomain)];

HRESULT SystemDomain::Attach()

{

// 判断 SystemDomain 是否已经构造

_ASSERTE(m_pSystemDomain == NULL);

if(m_pSystemDomain != NULL)

return COR_E_EXECUTIONENGINE;

// 初始化系统 stub 管理器和 SystemDomain 的静态成员变量

// ...

// 构造 SystemDomain 对象

m_pSystemDomain = new (&g_pSystemDomainMemory) SystemDomain();

if(m_pSystemDomain == NULL) return COR_E_OUTOFMEMORY;

// 初始化 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;

}

值得注意的是,为了让 SystemDomain 的构造不会失败,SystemDomain 及其基类 BaseDomain 的构造函数都为空,而初始化代码放到 Init 方法中完成,CLR 中很多类型的代码都使用类似的模式将构造和初始化分离以保障构造成功。BaseDomain::Init 函数在 SystemDomain::Attach 中直接被调用以初始化 SystemDomain 的父类;SystemDomain::Init 函数则在上面提到的 EEStartup 函数末尾才被调用,待会再详细讨论。

BaseDomain::Init 函数(vmappdomain.cpp:310)除了要负责初始化 BaseDomain 对象的一大堆成员变量外,主要负担堆和缓存的初始化。CLR 中的堆,实际上是在每个 AppDomain 中存在的,这也是为什么我们刚刚可以使用 EEHeap 命令列举 AppDomain 的原因。在初始化 BaseDomain 之后,会将 SystemDomain 的接口 VTable 映射表设置为共享,这是因为 SystemDomain 负责载入的 mscorlib 中类型实际上是所以 AppDomain 中都需要使用到的。

接着 SystemDomain::Attach 会调用 SystemDomain::CreateDefaultDomain 函数(vmappdomain.cpp:2522)构造缺省的 AppDomain,也就是前面试验中的 "Domain 0",用作载入用户指定 Assembly 执行。此函数只是简单地调用 SystemDomain::NewDomain 函数以非 Managed 方式构造新的 AppDomain 实例;然后将此 AppDomain 设置为缺省的 AppDomain。

以下内容为程序代码:

HRESULT SystemDomain::CreateDefaultDomain()

{

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::NewDomain 函数(vmappdomain.cpp:2480)比较简单,构造 AppDomain 实例后,通知此 AppDomain 其载入的 Assembly;最后会调用 AppDomain::SetupSharedStatics 函数(vmappdomain.cpp:4583) 构造并初始化一个内部类 System.SharedStatics。这个类被用于生成全局唯一的 GUID,在诸如 System.Runtime.Remoting.Identity.ProcessIDGuid 以及安全相关类型中被用到。

在 SystemDomain::Attach 函数的末尾,会调用 SharedDomain::Attach 函数构造并初始化 SharedDomain。此 SharedDomain 负责载入 Appdomain-neutral 的共享 Assembly。我以前写的一篇文章《.Net平台下CLR程序载入原理分析》中讨论了载入 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_DOMAIN标志,每个应用域拥有一份独立的配件的镜像,这样速度最快,管理最方便,但占用内存较多.相对的是所有应用域共享一份配件的镜像,(使用STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN标志)这样节约内存,但在此配件中存在静态变量等数据时,因为要保证每个应用域有独立的数据,所以会一定程度上影响效率.折中的方案是使用(使用STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST标志)此时,只有那些有Strong Name的配件才会被多个应用域共享.

SharedDomain::Attach 函数(vmappdomain.cpp:6440)的实现比较简单,与 SystemDomain::Attach 类似,其也是在 g_pSharedDomainMemory 分配的全局静态内存区构造 SharedDomain 对象,并调用 SharedDomain::Init 函数初始化之。而 SharedDomain::Init 函数(vmappdomain.cpp:6475)则首先调用基类的初始化函数 BaseDomain::Init,然后初始化 Assembly 映射表。

在完成 SystemDomain::Attach 函数调用和异常等初始化工作后,EEStartup 函数会调用 SystemDomain::Init 函数完成 SystemDomain 的初始化工作。

SystemDomain::Init 函数(vmappdomain.cpp:1074)首先初始化 fusion 系统关闭回调函数;然后获取 Windows 系统目录等配置信息;接着分别完成最重要的三项工作:载入 BCL 库所在 Assembly (mscorlib.dll);构造预分配异常对象;构造并初始化全局字符串常量表。

SystemDomain::LoadBaseSystemClasses 函数(vmappdomain.cpp:1263)首先调用 SystemDomain::LoadSystemAssembly 函数载入 mscorlib.dll;然后通过 Binder::StartupMscorlib 函数间接调用 g_Mscorlib.Init (Binder::Init) 完成 mscorlib 的初始化工作;最后从 mscorlib 中载入常用的一些类型,如g_pValueTypeClass、g_pArrayClass等等。

SystemDomain::CreatePreallocatedExceptions 函数(vmappdomain.cpp:1019)则使用刚刚获取的类型定义,构造预分配的三个异常对象:OutOfMemoryException、StackOverflowException 和 ExecutionEngineException。因为这三种异常被引发的时候,CLR 堆栈和堆可能已经被破坏或溢出,不能再通过传统的内存分配方式进行构造。而 .NET Framework 2.0 中对此类问题更是进一步提出了 CER(Constrained Execution Regions)等概念,确保局部构造的确定性等等。有兴趣的朋友可以参考我另外一篇文章《Finalization [2] Whidbey 中的改进》

对全局字符串常量表的初始化就比较简单,实际上是初始化了一个以字符串Hash值为键,以字符串为值的全局 HashMap。用于优化字符串性能,保障跨 AppDomain 字符串传递的高效率等等。有兴趣的朋友可以参考我另外一篇文章《CLR中字符串不变性的优化》

至此,CLR 在运行用户程序之前,启动 System Domain、Shared Domain 和 Default Domain 的流程基本上已经介绍完毕,下一节将介绍这三者如何搭配使用,使 CLR 在运行时能够在空间和效率上达到最优。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有