《CLR via C#》读书笔记(2) -- .NET程序的运行模型(上)

  •          平台确定

        通过第一节,我们知道:编译器在PE文件中写入了大量的元数据。其中在PE32(+)中有一个machine字段用来标识该程序集所面向的平台(一般应该是x86,x64,不确定(Any CPU)).
        在当前运行平台与程序集面向平台兼容的情况下,程序集为以所指定的平台类型运行(比如:面向x86则分配32从位地址空间,为其加载x86版本的所引用系统程序集;面向x64则为其分配64位的地址空间,为其加载x64版本的所引用系统程序集;如果any cpu则根据当前运行平台确定。)

 

  •         实例解剖CLR运行模型
    以如下代码为例:
    class Program
    {
         static void Main(string[] args)
         {
                Console.WriteLine("Hello World");
         }
    }

    生成如下IL代码:
    .method /*06000001*/ private hidebysig static 
            void  Main(string[] args) cil managed
    // SIG: 00 01 01 1D 0E
    {
      .entrypoint
      // Method begins at RVA 0x2050
      
    // Code size       13 (0xd)
      .maxstack  8
      IL_0000:  /* 00   |                  */ nop
      IL_0001:  /* 72   | (70)000001       */ ldstr      "Hello World" /* 70000001 */
      IL_0006:  /* 28   | (0A)000011       */ call       void [mscorlib/*23000001*/]System.Console/*01000013*/::WriteLine(string/* 0A000011 */
      IL_000b:  /* 00   |                  */ nop
      IL_000c:  /* 2A   |                  */ ret
    // end of method Program::Main
    生成如下CLR Header(部分):
    ----- CLR Header:
    Header size:                        0x00000048
    Major runtime version:              0x0002
    Minor runtime version:              0x0005
    0x00002068 [0x00000640] address [size] of Metadata Directory:       
    Flags:                              0x00000003
    Entry point token:                  0x06000001
    CLR将这个程序Run起来的步骤如下:

    1. 找到入口方法
    我们可以看到通过CLR Header的Entry point token字段可以知道,该程序集的入口方法是位于MethodDef表中(token 0x06 代表是methoddef表)的第一条记录所代表的方法。通过查询MethodDef表我们可以找到该方法的IL在该文件中的偏移。CLR会将IL编译为本地CPU指令后再执行该代码。

    2. 查找引用类型/成员
    其实该代码非常简单,只是调用System.Console.WriteLine方法向输出流上写了一段字符“Hello World”.
    从IL指令可以看到,该行代码分为两部分来执行:第一步是将"Hello World"字符串入栈;第二步是调用System.Console类型的WriteLine方法
    第一步没什么可说的,主角是第二步。
    第二步是调用一个外部引用的类型System.Console的方法WriteLine. CLR对该方法的调用过程其实也是费很大一番功夫,主要包括如下几步:
    (1) 加载程序集
          这一步其实是在CLR JIT将Main方法编译成本地代码时就进行了的。CLR会查找Main方法中所有的引用的外部类型,并将这些外部类型所在的程序集全部加载进来。
    (2) 查找方法IL
         对于引用的外部类型方法,CLR查找IL代码步骤如下:
         -首先查找MemberRef表,找出定义该方法的类型
         -然后查找TypeRef表,找出该类型是在当前程序集还是在别的程序集
         --若是当前程序集,则通过ModuleRef能找到其所在Module,通过Module的MethodDef元数据可以知道方法的IL的存放位置偏移
         --若不是当前程序集,则通过查找目标程序集的清单元数据查找到目标Module,最后也是通过MethodDef元数据表找到IL的存放位置。

    对于当前的例子,CLR首先从MemberRef中找到WriteLine方法对应的条目,然后找到其所在类型System.Console在TypeRef中所对应的条目。
    通过该元数据可以知道其属于mscorlib这个程序集,并可以导航至AssemblyRef元数据表。通过AssemblyRef元数据表,我们便可以定位到mscorlib这个程序集
    的清单文件,进行最终找到定义System.Console这个类型的Module,从该Module的MethodDef数据表中找到WriteLine方法的IL存放地址。
    (3) 构造数据结构保存方法的本地指令地址
         因为此处访问了System.Console这个类型的方法。CLR会
         首先在内存中为Console这个类型创建一个数据结构(假设称之为ConsoleLinker),这个数据结构是为了保存Console中所有方法的本地代码地址。但是当这个数据结构被初始化的时候,每一个方法的本地代码地址都被设为JIT的JITCompiler方法的地址。
         然后,当调用WriteLine方法时JITCompiler函数会被调用,这个函数所做的事情是先将步骤(2)所找到的方法IL代码编译成本地代码,然后保存于内存中,并将
    ConsoleLinker中保存WriteLine方法地址的地方设为本地代码的地址。(当以后再次访问这个方法时,便可以直接执行本地代码,而不用经过JIT编译了)

    (4) 执行方法调用
    通过以上步骤,已经可以知道所引用外部方法的本地代码地址.CLR会直接去执行该地址所保存的本地代码。

    最后附上两幅图,我觉得最能代表整个执行过程:
    第一幅,CLR查找所引用的类型的模型:

    第二幅,当调用引用的类型方法时,其调用模型为:
posted @ 2012-11-20 22:45  self.refactoring  阅读(311)  评论(0编辑  收藏  举报