Apache HTTP伺服器版本 2.4
Apache 2.x 是一款通用網頁伺服器,專門提供靈活性、可攜性與效能的平衡。儘管並未特別設計為設定基準紀錄,但 Apache 2.x 仍可在許多實際情況下達到高水準的效能。
與 Apache 1.3 相較,2.x 版本包含許多額外的最佳化功能,用以增加傳輸量和可擴充性。這些改善功能大部分都已納入預設值。然而,仍有編譯時間和執行時期的組態選項可能大幅影響效能。本文說明伺服器管理員可設定哪些選項,以調整 Apache 2.x 安裝的效能。這些組態設定選項中有一些能讓 httpd 更有效地利用硬體和作業系統的功能,而另一些則讓管理員能夠捨棄某些功能以換取更快的速度。
影響網頁伺服器效能最大的硬體問題是 RAM。網頁伺服器不應該進行換頁,因為換頁會將每個要求的延遲時間增加到使用者認為「夠快」的程度以上。這會導致使用者停止並重新載入,進而進一步增加負載。您可以,而且應該控制 MaxRequestWorkers
設定,讓您的伺服器不再產生那麼多子程序而開始換頁。這樣做的步驟很簡單:透過像是 top
的工具檢視您的程序清單,找出 Apache 程序的平均大小,並將這個數字除以您的總可用記憶體,並為其他程序留下一些空間。
除此以外,其他的事項都屬於例行事項:取得足夠快的 CPU、足夠快的網路卡和足夠快的磁碟機,而「足夠快」的定義則需要靠實驗來確認。
作業系統的選擇在很大程度上取決於個別情況。但有一些已證實普遍適用的大原則:
執行您所選擇的操作系統的最新穩定版本和修正程式等級。許多作業系統供應商已於近年大幅改善其 TCP 堆疊和執行緒程式庫的效能。
如果您的作業系統支援 sendfile(2)
系統呼叫,請務必安裝啟用此呼叫所需的版本和/或修正程式。(例如,對 Linux,這表示使用 Linux 2.4 或更新版本。對 Solaris 8 的早期版本,您可能需要套用修正程式。)在可用的系統上,sendfile
可以讓 Apache 2 更快地提供靜態內容,而且還能降低 CPU 使用率。
相關模組 | 相關指令 |
---|---|
在 Apache 1.3 之前,HostnameLookups
採用預設 On
。由於此設定在要求完成之前,需要執行 DNS 查詢功能,因此會為每個要求增加延遲。在 Apache 1.3 中,此設定已預設為 Off
。如果您需要將記錄檔中的位址解析為主機名稱,請使用 Apache 附帶的 logresolve
程式,或任何一種可用的多種記錄報告套件。
建議您在生產用網路伺服器機器以外的機器上做此類記錄檔後處理,避免該活動對伺服器效能造成負面影響。
如果您使用任何
或 Allow
的 domain
指令(也就是使用主機名稱或網域名稱,而非 IP 位址),您會需要執行兩次 DNS 查詢(反向查詢,接著是正向查詢,確認反向查詢未偽造)。因此,如果您能使用 IP 位址,而非名稱,則使用這些指令時,就能獲得最佳效能。Deny
的 domain
請注意,可以設定指令範圍,例如在 <Location "/server-status">
中。在這種情況下,DNS 查詢只會執行在符合條件的要求上。以下是只針對 .html
和 .cgi
檔案停用查詢的範例:
HostnameLookups off <Files ~ "\.(html|cgi)$"> HostnameLookups on </Files>
但是,即使如此,如果您只需要某些 CGI 中的 DNS 名稱,您可以考慮在需要它的特定 CGI 中執行 gethostbyname
呼叫。
在您的 URL 空間中,如果任何地方沒有 Options FollowSymLinks
,或您有 Options SymLinksIfOwnerMatch
,Apache 就必須發出額外的系統呼叫來檢查符號連結。(針對每一個檔案名稱元件發出一個額外的呼叫)例如,如果您有:
DocumentRoot "/www/htdocs" <Directory "/"> Options SymLinksIfOwnerMatch </Directory>
並對 URI /index.html
提出要求,接著 Apache 會對 /www
、/www/htdocs
和 /www/htdocs/index.html
執行 lstat(2)
。這些 lstat
的結果從未快取,因此它們會發生在每個單一要求上。如果您真的想要符號連結安全檢查,您可以執行類似以下動作
DocumentRoot "/www/htdocs" <Directory "/"> Options FollowSymLinks </Directory> <Directory "/www/htdocs"> Options -FollowSymLinks +SymLinksIfOwnerMatch </Directory>
至少可以避免針對 DocumentRoot
路徑的額外檢查。請注意,如果您在文檔根目錄外有任何 Alias
或 RewriteRule
路徑,您需要新增類似的區段。為了獲得最高的效能,且沒有符號連結保護,請在所有位置設定 FollowSymLinks
,而且不要設定 SymLinksIfOwnerMatch
。
在 URL 空間中允許覆寫的地方(通常是 .htaccess
檔案),Apache 會嘗試為每個檔案名稱元件開啟 .htaccess
。例如,
DocumentRoot "/www/htdocs" <Directory "/"> AllowOverride all </Directory>
並對 URI /index.html
提出要求。接著 Apache 會嘗試開啟 /.htaccess
、/www/.htaccess
和 /www/htdocs/.htaccess
。解決方案類似於先前的 Options FollowSymLinks
案例。要獲得最高的效能,請在檔案系統中的每個位置使用 AllowOverride None
。
如果真的對最後一滴效能感興趣,請儘可能避免內容協商。實際上,協商的好處大於效能罰則。有一個案例,您可以在其中加速伺服器。不要使用萬用字元,例如
DirectoryIndex index
使用完整的選項清單
DirectoryIndex index.cgi index.pl index.shtml index.html
在其中把最常見的選項列在第一位。
另外請注意,與使用 MultiViews
相較,明確建立 type-map
檔案能提供更好的效能,因為不需要掃描目錄的檔案,只要讀取這個單一檔案就能夠判斷必要的資料。
如果您的網站需要內容協商,請考慮使用 type-map
檔案,而不是 Options MultiViews
指令來達成協商。請參閱 內容協商 文件,以取得協商方法的完整說明和建立 type-map
檔案的說明。
在 Apache 2.x 需要檢視正在傳遞檔案的內容的情況下——例如,在進行伺服器端包含處理時——如果作業系統支援某種形式的 mmap(2)
,它通常會將檔案記憶體對應。
在某些平台上,這個記憶體對應會改善效能。但是,在某些情況下,記憶體對應會損害 httpd 的效能,甚至穩定性。
在某些作業系統中,當 CPU 數量增加時,mmap
的擴充性不如 read(2)
。例如,在多處理器 Solaris 伺服器上,當停用 mmap
時,Apache 2.x 有時傳遞伺服器剖析檔案的速度較快。
如果您對位於 NFS 掛載檔案系統上的檔案進行記憶體對應,而且另一個 NFS 用戶端電腦上的處理序刪除或截斷檔案,您的處理序在下一次嘗試存取對應的檔案內容時可能會發生匯流排錯誤。
對於這兩種因素都適用的安裝,您應使用 EnableMMAP off
停用已傳送檔案的記憶體對應。(註:該指令可以個別針對目錄覆寫。)
在 Apache 2.x 可以忽略要傳送檔案的內容的情況中,例如,傳送靜態檔案內容時,如果作業系統支援 sendfile(2)
操作,它通常會對該檔案使用 kernel sendfile 支援。
在大多數平台上,使用 sendfile 可透過消除單獨的讀取與傳送機制來改善效能。但是,有一些情況會造成使用 sendfile 會損害 httpd 的穩定性。
一些平台可能會損壞建構系統未偵測到的 sendfile 支援,特別是在於這些二進位檔案是在另一部電腦建構,然後再移至具有損壞 sendfile 支援的機器中時。
對於 NFS 掛載的檔案系統,kernel 可能無法透過自己快取可靠地傳送網路檔案。
對於這兩種因素都適用的安裝,您應使用 EnableSendfile off
停用 sendfile 傳送檔案內容。(註:該指令可以個別針對目錄覆寫。)
在 Apache 1.3 之前,MinSpareServers
、MaxSpareServers
和 StartServers
設定都會對基準測試結果產生巨大的影響。特別是,Apache 需要一個「引導」期間才能達到足夠的子程序,以傳送所套用的負載。在最初產生 StartServers
子程序後,每秒只會產生一個子程序滿足 MinSpareServers
設定。因此,由 100 個同時用戶端存取的伺服器使用預設 StartServers
的 5
會花費約 95 秒的時間產生足夠的子程序來處理負載。這在實際的伺服器上運作良好,因為不會頻繁重新啟動。但對於只有十分鐘運作時間的基準測試來說,表現很差。
每秒一個規則是為了避免透過啟動新的子程序來淹沒機器。如果機器忙於產生子程序,它無法為要求提供服務。但它對 Apache 所感知的效能有如此重大的影響,它不得不被更換。從 Apache 1.3 開始,程式碼會放寬每秒一個的規則。它會產生一個,等待一秒,然後產生兩個,等待一秒,再產生四個,並且會持續呈指數成長,直到它每秒產生 32 個子程序。滿足 MinSpareServers
設定後,它就會停止。
похоже, он и так достаточно отзывчивый, поэтому не нужно постоянно подстраивать MinSpareServers
, MaxSpareServers
и StartServers
. Если за секунду создается больше 4 дочерних элементов, то выводится сообщение в ErrorLog
. Если вы видите много таких ошибок, подумайте о настройке этих параметров. Используйте в качестве руководства вывод mod_status
.
К созданию процессов относится также и завершение процессов, которое вызвано параметром MaxConnectionsPerChild
. По умолчанию это значение равно 0
, а это значит, что нет ограничений на количество обрабатываемых соединений на дочерний элемент. Если в вашей конфигурации этот параметр в настоящее время установлен на очень низкое число, например 30
, вы можете смело значительно его повысить. Если вы используете SunOS или старую версию Solaris, ограничьте число, приблизительно до 10000
из-за потерь памяти.
Когда используются сохраняемые соединения, дочерние элементы будут заняты простаиванием и ожиданием новых запросов на уже открытом соединении. Значение по умолчанию KeepAliveTimeout
равно 5
секундам, это значение призвано минимизировать такой эффект. Придется искать компромисс между сетевой пропускной способностью и ресурсами сервера. Ни в коем случае не устанавливайте значение больше приблизительно 60
секунд, т. к. практически вся выгода теряется.
Apache 2.x поддерживает модули взаимодействия с несколькими потоками, которые называются модулями многопроцессорной обработки (MPM). При сборке Apache вы должны выбрать, какой MPM будет использоваться. Существуют MPM для конкретных платформ: mpm_netware
, mpmt_os2
и mpm_winnt
. Для общих систем типа Unix имеется несколько MPM, из которых можно выбирать. Выбор MPM может влиять на скорость и масштабируемость httpd
worker
использует несколько дочерних процессов со множеством потоков в каждом. Каждый поток обрабатывает одно соединение за раз. В целом Worker является хорошим выбором для серверов с высоким трафиком, поскольку он требует меньше памяти, чем MPM prefork.event
потоковый как и Worker MPM, но рассчитан на обработку большего количества запросов одновременно путем передачи части процессов другим рабочим потокам, высвобождая таким образом основные потоки для обработки новых запросов.prefork
использует несколько дочерних процессов, в каждом из которых по одному потоку. Каждый процесс обрабатывает одно соединение за раз. Во многих системах prefork работает не медленнее, чем worker, но он занимает больше памяти. Беспотоковая архитектура Prefork по сравнению с Worker имеет ряд плюсов в некоторых ситуациях: его можно использовать со сторонними модулями, не поддерживающими несколько потоков, а в платформах со слабой поддержкой отладки потоков его легче отлаживать.如需取得這些及其他 MPM 的更多資訊,請參閱 MPM 文件。
由於記憶體使用量是效能最重要的考量因素,因此您應該嘗試移除實際上未使用的模組。如果您已將模組建置為 DSO,移除模組只是一個將該模組的相關 LoadModule
指令註解掉的問題。這樣可以讓您嘗試移除模組,看看您的網站是否還能在沒有該模組的情況下運作。
反之,如果您已將模組靜態連結到您的 Apache 二進位檔,則必須重新編譯 Apache 才能移除不需要的模組。
這裡出現了一個相關問題,當然,哪些模組是您需要的,哪些不是。答案會依不同的網站而有所不同。然而,您能運作的最小模組清單通常會包含 mod_mime
、mod_dir
和 mod_log_config
。當然,`mod_log_config` 是可選的,因為您可以執行沒有記錄檔案的網站。然而,這不建議這麼做。
某些模組,例如 mod_cache
和工人 MPM 的最新開發版本,使用 APR 的原子 API。此 API 提供可供輕量級執行緒同步使用的原子操作。
預設情況下,APR 會使用每個目標作業系統 / CPU 平台上可用的最有效率的機制來實作這些操作。例如,許多現代 CPU 都有一個在硬體中執行原子比較與交換 (CAS) 作業的指令。然而,在某些平台上,APR 預設會改用速度較慢、基於互斥鎖的原子 API 實作,以確保與缺乏此類指令的舊 CPU 型號相容。如果您要為這些平台之一建置 Apache,且您計畫只在較新的 CPU 上執行,您可以在建置時設定 Apache,並使用 `--enable-nonportable-atomics` 選項選擇一個較快的原子實作
./buildconf
./configure --with-mpm=worker --enable-nonportable-atomics=yes
以下平台適用 `--enable-nonportable-atomics` 選項
--enable-nonportable-atomics
進行組態,APR 會產生使用 486 opcode 以進行快速硬體比對和交換的程式碼。這將帶來更高效能的原子運算,但產生的可執行檔將只能在 486 及更新版本的晶片上執行(無法在 386 上執行)。如果你包含 mod_status
,且你在建置及執行 Apache 時設定 ExtendedStatus On
,那麼在每個要求中,Apache 會針對 gettimeofday(2)
(或視你的作業系統而定的 times(2)
)執行兩次呼叫,以及(在 1.3 版之前)針對 time(2)
執行多次額外呼叫。這些動作都是為了讓狀態報告包含時間指示。為獲得最高的效能,請設定 ExtendedStatus off
(這是預設值)。
本小節尚未針對 Apache HTTP Server 2.x 版本中所做的變更進行完全更新。部分資訊可能仍然相關,但請小心使用。
以下討論 Unix socket API 的一個缺點。假設你的網路伺服器使用多個 Listen
陳述式在多個埠口或多個位址上進行監聽。為了測試每個 socket 以查看是否有準備好的連線,Apache 會使用 select(2)
。select(2)
指示 socket 中有零或至少一個正在等待的連線。Apache 的模型包括多個子處理序,所有閒置的子處理序都會同時測試新的連線。一個簡陋的實作類似以下(此範例不符合程式碼,為了教學目的而構思):
for (;;) { for (;;) { fd_set accept_fds; FD_ZERO (&accept_fds); for (i = first_socket; i <= last_socket; ++i) { FD_SET (i, &accept_fds); } rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL); if (rc < 1) continue; new_connection = -1; for (i = first_socket; i <= last_socket; ++i) { if (FD_ISSET (i, &accept_fds)) { new_connection = accept (i, NULL, NULL); if (new_connection != -1) break; } } if (new_connection != -1) break; } process_the(new_connection); }
但這個簡陋的實作有嚴重的飢餓問題。請回想,多個子處理序會同時執行這個迴圈,因此當它們在要求間時,多個子處理序會在 select
中封鎖。當任何 socket 上出現單一要求時,所有那些封鎖的子處理序會喚醒並從 select
返回。(喚醒的子處理序數量會視作業系統及時間問題而有所不同。)然後,它們都會進入迴圈,並嘗試「驗收」連線。但只有一個會成功(假設依然只有一個連線準備好)。其餘會在 accept
中封鎖。這有效地將那些子處理序鎖定為只為該一個 socket 提供要求,而不是其他 socket,而且它們會在那裏陷住,直到該 socket 上出現足夠新的要求將它們全部喚醒為止。這個飢餓問題最早記錄在 PR#467 中。至少有兩種解決方案。
解決方法之一是建立非封鎖式 soket。如此一來,accept
將不會封鎖子程序,且子程序能繼續立即執行。但這會造成浪費 CPU 時間。假設你在 select
中有 10 個閒置子程序,並且有一個連線進入。那這 9 個子程序會喚醒、嘗試以 accept
接受連線、失敗,並迴圈返回 select
,最終一事無成。同時,在這些子程序回到 select
之前,沒有任何子程序會處理其他 soket 產生的要求。總體而言,除非你有與閒置子程序數量一樣多的閒置 CPU (在多處理器環境中)(這不太可能發生),否則這項解決方法似乎不太有效。
另一項解決方法,即 Apache 使用的方法,是讓內部迴圈串列化進入。迴圈如下所示 (差異已標示)
for (;;) { accept_mutex_on (); for (;;) { fd_set accept_fds; FD_ZERO (&accept_fds); for (i = first_socket; i <= last_socket; ++i) { FD_SET (i, &accept_fds); } rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL); if (rc < 1) continue; new_connection = -1; for (i = first_socket; i <= last_socket; ++i) { if (FD_ISSET (i, &accept_fds)) { new_connection = accept (i, NULL, NULL); if (new_connection != -1) break; } } if (new_connection != -1) break; } accept_mutex_off (); process the new_connection; }
函數 accept_mutex_on
和 accept_mutex_off
實作一個互斥鎖。任何時間內只有一個子程序可以擁有互斥鎖。有數種方法可實作這些互斥鎖。可以在 src/conf.h
(1.3 之前) 或 src/include/ap_config.h
(1.3 或更新版本) 中定義選擇項目。有些架構沒有任何鎖定選項,在這些架構上,不建議使用多個 Listen
指令。
Mutex
指令可用於在執行階段變更 mpm-accept
互斥鎖的互斥鎖實作。有關不同互斥鎖實作方式的特殊考量,會記錄在該指令中。
曾考慮過但未實作的另一項解決方法,是讓迴圈部分串列化,也就是說,讓一定數量的程序進入。這僅適用於多處理器環境,在多處理器環境中,有可能會有多個子程序同時執行,而串列化實際上不會完全利用頻寬。這是未來可以探討的領域,但優先順序較低,因為高並行網路伺服器並非常態。
如果您想要達到最高效能,理想狀況下,你應執行沒有多個 Listen
陳述式的伺服器。但請繼續往下讀。
上述說明完全適用於多 soket 伺服器,但單一 soket 伺服器呢?理論上,他們不應遭遇這些問題,因為所有的子程序都可以在 accept(2)
中封鎖,直到連線進入,而且不會造成執行緒飢餓。在實際狀況中,這幾乎會出現與上述非封鎖式解決方法中所探討的「旋轉」行為相同的情形。在大部分 TCP 堆疊的實作方式中,當單一連線進入時,內核實際上會喚醒所有封鎖在 accept
中的程序。其中一個程序取得連線並返回使用者空間。其餘的程序則會在內核中旋轉,並在發現沒有連線時重新進入睡眠狀態。這個旋轉的過程會隱藏在使用空間的程式碼中,但它確實存在。這可能造成與非封鎖式解決方案套用在多 soket 案例中時相同的高負載浪費行為。
因為這個原因,我們發現,如果我們序列化,甚至連單一 socket 案例都可以得到許多架構更「精良」的表現。因此,這實際上幾乎在所有案例中都是預設值。在 Linux (雙處理器 Pentium 專業版 166 w/128Mb RAM 上的 2.0.30) 上的粗糙實驗顯示,序列化單一 socket 案例會造成每秒要求在未序列化單一 socket 上減少不到 3%。但未序列化的單一 socket 在每個要求上顯示額外的 100ms 延遲。這個延遲在長途路徑線上很可能是失效的,只會在 LAN 上發生問題。如果你想要覆寫單一 socket 序列化,你可以定義 SINGLE_LISTEN_UNSERIALIZED_ACCEPT
,然後單一 socket 伺服器將完全不執行序列化。
正如 draft-ietf-http-connection-00.txt 第 8 節所討論的,為了讓 HTTP 伺服器能可靠地執行通訊協定,它需要獨立地關閉每個溝通方向。(請記得 TCP 連線是雙向的。每個一半都獨立於另一個。)
當此功能新增至 Apache,它因為短視而造成各種版本 Unix 上出現一系列的問題。TCP 規格沒有指出 FIN_WAIT_2
狀態具有逾時,但沒有禁止它。在沒有逾時的系統上,Apache 1.2 導致許多 socket 永久停留在 FIN_WAIT_2
狀態。在許多案例中,可以透過簡單升級至供應商提供的最新 TCP/IP 修補程式來避免此問題。在供應商從未發布修補程式的案例中 (即 SunOS4 -- 雖然持有原始程式碼授權的人員可以自行修補),我們決定停用此功能。
有兩種方式可以達成此目的。第一個是 socket 選項 SO_LINGER
。但是事與願違,這從未在大多數 TCP/IP 堆疊中正確執行。甚至在使用正確執行的堆疊 (即 Linux 2.0.31) 上,此方法證明比下一個解決方案更昂貴 (CPU 時間)。
Apache 主要在稱為 lingering_close
的函數 (位於 http_main.c
中) 中執行此函數。該函數大致如下
void lingering_close (int s) { char junk_buffer[2048]; /* shutdown the sending side */ shutdown (s, 1); signal (SIGALRM, lingering_death); alarm (30); for (;;) { select (s for reading, 2 second timeout); if (error) break; if (s is ready for reading) { if (read (s, junk_buffer, sizeof (junk_buffer)) <= 0) { break; } /* just toss away whatever is here */ } } close (s); }
這自然會在連線結束時增加一些花費,但如果要可靠地執行,這是必要的。隨著 HTTP/1.1 變得更普遍,並且所有連線都一致持續,此花費將攤銷在更多要求上。如果你想玩火並停用此功能,你可以定義 NO_LINGCLOSE
,但這完全不建議這麼做。特別是,由於 HTTP/1.1 管線持續連線開始使用,lingering_close
絕對必要 (而且 管線連線較快,因此您需要支援它。)
Apache 的父代及子代會透過稱為計分板的東西互相溝通。理想情況下,這部分應以共用記憶體來執行。對於我們有權限使用或已收到詳細埠的作業系統,通常會透過共用記憶體來執行。其餘的則預設使用磁碟上的檔案。磁碟上的檔案不僅速度慢,而且不可靠(且功能較少)。檢閱架構的 src/main/conf.h
檔案,並尋找 USE_MMAP_SCOREBOARD
或 USE_SHMGET_SCOREBOARD
。定義這兩個其中一個(及其伴隨的 HAVE_MMAP
與 HAVE_SHMGET
)便能啟用已提供的共用記憶體程式碼。如果你的系統有其他類型的共用記憶體,歡迎編輯檔案 src/main/http_main.c
,並加入必要掛接,以供 Apache 使用。(請也再寄還我們一個修補程式。)
如果你無意使用動態載入模組(如果你正在閱讀這篇文章且針對伺服器的每一點效能進行調整,則你可能不會),那麼你應該在建置你的伺服器時加入 -DDYNAMIC_MODULE_LIMIT=0
。這將節省僅分配給支援動態載入模組的 RAM。
以下是使用 Solaris 8 上的 worker MPM,執行 Apache 2.0.38 產生的系統呼叫追蹤。此追蹤使用
truss -l -p httpd_child_pid.
-l
選項會指示 truss 記錄呼叫每個系統呼叫的 LWP (輕量級程序,即 Solaris 型式的核心層級執行緒) 的 ID。
其他系統可能有不同的系統呼叫追蹤工具程式,例如 strace
、ktrace
或 par
。它們都會產生類似的輸出。
在此追蹤中,客戶端已從 httpd 要求一個 10 KB 的靜態檔案。非靜態要求或要求內容協商的追蹤看起來截然不同(某些案例中非常醜陋)。
/67: accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...) /67: accept(3, 0x00200BEC, 0x00200C0C, 1) = 9
在此追蹤中,聆聽器執行緒正在 LWP #67 中執行。
accept(2)
串行化。在此特定平台上,除非 worker MPM 聆聽多個埠,否則它預設會使用非串行化 accept。/65: lwp_park(0x00000000, 0) = 0 /67: lwp_unpark(65, 1) = 0
在接受連線後,聆聽器執行緒會喚醒一個工作執行緒來執行要求處理。在此追蹤中,處理要求的工作執行緒會對應到 LWP #65。
/65: getsockname(9, 0x00200BA4, 0x00200BC4, 1) = 0
為了執行虛擬主機,Apache 需要知道用於接受連線的區域 socket 位址。在許多情況下可以避免此呼叫(例如,當沒有虛擬主機,或使用不具有萬用字元位址的 Listen
指令時)。但目前尚未進行任何努力來執行這些最佳化。
/65: brk(0x002170E8) = 0 /65: brk(0x002190E8) = 0
brk(2)
呼叫會配置堆疊記憶體。在系統呼叫追蹤中,這種情況很少見,因為 httpd 在大部分的處理中使用自訂記憶體配置器(apr_pool
及 apr_bucket_alloc
)。在此追蹤中,httpd 剛啟動,因此必須呼叫 malloc(3)
來取得必要的原始記憶體區塊,以便建立自訂記憶體配置器。
/65: fcntl(9, F_GETFL, 0x00000000) = 2 /65: fstat64(9, 0xFAF7B818) = 0 /65: getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B910, 2190656) = 0 /65: fstat64(9, 0xFAF7B818) = 0 /65: getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B914, 2190656) = 0 /65: setsockopt(9, 65535, 8192, 0xFAF7B918, 4, 2190656) = 0 /65: fcntl(9, F_SETFL, 0x00000082) = 0
接下來,工作執行緒將與用戶端的連線(檔案描述符 9)設定為非封鎖模式。setsockopt(2)
與 getsockopt(2)
呼叫是 Solaris 的 libc 處理 socket 上的 fcntl(2)
之副作用。
/65: read(9, " G E T / 1 0 k . h t m".., 8000) = 97
工作執行緒從用戶端讀取要求。
/65: stat("/var/httpd/apache/httpd-8999/htdocs/10k.html", 0xFAF7B978) = 0 /65: open("/var/httpd/apache/httpd-8999/htdocs/10k.html", O_RDONLY) = 10
此 httpd 已設定為 Options FollowSymLinks
和 AllowOverride None
。因此,它不需要lstat(2)
通往請求檔案路徑中的每個目錄,也不需要檢查 .htaccess
檔案。它只需呼叫 stat(2)
來驗證檔案,確認符合下列條件:1) 檔案存在,2) 檔案是常規檔案,而不是目錄。
/65: sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C) = 10269
在此範例中,httpd 能夠使用單一 sendfilev(2)
系統呼叫送出 HTTP 回應標頭與請求的檔案。不同作業系統的 Sendfile 語意不同。某些其他系統需要執行 write(2)
或 writev(2)
呼叫才能在呼叫 sendfile(2)
之前送出標頭。
/65: write(4, " 1 2 7 . 0 . 0 . 1 - ".., 78) = 78
此 write(2)
呼叫會在存取記錄中記錄請求。請注意,此追蹤中缺少 time(2)
呼叫。與 Apache 1.3 不同,Apache 2.x 使用 gettimeofday(3)
來查詢時間。在某些作業系統(例如 Linux 或 Solaris)中,gettimeofday
具有最佳化實作,其必須具有的時間成本低於一般的系統呼叫。
/65: shutdown(9, 1, 1) = 0 /65: poll(0xFAF7B980, 1, 2000) = 1 /65: read(9, 0xFAF7BC20, 512) = 0 /65: close(9) = 0
工作執行緒執行連線的延遲關閉。
/65: close(10) = 0 /65: lwp_park(0x00000000, 0) (sleeping...)
最後,工作執行緒關閉它剛剛傳遞的檔案,並封鎖執行緒,直到聆聽器將另一個連線指定給它。
/67: accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)
與此同時,聆聽器執行緒只要將這個連線分配給工作執行緒,就能夠接受另一個連線(需符合工作人員 MPM 中某個流控邏輯,如果所有可用的工作人員都忙碌,則會限制聆聽器)。儘管此追蹤中看不出來,但下一個 accept(2)
能夠(而且通常會在高負載條件下)與工作執行緒處理剛接受的連線並行發生。