天天看點

Linux 網卡驅動學習(六)(應用層、tcp 層、ip 層、裝置層和驅動層作用解析)

本文将介紹網絡連接配接建立的過程、收發包流程,以及當中應用層、tcp層、ip層、裝置層和驅動層各層發揮的作用。

對于使用socket進行網絡連接配接的server端程式。我們會先調用socket函數建立一個套接字:

fd = socket(AF_INET, SOCK_STREAM, 0);

以上指定了連接配接協定,socket調用傳回一個檔案句柄,與socket檔案相應的inode不在磁盤上,而是存在于記憶體。

之後我們指定監聽的port、同意與哪些ip建立連接配接,并調用bind完畢port綁定:

server_addr.sin_family = AF_INET;

server_addr.sin_port   = htons(PORT);

server_addr.sin_addr.s_addr = INADDR_ANY;

bind(fd, (struct sockaddr_in *)&server_addr, sizeof(struct sockaddr_in));

port作為程序的辨別,client依據serverip和port号就能找到對應程序。

接着我們調用listen函數。對port進行監聽:

listen(fd, backlog);

backlog值指定了監聽隊列的長度,下面核心參數限制了backlog可設定的最大值:

linux # sysctl -a | grep somaxconn

net.core.somaxconn = 128

監聽port在listen調用後變為LISTEN狀态:

linux # netstat -antp | grep 9999

Proto  Recv-Q Send-Q Local Address  Foreign Address  State  PID/Program name

tcp         0      0  0.0.0.0:9999        0.0.0.0:* LISTEN       8709/server

對應地。client調用connect進行連接配接,tcp三次握手在connect調用傳回之前完畢:

​​

Linux 網卡驅動學習(六)(應用層、tcp 層、ip 層、裝置層和驅動層作用解析)

假設server端向client發送SYN+ACK後。client不傳回ACK,則server保持半連接配接(SYN_RECV)狀态:

linux # netstat -np | grep SYN_RECV

tcp      0        0     0.0.0.0:9999  127.0.0.0.1:5334   SYN_RECV  - 

若隊列中的連接配接均處于半連接配接狀态。server将不能處理正常的請求,syn泛洪攻擊(syn flood)就是利用這個特點完畢DoS(拒絕服務攻擊)。

當連接配接數超過隊列長度backlog時。超出的連接配接也保持為半連接配接狀态。直到數量達到核心參數tcp_max_syn_backlog值,超出該值的連接配接請求将被丢棄:

linux # sysctl -a | grep tcp_max_syn

net.ipv4.tcp_max_syn_backlog = 1024

accept調用用于處理新到來的連接配接:

new_fd = accept(fd, (struct sockaddr*)&client_addr, &sin_size);

其傳回一個檔案描寫叙述符。興許我們能夠對該檔案描寫叙述符調用write、read等操作函數。原監聽port仍處于LISTEN狀态:

tcp     0    0    0.0.0.0:9999       0.0.0.0:*      LISTEN  8709/server

tcp     0    0  127.0.0.1:9999 127.0.0.1:52274 ESTABLISHED  -

以上為網絡連接配接建立過程中。應用層所做的工作,server端完畢了socket建立、port綁定、port監聽、連接配接和收發包任務,而client端相對簡單,僅僅需包括連接配接和收發包。

2、tcp層

核心代碼中。tcp_sendmsg是tcp發包的主入口函數,該函數中struct

sk_buff結構用于描寫叙述一個資料包。

對于超過MTU(maximum transmission unit, 最大傳輸單元)的資料包。tcp層會對資料包進行拆分,若開啟了網口的tcp

segmentation offload功能,則拆分工作由網卡完畢:

linux # ethtool -k ether

Offload parameters for eth1:

rx-checksumming: on

tx-checksumming: on

scatter-gather: on

tcp segmentation offload: on

下面核心參數是核心為tcp socket預留的用于發送資料包的緩沖區大小,機關為byte:

