zoukankan      html  css  js  c++  java
  • CLR基础小结

    时至今日,CLR基础部分的学习算是告一段落了。但是因为知识有些庞杂,所以希望在此处做一些总结,以整理所学所得。

    1.CLR的执行模型:

    简单来说,这部分所叙述的,就是如何将源代码生为一个应用程序,或者生成为一组可以重新分发的组件,以及这些组件中包含的类型。最后还解释了应用程序如何执行。

    源代码编译成托管模块

    首先,必须要了解的一个事情就是,CLR的核心功能,以及CLR在托管的时候所做的事情。CLR,全称Common Language Runtime,公共语言运行时,是一个可由多种编程语言使用的“运行时”。任何面相CLR的语言都可以使用CLR的核心功能。使用支持CLR的语言创建了源代码文件,然后用对应的编译器检查语法和分析源代码。无论是选择哪个编译器,都是生成托管模块(比如C#就是:C#源码通过C#编译器生成了托管模块)。所谓托管模块,就是可移植执行体文件(.exe,.dll,.scr等都是可移植执行体文件)。

    本机代码编译器(native code compilers)生成的是面相CPU架构的代码。而每个面相CLR的编译器生成的都是IL(Intermediate Language,中间语言)代码。IL代码也被称为托管代码,由CLR管理它的执行。除了生成IL,面相CLR的每个编译器还要在每个托管模块中生成完整的元数据。元数据是一个数据表的集合,描述了数据的各种引用、定义等。编译器会同时生成元数据和IL代码,将之绑在一起,嵌入进最终生成的托管模块,所以元数据和它描述的IL代码一直是同步的。

    编译器生成的数据,分为托管模块(包含托管代码IL和托管数据集的模块),和非托管模块(不需要CLR执行, 在运行时直接操纵非托管数据)。

    托管模块合并成程序集

    托管模块并不是CLR的工作对象,而程序集(assembly)才是。程序集,是一个或多个模块/资源文件的逻辑性分组,也是重用、安全性以及版本控制的最小单元。多个托管模块和资源文件,通过一些合并程序集所用到的工具,例如C#编译器(CSC.exe),合并成了程序集(托管模块合并成了IL和元数据,资源文件还是资源文件)。

    加载CLR

    生成的程序集,可以是.exe、.dll,最终由CLR管理这些程序集中代码的执行。换言之,机器必须安装好.NET Framework。在可执行文件运行时,Windows检查文件头(32位or64位);然后检查头中嵌入的CPU架构信息。在确定CPU符合要求后,运行该程序。

    为了运行托管应用程序,Windows检查EXE文件头,创建好进程后,会在进程地址空间加载MSCorEE.dll的x86,x64或ARM版本。然后,进程的主线程调用MSCorEE.dll中定义的一个方法。这个方法初始化CLR,加载EXE程序集,再调用入口函数main。

    如果是非托管,则应用程序调用LoadLibrary加载托管程序集,Windows会自动加载并且初始化CLR以处理程序集中的代码。

    执行程序集代码

    托管程序集同时包含元数据和IL。IL能访问操作对象类型,并提供了指令来初始化对象、调用对象上的虚方法以及直接操作数组元素,甚至可以抛出异常。可以把IL看成一种面向对象的机器语言。

    为了执行方法,首先必须把IL转换成本季CPU指令,这是CLR的JIT编译器的职责。在Main执行之前,CLR会检测出Main的代码所引用的所有类型。此时,CLR会分配一个内部数据结构来管理对引用类型的访问。在这个内部结构中,每个类型的每个方法都有一个对应的记录项(entry,也可以翻译成入口),每个记录项都有一个地址,根据这个地址可以找到对应方法的实现。对这个结构初始化的时候,CLR将每个记录下都设置成包含在CLR内部的一个未编档函数,也就是JITCompiler。

    Main首次调用函数的时候,JITCompiler被调用。JITCompiler负责将方法的IL代码编译成本机指令。因为IL是JIT(just-in-time,即时)编译的,所以把CLR的这个组件称为JIT编译器。JITCompiler的执行过程如下:

    ●在负责实现类型的程序集的元数据中查找被调用的方法

    ●从元数据中获取该方法的IL

    ●分配内存

    ●将IL编译成本机CPU指令,然后将这些本机代码存储到分配的内存中

    ●在Type表中修改与方法对应的条目,使其指向分配的内存块

    ●跳转到内存块中的本机代码

    在Main第二次调用的时候,因为已经对方法进行了验证和编译,所以会跳过JITCompiler直接执行内存块的代码。也正因如此,才使得仅在首次调用的时候才会发生性能损失。

    将IL编译成本机CPU指令的时候,有一个验证的过程。这个过程会检查IL代码,确定代码的安全性。

    Microsoft C#编译器默认生成安全的代码,但是也允许开发人员写不安全的代码。所谓不安全的代码,就是代码可以直接操作内存地址。使用的时候,不安全代码的方法需要用unsafe做标记。并且在编译的时候,需要使用/unsafe开关来编译源代码。

    NGen.exe

    NGen.exe工具,可以在应用程序安装到用户的计算机上时,将IL代码编译成本机代码。因为代码在安装的时候已经编译好,所以通常不需要在运行的时候编译IL代码。所以,NGen.exe的主要作用是:提高应用程序的启动速度,以及减少应用程序的工作集。

    每当CLR加载程序集文件,都会检查是否存在一个对应的、由NGen生成的本机文件。如果找不到本机文件,CLR就和往常一样对IL代码进行JIT编译。如果有对应的本机文件,CLR就直接使用本机文件中编译好的代码,不需要在运行时编译。

    但是,NGen生成的文件也会有如下问题:没有知识产权保护、生成的文件可能丢失同步、较差的执行性能。所以,服务端的应用程序,NGen的作用就不甚明显。而在客户端的程序,就可以提高启动速度、缩小工作集等。

    本章的其他内容为后续章节的概述,不涉及很复杂的理解,所以就不在此做过多赘述了。


    2.生成、打包、部署和管理应用程序及类型:

    在本章,将重点解释如何生成仅供自己的应用程序使用的程序集

    将类型生成到模块中

    Program.cs

     1 using System;
     2 
     3 namespace Project_1
     4 {
     5     public class Program
     6     {
     7         public static void Main(string[] args)
     8         {
     9             System.Console.WriteLine("HaHa");
    10        }
    11     }
    12 }

    执行命令,生成可执行文件Program.exe:csc.exe /out:Program.exe /t:exe /r:MscorLib.dll Program.cs

    可以简化为:csc.exe Program.cs

    响应文件,是包含一组编译器命令行开关的文本文件。执行CSC.exe时,编译器打开响应文件,并使用其中包含的所有开关。此外,编译器还会查找CSC.rsp文件。该文件引用了和CSC.exe相关的所有程序集。

    元数据

    元数据是由三种表构成的二进制数据块:定义表,引用表和清单表。编译源码的时候,任何定义的东西都导致在某个表中创建一个记录项。此外,编译器还会检测源码引用的类型、字段、方法、属性和事件,并创建相应的元数据表记录项。在创建的元数据表中包含一组引用表,包含了引用的内容。

    要查看元数据表,可以使用IL反汇编工具ILDasm.exe:ILDasm Program.exe

    将模块合并成程序集

     这部分内容涉及到很多实际操作,具体内容就不做赘述了,详情请见https://www.cnblogs.com/renzhoushan/p/10366289.html。这里只简述一下为什么这样做。

    之所以要引入“程序集”的概念,就是为了可重用类型的逻辑表示与物理表示可以分开。之所以使用多文件程序集,主要用如下三点理由:

    ●不同类型用不同的文件,使得文件可以以“增量”方式下载;

    ●可以在程序集中添加资源或数据文件;

    ●程序集包含的各个类型可以用不同的编程语言来实现。

    程序集版本资源信息和语言文化

     在生成PE文件程序集时,还会在PE文件中嵌入标准的Win32版本资源,可以查看文件属性来检查该资源的版本号。

     除了版本号,程序集还将语言文化作为身份标识的一部分。如果程序包含语言文化特有的资源,Microsoft会建议专门创建一个程序集来包含代码和应用程序的默认资源。生成该程序集时不要指定具体的语言文化。其他程序集通过引用该程序及来操纵它公开的类型。然后,创建一个或多个单独的程序集,只在其中包含语言文化特有的资源:不包含任何代码。标记了语言文化的程序集,被称为附属程序集。为附属程序集指定的语言文化应准确反映程序集中的资源的语言文化。想要支持的每种语言文化都要创建单独的附属程序集。

    简单的应用程序部署:私有部署的程序集

     不同的程序集打包方式不同。例如,Windows Store应用在打包的时候,VS会将所有必要的程序集打包成一个.appx文件。用户在安装的时候,其中包含的所有程序集都进入一个目录,CLR从该目录加载程序集,Windows则在用户的“开始”添加对应的程序磁贴。如果是其他用户安装同样的.appx,则会使用之前安装好的,只是添加了一个磁贴。在卸载的时候,也是先删除磁贴。如果没有其他用户安装该应用,就删除目录和其中所有的程序集。

    在应用程序基目录或者子目录部署的程序集称为私有部署的程序集,这是因为程序集文件不和其他任何程序共享。之所以能实现简单的安装/移动/卸载,是因为每个程序及都用元数据注明了自己引用的程序集,不需要注册表设置。

    简单管理控制/配置 

     为了实现对应用程序的管理控制,可以在应用程序目录放入一个配置文件。发布者可以创建并打包该文件。安装程序会将配置文件安装到应用程序的基目录。此外,计算机管理员或最终用户也能创建或修改该文件,CLR会解析文件内容来更改程序集文件的定位和加载策略。

    3.共享程序集和强命名程序集:

     

  • 相关阅读:
    通过elasticsearch对日志进行搜索热词统计
    登陆获取shell时的配置文件加载过程
    linux共享库加载
    linux安全相关
    ELK常用API使用方法
    linux bash缓存
    redis主从架构及redis集群
    排查电脑的网络问题
    Logstash添加Protobuf解析插件
    Macaca上手体验
  • 原文地址:https://www.cnblogs.com/renzhoushan/p/10402504.html
Copyright © 2011-2022 走看看