[你必须知道的.NET]第二十六回:认识元数据和IL(下)

2024-01-02 00:58

本文主要是介绍[你必须知道的.NET]第二十六回:认识元数据和IL(下),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说在,开篇之前
书接上回: 
第二十四回:认识元数据和IL(上), 第二十五回:认识元数据和IL(中) 

我们继续。 

终于到了,说说元数据和IL在JIT编译时的角色了,虽然两个回合的铺垫未免铺张,但是却丝毫不为过,因为只有充分的认知才有足够的体会,技术也是如此。那么,我们就开始沿着方法调用的轨迹,追随元数据和IL在那个神秘瞬间所贡献的力量吧。  

                                                                                      www.anytao.com

 

 

5 元数据和IL在JIT编译时

CLR最终执行的只有本地机器码,所以JIT编译的作用是在运行时将IL代码解析为机器码执行。对于JIT编译,我们会以专门的篇幅来全面了解,本文只将目光关注于元数据和IL在程序执行时的作用和参与细节。首先,IL是基于栈执行的,执行方法调用时,方法参数、局部变量还有返回值等被分配于栈上,并执行其调用过程,既然是关注JIT编译时,因此我们自然而然将关注方法的执行,因为JIT编译是以执行方法调用而触发的。

首先,对本文开始的代码加点新料:

// Release : code04, 2009/02/24                    
// Author  : Anytao, http://www.anytao.com
// List    : Base.cs
public class Base
{public void M(){Console.WriteLine("M in Base");}public virtual void N(){Console.WriteLine("N in Base");}
}

还有:

// Release : code05, 2009/02/24                    
// Author  : Anytao, http://www.anytao.com
// List    : Three.cs
public class Three : Base
{private static int ID { get; set; }public override void N(){//Something new in ThreeConsole.WriteLine("N in Three");}public void M(){Console.WriteLine("M in Three");M1();}public void M1(){Console.WriteLine("M1 in Three");}
}

还有执行代码:

static void Main(string[] args)
{Base three = new Three();three.M();three.N();
}

小窥方法表

以该例而言,执行Main方法调用时,同时伴随着对于Three实例的创建,和相应类型信息的加载。我们先将类型信息创建的秘密放在以后的内容中,好留点悬念在未来发挥,哈哈。然而,类型加载一定是在实例创建之前完成的,也就是我们常常提起的方法表创建。类型加载是由class loader负责执行的,其过程简言之就是从元数据表中获取相应的类型信息,创建方法表(包含CORINFO_CLASS_STRUCT结构),其结构主要包括非虚方法表和虚方法表,按照继承的虚方法、新引入的虚方法、实例方法和静态方法的顺序排列,以类Three类型为例其CORINFO_CLASS_STRUCT结构可以表示为:

Note: 在本例中Three没有定义任何静态方法,其方法表中父类方法N已有子类覆写,同时因为有静态成员存在的原因,CLR会自动创建类型构造器,详细情况可参考《你必须知道的.NET》1.2节 “什么是继承”。

我们可以同过加载SOS调试来了解相应的方法表信息:

