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

部分CLR基础。这部分为三章(第一章:CLR的只想能够模型,第二章:生成、打包、部署和管理应用程序及类型,第三章:共享程序集和强命名程序集)大部分都是概念性的东西,看起来比较的枯燥无味,由于都是概念性的东西所以也比较难于理解,不过这些知识也是后面深入的前提,要想深入的了解.net平台及C#语言,这些概念性的知识也是必须懂得的。废话不多说了,咱们还是硬着头皮一起来看看第一章吧!
由于一章内容较多,我会分开来写。
本篇内容大纲:

  1. 将源代码编译成托管模块
  2. 将托管模块合并成程序集
  3. 加载公共语言运行时
  4. 执行程序集的代码

一、将源代码编译成托管代码

1、公共语言运行时(Common Language Runtime,CLR)

CLR 是一个运行环境,一个可由多种编程语言使用的运行环境。CLR的核心功能包括:如内存管理、程序集加载、安全性、异常处理和线程同步等,可由面向CLR的所有语言使用。CLR是.NET Framework的主要执行引擎。
事实上,在程序运行时,CLR并不关心开发人员使用的是哪一种编程语言。因为我们使用任何一种支持CLR的编程语言创建源代码,最后都会被对应的编译器编译成一个托管模块(managed module)。如下图所描述:

托管模块 是一个标准的32位的Windows 可移植执行体(Portable Executable)文件简称(PE32),或者是个64位的(PE32+)文件。他们都需要CLR才能执行。
下表展示了一个托管模块的各个组成部分

组成部分 说明
PE32或PE32+ 头 标准windows PE 文件头,类似于“公共对象文件格式”(Common Object File Format,COFF)头。如果这个头使用PE32格式,文件能在Windows 的32位和64位版本上运行。如果这个头使用PE32+格式,文件只能在Windows的64位版本上运行。这个头还标识了文件类型,包括 GUI,CUI或者DLL,并包含一个时间标记来指出文件的生成时间。对于只包含IL代码的模块,这个头包含了与本地CPU代码有关的信息。
CLR 头 包含使这个模块成为一个托管模块的信息(可由CLR和一些实用程序进行解释)。头中包含了需要的CLR版本,一些标志(Flag),托管模块的入口方法(Main方法)的MethodDef 元数据标记(Token),以及模块的元数据、资源、强名称、一些flag以及其他不太重要的数据项的位置/大小。
元数据 每个托管模块都包含元数据表。主要有两种类型的表:一种类型的表描述源代码中定义的类型成员;另一种类型的表描述源代码中引用的类型成员。
IL(中间语言)代码 编译器编译源代码时生成的代码。在运行时,CLR将IL代码编译成本地CPU指令。

2、元数据

每个面向CLR的编译器生成的都是IL(Intermediate language)中间语言代码。IL代码有时也成为托管代码,因为CLR要管理它的执行。除了生成IL代码,面向CRL的每个编译器还要在每个托管模块中生成生成完整的元数据。我们知道IL是各种面向CLR的编程语言生成的统一由CLR管理执行的中间语言,那元数据又是什么呢?简单的说,元数据(metadata)是一组数据表。其中一些数据表描述了托管模块中定义的内容,比如模块中定义的类型、成员等。还有一些数据表描述了托管模块中导入的类型及成员。正如上面表格中的描述的一样,元数据表分为两种类型,一种类型的表是描述模块自身源代码中定义的类型和成员;另一种类型是描述引用的类型和成员。总之元数据就是要用来描述我们源代码的一种数据表。这里的源代码是指的编译后IL代码,元数据总是与它描述的IL代码关联在一起。事实上,元数据总是嵌入和代码相同的exe/dll文件中,这使元数据和它所描述IL代码密不可分。由于编译器同时生成元数据和IL代码,把他们绑定在一起,并嵌入最终生成的托管模块,所以元数据和它描述的IL代码永远都不会失去同步。元数据有多种用途,下面列出一部分常用的用途

