首先網路封包會通過網路線-> PHY -> RGMII -> GEMAC。
網路驅動程式會把網路中的封包讀出來放到記憶體的 Ring Buffer 中,這個過程使用 DMA (Direct Memory Access),不需要 Main CPU 參與,Linux核心程式SoftIRQ從 Ring buffer 中讀取封包進行處理,執行 IP 和 TCP/UDP 層的邏輯,最後把封包放到應用程式的 Socket Buffer 中。
應用程式從 socket buffer 中讀取封包進行處理
在接收 UDP 封包的過程中,圖中任何一個過程都可能會主動或者被動地把封包丟棄,因此丟包可能發生在網卡和驅動,也可能發生在系統和應用。
之所以沒有分析發送資料流程程,一是因為發送流程和接收類似,只是方向相反;另外發送流程封包丟失的機會通常比接收小的多。
STB雖然用的GENET1,但是由於GENET0沒有啟動,所以依然是使用,eth0的interface。後面RX (receive)表示接收封包,TX(transmit)表示發送封包。
確認有 UDP 丟包發生的命令
要查看ETHENET MAC是否有丟包? TestApp下使用 ethtool -S eth0 查看,在輸出中查找 bad 或者 drop 對應的欄位是否有資料,在正常情況下,這些欄位對應的數字應該都是 0。如果看到對應的數字在不斷增長,就說明網卡有丟包。
而Client Build 之中沒有 ethtool, ifconfig 也是一樣的效果:
# ifconfig eth0
eth0 Link encap:Ethernet HWaddr AC:6F:BB:8B:2F:CA
inet addr:192.168.2.106 Bcast:192.168.2.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:94710232 errors:7580 dropped:7580 overruns:0 frame:0
TX packets:84125 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes: 852509147 (813.0 MiB) TX bytes:27528913 (26.2 MiB)
這裡可以RX看到掉了一些,計算之後,大約產生了0.0080%的掉包。那UDP的狀況呢? 因為沒有工具,所以只好到/proc之中去看看。
# cd /proc/net/
# /proc/5984/net # cat snmp | grep -i udp
Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti
Udp: 94474880 383 111992 51512 111992 0 0 1151
UdpLite: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti
UdpLite: 0 0 0 0 0 0 0 0
這裡看到的數字就非常差了: 這裡的單位是Package,
InDatagrams 94,474,880 // 進來的UDP封包
NoPorts 383 //
InErrors 111992 // 進來的UDP封包 產生的Error
OutDatagrams 51512
RcvbufErrors 111992 // RcvbufErrors, 0.11% Error,這裡的掉包就是Buffer Size不足,這個數字與InErrors的值是一樣的
SndbufErrors 0
InCsumErrors 0
IgnoredMulti 1151 // 被忽略的Multicast 封包。
此外,TestApp可以使用 netstat -s 命令查看,加上 --udp 可以只看 UDP 相關的封包資料:
# netstat -s -u <Enter>
IcmpMsg:
InType0: 3
InType3: 1719356
InType8: 13
InType11: 59
OutType0: 13
OutType3: 1737641
OutType8: 10
OutType11: 263
Udp:
517488890 packets received
2487375 packets to unknown port received.
47533568 packet receive errors
147264581 packets sent
12851135 receive buffer errors
0 send buffer errors
UdpLite:
…
IpExt:
OutMcastPkts: 696
InBcastPkts: 2373968
InOctets: 4954097451540
OutOctets: 5538322535160
OutMcastOctets: 79632
InBcastOctets: 934783053
InNoECTPkts: 5584838675
對於上面的輸出,關注下面的資訊來查看 UDP 丟包的情況:
packet receive errors 不為 0 ,並且在一直增長,說明系統有 UDP 丟包。
packets to unknown port received 表示系統接收到的 UDP 封包所在的目標埠沒有應用在監聽,一般是服務沒有啟動導致的,並不會造成嚴重的問題。
receive buffer errors 表示因為 UDP 的接收Buufer太小導致丟包的數量。
並不是丟包數量不為零就有問題,對於 UDP 來說,如果有少量的丟包很可能是預期的行為,比如丟包率(丟包數量/接收封包數量)在萬分之一甚至更低。
網卡或者驅動程式丟包
之前講過,如果 ethtool -S eth0 中有 rx_***_errors 那麼很可能是實體網路有問題,導致系統丟包。
# ethtool -S eth0 | grep rx_ | grep errors
rx_crc_errors: 0
rx_missed_errors: 0
rx_long_length_errors: 0
rx_short_length_errors: 0
rx_align_errors: 0
rx_errors: 0
rx_length_errors: 0
rx_over_errors: 0
rx_frame_errors: 0
rx_fifo_errors: 0
netstat -i 也會提供每個網卡的接發封包以及丟包的情況,正常情況下輸出中 error 或者 drop 應該為 0。
如果硬體或者驅動沒有問題,一般網卡丟包是因為設置的緩存區(ring buffer)太小,可以使用 ethtool 命令查看和設置網卡的 ring buffer。
ethtool -g 可以查看某個網卡的 ring buffer,比如下面的例子
# ethtool -g eth0
Ring parameters for eth0:
Pre-set maximums:
RX: 4096
RX Mini: 0
RX Jumbo: 0
TX: 4096
Current hardware settings:
RX: 256
RX Mini: 0
RX Jumbo: 0
TX: 256
Pre-set 表示網卡最大的 ring buffer 值,可以使用 ethtool -G eth0 rx 8192 設置它的值。
Linux 系統丟包
Linux 系統丟包的原因很多,常見的有:UDP 封包錯誤、UDP buffer size 不足、系統負載過高等,這裡對這些丟包原因進行分析。
UDP 封包錯誤
如果在傳輸過程中UDP 封包被修改,會導致 checksum 錯誤,或者長度錯誤,Linux 在接收到 UDP 封包時會對此進行校驗,一旦發明錯誤會把封包丟棄。
如果希望 UDP 封包 checksum 及時有錯也要發送給應用程式,可以在通過 socket 參數關掉 UDP checksum 檢查:
int disable = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_NO_CHECK, (void*)&disable, sizeof(disable)
UDP buffer size 不足
Linux 系統在接收封包之後,會把封包保存到緩存區中。因為緩存區的大小是有限的,如果出現 UDP 封包過大(超過緩存區大小或者 MTU 大小)、接收到封包的速率太快,都可能導致 Linux 因為緩存滿而直接丟包的情況。
在系統層面,Linux 設置了 receive buffer 可以配置的最大值,可以在下面的檔中查看,一般是 Linux 在啟動的時候會根據記憶體大小設置一個初始值。
/proc interfaces
System-wide UDP parameter settings can be accessed by files in the directory /proc/sys/net/ipv4/.
udp_mem: This is a vector of three integers governing the number of pages allowed for queueing by all UDP sockets.
min: Below this number of pages, UDP is not bothered about its memory appetite.
When the amount of memory allocated by UDP exceeds this number, UDP starts to moderate memory usage.
pressure: This value was introduced to follow the format of tcp_mem (see tcp(7)).
max: Number of pages allowed for queueing by all UDP sockets.
Defaults values for these three items are calculated at boot time from the amount of available memory.
udp_rmem_min (integer; default value: PAGE_SIZE;)
Minimal size, in bytes, of receive buffers used by UDP sockets in moderation.
Each UDP socket is able to use the size for receiving data, even if total pages of UDP sockets exceed udp_mem pressure.
udp_wmem_min (integer; default value: PAGE_SIZE;)
Minimal size, in bytes, of send buffer used by UDP sockets in moderation.
Each UDP socket is able to use the size for sending data, even if total pages of UDP sockets exceed udp_mem pressure.
/proc/sys/net/core/rmem_max: 允許設置的 receive buffer 最大值
/proc/sys/net/core/rmem_default:預設使用的 receive buffer 值
/proc/sys/net/core/wmem_max: 允許設置的 send buffer 最大值
/proc/sys/net/core/wmem_dafault:預設使用的 send buffer 最大值
在G5盒子上的值如下:
/proc/sys/net/core # cat rmem_max 2097512
/proc/sys/net/core # cat rmem_def* 163840
/proc/sys/net/core # cat netdev_budget 300
/proc/sys/net/core # cat netdev_max_backlog 1000
/proc/sys/net/ipv4 # cat udp_mem 8379 11175 16758
/proc/sys/net/ipv4 # cat udp_rmem_min 4096
/proc/sys/net/ipv4 # cat udp_wmem_min 4096
這時候回頭看了一下DT_G4的盒子:
/proc/1599/net # uptime
07:19:26 up 1:30, 0 users, load average: 0.37, 0.33, 0.28
/proc/1599/net # cat snmp | grep -i udp
Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti
Udp: 1392551 161 0 3731 0 0 0 86
UdpLite: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti
UdpLite: 0 0 0 0 0 0 0 0
InError 都是 0。
/proc/1599/net # ifconfig eth0
eth0 Link encap:Ethernet HWaddr AC:6F:BB:57:18:DA
inet addr:192.168.2.111 Bcast:192.168.2.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1465386 errors:0 dropped:0 overruns:0 frame:0
TX packets:16647 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1993097447 (1.8 GiB) TX bytes:3483616 (3.3 MiB)
網卡也沒有掉包。於是檢查一下UDP Buffer的設定,有沒有差異?
唯一有差異的是 udp_mem。
/proc/sys/net/ipv4 # cat udp_mem
10029 13375 20058 // G4 DevBuild 的設定
/proc/sys/net/ipv4 # cat udp_mem
8379 11175 16758 // G5 DevBuild 的設定
G5 的Size 小了 20%。又看了 一下TestApp
G4 : 11640 11520 23280
G5: 10626 14168 21252
都不一樣。
如何調大一些呢?
# echo "1640 11520 23280" > udp_mem <Enter>
可以使用 sysctl 命令讓它立即生效:
sysctl -w net.core.rmem_max=1024000
sysctl -w net.core.wmem_max=1024000
sysctl -w net.core.wmem_default=1024000
sysctl -w net.core.rmem_default=1024000
sysctl -w net.ipv4.udp_mem="1024000 1024000 1024000"
sysctl -w net.ipv4.udp_rmem_min=1024000
sysctl -w net.ipv4.udp_wmem_min=1024000
設定後,可以查詢
# sysctl -a
# sysctl -w net.core.rmem_max=26214400 # 設定BUFFER為 25M
也可以修改 /etc/sysctl.conf 中對應的參數在下次啟動時讓參數保持生效。
如果封包封包過大,可以在發送方對資料進行分割,保證每個封包的大小在 MTU 內。
另外一個可以配置的參數是 netdev_max_backlog,它表示 Linux 核心程式從網卡驅動中讀取封包後可以BUFFER的封包數量,預設是 1000,可以調大這個值,比如設置成 2000:
# sysctl -w net.core.netdev_max_backlog=2000
系統負載過高
系統 CPU、memory、IO 負載過高都有可能導致網路丟包,比如 CPU 如果負載過高,系統沒有時間進行封包的 checksum 計算、複製記憶體等操作,從而導致網卡或者 socket buffer 出丟包;memory 負載過高,會應用程式處理過慢,無法及時處理封包;IO 負載過高,CPU 都用來回應 IO wait,沒有時間處理緩存中的 UDP 封包。
Linux 系統本身就是相互關聯的系統,任何一個元件出現問題都有可能影響到其他元件的正常運行。對於系統負載過高,要麼是應用程式有問題,要麼是系統不足。對於前者需要及時發現,debug 和修復;對於後者,也要及時發現並擴容。
應用丟包
上面提到系統的 UDP buffer size,調節的 sysctl 參數只是系統允許的最大值,每個應用程式在創建 socket 時需要設置自己 socket buffer size 的值。
Linux 系統會把接受到的封包放到 socket 的 buffer 中,應用程式從 buffer 中不斷地讀取封包。所以這裡有兩個和應用有關的因素會影響是否會丟包:socket buffer size 大小以及應用程式讀取封包的速度。
對於第一個問題,可以在應用程式初始化 socket 的時候設置 socket receive buffer 的大小,比如下面的代碼把 socket buffer 設置為 20MB:
uint64_t receive_buf_size = 20*1024*1024; //20 MB
setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &receive_buf_size, sizeof(receive_buf_size));
很明顯,增加應用的 receive buffer 會減少丟包的可能性。另外一個因素是應用讀取 buffer 中封包的速度,對於應用程式來說,處理封包應該採取非同步的方式
包丟在什麼地方??
想要詳細瞭解 Linux 系統在執行哪個函數時丟包的話,可以使用 dropwatch 工具,它監聽系統丟包資訊,目前的Kernel當中,已經有加入 kfree_skb()的資訊,並列印出丟包發生的函數位址:
# dropwatch -l kas
Initalizing kallsyms db
dropwatch> start
Enabling monitoring...
Kernel monitoring activated.
Issue Ctrl-C to stop monitoring
1 drops at tcp_v4_do_rcv+cd (0xffffffff81799bad)
10 drops at tcp_v4_rcv+80 (0xffffffff8179a620)
1 drops at sk_stream_kill_queues+57 (0xffffffff81729ca7)
4 drops at unix_release_sock+20e (0xffffffff817dc94e)
1 drops at igmp_rcv+e1 (0xffffffff817b4c41)
1 drops at igmp_rcv+e1 (0xffffffff817b4c41)
通過這些資訊,找到對應的核心程式代碼處,就能知道核心程式在哪個步驟中把封包丟棄,以及大致的丟包原因。可惜目前的系統之中,沒有加入這個命令。
此外,還可以使用 Linux perf 工具也可以監聽 kfree_skb()(把網路封包丟棄時會調用該函數) 使用方式:
# perf record -g -a -e skb:kfree_skb
# perf script
關於 perf 命令的使用和解讀,網上有很多文章可以參考。

沒有留言:
張貼留言