Apache動態共用物件(DSO)支援


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_soLoadModule指令在伺服器啟動或重新啟動時載入此模組。

可以通過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.solibfoo.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_perlmod_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()自己載入程式碼。