題 Web服務器如何“監聽”IP地址,中斷或輪詢?


我正在嘗試了解Web服務器的較低細節。 我想知道服務器,比如說Apache,是在不斷輪詢新請求,還是由某種中斷系統工作。如果是中斷,什麼引發中斷,是網卡驅動程序嗎?


86
2017-11-09 20:39


起源


要理解的關鍵字是 “服務器”。在服務器 - 客戶端模型(與主 - 從模型)中的服務器 等待 來自客戶的請求。這些要求是 事件 需要維修。 Web服務器是一個應用程序。您的問題將應用程序SW與硬件術語(例如中斷和NIC)相結合,而不是將相關概念保留在同一個抽象層。 NIC驅動程序實際上可能有時使用輪詢,例如當存在大量數據包時,Linux NAPI驅動程序會回歸到輪詢。但這與事件處理應用程序SW無關。 - sawdust
@sawdust非常有趣。問題實際上是為了理解SW和HW過程之間的聯繫 - user2202911
它與命令行(和其他GUI)程序監聽鍵盤的方式非常相似。特別是在一個窗口系統中,你有一個內核從鍵盤設備接收數據並將其交給窗口管理器的步驟,窗口管理器識別具有焦點的窗口並將數據提供給該窗口。 - G-Man
@ G-Man:我理論,是的。實際上,大多數打字員不會輸入1 Gbit / s,這證明有兩種不同的體系結構。一個乾淨,靈活和緩慢,一個笨拙但高速。 - MSalters


答案:


簡短的回答是:某種中斷系統。本質上,它們使用阻塞I / O,這意味著它們在等待新數據時會休眠(阻塞)。

  1. 服務器創建一個偵聽套接字,然後在等待新連接時阻塞。在此期間,內核將進程置於 可中斷的睡眠 陳述並運行其他進程。這一點非常重要:不斷進行流程輪詢會浪費CPU。內核能夠通過阻止進程來更有效地使用系統資源,直到有工作要做。

  2. 當新數據到達網絡時,網卡會發出中斷。

  3. 看到來自網卡的中斷,內核通過網卡驅動程序從網卡讀取新數據並將其存儲到內存中。 (這必須快速完成,通常在中斷處理程序中處理。)

  4. 內核處理新到達的數據並將其與套接字相關聯。在該套接字上阻塞的進程將被標記為runnable,這意味著它現在可以運行。它不一定立即運行(內核可能決定仍然運行其他進程)。

  5. 閒暇時,內核將喚醒被阻止的Web服務器進程。 (因為它現在可以運行了。)

  6. Web服務器進程繼續執行,就好像沒有時間過去一樣。它的阻塞系統調用返回並處理任何新數據。然後......轉到第1步。


181
2017-11-09 21:14



+1用於清晰描述內核與Web服務器進程。 - Russell Borogove
我無法相信一些複雜的東西,因為這可以如此清晰簡單地總結,但你做到了。 +1 - Brandon
+1很棒的答案。此外,對於現代NIC,操作系統和驅動程序,2到3之間的步驟可能會更複雜一些。例如,用 NAPI 在Linux上,在中斷上下文中實際上不接收數據包。相反,內核說“好的網卡,我知道你有數據。退出煩我(禁用中斷源),我很快就會回來搶這個包 和 任何可能在我之前到達的後續數據包。“ - Jonathon Reinhart
輕微的挑剔:沒有必要阻止。一旦服務器進程創建了一個偵聽套接字,內核就會接受該端口上的SYN,即使你沒有被阻塞 accept。它們(幸運的是,或者它會完全糟糕!)獨立,異步運行的任務。當連接進入時,它們被放入隊列中 accept 拉他們。只有沒有,它才會阻止。 - Damon
“從網卡讀取新數據並將其存儲到內存中。(這必須快速完成,通常在中斷處理程序中處理。)”是不是通過直接內存訪問完成了? - Siyuan Ren


有很多“較低”的細節。

首先,考慮內核有一個進程列表,並且在任何給定時間,其中一些進程正在運行,而另一些則沒有。內核允許每個正在運行的進程佔用一些CPU時間,然後中斷它並移動到下一個。如果沒有可運行的進程,那麼內​​核可能會發出類似的指令 HLT 到CPU暫停CPU直到有硬件中斷。

服務器中的某個地方是 系統調用 那說“給我一些事可做”。有兩種方法可以做到這一點。在Apache的情況下,它調用 accept 在Apache先前已打開的套接字上,可能正在偵聽端口80.內核維護一個連接嘗試的隊列,並且每次都添加到該隊列 TCP SYN 收到了。內核如何知道接收到TCP SYN取決於設備驅動程序;對於許多NIC,當接收到網絡數據時,可能存在硬件中斷。

accept 要求內核在下一次連接啟動時返回給我。如果隊列不為空,那麼 accept 馬上回來如果隊列為空,則從正在運行的進程列表中刪除進程(Apache)。稍後啟動連接時,將恢復該過程。這稱為“阻塞”,因為調用它的過程, accept()看起來像一個函數在它有結果之前不會返回,這可能是從現在開始的一段時間。在此期間,該過程無法執行任何其他操作。

一旦 accept 返回,Apache知道有人正在嘗試啟動連接。然後它打電話 叉子 在兩個相同的進程中拆分Apache進程。其中一個進程繼續處理HTTP請求,其他調用 accept 再次獲得下一個連接。因此,總有一個主過程除了調用之外什麼都不做 accept 並生成子流程,然後每個請求都有一個子流程。

這是一個簡化:可以使用線程而不是進程來執行此操作,並且也可以 fork 事先有一個工作進程準備好在收到請求時去,從而減少啟動開銷。根據Apache的配置方式,它可以執行上述任一操作。

這是第一個如何做到的廣泛類別,它被稱為 阻止IO 因為系統調用了 accept 和 read 和 write 對套接字進行操作將暫停進程,直到它們有東西返回。

另一種廣泛的做法是稱為非阻塞或基於事件或 異步IO。這是通過系統調用實現的 select 要么 epoll。這些都做同樣的事情:你給他們一個套接字列表(或者一般來說,文件描述符)以及你想用它們做什麼,然後內核阻塞直到它準備好做其中一件事。

使用此模型,您可以告訴內核(使用 epoll),“告訴我當端口80上有新連接或新數據讀取我打開的這些9471其他連接中的任何一個”。 epoll 阻止,直到其中一件事準備好,然後你去做。然後你再說一遍。系統調用如 accept 和 read 和 write 從不阻止,部分是因為每當你打電話給他們時, epoll 只是告訴你他們已經準備好所以沒有理由阻止,也因為當你打開套接字或文件時你指定你想要它們處於非阻塞模式,所以這些調用將失敗 EWOULDBLOCK 而不是阻止。

該模型的優點是您只需要一個流程。這意味著您不必為每個請求分配堆棧和內核結構。 Nginx的 和 HAProxy的 使用這個模型,這是他們在類似硬件上處理比Apache更多連接的一個重要原因。


7
2017-11-12 19:38