在内存中装入动态目标文件时,将会检查该目标文件的任何其他依赖项。缺省情况下,将立即装入存在的所有依赖项。此循环会一直继续,直到找遍整个依赖项树为止。最后,解析由重定位指定的所有目标文件间数据引用。无论应用程序在执行期间是否引用了这些依赖项中的代码,都会执行这些操作。
在延迟装入模型中,标记为延迟装入的所有依赖项仅在显式引用时装入。通过利用函数调用的延迟绑定,依赖项装入将会一直延迟,直到第一次引用该函数。因此,绝不会装入从不引用的目标文件。
重定位引用可以是即时的,也可以是延迟的。因为初始化目标文件时必须解析即时引用,所以必须即时装入满足此引用要求的任何依赖项。因此,将这类依赖项标识为延迟可装入几乎没有效果。请参见执行重定位的时间。通常,建议不要在动态目标文件之间进行即时引用。
链接编辑器对调试库 liblddbg 的引用将使用延迟装入。由于不会经常进行调试,因此每次调用链接编辑器时装入此库既无必要又需要很大开销。通过指示可以延迟装入此库,处理库的开销将转至要求调试输出的那些调用。
实现延迟装入模型的替代方法是在需要时使用 dlopen() 和 dlsym() 来装入并绑定到依赖项。如果 dlsym() 引用数很少,则此模型非常理想。如果在链接编辑时不知道依赖项名称或位置,则此模型也很适用。对于与已知依赖项的更复杂交互,对正常符号引用进行编码并指定要延迟装入的依赖项更为简单。
通过链接编辑器选项 -z lazyload 和 -z nolazyload,可将目标文件分别指定为延迟装入或正常装入。这些选项在链接编辑命令行中与位置相关。该选项之后的任何依赖项都将采用其指定的装入属性。缺省情况下,-z nolazyload 选项有效。
以下简单程序具有依赖项 libdebug.so.1。动态节 (.dynamic) 显示 libdebug.so.1 被标记为延迟装入。符号信息节 (.SUNW_syminfo) 显示触发 libdebug.so.1 装入的符号引用。
$ cc -o prog prog.c -L. -zlazyload -ldebug -znolazyload -lelf -R'$ORIGIN' $ elfdump -d prog Dynamic Section: .dynamic index tag value [0] POSFLAG_1 0x1 [ LAZY ] [1] NEEDED 0x123 libdebug.so.1 [2] NEEDED 0x131 libelf.so.1 [3] NEEDED 0x13d libc.so.1 [4] RUNPATH 0x147 $ORIGIN ... $ elfdump -y prog Syminfo section: .SUNW_syminfo index flgs bound to symbol .... [52] DL [1] libdebug.so.1 debug
值为 LAZY 的 POSFLAG_1 指示应该延迟装入下面的 NEEDED 项 libdebug.so.1。由于 libelf.so.1 没有前述的 LAZY 标志,因此该库将在程序初始启动时装入。
注 - libc.so.1 具有特殊的系统要求,该要求指出不应延迟装入该文件。如果在处理 libc.so.1 时 -z lazyload 有效,则实际上会忽略该标志。
使用延迟装入时,可能需要准确声明在应用程序使用的目标文件中的依赖项和运行路径。例如,假定有两个目标文件 libA.so 和 libB.so,它们同时引用了 libX.so 中的符号。libA.so 将 libX.so 声明为依赖项,但 libB.so 没有这样操作。通常,将 libA.so 与 libB.so 一起使用时,libB.so 可以引用 libX.so,因为 libA.so 使此依赖项可用。但是,如果 libA.so 将 libX.so 声明为延迟装入,则在 libB.so 引用此依赖项时可能不会装入 libX.so。如果 libB.so 将 libX.so 声明为依赖项,但未能提供查找该依赖项所需的运行路径,则可能会出现类似错误。
无论是否延迟装入,动态目标文件都应声明其所有依赖项以及如何查找这些依赖项。对于延迟装入,此依赖项信息更为重要。
延迟装入可以提供替代 dlopen(3C) 和 dlsym(3C) 的方法。请参见运行时链接编程接口。例如,libfoo.so.1 中的以下代码将验证是否装入了目标文件,然后调用该目标文件提供的接口。
void foo() { void *handle; if ((handle = dlopen("libbar.so.1", RTLD_LAZY)) != NULL) { int (*fptr)(); if ((fptr = (int (*)())dlsym(handle, "bar1")) != NULL) (*fptr)(arg1); if ((fptr = (int (*)())dlsym(handle, "bar2")) != NULL) (*fptr)(arg2); .... }
尽管非常灵活,但这种使用 dlopen() 和 dlsym() 的模型不是自然的编码样式,并存在一些缺点。
必须要知道其中的符号将要退出的目标文件。
使用函数指针的调用不会通过编译器或 lint(1) 提供任何验证方式。
如果提供所需接口的目标文件满足下列条件,则可以简化此代码。
可在链接编辑时作为依赖项建立该目标文件。
该目标文件始终可用。
使用函数引用会触发延迟装入,可以实现同样的 libbar.so.1 推迟装入。在此情况下,对函数 bar1() 的引用将导致延迟装入关联的依赖项。这种编码要自然得多,标准函数调用可用于编译器或 lint(1) 验证。
void foo() { bar1(arg1); bar2(arg2); .... } $ cc -G -o libfoo.so.1 foo.c -L. -zdefs -zlazyload -lbar -R'$ORIGIN'
但是,如果提供所需接口的目标文件并非始终可用,则此模型会失败。在此情况下,最好能够在不必知道依赖项名称的情况下测试依赖项是否存在。需要一种测试满足函数引用要求的依赖项可用性的方法。
一个测试函数是否存在的强大模型,可以通过显式定义推迟依赖项,以及使用 dlsym(3C) 与 RTLD_PROBE 句柄来实现。
显式定义的推迟依赖项是对延迟可装入依赖项的扩展。与推迟依赖项关联的符号引用则称为推迟符号。仅在首次引用符号时,才会处理根据该符号进行的重定位。这些重定位不会在 LD_BIND_NOW 处理过程中进行处理,也不会通过带有 RTLD_NOW 标志的 dlsym(3C) 进行处理。
推迟依赖项是在链接编辑时使用链接编辑器 -z deferred 选项建立的。
$ cc -G -o libfoo.so.1 foo.c -L. -zdefs -zdeferred -lbar -R'$ORIGIN'
使用已建立的 libbar.so.1 作为推迟依赖项时,对 bar1() 的引用可以验证依赖项是否可用。此测试可用于控制对依赖项以使用 dlsym(3C) 的方式提供的函数的引用。然后此代码可以对 bar1() 和 bar2() 做出自然调用。这些调用更加清晰且更易于编写,并允许编译器捕捉调用序列中的错误。
void foo() { if (dlsym(RTLD_PROBE, "bar1")) { bar1(arg1); bar2(arg2); .... }
推迟依赖项提供其他级别的灵活性。如果尚未装入该依赖项,则可以在运行时对其进行更改。该机制提供的灵活性级别类似于 dlopen(3C),其中各个目标文件可以由调用者装入和绑定。
如果已知原始依赖项名称,则可以使用带有 RTLD_DI_DEFERRED 参数的 dlinfo(3C) 将原始依赖项更换为新的依赖项。此外,与依赖项关联的推迟符号可以用来识别使用带有 RTLD_DI_DEFERRED_SYM 参数的 dlinfo(3C) 的推迟依赖项。