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