linux网络软中断softirq底层机制及并发优化

简介:

  linux网络软中断softirq底层机制及并发优化

在实际生产系统环境中,我们经常碰到过高的软中断导致CPU的si负载偏高,从而导致性能服务器性能出现瓶颈。而这种瓶颈出现的时候往往是在业务高峰期,此时很多优化手段不敢轻易去上,只能祈祷平稳度过。但是如果能从底层去了解网络软中断,就可以在事前将优化做充足。

1.1.1 软中断

软中断(softirq)表示可延迟函数的所有种类, linux上使用的软中断个数是有限的,linux最多注册32个,目前使用了10个左右,在include/linux/interrupt.h中定义,如下。

enum

{      

        HI_SOFTIRQ=0,

        TIMER_SOFTIRQ,

        NET_TX_SOFTIRQ, 

        NET_RX_SOFTIRQ, 

        BLOCK_SOFTIRQ,

        IRQ_POLL_SOFTIRQ,

        TASKLET_SOFTIRQ,

        SCHED_SOFTIRQ,

        HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the

                            numbering. Sigh! */

        RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

       

        NR_SOFTIRQS

};

软中断(即使同一类型的软中断)可以并发运行在多个CPU上,因此软中断是可重入函数必须使用自旋锁保护其数据结构。一个软中断不会去抢占另外一个软中断。特别适合网络后半段的处理。

1.1.2 网络软中断定义

软中断通过open_softirq函数(定义在kernel/softirq.c文件中)来注册的。open_softirq注册一个软中断处理函数,即在软中断向量表softirq_vec数组中添加新的软中断处理action函数。

我们可以从start_kernel函数开始,该函数定义在init/main.c中。会调用softirq_init()该函数会调用open_softirq函数来注册相关的软中断,但是并没有注册网络相关的软中断:

  该函数如下:

void __init softirq_init(void)

{

        int cpu;

 

        for_each_possible_cpu(cpu) {

                per_cpu(tasklet_vec, cpu).tail =

                        &per_cpu(tasklet_vec, cpu).head;

                per_cpu(tasklet_hi_vec, cpu).tail =

                        &per_cpu(tasklet_hi_vec, cpu).head;

        }

       

        open_softirq(TASKLET_SOFTIRQ, tasklet_action);

        open_softirq(HI_SOFTIRQ, tasklet_hi_action);

}      

            那么网络相关的软中断在哪里呢?其也是在startup_kernel函数中的中,调用链路如下:

            startup_kernel->rest_init->kernel_init->kernel_init_freeable->do_basic_setup();         而do_basic_setup函数会进行驱动设置。会通过调用net_dev_init函数。

net_dev_init函数(定义在net/core/dev.c),最注册软中断,如下:

  open_softirq(NET_TX_SOFTIRQ, net_tx_action); 

open_softirq(NET_RX_SOFTIRQ, net_rx_action);

定义在:kernel/softirq.c文件中

              void open_softirq(int nr, void (*action)(struct softirq_action *))

{              

        softirq_vec[nr].action = action;

}  

这个就是网络接收和发送的软中断,并关联两个函数net_tx_action和net_rx_action。软中断由softirq_action结构体表示,

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

            定义有了,那何时会去调用呢?

1.1.3 软中断调用

这需要回到网卡的中断函数中(位于驱动代码中)

在网卡驱动的中断函数中(如果是e1000,则是e1000_intr函数),其会调用__napi_schedule函数,其调用____napi_schedule,该函数会设置NET_RX_SOFTIRQ。

net/core/dev.c

void __napi_schedule(struct napi_struct *n)

{

        unsigned long flags;

 

        local_irq_save(flags);

        ____napi_schedule(this_cpu_ptr(&softnet_data), n);

        local_irq_restore(flags);

}

/* Called with irq disabled */

static inline void ____napi_schedule(struct softnet_data *sd,

                                     struct napi_struct *napi)

{

        list_add_tail(&napi->poll_list, &sd->poll_list);

        __raise_softirq_irqoff(NET_RX_SOFTIRQ);

}

kernel/softirq.c文件

void __raise_softirq_irqoff(unsigned int nr)

{

        trace_softirq_raise(nr);

        or_softirq_pending(1UL << nr);

}

 

