CLR的執行模型

文章導讀數據庫

1.將源代碼編譯爲託管模塊編程

2.將託管模塊合併爲程序集windows

3.加載CLR數組

4.執行程序集代碼安全

5.Framework類庫---FCL網絡

6.通用類型系統---CTS數據結構

7.公共語言規範---CLS架構

什麼是CLR異步

簡單的翻譯過來:公共語言運行時。編程語言

這傢伙跟使用那種編程語言無關,只要你的編譯器是面向CLR的就行,他跟隨.net framework 一塊兒安裝。

固然了,只有託管模塊的運行才須要CLR,非託管代碼生成的模塊,那就另當別論了!

目前,微軟已經編寫出了多種面向CLR的語言編譯器,好比:微軟C/C++,C#,VB,F#,以及IL。

好了,知道了什麼是CLR,接下來就開始探索程序背後的祕密吧!

出於我的緣由,本系列文章中的代碼語言,以及開發工具均爲C#,VisualStudio 2010

將源代碼編譯爲託管模塊

咱們用本身擅長的語言完成代碼的編寫以後,編譯器首先要把源代碼編譯成一個託管模塊。

不管使用哪一種編譯器,最終的結果都是一個託管模塊

那麼什麼是託管模塊呢?

託管模塊:簡單來講就是一個標準的windows PE文件,32位程序就是PE32,64位程序就是PE32+。

託管模塊包含的內容:

  PE32或PE32+頭,這個頭標識了文件是GUI,CUI仍是DLL,以及和本地CPU代碼有關的信息;

  CLR頭,這個頭裏麪包含了使一個模塊成爲一個託管模塊所需的信息,他包含CLR版本,Main方法的MethodDef元數據標記等信息;

  元數據,元數據就是一組數據表的集合,主要包含兩種表:描述模塊中定義的類型和成員,描述模塊引用的類型和成員;

  IL代碼(注意:IL代碼存在於託管模塊中),編譯器將IL代碼編譯成本地的CPU指令;

注意:本地代碼編譯器生成的是面向特定CPU架構的代碼;面向CLR的編譯器生成的都是IL代碼。

一般狀況下,編譯器生成的元數據老是嵌入到和IL代碼相同的EXE/DLL文件中,這樣就保證了這兩種數據老是保持同步。

說到這裏,咱們順便再說一下[元數據]的做用:

  首先,有了元數據,編譯器能夠直接從託管模塊中讀取元數據,消除了對本地C/C++頭和庫文件的需求;

  而後,也是咱們最經常使用的一個功能:VisualStudio的智能感知,經過解析元數據,能夠指出一個類型提供了哪些的字段,屬性,方法,若是是方法的話還能指出方法所需的參數類型;

  還有,CLR的代碼驗證也是經過元數據來確保你的代碼只能執行類型安全的操做;

  再者,有了元數據,類型中的字段也能夠被序列化到內存塊兒中,經過網絡發送到另一臺機器,反序列化以後重建類型對象;

  最後,元數據還容許垃圾回收器跟蹤對象的生命週期,以肯定什麼時候進行垃圾回收;

題外話:在全部的面向CLR的編譯器中,只有C/C++編譯器最特殊,由於他既能編譯託管代碼,也能編譯非託管代碼;

將託管模塊合併爲程序集

前面說了那麼多託管模塊,其實,CLR並不直接和託管模塊關聯,CLR直接面向的是程序集。

程序集是一個抽象的感念,簡單來講,他就是對模塊和資源文件進行的邏輯分組;同時,程序集也是程序重用,安全和版本控制的最小單元;在CLR的世界裏,程序集就是一個組件。

編譯器先是把源代碼編譯生成託管模塊,而後再把託管模塊合併爲一個程序集文件,這個程序集文件其實也是一個標準的PE文件(沒錯,咱們前面說過,託管模塊也是一個PE文件,這是正確的!!!),與託管模塊相似,程序集文件也包含一組數據塊兒,稱做[清單],這個清單就是另一組簡單的元數據表,這些表記錄了組成這個程序集的文件信息(託管模塊)以及這些文件中定義的公共類型。而後,這些清單文件還記錄了與這個程序集相關聯的資源或數據文件信息。

