跳过导航链接 | |
退出打印视图 | |
Oracle Solaris 11.1 链接程序和库指南 Oracle Solaris 11.1 Information Library (简体中文) |
在重定位处理中,介绍了运行时链接程序重定位动态可执行文件和共享目标文件以创建可运行进程所依据的机制。重定位符号查找和执行重定位的时间将此重定位处理分为两类,以简化和帮助说明所涉及的机制。理论上,考虑重定位对性能的影响时也要区分这两种类别。
当运行时链接程序需要查找符号时,缺省情况下它会搜索每个目标文件进行查找。运行时链接程序首先搜索动态可执行文件,然后按照共享目标文件的装入顺序搜索每个共享目标文件。在多数情况下,需要符号重定位的共享目标文件最终都是符号定义的提供者。
在这种情况下,如果不需要此重定位所用的符号成为共享目标文件接口的一部分,则首选将此符号转换为静态或自动变量。还可以应用符号缩减以从共享目标文件接口中删除符号。有关更多详细信息,请参见缩减符号作用域。通过进行上述转换,链接编辑器在创建共享目标文件过程中,会产生针对这些符号处理符号重定位的开销。
应在共享目标文件中可见的全局数据项只是那些属于此共享目标文件用户接口的数据项。以前,这是要实现的硬性目标,因为通常将全局数据定义为允许从两个或多个位于不同源文件中的函数进行引用。通过应用符号缩减,可以删除不必要的全局符号。请参见缩减符号作用域。减少从共享目标文件导出的全局符号数会降低重定位成本,并全面改善性能。
在具有许多符号重定位和依赖项的动态进程中,使用直接绑定也可以显著降低符号查找开销。请参见第 6 章。
在应用程序获得控制权之前,必须在进程初始化过程中执行所有立即引用重定位。但是,可以延迟所有延迟引用重定位,直到调用第一个函数实例。立即重定位通常由于数据引用而产生。因此,减少数据引用数也会缩短进程的运行时初始化时间。
将数据引用转换为函数引用也可延迟初始化重定位成本。例如,可以通过功能接口返回数据项。此转换通常会显著改善性能,因为初始化重定位成本有效分布在整个进程执行过程中。特定的进程调用可能从不调用某些功能接口,因此完全避免了这些接口的重定位开销。
复制重定位一节中介绍了使用功能接口的优势。该节介绍了一种在动态可执行文件与共享目标文件之间使用的开销稍大的特殊重定位机制。此外,还提供了如何避免此重定位开销的示例。
可重定位目标文件中的重定位节通常与重定位必须应用到的节有一对一的关系。但是,当链接编辑器创建可执行文件或共享目标文件时,会将过程链接表重定位之外的所有重定位都放在名为 .SUNW_reloc 的单个公用节中。
通过此方式组合重定位记录会将所有的 RELATIVE 重定位组织在一起。所有符号重定位均按符号名称进行排序。组织 RELATIVE 重定位可允许使用 DT_RELACOUNT/DT_RELCOUNT .dynamic 项优化运行时处理。有序符号项有助于缩短运行时符号查找时间。
共享目标文件通常使用与位置无关的代码生成。对此类型代码的外部数据项的引用通过一组表实现间接寻址。有关更多详细信息,请参见与位置无关的代码。在运行时,将使用数据项的实际地址更新这些表。使用这些已更新的表,无需修改代码本身即可访问数据。
但是,动态可执行文件通常并不使用与位置无关的代码创建。它们所执行的任何外部数据引用看似只能在运行时通过修改执行引用的代码来实现。应避免修改只读文本段。可以使用复制重定位技术来解决此引用。
假设使用链接编辑器创建动态可执行文件,并且发现对数据项的引用位于其中一个相关共享目标文件中。将在动态可执行文件的 .bss 中分配空间,空间的大小等于共享目标文件中的数据项的大小。还为此空间指定在共享目标文件中定义的符号名称。分配此数据时,链接编辑器会生成特殊的复制重定位记录,指示运行时链接程序将数据从共享目标文件复制到动态可执行文件中的已分配空间。
由于指定给此空间的符号为全局符号,因此,使用此符号可以实现任何共享目标文件引用。动态可执行文件可继承数据项。进程中对此项进行引用的任何其他目标文件都绑定到副本。生成此副本所依据的原始数据实际上变成了未使用的数据。
此机制的以下示例使用一组在标准 C 库中维护的系统错误消息。在 SunOS 操作系统早期发行版中,通过两个全局变量 sys_errlist[] 和 sys_nerr 提供此信息接口。第一个变量提供错误消息字符串数组,而第二个变量告知数组本身的大小。这些变量通常按照以下方式在应用程序中使用:
$ cat foo.c extern int sys_nerr; extern char *sys_errlist[]; char * error(int errnumb) { if ((errnumb < 0) || (errnumb >= sys_nerr)) return (0); return (sys_errlist[errnumb]); }
应用程序使用 error 函数提供焦点以获取与编号 errnumb 关联的系统错误消息。
对使用此代码生成的动态可执文件进行检查,可以更详细地显示复制重定位的实现。
$ cc -o prog main.c foo.c $ elfdump -sN.dynsym prog | grep ' sys_' [24] 0x00021240 0x00000260 OBJT GLOB D 1 .bss sys_errlist [39] 0x00021230 0x00000004 OBJT GLOB D 1 .bss sys_nerr $ elfdump -c prog .... Section Header[19]: sh_name: .bss sh_addr: 0x21230 sh_flags: [ SHF_WRITE SHF_ALLOC ] sh_size: 0x270 sh_type: [ SHT_NOBITS ] sh_offset: 0x1230 sh_entsize: 0 sh_link: 0 sh_info: 0 sh_addralign: 0x8 .... $ elfdump -r prog Relocation Section: .SUNW_reloc type offset addend section symbol .... R_SPARC_COPY 0x21240 0 .SUNW_reloc sys_errlist R_SPARC_COPY 0x21230 0 .SUNW_reloc sys_nerr ....
链接编辑器已在动态可执行文件的 .bss 中分配了空间,以便接收由 sys_errlist 和 sys_nerr 表示的数据。这些数据是运行时链接程序在进程初始化时从 C 库中复制。因此,每个使用这些数据的应用程序都在其自己的数据段中获取数据的专用副本。
此技术存在两个缺点。第一,每个应用程序都会由于运行时产生的复制数据开销而降低了性能。第二,数据数组 sys_errlist 的大小现在已成为 C 库接口的一部分。假设要更改此数组的大小,则可能是因为添加了新的错误消息。任何引用此数组的动态可执行文件都必须进行新的链接编辑,以便可以访问所有新错误消息。如果不进行这种新的链接编辑,则动态可执行文件中的已分配空间不足以包含新的数据。
如果动态可执行文件所需的数据由功能接口提供,则不会存在这些缺点。ANSI C 函数 strerror(3C) 基于提供给它的错误号返回指向相应错误字符串的指针。此函数的一种实现可能如下所示:
$ cat strerror.c static const char *sys_errlist[] = { "Error 0", "Not owner", "No such file or directory", ...... }; static const int sys_nerr = sizeof (sys_errlist) / sizeof (char *); char * strerror(int errnum) { if ((errnum < 0) || (errnum >= sys_nerr)) return (0); return ((char *)sys_errlist[errnum]); }
现在,可以将 foo.c 中的错误例程简化为使用此功能接口。通过这种简化,无需在进程初始化时执行原始复制重定位。
此外,由于数据现在对于共享目标文件而言是局部数据,因此数据不再是其接口的一部分。因此,共享目标文件可以灵活地更改数据,而不会对任何使用此数据的动态可执行文件造成不良影响。通常,从共享目标文件接口中删除数据项会改善性能,同时使得共享接口和代码更易于维护。
ldd(1) 与 -d 或 -r 选项一起使用时,可以检验动态可执行文件中存在的任何复制重定位。
例如,假设动态可执行文件 prog 最初根据共享目标文件 libfoo.so.1 生成,并且已记录以下两个复制重定位。
$ cat foo.c int _size_gets_smaller[16]; int _size_gets_larger[16]; $ cc -o libfoo.so -G foo.c $ cc -o prog main.c -L. -R. -lfoo $ elfdump -sN.symtab prog | grep _size [49] 0x000211d0 0x00000040 OBJT GLOB D 0 .bss _size_gets_larger [59] 0x00021190 0x00000040 OBJT GLOB D 0 .bss _size_gets_smaller $ elfdump -r prog | grep _size R_SPARC_COPY 0x211d0 0 .SUNW_reloc _size_gets_larger R_SPARC_COPY 0x21190 0 .SUNW_reloc _size_gets_smaller
以下是此共享目标文件的新版本,其中包含这些符号的不同数据大小。
$ cat foo2.c int _size_gets_smaller[4]; int _size_gets_larger[32]; $ cc -o libfoo.so -G foo2.c $ elfdump -sN.symtab libfoo.so | grep _size [37] 0x000105cc 0x00000010 OBJT GLOB D 0 .bss _size_gets_smaller [41] 0x000105dc 0x00000080 OBJT GLOB D 0 .bss _size_gets_larger
针对此动态可执行文件运行 ldd(1) 将显示以下内容:
$ ldd -d prog libfoo.so.1 => ./libfoo.so.1 .... relocation R_SPARC_COPY sizes differ: _size_gets_larger (file prog size=0x40; file ./libfoo.so size=0x80) prog size used; possible data truncation relocation R_SPARC_COPY sizes differ: _size_gets_smaller (file prog size=0x40; file ./libfoo.so size=0x10) ./libfoo.so size used; possible insufficient data copied ....
ldd(1) 显示此动态可执行文件将复制此共享目标文件必须提供的所有数据,但只接受其已分配空间所允许的数据量。
通过根据与位置无关的代码生成应用程序可以删除复制重定位。请参见与位置无关的代码。