自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux 網(wǎng)絡(luò)發(fā)包流程

網(wǎng)絡(luò) Linux
當(dāng) Linux 要發(fā)送一個數(shù)據(jù)包的時候,這個包是怎么從應(yīng)用程序再到 Linux 的內(nèi)核最后由網(wǎng)卡發(fā)送出去的呢?

哈嘍大家好,我是咸魚。

之前咸魚在《Linux 網(wǎng)絡(luò)收包流程》一文中介紹了 Linux 是如何實現(xiàn)網(wǎng)絡(luò)接收數(shù)據(jù)包的。

簡單回顧一下:

  • 數(shù)據(jù)到達網(wǎng)卡之后,網(wǎng)卡通過 DMA 將數(shù)據(jù)放到內(nèi)存分配好的一塊 ring buffer 中,然后觸發(fā)硬中斷
  • CPU 收到硬中斷之后簡單的處理了一下(分配 skb_buffer),然后觸發(fā)軟中斷
  • 軟中斷進程 ksoftirqd 執(zhí)行一系列操作(例如把數(shù)據(jù)幀從 ring ruffer上取下來)然后將數(shù)據(jù)送到三層協(xié)議棧中
  • 在三層協(xié)議棧中數(shù)據(jù)被進一步處理發(fā)送到四層協(xié)議棧
  • 在四層協(xié)議棧中,數(shù)據(jù)會從內(nèi)核拷貝到用戶空間,供應(yīng)用程序讀取
  • 最后被處在應(yīng)用層的應(yīng)用程序去讀取

當(dāng) Linux 要發(fā)送一個數(shù)據(jù)包的時候,這個包是怎么從應(yīng)用程序再到 Linux 的內(nèi)核最后由網(wǎng)卡發(fā)送出去的呢?

那么今天咸魚將會為大家介紹 Linux 是如何實現(xiàn)網(wǎng)絡(luò)發(fā)送數(shù)據(jù)包。

發(fā)包流程

假設(shè)我們的網(wǎng)卡已經(jīng)啟動好(分配和初始化 RingBuffer) 且 server 和 client 已經(jīng)建立好 socket。

這里需要注意的是,網(wǎng)卡在啟動過程中申請分配的 RingBuffer 是有兩個:

  • igb_tx_buffer 數(shù)組:這個數(shù)組是內(nèi)核使用的,用于存儲要發(fā)送的數(shù)據(jù)包描述信息,通過 vzalloc申請的
  • e1000_adv_tx_desc 數(shù)組:這個數(shù)組是網(wǎng)卡硬件使用的,用于存儲要發(fā)送的數(shù)據(jù)包,網(wǎng)卡硬件可以通過 DMA 直接訪問這塊內(nèi)存,通過 dma_alloc_coherent分配

igb_tx_buffer 數(shù)組中的每個元素都有一個指針指向 e1000_adv_tx_desc;


這樣內(nèi)核就可以把要發(fā)送的數(shù)據(jù)填充到 e1000_adv_tx_desc 數(shù)組上;


然后網(wǎng)卡硬件會直接從 e1000_adv_tx_desc 數(shù)組中讀取實際數(shù)據(jù),并將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上。

拷貝到內(nèi)核

socket 系統(tǒng)調(diào)用將數(shù)據(jù)拷貝到內(nèi)核

應(yīng)用程序首先通過 socket 提供的接口實現(xiàn)系統(tǒng)調(diào)用,我們在用戶態(tài)使用的 send 函數(shù)和 sendto 函數(shù)其實都是 sendto 系統(tǒng)調(diào)用實現(xiàn)的,send/sendto函數(shù) 只是為了用戶方便,封裝出來的一個更易于調(diào)用的方式而已。

在 sendto 系統(tǒng)調(diào)用內(nèi)部,首先 sockfd_lookup_light 函數(shù)會查找與給定文件描述符(fd)關(guān)聯(lián)的 socket;

接著調(diào)用 sock_sendmsg 函數(shù)(sock_sendmsg ==> __sock_sendmsg ==> __sock_sendmsg_nosec);

其中 sock->ops->sendmsg 函數(shù)實際執(zhí)行的是 inet_sendmsg 協(xié)議棧函數(shù):

這時候內(nèi)核會去找 socket 上對應(yīng)的具體協(xié)議發(fā)送函數(shù)。

