CLR執行模型


前言   《CLR via C#》(Jeffrey Richter著)——.NET 界的經典之作,相讀"恨晚",讀的過程寫點筆記跟大家分享:

         【我也推薦大家看英文版,能夠直接領會原意


認識CLR

一 個被多種編程語言使用的運行時。核心功能包括:內存管理,程序集加載,安全性,異常處理,以及線程同步。這些核心功能能夠被所有以它作為目標平台的語言使 用,實際上,在運行時,CLR並不關心程序員使用哪一種語言編寫源碼的。微軟開發了很多以CLR作為目標平台的語言編譯器, 如:C++/CLI,C#,VB,F#,Iron Python,Iron Ruby,以及IL匯編。另外還有很多其他的公司,學校開發了相應的編譯器,如:Ada,APL,Caml,COBOL,Fortran,Lua等等。下 面的圖展示了編譯源文件的過程:

由 圖可知,我們不用去考慮使用什么編譯器,因為結果都是托管模塊。托管模塊是標准的PE32/32+文件(微軟Windows可移植可執行文件,PE32表 示32位,32+表示64位),它需要CLR執行。托管程序集一直采用了數據執行保護(Data Execution Prevention,DEP)和在Windows下的地址空間布局隨機化(ASLR),這兩個功能提升了整個系統的安全性。

托管模塊的組成部分

1.PE32/PE32+ header:標准的PE文件頭,類似通用對象文件格式頭(Common Object File Format,COFF).
                                  如果文件頭是PE32格式:可以運行在32位/64位Windows系統。
                                  PE32+格式:運行在64位系統。該文件頭還指明了文件類型,如:GUI, CUI,  DLL並且包含文件創建的時間戳。
                                  對於僅僅包含IL代碼的模塊這些在PE32(+)文件頭里面的信息會被忽略。對於包含本地CPU代碼,
                                  該文件頭包含本地CPU代碼的信息
2.CLR header:包含托管模塊的信息:需要CLR的版本,一些標志信息,MethodDef元數據(獲取托管模塊入口方法(Main方法)),以及模塊的元素據,
                      資源,強命名,一些標志的位置/尺寸
3.元數據:每一個托管模塊包含元數據表。有兩種主要的類型:1.描述在源碼中定義成員的類型。2.描述在源碼中引入的成員類型

4.IL 代碼:編譯器編譯源碼產生的代碼,在運行時,CLR將IL編譯為本地CPU指令

本地代碼編譯器會以具體的CPU架構為目標產生代碼,比如X86,X64,或IA64。所有符合CLR的編譯器都會編譯源碼生成IL代碼,也稱為托管代碼,因為由CLR管理IL的執行。

什么是元數據?

簡言之,元數據是描述在模塊里面定義了什么的這樣數據表集合,比如成員的類型,比如引用了其他的什么類型或成員。

元數據一直作為代碼嵌入在同名的EXE/DLL文件里面,使得它們不可能分開。因為編譯器是同時生成元數據和IL代碼並綁定到托管模塊。

元數據的用途

1.編譯不需要依賴於原生C/C++頭及庫文件,因為可以直接從托管模塊讀取元數據

2.智能提示

3.CLR用元數據進行代碼審核,確保類型安全

4.允許序列化一個對象的字段為一個內存塊,發送到其他機器,然后被反序列化,重建對象狀態。

5.允許GC跟蹤對象的生命周期

 

認識程序集

CLR不直接運行模塊,而是程序集。程序集是一個抽象的概念:

1.程序集是一個或多個模塊或資源文件的邏輯分組。

2.程序集是重用,安全性,版本控制的最小單元

根據我們使用的編譯器或工具,可以生成一個或多個文件的程序集。在CLR世界里,一個程序集就是我們通常所說的組件。

下面的圖具體說明什么是程序集:

一些托管模塊和資源文件通過一個工具進行處理,然后生成一個代表文件邏輯分組的PE32(+)文件。這個PE32(+)文件包含了數據塊——稱為清單,該清單另一種簡單元數據集表,這些表描述了組成程序集的文件。

