移植概述
移植过程本身非常简单:
回页首
移植指导
移植 Solaris 应用程序至少需要两个平台:一个源平台和一个(或多个)Linux 目标平台。在将应用程序移植到 Linux 目标平台上运行时,您应该考虑很多因素:
如果只有硬件和操作系统被改变了,那么您进行的就是真正的移植。您可以使用不同的方法来实现目标。下面的选项描述了移植方法的两个步骤:
Sun 的开发工具使创建兼容的源代码更容易,并有助于迁移极为重要的实用程序代码以避免重写,还可以减少安装时间。这个选项可以帮助 Linux 经验有限的开发者将 Solaris 源代码移植到 Linux 目标平台上。下面的 Web 站点提供了 Solaris GNU 工具:
http://www.sun.com/solaris/freeware.html
http://www.sunfreeware.com
在构建和测试了被移植的代码之后,您就可以遵循下一个选项来将代码移植到 Linux 目标平台了。
要开始移植,请仔细检查 Solaris 源代码,并测试这些代码以保证其运行无误。检查过程应该包括完整的编译、重构建以及测试周期。大多数情况下,代码在 Linux 目标系统上会出错,因为最初的源代码树中安装了有错误的软件更新。因此,除非您亲自测试过代码,否则不能轻信在移植时通常会听到的断言(“它在源系统上没有问题!”)。
下面的参考资料也有助于您进行 Linux 移植:
回页首
移植过程
下面的建议不是将 Solaris 应用程序移植到 Linux 环境的唯一方法。您可以运用自己的移植经验来建立自己的移植方法。
选择移植开发平台
Sparc 平台:这种方法能够让 Solaris 开发者轻松地实现迁移,将 Linux 应用程序移植到其它目标平台上。因为 Sun 提供了公共库和构建环境,从而将开发在 Linux 和 Solaris 操作环境上兼容的源代码的过程流水线化,所以第一步是修改 Solaris 平台上的移植代码。这些工具包括下面的使用指南:
在 Solaris 平台上测试和构建了 Linux 应用程序之后,您就可以移植到 Linux 目标平台上了。这种方法有下面几个优势:
Linux 目标平台:经验丰富的 Linux 开发者可以在 Linux 目标平台上修改要移植的代码。您需要确保安装了正确版本的库和编译器,如 gcc、tcl/tk、glib、GNOME 和 KDE。
使用 grep 命令
在确定移植开发平台之后,您需要搜索下面内容,它们可能会导致源代码中出现移植问题:
搜索了可能产生的问题之后,您需要决定是否希望保留单独的源代码库,以支持多平台(Solaris、Linux 及其它平台)。
找出潜在的问题
在使用 grep 命令之后,您还需要检查是否存在低效率或不可移植的代码。下面的列表可以帮助您找出移植问题:
使用移植工具
下面的工具已经可供使用,或者已在开发之中:
ibm.com/developerworks/linux/tools/l-solar.html
http://www.migratec.com/MigraTEC/migration_suite.htm
修正编译时找出的问题
通常,要重复好几次才能够编译出没有问题的代码。请确保使用了 -Wall 选项,以捕获所有警告消息。
测试和调试程序
您可以使用 gdb 工具来调试修改过的代码。要了解更多关于 gdb 的信息,请参阅 GNU gdb 调试程序。
回页首
比较 Linux 与 Solaris
这一部分将讨论目录、文件系统、信号、尾数问题、系统派生的数据类型以及绝对地址。
目录
Linux 目录的结构是标准化的。每个目录都有定义精确的任务。典型的 Solaris 和 Linux 安装将创建如下表所示的目录:
Solaris 目录 | 功能 | Linux 目录 | 功能 |
/bin | 用户命令。 | /bin | 包含普通用户启动时和启动后所需的二进制文件。 |
/boot | 包含开始启动过程的 LILO 引导程序所需的文件。内核文件驻留在 /boot 中。 | ||
/dev | 包含 /devices 中的符号链接条目,大多数真正的设备条目都是在 /devices 中创建的。 | /dev | 包含真正的块、字符和其它指向设备的设备文件,如 fd0(第一个软盘驱动器)和 hda1(第一个硬盘驱动器上的第一个分区)。 |
/devices | 对于系统中的每个真正的设备来说,都应该在该目录中为其创建一个条目。 | ||
/etc | 其它命令、网络配置文件和脚本等等。 | /etc | 预留给属于机器本身的配置文件。/etc 中没有二进制文件。X-Windows 配置文件 XF86Config 存储在 /etc/X11 中。 |
/home | 通常用作用户主目录。 | /home | 通常用作用户主目录。 |
/kernel | 包含内核模块。 | 对应的内核文件在 /boot 和 /lib 模块中。 | |
/opt | 应用程序包。 | ||
/platform | 特定于平台的 Unix 和用于启动的设备驱动程序。 | ||
/proc | 包含关于活动进程和线程的信息,还提供一个接口来控制这些进程和线程。每个进程目录都有一些文件条目,包含该进程的内核结构。 | /proc | 包含深入到内核的文件系统视图。对于每个进程,都有多个目录,另外,还有对应于系统计数器和限制的目录和文件。 |
/lib | 只包含那些需要执行 /bin 和 /sbin 中的二进制文件的库。/lib/modules 包含可装入的内核模块。 | ||
/lost&found | |||
/mnt | 系统管理员进行临时安装所用的安装点。 | ||
/root | 这是 root 用户的主目录,除了 root 用户的概要文件信息之外,里面通常没有任何重要文件。 | ||
/sbin | 系统控制命令,例如 mount 和 umount。 | /sbin | 只被 root 用户使用的可执行文件,而且只是那些需要安装 /usr 并执行系统恢复操作的可执行文件。 |
/tmp | 它也是一个映射到系统内存的特殊文件系统。 | /tmp | 与 Solaris 系统相同 |
/usr | 编译器,管理性质的。 | /usr | 多数应用程序软件安装所在的位置。 |
/var | 包括 lpd 和 mail spool。也被各种需要记录日志文件的应用程序(如系统消息)所使用。 | /var | 包括 lpd 和 mail spool。也被各种需要记录日志文件的应用程序所使用。 |
文件系统
Linux 可以使用几种类型的文件系统。每种文件系统都有自己的格式和一组特征(如文件名长度、文件大小的最大值等等)。Linux 还支持几种第三方文件系统类型,如 MS-DOS 文件系统。下面的表列出了 Linux 可以使用的各种文件系统类型:
文件系统 | 类型名 | 注释 |
Second Extended Filesystem | ext2fs | 最常见的 Linux 文件系统 |
Third Extended Filesystem | ext3fs | ext2fs 的日志记录文件系统 |
Extended Filesystem | ext | 已被 ext2fs 取代 |
Minix Filesystem | minix | 最早的 Minix 文件系统 |
Xia Filesystem | Xia | 与 ext2 类似 |
UMSDOS Filesystem | umsdos | 用于在 DOS 分区上安装 Linux |
MS-DOS Filesystem | msdos | 使用 tp 处理 MS-DOS 文件 |
/proc Filesystem | proc | 提供系统信息 |
System V Filesystem | sysv | 用于访问系统 V |
HPFS Filesystem | hpfs | HPFS 分区的只读访问 |
Journal Filesystem | jfs | 有日志记录的文件系统 |
ReiserFS Filesystem | reiserFs | 使用经典的平衡树(balanced tree)算法上的变量 |
最常用的文件系统类型是 Second Extended Filesystem,或者说 ext2fs。ext2fs 是最有效、最灵活的一种文件系统;它允许文件名长达 256 个字符,文件系统大小最多可以达到四千兆字节。您还可以将 ext2fs 升级为 ext3fs。您可以在 /proc/filesystem 目录的文件中找到您的内核目前支持哪些文件系统。
字节顺序问题
SPARC 体系结构是big endian(BE)的,它具有向前的字节顺序。第 0 位是最不重要的位,而第 0 字节是最重要的字节。请注意,在从 Solaris 向 pSeries 或 IBM eServer zSeries 移植时,字节顺序问题并不会产生影响,因为它们都是 BE 平台。Intel x86 体系结构是little endian(LE),所以第 0 字节是最不重要的字节(LSB)。图 1 说明了 BE 和 LE 表达方式的字节顺序:
数据类型不匹配
当把 4 字节的整数当作整数数据类型的数据元素对待时,LE 和 BE 则按相反方向查看整数中的各个位和字节。
int a=0x11121314; char b, *ptr; ptr= (char *) &a; // pointer ptr points to a b= ptr[1]; // b is 0x13 in LE and 0x12 in BE |
使用与尾数无关的解决方案可以解决 LE 和 BE 之间的字节顺序问题。
#define INTB16TO23(a) ((a>>16) & 0xff) b= INTB16TO23(a); // b is 0x12 in BE and LE |
网络通信
如果要在 BE 和 LE 系统之间转移多字节值的数据,那么我们要做的就只是提供交换字节的代码。对于网络通信来说,象 htons() 和 ntohs() 这样的函数也用来将端口号转换为网络字节顺序。
常用的尾数解决方案指南
要使代码能够移植,您可以使用如下的宏和条件编译指令:
#define BIG_ENDIAN 0 #define LITTLE_ENDIAN 1 #define BYTE_ORDER BIG_ENDIAN |
另一种可以实现这一点的选项是在编译命令行定义 BYTE_ORDER 的值。您可以只改变 makefile 来构建应用程序,也可以使用 GCC 编译选项来选择合适的特定于尾数的代码片段。
信号
Linux 接受四种缺省操作作为信号:忽略、停止进程、终止进程或在终止进程之后生成核心转储。Linux 支持由 System V、BSD 和 POSIX 提供的几乎每种信号,除了下面的特殊情况:
下面的 web 站点提供了关于 Linux 信号的更多详细信息:
http://www.linux-mag.com/2000-01/compile_01.html
http://www.linuxhq.com/guides/LPG/node138.html - SECTION001120000000000000000
注意:POSIX.1 只定义 SA_NOCLDSTOP。当移植使用信号操作的应用程序时,您可能必须修改 sa_flag 的值来获取合适的行为。
Solaris 中有 sig2str() 和 str2sig() API,它们用于在信号和字符串之间对信号名进行来回转换。因为 Linux 不支持这些 API,所以下面的代码对移植不起作用:
#include<signal.h> #include<stdlib.h> #include<stdio.h> int main(int argc, char **argv) { char signame[SIG2STR_MAX]; sig2str(atoll(argv[1]), signame); printf("Received signal = %s \n", signame); return 0; } |
下面是修正后的用于向 Linux 进行移植的代码:
#include<signal.h> #include<stdlib.h> #include<stdio.h> int main(int argc, char **argv) { const char *str int signo_ = atoll(argv[1]); str = sys_siglist[signo_]; printf ("Received signal = %s \n", str); return 0; } |
下面的表列出了 Solaris 和 Linux 操作系统中常用的信号。
Solaris | Solaris 缺省操作 | Linux | Linux 缺省操作 |
SIGHUP | 终止 | SIGHUP | 忽略 |
SIGINT | 终止 | SIGINT | 忽略 |
SIGQUIT | 终止,核心 | SIGQUIT | 终止,核心 |
SIGILL | 终止,核心 | SIGILL | 终止,核心 |
SIGTRAP | 终止,核心 | SIGTRAP | 忽略 |
SIGABRT | 终止,核心 | SIGABRT | 终止,核心 |
SIGEMT | 终止,核心 | SIGEMT | Linux 上不支持 |
SIGFPE | 终止,核心 | SIGFPE | 终止,核心 |
SIGKILL | 终止 | SIGKILL | 终止 |
SIGBUS | 终止,核心 | SIGBUS | 终止,核心 |
SIGSEGV | 终止,核心 | SIGSEGV | 终止,核心 |
SIGSYS | 终止,核心 | SIGSYS | Linux 上不支持 |
SIGPIPE | 终止 | SIGPIPE | 忽略 |
SIGALRM | 终止 | SIGALRM | 忽略 |
SIGTERM | 终止 | SIGTERM | 终止 |
SIGUSR1 | 终止 | SIGUSR1 | 忽略 |
SIGUSR2 | 终止 | SIGUSR2 | 忽略 |
SIGCHLD | 忽略 | SIGCHLD | 忽略 |
SIGPWR | 忽略 | SIGPWR | 忽略 |
SIGWINCH | 忽略 | SIGWINCH | 进程停止 |
SIGURG | 忽略 | SIGURG | 忽略 |
SIGPOLL | 终止 | SIGPOLL | Linux 上不支持 |
SIGSTOP | 进程停止 | SIGSTOP | 进程停止 |
SIGSTP | 进程停止 | SIGSTP | 进程停止 |
SIGCONT | 忽略 | SIGCONT | 忽略 |
SIGTTIN | 进程停止 | SIGTTIN | 进程停止 |
SIGTTOU | 进程停止 | SIGTTOU | 进程停止 |
SIGVTALRM | 终止 | SIGVTALRM | 终止,核心 |
SIGPROF | 终止 | SIGPROF | 忽略 |
SIGXCPU | 终止,核心 | SIGXCPU | 终止,核心 |
SIGXFSZ | 终止,核心 | SIGXFSZ | 终止,核心 |
SIGWAITING | 忽略 | SIGWAITING | Linux 上不支持 |
SIGLWP | 忽略 | SIGLWP | Linux 上不支持 |
SIGFREEZE | 忽略 | SIGFREEZE | Linux 上不支持 |
SIGTHAW | 忽略 | SIGTHAW | Linux 上不支持 |
SIGCANCEL | 忽略 | SIGCANCEL | Linux 上不支持 |
SIGRTMIN | 终止 | SIGRTMIN | Linux 上不支持 |
SIGRTMAX | 终止 | SIGRTMAX | Linux 上不支持 |
系统派生的数据类型
系统派生的数据类型可以有不同的字节大小。派生的数据类型就是用 typedef 定义的派生类型,或者用已知基本类型的结构定义的类型。下面几部分将比较 Solaris 和 Linux 中最常见的系统派生的数据类型。
数据类型 gid_t 用来代表用户的组 ID,而 uid_t 用来代表用户 ID。数据类型 mode_t 用来表示文件的模式,而 pid_t 用来以唯一的数字标识不同的进程。
OS | gid_t | mode_t | pid_t | uid_t |
Solaris | long | unsigned long | long | long |
Linux | unsigned int | unsigned int | int | unsigned int |
数据类型 size_t 和 ssize_t 应该结合内存中对象的大小来使用,并返回字节计数或错误指示。当程序使用多字节和宽字符子例程时,就需要数据类型 wint_t 来表示宽字符代码值以及文件尾标记。
OS | size_t | ssize_t | wint_t |
Solaris | unsigned int | int | long |
Linux | unsigned long | int | unsigned int |
绝对地址
每个平台都可能使用不同的内存位置来存储程序堆栈、系统库、堆等等。因此,使用硬编码地址已经很难保证到其它系统的可移植性了。某些寻址方式还忽略高阶位,所以硬编码地址 0x80000000(高阶位是 1)将被转换为 0x00000000,这很可能是非法的,而且绝对不是您想要的。在生成地址的时候,对地址的算术运算也能使高阶位变为 1。
如果应用程序使用了导致分段冲突或其它错误的绝对地址,那么就必须改变它。/proc/<pid>/map 将展示一个活动进程如何使用它内部地址空间的存储范围。如果代码必须使用绝对地址,那么这个 map 就可以用来查找还没有被保留的地址范围。
回页首
一些应用程序开发工具
C/C++ 应用程序基础架构
操作系统 |
|
运行时库 |
|
C/C++ 应用程序工具的使用
分析和设计 |
|
IDE |
|
低级编辑/编译/调试 |
|
构建 |
|
打包 |
|
测试 |
|
性能调优及基准测试 |
|
源代码管理 |
|
维护和故障检测 |
|
Java 应用程序工具的使用
|
|
IDE |
|
JVM |
|
编译器 |
|
性能工具 |
|
源代码管理 |
|
维护和故障检测 |
|
回页首
Solaris make 对 GNU make
Solaris make 对 GNU make
Solaris make 和 GNU make 都定义了相同的基本后缀集和隐式规则。下面的目标、宏和变量都是相同的:
注意:GNU make 不完全支持 %。只有第一个 % 才会被替换。
下面是示例:
#Begin Makefile FOO=abc def BAR=$(FOO:%=dir1/%.o dir1/%_cltn.o) BAR2=$(FOO:%=dir1/%.o) $(FOO:%=dir1/%_cltn.o) all: @echo FOO is $(FOO) @echo BAR is $(BAR) @echo BAR2 is $(BAR2) #end Makefile |
在 Sun 上,make 命令将打印:
FOO is abc def BAR is dir1/abc.o dir1/abc_cltn.o dir1/def.o dir1/def_cltn.o BAR2 is dir1/abc.o dir1/def.o dir1/abc_cltn.o dir1/def_cltn.o |
在使用 GNU 的 Linux 上,make 命令将打印:
FOO is abc def BAR is dir1/abc.o dir1/%_cltn.o dir1/def.o dir1/%_cltn.o BAR2 is dir1/abc.o dir1/def.o dir1/abc_cltn.o dir1/def_cltn.o |
为了解决这个差别带来的问题,您需要按下面所示修改代码:
BAR = $(FOO:%=dir1/%.o) BAR += $(FOO:%=dir/%_cltn.o) BAR2 = $(FOO:%=dir/%.o) BAR2 += ${FOO:%=dir1/%_cltn.o) |
make 的后缀
make 自带的内置后缀规则让开发者可以极大地简化 makefile。下面的单后缀和双后缀的表包含 Solaris 和 GNU make 命令的缺省推理规则。
单后缀推理规则
后缀 | Make | 单后缀规则 |
.c | Solaris | $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBD) |
GNU | $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(LOADLIBES) $(LDLIBS) -o $@ |
|
.C | Solaris | $(CCC) $(CCFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) |
GNU | $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(LOADLIBES) $(LDLIBS) -o $@ |
|
.cc | Solaris | $(CCC) $(CCFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) |
GNU | $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $(LOADLIBES) $(LDLIBS) -o $@ |
|
.f | Solaris | $(FC) $(FFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) |
GNU | $(FC) $(FFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(LOADLIBES)$(LDLIBS) -o $@ | |
.F | Solaris | $(FC) $(FFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) |
GNU | $(FC) $(FFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(LOADLIBES)$(LDLIBS) -o $@ | |
.mod | Solaris | $(M2C) $(M2FLAGS) $(MODFLAGS)-o $@ -e $@ $< |
GNU | $(M2C) $(M2FLAGS) $(MODFLAGS) $(TARGET_ARCH) -o $@ -e $@ $^ | |
.p | Solaris | $(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) |
GNU | $(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(LOADLIBES) $(LDLIBS) -o $@ |
|
.r | Solaris | $(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) |
GNU | $(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(LOADLIBES) $(LDLIBS) -o $@ |
|
.sh | Solaris | $(RM) $@ cat $<>$@ chmod -x $@ |
GNU | cat $<>$@ chmod a+x $@ |
双后缀推理规则
后缀 | Make | 双后缀规则 |
.c.o | Solaris | $(CC) $(CFLAGS) $(CPPFLAGS) -c $(OUTPUT_OPTION) $< |
GNU | $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c$(OUTPUT_OPTION) $< | |
.c.ln | Solaris | $(LINT) $(LINTFLAGS) $(CPPFLAGS) $(OUTPUT_OPTION) -c $< |
GNU | $(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -C$* $< | |
.cc.o | Solaris | $(CCC) $(CCFLAGS) $(CPPFLAGS) -c $(OUTPUT_OPTION) $< |
GNU | $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c$(OUTPUT_OPTION) $< | |
.C.o | Solaris | $(CC) $(CFLAGS) $(CPPFLAGS) -c $(OUTPUT_OPTION) $< |
GNU | $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c$(OUTPUT_OPTION) $< | |
.def.sym | Solaris | $(M2C) $(M2FLAGS) $(DEFFLAGS) -o $@ $< |
GNU | $(M2C) $(M2FLAGS) $(DEFFLAGS) $(TARGET_ARCH) -o $@ $< | |
.f.o | Solaris | $(FC) $(FFLAGS) -c $(OUTPUT_OPTION) $< |
GNU | $(FC) $(FFLAGS) $(TARGET_ARCH) -c $(OUTPUT_OPTION) $< | |
.F.o | Solaris | $(FC) $(FFLAGS) $(CPPFLAGS) -c $(OUTPUT_OPTIONS) $< |
GNU | $(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c$(OUTPUT_OPTION) $< | |
.l.c | Solaris | $(RM) $@ $(LEX) $(LFLAGS) -t $< > $@ |
GNU | @$(RM) $@ $(LEX) $(LFLAGS) -t $< > $@ |
|
.l.ln | Solaris | $(RM) $*.c $(LEX) $(LFLAGS) -t $< > $*.c $(LINT) $(LINTFLAGS) $(CPPFLAGS) -o $@ -i $*.c $(RM) $*.c |
GNU | @$(RM) $*.c $(LEX) $(LFLAGS) -t $< > $*.c $(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -i $*.c -o $@ $(RM) $*.c |
|
.mod.o | Solaris | $(M2C) $(M2FLAGS) $(MODFLAGS) -o $@ $< |
GNU | $(M2C) $(M2FLAGS) $(MODFLAGS) $(TARGET_ARCH) -o $@ $< | |
.r.o | Solaris | $(COMPILE.r) $(OUTPUT_OPTION) $< |
GNU | $(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -c $(OUTPUT_OPTION)$< | |
.s.o | Solaris | $(AS) $(ASFLAGS) -o $@ $< |
GNU | $(AS) $(ASFLAGS) $(TARGET_MACH) -o $@ $< | |
.S.o | Solaris | $(AS) $(ASFLAGS) -o $@ $< |
GNU | $(CC) $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c -o $@ $< | |
.y.c | Solaris | $(YACC) $(YFLAGS) $< mv y.tab.c $@ |
GNU | $(YACC) $(YFLAGS) $< mv -f y.tab.c $@ |
|
.y.ln | Solaris | $(YACC) $(YFLAGS) $< $(LINT) $(LINTFLAGS) $(CPPFLAGS) -o $@ -i y.tab.c$ (RM) y.tab.c |
GNU | $(YACC) $(YFLAGS) $< $(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -C$* y.tab.c $(RM) y.tab.c |
回页首
C 编译器的选项
Sun Workshop(Forte)编译器和 GCC 都支持扩展 C 语言。Sun Workshop 编译器的扩展几乎等同于 GCC。扩展的编译指示的形式是独一无二的。GCC 通常不使用编译指示,但在使用了 -Wall 选项的时候会警告忽略了编译指示。
GCC 上的 -m486 选项用来编译 x486 机器的二进制文件,它只会改变某些特定的优化。有一种版本的 GCC 可以对 586 进行很好的优化,但它很不可靠,尤其是在高优化设置时。关于 Pentium GCC,请参阅:
ftp://tsx-11.mit.edu/pub/linux/ALPHA/pentium-gcc/。
对于 pSeries 和 RS/6000 来说,使用“-mcpu”选项将禁用“-mpower”或“-mpowerpc”选项。我们推荐的做法是使用缺省的“-mcpu=common”。
对于 SPARC 来说,使用“-mcpu”选项也会为机器类型选择体系结构。
GNU C 库也有线程意识。当您链接到线程处理库 libpthread(用 gcc 的命令行参数 pthread )时,libc.so 中的某些函数将被线程处理库中的函数覆盖。
下面的表包含 Solaris 和 GNU GCC 上的 C 编译器的常用选项。
Sun Workshop | GCC | 描述 |
-# | -v | 打开详细(verbose)模式,显示每个被调用的组件。 |
-Xa | -ansi | 指定与 ANSI/ISO 标准的兼容。GCC 支持所有 ISO C89 程序。您可以使用“-std”来指定特殊版本的 ISO C。 |
-xinline | -finline-functions | 只插入那些指定的函数。 |
-p | -p | 生成额外代码,用来写适用于分析程序简档的概要文件信息。 |
-xa | -ax | 生成额外代码,用来写基本块的概要文件信息,记录每个基本块被执行的次数。 |
-xspace | -O0 | 不进行优化。 |
-xunroll= | -finroll_loops | 只对迭代次数可以在编译时或运行时决定的循环进行循环打开优化。 |
-xtarget = name | -b=machine | 参数机器指定进行编译的目标机器。另外,这里的每个目标机器类型都可以有自己特殊的选项,以“m”开头,在各种硬件模型或配置中进行选择。 |
-xo | -O, -O1, -O2, -O3, -Os | 控制各种优化。 |
-O | 同上 | 控制各种优化。 |
-xmaxopt | 保证 GCC 不使用编译指示。 | |
-xnolib | -nostdlib | 缺省情况下不链接任何库。 |
-fsingle | -fsingle-precision-constant | 将浮点常量作为单精度常量对待,而不是隐式地将其转换为双精度。 |
-C | -C | 告诉预处理器不要废弃注释。与“-E”选项一起使用。 |
-xtrigraphs | -trigraphs | 支持 ISO C trigraph。 |
-E | -E | 预处理所有指定的 C 源文件,并将结果输出到标准输出或指定的输出文件。 |
-xM | -M | 只运行指定的 C 程序上的预处理器,要求生成 makefile 依赖性(dependencies)信息并将结果发送到标准输出。 |
-xpg | -pg | 生成额外代码,来写适用于分析程序 gprof 的概要文件信息。 |
-c | -c | 命令编译器不要用 ld 进行链接,并为每个源文件生成 .o 文件。 |
-o | -o | 命名输出文件。 |
-S | -S | 命令 cc 生成组装源文件但并不组装程序。 |
-xtemp | TMPDIR | 如果设置了 TMPDIR 环境,就指定临时文件要使用的目录。 |
-xhelp=f | -help | 显示联机帮助信息。 |
-xtime | -time | 报告编译序列中的每个子进程所使用的 CPU 时间。 |
-w | -q | 禁止编译器警告。 |
-erroff= %none | -W | 显示警告消息。 |
-errwarn | -Werror | 将所有警告消息转换为错误消息。 |
回页首
链接程序调用
Sun 和 GNU 链接程序都接受为数众多的选项。下面的表解释了 SPARCworks 链接程序最常用的选项与 GNU 链接程序的功能对比。
Solaris Workshop | gld 选项 | 描述 |
-a | -static | 启用静态模式的缺省行为,防止与共享库进行链接。链接程序会创建可执行文件和导致错误消息的未定义符号。GNU ld 有 -static 选项,也能启用这种行为。 |
-b | 不用 -fPIC/-fpic 选项编译源代码,从而实现 GNU 链接程序的同等功能。 | |
-g | -g | 以操作系统的本机格式生成调试信息。 |
-G | -shared | 生成共享对象。与 GNU 链接程序等同的是 -shared。 |
-m | (-M) | 打印链接程序映射。-M 选项打印可以比较的某些东西,只是它们的格式不同,内容也稍有不同。 |
-s | -S/-s | 使用 GNU 链接程序的 -S(它只会删除调试信息),实现与 -s 选项同等的效果。 |
-h name | -sonamename | 将名称设置为共享对象的名称。如果使用 GNU 链接程序,就必须用 -soname 选项。 |
-o filename | -o filename | 将输出放在文件中。不管产生的输出是何种类型,它都适用。缺省情况是将可执行文件放在“a.out”中。 |
-Ldirectory | -L dir | 将目录 dir 添加到目录列表中。 |
-R path | -rpathpath | 指定运行时链接程序的搜索方向。GNU 链接程序使用 -rpath 选项。 |
每个 Linux 共享库都被分配了一个叫作 soname的特殊名称,它包括库的名称和版本号。除非您有特别的理由,否则请不要链接到特定版本之外的库上。通常请使用 C 编译器或链接程序的标准的 -l libname选项。链接程序将查找文件 lib libname.so,该文件是到正确版本的库的符号链接。
特定于 pSeries 和 RS/6000 的 GCC 选项
选项 | 描述 |
-fPIC | 生成与位置无关的代码(position-independent code,PIC),以便在共享库中使用。 |
-mpower、-mno-power、-mpowerpc、-mno-powerpc、-mnpowerpc64、-mno-powerpc64 | 指定 GCC 将生成各种体系结构的指令。 |
-mcpu=cpu_type | 设置体系结构类型、注册程序的使用、助记符的选择和特定机器类型的指令调度参数。 |
-mtune=cpu_type | 设置特定机器类型的指令调度参数。 |
-mlittle | 用小尾数模式编译 Power PC 处理器的代码。 |
-mbig | 用大尾数模式编译 Power PC 处理器的代码。 |
-mcall-linux | 编译 Power PC 基于 Linux 的 GNU 系统的代码。 |
回页首
Solaris 线程库(Solaris Thread Library,STL)
为 Solaris 编写的应用程序经常会使用非标准的专有函数,这会令向 Linux 进行移植的工作很困难。Linux( Intel 平台)上支持一种临时的 STL,这将减少迁移这些应用程序的麻烦。它提供了基于 POSIX 线程库的 Solaris 线程 API 的实现。STL 是 Solaris 兼容性库(Solaris Compatibility Libraries,SCL)的一个组件,您可以按开放源代码的形式免费获取 SCL 的这个部分。您可以从Compaq发起的 Sourceforge 项目获取它,这个项目位于:
http://sourceforge.net/projects/sctl
局限性
SCL STL 不支持下面的功能:
要让开发者使用 STL 库,您需要留意线程的局限性和问题。举例来说,POSIX 线程的暂挂和线程继续没有得到支持。一篇题为“Building an Open-Source Solaris-Compatible Threads Library”( Proceedings of the FREENIX,2001 年 6 月)的文章中包含更多详细信息和实现注释。您可以从下面的 Web 站点获取该文章的副本:
http://www.opensource.compaq.com/the_source/linux_papers/scl_solaris.htm
我们希望今后 Linux 版本的 STL 能够支持其它 Linux 目标平台(RS/6000、Power PC 等等)。
回页首
系统接口
下面这部分将讨论 Solaris 中使用了线程的应用程序、Solaris 中使用了 POSIX 线程的应用程序、Solaris 线程库和 POSIX 线程库的对比,以及对接口和定义的支持。
Solaris 中使用了线程的应用程序
使用 Solaris 线程编程接口(API)的应用程序基本上是不可移植的,例如 thr_create()、mutex_lock()、cond_signal() 等等。POSIX 线程中不支持的 Solaris 线程函数的主要方面有:
如果前面所述的任何一方面需要满足,那么将使用 Solaris 线程的应用程序移植到 Linux 就需要一定程度的工作了。否则,Solaris 线程 API 和 POSIX 线程 API 之间的映射就非常简单了。
我们不推荐您将 STL 与 POSIX 线程 API 调用混在一起。如果可能的话,您应该使用本机库,因为这将使您能够最大程度地控制线程。
下面的表将比较 Solaris 线程 API 与 POSIX 线程 API。
Solaris 线程 API | Linux POSIX 线程 API | 操作 |
thr_create() | pthread_create() | 创建一个新的控制线程。 |
thr_exit() | pthread_exit() | 终止调用线程的执行。 |
thr_getprio() | pthread_getschedparam() | 检索线程的优先级参数。 |
thr_getspecific() | pthread_getspecific() | 将新的特定于线程的值绑定到键。 |
thr_join() | pthread_join() | 暂挂调用线程,直到目标线程结束。 |
thr_keycreate() | pthread_key_create() | 创建一个定位特定于线程的数据的键。 |
thr_kill() | pthread_kill() | 向另一个线程发送信号。 |
thr_self() | pthread_self() | 返回进行调用的进程的线程 ID。 |
thr_setprio() | pthread_setschedparam() | 修改线程的优先级参数。 |
thr_setspecific() | pthread_setspecific() | 将新的特定于线程的值绑定到键。 |
thr_sigsetmask() | pthread_sigmask() | 改变或者检查发出调用的线程的信号掩码。 |
thr_yield() | sched_yield() | 使当前线程对另一个线程让步。 |
thr_setconcurrency() | pthread_setconcurrency() | 设置线程并发性级别。 |
thr_getconcurrency() | pthread_getconcurrency() | 获取线程并发性级别。 |
thr_setspecific() | pthread_setspecific() | 设置特定于线程的数据键。 |
thr_getspecific() | pthread_getspecific() | 获取特定于线程的数据键。 |
thr_suspend | 不支持 | 暂挂指定线程的执行。 |
thr_continue | 不支持 | 恢复被暂挂的线程的执行。 |
请注意:POSIX 标准不提供任何使得线程 A 在不与线程 B 合作的情况下暂挂线程 B 的执行的机制。实现暂挂或者重启机制的唯一方法就是让线程 B 周期性地检查某个全局变量,查找暂挂请求,然后找到条件变量时将自己暂挂(另一个线程可以在这之后对其发出信号,以重启 B)。
Solaris 中使用了 POSIX 线程的应用程序
Solaris 中使用了 POSIX 线程的应用程序很容易向 Linux 移植,只是在进程共享功能中会出现一些异常。
Solaris 线程库与 POSIX 线程库的对比
Linux 支持 POSIX 1003.1c。Solaris 既支持 POSIX 1003.1c,又支持专有信号 API。下面的表对专有的 Sun 例程和 Linux 中实现的 POSIX 1003.1c API 进行了对比。
Solaris 和 Linux 的接口之间的一个主要区别就是,Solaris 在 libthread 中定义这些函数,而 Linux 在 libpthread 中定义这些函数。
Solaris 库(lib 线程) | Linux POSIX 库(libp 线程) | 操作 |
sema_destroy() | sem_destroy() | 销毁信号状态。 |
sema_init() | sem_init() | 初始化信号。 |
sema_post() | sem_post() | 增加信号。 |
sema_wait() | sem_wait() | 阻止信号计数。 |
sema_trywait() | sem_trywait() | 减少信号计数。 |
mutex_destroy() | pthread_mutex_destroy() | 销毁或禁用与互斥对象相关的状态。 |
mutex_init() | pthread_mutex_init() | 初始化互斥变量。 |
mutex_lock() | pthread_mutex_lock() | 锁定互斥对象和块,直到互斥对象被释放。 |
mutex_unlock() | pthread_mutex_unlock() | 释放互斥对象。 |
cond_broadcast() | pthread_cond_broadcast() | 解除对等待条件变量的所有线程的阻塞。 |
cond_destroy() | pthread_cond_destroy() | 销毁与条件变量相关的任何状态。 |
cond_init() | pthread_cond_init() | 初始化条件变量。 |
cond_signal() | pthread_cond_signal() | 解除等待条件变量的下一个线程的阻塞。 |
cond_wait() | pthread_cond_wait() | 阻止条件变量,并在最后释放它。 |
rwlock_init() | pthread_rwlock_init() | 初始化读/写锁。 |
rwlock_destroy() | pthread_rwlock_destroy() | 锁定读/写锁。 |
rw_rdlock() | pthread_rwlock_rdlock() | 读取读/写锁上的锁。 |
rw_wrlock() | pthread_rwlock_wrlock() | 写读/写锁上的锁。 |
rw_unlock() | pthread_rwlock_unlock() | 解除读/写锁。 |
rw_tryrdlock() | pthread_rwlock_tryrdlock() | 读取非阻塞读/写锁上的锁。 |
rw_trywrlock() | pthread_rwlock_trywrlock() | 写非阻塞读/写锁上的锁。 |
要使用 POSIX 线程的 read/write lock 扩展,您必须在编译时定义:
_XOPEN_SOURCE=500 |
这要在加入 <pthread.h> 头文件之前完成,还要定义:
_POSIX_C_SOURCE=199506L |
Linux 线程不实现共享进程的互斥、条件和信号。该扩展的目的是允许不同的进程(有不同地址空间的进程)在共享内存中(SVR4 共享内存片段或映射的 mmap() 文件)分配的互斥、条件或信号之间进行同步。举例来说,如果在 pthread_mutex_attr_setpshared、pthread_rwlockattr_setpshared 和 pthread_condattr_setpshared 各自的标志参数(它们可以被传送到这些函数)中设置了 PTHREAD_PROCESS_SHARED,那么对这些函数的调用就会失败。
在从支持与这些函数共享进程的平台进行移植时,如果设置了 PTHREAD_PROCESS_SHARED 标志,您就必须重写应用程序,因为如果在上述函数中使用该标志的话,Linux 内核将返回 -1。对于互斥和简单的锁/解锁类型的信号和条件变量来说,pthread* 调用可能必须由用户编写的代码来替换。在可以得到合适的 Linux 内核支持前,您应该使用传统的交互进程通讯来对不同进程进行同步:System V 信号、消息队列、管道或套接字。
回页首
对接口和定义的支持
Linux 是使用接口和定义的层次结构来编写的。使用缺省的接口 _GNU_SOURCE,应用程序就能够利用所有的编译器功能了。这提供了最方便的编程环境。在一致的环境中还需要下面的宏:
回页首
其它提示和技巧
下面是前面部分中没有谈到的一些有用的信息。
make 的句法
总是应该在命令开始处输入一个制表符,而不是几个空格。不要在其它任何行之前使用制表符。如果您在应该使用制表符的地方使用了空格,或者作了相反的事,那么 makefile 就不会起作用。
使用 gcc fPIC 标志,而不是 -fpic 标志。
-fPIC 标志将生成与位置无关的代码,这些代码可以在任何地址被链接和加载。
gcc ++ 用来生成 makefile 信息的依赖性工具
-M 生成 make 依赖性信息。 -MM 类似于 -M,但忽略系统头文件。 -MD 类似于 -M,但将输出放在 a.d 文件中。 -MMD 类似于 -MD,但忽略系统头文件 -MG 将丢失的头文件当作生成的文件一样对待。
不使用 lD 来进行链接的选项
只要使用编译器前端来进行链接:
gcc -o progname foo.o bar.o main.o |
gcc 前端将插入所有正确的模块来进行适当的链接。添加 -v 参数还使您可以看到 gcc 操作的细节。
检查动态加载库
使用 ldd 来调用可执行文件;它会转储这些文件的库依赖性信息。
使用 LD_LIBRARY_PATH
Linux 系统在缺省情况下只搜索 /lib 和 /usr/lib。如果被链接到您的程序中共享的库安装在这些目录之外,那么就会找不到这些库,而系统会拒绝运行程序。
一种做法是在链接程序的时侯使用 -W1、-rpath 选项。另一种做法是在运行程序时设置 LD_LIBRARY_PATH 环境变量。
内存分配
总是应该使用 malloc() 来进行内存分配,而不是使用其它的系统调用来进行。
Linux 中丢失的 libsocket
对于 Solaris 来说,makefile 包含 -lsocket 库标志,然而 Linux 中看起来并没有出现 Solaris 中的 libsocket.*。没有必要链接任何特定的库,因为它们都是自动链接的。只要将 -lsocket 从 makefile 中取出来就可以了。
避免错误的引用符号
一个常见的错误就是使用撇号(')而不是回点号(`)来编译程序。您只要按“1”左面的那个键就可以输入(`)了。举例来说:
gcc -Wall -o intro intro.c `gtk-config -cflags -libs` |
静态链接某些库,而动态链接其它库
请使用下面的:
gcc -o some-program some-program.c -Xlinker -Bstatic -llib1 -lib2 -Xlinker -Bdynamic -llib3 |
硬编码的常量
避免对数据类型的大小进行硬编码;而是应该使用 sizeof()。请避免使用 malloc() 对常量进行硬编码。请使用 sizof(void *)来获取合适的指针大小。
数据对齐
多数处理器都需要每个内存中的数据项按 2 字节、4 字节或 8 字节边界进行数据对齐;否则处理器就必须执行多次读操作,从而导致性能降级,或者不得不引起硬件异常,让操作系统来处理,从而造成额外的性能降级。
为了解决这种不对齐带来的问题,GCC 编译器在每个没有对齐的项前面添加了填充字节(称为填充,padding),从而保证按照正确边界进行了对齐。虽然编译器添加的填充字节在应用程序代码中是不可见的,但它们的确存在,还能使内存中的数据结构布局与预期的不同。
移植 thr_suspend
请注意,thr_suspend() 一向非常危险,很容易引起争用情况。首先,我们无法控制目标线程真正在何处停止:即便有互斥存在,它也很可能在临界段的中间停住。由于这些原因,您最好不要使用互斥,而该用条件。
符合 ANSI
下面的示例是与不符合 ANSI 的代码,但示例可以在 Solaris Forte 编译器中运行:
const 引用
#include<iostream> class test { public: // Using a constant ref. to a variable whose value can be changed void test1 ( char & const newEvent) { cout << newEvent << endl;}; }; int main() { test t; char * const ptr="A"; t.test1(*ptr); return 0; } |
如果您用 g++ 编译,将得到下面的错误:
Error "discarding "const" applied to a reference" |
const char & 是对常量值的引用;char const & 引用是常量,但引用的值不是常量。
下面是纠正过的代码:
void test1 ( char const & newEvent) { cout << newEvent << endl;}; |
未定义的引用
下面的代码样本包含一个未定义的引用:
error" ANSI C++ forbids declaration 'ABC' with no type" error" (S) undefined reference to A::x" #include<iostream> class test { public: A(int q) { x=q;} ABC(int y); private: static int x; }; A::ABC(int z) { cout << z << endl; } int main() { A test(10); test.ABC(20); return 0; } |
下面是纠正过的代码:
#include<iostream> class test { public: A(int q) { x=q;} int ABC(int y); private: static int x; }; int A;;x=1; int A::ABC(int z) { cout << z << endl; } int main() { A test(10); test.ABC(20); return 0; } |
从 fstream 对象获取文件描述符
在 Solaris 上,您可以使用 fstream::rdbuf().fd() 方法来从 fstream 对象获取文件描述符。但是 Linux 上的 basic_filebuf 类就没有可用的 fd() 方法。这就是非标准的 C++ 代码。
对于标准的 C++ 代码来说,首先用 open (Posix) 打开文件,然后获取文件描述符 fd。然后用 fdopen(fd)(Posix)打开 FILE 对象,并将其命名为 myfile。接着用 mybuf(myfile)(一致的扩展)创建 filebuf 对象,并将其命名为 mybuf。之后,您就可以调用它的成员函数 str.rdbuf(mybuf)(标准 C++)将该缓冲与 ifstream 或 ofstream 对象 str 关联在一起。
回页首
目标硬件平台
Sparc
很多分发商都提供 Sun 的 Sparc 体系结构的 Linux。Sun 正在将 Solaris 作为 Linux 的软件伴侣和开放源代码的发展方向。Sun 有针对 Linux 的调整策略和将 Linux 应用程序迁移到 Solaris 的计划,但 Sun 并不支持或认可任何特定的 Linux 分发版。
pSeries
IBM 在 Linux PowerPC 社区中扮演了积极的角色。对最新一代处理器 POWER3 和 POWER4 的开发需要加入其它功能,这些功能都被添加到 Linux 2.4 内核中了。
使用 Linux 2.4 内核的 SuSE 版本 7.1 是第一个宣布支持 RS/6000 系统的,包括 B50、150、F50、170、260、270 和 p640 模型。
Yellow Dog Linux 也有支持 RS/6000 系统的 PowerPC 分发版。TurboLinux 和 Red Hat 也都发布了 PPC 版本。
据报告说,最新版本的 Linux 2.4 在 SMP 系统中的四到六处理器上伸缩性很好。该版本在 pSeries 和 RS/6000 系统(如 B50、150、260、270 和 p640)以及即将出现的 1 到 6 路的 p620 和 p660 系统上都能很好地工作。要了解 Linux for pSeries System Guide,请参阅:
ibm.com/servers/eserver/pseries/linux/guide.html
下面的软件中有 pSeries 上的 Linux 包:
任何应用程序都可以用 Linux 开发,然后部署到本机运行 Linux 的 pSeries 上,或者部署到使用 Linux 技术的 AIX Affinity 的 AIX 上。在开发和部署之后,基于 Linux 的应用程序是本机 AIX 应用程序,这意味着它们可以实现与其它任何 AIX 应用程序相同的可伸缩性和性能。然而,如果不为 pSeries 重新编译 Linux,您就不能在 pSeries 上运行 Linux。类似地,在 Linux 上为 pSeries 开发的应用程序也不能在 AIX 上以二进制的形式运行。pSeries 和 AIX 上的 Linux 有源代码兼容性。要了解关于 pSeries 上的 Linux 的更多信息,请参阅:
ibm.com/servers/eserver/pseries/linux/
zSeries
由于服务器结构的统一和服务器数量的减少,从大型服务器场到 zSeries 大型机的巩固使得客户的管理和能源花费更低了。IBM 在 zSeries 上对 Linux 提供了硬件上的支持,如 z900,还有支持 Linux for S/390 的 S/390 处理器,如 G5、G6 和 Multiprise 3000。目前,分发版可以从 SuSE、TurboLinux 和 and Red Hat 获得。
支持的应用程序和语言正在迅速增加,其数量如此众多,很难完整列出。它们包括 C/C++、perl、tcl、python、scheme、Regina(Rexx)和 Java JDK 1.3。数据库包括 MySQL、Postgres、DB2 UDB 和 DB2 Connect。中间件包括 Websphere 3.5、MQ Client、Tivoli 和 Apache。有一些分发版支持 Linux 内核 2.4、HiperSockets 支持、逻辑卷管理器(Logical Volume Manager)和日志记录文件系统 ReiserFS。您可以下载一个 64 位的内核进行 beta 测试。如果您需要一个详细描述可用的应用程序的 Web 页面,请参阅:
ibm.com/servers/eserver/zseries/solutions/s390da/linuxproduct.html
要了解特定于 S/390 和 zSeries 上的 Linux 的移植方面的其它信息,请参阅:
ibm.com/servers/esdd/articles/linux_s390/index.html
ibm.com/servers/eserver/zseries/os/linux
xSeries
几乎所有的 Linux 分发版(RedHat、Caldera、Mandrake、SuSE、Debian、TurboLinux 等等)在 Intel 平台上都在不断得到支持。然而,Linux 在 Intel 台式机方面还没有取得很大成功,但在服务器市场上却小有成果。
IBM e-Business Hosting 正在将 Red Hat Linux 6.2 加入到它的服务器硬件支持的一系列操作系统中。新提供的内容有:一项基本的服务、一组附加的增值服务,以及客户可以选择的价目表(从而能够启用、管理、监控和保护他们的 Linux 电子商务服务器)。服务器环境提供的如下所示:
要了解关于 xSeries 上的 Linux 的其它信息,请参阅:
ibm.com/servers/eserver/linux/xseries/