本文档介绍了如何使用DSO模块及其使用背后的理论。
DSO支持的实现
DSO对加载单个Apache httpd模块的支持基于名为mod_so
的模块,该模块必须静态编译到Apache httpd核心中。除了核心之外,它是唯一不能放入DSO本身的模块。实际上,所有其他分布式Apache httpd模块将被放入DSO中。将模块编译为名为mod_foo.so
的DSO后,可以在httpd.conf
文件中使用mod_so
的LoadModule
指令在服务器启动或重新启动时加载此模块。
可以通过configure
命令的--enable-mods-static
选项禁用单个模块的DSO构建,如安装文档中所述。
为了简化Apache httpd模块(尤其是第三方模块)的DSO文件的创建,可以使用名为apxs(APache eXtenSion)的支持程序。它可用于在Apache httpd源树之外构建基于DSO的模块。这个想法很简单:在安装Apache HTTP Server时,configure的make install
过程会安装Apache httpd C头文件,并将用于构建DSO文件的平台相关编译器和链接器标志放入apxs程序中。这样,用户可以使用apxs编译他的Apache httpd模块源代码,而无需Apache httpd分发源代码树,也无需为DSO支持提供依赖于平台的编译器和链接器标志。
使用摘要
为了概述Apache HTTP Server 2.x的DSO功能,这里有一个简短的摘要(步骤):
第1步 - 构建并将分布式Apache httpd模块(例如mod_foo.c
)安装到自己的DSO mod_foo.so
中:
$ ./configure --prefix=/path/to/install --enable-foo $ make install
Shell
第2步 - 配置Apache HTTP Server并启用所有模块。服务器启动期间仅加载基本集。可以通过激活或取消激活httpd.conf
中的LoadModule指令来更改已加载模块的集合。
$ ./configure --enable-mods-shared=all $ make install
Shell
第3步 - 有些模块仅对开发人员有用,不会构建。使用模块时全部设置。要构建所有可用的模块,包括开发人员模块都可使用。此外,可以通过configure
的--enable-load-all-modules
选项激活所有构建模块的LoadModule指令。
$ ./configure --enable-mods-shared=reallyall --enable-load-all-modules $ make install
Shell
使用apxs在Apache httpd源代码树之外构建并安装第三方Apache httpd模块(例如mod_foo.c
)到其自己的DSO mod_foo.so
中:
$ cd /path/to/3rdparty $ apxs -cia mod_foo.c
Shell
在所有情况下,一旦编译了共享模块,就必须在httpd.conf
中使用LoadModule指令来告诉Apache httpd激活模块。
背后机制
在现代Unix衍生产品中,存在一种称为动态共享对象(DSO)的动态链接/加载机制,它提供了一种以特殊格式构建程序代码的方法,以便在运行时将其加载到可执行程序的地址空间中。
这种加载通常可以通过两种方式完成:当一个可执行程序启动时,通过一个名为ld.so
的系统程序自动完成,或者通过系统调用dlopen()
/dlsym()
通过编程系统接口从Unix加载器手动执行程序。
在第一种方式中,DSO通常称为共享库或DSO库,并命名为libfoo.so
或libfoo.so.1.2
。它们驻留在系统目录(通常是/usr/lib
)中,并且通过在链接器命令中指定-lfoo
,在构建时建立可执行程序的链接。这个硬编码库引用了可执行程序文件,因此在启动时,Unix加载器能够在/usr/lib
中找到libfoo.so
,在通过链接器选项(如-R)进行硬编码的路径中,或者在通过环境变量LD_LIBRARY_PATH
。然后它解析可执行程序中可用于DSO的任何(尚未解决的)符号。
可执行程序中的符号通常不会被DSO引用(因为它是可重用的通用代码库),因此不需要进一步解析。可执行程序不需要自己做任何事情来使用DSO中的符号,因为完整的解析是由Unix加载器完成的。实际上,调用ld.so
的代码是运行时启动代码的一部分,该代码链接到已经绑定为非静态的每个可执行程序。动态加载公共库代码的优势显而易见:库代码只需要存储一次,就像libc.so
这样的系统库,为每个程序节省磁盘空间。
在第二种方式中,DSO通常称为共享对象或DSO文件,并且可以使用任意扩展名命名(尽管规范名称为foo.so
)。这些文件通常保留在特定于程序的目录中,并且没有自动建立的链接指向使用它们的可执行程序。相反,可执行程序通过dlopen()
手动将DSO在运行时加载到其地址空间。此时,不会从DSO解析可执行程序的符号。但是,Unix加载程序会自动解析DSO中可执行程序导出的符号集及其已加载的DSO库(尤其是来自无处不在的libc.so的所有符号)中的任何(尚未解析的)符号。通过这种方式,DSO可以了解可执行程序的符号集,就好像它首先与它静态链接一样。
最后,为了利用DSO的API,可执行程序必须通过dlsym()解析DSO中的特定符号,以便以后在调度表等内部使用。换句话说:可执行程序必须手动解析它需要的每个符号才能使用它。这种机制的优点在于,在所讨论的程序需要它们之前,不需要加载可选的程序部分(因此不需要花费内存)。必要时,可以动态加载这些程序部分以扩展基本程序的功能。
尽管这种DSO机制听起来很简单,但至少有一个困难的步骤:当使用DSO扩展程序时,从DSO的可执行程序中解析符号(第二种方式)。为什么?因为来自可执行程序符号集的“反向解析”DSO符号是针对库设计的(库不知道它所使用的程序),并且既不是在所有平台上都可用,也不是标准化的。实际上,可执行程序的全局符号通常不会重新导出,因此无法在DSO中使用。找到一种强制链接器导出所有全局符号的方法是使用DSO在运行时扩展程序时必须解决的主要问题。
共享库方法是典型的方法,因为它是DSO机制的设计方法,因此它几乎用于操作系统提供的所有类型的库。
DSO优点和缺点
基于DSO的功能具有以下优点:
- 服务器包在运行时更灵活,因为服务器进程可以在运行时通过
httpd.conf
配置 LoadModule 指令而不是在构建时配置选项进行组装。例如,通过这种方式,只需一个Apache httpd安装即可运行不同的服务器实例(标准版和SSL版,简约版和动态版[mod_perl,mod_php]等)。 - 即使在安装后,也可以使用第三方模块轻松扩展服务器包。这对于供应商软件包维护者来说是一个很大的好处,他们可以创建Apache httpd核心软件包以及包含PHP,
mod_perl
,mod_security
等扩展的其他软件包。 - 更简单的Apache httpd模块原型设计,因为使用DSO/apxs对,可以在Apache httpd源树之外工作,只需要
apxs -i
命令,然后重启apachectl
,即可将当前开发的模块的新版本带入正在运行的Apache HTTP服务器。
DSO具有以下缺点:
- 由于Unix加载器现在必须执行的符号解决开销,服务器在启动时的速度大约慢20%。
- 在某些平台下,服务器在执行时的速度大约慢5%,因为位置无关代码(PIC)有时需要复杂的汇编器技巧来进行相对寻址,这不一定和绝对寻址一样快。
- 由于DSO模块无法在所有平台上与其他基于DSO的库(
ld -lfoo
)链接(例如,基于a.out的平台通常不提供此功能,而基于ELF的平台),因此无法使用DSO机制所有类型的模块。或者换句话说,编译为DSO文件的模块仅限于使用来自Apache httpd核心,C库(libc)以及Apache httpd核心使用的所有其他动态或静态库或静态库归档(libfoo.a
)包含与位置无关的代码。使用其他代码的唯一机会是确保httpd核心本身已包含对它的引用或通过dlopen()
自己加载代码。