在 OpenPower 上兜兜风,第 2 部分: 到 64 位系统上的移植问题

性能和可移植性,永远是最好的朋友

在 OpenPower 上兜兜风 系列文章的第 2 部分中,Peter Seebach 将介绍向 64 位系统进行移植时所遇到的代码可移植问题,详细介绍了代码和数据的可移植性,并给出几个少见的真正需要修改代码的具体例子。

Peter Seebach, 自由作家, Plethora.net

Peter Seebach1999 年,Peter Seebach 首次在 Alpha 上测试了自己的代码已为 64 位环境做好了准备。对于这个问题来说,再仔细也不过分。当然,那些代码都可以工作,因此让我们开始吧。



2010 年 9 月 21 日

本系列文章的第 1 部分 对 OpenPower™ 程序以及移植到 64 位 Power Architecture™ 系统上时碰到的问题进行了简介。本文将进一步介绍可移植性问题。

对于大部分表现良好的软件来说,很明显,可移植性问题都是与 Makefile 有关的,而不会涉及真正的程序代码。如果您可以找到库和编译器选项,那么代码就很可能在任何地方都能工作。不过,从理论上来说,的确存在一些东西会阻止您的代码在新体系架构上工作;它们通常都会得到一些编译器警告,这样您就可以快速找到这些问题了。尽管并非所有的代码都可以很好地工作,不过令人惊讶地是;大部分 Linux® 代码从早期 64 位 PowerPC® 和 Alpha 系统以来已经是相当安全了。

代码的可移植性

对于大部分情况来说,使用可移植语言编写的代码都是可移植的;它可以很好地工作。当移动到一种新平台上时,您只需要重新编译代码即可,而不需要修改任何代码。这并不意味着您什么都不需要干;只不过是没有太多工作要做而已。如果您已经按照编程语言的规范做了很好的工作,那么一切都会进展顺利的。对于脚本语言来说,更是如此。

您会看到的大部分可移植性问题都是在 C 语言中发现的。C 语言在代码可移植性中的角色非常重要,如果您可以将自己的 C 语言代码移植到某个平台上,那么就很可能已经可以自如地使用这个平台了。C 语言中的可移植性问题一直延伸到 C 类型系统的问题;各个类型在不同平台上可能有不同特点。

通常,问题归结到我们假设了类型是否具有兼容的范围。举例来说,不严密的代码通常会将 int 和 long 对象当成可互换的类型。比范围更加微妙的是指针和整型之间的转换;由于这些转换通常都包括强制类型转换,因此编译器很难告诉您到底做错了什么。毕竟,强制类型转换对于编译器来说就是一个提示,表明您清楚自己正在做些什么。

对于大部分情况来说,试图将指针当作整型来处理的做法来源于试图对执行大量指针处理的程序进行优化,这可能是为了空间或效率的需要。最明显的一个例子是 emacs,在很多平台上它通常都依赖于一些指针操作,从而最快地构成自己的 lisp 实现。最终结果是,很多平台都可以很快地编译并运行 emacs。举例来说,在 68000 时代中,emacs 使用了指针的高位字节用作标记,这样做的基础是这个字节没有被使用;这在 68000、68008、68010 甚至 68EC020 上都可以很好地工作,它们只有 24 位地址总线连接到 32 位地址寄存器上。尽管处理器可以存储 32 位地址值,但是高 8 位(实际上在 68008 中多于 8 位)并没有被使用,因此这样做也不会产生什么影响。但是当将相同的代码移植到一台主存地址从 0x8000000 开始的 68030 机器上时,这些代码就会死掉。

如果您需要编写这种代码,就可能会碰到可移植性的问题;您应该开发一个具有单独标记的结构,并使用这些结构,而不是使用指针。这会产生大量的开销,不过这在新平台上可以很好地工作;如果需要,请一直使用这些代码,直到您对新平台的了解已经足够开发一个特定于平台的实现为止。这就是说,您通常都可以很好地使用低位数据;因为 malloc() 返回的是适合对所有数据类型进行对齐的空间,很多系统从来不会使用从 malloc() 中返回的指针的低 2 位或 3 位值。这样做依然有点疯狂。

内联汇编

