程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> [你必須知道的.NET]第二十六回:認識元數據和IL(下)

[你必須知道的.NET]第二十六回:認識元數據和IL(下)

編輯:關於.NET

書接上回:

第二十四回:認識元數據和IL(上) , 第二十五回:認識元數據和IL(中)

我們繼續。

終於到了,說說元數據和IL在JIT編譯時的角色了,雖然兩個回合的鋪墊未免鋪張,但是卻絲毫不為過,因為只有充分的認知才有足夠的體會,技術也是如此。那麼,我們就開始沿著方法調用的軌跡,追隨元數據和IL在那個神秘瞬間所貢獻的力量吧

5 元數據和IL在JIT編譯時

CLR最終執行的只有本地機器碼,所以JIT編譯的作用是在運行時將IL代碼解析為機器碼執行。對於JIT編譯,我們會以專門的篇幅來全面了解,本文只將目光關注於元數據和IL在程序執行時的作用和參與細節。首先,IL是基於棧執行的,執行方法調用時,方法參數、局部變量還有返回值等被分配於棧上,並執行其調用過程,既然是關注JIT編譯時,因此我們自然而然將關注方法的執行,因為JIT編譯是以執行方法調用而觸發的。

首先,對本文開始的代碼加點新料:

// Release : code04, 2009/02/24
// Author : Anytao, http://www.anytao.com
// List  : Base.cs
public class Base
{
   public void M()
   {
     Console.WriteLine("M in Base");
   }

   public virtual void N()
   {
     Console.WriteLine("N in Base");
   }
}

還有:

// Release : code05, 2009/02/24
// Author : Anytao, http://www.anytao.com
// List  : Three.cs
public class Three : Base
{
   private static int ID { get; set; }

   public override void N()
   {
     //Something new in Three
     Console.WriteLine("N in Three");
   }

   public void M()
   {
     Console.WriteLine("M in Three");

     M1();
   }

   public void M1()
   {
     Console.WriteLine("M1 in Three");
   }
}

還有執行代碼:

static void Main(string[] args)
{
   Base three = new Three();
   three.M();
   three.N();
}

小窺方法表

以該例而言,執行Main方法調用時,同時伴隨著對於Three實例的創建,和相應類型信息的加載。我們先將類型信息創建的秘密放在以後的內容中,好留點懸念在未來發揮,哈哈。然而,類型加載一定是在實例創建之前完成的,也就是我們常常提起的方法表創建。類型加載是由class loader負責執行的,其過程簡言之就是從元數據表中獲取相應的類型信息,創建方法表(包含CORINFO_CLASS_STRUCT結構),其結構主要包括非虛方法表和虛方法表,按照繼承的虛方法、新引入的虛方法、實例方法和靜態方法的順序排列,以類Three類型為例其CORINFO_CLASS_STRUCT結構可以表示為:

Note: 在本例中Three沒有定義任何靜態方法,其方法表中父類方法N已有子類覆寫,同時因為有靜態成員存在的原因,CLR會自動創建類型構造器,詳細情況可參考《你必須知道的.NET》1.2節 “什麼是繼承”。

我們可以同過加載SOS調試來了解相應的方法表信息:

在three.N()調用處打好斷點,來查看該時刻的dump信息,就像一個內存快照,發現多少東西就看攝影師的水准。

然後,通過dumpheap加載類型信息,獲取方法表地址(0x002a354c),

!dumpheap -type Three
Address    MT   Size
01d332c4 002a354c    12
total 1 objects
Statistics:
    MT  Count  TotalSize Class Name
002a354c    1      12 Anytao.Insidenet.MetadataIL.Three

並根據MT地址,以dumpmt查看相關的MethodDesc信息,

