Linux 網卡驅動學習(六)(應用層、tcp 層、ip 層、設備層和驅動層作用解析)
本文將介紹網絡連接建立的過程、收發包流程,以及當中應用層、tcp層、ip層、設備層和驅動層各層發揮的作用。
1、應用層
對於使用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
假設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狀態:
- linux # netstat -antp | grep 9999
- 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 Mini : 0
- RX Jumbo : 0
- TX : 511
以上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
Linux 網卡驅動學習(六)(應用層、tcp 層、ip 層、設備層和驅動層作用解析)