菜鳥CLR VIA C#之旅(1):品味細節,CLR的執行模型
從菜鳥剛接觸到.net時,菜鳥就知道CLR VIA C#是一本很牛的書,為什么?CSDN會告訴你——總會有人問“學.net什么書籍好?”,這個本沒有標準答案的問題,菜鳥卻從各種大牛一致的回答中找到了標準答案:C#入門經典—>C#高級編程—>CLR VIA C#,于是乎,對于大牛們的信任,這三本書都躺在菜鳥的床頭。雖然菜鳥很菜,但菜鳥喜歡在CDSN、博客園、codeproject(菜鳥英語不堪忍睹,每次都還需要打開Google翻譯)上閑逛,屬于那種不厚道的看帖不回帖的一員,甚至過了相當長的一段時間都還沒有注冊,不是菜鳥不想回答,而是菜鳥水平實在不堪忍睹,怕誤導人家,更怕關公面前耍大刀。菜鳥發現自己錯了,為什么?你懂的!為此在大三暑假的時候,菜鳥在CSDN上走火入魔似的回答了別人一個月的問題,即時菜鳥不會的(其實大多都不會),菜鳥會去翻書、找度娘、搜谷姐,想盡辦法幫別人回答,一個月后菜鳥的等級竟然有一顆星星了,當時激動的跑到灌水區散了下分,但之后菜鳥又回到了看帖不回帖的狀態,或許是開學了,或許是DOTA去了,或許是沒激情了,反正具體什么原因已記不清楚。
“紙上得來終覺淺,須知此事要躬行”,如果僅僅是看別人的文章或書籍,可能就在看的時候有感覺,合上之后就什么也不記得了,所以菜鳥決定將自己看到的或學到的好東西記錄下來——不再為看不懂高深的技術文章而煩惱,而是拿起《CLR VIA C#》踏踏實實從頭到底邊閱讀邊寫筆記。
在菜鳥上篇菜鳥CLR VIA C#之旅—開始旅行:千里之行始于足下完成后,得到了很多大牛們的支持,在此菜鳥深表感謝,更要謝謝那些提出意見的高手們,志志同學首先發了了菜鳥一個低級的錯誤,“將Hello world寫成了Hello word”,當時馮同學的一句“啥時候出一片 Hello,excel! 啊?”我還疑惑不解,哎,太粗心了。對于 toEverybody的留言:“又要人在研究C#編譯器產生的亂碼呀//唉....”菜鳥不敢茍同,但從這個留言中可以看出,很多的園友都把目光和焦點注意在如何理解IL代碼這個問題上。這真是個莫大的好消息,因為很明顯大家的思路慢慢的從應用向底層發生著轉變,技巧性的東西是一個方面的積累,底層的探索為也是必不可少的修煉(anytao)。
對于.NET程序員來說,IL代碼意味著:
? 通用的語言基礎是.NET運行的基礎,當我們對程序運行的結果有異議的時候,如何透過本質看表面,需要我們從本質入手來探索,這時 IL是你必須知道的基礎;
? 元數據和IL語言是CLR的基礎,了解必要的中間語言是深入認識CLR的捷徑;
? 大量的事例分析是以IL來揭密的,因此了解IL是讀懂他人代碼的必備基礎,可以給自己更多收獲。
很明顯這些優越性足以誘惑我們花時間和精力涉獵其中。然而,了解了IL的好處,并不意味著我們應該過分的來關注IL,有人甚至可以洋洋灑灑的寫一堆IL代碼來實現一個簡單Hello world程序,但是正如我們知道的那樣,程序設計已經走過了幾十年的發展,如果純粹的陶醉在歷史中,除了腦子不好,沒有其他的解釋。不然看見任何代碼都以IL的角度來分析,又將走進另一個誤區,我們的宗旨是追求但不過分。
好了,開始《CLR VIA C# 》第一章 “CLR的執行模型”的學習,了解下應用程序是如何執行的。如果能清楚的了解代碼是如何運行的,菜鳥認為對于以后代碼的調試和優化將起到關鍵作用。
1 將源代碼編譯成托管模塊
只要編譯器是面向CLR的任何語言,其創建的源代碼,都會用一個對應的編譯器來檢查語法和分析源代碼,最終結果都是生成一個托管模塊(managed module)。

托管模塊
托管模塊是一個標準的32位Microsoft Windows可移植執行體(PE32)文件,或者是一個標準的64位Windows可移植執行體(PE32+)文件,它們都需要CLR 才能執行。順便說一句,托管的程序集總是利用了Windows的數據執行保護(Data Execution Prevention,DEP)和地址空間布局隨機化(Address Space Layout Randomization,ASLR);這兩個功能旨在增強整個系統的安全性。托管模塊包括以下幾個部分:

