第一章 CLR执行模型

发布时间 2023-04-18 00:07:43作者: x_amos

1.程序的编译

所有支持CLR的语言都会使用其编译器将源代码编译为托管模块;

什么是托管模块?
托管模块是需要CLR才能执行标准的PE文件。

PE文件就是可以被执行或者间接执行的可移植执行体(在windows之间可移植)。如.exe,.dll文件等。
但C/C++生成的.exe或者dll并不是托管模块(微软实现的可托管的C++除外),就是说,托管模块可以是.exe,.dll,反过来不一定成立。

托管模块包括四个部分:
	标准PE文件头:包含一些偏移地址等信息,标识文件类型等功能;
	CLR头:包含一些标志,要求的CLR版本,入口方法的数据等信息;
	元数据:描述源代码中定义的类型和成员和引用的类型和成员,其实就是一个数据表的集合;
	IL(中间语言)代码:编译器生成的代码。最后由CLR将IL代码编译成CPU指令。

2.CLR和托管模块

托管模块可以单独或者和资源文件一起,被组合或者独立由工具生成为程序集文件,但是或多出一个描述程序集的清单的数据块。

其实就是托管模块,资源文件都是程序集的组成部分,可以单独组成程序集,也可以共同组成程序集。

C#编译器默认生成的就是程序集。

程序集清单是元数据表的集合,描述了构成程序集的文件,程序集中的public类型,程序集关联的资源文件或数据文件。

CLR直接工作对象就是程序集,CLR管理程序集中代码的执行。

3.程序集的加载

Windows检查 EXE 头文件,决定创建32位还是64位的进程,然后在进程地址空间加载对应的 MSCoreEE.dll,进程的主线程调用 MSCoreEE.dll 中方法,这个方法初始化 CLR,加载 EXE 程序集,再调用入口方法 Main,之后托管应用程序启动并运行。

为了执行程序中的方法,首先必须把方法的IL转成本机CPU指令,这是CLR的JIT(just-in-time)编译器的职责。

CLR 检测 Main 函数所有的类型,分配一个内部数据结构来管理对引用类型的访问,这个内部结构中,每个方法都有一个对应的记录项,每个记录项有一个地址,用来找到方法的实现。对于这个结构的初始化,是将每个记录项设置成包含CLR内部的一个未编档函数,这个函数就是 JITCompiler。

只有当记录项中的函数第一次被调用的时候,JJITCompiler 才会被调用来将方法的IL编译成本机CPU指令。

第二次调用此方法,因为已经对代码进行了验证和编译,会直接执行内存块中的代码,完全跳过 JJITCompiler 函数。

JIP编译器会将CPU指令存储到动态内存中,所以一旦应用程序关闭,编译好的代码也会被丢弃。所以在此运用此程序会再次调用 JIT编译器来编译IL代码,形成 CPU 指令。

JITCompiler函数的作用:
	1.根据程序集中的元数据查找被调用的方法
	2.从元数据中获取该方法的 IL
	3.分配内存块
	4.将 IL 编译成本机 CPU 指令,并将这些代码存到分配的内存块中
	5.在Type表中修改对应方法的条目,并指向分配的内存块
	6.跳转到内存块的代码