一、網卡收包流程
從比較高的層次看,一個數據包從被網卡接收到進入
1、加載網卡驅動,初始化
2、包從外部網絡進入網卡
3、網卡(通過
4、產生硬件中斷,通知系統收到了一個包
5、驅動調用
6、ksoftirqd 進程調用 NAPI 的 poll 函數從 ring buffer 收包(poll 函數是網卡驅動在初始化階段註冊的;每個 CPU 上都運行着一個 ksoftirqd 進程,在系統啓動期間就註冊了)
7、ring buffer 裏包對應的內存區域解除映射(unmapped)
8、(通過
9、如果
1) 、接收數據包是一個複雜的過程,但大致需要以下幾個步驟:
- 網卡收到數據包。
- 將數據包從網卡硬件緩存轉移到服務器內存中。
- 通知內核處理。
- 經過TCP/IP協議逐層處理。
- 應用程序通過read()從socket buffer讀取數據。
2)、NAPI 的使用方式:
1、驅動打開
2、數據包到達,網卡通過
3、網卡觸發一個硬中斷,中斷處理函數開始執行
4、軟中斷(softirq,稍後介紹),喚醒 NAPI 子系統。這會觸發在一個單獨的線程裏,調用驅動註冊的
5、驅動禁止網卡產生新的硬件中斷。這樣做是為了
6、一旦沒有包需要收了,NAPI 關閉,網卡的硬中斷重新開啓
7、轉步驟
和傳統方式相比,NAPI 一次中斷會接收多個包,因此可以減少硬件中斷的數量。
網卡調優
一、RSS、RPS、RFS、Irqbanlace
説明: cat /proc/interrupts 是查看硬件中斷號的方式
cat /proc/softirqs 是查看軟中斷具體分佈
1)、RSS(Receive Side Scaling,接收端擴展): 硬件層支持多隊列。這意味着收進來的包會被通過 DMA 放到位於不同內存的隊列上,而不同的隊列有相應的 NAPI 變量管理軟中斷 poll()過程。因此,多個 CPU 同時處理從網卡來的中斷,處理收包過程。---將每個隊列的硬件中斷進行分配和設置
2)、RPS:(Receive Packet Steering,接收包控制,接收包引導):是
這意味着,RPS 並不會減少 CPU 處理硬件中斷和 NAPI poll(軟中斷最重要的一部分)的時間,但是可以在
RPS 的工作原理是對個 packet 做 hash,以此決定分到哪個 CPU 處理。然後
3)、RFS(Receive flow steering)和 RPS 配合使用。RPS 試圖在 CPU 之間平衡收包,但是沒考慮數據的本地性問題,如何最大化 CPU 緩存的命中率。RFS 將屬於相同 flow 的包送到相同的 CPU進行處理,可以提高緩存命中率。
4)、Irqbanlace 動態設置smp_affinity_list即 RSS屬性
二、調優相關設置:
1、修改 RX queue 數量大多數情況是combined類型(RX queue 和 TX queue 是一對一綁定)
2、調整 RX queue 的大小,增加
3、RSS CPU親和性綁定
4、RPS、RFS設置
查看GRO配置
使用
$ ethtool -k eth0 | grep generic-receive-offload
sudo ethtool -K eth0 gro on
如果
$ sudo sysctl -w net.core.netdev_tstamp_prequeue=0 默認1 打開
7、如果你使用了 RPS,或者你的驅動調用了 netif_rx,那增加
例如:increase backlog to 3000 with sysctl.
$ sudo sysctl -w net.core.netdev_max_backlog=3000
默認值是 1000。
net.core.dev_weight 決定了 backlog poll loop 可以消耗的整體 budget:
$ sudo sysctl -w net.core.dev_weight=600
默認值是 64。
8、路由緩存刷新時間 默認10分鐘
route -Cn #查看路由緩存
三、觀察硬中斷和軟中斷是否均勻
1、硬中斷
2、軟中斷
3、隊列接收包的情況
4、丟包查看
# Format of /proc/net/softnet_stat:
# 每行其實代表一個 CPU
# column 1 : received frames 是處理的網絡幀的數量
# column 2 : dropped 是因為處理不過來而 drop 的網絡幀數量。
# column 3 : time_squeeze 由於budget或time limit用完而退出 net_rx_action 循環的次數
# column 4-8: all zeros
# column 9 : cpu_collision 是為了發送包而獲取鎖的時候有衝突的次數
# column 10 : received_rps CPU 被其他 CPU 喚醒去收包的次數
# column 11 : flow_limit_count 是達到 flow limit 的次數。flow limit 是 RPS 的特性
https://github.com/torvalds/linux/blob/v3.13/net/core/net-procfs.c#L161-L165
5、查看進程是否有綁核操作:taskset -pc <pid>
進程啓動時指定CPU命令taskset -c 1 ./redis-server ../redis.conf
taskset -pc 0-7 <pid>
參考:
https://colobu.com/2019/12/09/monitoring-tuning-linux-networking-stack-receiving-data/#%E8%BD%AF%E4%B8%AD%E6%96%AD%EF%BC%88IRQ%EF%BC%89
https://www.jianshu.com/p/e6162bc984c8
http://arthurchiao.art/blog/tuning-stack-rx-zh/
https://moonton.yuque.com/moonton-dev-team/ops/wugapd
https://www.ibm.com/developerworks/cn/linux/l-napi/index.html
http://arthurchiao.art/blog/monitoring-network-stack/
調優腳本:
#!/bin/bash
CPUS=`cat /proc/cpuinfo| grep "processor"| wc -l`
RPS_LOW_BIT="ffffffff"
RFS_MAX="32768"
is_start_irqbalance=0
SYSTEM=`rpm -q centos-release|cut -d- -f3`
#根據CPU數確定 RPS設置的CPU掩碼
if [ $CPUS -ge 32 ];then
RPS_LOW_BIT="ffffffff" #經測驗,命令行方式設置RPS最大支持32的。32位以上機器需要確認一下是否需要手動設置RPS
else
num=$(((1<<$CPUS)-1)) #根據CPUS計算CPU掩碼 十進制 CPUS超過64無法計算
RPS_LOW_BIT=$(printf %x $num) #轉換為十六進制
fi
# check for irqbalance running
IRQBALANCE_ON=`ps ax | grep -v grep | grep -q irqbalance; echo $?`
if [ "$IRQBALANCE_ON" == "0" ];then
if [ "$SYSTEM" == "6" ];then
/etc/init.d/irqbalance stop
elif [ "$SYSTEM" == "7" ];then
systemctl stop irqbalance
else
echo "系統版本獲取錯誤,請檢查" && exit 1
fi
fi
#設置網卡多隊列、接受queuei的開啓數為當前機器支持的最大數
function set_multiple_queues(){
dev=$1
checks=('RX' 'TX' 'Other' 'Combined')
for check in ${checks[@]}
do
pre_num=`ethtool -l ${dev}|grep "${check}"|sed -n 1p|grep -Eo '([0-9]+)' 2>/dev/null`
cur_num=`ethtool -l ${dev}|grep "${check}"|sed -n 2p|grep -Eo '([0-9]+)' 2>/dev/null`
if [ $cur_num -eq $CPUS ];then
echo "Dev:${dev} $check 等於當前CPU數 不設置"
continue
fi
set_num=$((pre_num>CPUS?CPUS:pre_num)) #處理隊列數>CPU數
set_mode=`echo $check|tr 'A-Z' 'a-z'`
if [ $pre_num -ne $cur_num ];then
ethtool -L ${dev} ${set_mode} ${set_num}
if [ $? -eq 0 ];then
echo -e "Dev:${dev} ${set_mode} ${set_num} 設置成功\n 'ethtool -L ${dev} ${set_mode} ${set_num}'"
else
echo -e "Dev:${dev} ${set_mode} ${set_num} 設置失敗\n 'ethtool -L ${dev} ${set_mode} ${set_num}'"
fi
ethtool -l ${dev}
else
echo "Dev:${dev} $check 當前開啓的多隊列數和允許隊列數一致 不設置"
fi
done
pre_rx_queues=`ethtool -g ${dev}|grep "RX:"|sed -n 1p|grep -Eo '([0-9]+)' 2>/dev/null`
cur_rx_queues=`ethtool -g ${dev}|grep "RX:"|sed -n 2p|grep -Eo '([0-9]+)' 2>/dev/null`
set_rx_num=$((pre_rx_queues>=cur_rx_queues ?pre_rx_queues:cur_rx_queues))
echo "設置 Dev:${dev} Rx queue"
if [ $pre_rx_queues -ne $cur_rx_queues ];then
ethtool -G ${dev} rx $set_rx_num
if [ $? -eq 0 ];then
echo -e "Dev:${dev} RX queue ${set_rx_num} 設置成功\n 'ethtool -G ${dev} rx $set_rx_num'"
else
echo -e "Dev:${dev} RX queue ${set_rx_num} 設置失敗\n 'ethtool -G ${dev} rx $set_rx_num'"
fi
else
echo -e "Dev:${dev} RX queue ${set_rx_num} 已經最大 不設置"
fi
}
#設置網卡的RPS、RFS屬性 --- 軟中斷 cat /proc/softirqs
function set_rps_and_rfs()
{
# $1 dev $2 irq_nums
dev=$1
irq_num=$(($2!=0 ? $2:$CPUS)) #處理無硬件中斷,但支持多隊列屬性的網卡
irqs=`expr $irq_num - 1`
rfs_num=`expr $RFS_MAX / $irq_num` #這裏取irq_nums的主要目的是為了處理隊列數>CPU數的問題
echo $RFS_MAX > /proc/sys/net/core/rps_sock_flow_entries
printf "%s %s %s %s\n" "Dev:$dev" "$RFS_MAX" "to" "/proc/sys/net/core/rps_sock_flow_entries"
for i in `seq 0 $irqs`
do
rps_file="/sys/class/net/$dev/queues/rx-$i/rps_cpus"
rfs_file="/sys/class/net/$dev/queues/rx-$i/rps_flow_cnt"
echo $RPS_LOW_BIT > $rps_file
echo $rfs_num > $rfs_file
printf "%s %s %s %s\n" "Dev:$dev" "$RPS_LOW_BIT" "to" "$rps_file"
printf "%s %s %s %s\n" "Dev:$dev" "$rfs_num" "to" "$rfs_file"
done
}
net_devs=`ls /sys/class/net/|grep -v 'lo|bond'`
for dev in `echo $net_devs`
do
ifconfig |grep $dev >>/dev/null
if [ $? -eq 0 ];then #只針啓用的網卡設置多隊列屬性
echo "1、設置Dev:$dev 的多隊列數值"
ethtool -l ${dev} >>/dev/null 2>&1
if [ $? -eq 0 ];then
set_multiple_queues ${dev}
else
echo "Dev:$dev 不支持ethtool查看多隊列"
fi
else
echo "Dev:${dev} 未啓用 不設置"
continue
fi
echo "2、設置Dev:${dev} RSS屬性"
irq_nums=`grep "${dev}-" /proc/interrupts|wc -l`
if [ $irq_nums -eq 0 ];then
echo "Dev:${dev} 不支持RSS屬性 不設置"
let is_start_irqbalance=$is_start_irqbalance+1
fi
set_cpu=0
for devseq in `grep "${dev}-" /proc/interrupts |awk '{print $1}'|sed -e 's/://g'`
do
#設置網卡的RSS屬性(硬中斷),實現隊列和cpu一一綁定,如果隊列數小於cpu數,則綁定前面的幾核
echo $set_cpu >/proc/irq/${devseq}/smp_affinity_list
#設置cpu親和性,smp_affinity_list支持十進制,smp_affinity需要CPU 掩碼設置。修改smp_affinity_list成功會改變smp_affinity
printf "%s %s %s %s\n" "設置Dev:$dev RSs親和性" "$set_cpu" "for" "/proc/irq/${devseq}/smp_affinity_list"
let set_cpu=$set_cpu+1
let is_start_irqbalance=$is_start_irqbalance-10 #如果有一個支持則不開啓irqbanlance
done
echo "3、設置Dev:${dev} RPS、RFS屬性"
set_rps_and_rfs $dev $irq_nums
done
if [ ${is_start_irqbalance} -gt 0 ];then
echo "4、執行託底操作開啓irqbanlance"
#is_start_irqbalance 如果此參數大於0,表明所有支持多隊列的網卡都無法通過`grep "${dev}-" /proc/interrupts`獲取到硬件中斷信息,目前解決方式是開啓irqbanlance服務
yum install -y irqbalance >/dev/null
if [ "$SYSTEM" == "6" ];then
/etc/init.d/irqbalance start
elif [ "$SYSTEM" == "7" ];then
systemctl start irqbalance
else
echo "系統版本獲取錯誤,請檢查" && exit 1
fi
IRQBALANCE_ON=`ps ax | grep -v grep | grep -q irqbalance; echo $?`
if [ "$IRQBALANCE_ON" -eq "0" ];then
echo "託底操作 irqbalance 開啓服務 成功"
else
echo "託底操作 irqbalance 開啓服務 失敗"
fi
else
echo "4、無需執行託底操作"
fi