原著 : Ralf S. Engelschall <[email protected]>, 1998四月
由现代Unix派生出来的操作系统都存在着的一种被叫做动态共享目标(DSO)的动态连接机制。它提供了一种在运行时将特殊格式的代码,在程序运行需要时,将需要的部分从外存调入内存执行的方法。
这种存取通常有两种途径:一种是ld由系统在程序开始时自动载入,这种载入可以由两条途径实现:自动是由系统在可执行程序开始时调用ld.so
来执行的。另一种手动,是通过在执行程序里程序系统界面到Unix装载程序通过系统调用tdlopen()/dlsym()
来执行的.
在第一种途径里DSO通常调用共享库或者调用叫做libfoo.so
或libfoo.so.1.2
的DSO库。它们位于系统目录(一般在/usr/lib
)并且在建立时通过特殊的 -lfoo
执行链接命令来链接到可执行程序内部 。这个hard-codes库被可执行程序包含以至于在开始的时候Unix装载程序可以在/usr/lib里定位libfoo.so
,在路径里hard-coded通过使用象-R的编译器选项或者
设置在路径里通过环境变量 LD_LIBRARY_PATH
. 她解决了所有的(还没有解决的)哪些在DSO可以用的可执行程序里的地址码.
可执行程序里的地址码通常不被DSO参考(因为它是可重用的一般代码的库)并且因此没有更好的解决.可执行程序不必改变自己来使用DSO的地址码因为UNIX装载程序已经全部解决了。(实际上In fact,请求ld.so
的代码是静态的在程序开始时编译进每个可执行程序的).一般库代码的静态装载的好处是明显的:库代码只需要被装载一次,在一个象libc.so
的系统库,为每个程序都节省了空间.
DSO的第二个途径是通常调用共享目标或者DSO文件并且可以用任意的扩展名命名(虽然通常用foo.so
).这些文件通常在一个程序特有的目录并且当它被用到的时候不是自动建立链接到可执行程序中.可执行程序通过dlopen()
手动的在运行时调用DSO到它的地址空间.在这个时候DSO没有为程序解决地址码的问题.但是内部的Unix调度程序自动解决所有(还没有解决的)从程序输出的在DSO里的地址码并且它早就调用了DSO库(特别是所有的在libc.so
里的地址码).这种方法This way the DSO得知程序的地址码就象已经在第一个地址被静态链接了一样。
最后,为了利用DSO的API,可执行程序必须为了以后用户使用内含的调度表而解决DSO通过dlsym()
的特殊标记
等等的问题.另外一个方面:可执行程序必须手动解决每一个特殊标记使之都能够被使用.这样一个机制的好处是任意一个程序的部分都直到在程序需要的时候装入(这样不用消耗内存) . 当被调用时,这些程序部分能够被加入,使程序的功能动态的增加.
虽然这种DSO机制非常好,但是这里至少还有一个难点:当使用DSO来扩展一个可执行程序时为了DSO而从可执行程序里解析地址码的问题(第二种方法).为什么?因为从可执行程序里面的地址码"反解析"出DSO地址码是与库的设计相反的(程序使用了哪些库是未知的) 而且这并不能在所有的平台下工作也没有一个标准.实际上可执行程序的全部地址码一般是不能反输出的并且这样就不能够为DSO所用.找到方法当使用DSO来扩展在运行的程序时来使编译器输出所有的地址码是主程序一个必须解决的问题。
共享库方面的使用是一个典型,因为这是为什么使用DSO机制的目的,因此这被几乎所有类型的操作系统提供的库.另一方面使用公用目标来扩展程序的方法并没有被大量程序使用。
好象1998年只有几个软件包使用DSO机制在运行时来动态的扩展它们的功能:Perl 5(通过它自己的XS机制和动态加载模块),Netscape Server,等等.从1.3版开始, Apache加入了这个团队,因为Apache早就使用一个模块概念来扩展它的功能并且在内部使用一个基于调度的列表来链接扩展模块到Apache核心模块.所以,Apache早就注定要使用DSO来在运行时加载它的模块。
就象Apache 1.3,设置系统支持两个可选的特点来利用DSO:用DSO库编译Apache的核心程序为了分享使用和编译Apache模块成为DSO文件为了在运行时清晰的装载。
DSO支持调用特别的Apache模块是基于一个名叫mod_so.c
的模块,这个模块必须静态的编译Apache的核心.这是除了http_core.c
以外仅有的模块不可以被放到DSO自己(bootstrapping!). 实际上所有的别的发布的Apache模块都可以被放置到DSO,通过个别的通过设置被DSO建立允许--允许可能的-共享设置
(看顶层的INSTALL
文件)或者通过改变AddModule
命令在你的src/Configuration
到SharedModule
命令(看src/INSTALL
文件).在编译一个模块到一个名字叫mod_foo.so
的DSO以后,你能够使用mod_so
的LoadModule
命令在你的httpd.conf
文件里在服务程序开始时或重新启动以后调用这个模块。
为了简化这种为了Apache模块而创建DSO文件的方法(尤其是第三方模块),一个新的名叫apxs的支持程序(APache eXtenSion)被使用.它可以用来建立基于DSO的模块,模块位于Apache源树以外。。这个思路很简单:当安装Apache时,设置使安装程序安装Apache C 头文件并且放置平台支持的编译器和链接程序标志,用来建立DSO文件到apxs
程序.通过这种方法,用户可以使用apxs
来编译它的Apache模块源代码而不用Apache发布源代码树并且不用手动的添加平台支持的编译程序和谅解程序的标志来获得DSO支持。
为了放置编译好的apache核心程序到一个DSO库(仅仅在一些支持的平台上需要强迫链接程序输出Apache核心的地址码--一个DSO模块化的先决条件)规定的SHARED_CORE
必须能够通过设置--允许-规定=SHARED_CORE
设置(看顶层的INSTALL
文件)或者通过改变Rule
命令,在你的Configuration
文件里规定SHARED_CORE=yes
(看src/INSTALL
文件).Apache核心代码接着被放置到一个名叫libhttpd.so的DSO库。因为静态库不能够在所有的平台上被链接成为一个DSO,一个附加的名叫 libhttpd.ep
的可执行程序被创建,这个程序不但包含这个静态代码而且有提供main()
剩余部分 的功能.最后,httpd
程序自己被用bootstrapping代码替换,后者自动确定Unix调度程序能够装载并且开始libhttpd.ep
,它通过提供LD_LIBRARY_PATH
到libhttpd.so
的方法实现.
Apache的src/Configure
script 现在仅仅有一个限制但是可以通过怎样编译DSO文件的知识来嵌入,因为象早就提到的,它依靠平台的支持.几乎所有的主要的Unix平台都能够支持.当前能够确定支持的(1998五月)如下:
o FreeBSD (2.1.5, 2.2.5, 2.2.6) o OpenBSD (2.x) o NetBSD (1.3.1) o BSDI (4.0) o Linux (Debian/1.3.1, RedHat/4.2) o Solaris (2.4, 2.5.1, 2.6) o SunOS (4.1.3) o Digital UNIX (4.0) o IRIX (6.2) o HP/UX (10.20) o UnixWare (2.01, 2.1.2) o SCO (5.0.4) o AIX (3.2, 4.1.5, 4.2, 4.3) o ReliantUNIX/SINIX (5.43) o SVR4 (-)
o Ultrix (这个平台下没有确定的版本)
为了给你一个关于Apache 1.3的DSO特点的概念, 这里有一个简短的摘要:
libhttpd.so
,一个可执行程序libhttpd.ep
和一个bootstrapping可执行程序httpd
(注意: 这仅仅需要一些平台的支持来强制编译器输出Apache的核心地址码,这是使用DSO模块化的先决条件):
$ ./configure --prefix=/path/to/install --enable-rule=SHARED_CORE ... $ make install |
- Edit src/Configuration: << Rule SHARED_CORE=default >> Rule SHARED_CORE=yes << EXTRA_CFLAGS= >> EXTRA_CFLAGS= -DSHARED_CORE_DIR=\"/path/to/install/libexec\" $ make $ cp src/libhttpd.so* /path/to/install/libexec/ $ cp src/libhttpd.ep /path/to/install/libexec/ $ cp src/httpd /path/to/install/bin/ |
mod_foo.c
,在它自己的DSO mod_foo.so
:
$ ./configure --prefix=/path/to/install --enable-shared=foo $ make install |
- Edit src/Configuration: << AddModule modules/xxxx/mod_foo.o >> SharedModule modules/xxxx/mod_foo.so $ make $ cp src/xxxx/mod_foo.so /path/to/install/libexec - Edit /path/to/install/etc/httpd.conf >> LoadModule foo_module /path/to/install/libexec/mod_foo.so |
mod_foo.c
,在它自己的DSO mod_foo.so
$ ./configure --add-module=/path/to/3rdparty/mod_foo.c --enable-shared=foo $ make install |
$ cp /path/to/3rdparty/mod_foo.c /path/to/apache-1.3/src/modules/extra/ - Edit src/Configuration: >> SharedModule modules/extra/mod_foo.so $ make $ cp src/xxxx/mod_foo.so /path/to/install/libexec - Edit /path/to/install/etc/httpd.conf >> LoadModule foo_module /path/to/install/libexec/mod_foo.so |
mod_foo.c
,放入在APACHE源代码树以外的自己的DSO mod_foo.so:
$ cd /path/to/3rdparty $ apxs -c mod_foo.c $ apxs -i -a -n foo mod_foo.so |
下面是Apache 1.3里的DSO的特点,有如下的优点:
LoadModule
httpd.conf
设置命令代替在建立时的AddModule
命令来反编译. 例如这种方法一能够运行不同的服务实例(标准和SSL版本,minimalistic & powered up版本 [mod_perl, PHP3],等等.),而只通过一个Apache. apxs
部分你可以不用Apache源代码树和只需要apxs -i
命令来重新启动一个新的你现在开发模块到运行Apache服务程序的版本。 DSO有如下缺点:
ld -lfoo
)发生冲突(例如a.out-based 平台经常不支持这个功能,但是ELF-based平台支持)你不能为所有类型的模块使用DSO机制。 或者换一句话说,模块作为DSO文件编译是受限制,只能使用APACHE核心地址码,APACHE核心使用的C库(libc
)和所有的别的动态和静态的库,或者静态库档案(libfoo.a
)包含的独立的代码。唯一使用别的代码的办法是或者确定APACHE核心自己早就包含自己的参考,通过dlopen()调用代码自己,或者在建立APACHE时允许SHARED_CHAIN
规则(当你的平台支持不用DSO库链接DSO文件)。SHARED_CORE
特性,因为这种方法使全部的地址码都被强制输出。作为结果,Apache src/Configure
script自动强制SHARED_CORE
在这些平台上,当DSO特性被用在Configuration
文件或者在configure命令行.