CLR的执行模型(4):执行程序集的代码

Posted renzhoushan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CLR的执行模型(4):执行程序集的代码相关的知识,希望对你有一定的参考价值。

一直觉得,一本书的第一章是一定要读通的,这样子才知道这本书适不适合自己。所以,对于第一章的内容,我就啰嗦一些了。

托管程序集同时包含元数据和IL。IL是和CPU无关的机器语言,并且比大多数CPU语言都高级。IL能访问和操作对象类型,并且提供了创建和初始化对象的指令、调用对象上的虚方法以及直接操作数组元素。甚至实现了抛出和捕捉异常的指令,所以可将IL看成一种机器语言。

为了执行方法,首先必须吧方法的IL转换成本机CPU指令。这是CLR的JIT的职责。

下图展示了一个方法首次调用时候发生的事情:

技术图片

在Main方法执行之前,CLR会检查Main的代码所引用的所有类型。这导致CLR分配一个内部数据结构来管理对引用类型的访问。图中,Main方法引用了一个Console类型,导致CLR分配一个内部结构。在这个内部结构中,COnsole类型定义的方法都有一个对应记录项。每个记录项都含有一个地址,根据地址即可找到方法的实现。对这个结构初始化时,CLR将每个记录项都指向包含在CLR内部的一个未编档函数。我们将该函数成为JITComplier。

Main方法首次调用WriteLine时,JITComplier函数会被调用。JITComplier函数负责将方法的IL代码编译成本机CPU指令。这个组件被称为JIT编译器或者JITter。

JITCompiler函数被调用时,要知道调用的是哪个方法,以及具体是什么类型定义了该方法。然后JITCompiler会在定义程序集的元数据中查找被调用方法的IL。接着,JITCompiler验证IL代码,并将IL代码编译成本机CPU指令。本机CPU指令保存到动态分配的内存块中。然后,JITCompiler回到CLR为类型创建的内部数据结构,找到与被调用方法对应的那条记录,修改最初对JITCompiler的引用,使其指向内存块的地址,最后,JITCompiler函数跳转到内存块中的代码,既WriteLine方法的具体实现。代码完成并返回时,会回到Main中代码继续执行。

然后Main要第二次调用WriteLine:

技术图片

第二次调用时,由于已对Write Line的代码进行了验证和编译,所以会直接执行内存块中的代码,完全跳过JITCompiler函数。执行完毕后会再次返回Main。

所以方法仅在首次调用才有性能损失。

 在实际上,托管应用程序的性能是超越了非托管的。

IL

IL基于栈。也就是说,IL的所有指令都要将操作数压入一个执行栈,并从栈弹出执行结果。由于IL没有提供操作寄存器的指令,所以人们很容易创建新的语言和编译器,生成面向CLR的代码。此外,IL指令还是无类型的。

IL的最大优势是程序的健壮性和安全性。将IL编译成本机CPU指令时,CLR会执行一个验证过程。这个过程会检查高级IL代码,确定代码所做的一切都是安全的。托管模块的元数据包含验证过程要用到的所有方法及类型信息。

WIndows每个进程都有自己的虚拟地址空间。独立地址空间之所以必要,是因为不能简单地信任一个应用程序的代码:应用程序完全有可能读写无效的内存地址。每个Windows进程都放到独立的地址空间,获得鲁棒性和稳定性,让一个进程干扰不到另一个进程。

但是,通过验证托管代码,可确保代码不会不正确地访问内存,不会干扰到另一个程序的代码。这样就可以将多个托管应用程序放到同一个Windows虚拟地址空间运行。

CLR提供了一个在操作系统进程中执行多个托管应用程序的能力,每个托管应用程序都在一个AppDomain(应用程序域)中执行。妹妹个托管EXE文件默认都在它自己的独立空间中运行,这个地址空间只由一个应用程序域。

不安全的代码

C#编译器默认生成安全的代码。这种代码的安全性可以验证。然而,编译器也允许开发人员写不安全的代码,不安全代码通过直接控制内存地址,并可以操作这些地址处的字节。这种功能通常只有在与非托管代码进行互相操作,或者在某些算法中,才需要这样做。

C#编译器要求包含不安全代码的所有方法都用unsafe关键字标记。除此以外,c#编译器要求使用/unsafe开关来编译源代码。


以上是关于CLR的执行模型(4):执行程序集的代码的主要内容,如果未能解决你的问题,请参考以下文章

CLR的执行模型

clr的执行模型(中)

C#基础之CLR的执行模型

CLR的执行模型(2):将托管模块合并成程序集

底层运行机制CLR-C#

CLR详解CLR中的程序集