NET CLR via C#读书笔记 - 第一章 CLR执行模型

CLR执行模型

1.1 CLR简介

CLR(Common Language Runtime(公共语言运行时))可以简单理解为是一个支持多种编程语言及多语言互操作,完整的高级虚拟机。

  程序在运行的时候有着惊人数量的运行时依赖。每个有用的程序都需要某些运行时函数库,以便其能跟电脑的其它资源(如用户输入设备,磁盘文件,网络通信等)交互,同时程序也需要转换成计算机硬件可以直接执行的某种格式。这些依赖的数量是多而广的,使得编程语言的设计者通常都引用其它标准来规范它们。例如C++编程语言不会规定C++程序的格式,每个C++编译器都会与特定的硬件架构(如x86架构)关联,与特定的操作系统环境(如Windows,Linux或者Mac OS)关联,这些架构和环境会规定可执行文件的文件格式以及加载的方式。因此,程序员不是在编写一个“C++可执行程序”,而是编写与架构和环境相关的可执行程序,例如“Windows X86可执行程序”或“Power PC Mac OS可执行程序”。
  CLR通过定义一个 [非常完整的规范]ecma-spec来描述一个程序从编译、到部署时绑定依赖、到运行整个生命周期的所有信息。CLR还定义了以下内容:
  ① 一套支持GC,并包含自己的执行程序基本操作的指令集(通用中间语言 - CLI)的虚拟机,这也就意味着CLR不需要依赖指定类型的CPU;
  ② 一套描述程序里声明的元素(如类型、值、变量等等)的元数据,以便编译器在生成其它可执行文件时有足够的信息来从“外部”调用程序里的功能;
  ③ 一个精确描述字节应该如何在文件里布局的文件格式,这样我们在讨论CLR EXE的时候,不与某个特定的操作系统或电脑硬件绑定;
  ④ 进程的生命周期语义,即一个CLR EXE引用其它CLR EXE的机制,和CLR在运行时找到进程依赖文件的规则;
  ⑤ 利用CLR内置功能(如垃圾回收、异常和泛型等)的类库,其除了提供如整形、字符串、数组、列表和字典等基本功能意外,还提供了如文件、网络和UI交互等操作系统服务。
多编程语言支持
  定义、规范和实现这些细节是一个艰巨的任务,这也就是类似CLR的完整抽象非常少的原因。实际上,大部分抽象都是为单个编程语言设计的。例如,Java运行时,Perl解释器或者早期的Visual Basic运行时提供了类似的完整抽象。但CLR跟这些先行者不同之处在于其支持多种编程语言。可能除了Visual Basic(因为它采用了COM对象模型),仅使用单个编程语言的体验是非常好的,但是要与其它编程语言互操作时体验就有点差了。编程语言之间互操作之所以困难,是因为这些编程语言仅能通过操作系统提供的原语来与“外族”编程语言通信。而操作系统的抽象层次太低阶(如操作系统不提供内存垃圾回收),就不得不采用一些复杂的技术。通过提供 通用语言运行时,CLR允许编程语言之间采用高阶结构(如可GC的数据结构)通信,大量减轻了互操作的麻烦。
  由于运行时在 许多 语言之间共享,这就意味着更多的资源可被支持。为一个编程语言实现好的调试器和性能分析工具需要大量的工作,因此只有一些很重要的编程语言才有完整的工具链支持。然而,CLR上实现的编程语言可以共享这些基础架构,实现新的编程语言的工作量也大大缩减了。也许更重要的是,所有在CLR上实现的编程语言都可以访问 所有 在CLR上实现的类库。庞大且不断增长的(严格调试和支持)功能是CLR如此成功的一个重要原因。
  简单来讲,CLR是一个将字节存到文件以创建和运行程序的完整规范。虚拟机可以使用不同编程语言写就的类库来运行这些程序。这个虚拟机,还有运行其上的不断增长的类库,就是我们说的通用语言运行时(CLR)。

以上内容为转载内容,如需了解更多请点击跳转阅读

