CLR via C# 读书笔记(一)CLR的执行模型

时间:2022-12-28 17:26:17

一、公共语言运行时

公共语言运行时(Common Language Runtime,CLR)是一个可由多种编程语言使用的“运行时”。
核心功能:内存管理,程序集加载,安全性,异常处理和线程同步
面向CLR的所有语言可以使用它所有的核心功能

二、托管模块

1.基本概念

托管模块(managed module)是一个标准的32位Microsoft Windows可移植执行体(PE32)文件,或者是一个标准的64位Windows可移植执行体(PE32+) 文件,他们需要CLR才能执行
可用任何支持CLR的语言创建源代码文件,然后对应的编译器检查语法和分析源代码,无论哪个编译器,输出结果都是托管模块

2.模块构成

托管模块由以下部分组成:
1)PE32或PE32+头
 标准Windows PE文件头,PE32格式文件能运行在32或64位系统上,PE+只能 运行在64位系统上
 这个头还标识了文件类型,包括GUI,CUI或者DLL,并包含一个时间标记来指出文件的生成时间
 只包含IL代码的模块,PE32(+)头的大多数信息会被忽略
 包含本机(native)CPU代码的模块,这个头包含与本机CPU代码有关的信息
2)CLR头
 包含使这个 模块 成为 托管模块 的信息
 头中包括:要求的CLR版本,一些标志(flag),托管模块入口方法(Main方法)的MethodDef元数据token以及模块的元数据资源强名称,一些标志及其他不太重要的数据项的位置,大小
3)元数据(metadata)
 每个托管模块都包含元数据表
 主要有两种表:
  表描述源代码中定义 的类型和成员
  表描述源代码中引用 的类型和成员
 元数据 是一些老技术的超集,老技术包括
  COM的“类型库”(Type Library)
  “接口定义语言”(Interface Definition Language,IDL)
 元数据总是和包含IL代码的文件关联

 编译器同时生成元数据和代码,并一起嵌入托管模块,所以元数据和IL代码永远不会失去同步

本机代码编译器(native code compilers)
 生成面向特定CPU架构的代码,如,x86,x64或ARM
CLR编译器
 生成的都是IL(Intermediate Language 中间语言)代码
 IL有时又给称为托管代码(managed code),因为CLR管理它的执行

三、程序集

程序集是抽象概念
 首先,它是一个或多个模块/资源文件的逻辑性分组
 其次,它是重用,安全性和版本控制的最小单元
CLR via C# 读书笔记(一)CLR的执行模型

四、执行程序集的代码

高级语言通常只公开了CLR全部功能的一个子集,然而IL汇编语言允许开发人员访问CLR的全部功能

执行方法,需要把方法的IL转换成本机CPU指令

CLR via C# 读书笔记(一)CLR的执行模型
1)Main执行前,CLR会检测出代码中引用的所有类型,并分配一个内部数据结构来管理引用类型的访问

2)在内部数据结构中,Console类型定义的每个方法都有一个对应的记录项(Entry)
 每个记录项都有一个地址,根据地址即可找到方法的实现
 内部数据结构初始化时,CLR将每个记录项设置成指向包含在CLR内部的一个未编档函数JITCompiler

3)Main方法首次调用WriteLine时,JITCompiler函数会被调用
 JITCompiler负责将IL代码编译成本机CPU指令
由于IL是即时(just in time)编译的,通常CLR的这个组件被称为JIT编译器

JITCompiler

JITCompiler函数第一次被调用时;
1) JITCompiler会在定义该类型的程序集的元数据中,查找被调用方法的IL
2)JITCompiler验证IL代码,并将IL代码编译成本机CPU指令,保存到动态分配的内存中
3)JITCompiler回到CLR创建的内部数据结构,找到被调用方法的那条记录,修改最初对JITCompiler的引用,使其指向内存块的地址
4)JITCompiler跳转到内存块中的代码

第二次调用,以为之前已经验证并编译了代码,所以会之久执行内存块中的代码
方法仅在首次调用时才会有些性能损耗,之后所有调用都以本机代码的形式运行,无需重新验证IL并把它编译成本机代码

JIT编译器本机CPU指令存储到动态内存中,这意味着,程序一旦终止,编译好的代码也会被丢弃
所以
1)再次运行程序
2)同时启动程序的两个实例(使用两个不同的进程)
这可能显著增加内存耗用
本机(native)应用程序只读代码页 可由应用程序正在运行的所有实例共享

五、IL和验证

IL基于栈,它的所有指令都要将操作数压入执行栈,并从栈弹出结果
IL指令是无类型的

IL的优势:
1)应用程序的健壮性(robustness)和安全性(reliability)
健壮性:系统对于参数变化的不敏感性
可靠性:描述系统的正确性

CLR执行一个名为验证(verification)的过程,这个过程会检查高级IL代码,确定代码所做的一切都是安全的
托管模块的元数据包含验证过程要用到的所有方法及类型信息
windows的每个进程都有自己的虚拟地址空间
将每个进程都放到独立的地址空间,将获得健壮性与稳定性,一个进程干扰不到另一个进程,通过验证托管代码,可确保代码会正确的访问内存

2)用一个进程运行多个应用程序,可减少进程数,从而增强性能,减少所需的资源,健壮性也丝毫没有下降
CLR确实提供了在一个操作系统进程中执行多个托管应用程序的能力,每个托管应用程序都在一个AppDomain中执行
CLR的宿主进程可决定在一个进程中运行多个AppDomain