内容


从 Solaris 迁移到 x86 上的 Linux 指南

将项目从 32/64 位 SPARC 上的 Solaris 迁移到 x86 上的 Linux

Comments

在各种风格的 UNIX 中,Solaris 被认为与 Linux 的风格最为接近。因此在开始将大型的基于 Unix 的应用程序移植到 Linux 上之前,首先要从 Solaris 中挑选出那些依赖于操作系统的代码。即便如此,在那些依赖于体系架构的领域、内存映射、线程或一些特殊的领域(例如系统管理和自然语言的支持),它们之间还是有差异的。

本文对这些差异进行了讨论,并加以对比,从而能够对您从运行在 32/64 位 SPARC 体系架构上的 Solaris 迁移到运行在 x86 体系架构上的 Linux 提供一些帮助。对于 Solaris 来说,这种讨论是基于版本 8 及更新的版本的。对于 Linux 来说,这种讨论着重于那些在基于 x86 处理器的服务器上可用的发行版本:SUSE LINUX Enterprise Server 9 和 Red Hat Enterprise Linux AS V3 或 V4。

本文内容包括:

  • 移植规划
  • 开发环境(编译器,make 工具,等等)
  • 依赖于体系架构或系统的区别

移植规划

下面 6 个步骤为从 SPARC 平台上的 Solaris 成功迁移到 x86 平台上的 Linux 提供了一个完整的路线图。如果您曾经将应用程序从一个操作系统移植到另外一个操作系统上,那么这些步骤可能就会听起来非常熟悉:

  1. 准备
  2. 环境和 makefile 的变化
  3. 编译器修正
  4. 测试和调试
  5. 性能优化
  6. 打包并分发

步骤 1. 准备

正确准备的关键是了解某些领域之间的差异,例如:

  • 系统调用
  • 文件系统的支持
  • 依赖于机器的代码
  • 线程
  • 内存映射
  • 系统调用
  • Endianness

在移植程序时,要确保有关的第三方包在目标平台上都是可用的。对于 32 位的应用程序来说,要考虑是否有必要迁移到 64 位的版本。还要确定在目标平台上使用哪种编译器。在基于 x86 的 Linux 平台上,可以使用 gcc 作为编译器。

步骤 2. 环境和 makefile 的变化

在这个步骤中,您将设置开发环境,这包括确定环境变量,修改 makefile,并对环境进行必要的修改。在这个步骤之后,您应该准备好开始编译自己的应用程序了。

在准备好进入下一个步骤之前,这个步骤可能会需要几次反复。

步骤 3. 编译

在这个步骤中,您将修正一些编译错误,链接错误,诸如此类。在能够得到一个干净的编译产品之前,这个步骤可能需要多次反复。

步骤 4. 测试和调试

在应用程序成功编译之后,要对其进行测试,看看是否存在运行时错误。在测试时,有些领域可能会涉及客户机/服务器的通信、数据交换格式、数据编码的转换(例如从单字节编码转换为多字节编码)以及数据的永久保存。

步骤 5. 性能优化

现在移植后的代码可以在目标平台上运行了。监视其性能,确保所移植的代码可以如我们所期望的一样工作;如果不能,就需要对性能进行优化。

有两个很好的性能分析工具:Performance Inspector 和 OProfile。Performance Inspector 提供了一组工具来判断 Linux 上的应用程序中的性能问题。从 2.6 版本的内核开始,OProfile 已经成为对代码进行优化分析的推荐工具。OProfile 在 RHEL4 中也可以使用。(有关这些工具的更多信息,请参阅 参考资料。)

步骤 6. 打包和分发

您要将所生成的代码分发给其他人吗?如果需要,就要确定打包的方法。

Linux 提供了几种方法对应用程序进行打包,例如 tarball、自行安装的 shell 脚本或 RPM。RPM 是 Linux 上广泛使用的包管理系统。Solaris使用 pkgadmin 作为自己的包管理器。

在 Solaris 中,pkgadmin 所使用的包规范模板文件与 RPM 所使用的规范文件不同 —— 将打包信息从模板文件转换成规范文件需要很多的努力。使用诸如 InstallShield for Multiplatforms(ISMP)之类的软件包可以在这两种平台上产生通用的打包软件,从而减少移植的努力。通过使用 ISMP,应用程序开发就可以使用一个跨平台的通用规范文件。

