.NET運行時之書(Book of the Runtime,簡稱BotR)是一系列描述.NET運行時的文檔,2007年左右在微軟內部創建,最初目的是為了幫助其新員工快速上手.NET運行時;隨著.NET開源,BotR也被公開了出來,如果想深入理解CLR,這系列文章不可錯過。 BotR系列目錄: [1] ...
.NET運行時之書(Book of the Runtime,簡稱BotR)是一系列描述.NET運行時的文檔,2007年左右在微軟內部創建,最初目的是為了幫助其新員工快速上手.NET運行時;隨著.NET開源,BotR也被公開了出來,如果想深入理解CLR,這系列文章不可錯過。
BotR系列目錄:
[1] CLR類型載入器設計(Type Loader Design)
[2] CLR類型系統概述(Type System Overview)
類型系統概述(Type System Overview)
原文:https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/type-system.md
作者: David Wrighton - 2010
翻譯:幾秋 (https://www.cnblogs.com/netry/)
介紹
CLR類型系統是我們在ECMA規範+擴展中描述的類型系統的表示形式。
概述
該類型系統是由一系列數據結構(其中一些在BotR的其它章節有描述)和操作這些數據結構的演算法組合而成,它不是通過反射暴露出來的類型系統,儘管反射確實依賴於這個系統。
由類型系統維護的主要數據結構是:
- MethodTable
- EEClass
- MethodDesc
- FieldDesc
- TypeDesc
- ClassLoader
由類型系統包含的主要演算法是:
- Type Loader: 用於載入類型並創建類型系統中大部分的重要數據結構。
- CanCastTo and similar: 類型比較功能。
- LoadTypeHandle: 主要用於查找類型。
- Signature parsing: 用於比較和收集有關方法和欄位的信息
- GetMethod/FieldDesc: 用於查找、載入方法和欄位。
- Virtual Stub Dispatch: 用於查找對介面的虛調用的存根。
還有很多輔助數據結構和演算法為CLR的其餘部分提供各種信息,但它們對於整個系統的理解並不那麼重要。
組件架構
類型系統的數據結構通常被各種演算法所使用。本文檔不會涉及類型系統演算法(),但是它會試圖描述各種主要數據結構。
依賴
類型系統大體上是給CLR中很多部分提供服務,多數核心組件都對類型系統的行為有某種形式的依賴性。下圖描述了影響類型系統的通用數據流,它不是很全面,但是指出了只要的信息流。
組件依賴
類型系統的主要依賴關係如下:
- 載入器工作需要獲取正確的元數據。
- 元數據系統(metadata system)提供一個元數據API去收集信息。
- 安全系統(security system)告知類型系統是否允許某些類型系統結構。
- 應用程式域(AppDomain)提供一個LoaderAllocator去處理類型系統數據結構的分配行為。
依賴於此組件的組件
類型系統有3個主要組件依賴於它:
- jit介面和jit helpers主要依賴於類型、方法,和欄位搜索功能。一旦找到了類型系統對象,返回的數據結構就會進行裁剪,以提供jit所需的信息。
- 反射使用類型系統提供對ECMA標準化概念相對簡單的訪問,CLR類型系統的數據結構中正好有這些。
- 常規托管代碼執行 需要使用類型系統進行類型比較邏輯和虛擬存根分派
類型系統設計
核心類型系統數據結構是表示實際載入類型的數據結構(例如,TypeHandle, MethodTable, MethodDesc, TypeDesc, EEClass)和允許在載入類型後找到它們的數據結構(例如,ClassLoader, Assembly, Module, RIDMaps)。
載入類型的數據結構和演算法在BotR的Type Loader 和 MethodDesc章節中有討論。
將這些數據結構綁定在一起是一組功能, 允許JIT/Reflection/TypeLoader/stackwalker去查找現存類型和方法,一般的想法是,這些搜索應該很容易地由ECMA CLI規範中指定的元數據令牌/簽名(metadata tokens/signatures)驅動。
最後,當合適的類型系統數據結構被找到,我們有演算法從類型中收集信息,有and/or比較兩個類型。可以在 Virtual Stub Dispatch找到這種演算法的一個特別複雜的例子。
設計目標和非目標
目標
- 訪問運行時執行(非反射)代碼所需的信息要非常快。
- 訪問編譯時生成代碼所需的信息要是非常直截了當的。
- 垃圾回收器(arbage collector)、堆棧遍歷器(stackwalker,沒找到準確的中文翻譯,詳情可參考這篇文章 Stackwalking in the .NET Runtime/)無需鎖或分配記憶體就可以方法信息。
- 一次只載入最少量的類型。
- 類型載入時只載入最少需要載入的類型。
- 類型系統的數據結構必須存儲在NGEN鏡像中。
非目標
- 元數據中的所有信息都直接反映在CLR的數據結構中。
- 所有的反射操作都要非常快。
運行時在托管代碼執行期間使用的一個典型演算法設計
類型轉換演算法(casting algorithm)是類型系統中的典型演算法,在托管代碼的執行過程中大量使用這種演算法。
這個演算法至少有4個獨立的入口(entry point),選擇每個入口都是為了提供不同的快速路徑,希望能夠實現最佳的性能。
- 一個對象能否被轉換成一個非類型等價的非數組類型(non-type equivalent non-array type)?
- 一個對象能否被轉換成一個沒有實現泛型變體(generic variance)的介面類型?
- 一個對象能否被轉換成一個數組類型?
- 一個類型的對象能否被轉換成轉換為任意其他托管類型?
除了最後一個之外,每個實現都進行了優化,以便在不完全通用的情況下提高性能。例如,“一個類型能否被轉換成一個父類?”就是 “一個對象能否被轉換成一個非類型等價的非數組類型”的變體,它通過單迴圈遍歷一個單鏈表實現。這隻能搜索可能的轉換操作的一個子集,但可以通過檢查試圖強制轉換的類型來確定是否是合適的集合,這個演算法在jit helper JIT_ChkCastClass_Portable
中實現。
假設:
- 演算法的特殊用途實現通常是性能改進。
- 額外版本的演算法不會提供無法剋服的維護問題(insurmountable maintenance problem)。
類型系統中典型搜索演算法設計
在類型系統中很多演算法遵循這種常見模式。類型系統通常用於查找類型,這可以通過任意數量的輸入觸發,如JIT、反射、序列化、遠程調用等等。在這些情況下,對類型系統的基本輸入是:
- 來自開始搜索的上下文(一個模塊或者程式集指針)。
- 在初始上下文中描述所需類型的標識符(identifier),通常是一個令牌(token),或者一個字元串(如果搜索上下文是一個程式集)。
這個演算法必須首先解碼標識符。對於搜索一個類型的場景,令牌可能是typedef令牌、typeref令牌、typespec令牌,或者是一個字元串。這些不同種類的標識符都會將導致不同形式的查詢。
- 一個typedef令牌將導致在模塊的RidMap中進行查找。這是一個簡單的數組索引。
- 一個typeref令牌將導致查找此令牌所引用的程式集,然後用找到的彙編指針和從typeref表中收集的字元串重新開始類型查找演算法。
- 一個typespec令牌指示必須對簽名(signature)進行解析才能找到簽名。解析簽名以查找載入該類型所需的信息,這將遞歸的觸發更多類型查找。
- 名稱(name)用於在程式集之間進行綁定。TypeDef/ExportedTypes表用來做匹配搜索。主要:此搜索通過清單模塊對象(manifest module object)上的哈希表進行優化。
從這個設計中可以明顯看出一些搜索演算法在類型系統中的共同特點:
- 搜索使用的輸入是和元數據加密耦合的。特別是元數據令牌和字元串名稱,它們檢查被傳來傳去,還有,這些搜索是和模塊綁在一起的,它們直接映射到 .dll和.exe文件。
- 使用緩存的信息去提高性能。RidMap和哈希表是經過優化的數據結構,用於改進這些查找。
- 這些演算法通常根據其輸入有3-4個不同的路徑。
除了這個總體設計,在此基礎上,還有一些額外的需求:
- 假設 在GC停止時搜索已載入的類型是安全的。
- 不變性 一個已經載入了的類型將總是可以被搜索到。
- 問題搜索程式依賴於元數據讀取。某些場景可能會導致性能不足。
此搜索演算法是 JIT期間使用的典型程式。它具有許多共同的特征:
- 它使用元數據
- 它需要在許多地方查找數據
- 在我們的數據結構中有相對少量的重覆數據
- 它通常不會深度遞歸,也沒有迴圈
這使我們能夠滿足性能要求,以及使用基於IL的JIT所必需的特征。
類型系統對垃圾回收器的要求
垃圾回收器要有關類型實例分配在GC堆上的信息,這是通過一個指向類型系統數據結構(MethodTable)的指針來完成,該MethodTable位於每一個托管對象的開頭。附著到這個MethodTable之上的,是一個描述類型實例GC佈局的數據結構。該佈局有兩種形式(一般類型和對象數組是一種,值類型數組是另一種)。
- 假設:類型系統的數據結構有一個生命周期,它超過了自身描述類型的托管對象的生命周期。
- 要求: 垃圾回收器需要在運行時掛起時執行堆棧遍歷器(stack walker),這將在下麵討論。
Stackwalker對類型系統的要求
堆棧遍歷器/GC堆棧遍歷器在兩種情況下要求類型系統輸入:
- 用於查找值類型在堆棧上的大小
- 用於查找要在堆棧上的值類型內報告的GC根
由於各種原因,包括希望延遲載入類型,以及避免生成多個版本的代碼(僅通過相關的gc信息不同),CLR當前需要遍歷堆棧上的方法簽名,這種需求很少得到滿足,因為它要求在特定的時刻執行堆棧遍歷,但是為了滿足我們的現實目標,當遍歷堆棧的時候,必須能夠遍歷簽名。
堆棧遍歷器以大約 3 種模式執行:
- 因為安全或者異常處理的原因,要遍歷當前線程的堆棧
- 因為GC,要遍歷使用線程的堆棧(所有線程都被EE掛起)
- 使用分析工具(profiler)需要遍歷指定的線程(指定線程被掛起)
在GC和分析工具遍歷堆棧的情況,由於線程被掛起,分配記憶體或占用大多數鎖是不安全的。這導致我們開發了一條通過類型系統的路徑,可以依賴它來遵循上述要求。型系統實現此目標所需的規則是:
- 如果一個方法已經被調用,那麼被調用方法的所有值類型參數都將被載入到進程中的某個應用程式域中。
- 從帶有簽名的程式集到實現該類型的程式集,引用它們的程式集必須在遍歷簽名之前被解析,這是遍歷堆棧中必要的一部分。
這是通過一系列廣泛而複雜的強制措施來實施的,包括類型載入器、NGEN鏡像生成過程和JIT。
- 問題: 堆棧遍歷器對類型系統的要求是非常脆弱的。
- 問題: 在類型系統上實現堆棧遍歷器的要求,需要侵入類型系統中的每個搜索類型時可能接觸到的函數
- 問題: 執行的簽名遍歷是使用正常的簽名遍歷代碼完成的。此代碼設計是在遍歷簽名時載入類型,但在這種情況下,類型載入功能是在假設實際上不會觸發任何類型載入的情況下使用的。
- 問題: 堆棧遍歷器不僅需要類型系統的支持,還需要程式集載入器的支持,要滿足類型系統的需要,載入器還有很多問題。
類型系統與NGEN
類型系統數據結構是保存到NGEN鏡像中的核心部分,不幸的是,這些數據結構邏輯內有指向其它NGEN鏡像的指針。為了處理這種情況,類型系統數據結構實現了一個稱為恢復(restoration)的概念。
在恢復期,當第一次需要類型系統數據結構時,該數據結構用正確的指針固定, 這與類型載入級別有關,請看前篇CLR類型載入器設計
還存在一個預恢復(pre-restored)數據結構的概念,這意味著數據結構在NGEN鏡像載入時足夠正確(在intra-module指針和預先載入類型修正之後),數據結構可以按原樣使用。此優化要求將NGEN鏡像“硬綁定”("hard bound")到其依賴程式集,詳情請查看NGEN相關文檔。
類型系統和域中性載入(Domain Neutral Loading)
類型系統是實現域中性載入的核心部分,它通過在AppDomain創建時啟用LoaderOptimization選項暴露給用戶。Mscorlib始終作為域中性載入,此功能的核心要求是類型系統數據結構不能要求指向特定域狀態(domain specific state)的指針,這主要表現在圍繞靜態欄位和類構造函數的需求中。特別是,由於這個原因,一個類的構造函數是否已經運行不是核心MethodTable數據結構的一部分。並且有一種機制來存儲附加到DomainFile數據結構而不是MethodTable數據結構。
物理結構
類型系統的主要部分位於:
- Class.cpp/inl/h – EEClass函數, 和BuildMethodTable
- MethodTable.cpp/inl/h – 操作methodtable的函數
- TypeDesc.cpp/inl/h – 檢查TypeDesc的函數
- MetaSig.cpp SigParser – 簽名代碼
- FieldDesc /MethodDesc –檢查這些數據結構的函數
- Generics – 泛型特定邏輯
- Array – 處理數組處理所需的特殊情況的代碼
- VirtualStubDispatch.cpp/h/inl – 虛擬存根分派(VSD)代碼
- VirtualCallStubCpu.hpp – 用於虛擬存根分派的處理器特定代碼
主要入口函數是BuildMethodTable、 LoadTypeHandleThrowing、CanCastTo*、 GetMethodDescFromMemberDefOrRefOrSpecThrowing、 GetFieldDescFromMemberRefThrowing、 CompareSigs和 VirtualCallStubManager::ResolveWorkerStatic.
相關閱讀
作者: 幾秋
出處: https://www.cnblogs.com/netry/p/clr-type-system-chinese.html
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。