[你必须知道的.NET]第二十六回:认识元数据和IL(下)
| 說在,開篇之前 |
| 書接上回:? 第二十四回:認識元數據和IL(上),?第二十五回:認識元數據和IL(中)? 我們繼續。? 終于到了,說說元數據和IL在JIT編譯時的角色了,雖然兩個回合的鋪墊未免鋪張,但是卻絲毫不為過,因為只有充分的認知才有足夠的體會,技術也是如此。那么,我們就開始沿著方法調用的軌跡,追隨元數據和IL在那個神秘瞬間所貢獻的力量吧。?? ??????????????????????????????????????????????????????????????????????????????????????www.anytao.com |
?
?
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 ThreeConsole.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),
- 并根據MT地址,以dumpmt查看相關的MethodDesc信息,
經過簡單的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],希望其中能提供靈光一現的思考。 ??????????????????????????????????????????????????????????????????????????????????????www.anytao.com |
?
- 加載之后,方法執行之前的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將要繼續分享還有:
| 系列預告 |
??????????????????????????????????????????????????????????????????????????????????????www.anytao.com |
限于繁忙的原因,我無法給出一個清晰的時間表,但力圖每次的內容都給您出足夠的收獲,如果你對.NET始終心懷興致,那么敬請期待《你必須知道的.NET》更多精彩。?
支持anytao的創業產品Worktile
Worktile,新一代簡單好用、體驗極致的團隊協同、項目管理工具,讓你和你的團隊隨時隨地一起工作。完全免費,現在就去了解一下吧。
https://worktile.com
- 《你必須知道的.NET》第3章 “一切從IL開始”
- DonBox,《.NET本質論》
- http://www.sloppycode.net/articles/inside-net-assemblies-and-metadata.aspx
- http://www.codeproject.com/KB/dotnet/dotnetformat.aspx
溫故知新
總結
以上是生活随笔為你收集整理的[你必须知道的.NET]第二十六回:认识元数据和IL(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 父亲节不知道送啥?血压、心电都能测的华为
- 下一篇: 12分钟充满史无前例 iQOO 10 P