编程提示...
当您编写新的应用程序时,尽可能使应用程序遵循 POSIX 标准通常会提供最高的可移植性。如果一个应用程序“遵循”或遵守 POSIX 的规则,它几乎可以在所有已知的 Un*x 版本和克隆版本,以及 Windows NT、OS/2 2.x 和最新版本的 VMS 上无需更改地编译。如果必须编写一些系统相关的代码,则可以将其隔离并清楚地标记,以便下一个将应用程序移植到新平台的人可以更轻松地移植它。并且在不符合 POSIX 标准的平台上的程序员可能会知道他们平台的局限性,并且知道如何将您的应用程序移植到他们的平台。
不幸的是,当前版本的 POSIX 标准略微显得过时。为了制作足够强大的应用程序,您通常需要使用更强大的标准,该标准定义了 POSIX 中缺少的功能。一般来说,如果您的程序在 System V(通常称为 SYSV)或 BSD 系统下编译,那么它很可能在 Linux 下无需更改地编译。Linux 中默认启用 System V 兼容性,我将在本文后面展示如何启用 BSD 兼容性。总的来说,Linux 库与 GCC 相结合,提供了最容易移植到的平台之一。事实上,在 Linux 上编写应用程序的最大挑战是不使用 Linux 上提供的所有功能。这样做会使应用程序难以移植到其他平台,即使这样做会使应用程序最初更容易为 Linux 编写,因为许多这些丰富的功能在其他平台上不可用。有人说 Linux 是一个比从中移植更好的移植目标平台。
展望未来,一个名为“Spec 1170”的规范正在编写中,所有操作系统都必须遵循它才能被称为“UNIXtm”。它比以前大多数 un*x 规范更强大——足够强大,以至于常见的应用程序不必违反它来完成实际工作,并且将被广泛遵循,因为大多数 Un*x 供应商已经承诺支持它。Spec1170 由当前 XPG3 规范的开发者 X/Open 开发。Linux 最终将遵守 Spec 1170 的大部分(如果不是全部),但开发者在看到最终标准之前不会对此做出承诺。
在大多数情况下,使库函数与 SYSV 和 BSD 都兼容是可能的,但在某些情况下,标准只是冲突。当这种情况发生时,如果 POSIX 定义了行为,则选择 POSIX 行为。(如果 POSIX 没有定义它,则选择最合理的行为,或使用其他一些标准行为。)例如,信号的行为已经从一个 unix 版本更改为另一个版本。
早期版本的 un*x 中的信号是不可靠的。每当调用信号时,信号处理程序都会被卸载,因此大多数信号处理程序做的第一件事就是重新安装自己,从而导致如下代码:
void sigfoo_handler(int foo) { signal(SIGFOO, sigfoo_handler); /* Rest of signal handler */ }
但是,如果在信号处理程序计划运行之后,并且在调用 signal() 之前,收到另一个 SIGFOO 信号,则会发生信号的默认行为(可能只是被忽略,或者程序可能被终止,具体取决于信号),或者如果在信号处理程序返回之前收到新信号,则信号可能会完全丢失。后来,引入了可靠信号来解决这些问题,但 SYSV 和 BSD 采取了不同的途径。
BSD 引入了一个 sigaction() 函数来替换旧的 signal() 函数,然后使用 sigaction() 重新实现了 signal(),但更改了 signal() 的语义,以便使用 signal() 函数安装的信号处理程序保持安装状态。SYSV 保留了旧版本的 signal(),它会卸载自身,并引入了一堆不同的函数来处理可靠信号。POSIX 使用 sigaction()。Linux 遵循 POSIX,但如果选择了 BSD 环境,则可以提供 BSD 信号,如下所述。
终端处理也各不相同,BSD 终端处理与 SYSV 和 POSIX 显着不同,尽管 SYSV 和 POSIX 终端处理非常相似,以至于 Linux 可以通过内核中的相同机制轻松提供这两个接口。SYSV 方案称为 termio,POSIX 方案称为 termio。较旧的 BSD 方案称为 sgtty。移植 BSD 应用程序时,您可能会注意到编译器警告和错误,这些警告和错误引用 sgtty.h、TIOCGETP、TIOCSETP、TIOCFLUSH、RAW 和其他类似内容。
要编译专门为 BSD 平台编写的应用程序,只需使用 -I/usr/include/bsd 标志进行编译,并将应用程序与 -lbsd 标志链接。这使得 BSD 终端处理 ioctl() 可以工作,并使 signal() 安装“最近”的 BSD 代码期望的可靠信号。
有些代码只是需要任何标准,或至少任何足够流行的标准未定义的功能,因此是为每个操作系统单独编写的。这方面的一个例子是查找当前负载平均值或其他系统负载的指示。对于大多数 Un*x 实现,这要求进程具有超级用户权限,并且需要在内核内存中的特殊位置查找魔数。在内核内存(通常是 /dev/kmem)中查找的确切位置取决于源代码的来源——SYSV 代码在一个位置查找,而 BSD 代码在另一个位置查找。在其他操作系统上,使用了许多不同的方法。
在 Linux 中,任何进程都可以通过简单地读取文件 /proc/loadavg 来获取负载平均值——您可以通过在命令行中键入 cat /proc/loadavg 来自己查看它。幸运的是,这个和许多其他类似的特殊功能在所有 Linux 安装中都是标准的,并且非常易于使用。
不幸的是,您可能会遇到一些不太容易解决的问题。某些文件(例如 /etc/utmp)的格式在 Linux 下并非都得到很好的定义。也就是说,标准确实存在,但它们在不同的平台上似乎被不同地解释。这种情况有望随着时间的推移而改善,但目前看来,编写可以在所有 Linux 安装上工作的代码是很困难的。
目前,我建议您自己拥有一个相当标准的 Linux 发行版,并使您的软件在您自己的计算机上工作,然后要求遇到使用您的软件问题的用户与您联系。当他们这样做时,为每个单独的案例解决问题,可能使用 #ifdef,这样当您发布新版本的软件时,它将在更多的 Linux 安装上工作。它还将使您的软件在更多的非 Linux 平台上工作。我还建议获取 Linux 文档项目手册页,其中包括定义这些格式的手册页(如果您的 Linux 发行版没有附带这些手册页)。尽您所能遵循它们,向作者指出它们不清楚的地方和原因,并在必要时帮助重写它们以使其清晰。在您的帮助下,这些小问题可以及时解决。
在某些情况下,在其他操作系统上编写的源代码会做出在原始操作系统上为真的假设,但在 Linux 下这些假设不成立,因为某些功能已被修复。select() 就是一个例子。当首次引入 select() 时,手册页中指出 time 参数(通过引用传递给 select 的 & time)当前未被修改,但在未来可能会被修改以返回 select 中剩余的时间。程序员要么从未注意到,要么完全忽略了这个建议,现在包括 Linux 在内的几个操作系统确实修改了 time 参数,依赖于它不被修改的程序正在崩溃。
因此,定期(大约每月一次,我认为),有人在新闻组 comp.os.linux.misc 上发帖,说:“Linux select() 调用已损坏!” 即使 Linux 手册页和 Linux GCC FAQ 都仔细解释了这种情况。操作系统不断改进,但有一件事永远不会改变:有些人总是拒绝阅读文档......
有些代码盲目地假设定义了 SIGBUS 和 SIGEMT 等信号,即使不能保证(在 POSIX 下)这些信号将在平台上存在。这种类型的代码很容易修复——只需将引用有问题的信号的代码包装在 #ifdef SIGNAME 中,以便它仅包含在定义该信号的操作系统上。或者,您可以通过在 Makefile 中的 CFLAGS 行中添加 -DSIGFOO=SIGUNUSED 来将信号重新定义为 SIGUNUSED。
许多程序员忽略了不要对 FILE 结构做任何假设的警告,并且随意解引用 FILE 结构的成员。暂时忽略原始标准 I/O 应该包含宏或函数来访问这些成员的论点,这会在 Linux 下引起问题,因为 Linux 下的标准 I/O 基于 C++ iostream 库,因此具有完全不同的结构成员。例如,在将 GNU Emacs 19.xx 移植到 Linux 时,我发现 emacs 想知道 stdio 缓冲中还剩下多少字符,默认行为是查看 (FILE)->_ptr - (FILE)-<_base,这在 Linux 下不存在。幸运的是,这是通过 Emacs 源代码中定义的宏完成的,称为 PENDING\_OUTPUT\_COUNT,我能够在特定于 Linux 的 file linux.h 中将其定义为 (FILE)->\_pptr - (FILE)->\_pbase。
此外,似乎许多操作系统的标准库都定义了一个名为 sys_siglist 的符号,但没有声明它。由于它在 Linux 头文件中声明,您只需注释掉程序源代码中对 sys_siglist 的任何额外声明即可。这对于其他几个功能也是如此:sys_siglistis 只是一个例子。
我原以为会找到一个示例程序来移植作为本专栏的示例,但在一个周末,当我下载一个又一个程序时,Linux C 库给我留下了越来越深刻的印象,因为一个又一个程序通过简单的调整(例如更改 Makefile 以使用 gcc 而不是 cc 并更改可执行文件的路径)就编译成功了。我终于找到了一个可能给某些人带来移植问题的程序,一个名为 Freyja 的编辑器。
我将提供的 makefile.unx 复制到 Makefile,并编辑了 Makefile。我将 CFLAGS 更改为 -O2 以使用 GCC 的最高优化级别,并在命令行中键入 make。GCC 抱怨 TIOCGETP、TIOCSETP 和 RAW 未定义。这意味着 Freyja 是以 BSD 为目标编写的。似乎也没有任何我可以进行的 #define 来将 Freyja 的行为更改为 SYSV 或 POSIX。
因此,按照 GCC-FAQ 中的步骤,我在 CFLAGS 行中添加了 -I/usr/include/bsd,并在链接行(在 Freyja 中由于某种奇怪的原因称为 FXLINK;通常称为 LDFLAGS)中添加了 -lbsd,然后再次运行 make。
这就是“移植”这个面向 bsd 的程序所需的全部工作。我必须阅读文档才能找到我需要使用参数 “-kT -s29” 调用它来告诉它如何写入屏幕和从键盘读取,或者我需要将等效的更改编译到 Freyja 使用的资源文件中,但这非常简单。
Freyja 由 Craig A. Finseth 编写,可通过 ftp 从 mail.unet.umn.edu 获取,或者如果您没有 ftp 访问权限,则可通过美国邮政获取。引用 README
软盘:给作者发送空白软盘
3 1/2 英寸(1.44 MB),或
3 1/2 英寸(720 KB)
以及一个 SASE 或足够的邮票来支付回邮费用加上一美元左右(这样我可以买一个软盘邮件包装袋)。或者您可以只寄给我大约 5.00 美元的支票、邮票或任何东西,我将提供软盘和邮件包装袋。非美国人士可以寄给我四张 1.44 MB 3 1/2 英寸软盘来代替现金。(更多的钱总是好的,但请不要感到有任何义务。)
地址是:Craig Finseth1343 LafondSt. Paul, MN55104, USA
求助!
这是您贡献的机会!如果您在将通用 Un*x 应用程序移植到 Linux 时遇到困难,请发送电子邮件至 johnsonm@redhat.com 或发送蜗牛邮件至 Programming Tips, Linux Journal, P.O. Box 85867, Seattle, WA 98145-1867,并附上有关如何通过互联网获取该程序的说明,或附上软盘、150MB QIC 磁带或标准 DAT 上随附的应用程序副本,并详细解释您在尝试移植它时所做的尝试,我可能会亲自尝试一下,特别是如果它看起来会成为本专栏有价值的材料。