默認情況下,編譯器實際做的工作就是將托管模塊轉換為程序集,也就是說編譯器會生成一個包含清單的托管模塊。對於只有一個托管模塊沒有資源或數據文件的項目而言,程序集就是托管模塊,創建的過程中也不需要額外的步驟。如果想把文件組合到程序集中,很多工具能夠實現。

程序集允許我們可重用的,安全的,版本控制組件的邏輯和物理概念分離開來。至於怎么分離代碼跟資源文件完全取決於自己。一個程序集的模塊可以包含引用的另外的程序集的信息(版本號等)——程序集的自我描述,換句話說,CLR可以決定程序集執行的直接依賴的順序。  

運行一個可執行文件需要做的工作?

如果一個非托管的程序調用LoadLibrary載入一個托管程序集,Windows會加載並初始化CLR(如果沒有加載),當然前提是進程已經啟動。

什么是LoadLibrary?

1.僅僅在桌面應用程序才有   2.載入指定的模塊到調用進程的地址空間。指定的模塊可能引起其他的模塊也被載入

 

認識IL

托管程序集包含元數據和IL。IL是一種獨立CPU的機器語言,它是微軟在咨詢了很多商業和學術語言/編譯器的作者后創造的。

IL比大多數CPU機器語言高級。Why?

1.IL可以訪問並操作對象的類型    2.IL具有創建和初始化對象的指令   3.IL可以調用對象的虛方法   4.IL可以直接操作數組元素  

5.IL具有處理錯誤異常的指令

可以認為IL是一種面向對象的機器語言

通常我們使用向C#這樣的高級語言開發,編譯器會生成IL。像其他的機器語言一樣,IL可以使用匯編語言編寫,微軟提供了IL匯編語言編譯器——ILAsm.exe,
以及反匯編編譯器——ILDasm.exe

不同於C#僅僅暴露CLR提供的功能的一個子集,IL匯編語言運行開發者訪問所有CLR的功能。所以這里更新一個誤區:不要以為C#提供給我們的功能就是CLR的全部功能。

了解CLR的JIT(即時編譯)

下圖展示當方法首次調用時發生的事情

在 Main方法執行之前,CLR會檢測被Main方法引用的所有類型並且分配一個管理訪問引用的類型的內部數據結構。上面例子Main方法中引用了 Console,所以CLR會分配一個內部的數據結構。這個數據結構包含了定義在Console類里面的每一個方法的入口,每一個入口保存的方法可以找到對應方法的地址。當這個內部結構初始化時,CLR設置每一個入口到一個內部的,未公開的CLR里面的函數,這個函數稱為JITCompiler。

了解JITCompiler函數

當 Main方法使Console第一次調用WriteLine方法時,JITCompiler函數會被調用。JITCompiler負責將一個方法的IL代 碼編譯為本地CPU指令,因為IL就是在"即時編譯"時被編譯的,CLR的這個組件通常被稱為JITer或JIT compiler。

當調用時,JITCompiler知道哪一個方法被調用並且定義在方法里面的類型是什么。

1.接着JITCompiler函數搜索調用方法的IL定義在程序集的元數據

2.再接下來就是審核並編譯該IL為本地CPU指令。本地CPU指令會保存在一個動態分配的內存塊里面

3.然后,JITCompiler回到CLR創建的內部數據結構的該方法的入口處,用編譯成的CPU指令保存的內存地址替換掉開始時推該方法的引用

4.最后,JITCompiler函數跳轉到放在內存里面的代碼處,該代碼實現了WriteLine方法(重載獲取一個string參數的)。當這個方法返回時回到Main並繼續往下執行。

往下執行,Main方法會第二次調用WriteLine方法,這一次,WriteLine的IL代碼已經被審核和編譯過了,不會調用 JITCompiler,而是直接跳轉到內存里存放該代碼的地方,在WriteLine方法執行完成返回Main。下圖展示了第二次調用 WriteLine方法的過程:

我知道很多朋友都必備了這本書,希望路過的朋友多留言或討論,或指正。

注   《CLR via C#》(Jeffrey Richter著)——.NET 界的經典之作,讀的過程寫點筆記跟大家分享,我也推薦大家看英文版,能夠直接領會原意 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM