1. 程式人生 > 實用技巧 >網絡卡軟中斷

網絡卡軟中斷

一、網絡卡收包流程

  從比較高的層次看,一個數據包從被網絡卡接收到進入 socket 接收佇列的整個過程如下:

1載入網絡卡驅動,初始化

2包從外部網路進入網絡卡

3網絡卡(通過 DMA)將包 copy 到核心記憶體中的 ring buffer

4產生硬體中斷,通知系統收到了一個包

5驅動呼叫 NAPI,如果輪詢(poll)還沒開始,就開始輪詢

6ksoftirqd程序呼叫 NAPI 的poll函式從 ring buffer 收包(poll函式是網絡卡驅動在初始化階段註冊的;每個 CPU 上都執行著一個ksoftirqd程序,在系統啟動期間就註冊了)

7ring buffer 裡包對應的記憶體區域解除對映(unmapped)

8(通過 DMA 進入)記憶體的資料包以skb的形式被送至更上層處理

9如果 packet steering 功能開啟,或者網絡卡有多佇列,網絡卡收到的包會被分發到多個 CPU

1)、接收資料包是一個複雜的過程但大致需要以下幾個步驟:

  1. 網絡卡收到資料包。
  2. 將資料包從網絡卡硬體快取轉移到伺服器記憶體中。
  3. 通知核心處理。
  4. 經過TCP/IP協議逐層處理。
  5. 應用程式通過read()socket buffer讀取資料。

2)NAPI 的使用方式:

1驅動開啟 NAPI 功能,預設處於未工作狀態(沒有在收包)

2資料包到達,網絡卡通過 DMA 寫到記憶體

3網絡卡觸發一個硬中斷,

中斷處理函式開始執行

4軟中斷(softirq,稍後介紹),喚醒 NAPI 子系統。這會觸發在一個單獨的執行緒裡,呼叫驅動註冊的poll方法收包

5驅動禁止網絡卡產生新的硬體中斷。這樣做是為了 NAPI 能夠在收包的時候不會被新的中斷打擾

6一旦沒有包需要收了,NAPI 關閉,網絡卡的硬中斷重新開啟

7轉步驟 2

和傳統方式相比,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,接收包控制,接收包引導): RSS 的一種軟體實現。因為是軟體實現的,意味著任何網絡卡都可以使用這個功能,即便是那些只有一個接收佇列的網絡卡。但是,因為它是軟體實現的,這意味著 RPS 只能在 packet 通過 DMA 進入記憶體後,RPS 才能開始工作。

這意味著,RPS 並不會減少 CPU 處理硬體中斷和 NAPIpoll(軟中斷最重要的一部分)的時間,但是可以在 packet 到達記憶體後,將 packet 分到其他 CPU,從其他 CPU 進入協議棧。

RPS 的工作原理是對個 packet 做 hash,以此決定分到哪個 CPU 處理。然後 packet 放到每個 CPU獨佔的接收後備佇列(backlog)等待處理。這個 CPU 會觸發一個程序間中斷向對端 CPU。如果當時對端 CPU 沒有在處理 backlog 佇列收包,這個程序間中斷會觸發它開始從 backlog 收包。因此,netif_receive_skb或者繼續將包送到協議棧,或者交給 RPS,後者會轉交給其他 CPU 處理。

3)RFS(Receive flow steering)和 RPS 配合使用。RPS 試圖在 CPU 之間平衡收包,但是沒考慮資料的本地性問題,如何最大化 CPU 快取的命中率。RFS 將屬於相同 flow 的包送到相同的 CPU進行處理,可以提高快取命中率。

4)Irqbanlace 動態設定smp_affinity_listRSS屬性

二、調優相關設定:

1、修改 RX queue 數量大多數情況是combined型別(RX queue 和 TX queue 是一對一繫結

2、調整 RX queue 的大小增加 RX queue 的大小可以在流量很大的時候緩解丟包問題

3、RSS CPU親和性繫結

4、RPSRFS設定

5、檢視GRO配置

使用 ethtool 修改 GRO 配置

$ ethtool -k eth0 | grep generic-receive-offload

sudo ethtool -K eth0 gro on

6、如果 RPS 開啟了,那這個選項可以將打時間戳的任務分散個其他 CPU,但會帶來一些延遲。

$ sudo sysctl -w net.core.netdev_tstamp_prequeue=0 預設1 開啟

7如果你使用了 RPS,或者你的驅動呼叫了netif_rx,那增加netdev_max_backlog可以改善在enqueue_to_backlog裡的丟包:

例如: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://juejin.im/post/592e756344d90400645d5273

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