本系列文章1-4,來源于陳莉君老師公眾號“Linux
內核之旅”
1. 前言
本文首先從宏觀上概述了
數據包發(fā)送的流程,接著分析了協議層注冊進內核以及被socket的過程,最后介紹了通過 socket 發(fā)送
網絡數據的過程。
從宏觀上看,一個數據包從用戶程序到達硬件網卡的整個過程如下:
- 使用系統(tǒng)調用(如?
sendto
,sendmsg
?等)寫數據 - 數據穿過socket 子系統(tǒng),進入socket 協議族(protocol family)系統(tǒng)
- 協議族處理:數據穿過協議層,這一過程(在許多情況下)會將數據(data)轉換成數據包(packet)
- 數據穿過路由層,這會涉及路由緩存和 ARP 緩存的更新;如果目的 MAC 不在 ARP 緩存表中,將觸發(fā)一次 ARP 廣播來查找 MAC 地址
- 穿過協議層,packet 到達設備無關層(device agnostic layer)
- 使用 XPS(如果啟用)或散列函數選擇發(fā)送隊列
- 調用網卡驅動的發(fā)送函數
- 數據傳送到網卡的?
qdisc
(queue discipline,排隊規(guī)則) - qdisc 會直接發(fā)送數據(如果可以),或者將其放到隊列,下次觸發(fā)NET_TX 類型軟中斷(softirq)的時候再發(fā)送
- 數據從 qdisc 傳送給驅動程序
- 驅動程序創(chuàng)建所需的DMA 映射,以便網卡從 RAM 讀取數據
- 驅動向網卡發(fā)送信號,通知數據可以發(fā)送了
- 網卡從 RAM 中獲取數據并發(fā)送
- 發(fā)送完成后,設備觸發(fā)一個硬中斷(IRQ),表示發(fā)送完成
- 硬中斷處理函數被喚醒執(zhí)行。對許多設備來說,這會觸發(fā) NET_RX 類型的軟中斷,然后 NAPI poll 循環(huán)開始收包
- poll 函數會調用驅動程序的相應函數,解除 DMA 映射,釋放數據
3. 協議層注冊
協議層分析我們將關注 IP 和 UDP 層,其他協議層可參考這個過程。我們首先來看協議族是如何注冊到內核,并被 socket 子系統(tǒng)使用的。當用戶程序像下面這樣創(chuàng)建 UDP socket 時會發(fā)生什么?
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
簡單來說,內核會去查找由 UDP 協議棧導出的一組函數(其中包括用于發(fā)送和接收網絡數據的函數),并賦給 socket 的相應字段。準確理解這個過程需要查看?
AF_INET
?地址族的代碼。內核初始化的很早階段就執(zhí)行了?
inet_init
?函數,這個函數會注冊?
AF_INET
?協議族 ,以及該協議族內的各協議棧(TCP,UDP,ICMP 和 RAW),并調用初始化函數使協議棧準備好處理
網絡數據。
inet_init
?定義在net/ipv4/af_inet.c 。
AF_INET
?協議族導出一個包含?
create
?方法的?
struct net_proto_family
?類型實例。當從用戶程序創(chuàng)建 socket 時,
內核會調用此方法:
static const struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
inet_create
?根據傳遞的 socket 參數,在已注冊的協議中查找對應的協議:
/* Look for the requested type/protocol pair. */
lookup_protocol:
err = -ESOCKTNOSUPPORT;
rcu_read_lock();
list_for_each_entry_rcu(answer,