网卡软中断

一、网卡收包流程

  从比较高的层次看,一个数据包从被网卡接收到进入 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 处理硬件中断和 NAPI poll(软中断最重要的一部分)的时间,但是可以在 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} 设置成功
 'ethtool -L ${dev} ${set_mode} ${set_num}'"
            else
                echo -e "Dev:${dev} ${set_mode} ${set_num} 设置失败
 '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} 设置成功
 'ethtool -G ${dev} rx $set_rx_num'"
         else
             echo -e "Dev:${dev} RX queue ${set_rx_num} 设置失败
 '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
" "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
" "Dev:$dev" "$RPS_LOW_BIT" "to" "$rps_file"
        printf "%s %s %s %s
" "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
" "设置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

  

原文地址:https://www.cnblogs.com/morse/p/13427503.html