Linux 網路發包流程

来源:https://www.cnblogs.com/edisonfish/archive/2023/08/17/17637507.html
-Advertisement-
Play Games

哈嘍大家好,我是鹹魚 之前鹹魚在《[Linux 網路收包流程](https://mp.weixin.qq.com/s?__biz=MzkzNzI1MzE2Mw==&mid=2247486122&idx=1&sn=df659a7458028772c9595e98d5cefbc1&chksm=c2930 ...


哈嘍大家好,我是鹹魚

之前鹹魚在《Linux 網路收包流程》一文中介紹了 Linux 是如何實現網路接收數據包的

簡單回顧一下:

  • 數據到達網卡之後,網卡通過 DMA 將數據放到記憶體分配好的一塊 ring buffer 中,然後觸發硬中斷
  • CPU 收到硬中斷之後簡單的處理了一下(分配 skb_buffer),然後觸發軟中斷
  • 軟中斷進程 ksoftirqd 執行一系列操作(例如把數據幀從 ring ruffer上取下來)然後將數據送到三層協議棧中
  • 在三層協議棧中數據被進一步處理髮送到四層協議棧
  • 在四層協議棧中,數據會從內核拷貝到用戶空間,供應用程式讀取
  • 最後被處在應用層的應用程式去讀取

當 Linux 要發送一個數據包的時候,這個包是怎麼從應用程式再到 Linux 的內核最後由網卡發送出去的呢?

那麼今天鹹魚將會為大家介紹 Linux 是如何實現網路發送數據包

發包流程

假設我們的網卡已經啟動好(分配和初始化 RingBuffer) 且 server 和 client 已經建立好 socket

這裡需要註意的是,網卡在啟動過程中申請分配的 RingBuffer 是有兩個:

  • igb_tx_buffer 數組:這個數組是內核使用的,用於存儲要發送的數據包描述信息,通過 vzalloc 申請的
  • e1000_adv_tx_desc 數組:這個數組是網卡硬體使用的,用於存儲要發送的數據包,網卡硬體可以通過 DMA 直接訪問這塊記憶體,通過 dma_alloc_coherent分配

igb_tx_buffer 數組中的每個元素都有一個指針指向 e1000_adv_tx_desc

這樣內核就可以把要發送的數據填充到 e1000_adv_tx_desc 數組上

然後網卡硬體會直接從 e1000_adv_tx_desc 數組中讀取實際數據,並將數據發送到網路上

image

拷貝到內核

  • socket 系統調用將數據拷貝到內核

應用程式首先通過 socket 提供的介面實現系統調用

我們在用戶態使用的 send 函數和 sendto 函數其實都是 sendto 系統調用實現的

send/sendto 函數 只是為了用戶方便,封裝出來的一個更易於調用的方式而已

/* sendto 系統調用 省略了一些代碼 */
SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
		unsigned int, flags, struct sockaddr __user *, addr,
		int, addr_len)
{
	...
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	...
	err = sock_sendmsg(sock, &msg, len);
	...	
}

sendto 系統調用內部,首先 sockfd_lookup_light 函數會查找與給定文件描述符(fd)關聯的 socket

接著調用 sock_sendmsg 函數(sock_sendmsg ==> __sock_sendmsg ==> __sock_sendmsg_nosec

其中 sock->ops->sendmsg 函數實際執行的是 inet_sendmsg 協議棧函數

/*
__sock_sendmsg_nosec 函數

iocb:指向與 I/O 操作相關的結構體 kiocb
sock: 指向要執行發送操作的套接字結構體
msg: 指向存儲要發送數據的消息頭結構體 msghdr
size: 要發送的數據大小

*/
static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,
				       struct msghdr *msg, size_t size)
{
	...
	return sock->ops->sendmsg(iocb, sock, msg, size);
}

這時候內核會去找 socket 上對應的具體協議發送函數

以 TCP 為例,具體協議發送函數為 tcp_sendmsg
image

tcp_sendmsg 會去申請一個內核態記憶體 skb(sk_buff) ,然後掛到發送隊列上(發送隊列是由 skb 組成的一個鏈表)
image

接著把用戶待發送的數據拷貝到 skb 中,拷貝之後會觸發【發送】操作

這裡說的發送是指在當前上下文中,待發送數據從 socket 層發送到傳輸層

