本章介绍如何使用和创建子程序库。对静态和动态库均进行了讨论。
软件库通常是先前已编译并组织成单个二进制库文件的子程序集。集中的每个成员称为库元素或模块。链接程序搜索库文件,在生成可执行二进制程序时加载用户程序所引用的目标模块。有关详细信息,请参见 ld(1) 和 Solaris《链接程序和库指南》。
软件库有两种基本类型:
静态库。该库中的模块在执行之前即被绑定到执行文件中。静态库通常以 libname.a 命名。.a 后缀指的是归档。
动态库。该库中的模块可在运行时绑定到可执行程序中。动态库通常以 libname.so 命名。.so 后缀指的是共享对象。
既有静态 (.a) 版本又有动态 (.so) 版本的典型系统库有:
Fortran 95 库:libfsu、libfui、libfai、libfai2、libfsumai、libfprodai、libfminlai、libfmaxlai、libminvai, libmaxvai、libifai、libf77compat
C 库:libc
使用库有两个优点:
对于程序调用的库例程,不需要有源代码。
只加载所需的模块。
库文件为程序共享常用子例程提供了一条简单途径。只需在链接程序时给出库名便可,那些解析程序中引用的库模块将被链接并合并到可执行文件中。
通过 LD_OPTIONS 环境变量向链接程序传递其他选项,可以获得库用法和库加载方面的摘要信息。在生成目标二进制文件时,编译器会用这些选项(以及它要求的其他选项)调用链接程序。
始终建议使用编译器调用链接程序,而不是直接调用链接程序,因为许多编译器选项要求特定的链接程序选项或库引用,缺少这些,链接时会产生无法预料的结果。 示例:使用 LD_OPTIONS 创建加载映射
demo% setenv LD_OPTIONS ’–m -Dfiles’ demo% f95 -o myprog myprog.f |
某些链接程序选项具有等价的编译器命令行选项,它们可以直接在 f95 命令中出现。它们包括 -Bx、-dx、-G、-hname、-Rpath 和 -ztext。有关详细信息,请参见 f95(1) 手册页或《Fortran 用户指南》。
在 Solaris《链接程序和库指南》中,可以找到链接程序选项和环境变量的更多详细示例和解释。
链接程序 -m 选项会生成显示库链接信息的加载映射。可执行二进制程序生成期间链接的例程会与其源自的库一起被列出。
demo% setenv LD_OPTIONS ’-m’ demo% f95 any.f any.f: MAIN: LINK EDITOR MEMORY MAP output input virtual section section address size .interp 100d4 11 .interp 100d4 11 (null) .hash 100e8 2e8 .hash 100e8 2e8 (null) .dynsym 103d0 650 .dynsym 103d0 650 (null) .dynstr 10a20 366 .dynstr 10a20 366 (null) .text 10c90 1e70 .text 10c90 00 /opt/SUNWspro/lib/crti.o .text 10c90 f4 /opt/SUNWspro/lib/crt1.o .text 10d84 00 /opt/SUNWspro/lib/values-xi.o .text 10d88 d20 sparse.o ... |
其他链接程序调试功能可通过链接程序的 -Dkeyword 选项获得。使用 -Dhelp 选项可以显示完整的列表。
示例:使用 -Dhelp 选项列出链接程序调试辅助选项:
demo% ld -Dhelp … debug: args display input argument processing debug: bindings display symbol binding; debug: detail provide more information debug: entry display entrance criteria descriptors … demo% |
例如,-Dfiles 链接程序选项会列出链接过程中引用的所有文件和库:
demo% setenv LD_OPTIONS ’-Dfiles’ demo% f95 direct.f direct.f: MAIN direct: debug: file=/opt/SUNWspro/lib/crti.o [ ET_REL ] debug: file=/opt/SUNWspro/lib/crt1.o [ ET_REL ] debug: file=/opt/SUNWspro/lib/values–xi.o [ ET_REL ] debug: file=direct.o [ ET_REL ] debug: file=/opt/SUNWspro/lib/libM77.a [ archive ] debug: file=/opt/SUNWspro/lib/libF77.so [ ET_DYN ] debug: file=/opt/SUNWspro/lib/libsunmath.a [ archive ] … |
有关这些链接程序选项的更为详细的信息,请参见《链接程序和库指南》。
每当分步完成编译和链接时,确保编译和链接选项的一致选择至关重要。在使用选项编译程序的任何部分时,必须使用相同的选项进行链接。另外,许多选项要求使用该选项编译所有源文件,包括链接步骤。
《Fortran 用户指南》中的选项说明具体指出了此类选项。
示例:用 -fast 编译 sbr.f,编译 C 例程,然后分步进行链接:
demo% f95 -c -fast sbr.f demo% cc -c -fast simm.c demo% f95 -fast sbr.o simm.o link step; passes -fast to the linker |
链接程序按某一规定顺序在若干位置搜索库。这些位置中有一些是标准路径,有一些则取决于编译器选项 -Rpath、-llibrary 和 -Ldir 以及环境变量 LD_LIBRARY_PATH。
链接程序所用的标准库搜索路径由安装路径确定,对于静态和动态加载,它们会有所不同。标准安装将 Sun Studio 编译器软件置于 /opt/SUNWspro/ 下。
生成可执行文件时,静态链接程序按指定顺序在以下路径(在其他路径中)中搜索 库:
/opt/SUNWspro/lib |
Sun Studio 共享库 |
/usr/ccs/lib/ |
SVr4 软件的标准位置 |
/usr/lib |
UNIX 软件的标准位置 |
这些是链接程序所用的缺省路径。
用户使用 -Rpath 指定的路径
/opt/SUNWspro/lib/
/usr/lib 标准 UNIX 缺省值
这些搜索路径被内置于可执行文件中。
使用 LD_LIBRARY_PATH 环境变量指定链接程序应在哪些目录路径中搜索用 -llibrary 选项指定的库。
可以指定多个目录,其间用冒号分隔。通常,LD_LIBRARY_PATH 变量包含两个用冒号分隔的目录列表,列表间用分号隔开:
dirlist1;dirlist2
首先搜索 dirlist1 中的目录,接着是命令行上用任何显式 -Ldir 指定的目录,再接着是 dirlist2 以及标准目录。
也就是说,如果使用多个 -L 调用编译器,如下所示:
f95 ... -Lpath1 ... -Lpathn ...
则搜索顺序是:
dirlist1 path1 ... pathn dirlist2 standard_paths
当 LD_LIBRARY_PATH 变量只包含一个用冒号分隔的目录列表时,它会被解释为 dirlist2。
在 Solaris 操作环境中,当搜索 64 位依赖性时,可以用相似的环境变量 LD_LIBRARY_PATH_64 来替代 LD_LIBRARY_PATH。有关详细信息,请参见 Solaris《链接程序和库指南》以及 ld(1) 手册页。
在 32 位 SPARC 处理器上,会忽略 LD_LIBRARY_PATH_64。
如果只定义了 LD_LIBRARY_PATH,它将被同时用于 32 位和 64 位链接。
如果同时定义了 LD_LIBRARY_PATH 和 LD_LIBRARY_PATH_64,则 32 位链接将用 LD_LIBRARY_PATH 来完成,而用 LD_LIBRARY_PATH_64 进行 64 位链接。
强烈建议不要对生产软件使用 LD_LIBRARY_PATH 环境变量。尽管它作为一种影响运行时链接程序搜索路径的临时机制很有用,但是任何可以引用该环境变量的动态可执行文件的搜索路径都会被改变。您可能会看到了意想不到的结果或性能降低。
使用 -llibrary 编译器选项对链接程序在解析外部引用时要搜索的其他库命名。例如,用选项 -lmylib 将库 libmylib.so 或 libmylib.a 添加到搜索列表中。
链接程序会在标准目录路径中查找其他的 libmylib 库。-L 选项(和 LD_LIBRARY_PATH 环境变量)会创建一个路径列表,告知链接程序到哪里查找位于标准路径以外的库。
假如 libmylib.a 位于 /home/proj/libs 目录中,则选项 –L/home/proj/libs 会告知链接程序在生成可执行文件时到哪里查找:
demo% f95 -o pgram part1.o part2.o -L/home/proj/libs -lmylib |
对于任何未解析的特殊引用,只对库进行一次搜索,并且只搜索在搜索时未定义的符号。如果命令行上列出了多个库,则会按其在命令行上出现的顺序来搜索这些库。-llibrary 选项放置在以下位置:
将 -llibrary 选项放置在任一 .f、.for、.F、.f95 或 .o 文件之后。
如果调用了 libx 中的函数,并且这些函数引用了 liby 中的函数,则将 -lx 置于 -ly 之前。
-Ldir 选项会将 dir 目录路径添加到库搜索列表中。链接程序首先在 -L 选项指定的任何目录中搜索库,然后在标准目录中进行搜索。只有将其放在它所应用的 –llibrary 选项之前,该选项才有用。
对于动态库,库搜索路径和加载顺序的更改与静态情况不同。实际链接发生在运行时而不是生成时。
生成可执行文件时,链接程序会在可执行文件本身中记录共享库的路径。这些搜索路径可以用 -Rpath 选项指定。这一点与 -Ldir 选项相反,该选项在生成时指示到哪里查找 -llibrary 选项所指定的库,但不会将该路径记录到二进制可执行文件中。
使用 dump 命令可以查看创建可执行文件时内置的目录路径。
示例:列出内置于 a.out 之中的目录路径:
demo% f95 program.f -R/home/proj/libs -L/home/proj/libs -lmylib demo% dump -Lv a.out | grep RPATH [5] RPATH /home/proj/libs:/opt/SUNWspro/lib |
在运行时,链接程序会确定到哪里查找可执行文件所需的动态库:
运行时 LD_LIBRARY_PATH 的值
生成可执行文件时已由 -R 指定的路径
如前所述,使用 LD_LIBRARY_PATH 会带来意想不到的副作用,因而不建议这样做。
当动态链接程序找不到所需库的位置时,它会发出以下错误消息:
ld.so: prog: fatal: libmylib.so: can’t open file: |
此消息表明这些库不在其应在的位置。您也许在生成可执行文件时指定了共享库的路径,但这些库随后已被移动。例如,您可能先用 /my/libs/ 中您自己的动态库生成了a.out,而后来又将这些库移到了另一目录。
使用 ldd 确定可执行文件期望在哪儿找到这些库:
demo% ldd a.out libfui.so.1 => /opt/SUNWspro/lib/libfui.so.1 libfai.so.1 => /opt/SUNWspro/lib/libfai.so.1 libfai2.so.1 => /opt/SUNWspro/lib/libfai2.so.1 libfsumai.so.1 => /opt/SUNWspro/lib/libfsumai.so.1 libfprodai.so.1 => /opt/SUNWspro/lib/libfprodai.so.1 libfminlai.so.1 => /opt/SUNWspro/lib/libfminlai.so.1 libfmaxlai.so.1 => /opt/SUNWspro/lib/libfmaxlai.so.1 libfminvai.so.1 => /opt/SUNWspro/lib/libfminvai.so.1 libfmaxvai.so.1 => /opt/SUNWspro/lib/libfmaxvai.so.1 libfsu.so.1 => /opt/SUNWspro/lib/libfsu.so.1 libsunmath.so.1 => /opt/SUNWspro/lib/libsunmath.so.1 libm.so.1 => /usr/lib/libm.so.1 libc.so.1 => /usr/lib/libc.so.1 libdl.so.1 => /usr/lib/libdl.so.1 /usr/platform/SUNW,Ultra-5_10/lib/libc_psr.so.1 |
如果可能的话,将这些库移动或复制到正确的目录中,或者在链接程序搜索的目录中建立到该目录的软链接(使用 ln -s)。或者,可能是没有正确设置 LD_LIBRARY_PATH。检查 LD_LIBRARY_PATH 是否包含运行时所需库的路径。
静态库文件是使用ar(1) 实用程序由预编译的目标文件(.o 文件)生成的。
链接程序从库中提取在当前链接的程序内引用了其入口点的任何元素,如子程序、入口名或 COMMON 子程序中已初始化的 BLOCKDATA 块。这些提取出来的元素(例程)会被永久绑定到链接程序生成的 a.out 可执行文件中。
与动态情况相比,关于静态库和链接,有三个主要问题需要谨记:
静态库更加自主,但适应能力较差。
如果以静态方式绑定 a.out 可执行文件,它所需的库例程会变成可执行二进制文件的一部分。但是,如果需要更新绑定到 a.out 可执行文件中的静态库例程,则必须重新链接并重新生成整个 a.out 文件以便利用已更新的库。对于动态库,库并不是 a.out 文件的一部分,并且链接是在运行时完成的。要利用已更新的动态库,只需将新库安装在系统中即可。
静态库中的“元素”是单独的编译单元,即 .o 文件。
由于单个编译单元(源文件)可以包含多个子程序,因此这些例程在一起编译时会变成静态库中的单一模块。这就意味着会将编译单元中的所有例程一起装入 a.out 可执行文件中,即使实际只调用了那些子程序中的一个。通过优化库例程分发到可编译源文件中的方式,可以改善这种情况。(尽管如此,只有程序实际引用的那些库模块才会被装入可执行文件。)
链接静态库时,顺序很重要。
链接程序按输入文件在命令行上出现的顺序(从左至右)对其进行处理。当链接程序决定是否从库中加载某一元素时,其决定取决于它已经处理的库元素。该顺序不仅依赖于元素在库文件中的出现顺序,而且还依赖于编译命令行中指定库的顺序。
示例:如果 Fortran 程序在两个文件(main.f 和 crunch.f)中,并且只有后者访问某个库,则在 crunch.f 或 crunch.o 之前引用该库是错误的:
demo% f95 main.f -lmylibrary crunch.f -o myprog |
(不正确)
demo% f95 main.f crunch.f -lmylibrary -o myprog |
(正确)
假设您可以将程序中的所有例程分布在一组源文件中,同时假定这些文件全部包含在子目录 test_lib/ 中。
进一步假定这些文件是以这样一种方式组织的:它们每一个都只包含一个用户程序将会调用的主要子程序,同时还包含该子程序可能会调用的任何“帮助程序”例程,但这些例程不会从库中的任何其他例程中调用。另外,从一个以上库例程中调用的任何帮助程序例程均被集合到单个源文件中。这样就给出了一个组织得非常合理的源文件及目标文件集。
假定每个源文件的名称均取自文件中第一个例程的名称,在多数情况下,该例程是库中的主要文件之一:
demo% cd test_lib demo% ls total 14 2 dropx.f 2 evalx.f 2 markx.f 2 delte.f 2 etc.f 2 linkz.f 2 point.f |
更低级的“帮助程序”例程被集中到文件 etc.f 中。其他文件可以包含一个或多个子程序。
首先,使用 -c 选项编译每一个库源文件,生成相应的可重定位 .o 文件:
demo% f95 -c *.f demo% ls total 42 2 dropx.f 4 etc.o 2 linkz.f 4 markx.o 2 delte.f 4 dropx.o 2 evalx.f 4 linkz.o 2 point.f 4 delte.o 2 etc.f 4 evalx.o 2 markx.f 4 point.o demo% |
现在,使用 ar 创建静态库 testlib.a:
demo% ar cr testlib.a *.o |
要使用该库,可在编译命令中包括此库文件,或者使用 -l 和 -L 编译选项。以下示例直接使用 .a 文件:
demo% cat trylib.f C program to test testlib routines x=21.998 call evalx(x) call point(x) print*, ’value ’,x end demo% f95 -o trylib trylib.f test_lib/testlib.a demo% |
注意,主程序只调用库中的两个例程。您可以验证并未将库中未调用的例程装入可执行文件,方法是查找用 nm 显示的可执行文件的名称列表中是否有这些例程。
demo% nm trylib | grep FUNC | grep point [146] | 70016| 152|FUNC |GLOB |0 |8 |point_ demo% nm trylib | grep FUNC | grep evalx [165] | 69848| 152|FUNC |GLOB |0 |8 |evalx_ demo% nm trylib | grep FUNC | grep delte demo% nm trylib | grep FUNC | grep markx demo% ..etc |
在上述示例中,grep 只在名称列表中查找与实际调用的那些库例程相应的项。
引用库的另一方法是通过 -llibrary 和 -Lpath 选项。这里,必须更改库的名称以符合 libname.a 惯例:
demo% mv test_lib/testlib.a test_lib/libtestlib.a demo% f95 -o trylib trylib.f -Ltest_lib -ltestlib |
-llibrary 和 -Lpath 选项与安装在系统公共访问目录(如 /usr/local/lib)中的库一起使用,以便其他用户可以引用它。例如,假如将 libtestlib.a 置于 /usr/local/lib 中,可以通知其他用户使用以下命令编译:
demo% f95 -o myprog myprog.f -L/usr/local/lib -ltestlib |
如果仅有几个元素需要重新编译,没有必要重新编译整个库。ar 的 -r 选项允许替换静态库中的个别元素。
示例:重新编译并替换静态库中的单个例程:
demo% f95 -c point.f demo% ar -r testlib.a point.o |
要在 ar 正在生成静态库时对其中的元素进行排序,请使用命令 lorder(1) 和 tsort(1):
demo% ar -cr mylib.a ’lorder exg.o fofx.o diffz.o | tsort’ |
动态库文件是由链接程序 ld 自预编译目标模块生成的,这些模块可在执行开始之后绑定到可执行文件。
动态库的另一功能是模块可供系统中其他正在执行的程序使用,而无需在每个程序的内存中复制模块。鉴于此原因,动态库也是一个共享库。
动态库提供下列功能:
链接程序在编译-链接过程中并不将目标模块绑定到可执行文件;这种绑定被推迟到了运行时。
共享库模块在第一个运行程序引用它时绑定到系统内存中。如果有任何后续运行程序引用它,会将该引用映射到上述第一个副本。
使用动态库,程序维护变得更加容易。一旦在系统中安装了已更新的动态库,无需重新链接可执行文件便会立即影响使用它的所有应用程序。
动态库引入了其他一些权衡考虑因素:
a.out 文件更小
将库例程绑定推迟到运行时意味着可执行文件的大小要小于同等意义上调用库静态版本的可执行文件;该可执行文件不包含库例程的二进制文件。
进程占用的内存可能更少
当使用库的若干进程同时处于活动状态时,仅有库的一个副本驻留在内存中,为所有进程所共享。
有可能增加系统开销
运行时加载和链接编辑库例程需要额外的处理器时间。另外,库中与位置无关的编码可能要比静态库中可重定位的编码执行得更慢。
有可能提高系统总体性能提高
库共享可减少内存占用,其结果将会改善系统的总体性能(减少了内存交换时的 I/O 访问时间)。
各程序间的性能特征随程序的不同会有很大变化。并非总能预先判断或估计动态库与静态库相比性能会提高(还是降低)。但是,如果所需库的这两种形式对您都可用,则分别评估一下程序使用每种库时的性能还是很值得的。
可以将位置无关代码 (position-independent code, PIC) 绑定到程序中的任何地址,而无需链接编辑器进行重新定位。从固有性质出发,此类代码可以在同时发生的进程间共享。因而,如果要生成动态共享库,必须使用 -xcode 编译器选项将组件例程编译成与位置无关。
在与位置无关的代码中,对全局项的每一引用均会通过全局偏移表中的指针编译为某一引用。每个函数调用均会通过过程链接表以相对编址模式进行编译。在 SPARC 处理器上,全局偏移表的大小限制为 8 K 字节。
编译器标志 -xcode=v 用于指定二进制对象的代码地址空间。使用该标志,不但可以生成 32、44 或 64 位绝对地址,而且可生成与位置无关的、大小不同的模型代码。(-xcode=pic13 等效于传统的 -pic 标志,-xcode=pic32 等效于 -PIC。)
-xcode=pic32 编译器选项与 -xcode=pic13 类似,但前者允许全局偏移表跨 32 位地址范围。有关详细信息,请参见 f95(1) 手册页或《Fortran 用户指南》。
可以在编译时指定动态或静态库绑定。这些选项实际上是链接程序选项,但它们是由编译器识别并传递给链接程序的。
–Bdynamic 用于在各种可能的情况下为共享动态绑定设置首选项。-Bstatic 将绑定只限制于静态库。
当库的静态和动态版本都可用时,使用该选项在命令行首选项间进行切换:
f95 prog.f -Bdynamic -lwells -Bstatic -lsurface
允许或不允许对整个可执行文件进行动态链接。(该选项只能在命令行上出现一次。)
–dy 允许链接动态共享库。-dn 不允许链接动态库。
某些静态系统库(如 libm.a 和 libc.a)不能在 64 位 Solaris 操作环境中使用。这些库只作为动态库提供。在这些环境中使用 -dn 将会导致错误,指示缺少某些静态系统库。另外,如果编译器命令行以 -Bstatic 结尾,其结果将是一样的。
要与特定库的静态版本进行链接,请使用类似下面的命令行:
f95 -o prog prog.f -Bstatic -labc -lxyz -Bdynamic
在此,链接的是用户的 libabc.a 和 libxyz.a 文件(而不是 libabc.so 或 libxyz.so),最后的 -Bdynamic 确保以动态方式链接包括系统库在内的其余各库。
在更复杂的情况下,可能必须在链接阶段根据需要用相应的 -Bstatic 或 -Bdynamic 显式引用每个系统库和用户库。首先使用设置为 ’-Dfiles’ 的 LD_OPTIONS 获取全部所需库的列表。然后用 -nolib 执行链接步骤(禁止自动链接系统库)并显式引用所需的库。例如:
f95 -m64 -o cdf -nolib cdf.o -Bstatic -lsunmath \ -Bdynamic -lm -lc
为符合链接加载程序和编译器假定的动态库命名惯例,请为您使用前缀 lib 和后缀 .so 创建的动态库命名。例如,编译器选项 -lmyfavs 可以引用 libmyfavs.so。
链接程序还接受可选的版本号后缀:例如,libmyfavs.so.1 代表库的第一版。
编译器的 -hname 选项将 name 记录为正在生成的动态库的名称。
生成动态库需要用 -xcode 选项和链接程序选项 -G、-ztext 和 -hname 编译源文件。这些链接程序选项可通过编译器命令行来提供。
您可以用静态库示例中使用的相同文件创建一个动态库。
示例:用 -pic 和其他链接程序选项编译:
demo% f95 -o libtestlib.so.1 -G -xcode=pic13 -ztext \ –hlibtestlib.so.1 *.f |
–ztext 会在发现与位置无关的代码以外的任何内容(如可重定位文本)时发出警告。
示例:使用动态库生成可执行文件 a.out:
demo% f95 -o trylib -R”pwd” trylib.f libtestlib.so.1 demo% file trylib trylib:ELF 32–bit MSB executable SPARC Version 1, dynamically linked, not stripped demo% ldd trylib libtestlib.so.1 => /export/home/U/Tests/libtestlib.so.1 libfui.so.1 => /opt/SUNWspro/lib/libfui.so.1 libfai.so.1 => /opt/SUNWspro/lib/libfai.so.1 libc.so.1 => /usr/lib/libc.so.1 |
注意,此示例使用 -R 选项将动态库路径(当前目录)绑定到可执行文件中。
file 命令显示可执行文件是以动态方式链接的。
生成动态库时,通过将已初始化的公共块集中到同一库中并在其他所有库之前引用该库,可确保正确初始化公共块(用 DATA 或 BLOCK DATA 表示的块)。
例如:
demo% f95 -G -xcode=pic32 -o init.so blkdat1.f blkdat2.f blkdat3.f demo% f95 -o prog main.f init.so otherlib1.so otherlib2.so |
首次编译会由定义公共块并在 BLOCK DATA 单元中对其进行初始化的文件创建一个动态库。第二次编译创建可执行二进制文件,将已编译的主程序与应用程序所需的动态库链接起来。注意,初始化所有公共块的动态库在其他所有库之前首先出现。这样将确保正确地初始化这些块。
下表展示了随编译器一同安装的库。
表 4–1 随编译器提供的主要库
库 |
名称 |
所需选项 |
---|---|---|
f95 内在支持 |
libfsu |
无 |
f95 接口 |
libfui |
无 |
f95 数组内在库 |
libf*ai |
无 |
f95 区间运算内在库 |
libifai |
-xinterval |
Sun 数学函数库 |
libsunmath |
无 |
如果您的可执行文件使用了 runtime.libraries 自述文件中列出的某个 Sun 动态库,则您的许可证包括将该库重新分发给客户的权利。
该自述文件位于 Sun Studio SDN 门户网站:
http://developers.sun.com/sunstudio/documentation/ss12/
请勿以任何形式重新分发或透露头文件、源代码、目标模块或目标模块的静态库。
有关更多详细信息,请参阅您的软件许可证。