include/linux/interrupt.h文件中:

#define or_softirq_pending(x)  (local_softirq_pending() |= (x))

arch/ia64/include/asm/hardirq.h文件中:

#define local_softirq_pending()         (local_cpu_data->softirq_pending)

arch/ia64/include/asm/processor.h文件中:

#define local_cpu_data          (&__ia64_per_cpu_var(ia64_cpu_info))

ia64_cpu_info的结构体为cpuinfo_ia64,定义在在文件arch/ia64/include/asm/processor.h中,定义了。其中定义了CPU类型,硬件BUG标志, CPU状态等。

struct cpuinfo_ia64 {

        unsigned int softirq_pending;  

………

DECLARE_PER_CPU(struct cpuinfo_ia64, ia64_cpu_info);

__ia64_per_cpu_var是取变量地址。

这样就可以看到,跟软中断相关的字段是每个CPU都有一个64(32位机器就是32)掩码的字段

他描述挂起的软中断。每一位对应相应的软中断。比如0位代表HI_SOFTIRQ.

明白了or_softirq_pending函数设置了CPU中NET_RX_SOFTIRQ,表示软中断挂起。

PS:可以参考Linux协议栈(6)——初始化及链路层实现 文件描述。

netif_rx该函数(net/core/dev.c)不特定于网络驱动程序,主要实现从驱动中获取包并丢到缓存队列中,等待其被处理。有些驱动(例如arch/ia64/hp/sim/simeth.c),在中断函数中调用, netif_rx, 而netif_rx函数调用enqueue_to_backlog函数,最后也会调用____napi_schedule函数。而e1000驱动则是直接调用了__napi_schedule函数.

NET_RX_SOFTIRQ(include/linux/interrupt.h)标记。

现在系统有挂起的软中断了,那么谁去运行呢?

1.1.4 触发软中断

l   当调用local_bh_enable()函数激活本地CPU的软中断时。条件满足就调用do_softirq() 来处理软中断。

l   当do_IRQ()完成硬中断处理时调用irq_exit()时会唤醒ksoftirq来处理软中断。

l   当内核线程ksoftirq/n被唤醒时,处理软中断。

以上几点在不同版本中会略有变化,比如某个函数放被包含在另一个函数里面了。在不影响大局理解的前提下,暂时不用去关心这个。

先来看下do_IRQ函数,该函数(arch/x86/kernel/irq.c文件)处理普通设备的中断。该函数会调用irq_exit()函数。irq_exit函数在kernel/softirq.c文件中定义,该函数会调用local_softirq_pending(),如果有挂起的软中断,就调用invoke_softirq函数,如果ksoftirq在运行就返回,如果没有运行就调用wakeup_softirqd唤醒ksoftirq

执行软中断函数do_softirq 参见于kernel/softirq.c文件,如果有待处理的软中断,会调用__do_softirq()函数, 然后执行相应软中断处理函数,注册两个函数net_tx_action和net_rx_action。

asmlinkage void do_softirq(void)

{      

        __u32 pending;

        unsigned long flags;

               

        if (in_interrupt())

                return;

       

        local_irq_save(flags);

       

        pending = local_softirq_pending();

               

        if (pending)

                __do_softirq();

 

        local_irq_restore(flags);

}

函数中有pending = local_softirq_pending();

#define local_softirq_pending()         (local_cpu_data->softirq_pending)

用于获取是否有挂起的软中断。

每个CPU下都有一个内核函数进程,他叫做ksoftirq/k,如果是第0个CPU,则进程的名字叫做ksoftirq/0。

            真正的软中断处理函数net_rx_action和net_tx_action做什么呢?

1.1.5 软中断执行

            net_rx_action(net/core/dev.c)用作软中断的处理程序,net_rx_action调用设备的poll方法(默认为process_backlog),process_backlog函数循环处理所有分组。调用__skb_dequeue从等待队列移除一个套接字缓冲区。

调用__netif_receive_skb(net/core/dev.c)函数,分析分组类型、处理桥接,然后调用deliver_skb(net/core/dev.c),该函数调用packet_type->func使用特定于分组类型的处理程序。

7fac7d033fc0cce0264869f44901b5e66b633e4a

1.1.6 并行优化

到此我们对软中断的整个流程有了清晰的认识,下面开始针对几个细节进行学习并探究如何在系统中去优化软中断并发。

网线收到帧(包处理后为帧)后,会将帧拷贝到网卡内部的FIFO缓冲区(一般现在网卡都支持DMA,如果支持则放到DMA内存中),然后触发硬件中断。硬件中断函数属于网卡驱动,在网卡驱动中实现。

