《趣談Linux》總結八:網絡系統

在這裏插入圖片描述

33 Socket通信

無論是用socket操作TCP,還是UDP,首先都要調用socket函數,socket函數用於創建一個socket的文件描述符,唯一標識一個socket;把它叫作文件描述符,是因爲在內核中會創建類似文件系統的數據結構,並且後續的操作都有用到它:

int socket(int domain, int type, int protocol);

三個參數的含義:

domain:表示使用什麼IP層協議。AF_INET表示IPv4,AF_INET6表示IPv6。

type:表示socket類型。SOCK_STREAM是TCP面向流的,SOCK_DGRAM是UDP面向數據報的,SOCK_RAW可以直接操作IP層,或者非TCP和UDP的協議。例如ICMP。

protocol表示的協議,包括IPPROTO_TCP、IPPTOTO_UDP。

TCP編程模式:
在這裏插入圖片描述
其總體狀態:用戶態加內核態如圖
在這裏插入圖片描述
UDP編程模式:
在這裏插入圖片描述

34 Socket內核數據結構

在這裏插入圖片描述
首先,Socket系統調用會有三級參數family、type、protocal,通過這三級參數,分別在net_proto_family表
中找到type鏈表,在type鏈表中找到protocal對應的操作。

這個操作分爲兩層,對於TCP協議來講,第一層是inet_stream_ops層,第二層是tcp_prot層。
於是,接下來的系統調用規律就都一樣了:

bind第一層調用inet_stream_ops的inet_bind函數,第二層調用tcp_prot的inet_csk_get_port函數;

listen第一層調用inet_stream_ops的inet_listen函數,第二層調用tcp_prot的inet_csk_get_port函數;

accept第一層調用inet_stream_ops的inet_accept函數,第二層調用tcp_prot的inet_csk_accept函數;

connect第一層調用inet_stream_ops的inet_stream_connect函數,第二層調用tcp_prot的tcp_v4_connect函數。

在內核中,爲每個Socket維護兩個隊列:
一個是已經建立了連接的隊列,這時候連接三次握手已經完畢,處於established狀態;
一個是還沒有完全建立連接的隊列,這個時候三次握手還沒完成,處於syn_rcvd的狀態。

三次連接體現在connect函數中:
在這裏插入圖片描述

35 發送網絡包

35.1 (上)ip層以上是如何發送數據的

socket對於用戶來講,是一個文件一樣的存在,擁有一個文件描述符。
因而對於網絡包的發送,可以使用對於socket文件的寫入系統調用,也就是write系統調用。

根據tcp_prot的定義,tcp的write調用的是tcp_sendmsg。

爲了減少內存拷貝的代價,有的網絡設備支持分散聚合(Scatter/Gather)I/O:
IP層沒必要通過內存拷貝進行聚合,直接讓散的數據零散的放在原處,在設備層進行聚合。
如果使用這種模式,網絡包的數據就不會放在連續的數據區域,而是放在struct skb_shared_info結構裏面指向的離散數據,skb_shared_info的成員變量skb_frag_t frags[MAX_SKB_FRAGS],會指向一個數組的頁面,就不能保證連續了:
在這裏插入圖片描述

  • 總結
    在這裏插入圖片描述
    到ip層,這個過程分成幾個層次:

VFS層:write系統調用找到struct file,根據裏面的file_operations的定義,調用sock_write_iter函數。
sock_write_iter函數調用sock_sendmsg函數。

Socket層:從struct file裏面的private_data得到struct socket,根據裏面ops的定義,調用inet_sendmsg函數。

Sock層:從struct socket裏面的sk得到struct sock,根據裏面sk_prot的定義,調用tcp_sendmsg函數。

TCP層:tcp_sendmsg函數會調用tcp_write_xmit函數,tcp_write_xmit函數會調用tcp_transmit_skb,在這裏實現了TCP層面向連接的邏輯。

IP層:擴展struct sock,得到struct inet_connection_sock,根據裏面icsk_af_ops的定義,調用ip_queue_xmit函數。

35.2 (下)總過程

總過程:
在這裏插入圖片描述
VFS層:write系統調用找到struct file,根據裏面的file_operations的定義,調用sock_write_iter函數。
sock_write_iter函數調用sock_sendmsg函數。

Socket層:從struct file裏面的private_data得到struct socket,根據裏面ops的定義,調用inet_sendmsg
函數。

Sock層:從struct socket裏面的sk得到struct sock,根據裏面sk_prot的定義,調用tcp_sendmsg函數。

TCP層:tcp_sendmsg函數會調用tcp_write_xmit函數,tcp_write_xmit函數會調用tcp_transmit_skb,在這
裏實現了TCP層面向連接的邏輯。

IP層:擴展struct sock,得到struct inet_connection_sock,根據裏面icsk_af_ops的定義,調用
ip_queue_xmit函數。

