遵循这个分为六步的指南可以加速移植速度。了解移植过程中通常会遇到的 Solaris 与 Linux® on POWER® 之间的差别。介绍在基于 IBM POWER 处理器的系统上运行的 Linux 开发环境,查看 SUN 的编译器/连接程序开关与 GNU GCC 和 IBM 原始编译器的开关之间的比较。最后,了解用于性能分析的工具和 Linux on POWER 的软件包。[本文根据最新产品版本进行了更新 —— 编辑。]
通常,将基于 Solaris 的应用程序移植到 Linux 是一项简单的任务,因为 Solaris 和 Linux 都是基于 UNIX® 的。要实现这个移植,通常只需在一些编译器和连接程序开关中通过较小的更改实现重新编译即可。只有当应用程序依靠于系统特定实现时,才需要对它们进行较大 改动。虽然 Solaris 和 Linux 都设计为遵循标准,但是它们的实现中有时会出现差别。本指南将着重讲述这些差别,并在 Linux 端没有对等项时,提供可能的解决方案。
本文的内容组织如下:
- 迁移路线图
- 迁移事项,包括字节排列顺序以及 32 位和 64 位移植问题
- Linux on POWER 的开发环境
- Solaris 与 Linux 之间的差别
- 可用于 Linux on POWER 的性能调优工具和软件打包工具
我们将重点介绍将运行于 32 位或 64 位 SPARC、Intel® 或 AMD x86 架构之上的 Solaris 迁移到运行于 POWER 架构之上的 Linux。对于 Solaris,我们的讨论将基于版本 9 及其更高版本。对于 Linux,我们将重点讨论基于 IBM POWER 处理器的服务器中的可用发行版本:SUSE LINUX Enterprise Server (SLES) 版本 11 和版本 10 SP 2 和 Red Hat Enterprise Linux (RHEL) 版本 5 更新 2。
移植路线图
|
步骤 1:准备
您需要了解源平台与目标平台之间的差别。例如,需要知道源平台上的字节排列顺序是否与目标平台上的字节排列顺序有所不同。如果源平台是 Solaris/x86,则您应该考虑字节排列顺序,因为 Linux on POWER 是大端字节的,而 Solaris/x86 是小端字节的。
还要确定目标平台上是否包含所有必需的第三方包(比如数据库和类库)。对于 32 位应用程序,要考虑是否有必要迁移到 64 位。在下面的 一般迁移事项 小节中,将提供字节排列顺序以及 32 位到 64 位问题的信息。
此外,要决定哪个编译器用于目标平台。如果应用程序是性能敏感的,那么还要考虑使用原始编译器。在 Linux on POWER 的开发环境 小节中,将提供关于 Linux on POWER 的可用编译器的详细信息。
步骤 2:配置
这一步中包括设置开发环境、设置环境变量、对 makefile 进行更改,等等。在结束这个阶段时,应该可以开始构建应用程序了。
步骤 3:构建
这一步中包括修复编译器错误、连接程序错误和其他信息。代码经常要多次重复执行步骤 2 和步骤 3 才能生成没有问题的构建。
步骤 4:运行时测试和调试
成功构建应用程序后,还要对其进行测试,检查运行时错误。
步骤 5:性能调优
现在,移植的代码已经可以在目标平台上运行。可以通过监控性能来确保移植的代码按照预期的那样执行。如果没有按照预期的那样执行,必须进行性能调优。在本文的 性能调优 小节中,将提供关于可用于 Linux on POWER 的性能调优工具的详细信息。
步骤 6:打包
您是否要将得到的代码传送给其他人?如果要传送,您想使用哪种打包方法?Linux 提供了多种方法来打包应用程序,比如自安装外壳脚本或 RPM。软件打包 小节中将提供关于 Linux 的软件打包的详细信息。
一般迁移事项
这一节将提供何时将应用程序从 32 位迁移到 64 位的信息,以及跨越字节排列顺序障碍的应用程序要考虑的一些问题。从 Solaris on x86 平台上迁移的应用程序将跨越字节排列顺序障碍。熟悉 32 位到 64 位迁移和字节顺序的开发人员可以跳到下一小节 “Linux on POWER 的开发环境”。
32 位到 64 位
一旦决定移植到 Linux on POWER,如果应用程序目前是 32 位的,那么还要考虑将应用程序移植到 64 位。不过,不需要 64 位功能的 32 位应用程序应该保持 32 位。涉及迁移到 64 位的任何移植工作都应该分为两步:
- 首先,以 32 位形式迁移到目标平台。
- 然后才能迁移到 64 位。
否则,因为平台迁移导致的问题很容易与因为迁移到 64 位导致的问题混淆。也就是说,如果应用程序可以满足下列条件,则应该移植到 64 位:
- 受益于大于 4GB 的虚拟地址空间。
- 受益于更多的物理内存(大于 4GB),同时用户可能在拥有大于 4GB 的物理内存的系统中进行部署。
- 受益于 64 位整数大小。
- 受益于完整 64 位寄存器,以便进行有效的 64 位运算。
- 使用大于 2GB 的文件。
应用程序可以保持 32 位,同时仍旧在 64 位 Linux on POWER 内核中运行,而无需进行任何代码更改。基于 POWER 处理器的 IBM Linux 服务器支持 32 位和 64 位应用程序同时在 64 位架构中运行,而在这两种模式之间不会有任何性能下降。
将 32 位应用程序转换为 64 位应用程序时,可能会出现两个基本问题:数据类型不一致 和使用不同数据模型的应用程序之间的互操作。
64 位环境使用与 32 位环境不同的数据类型模型。对于 Linux on POWER,ILP 32 模型用于 32 位环境中,而 LP64 用于 64 位环境中。这两种模型之间的区别在于长整型 和指针 的大小。因此,当应用程序从 ILP32 转换为 LP64 时,数据类型中会发生基本变化。长整型和整型的大小不再相同。指针和整型的大小也不再相同。如果对象(如 struct 和 union)包含指针或长整型数据类型,那么它们的大小在 64 位环境中会比较大。通常,由这些差别引起的其他问题有:
- 数据截断
- 指针赋值
- 数据对齐
- 数据共享
IBM XL 编译器提供 -qwarn64 选项帮助检查 32 位和 64 位模式之间可能出现的数据转换问题。
将应用程序从 32 位迁移到 64 位环境的主题在其他文章中已经进行了大量讲述。因此本文中我们将不再详细说明如何处理这些移植问题。(请参阅 “将 Linux 应用程序移植到 64 位系统” 了解更多信息)。
字节顺序(字节排列顺序)
因为 SPARC 和 POWER 架构都是大字节端的,所以在这些平台之间移植应用程序时,不会存在字符排列顺序问题。然而,如果将 Solaris on x86 应用程序移植到 Linux on POWER,可能需要处理字节排列顺序可移植性问题,因为 x86 是小字节端架构。
在将应用程序从一种架构类型迁移至另一种架构类型的过程中,经常会遇到字节排列顺序(endianness)问题。字节排列顺序 是数据元素及其单个字节在内存中存储和表示时的顺序。有两类字节排列顺序:大端字节 和小端字节。
对于大端字节处理器,比如 POWER、PowerPC™ 和 SPARC,在将字放在内存中时,是从最低位地址开始的,首先放入最有效的字节。另一方面,对于小端字节处理器,比如 Intel 和 Alpha 处理器,首先放入的是最低效的字节。
图 1 显示了大端字节和小端字节处理器如何在其内存中放置十六进制值,比如 0x89ABCDEF:
图 1. 字节顺序(字节排列顺序)
出现字节排列顺序问题的原因之一是不一致的数据引用。它经常表现为由于数据元素转换、使用联合数据结构或使用和操作位域导致数据类型不匹配。清单 1 说明了因为使用指针进行不一致的数据引用而引起的字节排列顺序问题:
清单 1. 不一致的数据引用
int main() { int value; char *p; p = (char*) &value; value = 0x89ABCDEF; printf("%X.%X.%X.%X\n" p[0], p[1], p[2], p[3]); return 0; } |
Linux on POWER 的开发环境
这一节将描述 Linux on POWER 开发环境的基础元素,包括发行版、编译器和 Java™ 技术的应用。
Linux 发行版本
有两个一流 Linux 供应商提供了 Linux for POWER 的企业版:SUSE LINUX 和 Red Hat。虽然 Linux 社区非常倚重这两个版本,但是它们不断推出新的版本时,发行版和版本的差异越来越大。大部分开发人员应该能够顺利在这两个产品之一的相同代码基础上部署系 统的软件:
- SLES11
于 2009 年初发行,Novell 的 SUSE Linux Enterprise Server 版本 11 提供新的内核改进、虚拟化改进、管理、硬件支持和互操作性。它在数据中心包含用于处理任务关键型工作负载的特性和更新。SUSE Linux Enterprise Server 提供一个开源、可伸缩并且高性能的数据中心解决方案。SLES11 基于 2.6.27 内核并提供 GCC 4.3.2、glibc 2.9 和 binutils 2.19。 - SLES10 SP2
SUSE Linux Enterprise Server 版本 10 Service Pack 2 基于 Linux 2.6.16 内核并支持 Linux on POWER 硬件的所有企业特性。该开发环境提供 GCC 4.1.2、glibc 2.4.31 和 GNU binutils 2.16.91。
- RHEL5.3
Red Hat® Enterprise Linux® 5.3 基于 Linux 2.6.18 内核并提供 GCC 4.1.2、glibc 2.5 和 binutils 2.17。相对以前的版本而言,RHEL5 包含许多改进。这些改进包括可切换的动态 I/O 调试器、IPv4/IPv6 性能改进,以及改善了可伸缩性和性能的内核 SMP 锁改进。
- RHEL4.8
Red Hat Enterprise Linux 4.8 基于 Linux 2.6.9 内核并提供 GCC 3.4.6、glibc 2.3.4 和 binutils 2.15。不过,GCC4 包被更新到版本 4.1.2。(参考资料 部分提供一个文章链接,该文章在受支持的平台上对比了 RHEL3、RHEL4 和 RHEL5 的特性)。
编译器
与 Solaris 相似,除 GNU Compiler Collection 之外,Linux on POWER 还提供了高性能编译器集合 —— IBM XL C/C++ 编译器集合。XL C/C++ Advanced Edition for Linux Web 站点中有关于其许可证的详细信息。(参考资料 部分提供相关链接)。下面简单概述每组编译器及其优势:
- GCC
对于跨多个平台开发的项目(其中 GCC 编译器为原始编译器),经常使用 GCC 编译器来部署 Linux on POWER 的应用程序。对于性能不是至关重要的应用程序尤其如此;例如,一些小的实用程序。GCC 还允许使用一些只有 GCC 才理解的代码原型,比如 GCC 特定宏。然而,IBM XL C/C++ 编译器中合并了其中许多 “GCC-isms”。
- Advance Toolchain
对 于能够利用最新 GCC 和工具链技术的项目,希望继续使用 GCC 编译器的客户可以使用 Advance Toolchain 包。Advance Toolchain 提供更新的 GCC 编译器版本,并在使用 SLES10 和 RHEL5 时针对 Power 6 调用了运行时库。在许多情况下,对于利用在 -O3 编译级别上进行编译的 Advance Toolchain 的 GCC 编译应用程序,其性能接近于在 -O3 级别上进行编译的 IBM C/C++ 编译器。IBM 编译器的优势是性能改进和调优存在比较大的空间,这在某些情况下尤为有用。
- XL C/C++
针对 Power 系统的 IBM Linux XL C/C++ 编译器已经可用。针对 RHEL5、SLES10 和 SLES11 的 XL C/C++ 编译器 10.1 版本已经可用。如果您希望在更旧的 Linux 发行版(比如 SLES 9、SLES10 sp1、RHEL5 或 RHEL 4)上进行构建,IBM 编译器的以前版本(比如 XL C/C++ 版本 8 和 9)仍然可用。XL C/C++ 编译器提供 GCC 的高性能代替品,以及许多其他特性。幸运的是,XL C/C++ 编译器生成 32 位和 64 位 GNU elf 对象,它们与 GCC 编译器生成的对象完全兼容,因为 XL C/C++ 编译器使用与 GCC 相同的 GNU 库、连接程序、汇编程序和 binutil。实际上,以前专门由 GCC 编译器提供的功能已经移植到 XL C/C++,以促进源代码兼容性;例如,一些 GCC 宏和在线功能。但是除了兼容性,XL C/C++ 还提供了无可匹敌的性能。
已经为 POWER 架构开发并修改了成熟的 XL C/C++ 优化例程并且收到了预期效果。通常对于高性能计算应用程序,尤其是那些高度依赖浮点操作的应用程序,使用 XL C/C++ 进行重新编译会很有好处。
虽然 XL C/C++ for Linux on POWER 不是免费的,然而开发人员可以免费试用 60 天。详细信息请访问 XL C/C++ for Linux Web 站点(参考资料 部分提供相关链接)。
- XL Fortran
如 果您希望使用 Fortran 的话,针对 Power 系统的 IBM XL Fortran 已经可用。针对 RHEL5、SLES10 和 SLES11 的 XL Fortran 版本 11.1 和 XL Fortran 版本 12.1 已经可用。详细信息请访问 XL Fortran for Linux Web 站点(参考资料 部分提供相关链接)。
Binutil
在 Solaris 10 中,可以选择使用原生构建实用程序或使用随 GNU 一起提供的实用程序。通过 Linux on POWER,GNU binutil 用于使用 XL C/C++ 编译器集合和 GCC 来生成对象。GNU Binutils 站点中非常详细地讲述了 GNU binutil。(参考资料 部分提供相关链接)。
Java 技术
如果 Java Runtime Environment 与开发软件平台的 Java Developer Kit (JDK) 兼容,Java 技术允许开发人员跨多个平台进行部署,而不用重新编译代码。通过 IBM Developer Kit for Linux, Java Technology Edition 版本 1.4.2、5.0 和 6.0,RHEL4、RHEL5、SLES9、SLES10 和 SLES11 都支持 Linux on POWER Java 开发。这一套开发人员套件有 32 位版本和 64 位版本,可从 IBM 免费下载(见 参考资料)。
Solaris 和 Linux on POWER 之间的差别
在这一节中,将了解 Solaris 与 Linux on POWER 之间的差别,其中包括系统调用、信号、数据类型、makefile、编译器选项、连接程序选项和线程库等。
系统调用和库函数
系统调用是用户程序使用的接口,它可以使内核代表调用线程或进程执行特定函数。要执行系统调用,还需要授权内核模式的上下文切换。库函数是普通 C 函数。它们没有任何上下文切换资源损耗,因为它们是使用其每个进程的地址空间的一部分。一些 Solaris 系统调用和库函数可能不能用于 Linux 中。发生这种情况时,可能要在 Linux 中实现打包器调用。
Linux on POWER 目前提供了 300 多种不同的系统调用。/usr/include/asm-powerpc/unistd.h 或 /usr/include/asm/unistd.h 中包含系统调用列表,具体目录取决于您使用的发行版。
下面是 Solaris 和 Linux 之间不兼容的一些例子:
- regexp() 和 regcmp():如果您在 Solaris 上使用 regexp() 和 regcmp(),则在 Linux 上需要将它们替换为 regexec() 和 regcomp()。
- 文 件系统接口例程:Solaris 文件系统例程使用 vfstab 结构,函数名中包含 vfs,比如 getvfsent。Linux 提供等同的接口,但是例程使用 fstab 结构,例程名称中包含 fs,比如 getfsent。Solaris 中的 vfstab 结构是在 /usr/include/sys/vfstab.h 中定义的。Linux 中的 fstab 则是在 /usr/include/fstab.h 中定义的。
- Device Information Library Functions (libdevinfo):libdevinfo 库包含一组接口,用于访问设备配置数据,如主号码和辅助号码。标准 Linux 安装不支持这一点。
- CPU Affinity:在默认情况下,多处理器系统中的进程在多个 CPU 之间切换。在某些情况下,通过明确地将进程绑定到特定 CPU(分配 CPU 亲和力)可以提高性能。Solaris 中 CPU 亲和力的系统调用与 Linux 中的不同。Linux 2.6 内核为 CPU 亲和力提供了 sched_setaffinity() 和 sched_getaffinity()。有关的详细信息,请参阅 Linux 手册。
表 1 列出了 Solaris 系统调用,这些系统调用与 Linux 中系统调用的名称、签名不同,或在 Linux 中不可用:
表 1. Solaris 系统调用
Solaris | Linux | 说明 |
---|---|---|
acl、facl | N/A | 获取或设置文件的 Access Control List (ACL) |
adjtime | N/A | 更正时间,以允许系统时钟同步 |
audit | N/A | 向审计日志写入记录 |
auditon | N/A | 对审计进行操作 |
auditsvc | N/A | 向特定文件描述符写入审计日志 |
getacct、putacct、wracct | N/A | 获取、放入或写入扩展报告数据 |
getaudit、setaudit、getaudit_addr、setaudit_addr | N/A | 获取和设置进程审计信息 |
getauid、setauid | N/A | 获取和设置用户审计身份 |
getdents | getdents** | 读取目录条目,并以单独格式将其放入文件系统中。Linux 中的 Dirent 结构与 Solaris 中的不同 |
getmsg、getpmsg | N/A | 获取数据流中的下一条消息 |
getpflags、setpflags | N/A | 获取或设置流程标记 |
getppriv、setppriv | N/A | 获取或设置进程标志 |
getustack、setustack | N/A | 检索或更改 per-LWP 堆栈边界信息 |
issetugid | N/A | 确定当前可执行程序是在运行 setuid 还是在运行 setgid |
llseek | _llseek | 移动扩展读/写文件指针 |
_lwp_cond_signal、_lwp_cond_broadcast | N/A | 对条件变量发送信号 |
_lwp_cond_wait、_lwp_cond_timedwait、_lwp_cond_reltimedwaid | N/A | 等待条件变量 |
_lwp_info | N/A | 返回单个 LWP 的时间核算信息 |
_lwp_kill | N/A | 向 LWP 发送信号 |
_lwp_mutex_lock、_lwp_mutex_unlock、_lwp_mutex_trylock | N/A | 互斥 |
_lwp_self | N/A | 获取 LWP 标识符 |
_lwp_sema_wait、_lwp_sema_trywait、_lwp_sema_init、_lwp_sema_post | N/A | 信号灯操作 |
_lwp_suspend、_lwp_continue | N/A | 继续或暂停 LWP 执行 |
memcntl | N/A | 内存管理控制 |
meminfo | N/A | 提供内存信息 |
mount | mount* | 装载文件系统。Solaris 中此系统调用的签名与 Linux 的不同 |
msgids | N/A | 发现所有消息队列标识符 |
msgrcv | msgrcv* | 消息接收操作。Linux 在其一个参数中使用 struct msgbuf 类型 |
msgsnap | N/A | 消息队列快照操作 |
msgsnd | msgsnd* | 消息发送操作。Linux 在其一个参数中使用 struct msgbuf 类型 |
ntp_adjtime | N/A | 调整本地时钟参数 |
ntp_gettime | N/A | 获取本地时钟值 |
open、openat | open* | 打开文件。openat() 不能在 Linux 中使用 |
pcsample | N/A | 程序执行时间配置文件 |
p_online | N/A | 返回或更改处理器操作状态 |
priocntl | N/A | 进程调度器控制 |
priocntlset | N/A | 一般化的进程调度器控制 |
processor_bind | sched_setaffinity | 将 LWP 绑定到处理器 |
processor_info | N/A | 确定处理器类型和状态 |
pset_bind | N/A | 将 LWP 绑定到处理器集合 |
pset_create、pset_destroy、pset_assign | N/A | 管理处理器集合 |
pset_info | N/A | 获取处理器集合的信息 |
pset_list | N/A | 获取处理器集合的列表 |
pset_setattr、pset_getattr | N/A | 设置或获取处理器集合属性 |
putmsg、putpmsg | N/A | 发送流消息 |
rename、renameat | rename* | 更改文件名。Linux 没有 renameat() 函数 |
resolvepath | N/A | 分析路径名称的所有符号链接 |
semids | N/A | 发现所有信号灯标识符 |
setrctl、getrctl | N/A | 设置或获取资源控制值 |
settaskid、gettaskid、getprojid | N/A | 设置或获取任务或项目 ID |
shmids | N/A | 发现所有共享内存标识符 |
sigsend、sigsendset | N/A | 向处理器或处理器组发送信号 |
__sparc_utrap_install | N/A | 安装 SPARC V9 用户陷阱处理程序 |
fstatat | N/A | 获取文件状态 |
swapctl | N/A | 管理交换空间 |
uadmin | N/A | 管理控制 |
unlink、unlinkat | unlink* | 删除目录条目。Linux 没有 unlinkat() 函数 |
futimesat | N/A | 设置文件访问权和修改时间。它分析关于 fildes 参数的路径 |
waitid | N/A | 等待子进程来更改状态 |
yield | sched_yield | 放弃对其他轻量级进程的执行 |
信号
信号用于向进程或线程通知特定事件。Linux 支持 POSIX 标准信号和 POSIX 实时信号。每个信号都有惟一名称和相应的信号编号。例如,对于 Solaris,SIGSTOP 的信号编号是 23,但是对于 Linux on POWER,其信号编号则为 19。/usr/include/bits/signum 中可以找到 Linux 上的信号编号定义。
对于所有可能的信号,系统会定义出现信号时执行的默认操作:
- Terminate:默认操作是终止进程。
- Ignore:默认操作是忽略信号。
- Core:默认操作是终止进程并转储内核。
- Stop:默认操作是停止进程。
有关 Linux 信号的完整列表,包括每个信号的简短描述及发出信号时的默认行为,请通过调用下列命令查看 Section 7 中的信号手册:# man 7 signal。对于 Linux,请注意以下事项:
- SIGABRT 和 SIGIOT 是相同的。
- SIGCLD 和 SIGCHLD 是相同的。
- SIGPOLL 和 SIGIO 是相同的。
- 对于 Linux,SIGPWR 的默认操作是终止进程,而在 Solaris 中则忽略该操作。
表 2 显示了 Solaris 支持但 Linux on POWER 不支持的信号:
表 2. Solaris 支持的信号
名称 | 默认操作 | 说明 |
---|---|---|
SIGEMT | core | 模拟陷阱 |
SIGWAITING | ignore | 线程库使用的并发信号 |
SIGLWP | ignore | 线程库使用的 Inter-LWP 信号 |
SIGFREEZE | ignore | 检查点暂停 |
SIGTHAW | ignore | 检查点继续 |
SIGCANCEL | ignore | 线程库使用的取消信号 |
SIGLOST | ignore | 资源丢失(运行于 Sparc 上的 Linux 支持该项) |
SIGXRES | ignore | 资源控制超出 |
在 Soalris 和 Linux 上,sigset_t 的定义有所不同。在 Linux 上,它在 /usr/include/bits/sigset.h 中定义为:
清单 2. 如何在 Linux 上定义 sigset_t
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int))) typedef struct { unsigned long int __val[_SIGSET_NWORDS]; } __sigset_t; #endif |
在 Solaris 上,它在 /usr/include/sys/signal.h 中定义为:
清单 3. 如何在 Solaris 上定义 sigset_t
typedef struct { /* signal set type */ unsigned int __sigbits[4]; } sigset_t; |
基本数据类型和对齐
系统中可以有两种不同的数据类型:基本数据类型 和衍生数据类型。
基本数据类型是 C 和 C++ 语言规范定义的所有数据类型。表 3 对 Linux on POWER 和 Solaris 中的基本数据类型进行了比较(值以字节为单位):
表 3. 基本数据类型
基本类型 | Linux on POWER, ILP32 | Linux on POWER, LP64 | Solaris, ILP32 | Solaris, LP64 |
---|---|---|---|---|
_Bool,bool | 1 | 1 | 1 | 1 |
char | 1 | 1 | 1 | 1 |
wchar_t | 2 | 4 | 4 | 4 |
short | 2 | 2 | 2 | 2 |
int | 4 | 4 | 4 | 4 |
float | 4 | 4 | 4 | 4 |
long | 4 | 8 | 4 | 8 |
pointer | 4 | 8 | 4 | 8 |
long long | 8 | 8 | 8 | 8 |
double | 8 | 8 | 8 | 8 |
long double | 16* | 16* | 16 | 16 |
*从 Red Hat Enterprise Linux 5 (RHEL5) 和 SUSE Linux Enterprise Server 10 (SLES10) 开始,在 Linux on POWER 中 long double 的默认大小是 128 位。在 RHEL5 或 SLES10 之前,如果 XL 编译器中使用了编译器选项 -qldbl128,那么其大小可以增加到 128 位。
当在平台之间或在 32 位和 64 位模式之间移植应用程序时,需要考虑不同环境中可用对齐设置之间的差别,以避免可能发生的性能下降和数据损坏。对于 IBM XL C/C++ 编译器,集合(C/C++ 结构/联合及 C++ 类)内的每个数据类型将根据 linuxppc 或 bit-packed 规则沿字节边界对齐(使用 -qalign 选项),其中 linuxppc 为默认值。linuxppc 使用 GNU C/C++ 对齐规则维护与 GNU C/C++ 对象的二进制兼容性。
表 4 用显示了 linuxppc 和 bit-packed 规则的对齐值:
表 4. 对齐值
数据类型 | linuxppc | bit-packed |
---|---|---|
_Bool,bool | 1 | 1 |
char | 1 | 1 |
wchar_t (32-bit) | 2 | 1 |
wchar_t (64-bit) | 4 | 1 |
int | 4 | 1 |
short | 2 | 1 |
long (32-bit) | 4 | 1 |
long (64-bit) | 8 | 1 |
long long | 8 | 1 |
float | 4 | 1 |
double | 8 | 1 |
long double | 16 | 1 |
SPARC 平台通常要求数据对齐遵循相等的数据大小:2 字节类型必须在 2 字节区域内,4 字节类型必须在 4 字节区域内。因此,当您从 Solaris 移植到 Linux 时,必须特别注意 Solaris 和 Linux 之间的不同大小的数据类型。
系统衍生数据类型
衍生数据类型是现有基本类型或其他衍生类型的衍生物或结构。根据使用的数据模型(32 位或 64 位)和硬件平台,系统衍生数据类型可以具有不同的字节大小。表 5 显示了 Linux 中的一些衍生数据类型,它们与 Solaris 中的那些衍生数据类型不同:
表 5. 衍生数据类型
数据类型 | Solaris ILP32 | Solaris LP64 | Linux on POWER ILP32 | Linux on POWER LP64 |
---|---|---|---|---|
gid_t | long | int | unsigned int | unsigned int |
mode_t | unsigned long | unsigned int | unsigned int | unsigned int |
pid_t | long | int | int | int |
uid_t | long | int | unsigned int | unsigned int |
wint_t | long | int | unsigned int | unsigned int |
GNU Make 与 Solaris Make
如果在源平台中使用 Solaris Make,为了在 Linux 中使用 GNU Make,需要修改 makefile。要获得 Solaris Make 和 GNU Make 之间差别的详细信息,请参阅 IBM 红皮书 “AIX 5L Porting Guide” 的第 6 章。
编译器选项
表 6 列出了 SUN Studio C/C++ 编译器广泛使用的编译器选项,以及 GNU Compiler Collection 和 IBM XL 编译器的等同选项:
表 6. 编译器选项
Sun Studio 选项 | GNU GCC 选项 | XL C/C++ 选项 | 说明 |
---|---|---|---|
-# | -v | -v | 指示编译器报告编译进度信息 |
-### | -### | -# | 跟踪编译,而不调用任何东西 |
-dn | -static | -qstaticlink | 指定静态链接 |
-dy | (Default) | (Default) | 指定动态链接 |
-G | -shared | -qmkshrobj | 生成共享对象,而不是生成动态链接可执行程序 |
-xmemalign | -malign-natural、 -malign-power |
-qalign | 指定最大假定内存对齐(memory alignment),以及未对齐数据访问的行为 |
-xO1、-xO2、-xO3、-xO4、-xO5 | -O、-O2、-O3 | -O、-O2、-O3、-O4、-O5 | 编译过程中在所有级别上优化代码 |
-KPIC | -fPIC | -qpic=large | 生成位置无关代码以用于共享库中(大模型) |
-Kpic | -fpic | -qpic=small | 生成位置无关代码以用于共享库中(小模型) |
-mt | -pthread | Use _r invocation mode | 编译和链接多线程代码 |
-R dirlist | -Wl、-rpath、dirlist | -Wl、-rpath、dirlist | 构建可执行文件的动态库搜索路径 |
-xarch | -mcpu、-march | -qarch、-qtune | 指定目标架构指令集合 |
XL C/C++ 编译器支持 GNU 编译器选项的一部分,从而有利于移植使用 GCC 和 g++ 编译器开发的应用程序。将 gxlc 或 gxlc++ 调用命令与 GNU 编译器选项一起使用来启用该支持。这些调用命令使用纯文本配置文件来控制 GNU-to-XL C/C++ 选项映射和默认值。您可以根据需求更改这些配置文件。参考 XL 编译器手册更多地了解 gxlc 和 gxlc++。
Solaris 线程和 NPTL
这一节将说明开发人员将多线程应用程序从 Solaris 迁移到 Linux 时会遇到的问题。
Solaris 支持两种线程实现:Solaris 线程和 POSIX 线程(pthreads)。有一些函数由 Solaris 线程 API 实现,而不是由 pthreads API 实现,反之亦然。对于那些符合以上情况的函数,关联的参数可能不符合。下列是仅受 Solaris 线程 API 支持的特性:
- 创建守护进程线程的能力。守护进程线程不影响进程退出状态。进程可以通过调用 exit() 退出,也可以通过让进程中的每个非守护进程线程调用 thr_exit() 来退出。
- 使 用 thr_suspend() 和 thr_continue() 来暂停或继续线程执行的能力。注意 thr_suspend() 可以暂停目标线程,而不涉及线程可能拥有的锁。如果暂停线程调用某一个函数,而该函数需要已暂停的目标线程所拥有的锁,那么就会出现死锁。
- 允许线程等待进程中任何未检验出的线程终止的能力。当 thr_join() 的第一个参数设为 0 时就会这样。如果将 pthread_join() 的第一个参数设为 0,程序将因段错误而终止。
表 7 将关键 Solaris 线程函数与 pthreads 函数进行了比较。对于其他 Solaris 线程函数,请参阅 SUN 的 “Multithreaded Porting Guide”(见 参考资料)。
表 7. 线程和 pthreads 函数
Solaris 线程 API | Linux POSIX 线程 API | 说明 |
---|---|---|
thr_create() | pthread_create() | 创建新的控制线程 |
thr_exit() | pthread_exit() | 终止执行调用线程 |
thr_join() | pthread_join() | 暂停调用线程,直到目标线程完成 |
thr_kill() | pthread_kill() | 向其他线程发送信号 |
thr_self() | pthread_self() | 返回调用进程的线程 ID |
thr_yield() | sched_yield() | 用其他线程替换当前线程 |
thr_getprio() | pthread_getschedparam() | 获取线程的优先级参数 |
thr_setprio() | pthread_setschedparam() | 修改线程的优先级参数 |
thr_getspecific() | pthread_getspecific() | 将新的线程特定值绑定到特定键 |
thr_setspecific() | pthread_setspecific() | 将新的线程特定值绑定到特定键 |
thr_getconcurrency() | pthread_getconcurrency() | 获取线程并发级别 |
thr_setconcurrency() | pthread_setconcurrency() | 设置线程并发级别 |
thr_sigsetmask() | pthread_sigmask() | 更改或检查调用线程的信号掩码 |
thr_keycreate() | pthread_key_create() | 创建确定线程特定数据的位置的键 |
N/A | pthread_key_delete() | 删除确定线程特定数据的位置的键 |
thr_suspend() | N/A | 暂停执行指定的线程 |
thr_continue() | N/A | 继续执行暂停的线程 |
fork1() | fork() | 常规分支 |
forkall() | N/A | 复制所有分支 |
在 Solaris 9 及其更早的版本中,fork() 的行为与 POSIX 线程中 fork() 的行为不同。在 POSIX 线程中,fork() 创建新的进程,复制子进程中的全部地址空间。不过,它只复制子进程中的调用线程。Solaris 线程 API 还提供复制所有分支语义的 forkall()。该函数复制子进程中的地址空间和所有线程。POSIX 线程标准不支持这个特性。
有一些 POSIX 线程例程在 Solaris 中可以实现,但在 Linux 中却无法实现,反之亦然。表 8 列出了这些例程:
表 8. POSIX 线程例程
例程 | Solaris | Linux |
---|---|---|
pthread_cond_reltimedwait_np | y | n |
pthread_mutexattr_getrobust_np | y | n |
pthread_mutexattr_setrobust_np | y | n |
pthread_mutex_consistent_np | y | n |
pthread_mutex_reltimedlock_np | y | n |
pthread_rwlock_reltimedrdlock_np | y | n |
pthread_rwlock_reltimedwrlock_np | y | n |
pthread_attr_getaffinity_np | n | y |
pthread_attr_setaffinity_np | n | y |
pthread_cleanup_pop_restore_np | n | y |
pthread_cleanup_push_defer_np | n | y |
pthread_getattr_np | n | y |
pthread_kill_other_threads_np | n | y |
pthread_rwlockattr_getkind_np | n | y |
pthread_rwlockattr_setkind_np n y pthread_timedjoin_np n y pthread_tryjoin_np n y
2.6 版之前发行的所有 Linux 版本中的 Linux 线程库都称为 LinuxThreads。该库自 glibc 2.0 以来已经得到 GNU C 库支持,而且在很大程度上与 POSIX 是兼容的。从 2.6 内核开始,引入了新的经过改善的线程库,称为 Native POSIX Threading Library(NPTL)。该实现在 LinuxThreads 之上提供了显著的性能提高。NPTL 与 POSIX 规范的兼容性也强于 LinuxThreads 包与 POSIX 规范的兼容性。然而,只使用 2.6 内核并不意味着就使用了 NPTL。发出下列命令可以查看正在使用的 POSIX 实现:
$ getconf GNU_LIBPTHREAD_VERSIONNPTL 实现一对一线程模型,在该模型中,用户线程与内核线程之间存在一对一的关系。NPTL 还实现进程间 POSIX 同步原语。特别是线程选项 PTHREAD_PROCESS_SHARED 现在已受支持。在默认情况下,创建每个线程时,detachstate 属性被设为 PTHREAD_CREATE_JOINABLE,调用策略设为 SCHED_OTHER,而且没有用户提供的堆栈。
如果 Solaris 应用程序使用 POSIX 线程 API,那么将其移植到 Linux 会非常简单。注意,即使使用 GCC,Solaris 也不支持 NPTL。表 9 显示了 Linux 中 POSIX 线程属性的默认值:
表 9. Linux 中 POSIX 线程属性的默认值
属性 | 默认值 |
---|---|
scope | PTHREAD_SCOPE_SYSTEM |
detachstate | PTHREAD_CREATE_JOINABLE |
schedparam | 0 |
inhiritsched | PTHREAD_EXPLICIT_SCHED |
schedpolicy | SCHED_OTHER |
虽然许多应用程序将从 2.4 内核迁移到 2.6,而无需重新编译,但是增加 NPTL 可能需要在多线程应用程序中进行少量的修改。关于如何通过使用 LinuxThreads 将应用程序迁移到 NPTL 超出了本文的范围。LinuxDevices.com 上的文章 “Migrating to Linux kernel 2.6” 提供其他相关信息(见 参考资料)。
性能调优
一旦在 Linux 中移植并成功执行了代码,需要完成性能监控和性能调优,以确保移植的代码在目标平台上可以正常执行。这一节将提供 Linux on POWER 中可以使用的工具列表,帮助您完成以上操作。
常用性能调优工具
两个常用的工具(sysstat 包 和 nmon)使得性能监控和调优更加容易:
- sysstat 包包含用于基础性能监控的通用扩展工具,包括 mpstat、iostat 和 sar 等工具。
- nmon 将各种经典的系统监控工具集成到一个一站式的数据收集工具中。此外,还包含许多方便使用的后期处理插件,可用于收集、绘制和合并信息。
OProfile
Oprofile 基于硬件相关事件(比如缓存遗漏或 CPU 周期)提供代码的配置文件。例如,Oprofile 可以帮助确定是哪个源例程导致大部分缓存遗漏。Oprofile 使用包括 IBM POWER4™、POWER5™、POWER6™ 和 PowerPC™ 970 在内的许多 CPU 中提供的硬件性能计数器。有关的详细信息,请参阅 Oprofile Web 站点(参考资料 部分提供相关链接)。
针对 SLES10、SLES11 和 RHEL5 的 OProfile for Linux 已经可用,它包含在 Advance Toolchain 中。已安装系统的 /usr/share/doc/packages/oprofile 目录包含关于 Oprofile 的快速参考。
Post-Link Optimization(FDPRpro)
这个工具收集正在处理一般工作负载的程序的行为信息,从而帮助优化程序的可执行映像。信息收集完成之后,它将重新分析程序(和收集到的配置文件)、 应用全局优化(包括程序重构),并为工作负载创建新版本的应用程序。优化器生成的新程序通常会更快,并且占用的内存比原先的程序少。
在撰写本文时,Post-Link Optimization 工具在 SLES8(以及更新版本)和 RHEL3(以及更新版本)上已经得到支持。要了解更多信息,请访问 alphaWorks Post-Link Optimization for Linux on POWER 站点(参考资料 部分提供相关链接)。
软件打包
应用程序软件是以称为包 的管理单位发送给最终用户的。包是相关文件(二进制文件、库、文档和源代码)和元数据的集合。包管理系统使用元数据来协调包中的所有块。Solaris 使用 pkgadmin 作为其包管理器。RPM 是 Linux 上广泛使用的包管理系统。要获得关于 RPM 的更多信息,请访问 www.rpm.org(参考资料 部分提供相关链接)。
注意,Solaris 中 pkgadmin 使用的包规范模板文件的格式与 RPM 使用的 spec 文件的格式不同,将包信息从模板文件转换到 spec 文件需要做大量工作。
结束语
大多数情况下,从 Solaris 到 Linux on POWER 的移植工作仅涉及在编译器/连接程序开关中进行重新编译或少量更改。然而,Solaris 和 Linux 的设计本质是不相同的:
- Solaris 侧重于性能、可伸缩性和可靠性,而忽略可移植性。
- Linux 设计时考虑了可移植性,几乎现在的所有硬件平台都支持 Linux。
现在的 Linux 发行版基于 Linux 2.6 内核,该内核继续利用大量社区和团体改进。与基于 Linux 2.4 内核的旧版本相比,SLES10 SP2、RHEL5.2 和 SLES11 在性能、可伸缩性和可靠性方面都有了显著改进。对于运行在 Power 系统上的 Linux,应用程序能够利用完整的电源、性能、可伸缩性、虚拟化和管理功能。
记住,Solaris 上的一些特定于系统的特性在 Linux 上是不可用的。
致谢
我们衷心感谢 Steve Munroe、Mark Brown、Kevin Li 和 Ralph Nissler 的宝贵点评。