  中断处理函数会在一个CPU上运行,如果绑定了一个核就在绑定的核上运行。硬中断处理函数构建sk_buff,把frame从网卡FIFO拷贝到内存skb中,然后触发软中断。如果软中断不及时处理内核缓存中的帧,也会导致丢包。这个过程要注意的是,如果网卡中断时绑定在CPU0上处理硬中断的,那么其触发的软中断也是在CPU0上的,因为修改的NET_RX_SOFTIRQ是cpu-per的变量,只有其上的ksoftirq进程会去读取及执行。

多队列网卡由原来的单网卡单队列变成了现在的单网卡多队列。通过多队列网卡驱动的支持,将各个队列通过中断绑定到不同的CPU核上,以满足网卡的需求,这就是多队列网卡的应用。

因此,加大队列数量可以优化系统网络性能,例如10GE的82599网卡,最大可以增加到64个网卡队列。

1.1.6.1  RSS/RPS/RFS/XPS

RSS (Receive Side Scaling ) (接收侧的缩放) 

把不同的流分散的不同的网卡多列中,就是多队列的支持,在2.6.36中引入。网卡多队列的驱动提供了一个内核模块参数,用来指定硬件队列个数。每个接收队列都有一个单独的IRQ(中断号),PCIe设备使用MSI-x来路由每个中断到CPU,有效队列的IRQ的映射由/proc/interrupts来指定的。一个终端能被任何一个CPU处理。一些系统默认运行irqbalance来优化中断(但是在NUMA架构下不太好,不如手动绑定到制定的CPU)。

RPS Receive Packet Steering (接收端包的控制) 

逻辑上以软件方式实现RSS,适合于单队列网卡或者虚拟网卡,把该网卡上的数据流让多个cpu处理。在netif_rx() 函数和netif_receive_skb()函数中调用get_rps_cpu (定义在net/core/dev.c),来选择应该执行包的队列。基于包的地址和端口(有的协议是2元组,有的协议是4元组)来计算hash值。hash值是由硬件来提供的,或者由协议栈来计算的。hash值保存在skb->rx_hash中,该值可以作为流的hash值可以被使用在栈的其他任何地方。每一个接收硬件队列有一个相关的CPU列表,RPS就可以将包放到这个队列中进行处理,也就是指定了处理的cpu.最终实现把软中断的负载均衡到各个cpu。需要配置了才能使用,默认数据包由中断的CPU来处理的。

对于一个多队列的系统,如果RSS已经配置了,导致一个硬件接收队列已经映射到每一个CPU。那么RPS就是多余的和不必要的。如果只有很少的硬件中断队列(比CPU个数少),每个队列的rps_cpus 指向的CPU列表与这个队列的中断CPU共享相同的内存域,那RPS将会是有效的。

RFS Receive Flow Steering (接收端流的控制) :

RPS依靠hash来控制数据包,提供了好的负载平衡,只是单纯把数据包均衡到不同的cpu,如果应用程序所在的cpu和软中断处理的cpu不是同一个,那么对于cpu cache会有影响。

RFS依靠RPS的机制插入数据包到指定CPU队列,并唤醒该CPU来执行。

数据包并不会直接的通过数据包的hash值被转发,但是hash值将会作为流查询表的索引。这个表映射数据流与处理这个流的CPU。流查询表的每条记录中所记录的CPU是上次处理数据流的CPU。如果记录中没有CPU,那么数据包将会使用RPS来处理。多个记录会指向相同的CPU。

rps_sock_flow_table是一个全局的数据流表,sock_rps_record_flow()来记录rps_sock_flow_table表中每个数据流表项的CPU号。

RFS使用了第二个数据流表来为每个数据流跟踪数据包:rps_dev_flow_table被指定到每个设备的每个硬件接收队列。

加速RFS

加速RFS需要内核编译CONFIG_RFS_ACCEL, 需要NIC设备和驱动都支持。加速RFS是一个硬件加速的负载平衡机制。要启用加速RFS,网络协议栈调用ndo_rx_flow_steer驱动函数为数据包通讯理想的硬件队列,这个队列匹配数据流。当rps_dev_flow_table中的每个流被更新了,网络协议栈自动调用这个函数。驱动轮流地使用一种设备特定的方法指定NIC去控制数据包。如果想用RFS并且NIC支持硬件加速,都需要开启硬件加速RFS。

XPS Transmit Packet Steering(发送端包的控制)

XPS要求内核编译了CONFIG_XPS,根据当前处理软中断的cpu选择网卡发包队列, XPS主要是为了避免cpu由RX队列的中断进入到TX队列的中断时发生切换,导致cpu cache失效损失性能

