在CLR的世界中,有一系列的令人Amazing的技術和架構。其中,CLR對應用程式在記憶體中記憶體配置,執行模型,程式之間的互動等一系列的技術,值得每一個致力於DotNet平台的技術人員深究。
編程人員在開發的過程中,如果把程式集的載入(Assemblies Load),反射(Reflection),寄宿(Hosting),應用程式定義域(AppDomain),這四種技術結合起來使用的話,不僅能更好的使用CLR這個平台提供的強大的功能,而且能夠構建更安全,更健壯的應用程式代碼。
這篇博文裡,就是使用Managed 程式碼的動態調試工具,來研究一下CLR內部AppDomain的世界。
首先,從一個C#程式開始:
class Program
{
static void Main(string[] args)
{
Program b = new Program();
b.test();
System.Console.ReadLine();
}
public void test()
{
int i = 67;
System.Console.WriteLine((char)i);
System.Console.WriteLine((char)67);
i = 1;
}
}
運行了這個應用程式以後,我們開啟windbg,attach到這個主控處理序。
.load SOS
載入SOS擴充調試模組,可以使用.chain指令查看載入是否正確。
0:003> lm
start end module name
00400000 00408000 TestConcoleApp (deferred)
76990000 76acd000 ole32 (deferred)
77be0000 77c38000 msvcrt (deferred)
省略若干
77fc0000 77fd1000 Secur32 (deferred)
78130000 781cb000 MSVCR80 (deferred)
79000000 79045000 mscoree (deferred)
79060000 790b3000 mscorjit (deferred)
790c0000 79b90000 mscorlib_ni (deferred)
79e70000 7a3d6000 mscorwks (deferred)
查看下意境載入了的模組,然後使用ld命令把我們的偵錯符號檔案載入。VS在編譯產生一個Console App的時候,在debug模式的時候會在debug的bin目錄下產生一個和應用程式同名的pdb檔案。我們要做的,就是載入這個檔案:
0:003> ld TestConcoleApp
*** WARNING: Unable to verify checksum for G:\Projects\TestConcoleApp\TestConcoleApp\bin\Debug\TestConcoleApp.exe
Symbols loaded for TestConcoleApp
有一個警告,咋這裡先不管,再使用lm查看已經載入了的模組的時候,可以看到這個module的偵錯符號檔案已經被載入上了。
此時,我們可以查看下Excute Engine (CLR)的堆裡面都有些什麼東西,我們可以使用!EEHeap命令,EE的意思,就是CLI的執行引擎,也就是我們常說的CLR。這個命令可以查看到一個主控處理序裡面的garbage-collected 和 Loader heaps相關資訊。
0:003> !eeheap
PDB symbol for mscorwks.dll not loaded
Loader Heap:
--------------------------------------
System Domain: 7a38f918
LowFrequencyHeap: Size: 0x0(0)bytes.
HighFrequencyHeap: 00a62000(8000:1000) Size: 0x1000(4096)bytes.
StubHeap: 00a6a000(2000:1000) Size: 0x1000(4096)bytes.
Virtual Call Stub Heap:
IndcellHeap: Size: 0x0(0)bytes.
LookupHeap: Size: 0x0(0)bytes.
ResolveHeap: Size: 0x0(0)bytes.
DispatchHeap: Size: 0x0(0)bytes.
CacheEntryHeap: Size: 0x0(0)bytes.
Total size: 0x2000(8192)bytes
/**********************************************
Loader Heap 中的系統域。這個域和下面的Shared Domian一起,是對託管的宿主程式,以及Managed 程式碼不可見的。這個域載入了兩個CLR執行中十分重要的Module,MSCorEE.dll和MScorwks.dll。MSCorEE.dll這個檔案就是大家熟悉的shim,墊片。在CLR載入中起到的重要作用。這裡就不分析了,大家可以參考別的文獻的介紹。
對於每個應用程式定義域,都會有自己的安全性描述元,安全上下文以及預設的上下文。這三個部分可以支援一個應用程式定義域來自訂一個單獨實施的安全性原則,譬如,可以用來確保宿主程式在載入Managed 程式碼的時候不會對這些重要的資料結構造成破壞。
**********************************************/
--------------------------------------
Shared Domain: 7a38fef0
LowFrequencyHeap: 00a90000(2000:1000) Size: 0x1000(4096)bytes.
HighFrequencyHeap: Size: 0x0(0)bytes.
StubHeap: 00a9a000(2000:1000) Size: 0x1000(4096)bytes.
Virtual Call Stub Heap:
IndcellHeap: Size: 0x0(0)bytes.
LookupHeap: Size: 0x0(0)bytes.
ResolveHeap: 00aab000(5000:1000) Size: 0x1000(4096)bytes.
DispatchHeap: 00aa7000(4000:1000) Size: 0x1000(4096)bytes.
CacheEntryHeap: Size: 0x0(0)bytes.
Total size: 0x4000(16384)bytes
/**********************************************
在共用域中,載入所有的應用程式定義域中都要使用到的assemblies,譬如MScorlib.dll。裝載了System.Object,System.ValueType這樣的基礎類。
**********************************************/
--------------------------------------
Domain 1: 154250
LowFrequencyHeap: 00a70000(2000:2000) Size: 0x2000(8192)bytes.
HighFrequencyHeap: 00a72000(8000:2000) Size: 0x2000(8192)bytes.
StubHeap: Size: 0x0(0)bytes.
Virtual Call Stub Heap:
IndcellHeap: Size: 0x0(0)bytes.
LookupHeap: Size: 0x0(0)bytes.
ResolveHeap: Size: 0x0(0)bytes.
DispatchHeap: Size: 0x0(0)bytes.
CacheEntryHeap: Size: 0x0(0)bytes.
Total size: 0x4000(16384)bytes
/**********************************************
對於特定的寄宿程式,可以根據需要建立多個應用的預設域。例如IE,Asp.Net,或者是SqlServer,可以建立一個或者是多個預設的域。網域名稱預設情況下的name就是module的名稱。
在預設域中,應用程式執行的時候需要裝載經來的assemblies可以被載入到這裡。
在每個應用程式定義域中,代碼建立的對象不能直接存取另外的應用程式定義域中的代碼。如果要訪問這些代碼,可以採用靜態委託,或者是appDomain的自己的方法來實現。
**********************************************/
--------------------------------------
Jit code heap:
LoaderCodeHeap: 00db0000(10000:1000) Size: 0x1000(4096)bytes.
Total size: 0x1000(4096)bytes
對於託管的應用程式,有兩種把IL代碼編譯成本地代碼的方式。一種是在第一次啟動並執行時候,調用JIT模組來Just-In-Time 編譯,編譯好了的本地代碼就放到這裡。在PE檔案中的相應的代碼處,就用一個指標指引CLR到這裡來找相關的編譯好了的本地代碼。第二中是安裝的時候就編譯成為本地代碼。
同時可以看到,Jit Heap佔用很少的記憶體空間。
--------------------------------------
Module Thunk heaps:
Module 790c2000: Size: 0x0(0)bytes.
Module 00a72c24: Size: 0x0(0)bytes.
Total size: 0x0(0)bytes
--------------------------------------
Module Lookup Table heaps:
Module 790c2000: Size: 0x0(0)bytes.
Module 00a72c24: Size: 0x0(0)bytes.
Total size: 0x0(0)bytes
--------------------------------------
Total LoaderHeap size: 0xb000(45056)bytes
總共的loader Heap的大小大概在45kb左右。
=======================================
Number of GC Heaps: 1
generation 0 starts at 0x013b1018
generation 1 starts at 0x013b100c
generation 2 starts at 0x013b1000
ephemeral segment allocation context: none
下面的這兩個segment對於應用程式定義域的其他代碼來說是read only的,所以,這兩部分的空間比較小。這塊經常儲存的是小的segement片段。除非你是很長很長的字串。而大的object,則保持在LOH中。GC Heap,可以有多個。每個GC Heap中,都可以有一個LOH。
而每個GC Heap的總大小=Segment佔用的空間+LOH
segment begin allocated size
0014d720 790d5588 790f4b38 0x0001f5b0(128432)
013b0000 013b1000 013b3ff4 0x00002ff4(12276)
Large object heap starts at 0x023b1000
segment begin allocated size
023b0000 023b1000 023b3250 0x00002250(8784)
Total Size 0x247f4(149492)
------------------------------
GC Heap Size 0x247f4(149492)
總共的GC堆大概150kb。
---------------------------------------------------------------------------------------------------------
託管線程的記憶體結構:
這裡,簡單的交代一下一個主控處理序的記憶體結構。在建立了一個託管的應用程式的線程以後,首先預設情況下建立了最少3個應用程式定義域,就是系統域,共用域,和預設域。前兩個對託管的使用者代碼來說是不可見的。當時,可以調用共用與中的assemblies。使用者的Managed 程式碼,和模組被load到預設域中。一個託管的宿主可以根據需要建立一個或者是多個預設域。
主控處理序,線程(hard thread,soft thread),應用程式定義域,程式集,模組的關係
主控處理序,應用程式定義域,程式集,模組,這四個概念從左至右是一對多的關係。及一個託管可以對應多個應用程式定義域,一個appdomain可以對應多個assemblies,一個assembly可以對應多個modules。modules就是我們經常看的.exe或者是.dll。exe檔案是windows下對PE檔案格式擴充了的託管模組。modules也可以是託管的動態連結程式庫檔案。
對於線程,情況有點特殊。這裡,首先要區別一個概念,作業系統的進程建立的線程和System.Threading.Thread這個類表示的線程。這裡,把作業系統建立的進程叫做hard thread,System.Threading.Thread這個類表示的線程叫做soft Thread。hard Thread和應用程式定義域的關係,是多堆多的關係。就是一個應用程式定義域中可以存在多個hard Thread。而一個hard Thread,也可以存在於多個應用程式定義域裡面。而soft Thread,是由應用程式定義域中的assemblies建立,所以,它只存在於相應的應用程式定義域中。
當一個系統的hard thread進入某個應用程式定義域中進行操作的時候,這個應用程式定義域就會執行個體化一個System.Threading.Thread類來完成這個線程對應的工作。
應用程式定義域的環境變數屬性:
對於每個應用程式定義域,有一系列的Environment屬性可以設定,通過設定這些屬性,可以配置一個應用程式定義域的特性,來滿足各種對於安全,效能等許多方面特別的需求。
可以參考MSDN的這裡:
http://msdn2.microsoft.com/en-us/library/system.appdomain_properties.aspx
獲知應用程式定義域相關的所有的Properties。
特別說明:應用程式定義域的動態目錄:
對於應用程式定義域的所有的屬性,需要特別提到一個叫做DynamicDirectory 的屬性。
這個屬性的產生,有兩個部分,譬如我本機上面的一個asp.net宿主進程的快取檔案夾:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\mesapplication\8a8504fd\e1680364
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\mesapplication這一部分,由一個叫做DYNAMIC_BASE的屬性來定義。同時包含了這個應用程式定義域中載入的專案檔資訊。
後面的兩部分8a8504fd\e1680364,根據專案檔的不同,由應用程式的APP_NAME這個屬性來決定。
這樣何在一起,就構成了在調試的過程中,進程在原生快取檔案夾。
在接下來的下篇中,講從如何?的角度,以執行個體原始碼來分析AppDomain的運行機制。
謹以此文攢人品,望能抽籤時人品爆發。