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。

 

目录
相关文章
|
7天前
|
安全 网络安全 数据安全/隐私保护
访问控制列表(ACL)是网络安全中的一种重要机制,用于定义和管理对网络资源的访问权限
访问控制列表(ACL)是网络安全中的一种重要机制,用于定义和管理对网络资源的访问权限。它通过设置一系列规则,控制谁可以访问特定资源、在什么条件下访问以及可以执行哪些操作。ACL 可以应用于路由器、防火墙等设备,分为标准、扩展、基于时间和基于用户等多种类型,广泛用于企业网络和互联网中,以增强安全性和精细管理。
42 7
|
19天前
|
域名解析 网络协议 安全
|
25天前
|
运维 监控 网络协议
|
9天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
28 5
|
12天前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
29 6
|
11天前
|
数据采集 网络协议 算法
移动端弱网优化专题(十四):携程APP移动网络优化实践(弱网识别篇)
本文从方案设计、代码开发到技术落地,详尽的分享了携程在移动端弱网识别方面的实践经验,如果你也有类似需求,这篇文章会是一个不错的实操指南。
31 1
|
1月前
|
网络协议 Java 应用服务中间件
深入浅出Tomcat网络通信的高并发处理机制
【10月更文挑战第3天】本文详细解析了Tomcat在处理高并发网络请求时的机制,重点关注了其三种不同的IO模型:NioEndPoint、Nio2EndPoint 和 AprEndPoint。NioEndPoint 采用多路复用模型,通过 Acceptor 接收连接、Poller 监听事件及 Executor 处理请求;Nio2EndPoint 则使用 AIO 异步模型,通过回调函数处理连接和数据就绪事件;AprEndPoint 通过 JNI 调用本地库实现高性能,但已在 Tomcat 10 中弃用
深入浅出Tomcat网络通信的高并发处理机制
|
21天前
|
存储 Ubuntu Linux
2024全网最全面及最新且最为详细的网络安全技巧 (三) 之 linux提权各类技巧 上集
在本节实验中,我们学习了 Linux 系统登录认证的过程,文件的意义,并通过做实验的方式对 Linux 系统 passwd 文件提权方法有了深入的理解。祝你在接下来的技巧课程中学习愉快,学有所获~和文件是 Linux 系统登录认证的关键文件,如果系统运维人员对shadow或shadow文件的内容或权限配置有误,则可以被利用来进行系统提权。上一章中,我们已经学习了文件的提权方法, 在本章节中,我们将学习如何利用来完成系统提权。在本节实验中,我们学习了。
|
25天前
|
缓存 监控 前端开发
优化网络应用的性能
【10月更文挑战第21天】优化网络应用的性能
16 2
|
26天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于贝叶斯优化CNN-LSTM网络的数据分类识别算法matlab仿真
本项目展示了基于贝叶斯优化(BO)的CNN-LSTM网络在数据分类中的应用。通过MATLAB 2022a实现,优化前后效果对比明显。核心代码附带中文注释和操作视频,涵盖BO、CNN、LSTM理论,特别是BO优化CNN-LSTM网络的batchsize和学习率,显著提升模型性能。