老狼的话:很久没有翻译文章了,不过第一眼看到Steven Pratschner的这篇文章时就觉得很有价值。这位老兄目前是微软.Net Compact Framework Team的成员,这篇文章在CLR设计的高度为我们讲述了CLR与操作系统之间的关系,十分值得一读。目前这位老兄的系列文章已经写到了第三章,我会在稍后继续为大家翻译后边的部分。大家可以在这里看到这篇文章的英文原版:http://blogs.msdn.com/stevenpr/
第一章:概述
过去几周,我一直在写一系列文章,来讨论创建.Net Compact Framework CLR时不同的设计结果是如何作出的。在第一章中,我讨论影响设计的环境因素,并提供CLR内存管理的概述。下面几章我们将详细讨论JIT编译器、垃圾回收和类装入器的主要设计思路,还有分析Compact Framework 应用程序内存使用的信息。
贯穿这个系列,我将着重讨论创建Compact Framework CLR时的设计决定,这些设计与.Net Framework CLR的设计有很大不同。
---------
表面上看起来,.Net Compact Framework是微软.Net Framework运行时环境的一个直接移植。但在设计的层次上,这两个产品的相似其实是有意为之,因为这样会提供相当的好处。Compact Framework和.Net Framework有相同的编程模型,使用相同的文件格式,共享相同的编译器等等。两个编程环境如此相似的主要好处是,开发者只要学习一个编程环境,就可以方便地在另一个环境上编程。例如,如果开发者熟悉.Net Framework,他几乎不需要花时间就可以学会使用.Net Compact Framework开发设备应用程序。
无论表面是如何类似,当你深入内部,你就会发现Compact Framework的实现,尤其是CLR组件,与桌面版本的实现完全不同。无须惊讶,Compact Framework的运行环境直接影响着它内部关键组件的架构。两个主要的环境因素影响了Compact Framework CLR的设计,第一是CLR需要运行在少量的内存环境中,第二是需要方便地跨越处理器和操作系统。
这个系列文章,通过了解对设计产生影响的约束条件,来讨论CLR的内部工作机制。通过这个系列我将指出CLR的哪些设计做了不同于桌面版本的修改,使托管代码能够运行在内存受限的环境中。
理解CLR的内部机制看起来是一个深奥的话题,但是对于你程序下面的平台如何工作有一个清晰的了解,会帮助你理解你的应用程序是如何使用设备的资源,也会帮你判断内存管理或性能相关的问题。
Compact Framework运行在许多不同的操作系统上,但是大部分是安装在Windows CE上的。让我们首先来了解一下Windows CE的内存机制。理解操作系统提供给Compact Framework的服务,将帮助我们理解Compact Framework团队在构建CLR时所作出的决定。
The Windows CE Memory Model
作为一个32位操作系统,Windows CE可以寻址4GB的虚拟地址空间,这方面和桌面版Windows是一致的。事实上,内存空间的划分对于Windows CE应用程序架构是有直接影响的。为了解决Windows CE应用程序访问内存的问题,每个应用程序只能操作32MB的虚拟地址空间。内存可以被分配到这32MB空间之外,但是这些内存是设备上所有应用程序共享的,这些分配的内存不是应用程序私有的。我们在这里只对Windows CE的内存模型进行一个简单介绍,使我们能够理解Compact Framework是如何访问内存的。关于Windows CE内存模型更详细的描述可以参考Doug Boling的《Programming Windows CE》(中文版名称《Windows CE程序设计》)。
下面的图描述了Windows CE应用程序可用的内存区域。
Figure 1
Memory available to Windows CE applications
正如我们所看到的,当程序运行时,会用到三个内存空间区域。
- System Code Space.装载系统DLL的只读代码页,比如coredll.dll。这是每个设备的系统代码空间,所以所有应用程序共享该代码页。如果需要,Windows CE可以交换这个内存区域到存储设备以压缩空间。
- Per-Process Address Space.每个Windows CE进程被分配了32MB的虚拟内存。每个线程的栈地址空间、应用程序可执行文件的代码页和任何堆分配和使用的空间都存储在这个空间中。
- High Memory Area.这1GB的高段内存区域为大量的虚拟内存请求提供了虚拟地址空间。任何对VirtulaAlloc的调用请求的虚拟内存空间将被分配到这部分空间中。另外,所有的内存映射文件将被存储在高段内存中。所有存储在高地址内存区域中的数据对设备上的所有应用程序可见。如果需要,Windows CE可以将将高地址内存区域交换分页到存储设备中。
当程序运行时,.Net Compact Framework从这三个区域中获取需要的内存。正如我们所见,为了给托管应用程序的开发者提供最佳体验,Compact Framework管理每个进程的地址空间。
.NET Compact Framework Memory Management Basics
提高开发者生产力是促使.Net Framework和.Net Compact Framewok被广泛采用的主要原因。对于CLR提高开发者生产力的讨论经常围绕着自动内存管理(垃圾收集)、进程独立等话题展开。Compact Framework除了明确提供了这些功能外,也提供了更多帮助开发者提高移动设备程序开发生产力的更多功能。特别是.Net Compact Framework CLR代替开发者管理每个进程的32MB虚拟内存空间。所以开发者就不必为他们的程序在32MB空间中分配或释放内存而担心了。Compact Framework使编写内存受限设备上的应用程序变得简单。我们将看到,在这个系列文章中,许多构建.Net Compact Framework CLR的关键设计决定就是为了有效地管理每个进程32MB的虚拟内存空间。Windows CE将每个进程限制到一个小的虚拟地址空间中,而Compact Framework团队所要做的事情就是设计一个平台,让应用程序在给定的空间中运行得更好。
在描述允许在内存受限设备上的设计细节前,我们需要先来看一下,当执行一个托管程序时,操作系统和CLR创建的所有运行时数据。在我讨论完运行一个程序需要的数据种类后,我将告诉大家CLR会将哪些运行时数据分配到Windows CE的哪个内存区域中去。我们首先来考虑“Hello World”程序运行时,运行时数据的哪些种类需要内存。
using System;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace HelloDevice
{
public class Form1 : Form
{
private MainMenu mainMenu1;
private Label label1;
public Form1()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.mainMenu1 = new System.Windows.Forms.MainMenu();
this.label1 = new System.Windows.Forms.Label();
// Position the label
this.label1.Location = new System.Drawing.Point(64, 81);
this.label1.Size = new System.Drawing.Size(100, 20);
this.label1.Text = "Hello Device!";
// Size the form
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.ClientSize = new System.Drawing.Size(240, 268);
this.Controls.Add(this.label1);
this.Menu = this.mainMenu1;
this.MinimizeBox = false;
this.Text = "Simple App";
}
}
static class Program
{
static void Main()
{
Application.Run(new Form1());
}
}
}
正如你所看到的,这个程序创建了一个带有“Hello Device!”字样的Label控件的窗体。我总结出该程序需要使用内存的六种情况。
- Native code pages for the CLR dlls.Compact Framework CLR由两个DLL组成,分别是mscoree.dll和mscoree2_0.dll。这两个DLL连同Compact Framework的Windows Forms实现的Native部分,netcfagl2_0.dll,被成为系统DLL。因此这些DLL的代码被加载到system code space。
- Application and Class Library assemblies.CLR必须将应用程序和它需要的类库的所有IL代码加载到内存中,在产生相应的本地代码指令或类加载器展开该类型的数据结构时,访问需要的元数据时,使JIT编译器可以访问到IL。程序集除了包含“Hello World”代码外,mscorlib所需要的IL还包括System、System.Windows.Forms和System.Drawing。如果需要,包含这些程序集的文件将被内存映射到高位内存区域中。
- JIT-compiled native code.当一个应用程序被执行时,每个方法被访问时,JIT编译器将被调用,生成本地代码。本地代码将被存储在进程虚拟地址空间的一个buffer里。
- Allocated reference types.上面分配引用类型的列表中,除了主窗体本身外,还有MainMenu、Label、Point和Size的实例被创建。更多类型在类库实例化时被创建。所有引用类型所需要的内存来自垃圾收集堆。GC堆是应用程序自己拥有的堆,存储在应用程序指定的地址空间中。
- In-memory representation of type metadata.在执行一个程序时需要用到类和方法,CLR从程序集拷贝中读取他们的元数据,并将它们映射到高位地址空间中。元数据在使用时产生类和方法的内存中表示(in-memory representation)。该表示被存储在AppDomain堆中。AppDomain堆被存储在每个进程的虚拟地址空间中。
- Miscellaneous allocations.除了上面讨论到的这些内存分配类型外,CLR还会在运行程序时产生一小部分附加数据。这类数据包括JIT编译器用来判断一个方法是否已经被编译的stub和其他短暂数据元素。
现在我们可以看到运行托管程序需要用到的数据种类,让我们将它们映射回Windows CE内存模型中去。图2表示的就是每种运行时数据种类被存储到Windows CE内存的哪个区域中。
Figure 2
The mapping between Compact Framework memory allocations and the Windows CE memory model.
根据图2,最重要的是了解哪些内存是分配在每个进程的空间中,而哪些是被所有进程共享。回到前边我们对Windows CE内存模型的讨论,我们知道,被加载到系统代码空间中的代码页和所有在高位内存地址空间上的内存分配都是被所有应用程序共享的,而在每个进程空间中的内存分配都是该进程私有的。因为进程中的分配不能共享,如何使用每个进程32MB的虚拟地址空间就是一个十分重要的事情。所以我们决定CLR将jitted代码、引用类型、in-memory type representations和其他小的内存分配都放在进程空间内。更多关于进程堆尺寸的信息可以参考Mike Zintel的Blog Advanced Compact Framework Memory Management。
现在我们讨论了基础话题,下一章我们将讨论.Net Compact Framework JIT编译器的一些基本设计思想。
This posting is provided "AS IS" with no warranties, and confers no rights.