IP層:ip_route_output_ports函數裏面會調用fib_lookup查找路由表。FIB全稱是Forwarding Information
Base,轉發信息表,也就是路由表。
還要填寫IP層的頭、通過iptables規則。

MAC層:IP層調用ip_finish_output進行MAC層。
MAC層需要ARP獲得MAC地址,因而要調用___neigh_lookup_noref查找屬於同一個網段的鄰居,他會調用
neigh_probe發送ARP。
有了MAC地址,就可以調用dev_queue_xmit發送二層網絡包了,它會調用__dev_xmit_skb會將請求放入
隊列。

設備層:網絡包的發送回觸發一個軟中斷NET_TX_SOFTIRQ來處理隊列中的數據。這個軟中斷的處理函數
是net_tx_action。
在軟中斷處理函數中,會將網絡包從隊列上拿下來,調用網絡設備的傳輸函數ixgb_xmit_frame,將網絡
包發的設備的隊列上去。

36 接收網絡包

網卡作爲一個硬件,接收到網絡包,應該怎麼通知操作系統,這個網絡包到達了呢?
可以觸發一箇中斷。
但是這裏有個問題,就是網絡包的到來,往往是很難預期的。網絡吞吐量比較大的時候,網絡包的到達會十分頻繁。這個時候,如果非常頻繁地去觸發中斷,是個很大的災難。
比如說,CPU正在做某個事情,一些網絡包來了,觸發了中斷,CPU停下手裏的事情,去處理這些網絡包,處理完畢按照中斷處理的邏輯,應該回去繼續處理其他事情。
這個時候,另一些網絡包又來了,又觸發了中斷,CPU手裏的事情又要停下來,去處理網絡包。
能不能要來的一起來,把網絡包好好處理一把,然後再回去集中處理其他事情呢?
“網絡包能不能一起來”,這個沒法控制,但是可以有一種集中時間段的機制:
就是當一些網絡包到來觸發了中斷,內核處理完這些網絡包之後,我們可以先進入主動輪詢poll網卡的方式,主動去接收到來的網絡包,如果一直有,就一直處理,等處理告一段落,就返回幹其他的事情。
當再有下一批網絡包到來的時候,再中斷,再輪詢poll。這樣就會大大減少中斷的數量,提升網絡處理的效率,這種處理方式稱爲NAPI

  • 內核接收網絡包的過程

硬件網卡接收到網絡包之後,通過DMA技術,將網絡包放入Ring Buffer;

硬件網卡通過中斷通知CPU新的網絡包的到來;

網卡驅動程序會註冊中斷處理函數ixgb_intr;

中斷處理函數處理完需要暫時屏蔽中斷的核心流程之後,通過軟中斷NET_RX_SOFTIRQ觸發接下來的處理
過程;

NET_RX_SOFTIRQ軟中斷處理函數net_rx_action,net_rx_action會調用napi_poll,進而調用
ixgb_clean_rx_irq,從Ring Buffer中讀取數據到內核struct sk_buff

調用netif_receive_skb進入內核網絡協議棧,進行一些關於VLAN的二層邏輯處理後,調用ip_rcv進入三層
IP層;

在IP層,會處理iptables規則,然後調用ip_local_deliver交給更上層TCP層;

在TCP層調用tcp_v4_rcv,這裏面有三個隊列需要處理:
如果當前的Socket不是正在被用戶態進程讀取,則放入backlog隊列;
如果正在被讀取,不需要很實時的話,則放入prequeue隊列;
其他情況調用tcp_v4_do_rcv;

在tcp_v4_do_rcv中:
如果是處於TCP_ESTABLISHED狀態,調用tcp_rcv_established;
其他的狀態,調用tcp_rcv_state_process;

在tcp_rcv_established中,調用tcp_data_queue,如果序列號能夠接的上,則放入sk_receive_queue隊列;
如果序列號接不上,則暫時放入out_of_order_queue隊列,等序列號能夠接上的時候,再放入sk_receive_queue隊列。

  • 用戶態讀取網絡包的過程

VFS層:read系統調用找到struct file,根據裏面的file_operations的定義,調用sock_read_iter函數。
sock_read_iter函數調用sock_recvmsg函數。

Socket層:從struct file裏面的private_data得到struct socket,根據裏面ops的定義,調用inet_recvmsg函
數。

Sock層:從struct socket裏面的sk得到struct sock,根據裏面sk_prot的定義,調用tcp_recvmsg函數。

TCP層:tcp_recvmsg函數會依次讀取receive_queue隊列、prequeue隊列和backlog隊列。
在這裏插入圖片描述
用戶態和內核態核心就是通過三個隊列來交互數據

發佈了232 篇原創文章 · 獲贊 260 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章