1.2 CLR执行模型

CLR执行模型可以归纳为以下部分:
① 将源代码编译成托管模块
② 将托管模块合并成程序集
③ 加载CLR
④ 执行程序集代码

1.2.1 将源代码编译成托管模块

  将源代码编译成托管模块,此工作由面向CLR的编译器负责完成,托管模块是标准的32位Microsoft Windows可移植执行体(PE)文件,或者是标准的64位Microsoft Windows可移植执行体(PE32+)文件。
编译出来的托管模块包含以下内容:

组成部分说明
PE32/PE32+头PE32格式,能在WIN 32或64位版本操作系统中运行。
PE32+格式,只能在WIN 64位版本操作系统中运行。
头信息中包含文件类型标识(GUI,CUI或DLL),时间标识(文件生成时间),如果托管模块中包含本机(native)CPU相关代码,则还需要额外包含本机CPU相关信息。
CLR头包含CLR版本信息,一些标志信息(flag),托管模块入口函数(main)的方法定义,模块的元数据,资源,强名称以及其它相关信息
元数据包含元数据表,主要有两种表,一种是记录代码中定义的类型和成员,另一种是代码中引用的类型和成员。
IL代码面向CLR的编译器编译源代码时生成的代码,该代码在运行时,由CLR加载并编译成本机CPU代码。

补充说明:托管模块中元数据和IL代码是强关联的,元数据与IL代码永远保持同步。

1.2.2 将托管模块合并成程序集

程序集是一个或多个模块/资源文件的逻辑性分组,CLR实际不直接和托管模块工作,实际是和程序集工作,程序集是重用,安全性以及版本控制的最小单元,在CLR中,程序集就相当于“组件”。

关于程序集的描述会在后续的读书笔记中详细描述,此处暂不展开讨论。

1.2.3 加载CLR

  前文简单描述了CLR是什么,所以用户计算机必须先安装好CLR,CLR包含在Microsoft .Net Framework框架中,关于Microsoft .Net Framework框架如何安装,此处不进行描述。
  用户计算机执行exe文件时,检查exe文件头,决定创建32位进程还是64位进程,随后在进程地址空间加载MSCorEE.dll的x86,x64或者ARM版本,然后进程的主线程调用MSCorEE.dll中定义的一个方法,这个方法初始化CLR,加载exe程序集,再调用程序集中的入口方法(main),由此exe启动完成并开始运行。

1.2.4 执行程序集代码

  为了在某一具体CPU机器上执行程序,首先要做的就是将托管模块中的IL代码转换为本机(native)CPU指令,这一工作由JIT(just-in-time)编译器完成。
执行流程

第一步:Main()方法执行前,CLR检测Main()方法中引用的所有类型,每一个检测出来的引用类型都会为其分配一个内部数据结构,用于映射源代码中定义或者引用的类型,本流程中Main()方法中引用了Console类型,所以CLR会生成一个Console的内部数据结构类型,内部数据结构中对原类型中所有方法都会有一个对应的记录项,并与之对应生成一个未编档函数(此处记为JITCompiler)。
第二步:Main()首次调用WriteLine时,会调用JITCompiler函数,函数内部执行步骤如上图所示,可分为下面6个小步骤:
① 在元数据中查找调用的方法
② 查找到后再查找与其相关的IL代码
③ 分配内存空间
④ 验证IL代码并编译成本机代码,并将本机代码保存到分配的内存空间中
⑤ 修改内部数据结构中与本JITCompiler对应的记录,使其指向本机代码保存的内存空间
⑥ 跳转到本机代码保存的内存空间,执行完成后返回Main()中。
第三步:第二次调用相同函数时,不需要再进行相关内存分配和验证操作,而是直接跳转到该函数的本机代码内存地址,直接执行相关函数。

  以上内容为对《NET CLR via C#(第四版)》第一章内容的阅读笔记,只记录其中核心部分内容,如需要详细阅读请自行查看本书第一章内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值