以 TCP 為例,具體協(xié)議發(fā)送函數(shù)為 tcp_sendmsg:

tcp_sendmsg 會去申請一個內(nèi)核態(tài)內(nèi)存 skb(sk_buff) ,然后掛到發(fā)送隊列上(發(fā)送隊列是由 skb 組成的一個鏈表):

接著把用戶待發(fā)送的數(shù)據(jù)拷貝到 skb 中,拷貝之后會觸發(fā)【發(fā)送】操作,這里說的發(fā)送是指在當(dāng)前上下文中,待發(fā)送數(shù)據(jù)從 socket 層發(fā)送到傳輸層。

需要注意的是,這時候不一定開始真正發(fā)送,因為還要進行一些條件判斷(比如說發(fā)送隊列中的數(shù)據(jù)已經(jīng)超過了窗口大小的一半)。

只有滿足了條件才能夠發(fā)送,如果沒有滿足條件這次系統(tǒng)調(diào)用就可能直接返回了。

網(wǎng)絡(luò)協(xié)議棧處理

傳輸層處理

接著數(shù)據(jù)來到了傳輸層,傳輸層主要看 tcp_write_xmit 函數(shù),這個函數(shù)處理了傳輸層的擁塞控制、滑動窗口相關(guān)的工作,該函數(shù)會根據(jù)發(fā)送窗口和最大段大小等因素計算出本次發(fā)送的數(shù)據(jù)大小,然后將數(shù)據(jù)封裝成 TCP 段并發(fā)送出去,如果滿足窗口要求,設(shè)置 TCP 頭然后將數(shù)據(jù)傳到更低的網(wǎng)絡(luò)層進行處理。

在傳輸層中,內(nèi)核主要做了兩件事:

(1) 復(fù)制一份數(shù)據(jù)(skb)

為什么要復(fù)制一份出來呢?因為網(wǎng)卡發(fā)送完成之后,skb 會被釋放掉,但 TCP 協(xié)議是支持丟失重傳的,所以在收到對方的 ACK 之前必須要備份一個 skb 去為重傳做準(zhǔn)備。

實際上一開始發(fā)送的是 skb 的拷貝版,收到了對方的 ACK 之后系統(tǒng)才會把真正的 skb 刪除掉。

(2) 封裝 TCP 頭

系統(tǒng)會根據(jù)實際情況添加 TCP 頭封裝成 TCP 段。

這里需要知道的是:每個 skb 內(nèi)部包含了網(wǎng)絡(luò)協(xié)議中的所有頭部信息,例如 MAC 頭、IP 頭、TCP/UDP 頭等,在設(shè)置這些頭部時,內(nèi)核會通過調(diào)整指針的位置來填充相應(yīng)的字段,而不是頻繁申請和拷貝內(nèi)存。

比如說在設(shè)置 TCP 頭的時候,只是把指針指向 skb 的合適位置。后面再設(shè)置 IP 頭的時候,再把指針挪一挪就行。

這種方式利用了 skb 數(shù)據(jù)結(jié)構(gòu)的鏈表特性可以避免內(nèi)存分配和數(shù)據(jù)拷貝所帶來的性能開銷,從而提高數(shù)據(jù)傳輸?shù)男省?/p>

網(wǎng)絡(luò)層處理

數(shù)據(jù)離開了傳輸層之后,就來到了網(wǎng)絡(luò)層。

網(wǎng)絡(luò)層主要做下面的事情:

(1) 路由項查找:

根據(jù)目標(biāo) IP 地址查找路由表,確定數(shù)據(jù)包的下一跳(ip_queue_xmit 函數(shù))。

(2) IP 頭設(shè)置:

根據(jù)路由表查找的結(jié)果,設(shè)置 IP 頭中的源和目標(biāo) IP 地址、TTL(生存時間)、IP 協(xié)議等字段。

(3) netfilter 過濾:

netfilter 是 Linux 內(nèi)核中的一個框架,用于實現(xiàn)數(shù)據(jù)包的過濾和修改。

在網(wǎng)絡(luò)層,netfilter 可以用于對數(shù)據(jù)包進行過濾、NAT(網(wǎng)絡(luò)地址轉(zhuǎn)換)等操作。

(4) skb 切分:

