1. 墊話
本文乃《Illustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data》一文的翻譯,是系列文章的第二篇。
2. 前言
本文為《[譯 1] linux 網(wǎng)絡(luò)棧監(jiān)控及調(diào)優(yōu):數(shù)據(jù)接收》一文添加圖解,旨在幫助讀者更清晰地了解 linux 網(wǎng)絡(luò)棧。 在 linux 網(wǎng)絡(luò)棧的監(jiān)控及調(diào)優(yōu)上沒有捷徑可言,如果你想做有效的調(diào)優(yōu),就必須搞清楚各個系統(tǒng)之間是怎么交互的。上一篇文章因為篇幅的緣故,可能會讓讀者難以從大圖上了解各系統(tǒng)之間是怎么拉通的,希望本文可以。
3. 開始
本文的圖解旨在描繪 linux 網(wǎng)絡(luò)棧工作原理的大圖,會忽略大量細節(jié)。如果想要全面了解,還是推薦閱讀上一篇文章,其中包含了網(wǎng)絡(luò)棧的各方面細節(jié)。本文的圖解,旨在幫助讀者建立起 high level 的內(nèi)核子系統(tǒng)交互的思維模型。 咱們從初始化工作開始,這是理解 packet 處理的必要前提。
4. 初始化
圖 1 網(wǎng)絡(luò)設(shè)備在 packet 到達并需要處理時,通常會觸發(fā)一個 IRQ。IRQ 處理函數(shù)是在很高的優(yōu)先級下執(zhí)行的,一般會阻塞其他 IRQs 的觸發(fā)(譯者注:often blocks additional IRQs from being generated。個人覺得原文這里并不準確,中斷上下文中關(guān)中往往只是讓 CPU 不響應(yīng)中斷,而不是讓其他設(shè)備直接不發(fā)出中斷)。故而,設(shè)備驅(qū)動中的 IRQ 處理函數(shù)必須越快越好,并將比較耗時的工作挪到中斷上下文之外去執(zhí)行,這就是為啥會有軟中斷系統(tǒng)。 linux 內(nèi)核軟中斷系統(tǒng)支持在設(shè)備驅(qū)動的中斷上下文之外處理工作。網(wǎng)絡(luò)設(shè)備場景下,軟中斷系統(tǒng)用作處理 incoming packets。內(nèi)核在 boot 階段做軟中斷系統(tǒng)的初始化。 圖 1 對應(yīng)前文“軟中斷”一節(jié),展示的是軟中斷系統(tǒng)及其 per-CPU 內(nèi)核線程的初始化。 軟中斷系統(tǒng)的初始化流程如下:
spawn_ksoftirqd(kernel/softirq.c)調(diào)用 smpboot_register_percpu_thread(kernel/smpboot.c)創(chuàng)建軟中斷內(nèi)核線程(每個 CPU 一個)。如代碼所示,run_ksoftirqd 作為 smp_hotplug_thread 的 thread_fn,會在一個 loop 中被執(zhí)行。
ksoftirqd 線程會在 run_ksoftirqd 中運行其 processing loop。
隨后,創(chuàng)建 softnet_data 數(shù)據(jù)結(jié)構(gòu)(前文“struct softnet_data 數(shù)據(jù)結(jié)構(gòu)初始化”一節(jié)),每個 CPU 一個。此數(shù)據(jù)結(jié)構(gòu)包含在網(wǎng)絡(luò)數(shù)據(jù)處理時所需要的重要信息。另外還有一個 poll_list,下文會說。設(shè)備驅(qū)動調(diào)用 napi_schedule 或其他 NAPI APIs,將 NAPI poll 數(shù)據(jù)結(jié)構(gòu)添加至 poll_list 上。
net_dev_init 調(diào)用 open_softirq 向軟中斷系統(tǒng)注冊 NET_RX_SOFTIRQ 軟中斷,被注冊的軟中斷處理函數(shù)是 net_rx_action(前文“軟中斷處理函數(shù)初始化”一節(jié))。軟中斷內(nèi)核線程會調(diào)用此函數(shù)來處理 packets。
圖 1 中的第 5 - 8 步與數(shù)據(jù)的到達有關(guān),下一節(jié)會說。
5. 數(shù)據(jù)到達
圖 2 數(shù)據(jù)從網(wǎng)絡(luò)上來了(前文“數(shù)據(jù)到達”一節(jié))! 網(wǎng)絡(luò)數(shù)據(jù)到達 NIC 時,NIC 會通過 DMA 將 packet 數(shù)據(jù)寫入 RAM。igb 網(wǎng)絡(luò)驅(qū)動會在 RAM 中構(gòu)建一個 ring buffer,其指向接收到的 packets。值得注意的是,有些 NIC 支持 "multiqueue",這些 NIC 可以使用多個處理器來處理 incoming 網(wǎng)絡(luò)數(shù)據(jù)(前文“準備從網(wǎng)絡(luò)接收數(shù)據(jù)”一節(jié))。簡化起見,圖 2 只畫了一個 ring buffer,但取決于 NIC 以及硬件配置,你的系統(tǒng)可能使用的是多個隊列。 下面流程的細節(jié)參閱前文“數(shù)據(jù)到達”一節(jié)。 我們來過一遍數(shù)據(jù)接收流程:
數(shù)據(jù)從網(wǎng)絡(luò)到達 NIC。
NIC 通過 DMA 將網(wǎng)絡(luò)數(shù)據(jù)寫入 RAM。
NIC 觸發(fā)一個 IRQ。
執(zhí)行設(shè)備驅(qū)動注冊的 IRQ 處理函數(shù)(前文“中斷處理”一節(jié))。
NIC 清除 IRQ,這樣新 packet 到來時可以繼續(xù)觸發(fā) IRQs。
調(diào)用 napi_schedule 拉起 NAPI 軟中斷 poll loop(前文“NAPI 與 napi_schedule”一節(jié))。
napi_schedule 的調(diào)用觸發(fā)了 圖 1 中的 5 - 8 步。如后面所見,NAPI 軟中斷 poll loop 拉起的原理,就是翻轉(zhuǎn)一個 bit 域,并向 poll_list 上添加一個數(shù)據(jù)結(jié)構(gòu)。napi_schedule 沒干什么其他事,這就是驅(qū)動將處理工作轉(zhuǎn)交給軟中斷系統(tǒng)的原理。 繼續(xù)分析 圖 1,對照圖中相應(yīng)的數(shù)字:
驅(qū)動調(diào)用 napi_schedule 將驅(qū)動的 NAPI poll 數(shù)據(jù)結(jié)構(gòu)添加至當前 CPU 的 poll_list 上。
軟中斷 pending bit 會被置上,如此該 CPU 上的 ksoftirqd 線程知曉有 packets 需要處理。
執(zhí)行 run_ksoftirqd 函數(shù)(在 ksoftirqd 內(nèi)核線程的 loop 中執(zhí)行)。
調(diào)用 __do_softirq 檢查是否有 pending 的 bit 域,以此確認是否有 pending 的軟中斷,進而調(diào)用 pending 軟中斷的處理函數(shù):net_rx_action,該函數(shù)干了所有的 incoming 網(wǎng)絡(luò)數(shù)據(jù)處理的臟活。
需要注意的是,軟中斷內(nèi)核線程執(zhí)行的是 net_rx_action,而不是設(shè)備驅(qū)動的 IRQ 處理函數(shù)。
6. 網(wǎng)絡(luò)數(shù)據(jù)處理的開始
圖 3 至此開始數(shù)據(jù)的處理。net_rx_action 函數(shù)(在 ksoftirqd 內(nèi)核線程中調(diào)用)會執(zhí)行當前 CPU poll_list 上注冊的 NAPI poll 數(shù)據(jù)結(jié)構(gòu)。poll 數(shù)據(jù)結(jié)構(gòu)的注冊一般有兩種情況:
設(shè)備驅(qū)動調(diào)用 napi_schedule。
Receive Packet Steering 場景(前文“Receive Packet Steering(RPS)”一節(jié))下使用 Inter-processor Interrupt。
我們將從 poll_list 獲取驅(qū)動 NAPI 數(shù)據(jù)結(jié)構(gòu)的流程串起來(下一節(jié)會講 RPS 是怎么通過 IPIs 注冊 NAPI 數(shù)據(jù)結(jié)構(gòu)的)。 圖 3 流程在前文有詳細拆解過,總結(jié)一下就是:
net_rx_action poll 檢查 NAPI poll list 中的 NAPI 數(shù)據(jù)結(jié)構(gòu)。
校驗 budget 及消耗的時間,以確保軟中斷不會霸占 CPU。
調(diào)用注冊的 poll 函數(shù)(前文“NAPI poll 函數(shù)及權(quán)重”一節(jié))。本文場景下,igb 驅(qū)動注冊的是 igb_poll 函數(shù)。
驅(qū)動的 poll 函數(shù)收取 RAM ring buffer 中的 packets(前文“NAPI poll”一節(jié))。
packets 進一步給到 napi_gro_receive,其可能會進一步被 Generic Receive Offloading 處理(前文“Generic Receive Offloading(GRO)”一節(jié))。
packets 要么被 GRO 處理,這樣整個調(diào)用鏈也就結(jié)束了;要么 packets 通過 net_receive_skb 進一步給到上層協(xié)議棧。
下面會講 net_receive_skb 是怎么實現(xiàn) Receive Packet Steering,也就是在多個 CPUs 之間分發(fā) packet 的。
7. 網(wǎng)絡(luò)數(shù)據(jù)的進一步處理
圖 4 從 netif_receive_skb 開始繼續(xù)網(wǎng)絡(luò)數(shù)據(jù)的處理,數(shù)據(jù)的具體路徑取決于是否使能了 Receive Packet Steering(RPS)。一個“開箱即用”的 linux 內(nèi)核(譯者注:意思就是通用的發(fā)行版)默認是不使能 RPS 的,如果你想用 RPS,就必須顯式地配置及使能之。 RPS 禁能的情況下(前文“禁能 RPS 場景(默認配置)”一節(jié)),對應(yīng) 圖 4 中的如下數(shù)字:
1. netif_receive_skb 將數(shù)據(jù)給到 __netif_receive_core。
6. __netif_receive_core 將數(shù)據(jù)給到系統(tǒng)中可能存在的 taps(前文“packet tap 投遞”一節(jié))(比如 PCAP,https://www.tcpdump.org/manpages/pcap.3pcap.html)。
7. __netif_receive_core 將數(shù)據(jù)給到協(xié)議層注冊的 handlers(前文“協(xié)議層投遞”一節(jié))。大多數(shù)情況下,此 handler 是 IPv4 協(xié)議棧所注冊的 ip_rcv 函數(shù)。
RPS 使能的情況下(前文“使能 RPS 場景”一節(jié)):
netif_receive_skb 將數(shù)據(jù)給到 enqueue_to_backlog。
packets 會被送到 per-CPU 的輸入隊列上以待后續(xù)處理。
將遠端 CPU 的 NAPI 數(shù)據(jù)結(jié)構(gòu)添加至該遠端 CPU 的 poll_list 上,并向該 CPU 發(fā)一個 IPI,進而喚醒遠端 CPU 上的軟中斷內(nèi)核線程(如果其并未在運行的話)。
當遠端 CPU 上的 ksoftirqd 內(nèi)核線程運行起來后,其處理模式與上一節(jié)中的相同,不同之處是,注冊進來的 poll 函數(shù)是 process_backlog,該函數(shù)會從當前(譯者注:本 CPU) CPU 的輸入隊列收取 packets。
packets 進一步給到 __net_receive_skb_core。
__net_receive_skb_core 將數(shù)據(jù)給到系統(tǒng)中可能存在的 taps(前文“packet tap 投遞”一節(jié))(比如 PCAP)。
__net_receive_skb_core 將數(shù)據(jù)給到協(xié)議層注冊的 handlers(前文“協(xié)議層投遞”一節(jié))。大多數(shù)情況下,此 handler 是 IPv4 協(xié)議棧所注冊的 ip_rcv 函數(shù)。
8. 協(xié)議棧及用戶 sockets
數(shù)據(jù)接下來要走的路徑是:協(xié)議棧、netfilter、Berkeley Packet Filters,最終到達用戶 socket。 雖然代碼路徑挺長的,但是邏輯是直白清晰的。 網(wǎng)絡(luò)數(shù)據(jù)路徑更詳細地拆解見前文“協(xié)議層注冊”一節(jié)。下面是 high level 的簡要總結(jié):
IPv4 協(xié)議層通過 ip_rcv 收取 packets。
會做 netfilter 以及路由優(yōu)化。
目標是本機的數(shù)據(jù),會進一步給到更 high level 的協(xié)議層,比如 UDP。
UDP 協(xié)議層通過 udp_rcv 收取 packets,并通過 udp_queue_rcv_skb 及 sock_queue_rcv 將數(shù)據(jù)入隊到用戶 socket 的接收 buffer 中。在入隊到接收 buffer 之前,會做 Berkeley Packet Filters。
值得注意的是,netfilter 在這個過程中會被調(diào)用多次,具體位置參考前文的詳細拆解(前文“協(xié)議層注冊”一節(jié))。
9. 總結(jié)
linux 網(wǎng)絡(luò)棧極其復(fù)雜,涉及到的系統(tǒng)很多。如果要監(jiān)控這個復(fù)雜的系統(tǒng)就必須得搞清楚這些系統(tǒng)之間是怎么交互的,以及對某一系統(tǒng)配置的調(diào)整,會如何影響到其他系統(tǒng)。本文作為前文的補充,試圖把這些問題梳理的更清晰易懂一些。
審核編輯:黃飛
?
評論
查看更多