            最后几个优化手段:

l   对于开了超线程的系统,一个中断只绑定到其中一个。

l   对于一个多队列的系统,多列队已经支持。那么RPS就是多余、不必要的。如果只有很少的硬件中断队列(比CPU个数少),每个队列的rps_cpus 指向的CPU列表与这个队列的中断CPU共享相同的内存域,那RPS将会是有效的。

l   RFS主要是为了避免cpu由内核态进入到用户态的时候发生切换,导致cpu cache失效损失性能。

l   不管什么时候,想用RFS并且NIC支持硬件加速,都开启硬件加速RFS。

 

目录
相关文章
|
4天前
|
缓存 监控 关系型数据库
《Linux 简易速速上手小册》第10章: 性能监控与优化(2024 最新版)
《Linux 简易速速上手小册》第10章: 性能监控与优化(2024 最新版)
9 0
|
14天前
|
安全 Linux 虚拟化
网络名称空间在Linux虚拟化技术中的位置
网络名称空间(Network Namespaces)是Linux内核特性之一,提供了隔离网络环境的能力,使得每个网络名称空间都拥有独立的网络设备、IP地址、路由表、端口号范围以及iptables规则等。这一特性在Linux虚拟化技术中占据了核心位置🌟,它不仅为构建轻量级虚拟化解决方案(如容器📦)提供了基础支持,也在传统的虚拟机技术中发挥作用,实现资源隔离和网络虚拟化。
网络名称空间在Linux虚拟化技术中的位置
|
14天前
|
网络协议 安全 Linux
Linux网络名称空间之独立网络资源管理
Linux网络名称空间是一种强大的虚拟化技术🛠️,它允许用户创建隔离的网络环境🌐,每个环境拥有独立的网络资源和配置。这项技术对于云计算☁️、容器化应用📦和网络安全🔒等领域至关重要。本文将详细介绍在Linux网络名称空间中可以拥有的独立网络资源,并指出应用开发人员在使用时应注意的重点。
|
14天前
|
安全 网络协议 Linux
Linux网络名称空间概述
Linux网络名称空间是操作系统级别的一种虚拟化技术🔄,它允许创建隔离的网络环境🌐,使得每个环境拥有自己独立的网络资源,如IP地址📍、路由表🗺️、防火墙规则🔥等。这种技术是Linux内核功能的一部分,为不同的用户空间进程提供了一种创建和使用独立网络协议栈的方式。本文旨在全方面、多维度解释Linux网络名称空间的概念、必要性和作用。
Linux网络名称空间概述
|
22天前
|
Linux
Linux中centos桌面消失网络图标
Linux中centos桌面消失网络图标
13 0
|
1月前
|
Shell Linux C语言
【Shell 命令集合 网络通讯 】Linux 向指定用户或终端发送消息 write命令 使用指南
【Shell 命令集合 网络通讯 】Linux 向指定用户或终端发送消息 write命令 使用指南
34 0
|
1月前
|
安全 Unix Shell
【Shell 命令集合 网络通讯 】Linux 向所有当前登录的用户发送消息或通知 wall命令 使用指南
【Shell 命令集合 网络通讯 】Linux 向所有当前登录的用户发送消息或通知 wall命令 使用指南
28 0
|
12天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
34 6
|
3天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。
|
17小时前
|
安全 Ubuntu Linux
Linux 网络操作命令Telnet
Linux 网络操作命令Telnet
4 0
Linux 网络操作命令Telnet