在切换到新平台上时明确需要移植的事情之一是内联汇编。除了内核开发之外,现在汇编已经是比较罕见的一种技术了。曾经在比较小的机器上非常常见,其中微小的优化通常都非常重要,编译器对此的处理也相当简单。在现代系统上,程序员使用内联汇编进行优化的机会已经很少了。不过这对于实现那些天生就不可移植的功能来说,这会非常有用。(举例来说,设计利用缓存溢出的程序很可能会对目标平台使用内联汇编。)移植内联汇编没什么捷径;我们只能分析这些汇编程序所实现的功能,并重写这些程序。

内联汇编有时也是必须的,举例来说,当您需要确保特定指令集都在相同的缓存线上(否则就会浪费内存映像)时。同样,使用这种代码,可移植性问题也就不是什么问题了。


数据可移植性

数据可移植性是真正存在问题的地方。只要数据在程序中的位置良好,那么它们到底是怎样存储的就不会有太大问题。只要数据的存储方式相同,编译器怎样存储 32 位整型就无关紧要了。

在您希望将数据移出程序时,数据可移植性问题就出现了。您可能会通过网络来发送数据;也可能将数据存储到磁盘上。如果数据可能需要被其他程序或其他硬件上的程序进行读取,那么您就需要了解有关数据可移植性的问题。

对于网络代码来说,公认的解决方案是将数据都以八位字节(8 位值)来表示,从而只发送定义好的数据流。有些协议,例如 XDR,就是用来解决更加专用的问题,或减少额外开销;不过对于很多应用程序来说,只编写纯文本流就足够了。

实际上,相同的问题通常也适用于数据文件;保存纯文本通常是实现可移植性的最简单方法。如果您真正关心空间的效率,那么压缩纯文本通常要比二进制数据格式要好,并且更容易读取。

如果数据文件已经写入磁盘了,那么这样做不会有太多好处。在这种情况中,您需要使用一种方法来正确读取数据文件。如果您可以提供更多有关文件的内容,这会非常有帮助;举例来说,如果它已经在不同平台上编写好了,而且可能是不兼容的,那么您就需要一种方法告诉它具体的版本是哪个。如果您没有想到要嵌入一些版本信息或标志值,这就需要一些技巧了。要做的一件事是寻找一个预测范围很大的数字,看一下当读取高位优先和低位优先时到底是什么情况。举例来说,如果您的文件中前面的项目是很多记录,从文件大小中您可以知道自己并没有几百万条记录,数字 385,875,968 在写入到具有不同字节次序机器上时可能解释成 23 更为合理。

字节交换并不复杂,因此很多人都可能误解了这个问题。这是最经常进行优化的任务之一,当然也是最经常出现错误的任务之一。

除非您花费大量时间来进行字节交换,否则就只需要清楚地编写代码即可。如果可以选择的话,请使用标准库函数来进行字节交换操作,并按照字节在数据文件中存储的顺序进行标准化;网络字节顺序最适合这种工作。由于某种技术原因您可能会希望使用某种顺序而不是其他顺序,从这个方面来讲网络字节顺序尚不够好;不过因为采用的是网络字节顺序,每个人可以读写它,这样看来网络字节顺序也相当不错。您可以使用 htons、htonl、ntohs 和 ntohl 来实现大部分功能;请参看 byteorder(3)。

如果您需要更多功能,请将数据作为一个无符号字符对象流进行读取,然后进行匹配。请不要浪费时间对这个问题进行优化,除非您已经对代码进行了仔细的分析,并且真正要花费大量时间在这上面 —— 可能您并不希望如此。

最后一点提示:如果支持新平台强制您更新数据文件格式,就请确保在每个文件的前面添加上一些容易解析的版本信息(如果还没有这样做的话)。然后,下一次需要更新格式时,您就不用费劲来告知具体的版本了。(您甚至可以更加聪明一点,确保版本信息都采用一种肯定不会在原始格式中出现的方法来存储版本信息,然后根据是否具有这些信息来辨别原有的格式。)


几个例子

我们不会梳理早已移植的代码,例如需要移植到 Power 体系架构上的那些 “不可移植” 的代码,而是要介绍几个显然是故意设计的例子。

清单 1. 我们为和平而来
#include <stdio.h>