如果數(shù)據(jù)包的大小超過了 MTU(最大傳輸單元),需要將數(shù)據(jù)包進行切分成多個片段,以適應(yīng)網(wǎng)絡(luò)傳輸,每個片段會被封裝成單獨的 skb。

數(shù)據(jù)鏈路層處理

當(dāng)數(shù)據(jù)來到了數(shù)據(jù)鏈路層之后,會有兩個子系統(tǒng)協(xié)同工作,確保數(shù)據(jù)包在發(fā)送和接收過程中能夠正確地對數(shù)據(jù)進行封裝、解析和傳輸。

(1) 鄰居子系統(tǒng)

管理和維護主機或路由器與其它設(shè)備之間的鄰居關(guān)系,鄰居子系統(tǒng)里會發(fā)送 arp 請求找鄰居,然后把鄰居信息存在鄰居緩存表里,用于存儲目標(biāo)主機的 MAC 地址。

當(dāng)需要發(fā)送數(shù)據(jù)包到某個目標(biāo)主機時,數(shù)據(jù)鏈路層會首先查詢鄰居緩存表,以獲取目標(biāo)主機的 MAC 地址,從而正確地封裝數(shù)據(jù)包(封裝 MAC 頭)。

(2) 網(wǎng)絡(luò)設(shè)備子系統(tǒng)

網(wǎng)絡(luò)設(shè)備子系統(tǒng)負責(zé)處理與物理網(wǎng)絡(luò)接口相關(guān)的操作,包括數(shù)據(jù)包的封裝和發(fā)送,以及從物理接口接收數(shù)據(jù)包并進行解析。

網(wǎng)絡(luò)設(shè)備子系統(tǒng)不但處理數(shù)據(jù)包的格式轉(zhuǎn)換,如在以太網(wǎng)中添加幀頭和幀尾,以及從幀中提取數(shù)據(jù),還負責(zé)處理硬件相關(guān)的操作,如發(fā)送和接收數(shù)據(jù)包的時鐘同步、物理層錯誤檢測等。

(3) 到達網(wǎng)卡發(fā)送隊列

接著網(wǎng)絡(luò)設(shè)備子系統(tǒng)會選擇一個合適的網(wǎng)卡發(fā)送隊列并把 skb 添加到隊列中(繞過軟中斷處理程序),然后,內(nèi)核會調(diào)用網(wǎng)卡驅(qū)動的入口函數(shù) dev_hard_start_xmit 來觸發(fā)數(shù)據(jù)包的發(fā)送。

在一些情況下,鄰居子系統(tǒng)還會將 skb 數(shù)據(jù)包添加到軟中斷隊列(softnet_data)上,并觸發(fā)軟中斷(NET_TX_SOFTIRQ),這個過程是為了將 skb 數(shù)據(jù)包交給軟中斷處理程序進行進一步處理和發(fā)送。軟中斷處理程序會負責(zé)實際的數(shù)據(jù)包發(fā)送,這就是為什么一般服務(wù)器上查看 /proc/softirqs,一般 NET_RX 都要比 NET_TX 大的多的原因之一。

即對于收包來說,都是要經(jīng)過 NET_RX 軟中斷;而對于發(fā)包來說,只有某些情況下才觸發(fā) NET_TX 軟中斷:

數(shù)據(jù)發(fā)送

驅(qū)動程序從發(fā)送隊列中讀取 skb 的描述信息,將其掛到 RingBuffer 上(前面提到的igb_tx_buffer 數(shù)組)

接著將 skb 的描述信息映射到網(wǎng)卡可訪問的內(nèi)存 DMA 區(qū)域中(前面提到的e1000_adv_tx_desc 數(shù)組)

網(wǎng)卡會直接從 e1000_adv_tx_desc 數(shù)組中根據(jù)描述信息讀取實際數(shù)據(jù)并將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)。這樣就完成了數(shù)據(jù)包的發(fā)送過程

收尾工作

當(dāng)數(shù)據(jù)發(fā)送完成后,網(wǎng)卡設(shè)備會觸發(fā)一個中斷(NET_RX_SOFTIRQ),這個中斷通常稱為“發(fā)送完成中斷”或者“發(fā)送隊列清理中斷”;

這個中斷的主要作用是執(zhí)行發(fā)送完成的清理工作,包括釋放之前為數(shù)據(jù)包分配的內(nèi)存,即釋放 skb 內(nèi)存和 RingBuffer 內(nèi)存;