  • 在three.N()调用处打好断点,来查看该时刻的dump信息,就像一个内存快照,发现多少东西就看摄影师的水准。
  • 然后,通过dumpheap加载类型信息,获取方法表地址(0x002a354c),
!dumpheap -type Three
Address       MT     Size
01d332c4 002a354c       12     
total 1 objects
Statistics:MT    Count    TotalSize Class Name
002a354c        1           12 Anytao.Insidenet.MetadataIL.Three
  • 并根据MT地址,以dumpmt查看相关的MethodDesc信息,
!dumpmt -md 002a354c
EEClass: 002a15b4
Module: 002a2f2c
Name: Anytao.Insidenet.MetadataIL.Three
mdToken: 02000003  (E:\anytao\Today\OnWriting\MetadataIL\Anytao.Insidenet.MetadataIL\Anytao.Insidenet.MetadataIL\bin\Debug\Anytao.Insidenet.MetadataIL.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 10
--------------------------------------
MethodDesc TableEntry MethodDesc      JIT Name
6f756a70   6f5d1328   PreJIT System.Object.ToString()
6f756a90   6f5d1330   PreJIT System.Object.Equals(System.Object)
6f756b00   6f5d1360   PreJIT System.Object.GetHashCode()
6f7c7460   6f5d1384   PreJIT System.Object.Finalize()
002ac0b8   002a3514     NONE Anytao.Insidenet.MetadataIL.Three.N()
002ac0d0   002a3540      JIT Anytao.Insidenet.MetadataIL.Three..ctor()
002ac0a8   002a34f4     NONE Anytao.Insidenet.MetadataIL.Three.get_ID()
002ac0b0   002a3504     NONE Anytao.Insidenet.MetadataIL.Three.set_ID(Int32)
002ac0c0   002a3520     NONE Anytao.Insidenet.MetadataIL.Three.M()
002ac0c8   002a3530     NONE Anytao.Insidenet.MetadataIL.Three.M1()

经过简单的Dump,方法表的信息和我们图示的信息相差无几,细心的观众可能会发现Dump信息中并不包含Three::cctor(),那么你答对了。图示的cctor是我基于为Three实现了类型构造器(静态构造函数)而特别加入的,而代码中dump的方法表并没有把类型构造器包含在内,这是个小粗心,希望细心的您看得够透。

执行细则

具体的执行过程为为:

  • class loader从TypeDef元数据表加载相关元数据信息,包括当前类型,继承层次的所有父类和实现的接口元数据,根据这些信息建立CORINFO_CLASS_STRUCT结构:

当然,对class loader,我们可以进行一点知识救急:

 

上课啦:class loader

Classic Loader是CLR提供的基本组件之一,作用正像其名称所宣扬的那样,load一个Class给CLR,class loader将Metadata和IL从PE文件中取出,并加载到运行时内存,简单的说就是我们下面要介绍的全过程缩影。

当然,如果你总是对CLR的Classic Loader耿耿于怀,不能释然。那么,我们也可以参考MSDN的资料来实现自定义的Classic Loader[How to: Write a Class Loader],希望其中能提供灵光一现的思考。

                                                                                      www.anytao.com

 

  • 加载之后,方法执行之前的CORINFO_CLASS_STRUCT中所有的方法表槽都保存了方法应该执行的行为逻辑,这些信息保存在被称为方法描述(MethodsDesc)的结构中,而MethodDesc则被初始化为指向IL代码,同时还包含一个指向触发JIT编译的PreJitStub地址,如下:

上述所有方法描述都指向各自的IL代码地址和JIT编译器,在此我们仅仅以N()方法为例来进行说明,详细的情况可以参考MSDN相关内容。

  • 简单的说,任何方法第一次执行时都会首先触发执行JIT编译,JIT的主要工作就是将IL代码翻译为Native Code,并插入指向Native Code的jmp指令地址覆盖原来的Call JIT Compiler指令:

  • 当该方法再次被执行时,因为MethodDesc中保存了机器码地址,以后的执行将不会执行JIT编译过程而直接执行x86(X64)机器码,实现整个执行过程。

纵观整个JIT编译的全过程,其细节的实现远比我们这里呈现的复杂,在粗略的步骤中我们大致了解了元数据和IL在整个过程中的作用、角色和关系,对了解CLR运行机制而言,适当的选择是明智的,如果有更多的心思探索,那么就在以后的岁月中由简及繁吧,但是相信这一定是一次美妙的旅程。

6 结论

Metadata描述了静态的结构,而IL阐释了动态的执行,这一静一动承载了太多的技术奥秘。

当这篇文章行将结束的时候,我发现牵一发而动全身,由此引入的新问题接踵而至,方法调用、程序集、程序域、CLR加载过程在元数据和IL的分析中若隐若现,也驱使我投入注意在后面的《你必须知道的.NET》中,将这些内容一一过招。由此才能在复杂的概念和本质之余,由点及面的对所有内容综合把握,形成全面的了解和一条线贯穿的认识,那么未来Anytao将要继续分享还有:

系列预告
  • 程序集和模块
  • 程序域
  • CLR加载过程
  • JIT编译
  • 方法调用
  • 反射种种
  • 其他…

                                                                                      www.anytao.com

限于繁忙的原因,我无法给出一个清晰的时间表,但力图每次的内容都给您出足够的收获,如果你对.NET始终心怀兴致,那么敬请期待《你必须知道的.NET》更多精彩。 


支持anytao的创业产品Worktile
Worktile,新一代简单好用、体验极致的团队协同、项目管理工具,让你和你的团队随时随地一起工作。完全免费,现在就去了解一下吧。
https://worktile.com

  • 《你必须知道的.NET》第3章 “一切从IL开始”
  • DonBox,《.NET本质论》
  • http://www.sloppycode.net/articles/inside-net-assemblies-and-metadata.aspx
  • http://www.codeproject.com/KB/dotnet/dotnetformat.aspx

温故知新

这篇关于[你必须知道的.NET]第二十六回:认识元数据和IL(下)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/560952

相关文章

Python中你不知道的gzip高级用法分享

《Python中你不知道的gzip高级用法分享》在当今大数据时代,数据存储和传输成本已成为每个开发者必须考虑的问题,Python内置的gzip模块提供了一种简单高效的解决方案,下面小编就来和大家详细讲... 目录前言:为什么数据压缩如此重要1. gzip 模块基础介绍2. 基本压缩与解压缩操作2.1 压缩文

MySQL 删除数据详解(最新整理)

《MySQL删除数据详解(最新整理)》:本文主要介绍MySQL删除数据的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、前言二、mysql 中的三种删除方式1.DELETE语句✅ 基本语法: 示例:2.TRUNCATE语句✅ 基本语

MyBatisPlus如何优化千万级数据的CRUD

《MyBatisPlus如何优化千万级数据的CRUD》最近负责的一个项目,数据库表量级破千万,每次执行CRUD都像走钢丝,稍有不慎就引起数据库报警,本文就结合这个项目的实战经验,聊聊MyBatisPl... 目录背景一、MyBATis Plus 简介二、千万级数据的挑战三、优化 CRUD 的关键策略1. 查

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

mysql中的数据目录用法及说明

《mysql中的数据目录用法及说明》:本文主要介绍mysql中的数据目录用法及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、版本3、数据目录4、总结1、背景安装mysql之后,在安装目录下会有一个data目录,我们创建的数据库、创建的表、插入的

Navicat数据表的数据添加,删除及使用sql完成数据的添加过程

《Navicat数据表的数据添加,删除及使用sql完成数据的添加过程》:本文主要介绍Navicat数据表的数据添加,删除及使用sql完成数据的添加过程,具有很好的参考价值,希望对大家有所帮助,如有... 目录Navicat数据表数据添加,删除及使用sql完成数据添加选中操作的表则出现如下界面,查看左下角从左

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM

SpringBoot中4种数据水平分片策略

《SpringBoot中4种数据水平分片策略》数据水平分片作为一种水平扩展策略,通过将数据分散到多个物理节点上,有效解决了存储容量和性能瓶颈问题,下面小编就来和大家分享4种数据分片策略吧... 目录一、前言二、哈希分片2.1 原理2.2 SpringBoot实现2.3 优缺点分析2.4 适用场景三、范围分片

Redis分片集群、数据读写规则问题小结

《Redis分片集群、数据读写规则问题小结》本文介绍了Redis分片集群的原理,通过数据分片和哈希槽机制解决单机内存限制与写瓶颈问题,实现分布式存储和高并发处理,但存在通信开销大、维护复杂及对事务支持... 目录一、分片集群解android决的问题二、分片集群图解 分片集群特征如何解决的上述问题?(与哨兵模

浅析如何保证MySQL与Redis数据一致性

《浅析如何保证MySQL与Redis数据一致性》在互联网应用中,MySQL作为持久化存储引擎,Redis作为高性能缓存层,两者的组合能有效提升系统性能,下面我们来看看如何保证两者的数据一致性吧... 目录一、数据不一致性的根源1.1 典型不一致场景1.2 关键矛盾点二、一致性保障策略2.1 基础策略:更新数