《CLR via C#》读书笔记 第1章 CLR的执行模式

将源程序编译成托管模块

公共语言运行时(CLR)

概念:CLR是一个可由多种编程语言使用的“运行时”。CLR的核心功能(如内存管理、程序集加载、安全性、异常处理和线程同步)可由面向CLR的所有语言使用。如“运行时”使用异常来报告错误,面向它的语言都能通过异常来报告错误。

特性:CLR不关心开发人员使用的哪种语言,只需要编译器时面向CLR的(符合CLR标准)。可用支持CLR的任何语言创建源代码,然后使用对应的编译器检查语法和分析源代码。


image.png

标准:无论使用哪个编辑器,结果都是托管模块,托管模块的标准是32位Microsoft Windows可移植执行体(PE32)文件,或者是标准的64位windows可移植执行体(PE32+)文件,它们都需要CLR才能执行。


image.png

IL

本机代码编译器生成的都是面向特定CPU架构的代码。面向CLR的编译器生成都是IL(中间语言)代码。IL代码有时称为托管代码,因为CLR管理它的执行。

元数据(metadata)

概念:元数据是一个数据表集合,一些数据表描述了模块中定义了那些内容(比如类型和成员等),另一些描述了模块引用了什么(比如导入的类型及成员)。

用途:元数据总是嵌在和代码相同的EXE/DLL文件中,编译器同时生成元数据和代码,把它们绑定在一起并生成最终的托管模块。

  • 元数据避免了编译对原生C/C++头和库文件的需求,因为在实现类型/成员的IL代码中,包含有关引用类型/成员全部信息。编译器直接从托管模块读取元数据。

  • VS使用元数据来实现代码提示

  • CLR进行验证的过程会使用元数据确保类型安全

  • 元数据运行将对象的字段序列化到内存,并到其他机器反序列化完成对象重建状态。

  • 元数据允许垃圾回收器跟踪对象生存期。垃圾回收器能判断任何对象的类型,并从元数据中知道对于引用了那些其他的对象。

1.2 将托管程序合并成程序集

背景:CLR实际上不和模块工作。它和程序集工作。程序集(assembly)是一个或多个模块/资源文件的逻辑性分组。其次,程序集是重用、安全性以及版本控制的最小单元。在CLR中,程序集相当于“组件”。编译器默认将生成的托管模块转换成程序集。


image.png

1.3 加载公共语言运行时

生成的程序集既可以是可执行应用程序,也可以是DLL。最终都是由CLR管理这些代码的执行。所以目标机器必须安装.Net Framework。根据我们程序集编译的platform(32位/64位/ARM)不一样,.Net会加载不同版本MSCorEE.dll,进而进程的主线程调用MSCorEE.dll的方法初始化CLR,加载EXE程序集,在调用其Main方法。应用程序启动并运行。


image.png

1.4 执行程序集的代码

JIT 编译

为了执行方法,首先必须把方法的IL转换位本机CPU指令,这是CLR JIT(just-in-time “即时”)编译器的职责。

方法首次调用的时候,JITCompiler函数会被调用。JITCompiler函数负责将方法IL代码编译成CPU指令。JITCompiler会在定义(该类型的)程序集的元数据中查找被调用方法的IL,接着JITCompiler验证IL代码,并将IL代码编译成本地CPU指令。本地CPU指令分配到动态的内存块中。然后JITCompiler回到CLR为类型创建的内部数据结构,找到被调用方法对于的那条记录,修改对JITCompiler的引用,使其执行内存块(刚刚编译好的)的地址。最后JITCompiler函数跳转到内存块的代码,执行。


image.png

方法二次调用的时候,完全跳过JITCompiler函数,直接执行本机代码


image.png

JIT编译将本地CPU指令存储在动态内存中。程序一旦中止,编译好的代码也会被丢弃。再次运行或应用程序启动多个实例,JIT都必须再次将IL编译为本机代码。对于某些引用程序,可能会显著增加内存。相比之下,本机应用程序的只读代码页可由应用程序运行的所有实例共享。

编译指令

CLR的JIT编译器会对本机代码进行优化,类似于非托管C++编译器后端所做的事情。花费较多时间生成优化代码,提高性能。

