【BotR】CLR類型系統

来源:https://www.cnblogs.com/netry/archive/2022/09/23/clr-type-system-chinese.html
-Advertisement-
Play Games

.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 LoaderMethodDesc章節中有討論。

將這些數據結構綁定在一起是一組功能, 允許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

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文按照mac講解protobuf的安裝,windows上比較好安裝按照mac的基本流程就可以安裝成功,mac上的安裝有的時候比較容易出現問題 一、通過brew的方式安裝(僅Mac) 需要mac中存在brew,輸入命令:brew --version 查看是否存在brew,如不存在就進行安裝,安裝方法 ...
  • 大家好,,這篇文章咱們聊下JVM性能優化的問題 這篇文章主要介紹下JVM的運行數據區相關的內容,包括: 程式計數器 虛擬機棧 本地方法棧 堆 方法區 案例 和總結 好了,開始乾貨環節~ 作為一個常識性的知識,大家都知道位元組碼只是一個二進位文件存放在那裡。要想在jvm里跑起來,先得有個運行的記憶體環境。 ...
  • 在數據分析過程中,一般提取資料庫裡面的數據時候,拿著表格數據反覆思索,希望能夠根據自己所想立馬生成一張數據可視化的圖表來更直觀的呈現數據。 但想要進行數據可視化的時候,往往需要調用很多的庫與函數,還需要數據轉換以及大量的代碼處理編寫。這都是十分繁瑣的工作,確實只為了數據可視化我們不需要實現數據可視化 ...
  • “Java SPI是什麼?有什麼用?” 這是阿裡p6面試過程中,第二面的時候遇到的一個真實的問題。 如果你不理解SPI,建議你看完整個視頻。 大家好,我是Mic,一個工作了14年的Java程式員 這道面試題的文字版我已經整理在20萬字的文檔里了,有需要的可以在文章尾端領取 下麵來看看這個問題考察的目 ...
  • 如果你是準備自學Python或者正在學習,你應該能用得上: ① Python所有方向的學習路線圖,清楚各個方向要學什麼東西 ② 80多節Python課程視頻,涵蓋必備基礎、爬蟲和數據分析 ③ 100多個Python項目源碼,學習不再是只會理論 ④ 獨家Python圖文教程,手機也能學習 ⑤ 200多 ...
  • 練習題 1 成績等級 要求輸出成績等級A、B、C、D、E, 其中90-100分為A,80-89分為B,70-79分為C,60-69分為D,60分以下為E。 要求: - 用If語句實現; - 輸入百分製成績後要判斷該成績的合理性,對不合理的成績應輸出出錯信息。 參考答案: while True: co ...
  • 雖然編譯源碼折騰了幾個時間(卡在restore),最後還是跑起來了aspnetcore6.0mvc源碼項目,下麵說步驟,前提是網路能連外,對於不能連外的懶得折騰。 第一步 電腦找個地克隆下GitHub上的源碼下來 git clone --recursive https://github.com/do ...
  • 前面介紹了 YARP 通過配置文件的方式配置代理轉發(傳送門),而眾所周知,微軟的一貫作風就是能通過配置文件做的事情,通過編碼的方式也能實現!YARP 也不例外,廢話不多說,直接上代碼! 首先,參照官方文檔,我們先新建一個 InMemoryConfigProvider 類,並且繼承 IProxyCo ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...