linux # sysctl -a | grep tcp_wmem

net.ipv4.tcp_wmem = 4096 16384 131072

預設的用于包發送的緩沖區大小為16M。

除了用于緩沖收發資料包。對于每一個socket,核心還要配置設定一些資料結構用于保持連接配接狀态,核心對tcp層可使用的記憶體大小進行了限制:

linux # sysctl -a | grep tcp_mem

net.ipv4.tcp_mem = 196608 262144 393216

以上值以頁為機關。分别相應最小值、壓力值和最大值,并在系統啟動、tcp棧初始化時依據記憶體總量設定。通過proc提供的接口,我們能夠查到tcp已用的記憶體頁數:

linux # cat /proc/net/sockstat

sockets : used 91

TCP : inuse 8 orphan 0 tw 11 alloc 13 mem 2

3、ip層

核心代碼中。ip_queue_xmit函數是ip層的主入口函數,注意ip層與tcp層操作的都是同一塊記憶體(sk_buff結構)。期間并沒有發生資料包相關的記憶體拷貝。

ip層主要完畢查找路由的任務,其依據路由表配置,決定資料包發往哪個網口,另外,該層實作netfilter的功能。

4、網絡裝置層

dev_queue_xmit是網絡裝置層的主入口函數,該層為每一個網口維護一條資料包隊列。由ip層下發的資料包放入相應網口的隊列中。在該層中。資料包不是直接交給網卡,而是先緩沖起來,再通過軟中斷(NET_TX_SOFTIRQ)調用qdisc_run函數。該函數将資料包進一步交由網卡處理。

我們運作ifconfig時。txqueuelen訓示了網絡裝置層中。網口隊列的長度。

5、驅動層

使用不同驅動的網卡,對應的驅動層代碼就不一樣。這裡以e1000網卡為例。e1000_xmit_frame是該層的主入口函數。該層利用環形隊列進行資料包管理,由兩個指針負責維護環形隊列。運作ethtool指令,我們能夠查詢網口驅動層環形隊列長度:

linux # ethtool -g eth1

Ring parameters for ether

Pre-set maximums:

RX : 511

RX Mini : 0

RX Jumbo : 0

TX : 511

Current hardware settings:

RX : 200

以上RX與TX分别訓示收包隊列與發包隊列長度,機關為包個數。

網卡接收到資料包時将産生中斷,以通知cpu資料包到來的消息。而網卡接收包又很繁忙,假設每次收發包都向cpu發送硬中斷,那cpu将忙于處理網卡中斷。

對應的優化方案是NAPI(New API)模式,其關閉網卡硬中斷,使網卡不發送中斷。而非使cpu不接收網卡中斷。在e1000驅動代碼中。由e1000_clean函數實作NAPI模式。

不像寫檔案的過程,磁盤裝置層完畢記憶體資料到磁盤拷貝後,會将消息層層上報,這裡的網卡驅動層發包後不會往上層發送通知消息。

收包過程

以上為網絡發包所需經過的層次結構,以及各層的大體功能,以下我們簡單看下收包過程。

網卡接收到資料包後,通知上層,該過程不會發生拷貝,資料包丢給ip層。

核心代碼中,ip_rcv是ip層收包的主入口函數,該函數由軟中斷調用。

存放資料包的sk_buff結構包括有目的地ip和port資訊,此時ip層進行檢查,假設目的地ip不是本機。則将包丢棄,假設配置了netfilter。則依照配置規則對包進行轉發。

tcp_v4_rcv是tcp層收包的接收入口。其調用__inet_lookup_skb函數查到資料包須要往哪個socket傳送。之後将資料包放入tcp層收包隊列中,假設應用層有read之類的函數調用。隊列中的包将被取出。

tcp層收包使用的記憶體相同有限制:

linux # sysctl -a | grep rmem

net.ipv4.tcp_rmem = 4096 16384 131072

繼續閱讀