注意了:託管模塊是由編譯器編譯而來的,程序集是由託管模塊文件以及資源文件等[合併]而來的;

加載CLR

每一個程序集都被生成爲一個可執行應用程序,或是DLL文件(包含一組被可執行應用程序使用的類型文件),CLR的任務就是管理這些程序集中的代碼執行,因此,在執行這些文件以前,咱們必須確保.net framework已經在咱們的機器上正確安裝。

在繼續探索以前,咱們先了解一下32位系統和64位系統。若是程序集中只包含類型安全的託管代碼,那麼,這些代碼文件將可以在任何安裝了.net framework的操做系統中運行。

可是,有時候,咱們寫的代碼只但願在特定的windows版本中運行,爲了實現這個目標,C#編譯器提供了一個/platform命令行開關(在屬性,生成,目標平臺中),經過這個開關,咱們就能指定咱們的程序只能在特定的平臺(CPU架構)中運行;

windows在執行一個可執行文件時,會先查看文件的頭信息,以肯定程序是要32位仍是64位的地址空間,同時windows還會檢查頭文件中的CPU架構信息,以確保計算機的CPU架構與程序的CPU類型匹配。

在64位的windows操做系統中,還提供了一種稱做「WoW64」(windows on windows64)的技術,這種技術經過模擬x86指令集,使那些包含了x86本地代碼的32位應用程序運行在Itanium機器上,可是這樣作的的性能損失也是比較大的!

在檢查完程序集的頭信息以後,windows會建立用於該程序的進程,windows加載MSCorEE.dll程序集到進程的地址空間中,而後進程的主線程會調用定義在MSCorEE.dll中的方法,該方法初始化(若是CLR爲初始化)CLR,加載exe程序集,接着調用其入口函數:Main,此時,託管應用程序開始執行!

總結來講,一個應用程序的執行過程能夠分爲如下幾個步驟:

  1.windows檢測EXE文件頭,是32?64?WoW64?;

  2.建立對應版本的windows進程;

  3.在(2)中建立的進程裏面加載對應版本的MSCoreEE.dll;

  4.由(2)建立的進程主線程調用MSCoreEE.dll中定義的方法,暫時稱爲MethodA(注意:真實dll中並不存在此方法名);

  5.MethodA初始化CLR,加載EXE程序集;

  6.調用Main()方法;

注意:使用x86開關編譯的dll沒法在非64位windows系統的64位進程中運行,可是能夠在64位windows系統中的64位進程中做爲WoW64應用程序運行;

執行程序集代碼

爲了執行一個方法,必須把該方法的IL代碼轉換成本地CPU指令,而這一功能是由CLRJIT編譯器(記着這個NB的[編譯器])提供的;

在執行Main方法以前,CLR會檢測Main方法中的全部引用類型,併爲這些類型分配一個內部數據結構,該數據結構用於管理對全部引用類型的訪問;

舉例來講,若是Main方法中調用了Console類的WriteLine(string message)方法,那麼WriteLine方法是這樣執行的:

  1.CLR分配[內部數據結構],暫且稱其爲ObjectA吧,Console類型中的每一個方法在ObjectA中都有一個對應的記錄項,每一個記錄項都指向一個[CLR內部未文檔化的函數:  JITCompiler],這個函數就是前面說的NB的[編譯器]

  2.WriteLine方法第一次調用時,JITCompiler函數會被調用,JITCompiler函數在程序集的元數據中查找WriteLine方法的IL代碼;

  3.JITCompiler函數驗證IL代碼,將IL代碼編譯爲本地CPU指令,而且將這些CPU指令保存到一個動態分配的內存塊中;

  4.JITCompiler函數返回ObjectA,找到WriteLine方法對應的記錄項,修改其最初對JITCompiler函數的指向,讓它如今指向(3)中的內存塊的地址;

  5.JITCompiler函數跳轉到內存塊中的代碼,執行這些代碼;

  6.代碼執行完成,JITCompiler函數從新返回到Main方法中,繼續執行下面的代碼;

