Apache HTTP Server是一個模組化程式,管理員可以通過選擇一組模組來選擇要包含在伺服器中的功能。模組將編譯為動態共用對象(DSO),與主httpd二進位檔分開存在。DSO模組可以在構建伺服器時進行編譯,也可以在以後使用Apache Extension Tool(apxs)進行編譯和添加。或者,可以在構建伺服器時將模組靜態編譯為httpd二進位檔。
本文檔介紹了如何使用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
第2步 - 配置Apache HTTP Server並啟用所有模組。伺服器啟動期間僅加載基本集。可以通過啟動或取消啟動httpd.conf
中的LoadModule指令來更改已加載模組的集合。
$ ./configure --enable-mods-shared=all
$ make install
第3步 - 有些模組僅對開發人員有用,不會構建。使用模組時全部設置。要構建所有可用的模組,包括開發人員模組都可使用。此外,可以通過configure
的--enable-load-all-modules
選項啟動所有構建模組的LoadModule指令。
$ ./configure --enable-mods-shared=reallyall --enable-load-all-modules
$ make install
使用apxs在Apache httpd源代碼樹之外構建並安裝第三方Apache httpd模組(例如mod_foo.c
)到其自己的DSO mod_foo.so
中:
$ cd /path/to/3rdparty
$ apxs -cia mod_foo.c
在所有情況下,一旦編譯了共用模組,就必須在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()
自己加載代碼。