Breathe李

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

 

      相信很多有过面试经历的开发人员都在面试的时候被问及到.net比较底层的东西,如程序在底层是怎么运行的?笔者就遇到过面试官问反射的机制是什么?当时确实答的一塌糊涂,但是有幸还是找到了一份不错的工作。在工作中猛然觉醒,是时候(有一定的开发基础)有必要了解一些.net运行的机制了,在买了一本CLR Via C#的书并在细细品味了第一章之后,决定把所学到的东西与大家进行分享,虽然之前都知道的知识,但是还是让我学到了很多的东西。可能总结的过程中还是有些不对的地方,希望各位看官能指出,并提出,我将尽快改正。谢谢!(目前只总结第一章前半部分,后半部分文档还未整理出来,望大家海涵)

      言归正传,先从CLR是什么开始谈起。CLR(Common Language Runtime)公共语言运行时,是可被多种语言所同时使用,并且很多特性可用于所有针对它的开发语言。我们所编写的程序在编译的同时由编译器进行语法的检查和代码分析,然后生成一个托管模块。

如图:

clip_image002

图1.1

      这时又需要了解一个概念了,什么是托管模块?

      托管模块是由一个标准的32位Microsoft Windows 可执行体(PE32)文件或者是一个标准的64位Microsoft Windows 可执行体(PE32+)文件,以上两种的其中一种再加上CLR头信息(包含使该模块成为托管模块的一些信息,如:CLR的版本,托管模块的方法入口点(我们俗称的Main方法等))再加上元数据(这里的元数据并不是我们的源代码文件,是每个托管模块所包含的元数据表,一种类型表、一种源代码引用类型、成员表)最后再加上IL代码(就是由我们各个语言的编译器所编译出来的中间语言代码),在最后运行的时候,CLR控制IL代码的运行。

      写到这里大家可能觉得CLR是和托管模块一起工作的吧?其实不是,CLR只和程序集(assembly)一起工作。又来一个概念,什么是程序集?

      程序集是一个或者多个由资源文件、模块组成的逻辑性分组。程序集在整个程序中的重用和版本控制都是最小的单元。程序集是怎么形成的呢?当我们通过图1.1所示的过程把源代码文件通过各种语言的编译器编译成托管模块(中间语言和元数据)之后,再加上我们程序中需要的一些jpge、gif等资源文件交由生成程序集的工具进行处理之后,成为与CLR打交道的程序集(默认情况下,生成程序集的工具由编译器来完成,但不一定是编译器)

      那么如何确定我们机器上是否安装了.Net的框架呢?在运行里面可以输入%windir%/System32,查看目录中是否存在mscoree.dll文件。

      在说了这些之后,让我们看看CLR到底是如何让我们所写的代码跑起来的。

      在我们写完源代码文件之后,进行编译运行(这里单单以C#为例笔者主要做的就是C#的开发,惭愧于对其它语言只知皮毛,笔者会闲暇之余会去进行学习)那么在编译运行的时候我们程序是怎么跑起来的?不管我们的源文件是什么语言写的C#或者VB再或者J#这些都不重要,最终我们的语言会通过相对的编译器被编译成IL中间语言,那么如果我们需要执行下述代码:

clip_image004

图1.2

      首先,把我们的main方法编译成IL中间语言,然后再转换成本地的CPU指令(这一步是由JIT(just-in-time或者“即时”)编译器完成的)。那么在执行main方法之前,CLR会检测main方法的代码所有的类型,然后生成一个数据结构表,记录方法的类型、参数引用等一些信息,以便对引用类型的信息进行访问,这个时候Console类中的所有方法都已经记录在CLR生成的数据结构表中并有每个方法的内存地址。然而,在第一次调用Console.WriteLine方法的时候,JIT编译器就知道要调用哪个方法,然后JIT将IL编译成CPU指令,然而这个CPU指令就被分配在一个动态内存中,以便第二次调用的时候直接调用已经编译好的CPU指令,而省去了再编译的麻烦。注意:这里所说的再编译,是指应用程序一直在运行的时候,不需要再编译一次Console.WriteLine方法,而是直接运行。如果程序退出,将再一次对方法进行检验和编译。如图:

clip_image006

图1.3

      那么再次总结以下方法调用的过程:

      1、 当程序第一次运行时,会调用JIT编译器,它可以知道调用了那些方法,以及通过CLR生成的数据结构表找到定义该方法的类和具体实现;

      2、 然后JIT编译器在元数据中找到该方法的IL代码,进行验证,然后转换成CPU指令。并分配在动态内存中;

      3、 JIT编译器将调用的方法地址改为第二步的内存地址;

      4、 跳转到上Main方法编译后的代码。

      5、 执行完毕并返回

      当程序第二次执行的时候,就不需要JIT的验证,而是直接调用本地CPU的指令。

      以上为我在第一章前半部分整理出来的文档吧。后半部分我将再一次细细品味之后,总结出来与大家进行分享。不足之处请大家提出,我将即时改正。

posted on 2011-09-13 13:08  LouisLee  阅读(3347)  评论(16编辑  收藏  举报