需要註意的是,這時候不一定開始真正發送,因為還要進行一些條件判斷(比如說發送隊列中的數據已經超過了視窗大小的一半)

只有滿足了條件才能夠發送,如果沒有滿足條件這次系統調用就可能直接返回了

網路協議棧處理

  • 傳輸層處理

接著數據來到了傳輸層

傳輸層主要看 tcp_write_xmit 函數,這個函數處理了傳輸層的擁塞控制、滑動視窗相關的工作

該函數會根據發送視窗和最大段大小等因素計算出本次發送的數據大小,然後將數據封裝成 TCP 段併發送出去

如果滿足視窗要求,設置 TCP 頭然後將數據傳到更低的網路層進行處理

在傳輸層中,內核主要做了兩件事:

  • 複製一份數據(skb)

為什麼要複製一份出來呢?因為網卡發送完成之後,skb 會被釋放掉,但 TCP 協議是支持丟失重傳的

所以在收到對方的 ACK 之前必須要備份一個 skb 去為重傳做準備

實際上一開始發送的是 skb 的拷貝版,收到了對方的 ACK 之後系統才會把真正的 skb 刪除掉

  • 封裝 TCP 頭

系統會根據實際情況添加 TCP 頭封裝成 TCP 段

這裡需要知道的是:每個 skb 內部包含了網路協議中的所有頭部信息,例如 MAC 頭、IP 頭、TCP/UDP 頭等

在設置這些頭部時,內核會通過調整指針的位置來填充相應的欄位,而不是頻繁申請和拷貝記憶體
image

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

這種方式利用了 skb 數據結構的鏈表特性可以避免記憶體分配和數據拷貝所帶來的性能開銷,從而提高數據傳輸的效率

  • 網路層處理

數據離開了傳輸層之後,就來到了網路層

網路層主要做下麵的事情:

  • 路由項查找:

根據目標 IP 地址查找路由表,確定數據包的下一跳( ip_queue_xmit 函數)

  • IP 頭設置:

根據路由表查找的結果,設置 IP 頭中的源和目標 IP 地址、TTL(生存時間)、IP 協議等欄位

  • netfilter 過濾:

netfilter 是 Linux 內核中的一個框架,用於實現數據包的過濾和修改

在網路層,netfilter 可以用於對數據包進行過濾、NAT(網路地址轉換)等操作

  • skb 切分:

如果數據包的大小超過了 MTU(最大傳輸單元),需要將數據包進行切分成多個片段,以適應網路傳輸,每個片段會被封裝成單獨的 skb

  • 數據鏈路層處理

當數據來到了數據鏈路層之後,會有兩個子系統協同工作,確保數據包在發送和接收過程中能夠正確地對數據進行封裝、解析和傳輸

  • 鄰居子系統

管理和維護主機或路由器與其它設備之間的鄰居關係

鄰居子系統里會發送 arp 請求找鄰居,然後把鄰居信息存在鄰居緩存表裡,用於存儲目標主機的 MAC 地址

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

  • 網路設備子系統

網路設備子系統負責處理與物理網路介面相關的操作,包括數據包的封裝和發送,以及從物理介面接收數據包併進行解析

網路設備子系統不但處理數據包的格式轉換,如在乙太網中添加幀頭和幀尾,以及從幀中提取數據

還負責處理硬體相關的操作,如發送和接收數據包的時鐘同步、物理層錯誤檢測等

  • 到達網卡發送隊列

接著網路設備子系統會選擇一個合適的網卡發送隊列並把 skb 添加到隊列中(繞過軟中斷處理程式)

然後,內核會調用網卡驅動的入口函數 dev_hard_start_xmit 來觸發數據包的發送

在一些情況下,鄰居子系統還會將 skb 數據包添加到軟中斷隊列(softnet_data)上,並觸發軟中斷(NET_TX_SOFTIRQ)

這個過程是為了將 skb 數據包交給軟中斷處理程式進行進一步處理和發送。軟中斷處理程式會負責實際的數據包發送

這就是為什麼一般伺服器上查看 /proc/softirqs,一般 NET_RX 都要比 NET_TX 大的多的原因之一

即對於收包來說,都是要經過 NET_RX 軟中斷;而對於發包來說,只有某些情況下才觸發 NET_TX 軟中斷