union { long l; char c; } x;

int
main(void) {
	x.l = 33;
	printf("hello, world%c\n", x.c);
	return 0;
}

这个例子(这是由于它的不可预见性而仔细选择的)重点介绍的是 x86 和 Power 体系结构平台之间的主要区别:Endianness。在 Power 系统上,这个联合类型如下所示:

member bytes l 0 0 0 21 x 0

在 x86 系统上,它看起来则如下所示:

member bytes l 21 0 0 0 x 21

这就是说,在 x86 系统上,最低位字节(最小值)首先存储在 ll 的第一个字节中;在 Power Architecture 系统上,它们则存储到最后。第一个字节与字符成员是重叠的;最后一个字节则没有重叠。实际上,情况比这更加复杂。Power Architecture 可以对修改字节顺序提供一点支持。这是在 POWER5 上可用但是在 G5 上无法使用的功能之一;理论上来说,您可以为低位优先的用法来构建自己的代码。不幸的是,所使用的 Linux 发行版并没有提供对应的低位优先库来进行链接,因此就不能对低位优先测试。

字节次序通常是随着将数据写入文件时而产生的问题,不过正如上面的例子显示的一样,您可能会编写受此影响的代码。实际上,可能受此影响的主要情况出现在传递错误匹配的参数时;在低位优先的系统上,如果值都在合适的范围内,错误匹配参数也可能正常工作。举例来说,如果您将一个 32 位的值传递给一个期望 16 位值的函数,而且所传递的参数值也在正确范围之内,那么这个函数就永远不会注意到多出来的字节。或者,这也可能会引起恶性结果;千万不要拿这种东西来打赌。

这与另外一个主要区别 —— 通用对象的长度 —— 紧密关联在一起的。具体来说,就是指针。

清单 2. 滥用指针
#include <stdio.h>

int main() {
	char *h = "hello, world!\n";
	int x = (int) h;
	h = (char *) x;
	printf("%s", h);
	return 0;
}

这个函数在任何 int 类型和指针类型所占用的位数相同的系统上都可以很好地工作。如果指针比 int 类型所占用的位数多,这就不能保证可以工作。更为糟糕的是,即使在 64 位机器上,如果编译器碰巧将内容填充到第一个 4GB 中, 这也很可能不会正常工作。不过您并不需要太多地修改程序来获得一个惊喜:

清单 3. 滥用指针的解决方法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
	char *h = "hello, world!";
	char *t = NULL;
	t = malloc(1024*1024);
	strcpy(t, h);
	int x = (int) t;
	h = (char *) x;
	printf("(%p) %s\n", (void *) h, h);
	return 0;
}

这个程序在 x86 和 PowerPC 上都可以很好地运行,不过在 Power 系统上使用 -m64 选项进行编译时就会死掉。问题是 malloc 会产生一个庞大的地址,不能在 32 位的值中存储。即使根本没采用任何警告标记,编译器也可以捕获到这个问题:

清单 4. 您不能说我没有告诉您什么
$ cc -m64 -o t t.c
t.c: In function `main':
t.c:10: warning: cast from pointer to integer of different size
t.c:11: warning: cast to pointer from integer of different size
$ ./t
Segmentation fault

这种问题在大部分情况中都比前面提到的不同数据大小更容易避免,不过当您需要编写依赖于这种功能的代码时,就不得不从其余代码中假设指针的大小和表示;举例来说,您可能会实现诸如垃圾搜集库之类的功能,这种功能会真正关心指针的表示。好消息是对于常用的库和程序来说,已经有很多人已经解决了这些问题。


结束语

移植到 Power 体系结构上并不太困难。编译器可以方便地捕获一些显然的潜在问题;如果您有一些移植问题,那么使用 32 位模式来编译程序的选项可以为您提供一些抵御其他可能存在的缺陷的能力。本文介绍了目前依然存在的一些技术问题。

在本系列的最后一篇文章中,我们将讨论移植问题,具体来说,就是在移植到 “新体系架构” 过程中一些问题通常被过分夸大的原因。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux, Multicore acceleration
ArticleID=230960
ArticleTitle=在 OpenPower 上兜兜风,第 2 部分: 到 64 位系统上的移植问题
publish-date=09212010