Linux 網絡收包流程
哈嘍大家好,我是鹹魚
我們在跟別人網上聊天的時候,有沒有想過你發送的信息是怎麼傳到對方的電腦上的
又或者我們在上網衝浪的時候,有沒有想過 HTML 頁面是怎麼顯示在我們的電腦屏幕上的
無論是我們跟別人聊天還是上網衝浪,其實都依靠於計算機網絡這項技術
計算機網絡是指將多臺計算機通過通信設備和傳輸介質連接在一起,使得它們之間能夠相互通信、資源共享和協同工作
而計算機之間是通過數據包來實現信息傳輸和信息交換的,數據包是計算機網絡中傳輸數據的基本單位
今天鹹魚將以 Linux 爲例來給大家介紹一下 Linux 是如何實現網絡接收數據包的
網絡協議棧 & 網絡子系統
在正文開始之前,我們先來了解一下 Linux 中的網絡協議模型和網絡子系統
- 網絡協議模型(網絡協議棧)
在 Linux 中,Linux 網絡協議棧分成了五層
其中:
-
應用層提供 socket 接口來供用戶進程訪問內核空間的網絡協議棧
-
傳輸層、網絡層協議由 Linux 內核網絡協議棧實現
-
鏈路層協議靠網卡驅動來實現
-
物理層協議由硬件網卡實現
網絡子系統是 Linux 內核中的一部分,由多個模塊和驅動程序組成,它負責管理和控制系統的網絡功能以實現網絡通信
通過 Linux 網絡子系統(網絡架構)來實現上述網絡協議模型
其中
-
System call interface:爲應用程序獲取內核的網絡系統提供了接口,例如 socket
-
Protocol agnostic interface:爲和各種傳輸層協議的網絡交互提供的一層公共接口
-
Network protocals:對各種傳輸層協議的實現,如 TCP、UDP、IP 等
-
Device agnostic interface:爲各種底層網絡設備抽象出的公共接口,與各種網絡設備驅動連接在一起
-
Device drivers:與各種網絡設備交互的驅動
收包流程
當 Linux 接收一個數據包的時候,這個包是怎麼經過 Linux 的內核從而被應用程序拿到的呢?
- 到達網卡(NIC,Network Interface Card)
首先數據包到達網卡之後,網卡會校驗接收到的數據包中的目的 MAC 地址是不是自己的 MAC 地址,如果不是的話通常就會丟棄掉
這種只接受發送給自己的數據包(其餘的扔掉)的工作模式稱爲非混雜模式(Non-Promiscuous Mode)
混雜模式(Promiscuous Mode)則是網卡會接收通過網絡傳輸的所有數據包,而不僅僅是發送給它自己的數據包
非混雜模式是網卡默認的工作模式,可以儘可能的保護網絡安全和減少網絡負載
網卡在校驗完 MAC 地址之後還會校驗數據幀(Data Frame)中校驗字段 FCS 來一次確保接收到的數據包是正確的
- 網卡硬件緩衝區 ——> 系統內存(ring buffer)
當網卡接收到數據包時,它將數據包的內容存儲在硬件緩衝區中,然後通過 DMA 將接收到的數據從硬件緩衝區傳輸到系統內存中的指定位置,這個位置通常是一個環形緩衝區( ring buffer)
DMA(直接內存訪問,Direct Memory Access) DMA 是一種數據傳輸技術,允許外設(如網卡、硬盤控制器、顯卡等)直接訪問計算機內存,而無需經過 CPU 通過 DMA 可以大大提高數據傳輸的效率,減輕 CPU 的負擔
- 觸發硬中斷
當網卡將數據包 DMA 到用於接收的環形緩衝區(rx_ring)之後,就會觸發一個硬中斷來告訴 CPU 數據包收到了
什麼時候會觸發一個硬中斷,可以通過下面的參數來進行配置:
-
rx-usecs:當過這麼長時間過後,一箇中斷就會被產生
-
rx-frames:當累計接收到這麼多個數據幀後,一箇中斷就會被產生
上面的參數配置可以通過下面的命令來查看
# 以 CentOS 7 爲例
ethtool -c <網卡名稱>
當 ring buffer 滿了之後,新來的數據包將給丟棄
ifconfig 查看網卡的時候,可以裏面有個 overruns,表示因爲環形隊列滿而被丟棄的包
CPU 收到硬中斷之後就會停止手中的活,保存上下文,然後去調用網卡驅動註冊的硬中斷處理函數
爲數據包分配 skb_buff
,並將接收到的數據拷貝到 skb_buff
緩衝區中
當一個數據包經過了網卡引起中斷之後,每一個包都會在內存中分配一塊區域,稱爲
sk_buff
(套接字緩存,socket buffer)
sk_buff
是 Linux 網絡的一個核心數據結構
- 觸發軟中斷
網卡的硬中斷處理函數處理完之後驅動先 disable 硬中斷,然後 enable 軟中斷
ps:待 ring buffer 中的所有數據包被處理完成後,enable 網卡的硬中斷,這樣下次網卡再收到數據的時候就會通知 CPU
內核負責軟中斷進程 ksoftirqd
發現有軟中斷請求到來,進行下面的一些操作
# 查看軟中斷進程
[root@localhost ~]# ps -ef | grep ksoftirqd
調用 net_rx_action
函數
它會通過 poll
函數去 rx_ring
中拿數據幀,獲取的時候順便把 rx_ring
上的數據給刪除
static void net_rx_action(struct softirq_action *h)
{
struct softnet_data *sd = &__get_cpu_var(softnet_data);
unsigned long time_limit = jiffies + 2;
int budget = netdev_budget;
void *have;
local_irq_disable();
while (!list_empty(&sd->poll_list)) {
......
n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);
work = 0;
if (test_bit(NAPI_STATE_SCHED, &n->state)) {
work = n->poll(n, weight);
trace_napi_poll(n);
}
budget -= work;
}
}
除此之外,poll
函數會把 ring buffer
中的數據包轉換成內核網絡模塊能夠識別的 skb 格式(即 socket kernel buffer
)
socket kernel buffer (skb) 是 Linux 內核網絡棧處理網絡包(packets)所使用的 buffer,它的類型是 sk_buffer
3、最後進入 netif _receive_skb
處理流程,它是數據鏈路層接收數據幀的最後一關
根據註冊在全局數組 ptype_all
和 ptype_base
裏的網絡層數據幀類型去調用第三層協議的接收函數處理
例如對於 ip 包來講,就會進入到
ip_rcv
;如果是 arp 包的話,會進入到arp_rcv
- 到達網絡層(以 IP 協議爲例)
IP 層的入口函數在 ip_rcv
函數,調用 ip_rcv
函數進入三層協議棧
首先會對數據包進行各種檢查(檢查 IP Header),然後調用 netfilter
中的鉤子函數:NF_INET_PRE_ROUTING
netfilter:是 Linux 內核中進行數據包過濾,連接跟蹤(Connect Track),網絡地址轉換(NAT)等功能的主要實現框架
該框架在網絡協議棧處理數據包的關鍵流程中定義了一系列鉤子點(Hook 點),並在這些鉤子點中註冊一系列函數對數據包進行處理
這些註冊在鉤子點的函數即爲設置在網絡協議棧內的數據包通行策略,也就意味着,這些函數可以決定內核是接受還是丟棄某個數據包
NF_INET_PRE_ROUTING
會根據預設的規則對數據包進行判斷並根據判斷結果做相關的處理(修改或者丟棄數據包)
處理完成後,數據包交由 ip_rcv_finish
處理,該函數根據路由判決結果,決定數據包是交由本機上層應用處理,還是需要進行轉發
如果是交由本機處理,則會交由 ip_local_deliver
本地上交流程;如果需要轉發,則交由 ip_forward
函數走轉發流程
- 到達傳輸層(以 TCP 爲例)
傳輸層 TCP 處理入口在 tcp_v4_rcv
函數,首先檢查數據包的 TCP 頭部等信息,確保數據包的完整性和正確性
然後去查找該數據包對應的已經打開的 socket ,如果找不到匹配的 socket,表示該數據包不屬於任何一個已建立的連接,因此該數據包會被丟棄
如果找到了匹配的 socket,TCP 會進一步檢查該 socket 和連接的狀態,如果狀態正常,TCP 會將數據包從內核傳輸到用戶空間,放入 socket 的接收緩衝區(socket receive buffer)
- 應用層獲取數據
當數據包到達操作系統內核的傳輸層時,應用程序可以從套接字的接收緩衝區(socket receive buffer)中讀取數據包
一般有兩種方式讀取數據,一種是 recvfrom
函數阻塞在那裏等着數據來,這種情況下當 socket 收到通知後,recvfrom
就會被喚醒,然後讀取接收隊列的數據
另一種是通過 epoll 或者 select 監聽相應的 socket,當收到通知後,再調用 recvfrom
函數去讀取接收隊列的數據
總結
網絡模塊可以說是 Linux 內核中最複雜的模塊了
看起來一個簡簡單單的收包過程就涉及到許多內核組件之間的交互,如網卡驅動、協議棧,內核 ksoftirqd 線程等
鹹魚原本打算把收包和發包的流程都寫上的,但是光是寫收包流程就就要了我半條命了 QAQ
等下次有機會把發包的流程也寫一下
總結一下 Linux 網絡收包流程:
-
數據到達網卡之後,網卡通過 DMA 將數據放到內存分配好的一塊
ring buffer
中,然後觸發硬中斷 -
CPU 收到硬中斷之後簡單的處理了一下(分配
skb_buffer
),然後觸發軟中斷 -
軟中斷進程
ksoftirqd
執行一系列操作(例如把數據幀從ring ruffer
上取下來)然後將數據送到三層協議棧中 -
在三層協議棧中數據被進一步處理發送到四層協議棧
-
在四層協議棧中,數據會從內核拷貝到用戶空間,供應用程序讀取
-
最後被處在應用層的應用程序去讀取
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/6c0ZZ3ZZZ_ocIqH2iey1lw