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

轉自:http://blog.csdn.net/xy010902100449/article/details/47428187

本文將介紹網絡連接建立的過程、收發包流程,以及其中應用層、tcp層、ip層、設備層和驅動層各層發揮的作用。

1、應用層

對於使用socket進行網絡連接的服務器端程序,我們會先調用socket函數創建一個套接字:

  1. fd = socket(AF_INET, SOCK_STREAM, 0);

以上指定了連接協議,socket調用返回一個文件句柄,與socket文件對應的inode不在磁盤上,而是存在於內存。

 

之後我們指定監聽的端口、允許與哪些ip建立連接,並調用bind完成端口綁定:

  1. server_addr.sin_family = AF_INET;
  2. server_addr.sin_port   = htons(PORT);
  3. server_addr.sin_addr.s_addr = INADDR_ANY;
  4. bind(fd, (struct sockaddr_in *)&server_addr, sizeof(struct sockaddr_in));

端口作爲進程的標識,客戶端根據服務器ip和端口號就能找到相應進程。

 

接着我們調用listen函數,對端口進行監聽:

  1. listen(fd, backlog);

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

  1. linux # sysctl -a | grep somaxconn
  2. net.core.somaxconn = 128

監聽端口在listen調用後變爲LISTEN狀態:

  1. linux # netstat -antp | grep 9999
  2. Proto  Recv-Q Send-Q Local Address  Foreign Address  State  PID/Program name
  3. tcp         0      0  0.0.0.0:9999        0.0.0.0:* LISTEN       8709/server

相應地,客戶端調用connect進行連接,tcp三次握手在connect調用返回之前完成:

如果服務器端向客戶端發送SYNACK後,客戶端不返回ACK,則服務器保持半連接(SYN_RECV)狀態:

  1. linux # netstat -np | grep SYN_RECV
  2. tcp      0        0     0.0.0.0:9999  127.0.0.0.1:5334   SYN_RECV  - 

若隊列中的連接均處於半連接狀態,服務器將不能處理正常的請求,syn泛洪攻擊(syn flood)就是利用這個特點完成DoS(拒絕服務攻擊)


當連接數超過隊列長度backlog時,超出的連接也保持爲半連接狀態,直到數量達到內核參數tcp_max_syn_backlog值,超出該值的連接請求將被丟棄:

  1. linux # sysctl -a | grep tcp_max_syn
  2. net.ipv4.tcp_max_syn_backlog = 1024


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

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

其返回一個文件描述符,後續我們可以對該文件描述符調用writeread等操作函數,原監聽端口仍處於LISTEN狀態:

  1. linux # netstat -antp | grep 9999
  2. tcp     0    0    0.0.0.0:9999       0.0.0.0:*      LISTEN  8709/server
  3. tcp     0    0  127.0.0.1:9999 127.0.0.1:52274 ESTABLISHED  -

以上爲網絡連接建立過程中,應用層所做的工作,server端完成了socket創建、端口綁定、端口監聽、連接和收發包任務,而client端相對簡單,只需包含連接和收發包。

 

2、tcp

內核代碼中,tcp_sendmsgtcp發包的主入口函數,該函數中struct sk_buff結構用於描述一個數據包。

對於超過MTU(maximum transmission unit, 最大傳輸單元)的數據包,tcp層會對數據包進行拆分,若開啓了網口的tcp segmentation offload功能,則拆分工作由網卡完成:

  1. linux # ethtool -k ether
  2. Offload parameters for eth1:
  3. rx-checksumming: on
  4. tx-checksumming: on
  5. scatter-gather: on
  6. tcp segmentation offload: on

以下內核參數是內核爲tcp socket預留的用於發送數據包的緩衝區大小,單位爲byte

  1. linux # sysctl -a | grep tcp_wmem
  2. net.ipv4.tcp_wmem = 4096 16384 131072

默認的用於包發送的緩衝區大小爲16M

 

除了用於緩衝收發數據包,對於每個socket,內核還要分配一些數據結構用於保持連接狀態,內核對tcp層可使用的內存大小進行了限制:

  1. linux # sysctl -a | grep tcp_mem
  2. net.ipv4.tcp_mem = 196608 262144 393216

以上值以頁爲單位,分別對應最小值、壓力值和最大值,並在系統啓動、tcp棧初始化時根據內存總量設定。通過proc提供的接口,我們可以查到tcp已用的內存頁數:

  1. linux # cat /proc/net/sockstat
  2. sockets : used 91
  3. 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命令,我們可以查詢網口驅動層環形隊列長度:

  1. linux # ethtool -g eth1
  2. Ring parameters for ether
  3. Pre-set maximums:
  4. RX : 511
  5. RX Mini : 0
  6. RX Jumbo : 0
  7. TX : 511
  8. Current hardware settings:
  9. RX : 200
  10. RX Mini : 0
  11. RX Jumbo : 0
  12. TX : 511

以上RXTX分別指示收包隊列與發包隊列長度,單位爲包個數。

 

網卡接收到數據包時將產生中斷,以通知cpu數據包到來的消息,而網卡接收包又非常繁忙,如果每次收發包都向cpu發送硬中斷,那cpu將忙於處理網卡中斷。

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

 

不像寫文件的過程,磁盤設備層完成內存數據到磁盤拷貝後,會將消息層層上報,這裏的網卡驅動層發包後不會往上層發送通知消息。

 

收包過程

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

 

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

內核代碼中,ip_rcvip層收包的主入口函數,該函數由軟中斷調用。存放數據包的sk_buff結構包含有目的地ip和端口信息,此時ip層進行檢查,如果目的地ip不是本機,則將包丟棄,如果配置了netfilter,則按照配置規則對包進行轉發。

 

tcp_v4_rcvtcp層收包的接收入口,其調用__inet_lookup_skb函數查到數據包需要往哪個socket傳送,之後將數據包放入tcp層收包隊列中,如果應用層有read之類的函數調用,隊列中的包將被取出。

 

tcp層收包使用的內存同樣有限制:

  1. linux # sysctl -a | grep rmem
  2. net.ipv4.tcp_rmem = 4096 16384 131072
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章