全面解析Linux網(wǎng)絡(luò)I/O:數(shù)據(jù)是如何“跑”起來的?
數(shù)字化時代,網(wǎng)絡(luò)已深度融入生活與工作,從日常上網(wǎng)、觀影到企業(yè)數(shù)據(jù)傳輸,都離不開網(wǎng)絡(luò)支持。而 Linux 內(nèi)核作為操作系統(tǒng)核心,在網(wǎng)絡(luò)數(shù)據(jù)處理中扮演關(guān)鍵角色,其核心功能之一便是管理網(wǎng)絡(luò)數(shù)據(jù)的輸入輸出。Linux 內(nèi)核因開源、高效、穩(wěn)定,被廣泛應(yīng)用于服務(wù)器、嵌入式設(shè)備、超級計算機等場景。
全球超 90% 的超級計算機運行 Linux 系統(tǒng),谷歌、亞馬遜等互聯(lián)網(wǎng)巨頭的服務(wù)器集群,也依賴其強大網(wǎng)絡(luò)處理能力,支撐海量數(shù)據(jù)傳輸與高并發(fā)請求。那么,Linux 內(nèi)核如何實現(xiàn)網(wǎng)絡(luò)數(shù)據(jù)的輸入輸出?本文將深入其源碼,解析網(wǎng)絡(luò)數(shù)據(jù)處理的技術(shù)原理,探究從接收、協(xié)議解析到發(fā)送的全過程,展現(xiàn) Linux 內(nèi)核在網(wǎng)絡(luò)領(lǐng)域的精妙設(shè)計。
Part1.Linux網(wǎng)絡(luò)棧架構(gòu)總覽
Linux 網(wǎng)絡(luò)棧采用分層架構(gòu),這種架構(gòu)設(shè)計就如同建造高樓,每一層都有其獨特的功能和職責(zé),層層協(xié)作,共同構(gòu)建起強大的網(wǎng)絡(luò)通信體系。從下往上,Linux 網(wǎng)絡(luò)棧主要分為鏈路層、網(wǎng)絡(luò)層、傳輸層和應(yīng)用層 ,各層緊密配合,實現(xiàn)網(wǎng)絡(luò)數(shù)據(jù)的高效傳輸。
圖片
1.1應(yīng)用層:用戶的交互界面
應(yīng)用層是用戶與網(wǎng)絡(luò)應(yīng)用程序進(jìn)行交互的界面,就像是各種商店,為用戶提供各種具體的網(wǎng)絡(luò)服務(wù)。它負(fù)責(zé)處理特定的應(yīng)用程序細(xì)節(jié),如 HTTP(Hypertext Transfer Protocol)協(xié)議用于網(wǎng)頁瀏覽、SMTP(Simple Mail Transfer Protocol)協(xié)議用于郵件發(fā)送、FTP 協(xié)議用于文件傳輸?shù)取?yīng)用層通過調(diào)用傳輸層提供的接口,實現(xiàn)數(shù)據(jù)的傳輸和交互。
例如,當(dāng)我們在瀏覽器中輸入網(wǎng)址并按下回車鍵后,瀏覽器會使用 HTTP 協(xié)議向服務(wù)器發(fā)送請求,服務(wù)器接收到請求后,會根據(jù)請求內(nèi)容返回相應(yīng)的網(wǎng)頁數(shù)據(jù),瀏覽器再將這些數(shù)據(jù)解析并顯示出來,供用戶瀏覽。
(1) Socket
應(yīng)用層的各種網(wǎng)絡(luò)應(yīng)用程序基本上都是通過 Linux Socket 編程接口來和內(nèi)核空間的網(wǎng)絡(luò)協(xié)議棧通信的。Linux Socket 是從 BSD Socket 發(fā)展而來的,它是 Linux 操作系統(tǒng)的重要組成部分之一,它是網(wǎng)絡(luò)應(yīng)用程序的基礎(chǔ)。從層次上來說,它位于應(yīng)用層,是操作系統(tǒng)為應(yīng)用程序員提供的 API,通過它,應(yīng)用程序可以訪問傳輸層協(xié)議。
- socket 位于傳輸層協(xié)議之上,屏蔽了不同網(wǎng)絡(luò)協(xié)議之間的差異
- socket 是網(wǎng)絡(luò)編程的入口,它提供了大量的系統(tǒng)調(diào)用,構(gòu)成了網(wǎng)絡(luò)程序的主體
- 在Linux系統(tǒng)中,socket 屬于文件系統(tǒng)的一部分,網(wǎng)絡(luò)通信可以被看作是對文件的讀取,使得我們對網(wǎng)絡(luò)的控制和對文件的控制一樣方便。
圖片
(2)應(yīng)用層處理流程
- 網(wǎng)絡(luò)應(yīng)用調(diào)用Socket API socket (int family, int type, int protocol) 創(chuàng)建一個 socket,該調(diào)用最終會調(diào)用 Linux system call socket() ,并最終調(diào)用 Linux Kernel 的 sock_create() 方法。該方法返回被創(chuàng)建好了的那個 socket 的 file descriptor。對于每一個 userspace 網(wǎng)絡(luò)應(yīng)用創(chuàng)建的 socket,在內(nèi)核中都有一個對應(yīng)的 struct socket和 struct sock。其中,struct sock 有三個隊列(queue),分別是 rx , tx 和 err,在 sock 結(jié)構(gòu)被初始化的時候,這些緩沖隊列也被初始化完成;在收據(jù)收發(fā)過程中,每個 queue 中保存要發(fā)送或者接受的每個 packet 對應(yīng)的 Linux 網(wǎng)絡(luò)棧 sk_buffer 數(shù)據(jù)結(jié)構(gòu)的實例 skb。
- 對于 TCP socket 來說,應(yīng)用調(diào)用 connect()API ,使得客戶端和服務(wù)器端通過該 socket 建立一個虛擬連接。在此過程中,TCP 協(xié)議棧通過三次握手會建立 TCP 連接。默認(rèn)地,該 API 會等到 TCP 握手完成連接建立后才返回。在建立連接的過程中的一個重要步驟是,確定雙方使用的 Maxium Segemet Size (MSS)。因為 UDP 是面向無連接的協(xié)議,因此它是不需要該步驟的。
- 應(yīng)用調(diào)用 Linux Socket 的 send 或者 write API 來發(fā)出一個 message 給接收端
- sock_sendmsg 被調(diào)用,它使用 socket descriptor 獲取 sock struct,創(chuàng)建 message header 和 socket control message
- _sock_sendmsg 被調(diào)用,根據(jù) socket 的協(xié)議類型,調(diào)用相應(yīng)協(xié)議的發(fā)送函數(shù)。對于 TCP ,調(diào)用 tcp_sendmsg 函數(shù)。對于 UDP 來說,userspace 應(yīng)用可以調(diào)用 send()/sendto()/sendmsg() 三個 system call 中的任意一個來發(fā)送 UDP message,它們最終都會調(diào)用內(nèi)核中的 udp_sendmsg() 函數(shù)。
圖片
1.2傳輸層:數(shù)據(jù)的可靠保障
傳輸層是網(wǎng)絡(luò)通信的可靠保障,如同快遞服務(wù)中的包裹跟蹤系統(tǒng),確保數(shù)據(jù)能夠準(zhǔn)確無誤地從發(fā)送方傳輸?shù)浇邮辗健K饕峁┒说蕉说耐ㄐ欧?wù),常見的傳輸層協(xié)議有 TCP(Transmission Control Protocol)和 UDP(User Datagram Protocol)。
TCP 協(xié)議是一種面向連接的、可靠的傳輸協(xié)議,它通過三次握手建立連接,在數(shù)據(jù)傳輸過程中進(jìn)行流量控制和擁塞控制,確保數(shù)據(jù)的完整性和有序性;TCP棧的簡要過程包括連接建立、數(shù)據(jù)傳輸和連接關(guān)閉三個主要階段:
①連接建立(三次握手)
- 客戶端選擇一個初始序列號 x,發(fā)送一個帶有 SYN 標(biāo)志的數(shù)據(jù)包到服務(wù)器,此時客戶端進(jìn)入 SYN_SENT 狀態(tài)。
- 服務(wù)器收到 SYN 包后,如果同意連接,會分配 TCP 資源,選擇自己的初始序列號 y,并發(fā)送一個 SYN-ACK 包,確認(rèn)客戶端的序列號為 x+1。此時服務(wù)器進(jìn)入 SYN_RCVD 狀態(tài)。
- 客戶端收到 SYN-ACK 包后,發(fā)送一個 ACK 包給服務(wù)器,確認(rèn)服務(wù)器的序列號為 y+1,客戶端進(jìn)入 ESTABLISHED 狀態(tài)。服務(wù)器收到 ACK 包后,也進(jìn)入 ESTABLISHED 狀態(tài),至此 TCP 連接建立成功。
②數(shù)據(jù)傳輸
- 應(yīng)用程序創(chuàng)建要發(fā)送的數(shù)據(jù),通過系統(tǒng)調(diào)用(如 write)將數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間的發(fā)送 socket 緩沖區(qū)。
- TCP 協(xié)議從發(fā)送緩沖區(qū)中取出數(shù)據(jù),構(gòu)造 TCP 段,包括添加 TCP 頭部,設(shè)置序列號、確認(rèn)號等字段,并計算 TCP 校驗和。TCP 段的 payload(有效載荷)大小受接收窗口、擁塞窗口和最大段大小(MSS)限制。
- TCP 段被傳輸?shù)?IP 層,IP 層在 TCP 段頭部加上 IP 頭信息,用于路由。然后 IP 包被傳輸?shù)芥溌穼樱溌穼犹砑右蕴W(wǎng)頭部信息,并通過 ARP 協(xié)議獲取下一跳的 MAC 地址,最終將數(shù)據(jù)包發(fā)送到網(wǎng)絡(luò)中。
- 接收方收到數(shù)據(jù)包后,從鏈路層依次向上解封裝,到達(dá) TCP 層。TCP 層根據(jù)序列號和確認(rèn)號對數(shù)據(jù)進(jìn)行排序和確認(rèn),將正確的數(shù)據(jù)放入接收 socket 緩沖區(qū),供應(yīng)用程序讀取。如果發(fā)現(xiàn)數(shù)據(jù)丟失或錯誤,接收方會請求發(fā)送方重發(fā)相應(yīng)數(shù)據(jù),發(fā)送方會緩存未收到 ACK 的數(shù)據(jù),在超時未收到確認(rèn)時進(jìn)行重發(fā)。同時,接收方會根據(jù)自己的接收能力,通過窗口字段告知發(fā)送方自己能接收的最大字節(jié)數(shù),進(jìn)行流控制。發(fā)送方還會根據(jù)網(wǎng)絡(luò)狀況進(jìn)行擁塞控制,避免網(wǎng)絡(luò)擁塞。
③連接關(guān)閉(四次揮手)
- 當(dāng)客戶端完成數(shù)據(jù)傳輸并希望斷開連接時,它發(fā)送一個 FIN 包,進(jìn)入 FIN-WAIT-1 狀態(tài)。
- 服務(wù)器收到 FIN 包后,發(fā)送一個 ACK 包確認(rèn),進(jìn)入 CLOSE-WAIT 狀態(tài)。客戶端收到 ACK 包后,進(jìn)入 FIN-WAIT-2 狀態(tài)。
- 服務(wù)器完成數(shù)據(jù)傳輸后,發(fā)送一個 FIN 包給客戶端,請求關(guān)閉其端的連接,此時服務(wù)器進(jìn)入 LAST-ACK 狀態(tài)。
- 客戶端收到服務(wù)器的 FIN 包后,發(fā)送一個 ACK 包進(jìn)行確認(rèn),然后進(jìn)入 TIME-WAIT 狀態(tài)。經(jīng)過 2MSL(最大報文段生存時間的兩倍)后,客戶端確保服務(wù)器接收到最終的 ACK 包,然后關(guān)閉連接。服務(wù)器收到 ACK 后,也關(guān)閉連接。
UDP 協(xié)議則是一種無連接的、不可靠的傳輸協(xié)議,它不保證數(shù)據(jù)的可靠傳輸,但具有傳輸速度快、開銷小的特點,適用于對實時性要求較高但對數(shù)據(jù)準(zhǔn)確性要求相對較低的應(yīng)用場景,如視頻直播、音頻通話等。例如,當(dāng)我們使用 FTP(File Transfer Protocol)進(jìn)行文件傳輸時,通常會使用 TCP 協(xié)議,以確保文件的完整傳輸;而在觀看在線視頻時,由于對實時性要求較高,即使少量數(shù)據(jù)丟失也不會對觀看體驗造成太大影響,因此可能會使用 UDP 協(xié)議。
UDP 棧的簡要過程包括數(shù)據(jù)發(fā)送和數(shù)據(jù)接收兩個主要方面:
①數(shù)據(jù)發(fā)送過程
- 創(chuàng)建套接字:應(yīng)用層通過調(diào)用 Socket API 中的socket函數(shù)創(chuàng)建一個 UDP 套接字。該函數(shù)會最終調(diào)用內(nèi)核中的sock_create方法,返回一個文件描述符,內(nèi)核中會為該套接字創(chuàng)建對應(yīng)的struct socket和struct sock結(jié)構(gòu)體,其中struct sock包含接收隊列(rx)、發(fā)送隊列(tx)和錯誤隊列(err)。
- 構(gòu)建 UDP 數(shù)據(jù)報:應(yīng)用程序調(diào)用send、sendto或sendmsg等函數(shù)發(fā)送數(shù)據(jù),這些函數(shù)最終會調(diào)用內(nèi)核中的udp_sendmsg函數(shù)。udp_sendmsg函數(shù)根據(jù)目的地址和端口號,將應(yīng)用層數(shù)據(jù)封裝成 UDP 數(shù)據(jù)報,添加 UDP 首部,首部包含源端口號、目的端口號、長度和校驗和(校驗和為可選項)。
- 發(fā)送到網(wǎng)絡(luò)層:UDP 將構(gòu)建好的數(shù)據(jù)報通過調(diào)用ip_append_data方法或ip_queue_xmit函數(shù),將數(shù)據(jù)包發(fā)送給網(wǎng)絡(luò)層(IP 層)。
- 網(wǎng)絡(luò)層處理:IP 層接收到數(shù)據(jù)包后,添加 IP 首部,進(jìn)行路由處理,確定下一跳地址。如果數(shù)據(jù)包長度超過網(wǎng)絡(luò)最大傳輸單元(MTU),可能會進(jìn)行分片處理,最后將處理好的數(shù)據(jù)包發(fā)送到鏈路層。
- 鏈路層發(fā)送:鏈路層收到數(shù)據(jù)包后,添加鏈路層首部(如以太網(wǎng)首部),包含源 MAC 地址和目的 MAC 地址等信息,并通過物理層將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)中。
②數(shù)據(jù)接收過程
- 物理層接收:物理層從網(wǎng)絡(luò)中接收數(shù)據(jù)幀,將其傳遞給鏈路層。
- 鏈路層解析:鏈路層去除鏈路層首部,根據(jù)首部中的協(xié)議類型字段,判斷出是 UDP 數(shù)據(jù)報,將其傳遞給網(wǎng)絡(luò)層。
- 網(wǎng)絡(luò)層處理:IP 層檢查 IP 首部,進(jìn)行路由相關(guān)處理和校驗等,根據(jù)協(xié)議字段確定上層協(xié)議為 UDP 后,去除 IP 首部,將 UDP 數(shù)據(jù)報傳遞給 UDP 層。
- UDP 層解析:UDP 層根據(jù) UDP 首部中的目的端口號,將數(shù)據(jù)報從接收隊列中取出,計算校驗和(若有),驗證數(shù)據(jù)完整性。如果校驗通過,提取數(shù)據(jù)載荷部分。
- 交付應(yīng)用層:UDP 將提取出的數(shù)據(jù)載荷通過 Socket 接口發(fā)送給對應(yīng)的應(yīng)用程序,應(yīng)用程序通過調(diào)用recv或recvfrom等函數(shù)接收數(shù)據(jù),完成數(shù)據(jù)接收過程。
1.3網(wǎng)絡(luò)層:數(shù)據(jù)的導(dǎo)航者
網(wǎng)絡(luò)層如同交通樞紐的調(diào)度員,負(fù)責(zé)數(shù)據(jù)包在網(wǎng)絡(luò)中的傳輸和路由選擇。其核心協(xié)議是 IP(Internet Protocol)協(xié)議,它為每個網(wǎng)絡(luò)節(jié)點分配唯一的 IP 地址,使得數(shù)據(jù)包能夠在不同的網(wǎng)絡(luò)之間進(jìn)行傳輸。當(dāng)網(wǎng)絡(luò)層接收到鏈路層傳來的數(shù)據(jù)包后,會根據(jù)數(shù)據(jù)包中的目的 IP 地址,通過路由表查找最佳的傳輸路徑,然后將數(shù)據(jù)包轉(zhuǎn)發(fā)到下一個網(wǎng)絡(luò)節(jié)點。
此外,網(wǎng)絡(luò)層還包括 ICMP(Internet Control Message Protocol)協(xié)議,用于網(wǎng)絡(luò)診斷和錯誤報告;IGMP(Internet Group Management Protocol)協(xié)議,用于多播通信。例如,當(dāng)我們在瀏覽器中輸入一個網(wǎng)址并訪問時,網(wǎng)絡(luò)層會根據(jù)目標(biāo)服務(wù)器的 IP 地址,規(guī)劃出數(shù)據(jù)從本地計算機到目標(biāo)服務(wù)器的傳輸路徑,確保數(shù)據(jù)能夠準(zhǔn)確無誤地到達(dá)。
網(wǎng)絡(luò)層的任務(wù)就是選擇合適的網(wǎng)間路由和交換結(jié)點, 確保數(shù)據(jù)及時傳送。網(wǎng)絡(luò)層將數(shù)據(jù)鏈路層提供的幀組成數(shù)據(jù)包,包中封裝有網(wǎng)絡(luò)層包頭,其中含有邏輯地址信息- -源站點和目的站點地址的網(wǎng)絡(luò)地址。其主要任務(wù)包括 (1)路由處理,即選擇下一跳 (2)添加 IP header(3)計算 IP header checksum,用于檢測 IP 報文頭部在傳播過程中是否出錯 (4)可能的話,進(jìn)行 IP 分片(5)處理完畢,獲取下一跳的 MAC 地址,設(shè)置鏈路層報文頭,然后轉(zhuǎn)入鏈路層處理。
IP 棧基本處理過程主要包括數(shù)據(jù)包的接收、校驗、路由選擇、分片以及發(fā)送等步驟:
步驟一:鏈路層接收分組并送至網(wǎng)絡(luò)層:當(dāng)網(wǎng)卡收到與自己 MAC 地址匹配或廣播的以太網(wǎng)幀時,會產(chǎn)生硬件中斷,網(wǎng)卡驅(qū)動處理該中斷,從 DMA 或其他方式獲取分組數(shù)據(jù)寫入內(nèi)存,分配套接字緩沖區(qū) skb,并調(diào)用 netif_rx (skb) 函數(shù)。
skb 進(jìn)入到達(dá)隊列等待 CPU 處理,同時標(biāo)記 NET_RX_SOFTIRQ 網(wǎng)絡(luò)接收軟中斷。CPU 調(diào)用 do_softirq () 處理軟中斷,再調(diào)用 net_rx_action (),在 netif_receive_skb 函數(shù)中,根據(jù)協(xié)議將 skb 發(fā)送到相關(guān)協(xié)議處理函數(shù),如 ip_rcv () 用于處理 IP 協(xié)議。
步驟二:網(wǎng)絡(luò)層處理分組(以 IPv4 為例):
- ip_rcv 函數(shù)校驗:skb 被送到 ip_rcv () 函數(shù),驗證 IP 分組,檢查目的地是否為本機地址、校驗和是否正確等。若正確,交給 netfilter 的 NF_IP_ROUTING;否則,丟棄數(shù)據(jù)包。
- ip_rcv_finish 函數(shù)路由判斷:隨后分組進(jìn)入 ip_rcv_finish () 函數(shù),根據(jù) skb 結(jié)構(gòu)中的目的或路由信息進(jìn)行處理。若 skb->dst 無路由信息,則通過 ip_route_input () 查找路由,若目的地不可達(dá),丟棄數(shù)據(jù)包。最后執(zhí)行 dst_input,根據(jù)結(jié)果決定數(shù)據(jù)包下一步處理方式:本機分組由 ip_local_deliver 處理;需要轉(zhuǎn)發(fā)的數(shù)據(jù)由 ip_forward () 函數(shù)處理;組播數(shù)據(jù)包由 ip_mr_input () 函數(shù)處理。
- ip_forward 轉(zhuǎn)發(fā)數(shù)據(jù)包:若需轉(zhuǎn)發(fā),ip_forward () 函數(shù)會先處理 IP 頭選項,記錄本地 IP 地址和時間戳等,確認(rèn)分組可轉(zhuǎn)發(fā)后,將 TTL 減 1,若 TTL 為 0 則丟棄。接著根據(jù) MTU 大小和路由信息對數(shù)據(jù)分組進(jìn)行分片,最后將數(shù)據(jù)分組送往外出設(shè)備。若轉(zhuǎn)發(fā)失敗,則回應(yīng) ICMP 消息說明原因。之后執(zhí)行 ip_forward_finish () 函數(shù)準(zhǔn)備發(fā)送,再通過 dst_output (skb) 將分組發(fā)到轉(zhuǎn)發(fā)的目的主機或本地主機,最后調(diào)用 ip_finish_output () 進(jìn)入鄰居子系統(tǒng)。
- ip_local_deliver 本地處理:對于本機分組,ip_local_deliver 中會對 ip 分片進(jìn)行重組,經(jīng)過 LOCAL_IN 鉤子點,然后調(diào)用 ip_local_deliver_finish。ip_local_deliver_finish 函數(shù)處理原始套接字的數(shù)據(jù)接收,并調(diào)用上層協(xié)議的包接收函數(shù),將數(shù)據(jù)包傳遞到傳輸層。
1.4鏈路層:網(wǎng)絡(luò)通信的基石
鏈路層,如同高樓的地基,是網(wǎng)絡(luò)通信的基礎(chǔ)。它負(fù)責(zé)處理與物理網(wǎng)絡(luò)設(shè)備的直接交互,包括網(wǎng)卡驅(qū)動程序以及數(shù)據(jù)鏈路協(xié)議的實現(xiàn)。其主要功能是將網(wǎng)絡(luò)層傳來的數(shù)據(jù)包封裝成數(shù)據(jù)幀,并通過物理網(wǎng)絡(luò)介質(zhì)進(jìn)行傳輸。在接收數(shù)據(jù)時,則進(jìn)行相反的操作,將接收到的數(shù)據(jù)幀解封裝,提取出數(shù)據(jù)包傳遞給網(wǎng)絡(luò)層。常見的鏈路層協(xié)議有以太網(wǎng)協(xié)議、PPP(Point-to-Point Protocol)協(xié)議等。例如,在以太網(wǎng)中,鏈路層會在數(shù)據(jù)包前后添加以太網(wǎng)頭部和尾部,形成以太網(wǎng)幀,其中頭部包含源 MAC 地址和目的 MAC 地址等信息,用于在局域網(wǎng)內(nèi)標(biāo)識數(shù)據(jù)的發(fā)送方和接收方。
功能上,在物理層提供比特流服務(wù)的基礎(chǔ)上,建立相鄰結(jié)點之間的數(shù)據(jù)鏈路,通過差錯控制提供數(shù)據(jù)幀(Frame)在信道上無差錯的傳輸,并進(jìn)行各電路上的動作系列。數(shù)據(jù)鏈路層在不可靠的物理介質(zhì)上提供可靠的傳輸。該層的作用包括:物理地址尋址、數(shù)據(jù)的成幀、流量控制、數(shù)據(jù)的檢錯、重發(fā)等。在這一層,數(shù)據(jù)的單位稱為幀(frame)。數(shù)據(jù)鏈路層協(xié)議的代表包括:SDLC、HDLC、PPP、STP、幀中繼等。
實現(xiàn)上,Linux 提供了一個 Network device 的抽象層,其實現(xiàn)在 linux/net/core/dev.c。具體的物理網(wǎng)絡(luò)設(shè)備在設(shè)備驅(qū)動中(driver.c)需要實現(xiàn)其中的虛函數(shù)。Network Device 抽象層調(diào)用具體網(wǎng)絡(luò)設(shè)備的函數(shù)。
圖片
Part2.數(shù)據(jù)輸入:從網(wǎng)卡到內(nèi)核的旅程
2.1網(wǎng)卡接收與中斷處理
在 Linux 系統(tǒng)中,網(wǎng)絡(luò)數(shù)據(jù)的輸入旅程始于網(wǎng)卡。網(wǎng)卡,作為計算機與網(wǎng)絡(luò)之間的物理接口,如同一位勤勞的快遞員,時刻監(jiān)聽著網(wǎng)絡(luò)上的數(shù)據(jù)傳輸。當(dāng)網(wǎng)卡接收到數(shù)據(jù)包時,它首先會進(jìn)行初步的篩選。如果目的地址不是該網(wǎng)卡,且該網(wǎng)卡沒有開啟混雜模式,那么這個數(shù)據(jù)包就會被網(wǎng)卡無情地丟棄,就好像快遞員發(fā)現(xiàn)包裹不是送到自己負(fù)責(zé)的區(qū)域,便會直接退回。
圖片
對于符合條件的數(shù)據(jù)包,網(wǎng)卡會通過 DMA(Direct Memory Access)技術(shù),將其寫入到預(yù)先分配好的內(nèi)存地址中。DMA 技術(shù)就像是一條數(shù)據(jù)高速公路,允許設(shè)備直接與內(nèi)存進(jìn)行數(shù)據(jù)傳輸,而無需 CPU 的頻繁干預(yù),大大提高了數(shù)據(jù)傳輸?shù)男省@纾谝慌_配備千兆網(wǎng)卡的服務(wù)器上,通過 DMA 技術(shù),網(wǎng)卡可以快速地將大量的網(wǎng)絡(luò)數(shù)據(jù)包寫入內(nèi)存,為后續(xù)的處理做好準(zhǔn)備。
在完成數(shù)據(jù)寫入后,網(wǎng)卡會通過硬件中斷(IRQ)通知 CPU,就如同快遞員按響門鈴,告訴主人有新的包裹送達(dá)。硬件中斷是一種由硬件設(shè)備發(fā)出的電信號,它會使 CPU 立即暫停當(dāng)前正在執(zhí)行的任務(wù),保存現(xiàn)場信息,然后跳轉(zhuǎn)到對應(yīng)的中斷處理程序。在 Linux 系統(tǒng)中,每個硬件設(shè)備都有一個唯一的中斷號,用于標(biāo)識該設(shè)備發(fā)出的中斷請求,這樣 CPU 就能準(zhǔn)確地知道是哪個設(shè)備在請求服務(wù)。
2.2網(wǎng)絡(luò)驅(qū)動的接力
當(dāng) CPU 收到網(wǎng)卡發(fā)出的中斷信號后,會調(diào)用已經(jīng)注冊的中斷函數(shù),這個中斷函數(shù)會進(jìn)一步調(diào)用到網(wǎng)卡驅(qū)動程序中相應(yīng)的函數(shù)。網(wǎng)絡(luò)驅(qū)動程序就像是一位翻譯官,負(fù)責(zé)將網(wǎng)卡接收到的原始數(shù)據(jù)轉(zhuǎn)換為內(nèi)核能夠理解的格式。
以常見的 e1000 網(wǎng)卡驅(qū)動為例,其硬中斷處理函數(shù) e1000_intr () 會首先禁用網(wǎng)卡的中斷,表示驅(qū)動程序已經(jīng)知曉內(nèi)存中有數(shù)據(jù),讓網(wǎng)卡下次收到數(shù)據(jù)包時直接寫入內(nèi)存即可,無需再通知 CPU,以此提高效率,避免 CPU 被頻繁中斷。接著,啟動軟中斷,將耗時較長的數(shù)據(jù)處理任務(wù)交給軟中斷處理函數(shù)。
在軟中斷處理函數(shù)中,網(wǎng)絡(luò)驅(qū)動會從內(nèi)存中讀取數(shù)據(jù)包,并將其轉(zhuǎn)換為內(nèi)核網(wǎng)絡(luò)模塊能識別的 skb(socket buffer)格式。skb 是 Linux 內(nèi)核中用于表示網(wǎng)絡(luò)數(shù)據(jù)包的數(shù)據(jù)結(jié)構(gòu),它包含了數(shù)據(jù)包的各種信息,如數(shù)據(jù)內(nèi)容、源地址、目的地址等,就像是一個包裹的詳細(xì)清單,方便內(nèi)核后續(xù)的處理。完成格式轉(zhuǎn)換后,驅(qū)動會調(diào)用 napi_gro_receive 函數(shù),該函數(shù)會處理 GRO(Generic Receive Offload)相關(guān)的內(nèi)容,即將可以合并的數(shù)據(jù)包進(jìn)行合并,這樣就只需要調(diào)用一次協(xié)議棧,減少了系統(tǒng)開銷。
2.3網(wǎng)絡(luò)層的初次校驗
當(dāng)數(shù)據(jù)包以 skb 格式進(jìn)入網(wǎng)絡(luò)層后,首先會調(diào)用 ip_rcv 函數(shù)進(jìn)行處理。這個函數(shù)就像是一位嚴(yán)格的質(zhì)檢員,會對數(shù)據(jù)包進(jìn)行多項嚴(yán)格的檢查,以確保數(shù)據(jù)包的完整性和正確性。
它會檢查數(shù)據(jù)包的 IP 首部長度是否符合要求。IP 首部包含了數(shù)據(jù)包的各種控制信息,如版本號、首部長度、服務(wù)類型、總長度等,如果首部長度不正確,那么數(shù)據(jù)包可能在傳輸過程中出現(xiàn)了錯誤,無法被正確處理。它還會檢查數(shù)據(jù)包的校驗和。校驗和是一種用于檢測數(shù)據(jù)傳輸錯誤的機制,通過對數(shù)據(jù)包的內(nèi)容進(jìn)行特定的算法計算得出一個值,接收方在收到數(shù)據(jù)包后,會重新計算校驗和并與發(fā)送方發(fā)送的校驗和進(jìn)行比較,如果兩者不一致,就說明數(shù)據(jù)包在傳輸過程中可能發(fā)生了錯誤。
在進(jìn)行這些基本檢查的同時,ip_rcv 函數(shù)還會與 netfilter 的 NF_IP_PRE_ROUTING 鉤子點交互。netfilter 是 Linux 內(nèi)核中的一個框架,它提供了一系列的鉤子點,允許用戶通過編寫規(guī)則對網(wǎng)絡(luò)數(shù)據(jù)包進(jìn)行過濾、修改等操作。例如,在一些網(wǎng)絡(luò)安全場景中,管理員可以通過在 NF_IP_PRE_ROUTING 鉤子點設(shè)置規(guī)則,對進(jìn)入網(wǎng)絡(luò)層的數(shù)據(jù)包進(jìn)行安全檢查,如檢測是否存在惡意攻擊的特征,如果發(fā)現(xiàn)可疑數(shù)據(jù)包,則可以直接丟棄或進(jìn)行其他處理,從而保護(hù)系統(tǒng)的安全。
2.4路由決策與后續(xù)走向
經(jīng)過網(wǎng)絡(luò)層的初次校驗后,數(shù)據(jù)包會進(jìn)入 ip_rcv_finish 函數(shù),在這里,路由決策將決定數(shù)據(jù)包的后續(xù)走向。路由決策就像是為數(shù)據(jù)包規(guī)劃一條旅行路線,根據(jù)數(shù)據(jù)包的目的 IP 地址,查找最佳的傳輸路徑。
在進(jìn)行路由決策時,首先會檢查路由緩存項。路由緩存就像是一本常用路線的速查手冊,記錄了最近使用過的路由信息。如果在緩存中找到了匹配的路由項,那么就可以直接使用該路由項,大大提高了路由查找的效率。如果路由緩存未命中,就需要調(diào)用 ip_route_input_slow 函數(shù)查找路由表。路由表則是一本詳細(xì)的地圖集,包含了網(wǎng)絡(luò)中所有可能的路由信息。在查找路由表時,會根據(jù)目的 IP 地址、子網(wǎng)掩碼等信息,找到最佳的下一跳地址。
根據(jù)路由結(jié)果,數(shù)據(jù)包有兩種可能的走向。如果目的 IP 地址是本地主機的地址,那么數(shù)據(jù)包將走向本地交付,即被傳遞到本地的應(yīng)用程序進(jìn)行處理;如果目的 IP 地址是其他網(wǎng)絡(luò)主機的地址,那么數(shù)據(jù)包將被轉(zhuǎn)發(fā)到下一個網(wǎng)絡(luò)節(jié)點。在不同的走向下,會設(shè)置不同的處理函數(shù)。對于本地交付,會設(shè)置 ip_local_deliver 函數(shù),負(fù)責(zé)將數(shù)據(jù)包傳遞到本地的傳輸層協(xié)議進(jìn)行進(jìn)一步處理;對于轉(zhuǎn)發(fā),會設(shè)置 ip_forward 函數(shù),負(fù)責(zé)將數(shù)據(jù)包轉(zhuǎn)發(fā)到合適的網(wǎng)絡(luò)接口,繼續(xù)其傳輸旅程。
2.5本地交付的深入剖析
當(dāng)數(shù)據(jù)包被確定為本地交付后,會進(jìn)入 ip_local_deliver 函數(shù)進(jìn)行處理。在這個函數(shù)中,首先會對 IP 報文進(jìn)行分片處理。在網(wǎng)絡(luò)傳輸過程中,由于不同網(wǎng)絡(luò)鏈路的最大傳輸單元(MTU)可能不同,當(dāng)數(shù)據(jù)包的大小超過了鏈路的 MTU 時,就需要將數(shù)據(jù)包進(jìn)行分片,將其拆分成多個較小的片段進(jìn)行傳輸。例如,在一個以太網(wǎng)鏈路中,MTU 通常為 1500 字節(jié),如果一個數(shù)據(jù)包的大小為 2000 字節(jié),那么就需要將其分成兩個片段,分別為 1500 字節(jié)和 500 字節(jié)。
ip_local_deliver 函數(shù)會對這些分片進(jìn)行重組,將它們還原成完整的數(shù)據(jù)包。它會根據(jù) IP 首部中的分片標(biāo)識、標(biāo)志位和片偏移等信息,判斷各個分片之間的順序和關(guān)系,然后將它們正確地組合起來。完成分片重組后,數(shù)據(jù)包會被傳遞到 netfilter 的 NF_IP_LOCAL_IN 鉤子點,在這里,用戶可以再次對數(shù)據(jù)包進(jìn)行過濾、修改等操作。經(jīng)過 NF_IP_LOCAL_IN 鉤子點的處理后,數(shù)據(jù)包最終會到達(dá)傳輸層,根據(jù)其協(xié)議類型(如 TCP、UDP 等),被傳遞到相應(yīng)的傳輸層協(xié)議模塊進(jìn)行后續(xù)的處理,如 TCP 協(xié)議的連接管理、數(shù)據(jù)確認(rèn)等。在實際的網(wǎng)絡(luò)場景中,分片處理非常重要。例如,當(dāng)我們從互聯(lián)網(wǎng)上下載一個大型文件時,文件會被分成多個數(shù)據(jù)包進(jìn)行傳輸,這些數(shù)據(jù)包可能會經(jīng)過不同的網(wǎng)絡(luò)鏈路,由于各鏈路的 MTU 不同,數(shù)據(jù)包可能會被分片。如果接收方不能正確地對這些分片進(jìn)行重組,那么就無法得到完整的文件,導(dǎo)致下載失敗。
Part3.數(shù)據(jù)輸出:從內(nèi)核到網(wǎng)卡的征程
3.1傳輸層的發(fā)起
在 Linux 內(nèi)核中,網(wǎng)絡(luò)數(shù)據(jù)的輸出是一個復(fù)雜而有序的過程,傳輸層在其中扮演著重要的發(fā)起角色。以常見的 TCP 和 UDP 協(xié)議為例,當(dāng)應(yīng)用程序調(diào)用 send () 或 sendto () 等系統(tǒng)調(diào)用發(fā)送數(shù)據(jù)時,數(shù)據(jù)首先進(jìn)入傳輸層。
圖片
對于 TCP 協(xié)議,以一個 Web 服務(wù)器向客戶端發(fā)送網(wǎng)頁數(shù)據(jù)為例,當(dāng) Web 服務(wù)器的應(yīng)用程序調(diào)用 send () 函數(shù)時,數(shù)據(jù)會被傳遞到 TCP 層。TCP 層會為數(shù)據(jù)添加 TCP 首部,首部中包含源端口號、目的端口號、序列號、確認(rèn)號等重要信息。這些信息就像是貨物運輸中的詳細(xì)清單,用于建立和維護(hù)可靠的連接。在這個過程中,TCP 協(xié)議會進(jìn)行一系列的操作,如擁塞控制、流量控制等,以確保數(shù)據(jù)能夠穩(wěn)定、高效地傳輸。擁塞控制就像是交通警察,通過調(diào)整發(fā)送窗口的大小,避免網(wǎng)絡(luò)擁塞,確保數(shù)據(jù)能夠順利傳輸;流量控制則是根據(jù)接收方的接收能力,調(diào)整發(fā)送方的發(fā)送速率,防止接收方緩沖區(qū)溢出。
而 UDP 協(xié)議則相對簡單,它不提供可靠的傳輸機制,也沒有擁塞控制和流量控制。在視頻直播應(yīng)用中,為了保證直播的實時性,通常會使用 UDP 協(xié)議。當(dāng)直播服務(wù)器調(diào)用 sendto () 函數(shù)發(fā)送視頻數(shù)據(jù)時,UDP 層同樣會添加 UDP 首部,首部包含源端口號和目的端口號等信息。由于 UDP 協(xié)議不需要建立連接,也不保證數(shù)據(jù)的可靠傳輸,所以它的傳輸效率較高,能夠滿足視頻直播對實時性的要求。
3.2 IP層的處理前奏
當(dāng)數(shù)據(jù)從傳輸層傳遞到 IP 層后,首先會到達(dá)本地發(fā)送數(shù)據(jù)包在 netfilter 的 NF_IP_LOCAL_OUT 鉤子點。這個鉤子點就像是一個關(guān)卡,在這里可以對數(shù)據(jù)包進(jìn)行各種自定義的處理,如過濾、修改等。
在一些安全防護(hù)場景中,管理員可能會在 NF_IP_LOCAL_OUT 鉤子點設(shè)置規(guī)則,對本地發(fā)送的數(shù)據(jù)包進(jìn)行安全檢查。如果發(fā)現(xiàn)數(shù)據(jù)包中包含惡意代碼或違反安全策略的內(nèi)容,就可以直接丟棄該數(shù)據(jù)包,從而保護(hù)系統(tǒng)的安全。而對于轉(zhuǎn)發(fā)的數(shù)據(jù)包,由于其來源并非本地生成,所以會跳過 NF_IP_LOCAL_OUT 鉤子點,直接進(jìn)入后續(xù)的處理流程。
這是因為轉(zhuǎn)發(fā)數(shù)據(jù)包的處理邏輯與本地發(fā)送數(shù)據(jù)包有所不同,轉(zhuǎn)發(fā)數(shù)據(jù)包更關(guān)注的是如何快速、準(zhǔn)確地將數(shù)據(jù)轉(zhuǎn)發(fā)到下一個網(wǎng)絡(luò)節(jié)點,而不是進(jìn)行本地的安全檢查等操作。鉤子點的處理對數(shù)據(jù)包流向起著關(guān)鍵的控制作用,通過合理設(shè)置鉤子點的規(guī)則,可以實現(xiàn)對網(wǎng)絡(luò)數(shù)據(jù)的有效管理和安全防護(hù)。
3.3路由與協(xié)議設(shè)置
在經(jīng)過 NF_IP_LOCAL_OUT 鉤子點的處理后,數(shù)據(jù)包會進(jìn)入 ip_output 函數(shù)。在這個函數(shù)中,會進(jìn)行一系列重要的設(shè)置,以確保數(shù)據(jù)包能夠正確地傳輸?shù)侥繕?biāo)地址。
ip_output 函數(shù)會根據(jù)數(shù)據(jù)包的目的 IP 地址,查找路由表,確定數(shù)據(jù)包應(yīng)該從哪個網(wǎng)絡(luò)設(shè)備發(fā)送出去,以及下一跳的地址。這個過程就像是為數(shù)據(jù)包規(guī)劃一條旅行路線,根據(jù)目的地的不同,選擇最合適的交通路線。它會設(shè)置數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備和協(xié)議類型。以一個企業(yè)內(nèi)部網(wǎng)絡(luò)為例,當(dāng)一臺計算機要向外部網(wǎng)絡(luò)發(fā)送數(shù)據(jù)時,ip_output 函數(shù)會根據(jù)路由表的信息,選擇合適的出口網(wǎng)絡(luò)設(shè)備,如企業(yè)的網(wǎng)關(guān)設(shè)備,并將數(shù)據(jù)包的協(xié)議類型設(shè)置為 IP 協(xié)議。
完成這些設(shè)置后,數(shù)據(jù)包會被傳遞到 netfilter 的 NF_IP_INET_POST_ROUTING 鉤子點。在這個鉤子點,同樣可以對數(shù)據(jù)包進(jìn)行一些最后的處理,如網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)等。NAT 技術(shù)可以將企業(yè)內(nèi)部網(wǎng)絡(luò)的私有 IP 地址轉(zhuǎn)換為合法的公網(wǎng) IP 地址,使得企業(yè)內(nèi)部的計算機能夠訪問外部網(wǎng)絡(luò)。這些設(shè)置對數(shù)據(jù)包傳輸至關(guān)重要,它們確保了數(shù)據(jù)包能夠在復(fù)雜的網(wǎng)絡(luò)環(huán)境中準(zhǔn)確無誤地找到自己的傳輸路徑,最終到達(dá)目標(biāo)地址。
3.4數(shù)據(jù)分片與最終發(fā)送
當(dāng)數(shù)據(jù)包到達(dá) ip_finish_output 函數(shù)時,會對報文長度與 MTU(最大傳輸單元)進(jìn)行比較。MTU 是指數(shù)據(jù)鏈路層一次能夠傳輸?shù)淖畲髷?shù)據(jù)量,不同的網(wǎng)絡(luò)鏈路類型,其 MTU 值也不同,例如以太網(wǎng)的 MTU 通常為 1500 字節(jié)。
如果報文長度超過了 MTU,就需要進(jìn)行分片處理。這就好比將一個大包裹拆分成多個小包裹進(jìn)行郵寄,以確保每個小包裹都能順利通過傳輸鏈路。在分片過程中,會為每個分片分配一個標(biāo)識,以及片偏移等信息,以便在接收端能夠正確地重組這些分片。以一個發(fā)送大型文件的場景為例,假設(shè)文件被封裝成一個大小為 3000 字節(jié)的數(shù)據(jù)包,而網(wǎng)絡(luò)鏈路的 MTU 為 1500 字節(jié),那么這個數(shù)據(jù)包就需要被分成兩個分片,第一個分片包含 1500 字節(jié)的數(shù)據(jù),第二個分片包含剩余的 1500 字節(jié)數(shù)據(jù)。每個分片都會有自己的 IP 首部,首部中的標(biāo)識字段相同,用于表示它們屬于同一個原始數(shù)據(jù)包,片偏移字段則表示該分片在原始數(shù)據(jù)包中的位置。
經(jīng)過分片處理后,或者如果報文長度小于等于 MTU,數(shù)據(jù)包會通過鄰居子系統(tǒng)發(fā)送到網(wǎng)絡(luò)設(shè)備。鄰居子系統(tǒng)負(fù)責(zé)解析目標(biāo) IP 地址對應(yīng)的 MAC 地址,就像是根據(jù)收件人的地址找到對應(yīng)的門牌號。通過 ARP(地址解析協(xié)議)等機制,鄰居子系統(tǒng)可以獲取到目標(biāo) IP 地址對應(yīng)的 MAC 地址,并將其添加到數(shù)據(jù)包的鏈路層首部中。最后,數(shù)據(jù)包會被發(fā)送到網(wǎng)絡(luò)設(shè)備,通過物理網(wǎng)絡(luò)介質(zhì)傳輸?shù)侥繕?biāo)地址。在實際的網(wǎng)絡(luò)傳輸中,網(wǎng)絡(luò)帶寬是一個重要的因素。如果網(wǎng)絡(luò)帶寬較低,而數(shù)據(jù)包過大,就容易導(dǎo)致網(wǎng)絡(luò)擁塞,影響數(shù)據(jù)傳輸?shù)男省R虼耍制幚砜梢詫⒋蟮臄?shù)據(jù)包拆分成多個小的分片,降低每個分片的大小,從而更有效地利用網(wǎng)絡(luò)帶寬,提高數(shù)據(jù)傳輸?shù)某晒β省?/span>
Part4.實例分析與代碼解讀
4.1選取典型場景
為了更直觀地理解 Linux 內(nèi)核中網(wǎng)絡(luò)數(shù)據(jù)的輸入輸出流程,我們以 Web 服務(wù)器與客戶端的通信為例進(jìn)行分析。在這個場景中,客戶端通過瀏覽器訪問 Web 服務(wù)器,請求獲取網(wǎng)頁內(nèi)容。當(dāng)我們在瀏覽器地址欄輸入網(wǎng)址并按下回車鍵后,一系列復(fù)雜的網(wǎng)絡(luò)數(shù)據(jù)交互就此展開。
首先,客戶端的瀏覽器會解析 URL,提取出服務(wù)器的域名和請求的資源路徑。接著,通過 DNS 解析將域名轉(zhuǎn)換為對應(yīng)的 IP 地址。在獲取到服務(wù)器的 IP 地址后,客戶端與服務(wù)器之間建立 TCP 連接,這一過程通過著名的三次握手來完成。三次握手確保了雙方的通信連接是可靠的,就像在正式開始對話之前,雙方先確認(rèn)彼此都準(zhǔn)備好進(jìn)行交流。
TCP 連接建立成功后,客戶端向服務(wù)器發(fā)送 HTTP 請求,請求中包含了請求方法(如 GET、POST 等)、請求頭和請求體等信息。服務(wù)器接收到 HTTP 請求后,會根據(jù)請求內(nèi)容進(jìn)行處理,可能會讀取相應(yīng)的網(wǎng)頁文件或調(diào)用后端程序生成動態(tài)內(nèi)容。然后,服務(wù)器將處理結(jié)果封裝在 HTTP 響應(yīng)中返回給客戶端。客戶端收到 HTTP 響應(yīng)后,對其進(jìn)行解析,提取出網(wǎng)頁內(nèi)容,并通過瀏覽器進(jìn)行渲染,最終將網(wǎng)頁展示在用戶面前。當(dāng)通信結(jié)束后,客戶端和服務(wù)器之間會通過四次揮手來斷開 TCP 連接。
4.2關(guān)鍵代碼片段解析
結(jié)合上述 Web 服務(wù)器與客戶端通信的場景,我們來看一些 Linux 內(nèi)核中實現(xiàn)網(wǎng)絡(luò)數(shù)據(jù)輸入輸出的關(guān)鍵代碼片段。
在網(wǎng)絡(luò)數(shù)據(jù)輸入方面,當(dāng)網(wǎng)卡接收到數(shù)據(jù)包時,會觸發(fā)中斷處理程序。以 e1000 網(wǎng)卡驅(qū)動為例,其硬中斷處理函數(shù)如下:
irqreturn_t e1000_intr(int irq, void *data) {
struct e1000_adapter *adapter = data;
struct e1000_ring *rx_ring;
struct sk_buff *skb;
u32 status;
int work_done = 0;
// 禁用網(wǎng)卡中斷
e1000_disable_irq(adapter);
// 遍歷接收隊列
for (rx_ring = adapter->rx_ring; rx_ring; rx_ring = rx_ring->next) {
// 循環(huán)處理接收隊列中的數(shù)據(jù)包
while ((status = e1000_clean_rx_irq(adapter, rx_ring))) {
// 從接收隊列中獲取skb
skb = e1000_rx_skb(adapter, rx_ring);
if (skb) {
// 將skb傳遞給協(xié)議棧
netif_receive_skb(skb);
work_done++;
}
}
}
// 啟動軟中斷
napi_schedule(&adapter->napi);
// 恢復(fù)網(wǎng)卡中斷
e1000_enable_irq(adapter);
return IRQ_RETVAL(work_done);
}
在這段代碼中,首先禁用網(wǎng)卡中斷,以避免在處理當(dāng)前中斷時再次被中斷。然后遍歷接收隊列,調(diào)用 e1000_clean_rx_irq 函數(shù)清理接收隊列中的數(shù)據(jù)包,并通過 e1000_rx_skb 函數(shù)將數(shù)據(jù)包轉(zhuǎn)換為 skb 格式。接著,使用 netif_receive_skb 函數(shù)將 skb 傳遞給協(xié)議棧進(jìn)行后續(xù)處理。最后,啟動軟中斷,并恢復(fù)網(wǎng)卡中斷。
在網(wǎng)絡(luò)數(shù)據(jù)輸出方面,當(dāng)應(yīng)用程序調(diào)用 send () 函數(shù)發(fā)送數(shù)據(jù)時,最終會調(diào)用到 tcp_sendmsg 函數(shù)。下面是 tcp_sendmsg 函數(shù)的簡化代碼:
int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t size) {
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
int err, copied;
// 分配skb
skb = alloc_skb(size + TCP_HEADER_LEN, GFP_KERNEL);
if (!skb)
return -ENOMEM;
// 設(shè)置skb的相關(guān)信息
skb_reserve(skb, TCP_HEADER_LEN);
skb->data_len = size;
skb->len = size + TCP_HEADER_LEN;
// 將用戶數(shù)據(jù)拷貝到skb中
copied = skb_add_data(skb, msg->msg_iov->iov_base, size);
if (copied < size) {
// 拷貝失敗,釋放skb
kfree_skb(skb);
return -EFAULT;
}
// 設(shè)置TCP首部信息
tcp_init_skb(skb, sk);
// 調(diào)用網(wǎng)絡(luò)層發(fā)送接口
err = ip_queue_xmit(skb, &(inet->cork.fl));
if (err) {
// 發(fā)送失敗,釋放skb
kfree_skb(skb);
}
return err? err : size;
}
在這段代碼中,首先分配一個 skb,并預(yù)留出 TCP 首部的空間。然后將用戶數(shù)據(jù)從應(yīng)用層拷貝到 skb 中,并設(shè)置 TCP 首部的相關(guān)信息。最后,調(diào)用 ip_queue_xmit 函數(shù)將 skb 傳遞給網(wǎng)絡(luò)層進(jìn)行發(fā)送。如果發(fā)送過程中出現(xiàn)錯誤,會釋放 skb 并返回錯誤信息。
通過對這些關(guān)鍵代碼片段的解析,我們可以更深入地理解 Linux 內(nèi)核中網(wǎng)絡(luò)數(shù)據(jù)輸入輸出的實際代碼邏輯,感受到內(nèi)核開發(fā)者在實現(xiàn)網(wǎng)絡(luò)通信功能時的精妙設(shè)計和嚴(yán)謹(jǐn)思考。