!dumpmt -md 002a354c
EEClass: 002a15b4
Module: 002a2f2c
Name: Anytao.Insidenet.MetadataIL.Three
mdToken: 02000003 (E:\anytao\Today\OnWriting\MetadataIL\Anytao.Insidenet.MetadataIL\Anytao.Insidenet.MetadataIL\bin\Debug\Anytao.Insidenet.MetadataIL.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 10
--------------------------------------
MethodDesc Table
   Entry MethodDesc   JIT Name
6f756a70  6f5d1328  PreJIT System.Object.ToString()
6f756a90  6f5d1330  PreJIT System.Object.Equals(System.Object)
6f756b00  6f5d1360  PreJIT System.Object.GetHashCode()
6f7c7460  6f5d1384  PreJIT System.Object.Finalize()
002ac0b8  002a3514   NONE Anytao.Insidenet.MetadataIL.Three.N()
002ac0d0  002a3540   JIT Anytao.Insidenet.MetadataIL.Three..ctor()
002ac0a8  002a34f4   NONE Anytao.Insidenet.MetadataIL.Three.get_ID()
002ac0b0  002a3504   NONE Anytao.Insidenet.MetadataIL.Three.set_ID(Int32)
002ac0c0  002a3520   NONE Anytao.Insidenet.MetadataIL.Three.M()
002ac0c8  002a3530   NONE Anytao.Insidenet.MetadataIL.Three.M1()

經過簡單的Dump,方法表的信息和我們圖示的信息相差無幾,細心的觀眾可能會發現Dump信息中並不包含Three::cctor(),那麼你答對了。圖示的cctor是我基於為Three實現了類型構造器(靜態構造函數)而特別加入的,而代碼中dump的方法表並沒有把類型構造器包含在內,這是個小粗心,希望細心的您看得夠透。

執行細則

具體的執行過程為為:

class loader從TypeDef元數據表加載相關元數據信息,包括當前類型,繼承層次的所有父類和實現的接口元數據,根據這些信息建立CORINFO_CLASS_STRUCT結構:

當然,對class loader,我們可以進行一點知識救急:

上課啦:class loader

Classic Loader是CLR提供的基本組件之一,作用正像其名稱所宣揚的那樣,load一個Class給CLR,class loader將Metadata和IL從PE文件中取出,並加載到運行時內存,簡單的說就是我們下面要介紹的全過程縮影。

當然,如果你總是對CLR的Classic Loader耿耿於懷,不能釋然。那麼,我們也可以參考MSDN的資料來實現自定義的Classic Loader[How to: Write a Class Loader],希望其中能提供靈光一現的思考。

加載之後,方法執行之前的CORINFO_CLASS_STRUCT中所有的方法表槽都保存了方法應該執行的行為邏輯,這些信息保存在被稱為方法描述(MethodsDesc)的結構中,而MethodDesc則被初始化為指向IL代碼,同時還包含一個指向觸發JIT編譯的PreJitStub地址,如下:

上述所有方法描述都指向各自的IL代碼地址和JIT編譯器,在此我們僅僅以N()方法為例來進行說明,詳細的情況可以參考MSDN相關內容。

簡單的說,任何方法第一次執行時都會首先觸發執行JIT編譯,JIT的主要工作就是將IL代碼翻譯為Native Code,並插入指向Native Code的jmp指令地址覆蓋原來的Call JIT Compiler指令:

當該方法再次被執行時,因為MethodDesc中保存了機器碼地址,以後的執行將不會執行JIT編譯過程而直接執行x86(X64)機器碼,實現整個執行過程。

縱觀整個JIT編譯的全過程,其細節的實現遠比我們這裡呈現的復雜,在粗略的步驟中我們大致了解了元數據和IL在整個過程中的作用、角色和關系,對了解CLR運行機制而言,適當的選擇是明智的,如果有更多的心思探索,那麼就在以後的歲月中由簡及繁吧,但是相信這一定是一次美妙的旅程。

6 結論

Metadata描述了靜態的結構,而IL闡釋了動態的執行,這一靜一動承載了太多的技術奧秘。

當這篇文章行將結束的時候,我發現牽一發而動全身,由此引入的新問題接踵而至,方法調用、程序集、程序域、CLR加載過程在元數據和IL的分析中若隱若現,也驅使我投入注意在後面的《你必須知道的.NET》中,將這些內容一一過招。由此才能在復雜的概念和本質之余,由點及面的對所有內容綜合把握,形成全面的了解和一條線貫穿的認識,那麼未來Anytao將要繼續分享還有:

系列預告

程序集和模塊

程序域

CLR加載過程

JIT編譯

方法調用

反射種種

其他…

限於繁忙的原因,我無法給出一個清晰的時間表,但力圖每次的內容都給您出足夠的收獲,如果你對.NET始終心懷興致,那麼敬請期待《你必須知道的.NET》更多精彩。

出處:http://anytao.cnblogs.com/

本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved