精华内容
下载资源
问答
  • 2021-03-07 15:50:43

    说起这个softirq ,很多人还是一头雾水,觉得这个是什么东西,跟tasklets 和 workqueue有什么不同。

    每次谈到这个,很多人,包括我,都是有点紧张,特别是面试的时候,因为你一旦说错了什么,那么你这次面试估计就歇菜了。

    谈到这个,我们不得不说中断,中断处理,我相信很多人都是知道的,中断分为上半部和下半部,原来的Linux 内核是没有下半部的,中断来了,就在中断里处理事件,说白了,就是执行一些函数操作,但是这个会导致一个问题,就是系统调度慢了,对于用户来说,你用手机的时候,感觉到十分卡顿,真想摔了这个手机,所以,后来就出现了中断下半部。

    中断下半部的机制有很多种。例如:softirq、tasklet、workqueue或是直接创建一个kernel thread来执行bottom half(这在旧的kernel驱动中常见,现在,一个理智的driver厂商是不会这么做的

    tasklet 是基于softirq实现的,所以我们讨论tasklet的时候,其实也是在说softirq,他们都是运行在中断上下文的。

    workqueue和softirq不同的是,它运行是进程上下文。

    为什么需要这么多机制来实现中断下半部呢?

    你可以理解,我如果要去纽约,我可以坐飞机,可以坐轮船,也可以开小汽车,甚至,我还可以骑自行车,中断下半部也是一样,tasklet的出现,是因为把softirq封装起来,更加方便别人使用。workqueue的话,紧急程度就没有softirq那么紧急,可以说优先级没有那么高,如果是非常紧急的事情,比如网络事件,我们还是优先使用softirq来实现。

    Linux 内核里面可以有多少种softirq呢?

    softirq是一种非常紧急的事件,所以说,不是你想用就用的,内核里面定义了一个枚举变量来说明softirq支持的类型。

    /* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
     frequency threaded job scheduling. For almost all the purposes
     tasklets are more than enough. F.e. all serial device BHs et
     al. should be converted to tasklets, not to softirqs.
     */
    
    enum
    {
    	HI_SOFTIRQ=0,
    	TIMER_SOFTIRQ,
    	NET_TX_SOFTIRQ,
    	NET_RX_SOFTIRQ,
    	BLOCK_SOFTIRQ,
    	BLOCK_IOPOLL_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
    };
    
    

    看到上面的那段英文了没,它说,「大部分情况下,tasklets已经满足你的要求,就不要只想着用softirq

    软中断是什么时候执行的呢?

    软中断和workqueue存在非常大的区别,我们在上面说过,软中断是在中断上下文的,但是中断上半部已经脱离了中断了,它如何跟中断上下文存在千丝万缕的联系呢?说到这里,我们不拿出代码来说,那就是耍流氓了。

    /*
     * Exit an interrupt context. Process softirqs if needed and possible:
     */
    void irq_exit(void)
    {
    #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
    	local_irq_disable();
    #else
    	WARN_ON_ONCE(!irqs_disabled());
    #endif
    
    	account_irq_exit_time(current);
    	preempt_count_sub(HARDIRQ_OFFSET);
    	if (!in_interrupt() && local_softirq_pending())
    //此处判断是否从中断上下文退出,并判断是否有软中断任务挂起待处理
    		invoke_softirq();
    //启用,排程软中断处理
    
    	tick_irq_exit();
    	rcu_irq_exit();
    	trace_hardirq_exit(); /* must be last! */
    }
    

    我们看看irq_exit()这个函数,这个函数是在什么时候调用?就是中断上半部执行结束后,需要跳出中断上半部的时候,我们需要执行irq_exit(),然后在里面有一个判断。

    if (!in_interrupt() && local_softirq_pending())
    		invoke_softirq();
    
    	tick_irq_exit();
    	rcu_irq_exit();
    	trace_hardirq_exit(); /* must be last! */
    }
    

    这几行代码就是判断当前是否需要执行软中断,说白了,就是CPU需要执行一段优先级非常高的代码,这段代码需要在中断结束,关闭中断后马上执行。

    #define local_softirq_pending() this_cpu_read(irq_stat.__softirq_pending)
    

    那我们在中断上半部执行结束后,如何设置需要执行softirq呢?使用这个函数

    #define set_softirq_pending(x) __this_cpu_write(irq_stat.__softirq_pending, (x))
    

    softirq相关的代码都在 softirq.c 这个文件里面,如果想有更深入了解的同学,可以一睹源码风采,不吹,C语言的源码真是一个宝藏,细细评味,可以挖掘出非常多的好东西。

    我们分析下 invoke_softirq

    这个函数是执行softirq的函数,里面也有一些判断的东西,我看了下源码,理解了下,顺便解读下自己的看法,如果有疑问或者问题的,请读者们指出来,只有不断的探讨,大家才可能收获更多的东西。

    static inline void invoke_softirq(void)
    {
    	if (!force_irqthreads) {
    #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
    		/*
    		 * We can safely execute softirq on the current stack if
    		 * it is the irq stack, because it should be near empty
    		 * at this stage.
    		 */
    		__do_softirq();
    #else
    		/*
    		 * Otherwise, irq_exit() is called on the task stack that can
    		 * be potentially deep already. So call softirq in its own stack
    		 * to prevent from any overrun.
    		 */
    		do_softirq_own_stack();
    #endif
    	} else {
    		wakeup_softirqd();
    	}
    }
    

    不会看注释的码农不是好码农,不写注释的码农就不是码农,如果你想写代码,就一定要会写注释,同时,你也要会看别人的注释,好吧,这类源码都是老外写的,所以我们一定要习惯看英文注释,慢慢看,不要着急,学会上下文推敲,这样才像一个大神嘛。

    我不是大神,所以,我就瞎说一下上面使用一个 force_irqthreads 来区分一个东西,就是软中断上下文,软中断上下文是不能睡眠的,你知道的,你要是在中断里面睡眠,那系统调度就起不来了,起不来的原因那可真是五花八门,因为你不知道进入中断的时候做了什么事情,在中断里面的时候,我们只有更高优先级的中断才能打断当前中断,现在新版本的Linux内核取消了中断嵌套,那你要是在中断里面睡觉,就没有人叫你起床了,那就只能出现panic,挂机了。用我的话来说,那就是睡死了。

    如果你的softirq里面执行很多东西,在软中断上文没有执行完,那你就需要用到软中断线程把剩下的事情做完,然后就出现了wakeup_softirqd(),这个就是处理软中断下半部的。

    看看__do_softirq()里面做的事情吧

    asmlinkage __visible void __softirq_entry __do_softirq(void)
    {
     unsigned long end = jiffies + MAX_SOFTIRQ_TIME; //最大处理时间:2毫秒
     unsigned long old_flags = current->flags;
     int max_restart = MAX_SOFTIRQ_RESTART; //最大回圈次数:10次
     struct softirq_action *h;
     bool in_hardirq;
     __u32 pending;
     int softirq_bit;
    
     /*
     * Mask out PF_MEMALLOC s current task context is borrowed for the
     * softirq. A softirq handled such as network RX might set PF_MEMALLOC
     * again if the socket is related to swap
     */
     current->flags &= ~PF_MEMALLOC;
    
     pending = local_softirq_pending(); //获取本地CPU上等待处理的软中断掩码
     account_irq_enter_time(current);
    
     __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
     in_hardirq = lockdep_softirq_start();
    
    restart:
     /* Reset the pending bitmask before enabling irqs */
     set_softirq_pending(0); //清除本地CPU上等待处理的软中断掩码
    
     local_irq_enable(); // 开中断状态下处理软中断
    
     h = softirq_vec; // h指向软中断处理函式阵列首元素
    
     while ((softirq_bit = ffs(pending))) { //依次处理软中断,软中断编号越小,越优先处理,优先顺序越高
     unsigned int vec_nr;
     int prev_count;
    
     h += softirq_bit - 1;
    
     vec_nr = h - softirq_vec;
     prev_count = preempt_count();
    
     kstat_incr_softirqs_this_cpu(vec_nr);
    
     trace_softirq_entry(vec_nr);
     h->action(h); //呼叫软中断回拨处理函式
     trace_softirq_exit(vec_nr);
     if (unlikely(prev_count != preempt_count())) {
     pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
     vec_nr, softirq_to_name[vec_nr], h->action,
     prev_count, preempt_count());
     preempt_count_set(prev_count);
     }
    
     //回圈下一个等待处理的软中断
     h++;
     pending >>= softirq_bit;
     }
    
     rcu_bh_qs();
     local_irq_disable(); //关中断,判断在处理上次软中断期间,硬中断处理函式是否又排程了软中断
    
     pending = local_softirq_pending();
     if (pending) { //软中断再次被排程
     if (time_before(jiffies, end) && !need_resched() &&
     --max_restart) //没有达到超时时间,也不需要被排程,并且排程次数也没有超过10次
     goto restart; //重新执行软中断
    
     wakeup_softirqd(); //否则唤醒软中断核心执行绪处理剩下的软中断,当前CPU退出软中断上下文
     }
    
     lockdep_softirq_end(in_hardirq);
     account_irq_exit_time(current);
     __local_bh_enable(SOFTIRQ_OFFSET);
     WARN_ON_ONCE(in_interrupt());
     current_restore_flags(old_flags, PF_MEMALLOC);
    }
    

    这段代码主要是展示了软中断上下文的处理策略,一次处理所有等待的软中断,处理轮训结束或者时间超过2ms,就跳出软中断上下文,跑到软中断线程里面去执行,避免系统响应过慢。

    如何加入新的软中断?

    说了那么多,这个应该是重点了,一直强调,不要加软中断,使用tasklet就够了,但是无法避免就是有人要用啊。

    内核使用下面函数来新增一个软中断

    void open_softirq(int nr, void (*action)(struct softirq_action *))
    {
    	softirq_vec[nr].action = action;
    }
    

    当然了,你可以新增一个枚举变量

    内核里面是这样使用下面这个函数调用

    open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);
    

    好了,就说这么多~

    更多相关内容
  • linux softirq机制

    2021-05-16 02:32:04
    Copyright © 2003 by 詹荣开E-mail:zhanrk@sohu.comLinux-2.4.0Version 1.0.0,2003-2-14摘要:本文主要从内核实现的角度分析了Linux 2.4.0内核的Softirq机制。本文是为那些想要了解Linux I/O子系统的读者和Linux...

    Copyright © 2003 by 詹荣开

    E-mail:zhanrk@sohu.com

    Linux-2.4.0

    Version 1.0.0,2003-2-14

    摘要:本文主要从内核实现的角度分析了Linux 2.4.0内核的Softirq机制。本文是为那些想要了解Linux I/O子系统的读者和Linux驱动程序开发人员而写的。

    关键词:Linux、Softirq、软中断、Bottom half、设备驱动程序

    申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文档协议(GFDL)。

    你应该已经和文档一起收到一份GNU通用公共许可证(GPL)的副本。如果还没有,写信给:

    The Free Software Foundation, Inc., 675 Mass Ave, Cambridge,MA02139, USA

    欢迎各位指出文档中的错误与疑问。

    前言

    中断服务程序往往都是在CPU关中断的条件下执行的,以避免中断嵌套而使控制复杂化。但是CPU关中断的时间不能太长,否则容易丢失中断信号。为此,Linux将中断服务程序一分为二,各称作“Top Half”和“Bottom Half”。前者通常对时间要求较为严格,必须在中断请求发生后立即或至少在一定的时间限制内完成。因此为了保证这种处理能原子地完成,Top Half通常是在CPU关中断的条件下执行的。具体地说,Top Half的范围包括:从在IDT中登记的中断入口函数一直到驱动程序注册在中断服务队列中的ISR。而Bottom Half则是Top Half根据需要来调度执行的,这些操作允许延迟到稍后执行,它的时间要求并不严格,因此它通常是在CPU开中断的条件下执行的。

    但是,Linux的这种Bottom Half(以下简称BH)机制有两个缺点,也即:(1)在任意一时刻,系统只能有一个CPU可以执行Bottom Half代码,以防止两个或多个CPU同时来执行Bottom Half函数而相互干扰。因此BH代码的执行是严格“串行化”的。(2)BH函数不允许嵌套。

    这两个缺点在单CPU系统中是无关紧要的,但在SMP系统中却是非常致命的。因为BH机制的严格串行化执行显然没有充分利用SMP系统的多CPU特点。为此,Linux2.4内核在BH机制的基础上进行了扩展,这就是所谓的“软中断请求”(softirq)机制。

    6.1 软中断请求机制

    Linux的softirq机制是与SMP紧密不可分的。为此,整个softirq机制的设计与实现中自始自终都贯彻了一个思想:“谁触发,谁执行”(Who marks,Who runs),也即触发软中断的那个CPU负责执行它所触发的软中断,而且每个CPU都由它自己的软中断触发与控制机制。这个设计思想也使得softirq 机制充分利用了SMP系统的性能和特点。

    6.1.1 软中断描述符

    Linux在include/linux/interrupt.h头文件中定义了数据结构softirq_action,来描述一个软中断请求,如下所示:

    /* softirq mask and active fields moved to irq_cpustat_t in

    * asm/hardirq.h to get better cache usage. KAO

    */

    struct softirq_action

    {

    void (*action)(struct softirq_action *);

    void *data;

    };

    其中,函数指针action指向软中断请求的服务函数,而指针data则指向由服务函数自行解释的数据。

    基于上述软中断描述符,Linux在kernel/softirq.c文件中定义了一个全局的softirq_vec[32]数组:

    static struct softirq_action softirq_vec[32] __cacheline_aligned;

    在这里系统一共定义了32个软中断请求描述符。软中断向量i(0≤i≤31)所对应的软中断请求描述符就是softirq_vec[i]。这个数组是个系统全局数组,也即它被所有的CPU所共享。这里需要注意的一点是:每个CPU虽然都由它自己的触发和控制机制,并且只执行他自己所触发的软中断请求,但是各个CPU所执行的软中断服务例程却是相同的,也即都是执行softirq_vec[]数组中定义的软中断服务函数。

    6.1.2 软中断触发机制

    要实现“谁触发,谁执行”的思想,就必须为每个CPU都定义它自己的触发和控制变量。为此,Linux在include/asm- i386/hardirq.h头文件中定义了数据结构irq_cpustat_t来描述一个CPU的中断统计信息,其中就有用于触发和控制软中断的成员变量。数据结构irq_cpustat_t的定义如下:

    /* entry.S is sensitive to the offsets of these fields */

    typedef struct {

    unsigned int __softirq_active;

    unsigned int __softirq_mask;

    unsigned int __local_irq_count;

    unsigned int __local_bh_count;

    unsigned int __syscall_count;

    unsigned int __nmi_count; /* arch dependent */

    } ____cacheline_aligned irq_cpustat_t;

    结构中每一个成员都是一个32位的无符号整数。其中__softirq_active和__softirq_mask就是用于触发和控制软中断的成员变量。

    ①__softirq_active变量:32位的无符号整数,表示软中断向量0~31的状态。如果bit[i](0≤i≤31)为1,则表示软中断向量i在某个CPU上已经被触发而处于active状态;为0表示处于非活跃状态。

    ②__softirq_mask变量:32位的无符号整数,软中断向量的屏蔽掩码。如果bit[i](0≤i≤31)为1,则表示使能(enable)软中断向量i,为0表示该软中断向量被禁止(disabled)。

    根据系统中当前的CPU个数(由宏NR_CPUS表示),Linux在kernel/softirq.c文件中为每个CPU都定义了它自己的中断统计信息结构,如下所示:

    /* No separate irq_stat for s390, it is part of PSA */

    #if !defined(CONFIG_ARCH_S390)

    irq_cpustat_t irq_stat[NR_CPUS];

    #endif /* CONFIG_ARCH_S390 */

    这样,每个CPU都只操作它自己的中断统计信息结构。假设有一个编号为id的CPU,那么它只能操作它自己的中断统计信息结构irq_stat [id](0≤id≤NR_CPUS-1),从而使各CPU之间互不影响。这个数组在include/linux/irq_cpustat.h头文件中也作了原型声明。

    l 触发软中断请求的操作函数

    函数__cpu_raise_softirq()用于在编号为cpu的处理器上触发软中断向量nr。它通过将相应的__softirq_active成员变量中的相应位设置为1来实现软中断触发。如下所示(include/linux/interrupt.h):

    static inline void __cpu_raise_softirq(int cpu, int nr)

    {

    softirq_active(cpu) |= (1<

    }

    为了保证“原子”性地完成软中断的触发过程,Linux在interrupt.h头文件中对上述内联函数又作了高层封装,也即函数 raise_softirq()。该函数向下通过调用__cpu_raise_softirq()函数来实现软中断的触发,但在调用该函数之前,它先通过 local_irq_save()函数来关闭当前CPU的中断并保存标志寄存器的内容,如下所示:

    /* I do not want to use atomic variables now, so that cli/sti */

    static inline void raise_softirq(int nr)

    {

    unsigned long flags;

    local_irq_save(flags);

    __cpu_raise_softirq(smp_processor_id(), nr);

    local_irq_restore(flags);

    }

    6.1.3 Linux对软中断的预定义分类

    在软中断向量0~31中,Linux内核仅仅使用了软中断向量0~3,其余被留待系统以后扩展。Linux在头文件include/linux/interrupt.h中对软中断向量0~3进行了预定义:

    /* PLEASE, avoid to allocate new softirqs, if you need not _really_ high

    frequency threaded job scheduling. For almost all the purposes

    tasklets are more than enough. F.e. all serial device BHs et

    al. should be converted to tasklets, not to softirqs.

    */

    enum

    {

    HI_SOFTIRQ=0,

    NET_TX_SOFTIRQ,

    NET_RX_SOFTIRQ,

    TASKLET_SOFTIRQ

    };

    其中,软中断向量0(即HI_SOFTIRQ)用于实现高优先级的软中断,如:高优先级的tasklet(将在后面详细描述)。软中断向量1和2 则分别用于网络数据的发送与接收。软中断向量3(即TASKLET_SOFTIRQ)则用于实现诸如tasklet这样的一般性软中断。关于 tasklet我们将在后面详细描述。NOTE!Linix内核并不鼓励一般用户扩展使用剩余的软中断向量,因为它认为其预定义的软中断向量 HI_SOFTIRQ和TASKLET_SOFTIRQ已经足够应付绝大多数应用。

    6.1.4 软中断机制的初始化

    函数softirq_init()完成softirq机制的初始化。该函数由内核启动例程start_kernel()所调用。函数源码如下所示(kernel/softirq.c):

    void __init softirq_init()

    {

    int i;

    for (i=0; i<32; i++)

    tasklet_init(bh_task_vec+i, bh_action, i);

    open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);

    open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);

    }

    初始化的过程如下:

    (1)先用一个for循环来初始化用于实现BH机制的bh_task_vec[32]数组。这一点我们将在后面详细解释。

    (2)调用open_softirq()函数开启使用软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ,并将它们的软中断服务函数指针分别指向tasklet_action()函数和tasklet_hi_action()函数。函数open_softirq()的主要作用是初始化设置软中断请求描述符softirq_vec[nr]。

    6.1.5 开启一个指定的软中断向量

    函数open_softirq()用于开启一个指定的软中断向量nr,也即适当地初始化软中断向量nr所对应的软中断描述符 softirq_vec[nr]。它主要做两件事情:(1)初始化设置软中断向量nr所对应的软中断描述符softirq_vec[nr]。(2)将所有 CPU的软中断屏蔽掩码变量__softirq_mask中的对应位设置为1,以使能该软中断向量。该函数的源码如下所示(kernel/softirq.c):

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

    {

    unsigned long flags;

    int i;

    spin_lock_irqsave(&softirq_mask_lock, flags);

    softirq_vec[nr].data = data;

    softirq_vec[nr].action = action;

    for (i=0; i

    softirq_mask(i) |= (1<

    spin_unlock_irqrestore(&softirq_mask_lock, flags);

    }

    6.1.6 软中断服务的执行函数do_softirq()

    函数do_softirq()负责执行数组softirq_vec[32]中设置的软中断服务函数。每个CPU都是通过执行这个函数来执行软中断服务的。由于同一个CPU上的软中断服务例程不允许嵌套,因此,do_softirq()函数一开始就检查当前CPU是否已经正出在中断服务中,如果是则 do_softirq()函数立即返回。举个例子,假设CPU0正在执行do_softirq()函数,执行过程产生了一个高优先级的硬件中断,于是 CPU0转去执行这个高优先级中断所对应的中断服务程序。总所周知,所有的中断服务程序最后都要跳转到do_IRQ()函数并由它来依次执行中断服务队列中的ISR,这里我们假定这个高优先级中断的ISR请求触发了一次软中断,于是do_IRQ()函数在退出之前看到有软中断请求,从而调用 do_softirq()函数来服务软中断请求。因此,CPU0再次进入do_softirq()函数(也即do_softirq()函数在CPU0上被重入了)。但是在这一次进入do_softirq()函数时,它马上发现CPU0此前已经处在中断服务状态中了,因此这一次do_softirq()函数立即返回。于是,CPU0回到该开始时的do_softirq()函数继续执行,并为高优先级中断的ISR所触发的软中断请求补上一次服务。从这里可以看出,do_softirq()函数在同一个CPU上的执行是串行的。

    函数源码如下(kernel/softirq.c):

    asmlinkage void do_softirq()

    {

    int cpu = smp_processor_id();

    __u32 active, mask;

    if (in_interrupt())

    return;

    local_bh_disable();

    local_irq_disable();

    mask = softirq_mask(cpu);

    active = softirq_active(cpu) & mask;

    if (active) {

    struct softirq_action *h;

    restart:

    /* Reset active bitmask before enabling irqs */

    softirq_active(cpu) &= ~active;

    local_irq_enable();

    h = softirq_vec;

    mask &= ~active;

    do {

    if (active & 1)

    h->action(h);

    h++;

    active >>= 1;

    } while (active);

    local_irq_disable();

    active = softirq_active(cpu);

    if ((active &= mask) != 0)

    goto retry;

    }

    local_bh_enable();

    /* Leave with locally disabled hard irqs. It is critical to close

    * window for infinite recursion, while we help local bh count,

    * it protected us. Now we are defenceless.

    */

    return;

    retry:

    goto restart;

    }

    结合上述源码,我们可以看出软中断服务的执行过程如下:

    (1)调用宏in_interrupt()来检测当前CPU此次是否已经处于中断服务中。该宏定义在hardirq.h,请参见5.7节。

    (2)调用local_bh_disable()宏将当前CPU的中断统计信息结构中的__local_bh_count成员变量加1,表示当前CPU已经处在软中断服务状态。

    (3)由于接下来要读写当前CPU的中断统计信息结构中的__softirq_active变量和__softirq_mask变量,因此为了保证这一个操作过程的原子性,先用local_irq_disable()宏(实际上就是cli指令)关闭当前CPU的中断。

    (4)然后,读当前CPU的__softirq_active变量值和__softirq_mask变量值。当某个软中断向量被触发时(即 __softirq_active变量中的相应位被置1),只有__softirq_mask变量中的相应位也为1时,它的软中断服务函数才能得到执行。因此,需要将__softirq_active变量和__softirq_mask变量作一次“与”逻辑操作。

    (5)如果active变量非0,说明需要执行软中断服务函数。因此:①先将当前CPU的__softirq_active中的相应位清零,然后用local_irq_enable()宏(实际上就是sti指令)打开当前CPU的中断。②将局部变量mask中的相应位清零,其目的是:让 do_softirq()函数的这一次执行不对同一个软中断向量上的再次软中断请求进行服务,而是将它留待下一次do_softirq()执行时去服务,从而使do_sottirq()函数避免陷入无休止的软中断服务中。③用一个do{}while循环来根据active的值去执行相应的软中断服务函数。 ④由于接下来又要检测当前CPU的__softirq_active变量,因此再一次调用local_irq_disable()宏关闭当前CPU的中断。⑤读取当前CPU的__softirq_active变量的值,并将它与局部变量mask进行与操作,以看看是否又有其他软中断服务被触发了(比如前面所说的那种情形)。如果有的话,那就跳转到entry程序段(实际上是跳转到restart程序段)重新执行软中断服务。如果没有的话,那么此次软中断服务过程就宣告结束。

    (6)最后,通过local_bh_enable()宏将当前CPU的__local_bh_count变量值减1,表示当前CPU已经离开软中断服务状态。宏local_bh_enable()也定义在include/asm-i386/softirq.h头文件中。

    展开全文
  • Linux 中断子系统之softirq 1 前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),是全程关闭中断的,另外一部分是deferable task(bottom half),属于不那么紧急需要处理的事情。...

    Linux 中断子系统之softirq

    1 前言

    对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),是全程关闭中断的,另外一部分是deferable task(bottom half),属于不那么紧急需要处理的事情。在执行bottom half的时候,是开中断的。有多种bottom half的机制,例如:softirq、tasklet、workqueue或是直接创建一个kernel thread来执行bottom half(这在旧的kernel驱动中常见,现在,一个理智的driver厂商是不会这么做的)。本文主要讨论softirq机制。由于tasklet是基于softirq的,因此本文也会提及tasklet,但主要是从需求层面考虑,不会涉及其具体的代码实现。

    在普通的驱动中一般是不会用到softirq,但是由于驱动经常使用的tasklet是基于softirq的,因此,了解softirq机制有助于撰写更优雅的driver。softirq不能动态分配,都是静态定义的。内核已经定义了若干种softirq number,例如网络数据的收发、block设备的数据访问(数据量大,通信带宽高),timer的deferable task(时间方面要求高)。

    2 Softirq机制

    2.1 Softirq number

    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
    };
    

    HI_SOFTIRQ用于高优先级的tasklet;
    TASKLET_SOFTIRQ用于普通的tasklet。TIMER_SOFTIRQ是for software timer的(所谓software timer就是说该timer是基于系统tick的);
    NET_TX_SOFTIRQ和NET_RX_SOFTIRQ是用于网卡数据收发的;
    BLOCK_SOFTIRQ和BLOCK_IOPOLL_SOFTIRQ是用于block device的;
    SCHED_SOFTIRQ用于多CPU之间的负载均衡的;
    HRTIMER_SOFTIRQ用于高精度timer的。RCU_SOFTIRQ是处理RCU的。

    2.2 Softirq 描述符

    softirq是静态定义的,也就是说系统中有一个定义softirq描述符的数组,而softirq number就是这个数组的index。这个概念和早期的静态分配的中断描述符概念是类似的。具体定义如下:

    struct softirq_action
    {
    	void	(*action)(struct softirq_action *);
    };
    static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
    

    系统支持多少个软中断,静态定义的数组就会有多少个entry。____cacheline_aligned保证了在SMP的情况下,softirq_vec是对齐到cache line的。softirq描述符非常简单,只有一个action成员,表示如果触发了该softirq,那么应该调用action回调函数来处理这个soft irq。对于硬件中断而言,其mask、ack等都是和硬件寄存器相关并封装在irq chip函数中,对于softirq,没有硬件寄存器,只有“软件寄存器”,定义如下:

    typedef struct { 
        unsigned int __softirq_pending; 
    #ifdef CONFIG_SMP 
        unsigned int ipi_irqs[NR_IPI]; 
    #endif 
    } ____cacheline_aligned irq_cpustat_t;
    
    irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
    

    ipi_irqs这个成员用于处理器之间的中断,我们留到下一个专题来描述。__softirq_pending就是这个“软件寄存器”。softirq采用谁触发,谁负责处理的。例如:当一个驱动的硬件中断被分发给了指定的CPU,并且在该中断handler中触发了一个softirq,那么该CPU负责调用该softirq number对应的action callback来处理该软中断。因此,这个“软件寄存器”应该是每个CPU拥有一个(专业术语叫做banked register)。为了性能,irq_stat中的每一个entry被定义对齐到cache line

    2.3 Softirq注册

    通过调用open_softirq接口函数可以注册softirq的action callback函数,具体如下:

    void open_softirq(int nr, void (*action)(struct softirq_action *)) 
    { 
        softirq_vec[nr].action = action; 
    }
    

    softirq_vec是一个多CPU之间共享的数据,不过,由于所有的注册都是在系统初始化的时候完成的,那时候,系统是串行执行的。此外,softirq是静态定义的,每个entry(或者说每个softirq number)都是固定分配的,因此,不需要保护。

    2.4 Softirq触发

    在linux kernel中,可以调用raise_softirq这个接口函数来触发本地CPU上的softirq,具体如下:

    void raise_softirq(unsigned int nr) 
    { 
        unsigned long flags;
    
        local_irq_save(flags); 
        raise_softirq_irqoff(nr); 
        local_irq_restore(flags); 
    }
    

    虽然大部分的使用场景都是在中断handler中(也就是说关闭本地CPU中断)来执行softirq的触发动作,但是,这不是全部,在其他的上下文中也可以调用raise_softirq。因此,触发softirq的接口函数有两个版本,一个是raise_softirq,有关中断的保护,另外一个是raise_softirq_irqoff,调用者已经关闭了中断,不需要关中断来保护“soft irq status register”。
    所谓trigger softirq,就是在__softirq_pending(也就是上面说的soft irq status register)的某个bit置一。从上面的定义可知,__softirq_pending是per cpu的,因此不需要考虑多个CPU的并发,只要disable本地中断,就可以确保对,__softirq_pending操作的原子性。

    具体raise_softirq_irqoff的代码如下:

    inline void raise_softirq_irqoff(unsigned int nr) 
    { 
        __raise_softirq_irqoff(nr); ----------------(1if (!in_interrupt()) 
            wakeup_softirqd();------------------(2}
    

    (1)__raise_softirq_irqoff函数设定本CPU上的__softirq_pending的某个bit等于1,具体的bit是由soft irq number(nr参数)指定的。

    (2)如果在中断上下文,我们只要set __softirq_pending的某个bit就OK了,在中断返回的时候自然会进行软中断的处理。但是,如果在context上下文调用这个函数的时候,我们必须要调用wakeup_softirqd函数用来唤醒本CPU上的softirqd这个内核线程。具体softirqd的内容请参考下一个章节。
    2.5 Softirq disable/enable
    在linux kernel中,可以使用local_irq_disable和local_irq_enable来disable和enable本CPU中断。和硬件中断一样,软中断也可以disable,接口函数是local_bh_disable和local_bh_enable。虽然和想像的local_softirq_enable/disable有些出入,不过bh这个名字更准确反应了该接口函数的意涵,因为local_bh_disable/enable函数就是用来disable/enable bottom half的,这里就包括softirq和tasklet。

    先看disable吧,毕竟禁止bottom half比较简单:

    static inline void local_bh_disable(void) 
    { 
        __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET); 
    }
    
    static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt) 
    { 
        preempt_count_add(cnt); 
        barrier(); 
    }
    

    看起来disable bottom half比较简单,就是讲current thread info上的preempt_count成员中的softirq count的bit field9~15加上一就OK了。

    enable函数比较复杂,如下:

    static inline void local_bh_enable(void) 
    { 
        __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET); 
    }
    
    void __local_bh_enable_ip(unsigned long ip, unsigned int cnt) 
    { 
        WARN_ON_ONCE(in_irq() || irqs_disabled());-----------(1preempt_count_sub(cnt - 1); ------------------(2if (unlikely(!in_interrupt() && local_softirq_pending())) { -------(3do_softirq(); 
        }
    
        preempt_count_dec(); ---------------------(4preempt_check_resched(); 
    }
    

    (1)disable/enable bottom half是一种内核同步机制。在硬件中断的handler(top half)中,不应该调用disable/enable bottom half函数来保护共享数据,因为bottom half其实是不可能抢占top half的。同样的,soft irq也不会抢占另外一个soft irq的执行,也就是说,一旦一个softirq handler被调度执行(无论在哪一个processor上),那么,本地的softirq handler都无法抢占其运行,要等到当前的softirq handler运行完毕后,才能执行下一个soft irq handler。注意:上面我们说的是本地,是local,softirq handler是可以在多个CPU上同时运行的,但是,linux kernel中没有disable all softirq的接口函数(就好像没有disable all CPU interrupt的接口一样,注意体会local_bh_enable/disable中的local的含义)。

    说了这么多,一言以蔽之,local_bh_enable/disable是给进程上下文使用的,用于防止softirq handler抢占local_bh_enable/disable之间的临界区的。

    irqs_disabled接口函数可以获知当前本地CPU中断是否是disable的,如果返回1,那么当前是disable 本地CPU的中断的。如果irqs_disabled返回1,有可能是下面这样的代码造成的:

    local_irq_disable();
    
    …… 
    local_bh_disable();
    
    ……
    
    local_bh_enable(); 
    …… 
    local_irq_enable();
    

    本质上,关本地中断是一种比关本地bottom half更强劲的锁,关本地中断实际上是禁止了top half和bottom half抢占当前进程上下文的运行。也许你会说:这也没有什么,就是有些浪费,至少代码逻辑没有问题。但事情没有这么简单,在local_bh_enable—>do_softirq—>__do_softirq中,有一条无条件打开当前中断的操作,也就是说,原本想通过local_irq_disable/local_irq_enable保护的临界区被破坏了,其他的中断handler可以插入执行,从而无法保证local_irq_disable/local_irq_enable保护的临界区的原子性,从而破坏了代码逻辑。

    in_irq()这个函数如果不等于0的话,说明local_bh_enable被irq_enter和irq_exit包围,也就是说在中断handler中调用了local_bh_enable/disable。这道理是和上面类似的,这里就不再详细描述了。

    (2)在local_bh_disable中我们为preempt_count增加了SOFTIRQ_DISABLE_OFFSET,在local_bh_enable函数中应该减掉同样的数值。这一步,我们首先减去了(SOFTIRQ_DISABLE_OFFSET-1),为何不一次性的减去SOFTIRQ_DISABLE_OFFSET呢?考虑下面运行在进程上下文的代码场景:

    ……
    
    local_bh_disable
    
    ……需要被保护的临界区……
    
    local_bh_enable
    
    ……
    

    在临界区内,有进程context 和softirq共享的数据,因此,在进程上下文中使用local_bh_enable/disable进行保护。假设在临界区代码执行的时候,发生了中断,由于代码并没有阻止top half的抢占,因此中断handler会抢占当前正在执行的thread。在中断handler中,我们raise了softirq,在返回中断现场的时候,由于disable了bottom half,因此虽然触发了softirq,但是不会调度执行。因此,代码返回临界区继续执行,直到local_bh_enable。一旦enable了bottom half,那么之前raise的softirq就需要调度执行了,因此,这也是为什么在local_bh_enable会调用do_softirq函数。

    调用do_softirq函数来处理pending的softirq的时候,当前的task是不能被抢占的,因为一旦被抢占,下一次该task被调度运行的时候很可能在其他的CPU上去了(还记得吗?softirq的pending 寄存器是per cpu的)。因此,我们不能一次性的全部减掉,那样的话有可能preempt_count等于0,那样就允许抢占了。因此,这里减去了(SOFTIRQ_DISABLE_OFFSET-1),既保证了softirq count的bit field9~15被减去了1,又保持了preempt disable的状态。

    (3)如果当前不是interrupt context的话,并且有pending的softirq,那么调用do_softirq函数来处理软中断。

    (4)该来的总会来,在step 2中我们少减了1,这里补上,其实也就是preempt count-1。

    (5)在softirq handler中很可能wakeup了高优先级的任务,这里最好要检查一下,看看是否需要进行调度,确保高优先级的任务得以调度执行。

    2.6 Softirq的处理

    我们说softirq是一种defering task的机制,也就是说top half没有做的事情,需要延迟到bottom half中来执行。那么具体延迟到什么时候呢?这是本节需要讲述的内容,也就是说soft irq是如何调度执行的。

    在上一节已经描述一个softirq被调度执行的场景,本节主要关注在中断返回现场时候调度softirq的场景。我们来看中断退出的代码,具体如下:

    void irq_exit(void) 
    { 
    …… 
        if (!in_interrupt() && local_softirq_pending()) 
            invoke_softirq();
    
    …… 
    }
    

    代码中“!in_interrupt()”这个条件可以确保下面的场景不会触发sotfirq的调度:

    (1)中断handler是嵌套的。也就是说本次irq_exit是退出到上一个中断handler。当然,在新的内核中,这种情况一般不会发生,因为中断handler都是关中断执行的。

    (2)本次中断是中断了softirq handler的执行。也就是说本次irq_exit是不是退出到进程上下文,而是退出到上一个softirq context。这一点也保证了在一个CPU上的softirq是串行执行的(注意:多个CPU上还是有可能并发的)

    我们继续看invoke_softirq的代码:

    static inline void invoke_softirq(void) 
    { 
        if (!force_irqthreads) { 
    #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK 
            __do_softirq(); 
    #else 
            do_softirq_own_stack(); 
    #endif 
        } else { 
            wakeup_softirqd(); 
        } 
    }
    

    force_irqthreads是和强制线程化相关的,主要用于interrupt handler的调试(一般而言,在线程环境下比在中断上下文中更容易收集调试数据)。如果系统选择了对所有的interrupt handler进行线程化处理,那么softirq也没有理由在中断上下文中处理(中断handler都在线程中执行了,softirq怎么可能在中断上下文中执行)。本身invoke_softirq这个函数是在中断上下文中被调用的,如果强制线程化,那么系统中所有的软中断都在sofirq的daemon进程中被调度执行。

    如果没有强制线程化,softirq的处理也分成两种情况,主要是和softirq执行的时候使用的stack相关。如果arch支持单独的IRQ STACK,这时候,由于要退出中断,因此irq stack已经接近全空了(不考虑中断栈嵌套的情况,因此新内核下,中断不会嵌套),因此直接调用__do_softirq()处理软中断就OK了,否则就调用do_softirq_own_stack函数在softirq自己的stack上执行。当然对ARM而言,softirq的处理就是在当前的内核栈上执行的,因此do_softirq_own_stack的调用就是调用__do_softirq(),代码如下(删除了部分无关代码):

    asmlinkage void __do_softirq(void) 
    {
    
    ……
    
        pending = local_softirq_pending();----------获取softirq pending的状态
    
        __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);-标识下面的代码是正在处理softirq
    
        cpu = smp_processor_id(); 
    restart: 
        set_softirq_pending(0); ---------清除pending标志
    
        local_irq_enable(); ------打开中断,softirq handler是开中断执行的
    
        h = softirq_vec; -------获取软中断描述符指针
    
        while ((softirq_bit = ffs(pending))) {----寻找pending中第一个被设定为1的bit 
            unsigned int vec_nr; 
            int prev_count;
    
            h += softirq_bit - 1; ------指向pending的那个软中断描述符
            vec_nr = h - softirq_vec;----获取soft irq number 
            h->action(h);---------指向softirq handler 
    
            h++; 
            pending >>= softirq_bit; 
        }
    
        local_irq_disable(); -------关闭本地中断
    
        pending = local_softirq_pending();----------(注1if (pending) { 
            if (time_before(jiffies, end) && !need_resched() && 
                --max_restart) 
                goto restart;
    
            wakeup_softirqd(); 
        }
    
        __local_bh_enable(SOFTIRQ_OFFSET);----------标识softirq处理完毕 
    }
    

    (注1)再次检查softirq pending,有可能上面的softirq handler在执行过程中,发生了中断,又raise了softirq。如果的确如此,那么我们需要跳转到restart那里重新处理soft irq。当然,也不能总是在这里不断的loop,因此linux kernel设定了下面的条件:
    (1)softirq的处理时间没有超过2个ms
    (2)上次的softirq中没有设定TIF_NEED_RESCHED,也就是说没有有高优先级任务需要调度
    (3)loop的次数小于 10次

    因此,只有同时满足上面三个条件,程序才会跳转到restart那里重新处理soft irq。否则wakeup_softirqd就OK了。这样的设计也是一个平衡的方案。一方面照顾了调度延迟:本来,发生一个中断,系统期望在限定的时间内调度某个进程来处理这个中断,如果softirq handler不断触发,其实linux kernel是无法保证调度延迟时间的。另外一方面,也照顾了硬件的thoughput:已经预留了一定的时间来处理softirq。

    展开全文
  • POLL_SOFTIRQ, /* 块设备软中断 */ TASKLET_SOFTIRQ, /* tasklet软中断 */ SCHED_SOFTIRQ, /* 进程调度及负载均衡的软中断 */ HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on ...

    背景Read the fucking source code!  --By 鲁迅

    A picture is worth a thousand words. --By 高尔基

    说明:Kernel版本:4.14

    ARM64处理器,Contex-A53,双核

    使用工具:Source Insight 3.5, Visio

    1. 概述

    中断子系统中有一个重要的设计机制,那就是Top-half和Bottom-half,将紧急的工作放置在Top-half中来处理,而将耗时的工作放置在Bottom-half中来处理,这样确保Top-half能尽快完成处理,那么为什么需要这么设计呢?看一张图就明白了:

    5e9526f97892e5e42572490ac56e8c49.pngARM处理器在进行中断处理时,处理器进行异常模式切换,此时会将中断进行关闭,处理完成后再将中断打开;

    如果中断不分上下半部处理,那么意味着只有等上一个中断完成处理后才会打开中断,下一个中断才能得到响应。当某个中断处理处理时间较长时,很有可能就会造成其他中断丢失而无法响应,这个显然是难以接受的,比如典型的时钟中断,作为系统的脉搏,它的响应就需要得到保障;

    中断分成上下半部处理可以提高中断的响应能力,在上半部处理完成后便将中断打开(通常上半部处理越快越好),这样就可以响应其他中断了,等到中断退出的时候再进行下半部的处理;

    中断的Bottom-half机制,包括了softirq、tasklet、workqueue、以及前文中提到过的中断线程化处理等,其中tasklet又是基于softirq来实现的,这也是本文讨论的主题;

    在中断处理过程中,离不开各种上下文的讨论,了解不同上下文的区分有助于中断处理的理解,所以,还是来一张老图吧:

    1efb0bc38c225918840ef812de806015.pngtask_struct结构体中的thread_info.preempt_count用于记录当前任务所处的context状态;

    PREEMPT_BITS:用于记录禁止抢占的次数,禁止抢占一次该值就加1,使能抢占该值就减1;

    SOFTIRQ_BITS:用于同步处理,关掉下半部的时候加1,打开下半部的时候减1;

    HARDIRQ_BITS:用于表示处于硬件中断上下文中;

    前戏结束了,直奔主题吧。

    2. softirq

    2.1 初始化

    softirq不支持动态分配,Linux kernel提供了静态分配,关键的结构体描述如下,可以类比硬件中断来理解:/* 支持的软中断类型,可以认为是软中断号, 其中从上到下优先级递减 */

    enum

    {

    HI_SOFTIRQ=0,       /* 最高优先级软中断 */

    TIMER_SOFTIRQ,      /* Timer定时器软中断 */

    NET_TX_SOFTIRQ,     /* 发送网络数据包软中断 */

    NET_RX_SOFTIRQ,     /* 接收网络数据包软中断 */

    BLOCK_SOFTIRQ,      /* 块设备软中断 */

    IRQ_POLL_SOFTIRQ,   /* 块设备软中断 */

    TASKLET_SOFTIRQ,    /* tasklet软中断 */

    SCHED_SOFTIRQ,      /* 进程调度及负载均衡的软中断 */

    HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on thenumbering. Sigh! */

    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq, RCU相关的软中断 */

    NR_SOFTIRQS

    };

    /* 软件中断描述符,只包含一个handler函数指针 */

    struct softirq_action {

    void(*action)(struct softirq_action *);

    };

    /* 软中断描述符表,实际上就是一个全局的数组 */

    static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

    /* CPU软中断状态描述,当某个软中断触发时,__softirq_pending会置位对应的bit */

    typedef struct {

    unsigned int __softirq_pending;

    unsigned int ipi_irqs[NR_IPI];

    } ____cacheline_aligned irq_cpustat_t;

    /* 每个CPU都会维护一个状态信息结构 */

    irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

    /* 内核为每个CPU都创建了一个软中断处理内核线程 */

    DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

    来一张图吧:

    a2f91718d88074baba33fa21ac76b63c.pngsoftirq_vec[]数组,类比硬件中断描述符表irq_desc[],通过软中断号可以找到对应的handler进行处理,比如图中的tasklet_action就是一个实际的handler函数;

    软中断可以在不同的CPU上并行运行,在同一个CPU上只能串行执行;

    每个CPU维护irq_cpustat_t状态结构,当某个软中断需要进行处理时,会将该结构体中的__softirq_pending字段或上1UL << XXX_SOFTIRQ;

    2.2 流程分析

    2.2.1 软中断注册

    中断处理流程中设备驱动通过request_irq/request_threaded_irq接口来注册中断处理函数,而在软中断处理流程中,通过open_softirq接口来注册,由于它实在是太简单了,我忍不住想把代码贴上来:void open_softirq(int nr, void (*action)(struct softirq_action *))

    {

    softirq_vec[nr].action = action;

    }

    也就是将软中断描述符表中对应描述符的handler函数指针指向对应的函数即可,以便软中断到来时进行回调。

    那么,问题来了,什么时候进行软中断函数回调呢?

    2.2.2 软中断执行之一:中断处理后

    先看第一种情况,用图片来回答问题:

    3a099b32e58d4e7cab6b5103f41e752e.pngLinux中断子系统(二)-通用框架处理文章中讲述了整个中断处理流程,在接收到中断信号后,处理器进行异常模式切换,并跳转到异常向量表处进行执行,关键的流程为:el0_irq->irq_handler->handle_arch_irq(gic->handle_irq)->handle_domain_irq->__handle_domain_irq;

    在__handle_domain_irq函数中,irq_enter和irq_exit分别用于来标识进入和离开硬件中断上下文处理,这个从preempt_count_add/preempt_count_sub来操作HARDIRQ_OFFSET可以看出来,这也对应到了上文中的Context描述图;

    在离开硬件中断上下文后,如果!in_interrupt() && local_softirq_pending为真,则进行软中断处理。这个条件有两个含义:1)!in_interrupt()表明不能处在中断上下文中,这个范围包括in_nmi、in_irq、in_softirq(Bottom-half disable)、in_serving_softirq,凡是处于这几种状态下,软中断都不会被执行;2)local_softirq_pending不为0,表明有软中断处理请求;

    软中断执行的入口就是invoke_softirq,继续分析一波:

    1993ffa253a88d625fb012e8748a4ba2.pnginvoke_softirq函数中,根据中断处理是否线程化进行分类处理,如果中断已经进行了强制线程化处理(中断强制线程化,需要在启动的时候传入参数threadirqs),那么直接通过wakeup_softirqd唤醒内核线程来执行,否则的话则调用__do_softirq函数来处理;

    Linux内核会为每个CPU都创建一个内核线程ksoftirqd,通过smpboot_register_percpu_thread函数来完成,其中当内核线程运行时,在满足条件的情况下会执行run_ksoftirqd函数,如果此时有软中断处理请求,调用__do_softirq来进行处理;

    上图中的逻辑可以看出,最终的核心处理都放置在__do_softirq函数中完成:

    9fe174ed4ceddc55ebdc6a37e584000a.pnglocal_softirq_pending函数用于读取__softirq_pending字段,可以类比于设备驱动中的状态寄存器,用于判断是否有软中断处理请求;

    软中断处理时会关闭Bottom-half,处理完后再打开;

    软中断处理时,会打开本地中断,处理完后关闭本地中断,这个地方对应到上文中提到的Top-half和Bottom-half机制,在Bottom-half处理的时候,是会将中断打开的,因此也就能继续响应其他中断,这个也就意味着其他中断也能来打断当前的Bottom-half处理;

    while(softirq_bit = ffs(pending)),循环读取状态位,直到处理完每一个软中断请求;

    跳出while循环之后,再一次判断是否又有新的软中断请求到来(由于它可能被中断打断,也就意味着可能有新的请求到来),有新的请求到来,则有三个条件判断,满足的话跳转到restart处执行,否则调用wakeup_sotfirqd来唤醒内核线程来处理:time_before(jiffies, MAX_SOFTIRQ_TIME),软中断处理时间小于两毫秒;

    !need_resched,当前没有进程调度的请求;

    max_restart = MAX_SOFTIRQ_RESTART,跳转到restart循环的次数不大于10次;

    这三个条件的判断,是基于延迟和公平的考虑,既要保证软中断尽快处理,又不能让软中断处理一直占据系统,正所谓trade-off的艺术;

    __do_softirq既然可以在中断处理过程中调用,也可以在ksoftirqd中调用,那么softirq的执行可能有两种context,插张图吧:

    95e543b621090ab99005dd55e8eee95f.png

    让我们来思考最后一个问题:硬件中断触发的时候是通过硬件设备的电信号,那么软中断的触发是通过什么呢?答案是通过raise_softirq接口:

    03aab685b3aab82783a4daaf04ff89d3.png可以在中断处理过程中调用raise_softirq来进行软中断处理请求,处理的实际也就是上文中提到过的irq_exit退出硬件中断上下文之后再处理;

    raise_softirq_irqoff函数中,最终会调用到or_softirq_pending,该函数会去读取本地CPU的irq_stat中__softirq_pending字段,然后将对应的软中断号给置位,表明有该软中断的处理请求;

    raise_softirq_irqoff函数中,会判断当前的请求的上下文环境,如果不在中断上下文中,就可以通过唤醒内核线程来处理,如果在中断上下文中处理,那就不执行;

    多说一句,在软中断整个处理流程中,会经常看到in_interrupt()的条件判断,这个可以确保软中断在CPU上的串行执行,避免嵌套;

    2.2.3 软中断执行之二:Bottom-half Enable后

    第二种软中断执行的时间点,在Bottom-half使能的时候,通常用于并发处理,进程空间上下文中进行调用:

    3de61764005557b50cfaab857bad74af.png在讨论并发专题的时候,我们谈到过Bottom-half与进程之间能产生资源争夺的情况,如果在软中断和进程之间有临界资源(软中断上下文优先级高于进程上下文),那么可以在进程上下文中调用local_bh_disable/local_bh_enable来对临界资源保护;

    图中左侧的函数,都是用于打开Bottom-half的接口,可以看出是spin_lock_bh/read_lock_bh/write_lock_bh等并发处理接口的变种形式调用;

    __local_bh_enable_ip函数中,首先判断调用该本接口时中断是否是关闭的,如果已经关闭了再操作BH接口就会告警;

    preempt_count_sub需要与preempt_count_add配套使用,用于操作thread_info->preempt_count字段,加与减的值是一致的,而在__local_bh_enable_ip接口中,将cnt值的减操作分成了两步:preempt_count_sub(cnt-1)和preempt_count_dec,这么做的原因是执行完preempt_count_sub(cnt-1)后,thread_info->preempt_count字段的值保留了1,把抢占给关闭了,当do_softirq执行完毕后,再调用preempt_count_dec再减去剩下的1,进而打开抢占;

    为什么在使能Bottom-half时要进行软中断处理呢?在并发处理时,可能已经把Bottom-half进行关闭了,如果此时中断来了后,软中断不会被处理,在进程上下文中打开Bottom-half时,这时候就会检查是否有软中断处理请求了;

    3. tasklet

    从上文中分析可以看出,tasklet是软中断的一种类型,那么两者有啥区别呢?先说结论吧:软中断类型内核中都是静态分配,不支持动态分配,而tasklet支持动态和静态分配,也就是驱动程序中能比较方便的进行扩展;

    软中断可以在多个CPU上并行运行,因此需要考虑可重入问题,而tasklet会绑定在某个CPU上运行,运行完后再解绑,不要求重入问题,当然它的性能也就会下降一些;

    3.1 数据结构

    8031bef6eeecca7abf8aa94b957a2360.pngDEFINE_PER_CPU(struct tasklet_head, tasklet_vec)为每个CPU都分配了tasklet_head结构,该结构用来维护struct tasklet_struct链表,需要放到该CPU上运行的tasklet将会添加到该结构的链表中,内核中为每个CPU维护了两个链表tasklet_vec和tasklet_vec_hi,对应两个不同的优先级,本文以tasklet_vec为例;

    struct tasklet_struct为tasklet的抽象,几个关键字段如图所示,通过next来链接成链表,通过state字段来标识不同的状态以确保能在CPU上串行执行,func函数指针在调用task_init()接口时进行初始化,并在最终触发软中断时执行;

    3.2 流程分析

    9033acff268c2fd10e7daabd6b2412f9.pngtasklet本质上是一种软中断,所以它的调用流程与上文中讨论的软中断流程是一致的;

    调度tasklet运行的接口是tasklet_schedule,如果tasklet没有被调度则进行调度处理,将该tasklet添加到CPU对应的链表中,然后调用raise_softirq_irqoff来触发软中断执行;

    软中断执行的处理函数是tasklet_action,这个在softirq_init函数中通过open_softirq函数进行注册的;

    tasklet_action函数,首先将该CPU上tasklet_vec中的链表挪到临时链表list中,然后再对这个list进行遍历处理,如果满足执行条件则调用t->func()执行,并continue跳转遍历下一个节点。如果不满足执行条件,则继续将该tasklet添加回原来的tasklet_vec中,并再次触发软中断;

    3.3 接口

    简单贴一下接口吧:/* 静态分配tasklet */

    DECLARE_TASKLET(name, func, data)

    /* 动态分配tasklet */

    void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

    /* 禁止tasklet被执行,本质上是增加tasklet_struct->count值,以便在调度时不满足执行条件 */

    void tasklet_disable(struct tasklet_struct *t);

    /* 使能tasklet,与tasklet_diable对应 */

    void tasklet_enable(struct tasklet_struct *t);

    /* 调度tasklet,通常在设备驱动的中断函数里调用 */

    void tasklet_schedule(struct tasklet_struct *t);

    /* 杀死tasklet,确保不被调度和执行, 主要是设置state状态位 */

    void tasklet_kill(struct tasklet_struct *t);

    收工!

    展开全文
  • 硬件的中断处理函数处于中断上半部分,在CPU关中断的状态下执行,中断线程、软中断(softirq)及小任务(tasklet)属于中断的下半部分(bottom half),在CPU开中断的状态下执行。小任务基于软中断实现,实质是对软...
  • →local_irq_disable() // 关闭本地中断 →local_softirq_pending() // 判断是否有软中断 →_do_softirq() →local_irq_enable() →_do_softirq() (3)_do_softirq(void) _do_softirq(void) →local_softirq_...
  • softirq机制

    2018-12-01 15:05:23
    对于中断处理而言,linux将其分成了两个部分, 一个叫做中断handler(top half), 是全程关闭中断的, 主要处理中断的一些实时性任务, 另外一部分是...bottom half的机制主要有softirq, tasklet和workqueue, 这三者有本...
  • Linux 下半部机制介绍(二)——softirq(软中断)框架介绍
  • softirq部分代码解读

    2020-10-12 11:58:00
    softirq部分代码解读 文中有部分摘抄自其他参考文章, 由于暂时没有找到参考文章的链接了, 后续补上。 /proc/softirqs 下的 softirq 显示 某些/proc/softirqs的信息: /proc/softirqs 代码位置 显示代码在linux-4.9...
  • 说起这个softirq ,很多人还是一头雾水,觉得这个是什么东西,跟tasklets 和 workqueue有什么不同。 每次谈到这个,很多人,包括我,都是有点紧张,特别是面试的时候,因为你一旦说错了什么,那么你这次面试估计就...
  • Kernel 软中断Softirq

    2019-05-13 23:54:00
     从上面的定义可以看出:内核预定义了一组软中断类型,每种类型的软中断的数据结构都是一个 softirq_action,一个softirq_action就是一个回调函数。那么这个数组是如何被调度执行的?   2,软中断如何被调度...
  • 软中断执行的入口就是invoke_softirq,继续分析一波: invoke_softirq函数中,根据中断处理是否线程化进行分类处理,如果中断已经进行了强制线程化处理(中断强制线程化,需要在启动的时候传入参数threadirqs),...
  • SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS }; 内核用softirq_action结构管理软件中断的注册和激活等操作...
  • Linux 之软中断softirq

    千次阅读 2018-10-22 16:50:13
    softirq驱动开发人员一般都不会用到,到内核代码中会用到softirq机制,如在timer定时器中有用到softirq机制。。。 下面我们来简单了解一下Linux中的软中断的使用。 注册软中断处理函数open_softirq void open...
  • 一、软中断概述 现在我们开始讨论下半部的实现... 软中断的代码位于kernel/softirq.c文件中 二、软中断的实现 struct softirq_action 软中断是在编译期间静态分配的。它不像tasklet那样能被动态地注册或注销 ...
  • 里面定义了一个enum如下: enum { HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, /* ...
  • 1. 为何有softirq和tasklet 1、为何有top half和bottom half 中断处理模块是任何OS中最重要的一个模块,对系统的性能会有直接的影响。想像一下:如果在通过U盘进行大量数据拷贝的时候,你按下一个key,需要半秒的...
  • 总结:中断分为上半部和下半部,上半部关中断;...下半部有workqueue/softirq/tasklet三种方式。 二介绍了为何要分top half和bottom half?workqueue/softirq/tasklet区别? 三重点分析了preemp...
  • 在普通的驱动中一般是不会用到softirq,但是由于驱动经常使用的tasklet是基于softirq的,因此,了解softirq机制有助于撰写更优雅的driver。softirq不能动态分配,都是静态定义的。内核已经定义了若干种softirq ...
  • softirq

    2012-11-21 13:14:22
    本文对 Linux 内核软中断的执行流程进行了分析,并尽可能的结合当前运行环境详细地写出我的理解,但这并不表明我的理解一定正确。这本是论坛里的一篇帖子,发出来是为了...连 softirq 的调用点都不一样了,以前是三
  • 不过这里do_softirq 并不是在kernel/softirq.c 中定义的那个,因为宏__ARCH_HAS_DO_SOFTIRQ 在x86下被定义了,所以真正的do_softirq在arch/x86/kernel/irq_32.c : asmlinkage void do_softirq(void) { ...
  • 在普通的驱动中一般是不会用到softirq,但是由于驱动经常使用的tasklet是基于softirq的,因此,了解softirq机制有助于撰写更优雅的driver。softirq不能动态分配,都是静态定义的。内核已经定义了若干种softirq ...
  • Linux内核深入理解中断和异常(7):中断下半部:Softirq, Tasklets and Workqueues rtoax 2021年3月 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210325171547759.png?x-oss-process=image/watermark,...
  • linux之softirq

    千次阅读 2017-01-24 18:16:29
    1)在中断上下文中,通过调用函数raise_softirq,置位irq_stat[cpu].__softirq_pending中的相应软中断位,则会在硬中断结束后在函数irq_exit中调用invoke_softirq,实现软中断处理; 2)在非中断上下文中,通过调用...
  • 在图中我们看 到中断的处理大体上被分成两部分HARDIRQ和SOFTIRQ,对应到代码层面,do_IRQ()中调用irq_enter函数可以看做hardirq 部分的开始,而irq_exit函数的调用则标志着softirq部分的开始: unsigned int __irq_...
  • 软件中断(softIRQ)是内核提供的一种延迟执行机制,它完全由软件触发,虽然说是延迟机制,实际上,在大多数情况下,它与普通进程相比,能得到更快的响应时间。软中断也是其他一些内核机制的基础,比如tasklet,高...
  • */ #define in_irq() (hardirq_count()) #define in_softirq() (softirq_count()) #define in_interrupt() (irq_count()) #define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET) #define in_nmi() ...
  • 前一章分析了内核中延迟执行的一些机制,这章来讲讲linux中常用的中断机制,会侧重介绍softirq、tasklet这两个机制 二、linux中断机制的介绍 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top ...
  • linux网络软中断softirq底层机制及并发优化在实际生产系统环境中,我们经常碰到过高的软中断导致CPU的si负载偏高,从而导致性能服务器性能出现瓶颈。而这种瓶颈出现的时候往往是在业务高峰期,此时很多优化手段不敢...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,190
精华内容 5,676
关键字:

softirq

友情链接: c#版本的Astar算法.rar