1)PE32或PE32+頭:PE32能在windows32位或64位機器上運行,PE32+只能在64位機器上運行。
2)CLR頭:包含了需要的CLR版本,一些flag,托管模塊入口方法(Main方法)以及各種信息。
3)IL(中間語言)代碼:編譯器編譯源代碼后生成的代碼,在運行的時候,CLR再將IL編譯為本地CPU指令。
4)元數據:每個托管模塊都包含元數據表,有兩種類型,一種類型的表描述源代碼中定義的類型和成員,一種類型的表描述源代碼中引用的類型和成員。
元數據的用途:
? 在編譯時,元數據消除了對頭和庫文件的需求。
? VS的“智能感知功能”就是依靠元數據來解譯一個類型提供了什么方法、屬性、事件和字段。如果是一個方法將是傳入的參數。
? IL的“安全”檢查需要元數據。
? 元數據可以對一個對象的代碼序列化,存入到內存,發生到另一臺機器上。可以在另一臺機器上進行反序列化來重建對象狀態。
? 元數據可以運行垃圾收集器跟蹤對象的生存期。
2 將托管模塊合并成程序集

在這幅圖中,一些托管模塊和資源(或數據)文件準備交由一個工具處理。該工具生成單獨一個PE32(+)文件來表示文件的邏輯性分組。實際發生的事情是,這個PE32(+)文件包含一個名為“清單”(manifest)的數據塊。清單是由元數據表構成的另一種集合。這些表描述了構成程序集的文件,由程序集中的文件實現的公開導出的類型,以及與程序集關聯在一起的資源或數據文件。
程序集的類型
既可以生成單文件程序集,也可以生成多文件程序集,取決于你對于編譯器或工具的選擇。

程序集清單中的內容
|
信息 |
說明 |
|
程序集名稱 |
指定程序集名稱的文本字符串。 |
|
版本號 |
主版本號和次版本號,以及修訂號和內部版本號。 公共語言運行時使用這些編號來強制實施版本策略。 |
|
區域性 |
有關該程序集支持的區域性或語言的信息。 此信息只應用于將一個程序集指定為包含特定區域性或特定語言信息的附屬程序集。 (具有區域性信息的程序集被自動假定為附屬程序集。) |
|
強名稱信息 |
如果已經為程序集提供了一個強名稱,則為來自發行者的公鑰。 |
|
程序集中所有文件的列表 |
在程序集中包含的每一文件的散列及文件名。 請注意,構成程序集的所有文件所在的目錄必須是包含該程序集清單的文件所在的目錄。 |
|
類型引用信息 |
運行時用來將類型引用映射到包含其聲明和實現的文件的信息。 該信息用于從程序集導出的類型。 |
|
有關被引用程序集的信息 |
該程序集靜態引用的其他程序集的列表。 如果依賴的程序集具有強名稱,則每一引用均包括該依賴程序集的名稱、程序集元數據(版本、區域性、操作系統等)和公鑰。 |
3 加載公共語言運行時
每個程序集既可以是一個可執行應用程序,也可以是一個DLL,但最終都是由CLR管理這些程序集中代碼的執行, 這意味著必須在目標機器 上安裝好.NET Framerwork.
要知道是否已安裝.Net Framework,只需檢查%SystemRoot%System32目錄中的MSCorEE.dll文件。存在該文件,表明.Net Framework已安 裝。
.NET Framework SDK提供了一個名為CLRVer.exe的命令行實用程序,它能列出一臺機器上安裝的所有CLR版本,比如菜鳥電腦上:

4 執行程序集的代碼
托管程序集同時包含元數據和IL,IL是與CPU無關的機器語言,為了執行一個方法,必須把它的IL轉換成本地CPU指令。
方法的第一次調用:

Main方法首次調用WriteLine時:
? JITCompiler函數被調用,它知道要調用的是哪個方法,以及具體是什么類型定義了該方法
? JITCompiler會在定義(該類型的)程序集的元數據中查找被調用的方法的IL。
? JITCompiler驗證IL代碼,并將IL代碼編譯成本地CPU指令。本地CPU指令被保存到一個動態分配的內存塊中。
? JITCompiler返回CLR為類型創建的內部數據結構,找到與被調用的方法對應的那一條記錄,修改最初對JITCompiler的引用,讓它現在指向內存塊(其中包含了剛才編譯好的本地CPU指令)的地址。
? JITCompiler函數跳轉到內存塊中的代碼。這些代碼正是WriteLine方法(獲取單個String參數的那個版本)的具體實現。這些代碼執行完畢并返回時,會返回Main中的代碼,并跟往常一樣繼續執行。