开发环境

现在让我们来看一下 Solaris 和 Linux 开发环境的一些差异,包括以下内容:

  • Makefile
  • 编译器和链接选项
  • 32 位到 64 位的移植考虑

GNU Make 和 Solaris Make 的比较

如果您在原有的平台上使用的是 Solaris 的 Make,那么要在 Linux 上使用 GNU Make,就需要对 makefile 进行修改。AIX 5L Porting Guide (请参阅 参考资料 中的链接)的第 6 章详细介绍了二者之间的区别。

编译器和链接选项

正如前面介绍的一样,对 x86 上的 Linux 可用的编译器是 GNU GCC。下表是 SUN Studio C/C++ 编译器所广泛使用的一些编译选项,以及 GNU GCC 中的等价选项。

表 1. Solaris 到 Linux 的等价编译器选项
SUN StudioGNU GCC说明
-#-v通知编译器在编译的过程中显示信息。
-###-###跟踪编译过程,而不会真正调用任何操作。
-Bstatic-static让链接编辑器只查找那些名为 libx.a 的文件。
-Bdynamic(缺省值)通知链接编辑器在给定 lx 选项时,首先查找名为 libx.so 的文件,然后查找名为 libx.a 的文件。
-G-shared产生一个共享对象,而不是一个动态链接的可执行程序。
-xmemalign-malign-natural,指定假定内存边界对齐的最大值,以及访问不对齐的数据时的行为。
-xO1, -xO2, -xO3, -xO4, -xO5-O, -O2, -O3在编译过程中选择对代码进行优化的级别。有点类似于 O2。
-KPIC-fPIC生成位置无关的代码,用于大型共享库。
-Kpic-fpic生成位置无关的代码,用于小型共享库。
-mt-pthread为多线程代码进行编译和链接。
-R dirlist-Wl, -rpath, dirlist将动态链接库的搜索路径编译到可执行文件中。
-xarch-mcpu, -march指定目标体系架构的指令集。

表 2 解释了不同风格的 Linux 上的不同级别的 C 编译器之间的区别。

表 2. 不同级别的 C 编译器之间的区别
操作系统内核级GCC 级Glibc 级Gnu binutils 级JDK 级
SLES92.63.3.32.3.32.15.901.4.2*
RHEL32.43.2.32.3.22.14.901.4.2
RHEL42.63.4.32.3.4-22.15.92.0.2-10.EL41.4.2*
Solaris 10Solaris 103.3.22.2.31.4.2

注意: * 在 64 位的 IBM JDK 1.4.2 for Linux on x86 中并不支持 LinuxThreads。Java 支持 NPTL 64 位线程库。

还有,如果您要在 SLES9 上编译 C++ 程序,并将其部署到 RHEL4 上,那么就需要使用 compat 库,它在包 compat-libstdc++-33-3.2.3-47.3.i386.rpm 中。之所以需要这个 compat 库,是因为在 SLES9 和 RHEL4 之间需要不同级别的 ABI 兼容性。SLES9 遵循 LSB 1.3 标准,而 RHEL4 遵循 LSB 2.0 标准。

32 位到 64 位移植的考虑

要将应用程序从 32 位环境移植到 64 位环境中,您有两种选择:

  • 让应用程序可以容许 64 位操作
  • 开拓目标平台上的 64 位操作能力

首先,将应用程序在一个 64 位的环境中使用,然后试图开拓目标平台的 64 位能力。

尽管 32 位的应用程序通常都可以在 64 位的目标平台上很好地运行,但是在 64 位环境中启用应用程序也是非常重要的,因为有些第三方的产品会以 64 位模式启动,而不提供 32 位版本的库对象。

在迁移到 64 位的环境中时,您需要决定是否继续支持该产品的 32 位版本。如果需要支持这两个版本,那么就需要适当的时间进行打包和测试。

开拓 64 位的能力应该是在实现容许 64 位操作之后的下一个步骤。在开始这种尝试之前,应该仔细衡量一下开拓 64 位能力的优点。

应用程序从 32 位到 64 位的迁移这个主题在很多出版物中已经进行了介绍(例如 参考资料 中列出的 AIX 5L Porting Guide 的第 3 章)。

体系架构和系统特有的区别