最后,當(dāng)收到這個 TCP 報文的 ACK 應(yīng)答時,傳輸層就會釋放原始的 skb(前面有講到發(fā)送的其實是 skb 的拷貝版)。

可以看到,當(dāng)數(shù)據(jù)發(fā)送完成以后,通過硬中斷的方式來通知驅(qū)動發(fā)送完畢,而這個中斷類型是 NET_RX_SOFTIRQ。

前面我們講到過網(wǎng)卡收到一個網(wǎng)絡(luò)包的時候,會觸發(fā) NET_RX_SOFTIRQ中斷去告訴 CPU 有數(shù)據(jù)要處理,也就是說,無論是網(wǎng)卡接收一個網(wǎng)絡(luò)包還是發(fā)送網(wǎng)絡(luò)包結(jié)束之后,觸發(fā)的都是  NET_RX_SOFTIRQ。

總結(jié)

最后總結(jié)一下在 Linux 系統(tǒng)中發(fā)送網(wǎng)絡(luò)數(shù)據(jù)包的流程:

最后總結(jié)一下在 Linux 系統(tǒng)中發(fā)送網(wǎng)絡(luò)數(shù)據(jù)包的流程:

(1) 應(yīng)用程序通過 socket 提供的接口進行系統(tǒng)調(diào)用,將數(shù)據(jù)從用戶態(tài)拷貝到內(nèi)核態(tài)的 socket 緩沖區(qū)中

(2) 網(wǎng)絡(luò)協(xié)議棧從 socket 緩沖區(qū)中拿取數(shù)據(jù),并按照 TCP/IP 協(xié)議棧從上到下逐層處理

  • 傳輸層處理:以 TCP 為例,在傳輸層中會復(fù)制一份數(shù)據(jù)(為了丟失重傳),然后為數(shù)據(jù)封裝 TCP 頭
  • 網(wǎng)絡(luò)層處理:選取路由(確認下一跳的 IP)、填充 IP 頭、netfilter 過濾、對超過 MTU 大小的數(shù)據(jù)包進行分片等操作
  • 鄰居子系統(tǒng)和網(wǎng)絡(luò)設(shè)備子系統(tǒng)處理:在這里數(shù)據(jù)會被進一步處理和封裝,然后被添加到網(wǎng)卡的發(fā)送隊列中

(3) 驅(qū)動程序從發(fā)送隊列中讀取 skb 的描述信息然后掛在 RingBuffer 上,接著將 skb 的描述信息映射到網(wǎng)卡可訪問的內(nèi)存 DMA 區(qū)域中

(4) 網(wǎng)卡將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)

(5) 當(dāng)數(shù)據(jù)發(fā)送完成后觸發(fā)硬中斷,釋放  skb 內(nèi)存和 RingBuffer 內(nèi)存

責(zé)任編輯:趙寧寧 來源: 咸魚運維雜談
相關(guān)推薦

2025-02-26 07:59:47

2009-08-28 10:45:57

2017-05-31 17:09:52

LinuxShell命令

2018-11-04 07:48:32

GPON網(wǎng)絡(luò)故障網(wǎng)絡(luò)

2009-07-27 14:13:15

2014-03-10 10:06:40

WebSocket.Net

2017-08-24 11:54:43

Linux日志定時輪循機制

2011-12-19 09:36:33

JavaJDKubuntu

2010-06-06 15:54:54

Windows Pho

2019-02-26 08:51:34

網(wǎng)絡(luò)安全惡意軟件網(wǎng)絡(luò)攻擊

2023-11-01 10:43:31

Linux高性能網(wǎng)絡(luò)編程

2009-05-14 09:12:31

微軟AzurePHP

2021-10-15 08:51:09

Linux內(nèi)存 Kmalloc

2021-02-09 08:23:02

Linux操作系統(tǒng)

2011-06-28 13:38:15

Arm linux QT

2011-09-02 14:17:16

Windows AzuAndroid

2010-07-05 09:19:37

微軟云計算PHP

2016-12-27 19:10:38

Linux命令啟動流程

2020-06-05 13:37:17

網(wǎng)絡(luò)安全技術(shù)

2015-10-23 10:27:06

網(wǎng)絡(luò)爬蟲相似矩陣流程
點贊
收藏

51CTO技術(shù)棧公眾號