哈嘍大家好,我是咸魚
我們在跟別人網上聊天的時候,有沒有想過你發送的信息是怎么傳到對方的電腦上的
又或者我們在上網沖浪的時候,有沒有想過 html 頁面是怎么顯示在我們的電腦屏幕上的
無論是我們跟別人聊天還是上網沖浪,其實都依靠于計算機網絡這項技術
計算機網絡是指將多臺計算機通過通信設備和傳輸介質連接在一起,使得它們之間能夠相互通信、資源共享和協同工作
而計算機之間是通過數據包來實現信息傳輸和信息交換的,數據包是計算機網絡中傳輸數據的基本單位
今天咸魚將以 linux 為例來給大家介紹一下 Linux 是如何實現網絡接收數據包的
在正文開始之前,我們先來了解一下 Linux 中的網絡協議模型和網絡子系統
-
網絡協議模型(網絡協議棧)
在 Linux 中,Linux 網絡協議棧分成了五層
其中:
-
應用層提供 socket 接口來供用戶進程訪問內核空間的網絡協議棧
-
傳輸層、網絡層協議由 Linux 內核網絡協議棧實現
-
鏈路層協議靠網卡驅動來實現
-
物理層協議由硬件網卡實現
-
網絡子系統(網絡架構)
網絡子系統是 Linux 內核中的一部分,由多個模塊和驅動程序組成,它負責管理和控制系統的網絡功能以實現網絡通信
通過 Linux 網絡子系統(網絡架構)來實現上述網絡協議模型
其中
-
System call interface:為應用程序獲取內核的網絡系統提供了接口,例如 socket
-
Protocol agnostic interface:為和各種傳輸層協議的網絡交互提供的一層公共接口
-
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
上取下來)然后將數據送到三層協議棧中 -
在三層協議棧中數據被進一步處理發送到四層協議棧
-
在四層協議棧中,數據會從內核拷貝到用戶空間,供應用程序讀取
-
最后被處在應用層的應用程序去讀取