现在,让我们来看一下 Solaris 和 x86 上的 Linux 之间一些特定于体系架构和系统的区别,包括:基本的数据类型,内核参数的设置,体系架构特有的区别,endianness,系统调用,信号,数据类型,以及线程库。

参考资料 提供了在进行大型的移植尝试时,需要考虑的一个可行的检查清单。

基本的数据类型和对齐方式

在一个系统中有两种不同类型的数据类型:基本数据类型(basic data type)和派生数据类型(derived data types)。

基本数据类型 是由 C 和 C++ 语言规范所定义的所有数据类型。表 3 对 x86 上的 Linux 和 SPARC 上的 Solaris 这两者的基本数据类型进行了比较。

表 3. 比较 Linux 和 Solaris 的基本数据类型
Linux(x86)Solaris(SPARC)
基本类型ILP32
(大小)
(单位为字节)
LP64
(用于基于 IA64
的系统)
(大小)
(单位为字节)
ILP32
(大小)
(单位为字节)
LP64
(大小)
(单位为字节)
char1111
short2222
Int4444
float4444
long4848
pointer4848
long long8888
double8888
long double12161616

在两个平台之间或 32 位与 64 位模式之间移植应用程序时,您需要考虑不同环境中对齐设置之间的区别,从而避免出现性能降低和数据崩溃的危险。

表 4 介绍了 x86 上的 Linux 中以字节为单位的对齐值。

表 4. x86 上的 Linux 中的对齐值(以字节为单位)
数据类型Linux IA-32
(ILP32)
Linux IA-64
(LP64)
Bool11
Char11
wchar_t44
int44
Short22
long48
long long88
Float44
Double48
long double4*16

注意: 对齐取决于所使用的编译器开关。这些开关控制着“long double”类型的大小。i386 应用程序的二进制接口规定这个大小是 96 位,因此 -m96bit-long-double 在 32 位模式中是默认值。现代的体系架构(Pentium 及更新的体系架构)更喜欢将“long double”按照 8 字节或 16 字节进行对齐。在符合 ABI 规范的数组或结果中,这是不可能的。因此指定一个 -m128bit-long-double 会将“long double” 按照 16 字节边界进行对齐,它会为“long double”填充 32 位的 0。

系统派生的数据类型

派生数据类型是已有的基本类型或其他派生类型的一个派生物或结构。系统派生数据类型 可以根据所采用的数据模型(32 位或 64 位)和硬件平台而具有不同的字节大小。

表 5 给出了 Linux 上与 Solaris 上不同的一些派生数据类型。

表 5. Solaris 和 Linux 中的派生数据类型
OSgid_tmode_tpid_tuid_twint_t
Solaris ILP32 llongunsigned longlonglonglong
Solaris LP64intunsigned intintintint
Linux ILP32unsigned intunsigned intintunsigned intunsigned int
Linux LP64unsigned intunsigned intintunsigned intunsigned int

机器特有的代码的例子

在下面这些情况中需要机器特有的代码:

  • 获得堆栈指针
  • 原子锁

在 Linux-x86 平台上的堆栈指针可以实现为:

清单 1. 在 Linux-x86 上获取堆栈指针
int get_stack(void **StackPtr)
{
  *StackPtr = 0;
  __asm__ __volatile__ ("movl %%esp, %0": "=m" (StackPtr) );
 return(0);
}

在 Solaris,下面这段示例代码让您可以获取堆栈指针:

清单 2. 在 Solaris 上获取堆栈指针
        .section       ".text"
        .align  8
        .global my_stack
        .type   my_stack,2
my_stack:
        ! Save stack pointer through 1st parameter
        st      %sp,[%o0]
        ! Compute size of frame in return result reg
        retl
        sub     %fp,%sp,%o0
        .size  my_stack,(.-my_stack)

在 Linux x86 上,您可以使用一个 compare 和 swap 操作来实现原子锁。例如,下面是 Linux x86 上使用 compare 和 swap 操作的一个简单实现:

清单 3. Linux x86 上的 Compare 和 swap 操作
bool_t My_CompareAndSwap(IN int *ptr, IN int old, IN int new)
{
        unsigned char ret;
        /* Note that sete sets a 'byte' not the word */
        __asm__ __volatile__ (
                "  lock\n"
                "  cmpxchgl %2,%1\n"
                "  sete %0\n"
                : "=q" (ret), "=m" (*ptr)
                : "r" (new), "m" (*ptr), "a" (old)
                : "memory");
        return ret;
}