元数据部分用途

  • 编译时,元数据消除了对本地C/C++头和文件的需求,因为在负责实现类型/成员的IL代码文件中,已经包含了引用的类型/成员的全部信息。编译器可以从托管代码中读取元数据。
  • 我们强大的VS 编辑器使用元数据帮助我们写代码。它的”智能感知“(IntelliSense)技术可以解析元数据,指出一个类型提供了哪些方法、属性、事件、和字段。如果是一个方法,还能指出该方法需要什么参数。
  • CLR 的代码验证过程使用元数据确保代码只执行”类型安全”的操作。(稍后会讲到)
  • 元数据允许将一个对象的字段序列化到一个内存块中,将其发送到另一台机器,然后反序列化,在远程机器上重建该对象。
  • 元数据允许垃圾回收器跟踪对象的生存期。垃圾回收器能判断任何对象,并从元数据中知道那个对象中的哪些字段引用了其他对象。

二、将托管模块合并成程序集

CLR实际上是不和模块一起工作的。相反,它是和程序集一起工作的。程序集(Assembly)是一个抽象的概念,首先,程序集是一个或多个模块/资源文件的逻辑性分组。其次,程序集是重用、安全性、及版本控制的最小单元。取决于你对编译器或工具的选择,既可以生成单文件程序集,也可以生成多文件程序集。在CLR的世界中,程序集相当于一个“组件"。第二章会深入讲解程序集,这里就不花费太多的篇幅了,我们只需要知道:利用程序集这种概念性的东西,可以将一组文件当作一个单独的实体来对待。下图展示了程序集是怎样将模块和资源文件合并的

默认情况下,编译器实际会把生成的托管模块转换成程序集。所以,假如项目只有一个托管模块,没有资源文件,那么程序集就是托管模块,而且在生成过程中不需要采取任何额外的步骤。但是如果希望将一系列文件合并到一个程序集中,及必须掌握更多的工具(比如程序集连接器AL.exe)以及他们的命令选项。第二章会解释这些工具和选项。

在程序集的模块中,还包含与引用的程序集有关的信息(包括它们的版本号)。这些信息使得程序集能够自描述(self-describing)。

三、加载公共语言运行时

1、.NetFramework

我们生成的程序集既可以是一个可执行的应用程序(如winform 程序,控制台程序等),也可以是一个DLL(dynamic link Library)动态连接库。最后都是又CLR来管理这些程序集中的代码执行,这意味着必须在目标机器上安装好.NET Framework。要是知道目标机器上是否已安装.NET Framework,只需要检查 %SystemRoot%\System32 目录中是否有 MSCorEE.dll。然而一台机器上可能同时装有好几个版本的.NET Framework,要确切的知道已经安装了那些版本,请检查以下注册表的子项:
KEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP.
如果目标机器上装有 VS 还可以找到Visual Studio 命令提示 运行 CLRVer命令,即可列出当前机器上安装的所有CLR版本。

四、执行程序集

如前所述,托管程序集同时包含元数据和IL。IL是与CPU无关的机器语言,是Microsoft 在请教了外面的几个商业及学术性语言/编译器的作者之后,费尽心思开发出来的。IL表大多数CPU机器语言都要高级。通常,开发人员会用C#、VB、C++等高级语言来编程。这些高级语言的编译器会将代码生成IL中间代码,再交由CLR来管理执行。和其他机器语言一样,IL也能使用汇编语言来写,Microsoft甚至专门提供了一个名为ILAsm.exe的IL汇编器和一个名为 ILDasm.exe的IL反汇编器。

注意,高级语言通常只公开了CLR的一部分功能,而 IL 汇编语言允许开发人员访问CLR的所有功能。所以如果你选择的一种编程语言隐藏了你迫切需要的一个CLR的功能,你可以换用IL汇编语言或者提供了所需功能的另一种编程语言来写那部分代码。

1、方法的首次调用

为了执行一个方法,首先必须把它的IL代码转换成本地CPU指令。这是CLR的JIT(Just in time)即使编译器的职责。
下图展示是了一个方法首次调用发生的事情:

图1-方法的首次调用

上图解析:

1、在Main方法执行前,CLR会检测出Main方法代码引用的所有类型(上图Main方法引用了Console类型),并分配一个内部数据结构,用于管理对所引用的类型的访问。在这个内部数据结构中,Console类型的每个方法都有一个对应的记录项,每个记录项都容纳了一个地址,根据这个地址可以找到方法的实现。

2、CLR对这个内部结构初始化时,会将每个方法对应的记录项都设置成(指向)包含在CLR内部的一个未文档化的函数,我们称这个函数为 JITCompiler(即时编译器)。

3、当我们调用Console对应的方法时,会进到第一步中CLR分配的内部数据结构中,调用方法对应的记录项指向的JITCompiler,JITCompiler函数知道当前调用的是哪个方法,以及定义该方法的是哪个类型。然后,JITCompiler会在定义该类型的程序集的元数据中查找被调用的方法的IL代码,接着,JITCompiler验证IL代码,并将IL代码编译成本地CPU指令。动态分配一块内存空间,将刚编译的本地CPU指令保存进去。

4、JITCompiler返回到 CLR为类型创建的内部数据结构,找到与被调用方法对应的那一条记录,修改最初对JITCompiler的引用,让它现在指向刚分配的内存块(其中包含刚才编译好的本地CPU指令)地址。这些代码执行完毕返回时,就会返回到Main中的代码,并像往常一样继续执行。

以上是一个迭代的过程。每第一次执行一个方法,就会经过上面的几步。

2、方法的第二次调用

当Main方法第二次调用 WriteLine时。这一次由于已对WriteLine的代码进行了验证和编译,所以会直接执行内存块中的编译好的本地CPU指令,完全跳过JITCompiler函数。WriteLine方法执行完毕之后,会再次返回 Main方法。下图 展示了方法的第二次调用

图2-方法的第二次调用

由此可见,一个方法只有在首次调用时才会造成一些性能损失。以后对该方法的所有调用都是以本地代码的形式全速运行,无需重新验证IL并编译成本地代码。
JIT编译器将本地CPU指令存储到动态内存中。一旦应用程序终止,编译好的代码也会被丢弃。所以再次运行应用程序,JIT编译器必须再次将IL代码编译成本地指令。

3、JIT编译器对本地代码的优化

有两个C#编译器开关会影响代码的优化:/optimize 和 /debug。下表总结了这些开发对 C# 编译器生成的IL代码的质量的影响,以及对JIT编译器生成的本地代码的质量的影响。

编译器开关设置 C# IL 代码质量 JIT 本地代码质量
/optimize-   /debug-(默认) 未优化 有优化
/optimize-   /debug(+/full/pdbonly) 未优化 未优化
/optimize+  /debug(-/+/full/pdbonly) 有优化 有优化

在Visual Studio 中新建一个 C# 项目时,项目的“调试”(Debug)配置指定的是/optimize-和/debug:full 开关,而 “发布”(Release)配置指定的是/optimize+和/debug:pdbonly开关。

作者:邹毅
如果觉得本文让你有所收获,请键点击右下角的 推荐 按钮
本文版权归作者和博客园共有,欢迎转载,但必须保留原文连接。

 