image.png
  • /optimize-:在C#编译器生成为优化的IL代码中,包含许多NOP(no-operation,空操作)指令。还包含很多跳转下一行代码的分支指令,主要是用于VS加断点调试。

  • /debug(+/full/pdbonly):编译器会生成Program Database(PDB)文件,PDB文件帮助调试器查找局部变量并将IL指令映射到源代码。/debug:full告诉JIT编译器你打算调试程序集,JIT编译器会记录每条IL指令所生成的本机代码,这样使用VS的时候就可以对对应进程源代码进行调试。

新建C#项目时,项目Debug配置的时/optimize-和/debug:full,而Release配置的是/optimize+和debug:pdbonly

JIT优化

JIT编译编译器在运行时将IL代码编译成本机代码时,编译器对执行环境的认识比非托管编译器更深刻,下面列举托管代码相较于非托管代码的优势。

  • JIT编译器能判断应用运行的CPU,并生成对于CPU支持的特殊指令,相反,非托管应用程序通常时针对最小功能集合的CPU编译的,不会使用提升性能的特殊指令

  • JIT编译器能预判一个方法在它对应机器上总是测试失败。如主机只有一个CPU,JIT编译器不会执行需要多个cpu支持的IL代码生成CPU指令,从而减少代码,执行更快。

  • 应用程序运行时,CLR可以评估代码的执行,并将IL重新编译成本机代码,减少不正确的分支预测。虽然目前的版本不支持。。。

预编译

.Net Framewrok SDK配套的NGen.exe工具将程序集的所有IL代码编译成本机代码,并将这些代码保存到一个磁盘文件中。在运行程序集时,CLR自动判断是否存在该程序集的预编译版本。如果有,CLR加载预编译版本,这样一来就避免了运行时编译。但NGen.exe生成的代码时很保存的,不想JIT编译器生成代码那么高度的优化

1.4.1 IL和验证

IL编译成本地CPU指令时,CLR执行一个名为验证的过程。这个过程会检查高级IL代码,确定代码所作的一切都是安全的。比如,核实调用的每个方法都有正确数量以及类型的参数,每个方法都有一个返回语句等。托管模块的元数据包含验证中所用到的方法及类型信息。

Windows的每个进程都有自己的虚拟地址空间,独立地址空间之所以必要是因为不能简单的信任一个应用程序的代码。应用程序完全可以读写无效的内存地址。所有每个进程都放大独立的地址空间,互不干扰,保证程序健壮性与稳定性。通过验证托管代码,可以保证代码不会不正确的访问内存,不会干扰到另一个应用程序的代码。

健壮性与可靠性:可靠性主要描述系统的正确性,也就是你提供一个参数,他能残生稳定可预测的数据。但是如果你程序员执行完成后,没有正确释放内存,或者说系统没有自动帮它释放占用的资源,就认为这个程序“运行时”不健壮

1.4.2 不安全的代码

定义:C#编译器默认生成安全代码,这种代码安全性可以验证,然而C#编译器也运行写不安全代码,不安全代码允许直接操作内存地址,并可操作这些地址处的字节,通常只有在与非托管代码进行交互或提升对效率要求极高的一个算法性能的时候,才这样做。

风险:这种代码可能破坏数据结构,危害安全性,甚至造成新的安全漏洞,所以C#编译器要求包含不安全代码的所有方法都使用unsafe关键字标记。此外 C#编译器要求使用/unsafe编译器开关来编译源代码。

1.7 通用类型系统

CTS(通用类型系统):Microsoft制定的一个正式的规范来描述类型的定义和行为。

  • 字段(Field)
    作为对象状态一部分的数据变量,用名称和类型来区分
  • 方法(Method)
    针对对象执行操作的函数,通常会改变对象状态。方法有一个名称、一个签名、一个或多个修饰符。签名制定参数数量及顺序、类型、方法是否有返回值,如果有返回值还需要返回值类型。
  • 属性(Property)
    属性运行在访问值之前校验输入参数和对象的状态,以及在必要时在计算某个值(getter、setter)
  • 事件(Event)
    事件在对象以及其他相关对象之间实现通知机制。

访问权限:

  • private
    成员同类访问
  • family
    成员可由派生类访问
  • family and assembly
    成员和由派生类访问且必须在同一程序集
  • assembly
    成员同一程序集访问
  • family or assembly
    成员可由任何程序集中派生类访问
  • public
    成员可任意程序集中任何代码访问

简单总结今天学到的重要内容:

程序加载运行
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容