在 Solaris SPARC 上,加锁的原子操作可以如下实现:

清单 4. Solaris 上的原子锁
        .section       ".text"
        .align  8
        .global        My_Ldstub
        .type          My_Ldstub,2
My_Ldstub:
        ldstub         [%o0],%o0         ! Atomic load + set
        sll            %o0,24,%o0        ! Zero fill ...
        retl                             ! ... result register
        srl            %o0,24,%o0        ! ... and retrn
        .size          My_Ldstub,(.-My_Ldstub)

字节顺序(endianness)

由于 SPARC 采用的是 big-endian,而 x86 采用的是 little-endian,因此您需要考虑 endianness 的问题。

Endianness 的问题在以下这些情况中都变得非常重要:移植客户机要在异构网络环境中进行通信的应用程序,将数据永久地保存到磁盘上,产品跟踪(在 SPARC 平台上所生成的跟踪信息必须能够在 x86 平台上正确地格式化),以及其他相关领域。IA-64 Linux 内核默认使用 little-endian,但是也可以使用 big-endian 的字节顺序。

系统调用

Solaris 系统调用使用了一种在 Linux 上是不可用的不同的命名和签名机制,这在“从 Solaris 向 Linux on POWER 迁移指南” 中已经介绍过了。以下这些系统调用现在在 RHEL4 中可以使用:

  • Waitid
  • Putmsg
  • putpmsg
  • Getmsg
  • getpmsg

Curses 库

Linux 用于 xurses 库的那些库函数比 Solaris 更加类似于 AIX。例如,诸如 addwch 之类的函数在 Linux 中不受支持。有些函数,例如 mvchgat,在 Solaris 中不可用。当您将与 curses 有关的代码从 Solaris 移植到 Linux 上时,很多代码都需要重写。

Terminal IO

Solaris 中的 termio 结构与 Linux 中的这个结构是不同的。Linux 中的 termio 结构多了几个域,您可以使用它们来设置输入和输出速度。

Linux 中 termio 结构的定义位于 /usr/include/bits/termios.h 文件中,而 Solaris 中 termio 的定义则位于 /usr/include/sys/termios.h 文件中。

IOCTL

执行 ioctl 可以使用的选项在 Solaris 和 Linux 中是不同的。例如,要获得资源的利用情况,您可以向 ioctl 系统调用传递 PIOCUSAGE:

Return_code = ioctl("/proc/<pid>", PIOCUSAGE, &buff) ;

在 Linux 中,您必须直接从 /proc/<pid> 目录中读取相关的文件来获得相关信息,在 Linux 上不能使用 PIOCUSAGE 选项。在这两种情况中,pid 是正在执行命令的当前进程的进程 id。

另外一个例子是当您希望获取该进程的堆信息时。在 Solaris 中,要获取堆信息,您可以使用下面的方法:

Return_code = Ioctl("/proc/<pid>",PIOCPSINFO,&psinfo)

在这段代码中,psinfo 是一个 struct prpsinfo 结构;Prpsinfo.pr_size 就给出了该进程的堆大小。

在 Linux 中,正在使用的页数可以从 /proc/<pid>/mem 和 /proc/<pid>/stat 中获得。页面大小可以使用系统调用 getpagesize 获得。这两个值(页数和页面大小)的乘积就是堆的当前大小。

Getopt

在 Linux 中,只有在设置了 POSIXLY_CORRECT 环境变量之后,getopt 调用才遵循 POSIX 标准。

如果设置了环境变量 POSIXLY_CORRECT,那么对参数的分析一碰到非选项参数(以“-”开始的参数)就立即停止。 其余的参数全部被解释为非选项参数。

调用 shell 脚本之间的区别

如果 shell 脚本使用了 su 命令,就会派生一个子 shell。这种行为与 Solaris 不同,在 Solaris 中, su 命令不会派生一个新 shell。

如果您在 Linux 上执行命令 su - root,那么 ps 命令的输出就会如下所示:

30931 pts/4    00:00:00 su
31004 pts/4    00:00:00 ksh

在这段代码中,30931 是进程 31004 的祖先进程。您可能要修改一些受到这种关系影响的脚本。