第一章 CLR 的执行模型的更多相关文章

  1. 第一章 CLR的执行模型

    编译器将源代码编译为托管模块.托管木块包含: PE32或PE32+头 CLR头 元数据 IL(中间语言)代码 PE32头的文件可在32或64位的电脑上运行,PE32+的只能在64上运行.Window6 ...

  2. 第一部分 CLR基础:第1章 CLR的执行模型

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

  3. 第1章 CLR的执行模型

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

  4. CLR 的执行模型(2)

    第一章 CLR 的执行模型(2) 本篇内容大纲 Framework 类库(Framework Class Library , FCL) 通用类型系统(Common Type System,CTS) 公 ...

  5. 01.由浅入深学习.NET CLR 基础系列之CLR 的执行模型

    .Net 从代码生成到执行,这中间的一些列过程是一个有别于其他的新技术新概念,那么这是一个什么样的过程呢,有什么样的机制呢,清楚了这些基本的东西我们做.Net的东西方可心中有数.那么,CLR的执行模型 ...

  6. 第一章 CLR执行模型

    发现看过好几遍还是会忘记,因水平有限理解的不是很到位.欢迎各位大神及时指正. CLR执行模型 1.1编译器将源代码编译成托管模块 托管模块:是标准的windows可移植执行体文件(PE32(32位机器 ...

  7. 第一章、 CLR的执行模型

    1. 概述 本章主要是介绍从源代码到可执行程序的过程中,CLR所做的工作. 2. 名词解释 ① 公共语言运行时(Common Language Runtime, CLR),是一个可由多种语言使用的 运 ...

  8. 【C#进阶系列】01 CLR的执行模型——一个Hello World的故事

    好吧,废话少说,先上一章Hello World图: 我们有了一个Hello world程序,如此之简单,再加上我今天没有用汉字编程o(>﹏<)o,所以一切很简单明了. 故事开始: 编译: ...

  9. 第一章 Java代码执行流程

    说明:本文主要参考自<分布式Java应用:基础与实践> 1.Java代码执行流程 第一步:*.java-->*.class(编译期) 第二步:从*.class文件将其中的内容加载到内 ...

随机推荐

  1. 转让lua性能executeGlobalFunction

    没有其他的,搞搞cocos2dx的lua文字,话lua这件事情在几年前学过一段时间.还曾对自己c++介面,我已经做了一些小东西.只是时间的流逝,模糊记忆. 拿起点功夫和成本.下面是我的一些经验. co ...

  2. Cocos2d-x学习笔记(两)Cocos2d-x总体框架

    原创文章.转载请注明出处:http://blog.csdn.net/sfh366958228/article/details/38680123 前言 上一节我们简单分析了HelloWorldproje ...

  3. Eclipse的安装及汉化图解

    Eclipse的安装及汉化图解 Eclipse的安装 有了JDK,你可以编译Java源码,运行Java程序,但是还没有代码编辑器,没有版本管理工具,也不能方便的管理工程文件,不能与团队协作.安装Ecl ...

  4. 在Cocos2d-x正在使用SQLlite数据库

    SQLite,它是一个轻量级的数据库,合规ACID的关系型数据库管理系统,它的设计目标是嵌入式的,并且眼下已经在非常多嵌入式产品中使用了它,它占用资源非常的低.在嵌入式设备中,可能仅仅须要几百K的内存 ...

  5. swift学习:第一个swift程序

    原文:swift学习:第一个swift程序 最近swift有点火,赶紧跟上学习.于是,个人第一个swift程序诞生了... 新建项目

  6. Cracking Microservices practices

    微服务最佳实践 英文原文:Cracking Microservices practices 在我还不知道什么叫微服务架构的时候我就使用过它.以前,我写了一些管道程序(pipeline applicat ...

  7. UIApplicationMain方法介绍

    在IOS程序的main函数中执行了一个UIApplicationMain这个函数,下面介绍以下这个函数的作用. int UIApplicationMain(int argc, char *argv[] ...

  8. Android Studio怎样更改JDK和SDK的路径?

    这个对于非常多刚转到Android Studio上的来说,确实是一个问题.可能你在设置里面找了非常久都没找到这个选项. 直接上图吧,按下图就能够找到设置的地儿了,然后直接设置到你SDK或者JDK的路径 ...

  9. PHP经验——获得PHP版本信息及版本比较

    原文:PHP经验--获得PHP版本信息及版本比较 偶然看到别人写的一句代码: <?php if (version_compare("5.2", PHP_VERSION, &q ...

  10. 【iOS】Xib的使用与File&#39;Owner总结

    一.XIB的适用范围 xib(也叫Nib)与storyboard一样是用来描写叙述界面的. storyboard描写叙述的是比較大型的,大范围.适合描写叙述界面跳转等. 二.XIB的使用 Xib是小范 ...