網卡驅動發送

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

接著將 skb 的描述信息映射到網卡可訪問的記憶體 DMA 區域中(前面提到的e1000_adv_tx_desc 數組)

網卡會直接從 e1000_adv_tx_desc 數組中根據描述信息讀取實際數據並將數據發送到網路。這樣就完成了數據包的發送過程

收尾工作

當數據發送完成後,網卡設備會觸發一個硬體中斷(NET_RX_SOFTIRQ),這個硬中斷通常稱為“發送完成中斷”或者“發送隊列清理中斷”

這個硬中斷的主要作用是執行發送完成的清理工作,包括釋放之前為數據包分配的記憶體,即釋放 skb 記憶體和 RingBuffer 記憶體

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

可以看到,當數據發送完成以後,通過硬中斷的方式來通知驅動發送完畢,而這個中斷類型是 NET_RX_SOFTIRQ

前面我們講到過網卡收到一個網路包的時候,會觸發 NET_RX_SOFTIRQ中斷去告訴 CPU 有數據要處理

也就是說,無論是網卡接收一個網路包還是發送網路包結束之後,觸發的都是 NET_RX_SOFTIRQ

總結

最後總結一下在 Linux 系統中發送網路數據包的流程:
image

  • 應用程式通過 socket 提供的介面進行系統調用,將數據從用戶態拷貝到內核態的 socket 緩衝區中
  • 網路協議棧從 socket 緩衝區中拿取數據,並按照 TCP/IP 協議棧從上到下逐層處理
    • 傳輸層處理:以 TCP 為例,在傳輸層中會複製一份數據(為了丟失重傳),然後為數據封裝 TCP 頭
    • 網路層處理:選取路由(確認下一跳的 IP)、填充 IP 頭、netfilter 過濾、對超過 MTU 大小的數據包進行分片等操作
    • 鄰居子系統和網路設備子系統處理:在這裡數據會被進一步處理和封裝,然後被添加到網卡的發送隊列中
  • 驅動程式從發送隊列中讀取 skb 的描述信息然後掛在 RingBuffer 上,接著將 skb 的描述信息映射到網卡可訪問的記憶體 DMA 區域中
  • 網卡將數據發送到網路
  • 當數據發送完成後觸發硬中斷,釋放 skb 記憶體和 RingBuffer 記憶體

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 將Markdown文件上傳到博客園 # 1.下載python 下載地址為:http://npm.taobao.org/mirrors/python/ 安裝為3.10.11版本 在cmd視窗輸入python,彈出以下視窗為安裝成功 ![image-20230816102551883](https: ...
  • 所謂**數據轉置**,就是是將原始數據表格沿著對角線翻折,使原來的行變成新的列,原來的列變成新的行,從而更方便地進行數據分析和處理。 `pandas`中`DataFrame`的轉置非常簡單,每個`DataFrame`對象都有一個`T`屬性,通過這個屬性就能得到轉置之後的`DataFrame`。下麵介 ...
  • Elasticsearch是一個分散式、RESTful風格的搜索和數據分析引擎,適用於各種數據類型,數字、文本、地理位置、結構化數據、非結構化數據; ...
  • ### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本篇內容並非資料庫相關的核心知識,而是對 ...
  • # UGUI的InputField(輸入框)組件的介紹及使用 ## 1. 什麼是UGUI的InputField組件? UGUI的InputField組件是Unity中的一個用戶界面組件,用於接收用戶的輸入。它可以用於創建文本輸入框、密碼輸入框等功能。 ## 2. UGUI的InputField組件的 ...
  • ## 一、什麼是結果過濾器? 結果過濾器(**ResultFilter**),是對執行的Action結果進行處理的一種AOP思想,適用於任何需要直接環繞 View 或格式化處理的邏輯。結果過濾器可以**替換或更改 Action 結果**。在 IActionResult 執行的前後執行,使用它能夠控制 ...
  • <div class="van-cell van-field"> <div class="van-cell__title van-field__label">頭像:</div> <div class="van-cell__value van-field__value"> <div class="va ...
  • Windows服務是在Windows操作系統上,以後臺形式運行的應用程式。最近在公司要求使用Windows服務作為消息隊列的消費端,所以自行研究了一下C#中Windows服務如何創建以及如何使用,以及部署的方式。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...