重启动时的设备处理

从 RHEL4 开始,Linux 就采用了热插拔子系统 udev 的概念。它提供了可配置的动态设备命名的支持。设备配置是在每次重新启动时动态构建的。

例如,如果您有一个新目录 /dev/dsk,那么系统可能并不知道它的存在,这个目录在重启之后可能就会丢失。为了避免 /dev/dsk 目录丢失的问题,可以建立一个 /etc/udev/devices/dsk 目录,这样系统在重新启动时就可以维护这种信息,因此即使系统重新启动之后,/dev/dsk 也依然会存在。

内核参数

在 Solaris 中,内核参数可以在 /etc/system 文件中进行设置。在 Linux 中,内核参数可以使用 sysctl 系统调用进行修改。可以修改的参数在 /proc/sys/kernel 中都可以看到,procfs 的支持对于 sysctl 正常工作来说是必需的。

信号

信号之间的区别在“Technical guide for porting applications from Solaris to Linux”中已经详细介绍过了。其他值得注意的一些区别是 Solaris 有 38 个信号,而 Linux 使用了 31 个信号,而且 sigaction 结构也不相同。例如,在 Linux 中,sigaction 接口如下所示:

清单 5. Linux 中的 sigaction 结构
struct sigaction
  {
    /* Signal handler.  */
#ifdef __USE_POSIX199309
    union
      {
        /* Used if SA_SIGINFO is not set.  */
        __sighandler_t sa_handler;
        /* Used if SA_SIGINFO is set.  */
        void (*sa_sigaction) (int, siginfo_t *, void *);
      }
    __sigaction_handler;
# define sa_handler     __sigaction_handler.sa_handler
# define sa_sigaction   __sigaction_handler.sa_sigaction
#else
    __sighandler_t sa_handler;
#endif
    /* Additional set of signals to be blocked.  */
    __sigset_t sa_mask;
    /* Special flags.  */
    int sa_flags;
    /* Restore handler.  */
    void (*sa_restorer) (void);
  };

在 Solaris 中,sigaction 结构如下所示:

清单 6. Solaris 中的 sigaction 结构
struct sigaction {
        int sa_flags;
        union {
#ifdef  __cplusplus
                void (*_handler)(int);
#else
                void (*_handler)();
#endif
#if defined(__EXTENSIONS__) || ((__STDC__ - 0 == 0) && \
        !defined(_POSIX_C_SOURCE) && !defined(_XOPEN_SOURCE)) || \
        (_POSIX_C_SOURCE > 2) || defined(_XPG4_2)
                void (*_sigaction)(int, siginfo_t *, void *);
#endif
        }       _funcptr;
        sigset_t sa_mask;
#ifndef _LP64
        int sa_resv[2];
#endif
};

由于所支持的信号个数不同,siginfo_t 结构也不相同。对于 Linux,这可以在 /usr/include/bits/signal.h 文件中找到;对于 Solaris,这可以在 /usr/include/sys/siginfo.h 文件中找到。

线程支持和 IPC

有关 Linux on POWER 与 Solaris 之间进行移植的出版物已经介绍了 Solaris 线程与 Linux 的 POSIX 线程(由 NPTL 库提供)之间的区别。

我的经验是,如果应用程序早已在 Solaris 上就使用了 POSIX 线程,那么它们移植到使用基于 NPTL 线程的 Linux 上就非常简单。在 Linux 上还可以利用一些新特性,例如进程共享互斥锁,因此 Solaris 中使用 pthread 互斥锁和条件变量的那些有关 IPC 机制的代码在 Linux 中都可以使用。

自然语言的支持

在 Solaris 中对于自然语言的支持的代码也与 Linux 中可能会有所不同,或者它们只是名字有所不同而已。在 Solaris 中大部分的 locales 和代码页在 Linux 中都有对应的内容。

结束语

从 Solaris 到 x86 上的 Linux 的移植工作在大部分情况中只是一个重新编译的过程,或者对编译器/链接器开关稍微进行一些修改。有时可能需要对某些平台特有的内容进行一些修改,例如加锁、内存映射、线程,等等。您应该了解一下这些差异,并对移植好好进行一下规划,从而减少移植所需要的时间。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=84142
ArticleTitle=从 Solaris 迁移到 x86 上的 Linux 指南
publish-date=05302005