一個方法只有在首次調用時才會有一些性能損失,以后對該方法的調用都以本地代碼的形式全速運行,無需重新驗證IL并把它編譯成本地代碼。
一旦應用程序終止,編譯好的代碼也會被丟棄,所以再次運行,JIT編譯器必須再次將IL編譯成本地指令。
最后,了解下通用類型系統(Common Type System,CTS)以及公共語言規范(Common Language Specificcation,CLS)
CLR是完全圍繞類型展開的,而了解類型則有必要把焦點放在.NET類型體系的公共基礎架構上,由于通用類型系統的存在,.NET平臺下的各種語言可以無縫的集成,從MSDN的官方解釋上我們可以看到:
通用類型系統定義了如何在運行庫中聲明、使用和管理類型,同時也是運行庫支持跨語言集成的一個重要組成部分。通用類型系統執行以下功能:
-
建立一個支持跨語言集成、類型安全和高性能代碼執行的框架。
-
提供一個支持完整實現多種編程語言的面向對象的模型。
-
定義各語言必須遵守的規則,有助于確保用不同語言編寫的對象能夠交互作用。

我們都知道CTS支持兩種基本的類型:值類型和引用類型,至于他們之間的區別,這不是本節的主要內容,以后會詳細剖析.

菜鳥從anytao那里學習到:
.NET技術可以以規范和實現兩部分來劃分,而我們經常強調和提起的.NET Framwork,主要包括公共語言運行時(Common Language Runtime, CLR)和.NET框架類庫(Framework Class Library, FCL),其實是對.NET規范的實現。而另外一部分:規范,我們稱之為公共語言架構(Common Language Infrastructure, CLI),主要包括通用類型系統(CTS),公共語言規范(Common Language Specification, CLS)和通用中間語言(Common Intermediate Language, CIL)。我們以圖的形式來看看CTS在.NET技術陣營中的位置。


-
CLI,.NET技術規范,已經得到ECMA(歐洲計算機制造商協會)組織的批準實現了標注化。
-
CTS,上面已將,此不冗述。
-
CLS,定義了CTS的子集,開發基于CTS的編譯器,則必須遵守CLS規則,由本文開頭的圖中就可以看出CLS是面向.NET的開發語言必須支持的最小集合。
-
CIL,是一種基于堆棧的語言,是任何.NET語言編譯產生的中間代碼,我們可以理解為IL就是CLR的匯編語言。IL定義了一套與處理器無關的虛擬指令集,與CLR/CTS的規則進行映射,執行IL都會翻譯為本地機器語言來執行。常見的指令有:add, box, call, newobj, unbox。另外,IL很類似于Java世界里的字節碼(Bytecode),當然也完全不是一回事,最主要的區別是IL是即時編譯(Just in time, JIT)方式,而Bytecode是解釋性編譯,顯然效率上更勝一躊。
-
.NET Framework,可以說是CLI在windows平臺的實現,運行與windows平臺之上。
-
CLR,.NET框架核心,也是本系列的核心。類似于Java世界的JVM,主要的功能是:管理代碼執行,提供CTS和基礎性服務。對CLR的探討,將伴隨著這個系列的成長來慢慢展開,在此就不多說了。
-
FCL,提供了一整套的標準類型,以命名空間組織成樹狀形式,樹的根是System。對程序設計人員來說,學習和熟悉FCL是突破設計水平的必經之路,因為其中數以萬計的類幫助我們完成了程序設計絕大部分的基礎性工作,重要的是我們要知道如何去使用。
要和其他對象完全交互,而不管這些對象是以何種語言實現的,對象必須只向調用方公開那些它們必須與之互用的所有語言的通用功能。為此定義了公共語言規范 (CLS),它是許多應用程序所需的一套基本語言功能。CLS 規則定義了通用類型系統的子集,即所有適用于通用類型系統的規則都適用于 CLS,除非 CLS 中定義了更嚴格的規則。

題外話
不要過于專著于技術,這里的技術指工作中用于開發的技術。在幾年之后,當你只有.NET可以和你的孩子分享的話,是不是太可悲了。在軟件行業,技術雖然一定程度決定了薪水、決定了職位,但是我們的生活并不是100%是工作,有時間為技術而發愁,為何不綜合提升自己的其它能力呢?并且隨著職位的上升,往往溝通能力、經濟知識以及文學藝術修養比技術顯得更重要,這個時候嘆息自己過于專著技術往往為時過晚。
l 學習靠自己,不要期望別人教你什么,學習要主動;
l 不管水平高低,不要看不起自己,也不能看不起別人,學習要心態好;
l 不能不思進取,也不用讓自己為技術所累,給自己多一點技術之外的時間;
l 如果時間不充裕,優先考慮學習基礎的內容,同時也可以多關注一些新的思想;
l 如果別人能從你這里學到知識的話,那么你自己也一定學到了知識,請堅持分享。

浙公網安備 33010602011771號