如何构建 LSB 应用程序
LSB 项目成立于 1997 年,旨在解决当时开始出现的应用程序兼容性问题。不同的发行版使用不同版本的上游软件,并使用不同的选项进行构建。结果是,在一个发行版上构建的应用程序可能无法在另一个发行版上运行。更糟糕的是,该应用程序通常无法在同一发行版的不同版本上工作。
最初,LSB 旨在为 GNU/Linux 系统的基础创建一个通用的参考实现。除了参考实现之外,还将制定书面规范。这个想法并没有得到许多发行版的良好反响,这些发行版在其自身的基础软件方面投入了大量资金,他们认为这是一种竞争优势。
在相关各方进一步讨论之后,LSB 项目为了在整个社区中达成共识,进行了根本性的重点转移。这种转变将书面规范置于实现之上,并将 LSB 定义为行为规范,而不是上游特性/版本对的列表。这种新的重点被实现为三管齐下的方法:书面规范,定义系统的行为;正式的测试套件,用于衡量实现与规范的符合程度;以及示例实现,提供规范的示例。
LSB 规范实际上由通用部分 gLSB 和特定于架构的部分 archLSB 组成。gLSB 包含所有架构通用的内容;我们努力在 gLSB 中尽可能多地定义内容。archLSB 包含每个处理器架构独有的内容,例如机器指令集和 C 库符号版本。
LSB 尽可能地建立在现有标准之上,包括单 UNIX 规范 (SUS)(从 POSIX 演变而来)、系统 V 接口定义 (SVID) 和系统 V 应用程序二进制接口 (ABI)。LSB 使用来自 ABI 的 ELF 定义和来自 SUS 的接口行为。它添加了哪些接口在哪个库中可用的正式列表,以及与它们关联的数据结构和常量。有关当前指定的库列表,请参阅“Linux 标准库”侧边栏。
Linux 标准库
截至 LSB 1.3,以下共享库在 LSB 中指定。所有其他库必须静态链接到应用程序中。
基础库:libc、libm、libpthread、libpam、libutil、libdl、libcrypt、libncurses 和 libz。
图形库:libX11、libXt、libXext、libSM、libICE 和 libGL。
随着 LSB 在未来版本中继续增长,此库列表也将随之增长。
除了 LSB 的 ABI 部分之外,该规范还指定了一组可用于与应用程序关联的脚本中的命令。它还强制要求应用程序遵守文件系统层次结构标准 (FHS)。
LSB 的另一个组成部分是打包格式。LSB 指定包文件格式为 RPM 文件格式的子集。LSB 没有指定发行版必须基于 RPM,但仅指定它必须具有某种正确处理 RPM 格式文件的方法。
最后要提到的一项是程序解释器的名称。程序解释器是应用程序启动时执行的第一项,它负责将程序的其余部分和共享库加载到进程地址空间中。传统上,/lib/ld-linux.so.2 一直被使用,但 LSB 在 IA32 上指定了 /lib/ld-lsb.so.1。通常,/lib/ld-arch-lsb.so.1 用于其他架构。这为操作系统提供了进程执行早期的挂钩,以防需要执行某些特殊操作来为应用程序提供正确的运行时环境。您可以将以下内容传递给 GCC 以更改程序解释器
-Wl,--dynamic-linker=/lib/ld-lsb.so.1
很久以前,人们就意识到,代码更改在开发过程的早期进行比后期进行更便宜且更容易。考虑到这一点,LSB 项目创建了一个构建环境来协助创建符合 LSB 的应用程序。此构建环境提供了一组干净的头文件、存根库和编译器包装器。
LSB 将其大部分定义存储在数据库中。除了手动编辑会很繁琐的规范部分之外,我们还能够生成一组干净的头文件和存根库,其中仅包含 LSB 指定的内容。以这种方式使用数据库有助于确保工具和规范在进行更改和添加时保持同步。您需要安装的软件包在“Linux 标准库软件包”侧边栏中描述。
Linux 标准库软件包
您可以从 Linux 标准库 (请参阅在线资源部分) 获取 LSB 开发环境;只需按照下载链接即可。您应该安装以下软件包
lsbdev-base:包含头文件和库。
lsbdev-cc:包含编译器包装器工具。
lsbdev-chroot:包含基于 chroot 的备用环境。
lsbdev-c++:包含静态 libstdc++,可用于移植一些用于 LSB 1.3 的 C++ 应用程序。
构建符合 LSB 的应用程序的第一步是使用 LSB 头文件编译代码。如果代码无法编译,则它可能正在使用 LSB 之外的某些内容。这不一定是一个障碍,但您需要特别注意它。LSB 头文件安装在 /opt/lsbdev-base/include 中。作为快速测试,传递-I/opt/lsbdev-base/include给 GCC,看看会发生什么。稍后描述的编译器包装器会为您执行此步骤和一些其他相关步骤。
编译代码后,下一步也是下一个测试是将代码链接在一起以形成最终应用程序。通常,此步骤如下所示
gcc -o app1 obj1.o obj2.o -lfoo
LSB 存根库可以在 /opt/lsbdev-base/lib 中找到,并且可以通过将 -L 选项传递给编译器来指定。这些存根库仅在链接时使用。通常,正常的系统库在运行时使用。同样,稍后描述的编译器包装器会处理这些细节。
链接应用程序后,使用ldd命令查看正在使用的共享库。此时,不应有任何共享库,除非 LSB 中指定的那些库 (并在“Linux 标准库”侧边栏中列出)。如果有,您需要采取额外的步骤使它们静态链接。通常,-Wl,-Bstatic和-Wl,-Bdynamic选项可用于指定某些库应静态链接。到目前为止,您可能已经看到了一种模式:编译器包装器会为您处理此事。
例如,应用程序 xpdf 通常如下所示
# ldd /usr/bin/xpdf libXpm.so.4 => /usr/X11R6/lib/libXpm.so.4 libt1.so.1 => /usr/lib/libt1.so.1 libfreetype.so.6 => /usr/lib/libfreetype.so.6 libSM.so.6 => /usr/X11R6/lib/libSM.so.6 libICE.so.6 => /usr/X11R6/lib/libICE.so.6 libX11.so.6 => /usr/X11R6/lib/libX11.so.6 libpaper.so.1 => /usr/lib/libpaper.so.1 libstdc++-libc6.2-2.so.3 => /usr/lib/libstdc++-libc6.2-2.so.3 libm.so.6 => /lib/libm.so.6 libc.so.6 => /lib/libc.so.6 /lib/ld-linux.so.2 => /lib/ld-linux.so.2
这是符合 LSB 的 xpdf
# ldd /opt/lsb-xpdf/bin/xpdf libSM.so.6 => /usr/X11R6/lib/libSM.so.6 libICE.so.6 => /usr/X11R6/lib/libICE.so.6 libX11.so.6 => /usr/X11R6/lib/libX11.so.6 libm.so.6 => /lib/libm.so.6 libgcc_s.so.1 => /lib/libgcc_s.so.1 libc.so.6 => /lib/libc.so.6 /lib/ld-lsb.so.1 => /lib/ld-lsb.so.1
最后,我们来了解编译器包装器 lsbcc 和 lsbc++。它们是相同的程序;它们只是以不同的名称调用,以指示 C 或 C++ 模式。总体思路是,您可以在任何可以使用 GCC 的地方使用 lsbcc,在任何可以使用 g++ 的地方使用 lsbc++。
此包装器工具解析传递给它的所有选项,并稍微重新排列它们。然后,它插入一些额外的选项,以使 LSB 提供的头文件和库优先于正常的系统库使用。此工具还识别非 LSB 库并强制它们静态链接。
由于 LSB 提供的头文件和库被插入到搜索路径的头部,因此通常可以安全地使用 LSB 中未包含的内容。但是,请确保它们不依赖于有意从 LSB 头文件和库中省略的内容,并且可以静态链接到应用程序中。这允许 lsbcc 在大多数情况下是透明的。
通过告诉 configure 脚本使用 lsbcc 而不是 GCC,它在 LSB 环境中进行各种测试,并使用可能需要的任何调整或限制来配置软件。有时,这会导致正在使用的功能的便携式替代品。但是,通常,整体功能与使用 GCC 时非常接近。作为练习,尝试以两种方式运行 configure 脚本并比较结果。告诉 configure 使用 lsbcc 的另一个好处是,它会在生成的 makefile 中自动将 CC 设置为 lsbcc,因此您不必记住传递它 (make CC=lsbcc) 每次运行make.
lsbcc 命令默认调用 GCC 并使用修改后的参数,但可以使用环境变量来告诉它要使用的编译器。这应该适用于任何其他与 GCC 命令行选项兼容的编译器。
构建应用程序后,使用 lsbappchk 程序测试程序,以查看其是否符合 LSB。此程序检查您的应用程序使用的共享库列表;它还会检查以确保您仅使用 LSB 允许的接口。这是一个示例运行
# /opt/lsbappchk/bin/lsbappchk /bin/ls /opt/lsbappchk/bin/lsbappchk for LSB Specification 1.3.3 Checking binary /bin/ls Incorrect program interpreter: /lib/ld-linux.so.2 Header[ 1] PT_INTERP Failed Found wrong interpreter in .interp section: /lib/ld-linux.so.2 instead of: /lib/ld-lsb.so.1 DT_NEEDED: librt.so.1 is used, but not part of the LSB Symbol clock_gettime used, but not part of LSB
LSB 不要求操作系统提供的实用程序本身符合 LSB。因此,实际上并不期望发行版自己的 /bin/ls 通过此测试。它只是作为一个方便的示例。
lsbappchk 的输出告诉我们 /bin/ls 不是符合 LSB 的应用程序。第一个问题是它没有与 LSB 定义的程序解释器 /lib/ld-lsb.so.1 链接。下一个问题是应用程序正在查找共享库 librt.so.1,该库未包含在 LSB 定义的库集中。最后,使用了函数 clock_gettime(),但未静态链接到应用程序 (它将在 librt.so.1 中找到)。
修复此类应用程序的通用方法是使用 lsbcc 重建应用程序,这将正确设置程序解释器并导致使用 librt.a 而不是 librt.so。有时,静态链接库可能会将新的非 LSB 符号引入应用程序,因此此过程可能需要重复几次。
在某些较大的应用程序或相关应用程序集中,可能需要创建仅供这些应用程序使用的共享库。只要共享库作为应用程序的一部分安装并且它位于应用程序私有数据区中,而不是在任何系统库位置中,这在 LSB 下是允许的。lsbappchk 的 -L 选项允许您告诉测试工具共享库的完整路径,为了测试是否符合 LSB 的目的,该共享库被认为是应用程序的一部分。这是一个符合 LSB 的 Apache Web 服务器构建示例,它使用三个私有共享库
# /opt/lsbappchk/bin/lsbappchk \ -L /opt/lsb-apache/lib/libaprutil.so.0 \ -L /opt/lsb-apache/lib/libexpat.so.0 \ -L /opt/lsb-apache/lib/libapr.so.0 \ /opt/lsb-apache/sbin/httpd /opt/lsbappchk/bin/lsbappchk for LSB Specification 1.3.3 Adding symbols for library /opt/lsb-apache/lib/libaprutil.so.0 Adding symbols for library /opt/lsb-apache/lib/libexpat.so.0 Adding symbols for library /opt/lsb-apache/lib/libapr.so.0 Checking binary /opt/lsb-apache/sbin/httpd
正如我之前提到的,LSB 指定包必须以 RPM 文件格式交付。这并不意味着必须使用 RPM 来构建或打包您的应用程序,尽管这可能是最实用的选择,具体取决于您是否已在使用它。其他选项包括以 Debian 格式创建包,然后使用 alien 将其转换为 RPM。或者,您可以使用其他工具来创建 RPM 文件格式。我们有一个名为 mkpkg 的工具的开始,用于创建 RPM 格式文件,但它可能需要一些东西放在它之上才能使其对除最顽固的黑客之外的任何人都有用。
在我们的应用程序电池中,我们目前构建应用程序并将其安装在临时根目录中,然后调用 RPM 来打包已安装的应用程序。这看起来可能有点笨拙,但它可以正常工作,而且在野外发现的所有不同版本的 RPM 中产生更一致的结果。
这是一个 xpaint 应用程序的示例 spec 文件
Summary: An X Window System paint program Summary: XPaint Name: lsb-xpaint Version: 2.6.2 Release: 3 Vendor: Free Standards Group License: MIT Group: Appbat/graphics Buildroot: /usr/src/appbat/pkgroot/lsb-xpaint AutoReqProv: no PreReq: lsb >= 1.3 %description LSB conforming version of xpaint. XPaint is an X Window System color image editing program and painting program. Xpaint is added to the LSB Application Battery primarily to demonstrate the use of X11 libraries. %pre %install %post %preun %postun %clean %files %attr ( - bin bin ) /opt/lsb-xpaint
构建和打包此应用程序和应用程序电池中其他应用程序的完整源代码可以在 LSB 项目 CVS 树中找到。
是的,它确实有效,但公平地说,我们仍然遇到角落案例和各种不总是遵循干净、可移植代码规则的应用程序。作为 LSB 验证的一部分,我们创建了一个应用程序电池,该电池由此处描述的工具构建而成。这组应用程序包括 Apache、Samba、Lynx、Python、xpdf 和 groff。我们尝试选择一组实际应用程序,以尽可能覆盖 LSB 接口集。
LSB 1.3 版不支持 C++,因此需要静态链接库的规则适用。我们正在向 LSB 2.0 添加对 C++ 的支持,以避免这种情况。我们提供了 lsbdev-c++ 包,其中包含使用 lsbcc 配置和构建的 libstdc++ 版本。此版本和 GCC 3.2 版似乎产生了良好的结果。我们尝试了编译器和其他不同版本的 C 和 C++ 库的其他组合,但遇到了各种问题,具体取决于应用程序的性质。
对于一般的 LSB,只要有共识认为需要添加其他库并且它们已达到一定的稳定性水平,我们将继续向规范中添加其他库。这应该有助于缩小发行版提供的应用程序和符合 LSB 的应用程序之间的构建方式的差距。
对于 LSB 开发环境,我们将继续使工具更好、更透明。开发环境正在积极维护中,感谢使用这些工具的人们的反馈。随着 LSB 2.0 中添加 C++,开发环境将能够放弃今天使用的 lsbdev-c++ 包,转而使用 C++ 存根库,该库将移入基本 LSB 开发包中。
目前,您可能需要在 rpmrc 或 rpmmacros 文件中设置多个选项,以使 RPM 生成符合 LSB 的包。我们希望我们可以为 rpmbuild 设计一种 LSB 模式,它可以自动处理所有这些。希望这将使构建符合 LSB 的现有包变得更加容易。
首先,感谢自由标准组织及其成员为 LSB 项目提供支持,使我们能够完成如此多的工作。其次,感谢 LSB 开发环境核心开发团队,包括 Chris Yeoh、Marvin Heffler,尤其感谢 Mats Wichmann 在该项目的更具实验性的阶段所展现的耐心和毅力。
本文的资源: /article/7459。
Stuart R. Anderson (anderson@freestandards.org) 犯了一个错误,在说“我知道如何解决这个问题”时被听到了,此后他一直担任 LSB 书面规范的首席开发人员。在不从事 LSB 工作时,Stuart 通过一次转换一家公司来不断启发南卡罗来纳州的人们了解开源理念。