在同一個應用程序進程中,一個方法只有在首次執行的時候纔會調用JITCompiler函數,JITCompiler函數將本地CPU代碼保存到內存塊中,之後對該方法的重複調用都會使用同一份本地CPU代碼,可是,一旦應用程序終止,編譯好的CPU代碼也會被丟棄!

到這裏,咱們應該會明白,託管代碼(好比咱們用的C#)在真正執行以前是經歷了兩次編譯的:

  1.由對應的語言編譯器(C#編譯器)編譯爲包含IL代碼的託管模塊;

  2.由JIT編譯器(JITCompiler函數)將程序集中的IL代碼編譯爲本地CPU指令;

通過了這兩個步驟,託管代碼就能夠暢快的執行了!

但是,有人就要問了,非託管代碼(C/C++)都是針對一種具體的CPU平臺編譯的,一旦調用,就能直接執行;託管代碼卻要編譯兩次,那託管程序是否是性能就差呢?是的,經歷兩次編譯以後,確實會產生一些額外的性能開銷。這點是咱們全部使用託管代碼的人必須認可的!

可是,CLR的JIT編譯器會針對本地代碼進行優化,儘量將這些性能損失降到最低,並且,在某些狀況下,通過優化的代碼性能還要好過非託管代碼(本人爲驗證,水平不夠!!!)。咱們也可使用.net framework提供的NGen.exe本地代碼生成器預生成一份本地代碼,來提升程序的性能,;

前面講到JIT編譯器會將IL再編譯爲本地CPU代碼,其實,在進行此次編譯以前,CLR會執行一個名爲[驗證]的過程,這個過程會檢查高級IL代碼,確保代碼所作的一切操做都是安全可靠的。

在windows中,每個進程都有它本身的地址空間,這樣每一個應用程序就會佔用一些地址空間,若是同時運行多個程序的話,勢必會形成性能浪費,可是,CLR提供了一種很NB的能力:讓多個託管應用程序在同一個系統進程中執行。在這種狀況下,每一個託管應用程序都在系統進程中的一個獨立AppDomain中運行,一個系統進程中能夠同時存在多個AppDomain。

題外:默認狀況下C#編譯器只容許生成安全的(safe)代碼,然而,同時也容許開發人員編寫不安全(unsafe)代碼;C#編譯器要求全部包含不安全代碼的方法都用unsafe關鍵字標記,並且,C#編譯器還要求使用/unsafe編譯器開關編譯源代碼;當JIT編譯器試圖編譯一個unsafe方法時,會首先驗證包含該方法的程序集是否被授予了System.Security.Permissions.SecurityPermission權限,並且System.Security.Permissions.SecurityPermissionFlag的SkipVerification標記是否已被設置,若是已被設置,JIT編譯器就會編譯不安全代碼,而且容許它執行,不然,會拋出System.InvalidProgramException或System.Security.VerificationException異常;不過,默認狀況下,從本地或是「網絡共享」加載的程序集都會被授予徹底信任,經過Internet執行的程序集則不會授予執行不安全代碼的權限;

咱們能夠經過Peverify.exe這個工具來驗證咱們想要驗證的程序集的安全性,這個工具隨VisualStudio一塊兒安裝具體使用方法能夠參考MSDN

Framework類庫

.net framework中包含了framework類庫(Framework Class Library),FCL就是一組程序集的統稱,其中定義了大量的類型,每一個類型都公開了一些功能,具備相關功能的一組類型能夠放在同一命名空間下;開發人員能夠利用這些類型建立各類應用程序:

  1.Web Service:利用Asp.net XML Web Service技術和Windows Communication Foundation(WCF)技術能夠輕鬆的處理經過Internet發送的消息;

  2.Web Form:利用Asp.net能夠開發基於HTML的應用程序;

  3.Windows Form:利用Windows窗體編程技術或是WPF編程技術,經過使用操做系統提供的功能,開發GUI應用程序;

  4.富Internet應用程序:利用Sliverlight技術;

  5.Windows控制檯應用程序:編譯器,應用程序和工具通常都是做爲控制檯實現;

  6.Windows服務;

  7.數據庫存儲過程;

  8.組件庫;

部分常規FCL命名空間:

命名空間 內容說明
System 包含每一個類型都要用到的全部基本類型
System.Data 包含用於數據庫通訊以及處理數據的類型
System.IO 包含用於執行流I/O以及瀏覽目錄/文件的類型
System.Net 包含進行低級網絡通訊,並與一些經常使用Internet協議協做的類型
System.Runtime.InteropServices 包含容許託管代碼訪問非託管操做系統平臺功能的類型(好比com組件,win32函數,定製dll中的函數)
System.Security 包含用於保護數據和資源的類型
System.Text 包含處理各類編碼方式的文本的類型
System.Threading 包含用於異步操做和同步資源訪問的類型
System.Xml 包含用於處理XML架構和數據的類型

更多FCL類型的介紹,能夠參考Microsoft SDK配套文檔;

通用類型系統

通用類型系統:Common Type System(CTS);

framework中定義了大量的類型,如何定義一個類型,必須有一個規範,準則,這個工做就是CTS來作的。CTS描述了類型的定義和行爲。

一個類型能夠包含零個或者多個成員,具體的成員都有哪些呢?

  字段(Field):用來描述對象的狀態,能夠經過類型和名字區分;

  方法(Method):主要是針對對象執行一個操做,一般會改變對象的狀態,方法包含:名稱,簽名(參數的類型和個數),若干修飾符,返回值(若是有的話);

  屬性(Property):對調用者像是字段,對類型實現者像是方法,一般能夠利用屬性建立只讀或是隻寫字段;

  事件(Event):實現了一種在對象和其餘對象之間的通知機制;

除此以外,CTS還定義了類型的可訪問性和類型成員的訪問規則;

對於類型:

  有public,assembly(C#中使用internal修飾符);

對於類型成員:

  public:無限制訪問;

  family or assembly:可由任何程序集的派生類型訪問,也能夠由同一程序集的任何類型訪問,在C#中用protected internal修飾符標識;

  assembly:由同一程序集內的類型訪問,一般用internal修飾符標識;

  family and assembly:成員可由派生類訪問,但這些派生類必須在同一程序集內定義,C#沒有實現這種機制;

  family:可由派生類訪問,C#使用protected關鍵字標識;

  private;

除此以外,CTS還爲類型繼承,虛方法,對象生存週期等定義了相應的規則。

不管使用哪一種語言,類型的行爲都是徹底一致的,由於最終都是由CLR的CTS來定義這些行爲;

注:全部類型都必須從預約義的System.Object類型繼承,這個類型定義了一組基本的行爲:

  比較兩個實例的想等性;

  獲取實例的哈希碼;

  查詢一個實例的真正類型;

  執行實例的淺(按位)拷貝;

  獲取實例對象的當前狀態的一個字符串表示;

公共語言規範

COM容許使用不一樣的語言建立的對象之間互相通訊,但是語言的種類不少,各自的語法,以及各自支持的類型都不相同,如何實現這種[通訊]呢?

俗話說:無規矩不成方圓,因此,爲了實現這個宏偉的目標,就須要制定一套規矩,CLS(Common Language Specification)就是幹這個的。CLR可以實現這種語言間的集成,是由於CLR使用了標準的類型集,元數據以及公共執行環境;那麼CLS到底是如何規範這種集成呢?其實,CLS定義了一個全部語言都支持的最小功能子集,任何編譯器生成的類型想要兼容於由其它「符合CLS,面向CLR的語言」所生成的組件,就必須支持這個最小功能集。

須要注意的是,若是要定義一個供其它語言使用的類型,該類型得是public或是protected,並且不能在該類型中使用位於CLS外部的任何功能功能;若是你定義了一個這樣的類型,那麼最好在類型上面添加一個驗證:[assembly:CLSCompliant(true)],將該attribute應用於程序集便可;

至此,咱們稍微總結一下:在CLR中,一個類型的成員要麼是一個字段(數據),要麼是一個方法(行爲),因此,每種編程語言都必須都必須能訪問字段和方法。但是,每種編程語言都會公開各類概念,像是:枚舉,數組,索引器,委託,事件,構造器,析構器,操做符重載,轉換操做符等等。編譯器在源代碼中遇到上述任何一種構造,必須將其轉化爲字段或方法,使CLR和其餘任何編程語言都可以訪問這些構造。

相關文章
相關標籤/搜索