CLR
针对 ARM 处理器进行 .NET 开发
Andrew Pardoe
消费者今天是一个大型的驱动技术市场。 称为"IT 消费化"的趋势可见一斑,电池寿命长和始终连接和媒体丰富的经验是对所有技术客户很重要。 若要启用的电池寿命长的设备的最佳体验,Microsoft Windows 8 操作系统给带来系统建立在低功耗 ARM 处理器,而今天权力大多数移动设备上。 在本文中,我将讨论 Microsoft 框架和 ARM 处理器的详细信息、 你作为一个 开发人员应该请牢记和微软 (如我在 CLR 团队的项目经理) 我们不得不做把 的拿到臂。
作为 开发人员,你可以想象写在各种不同的处理器上运行的应用程序会构成有点左右为难。 ARM 处理器指令集体系结构 (ISA) 不兼容 x86 处理器的 ISA. 建基于 x86 在本机运行的
应用程序运行 x64 上很好,因为 x 64 处理器 ISA 是 x 86 的超集 ISA.但同样并不是真正的本机 x 86 在手臂上运行的应用程序 — — 他们需要重新编译该不兼容的体系结构上执行。 能够从一系列不同设备的选择是对消费者来说,很大,但它给开发商故事带来一定的复杂性。
在 语言编写您的应用程序不仅使您可以重用你现有的技能和代码,它还使您的应用程序,无需重新编译所有 Windows 8 处理器上运行。 由移植到手臂的 框架,我们帮助抽象的独特的特征的体系结构,对大多数 Windows 开发商不熟悉。 但仍有一些东西,您可能需要编写代码以在手臂上运行时的注意事项。
到臂之路:.NET 过去和现在
.NET 框架已经运行在 ARM 处理器上,但它不是在台式机运行的版本完全相同的 框架。 当我们开始 的第一个版本的工作,我们意识到能够跨处理器编写易于携带的代码是我们的提高开发人员的效率的价值主张的关键。 X86 处理器主导桌面计算的空间,但在嵌入式和移动的空间存在的五花八门的处理器。 要使开发人员能够针对那些处理器,我们创建了一个版本的 框架调用 框架精简版,在有内存和处理器的约束的机器上运行
的。
.NET 框架精简版支持的第一次设备为 4 MB 的内存和 CPU 的 33 兆赫一点了。 .NET 框架精简版的设计强调 (使其能在这种约束的设备上运行) 的有效执行和可移植性 (以便它可以运行跨了大量的处理器所共有的移动和嵌入的空格)。 但在最受欢迎的移动设备 — — 智能手机 — — 现在运行是相当于 10 年前的计算机的配置。 桌面 框架,旨在与至少 300 MHz 的处理器和 128 MB RAM 的 Windows XP 计算机上运行。 Windows Phone 设备今天需要至少 256 MB 的 RAM 和现代的手臂皮质处理器。
.NET 框架精简版仍然是 Windows 嵌入式紧凑的开发商故事很大一部分。 在方案中嵌入设备运行在约束配置中,经常使用低达 32 MB 的 RAM。 我们还创建了一个版本的 框架调用 微 Framework 中,有一点为 64 KB 的内存的处理器运行。所以我们实际上有三个版本的 框架,其中每个在一个不同的类的处理器上运行。 但这是我们的旗舰产品,桌面的 Framework 中,加入了压缩和微框架在 ARM 处理器上运行的第一次。
在手臂上运行
尽管 框架旨在保持中立的平台,它大多已上运行 x 基于 x86 的硬件在它的存在。 这意味着几个 x 86 特定模式已陷入集体心中的 程序员。 您应该能够集中精力编写伟大的应用程序,而不是写作的处理器体系结构中,但在编写 代码在手臂上运行时,你应该保持在头脑中的几件事。 这些措施包括一个较弱的内存模型和更严格的数据对齐要求,以及一些函数参数区别对待的地方。 最后,有几个项目配置步骤在 Visual Studio 中的不同时您的目标设备。 我将讨论每个。
一种较弱的内存模型"内存模式"是指对多线程程序中的全局状态所做更改的可见性。 两个 (或更多) 的线程之间共享数据的程序通常会锁定该共享的数据。 根据特定的锁定使用,如果一个线程访问数据,试图访问数据的其他线程将阻塞直到第一个线程完成与共享的数据。 但锁是没有必要的如果你知道每个线程访问共享的数据会这样做而不会干扰该数据的其他线程的视图。在这种方式中的进行编程称为"锁免费"算法。
你不知道,您的代码将执行的确切顺序时无锁算法的麻烦。 现代的处理器进行重新排序说明,以确保处理器可以在每个时钟周期上取得进展,并将写入到内存组合以减少延迟。 虽然几乎每个处理器执行这些优化,有差异的读取和写入顺序呈现方式的程序。 x 基于 x86
的处理器保证处理器将会看起来像它正在执行大多数的读取和写入程序指定它们的顺序相同。 这一保证被称为强内存模型,或强烈的写入顺序。 ARM 处理器别让尽可能多的担保 — — 他们一般自由地左右移动操作,只要这样做不会更改的代码将在单线程的程序中运行的方式。 ARM 处理器不会作出一些保证允许精心构造的无锁代码,但它有所谓的一种微弱的内存模型。
有趣的是,windows开发平台 框架 CLR 本身具有弱内存模型。 要写入订购 ECMA 公共语言基础结构 (CLI) 规范中的所有引用 (可用作在 PDF bit.ly/1Hv1xw) 的标准,CLR 是旨在满足、 挥发性的访问,请参阅。 在 C# 中,这意味着对变量的访问标记为 volatile 关键字 (请参阅参考的 CLI 规范第 126 条)。 但在过去十年中最托管的代码都已运行基于 x86 系统和 CLR 实时 (JIT) 编译器尚未添加到允许的硬件,所以没有哪里的内存模型会揭露潜并发错误的案件相对较少的 reorderings 多。 如果托管的代码的书面和测试仅对 x 基于 x86 的机器预计将手臂的系统上的工作方式相同,这可能会出现问题。
大多数的模式,需要在重新排序要格外谨慎是在托管代码中罕见。 但这些模式,确实存在一些看似简单。 下面是一个示例的代码,看上去不像它有一个 bug,但如果在另一个线程上更改了这些变量,则此代码可能会破坏机器上弱内存模型:
1.
2. static bool isInitialized = false;
3. static SomeValueType myValue;
4. if (!isInitialized)
5. {
6. myValue = new SomeValueType();
7. isInitialized = true;
8. }
9. myValue.DoSomething();
10.
若要使此代码正确,只是表明 isInitialized 标志是挥发性:
1.
2. static volatile bool isInitialized = false; // Properly marked as volatile
3.
执行此代码没有重新排序显示在左侧块图 1。 线程 0 是第一个在其本地的堆栈上初始化 SomeValueType,并将本地创建的 SomeValueType 复制到应用程序域的全局位置。 线程 1 确定通过检查它还需要创建 SomeValueType 的 isInitialized。 但这是没有问题,因为数据被写回到同一应用程序域全局位置。 (大多数情况下,在此示例中,DoSomething 方法所作的任何突变是幂等)。
图 1 写重新排序
右侧块显示支持写入重新排序 (和方便地放置在执行档) 的系统具有相同的代码的执行。 此代码将无法正确执行,因为线程 1 通过阅读 SomeValueType 并不需要初始化的 isInitialized 的值来确定。 在调用 DoSomething 指未初始化的内存。 从某些值类型中读取的任何数据将由 0 到 CLR 设置。
您的代码不会经常执行错误由于这种重新排序,但是它不会发生。 是完全合法的这些 reorderings — — 的写操作顺序无关紧要时在单个线程上执行。 编写并行的代码来标记正确使用挥发性关键字的可变变量时,最好。
CLR 允许公开更强的内存模型比 ECMA CLI 规范要求。 例如,基于 x86,CLR 的内存模型是强因为处理器的内存模型很强。.NET 团队可以做的内存模型,手臂一样强大模型基于 x86,但确保订购尽可能的完善可以对代码的执行性能产生显著影响。 我们做的有针对性的工作,以加强在胳膊上的内存模型 — — 具体来说,我们已经插入记忆障碍在关键点时写入到托管堆,保证类型安全 — — 但我们确信,只有这样做对性能影响最小。 该团队经历了多个设计审查,以确保在手臂 CLR 中的应用技术是正确的专家。 此外,性能基准测试显示 代码执行性能扩展本机 c + + 代码时相比跨 x86、 x 64 和手臂一样。
如果您的代码依赖于无锁的算法,取决于 x 86 执行 CLR (而不是 ECMA CLR 规范),您需要将 volatile 关键字添加到适当的相关变量。 一旦您已标记为易失性的共享的状态,CLR 会照顾一切为您。 如果你像大多数开发人员,你准备好要在手臂上运行,因为您已经使用锁来保护您的共享的数据、 正确标记为可变变量并测试您的应用程序在胳膊上。
数据对齐方式要求另一个差别可能会影响某些程序是 ARM 处理器需要一些数据的对齐。 你有没有在 64 位边界对齐 64 位值 (即 int64、 uint64 或一个双精度型) 时,对齐要求适用的特定模式。 CLR 会照顾你的对齐方式,但有两种方式,迫使未对齐的数据类型。 第一种方法是用 [ExplicitLayout] 自定义属性显式指定的结构布局。 第二种方式是结构的正确指定的托管代码和本机代码之间传递布局。
如果您注意到回来带垃圾的 P/Invoke 调用,可能要看一看被封送处理任何结构。 作为一个例子,我们同时移植的 COM 接口传递包含了 64 位双作为参数的托管代码中的函数的两个 32 位字段的 POINTL 结构一些 库修正 bug。 该函数使用位操作来获取两个 32 位字段。 这里是越野功能的简化的版本:
1.
2. void CalledFromNative(int parameter, long point)
3. {
4. // Unpack native POINTL from long point
5. int x = (int)(point & 0xFFFFFFFF);
6. int y = (int)((point >> 32) & 0xFFFFFFFF);
7. ... // Do something with POINTL here
8. }
9.
本机代码没有要对齐的 POINTL 结构上 64 位的边界,因为它包含两个 32 位字段。 但手臂需要 64 位双要对齐时传递到托管函数。 使某些类型的指定相同,托管本机调用的两边都是关键,如果您的类型需要的对齐方式。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论