【Linux中断】中断下半部-软中断softirq的原理与使用

软中断

软中断是中断下半部的典型处理机制,是随着SMP的出现应运而生的,也是tasklet实现的基础,软中断的出现是为了满足中断上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。

软中断有以下特性

  • 产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断(单个cpu上软中断不能嵌套执行),只能被硬件中断打断(上半部)
  • 可以并发运行在多个cpu上。所以软中断必须要设计为可重入的函数(允许多个CPU同时操作)。

软中断数据结构

  • 软中断描述符
sturct softirq_action
{
    void *(action)(struct softirq_action *);
};

描述每一种类型的软中断,void (*action)是软中断触发时的执行函数。

  • 软中断全局数据和支持的枚举类型
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
enum
{
    HI_SOFTIRQ=0,     // 高优先级的tasklet
    TIMER_SOFTIRQ,    // 用于定时器的下半部
    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 the
                numbering. Sigh! */
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */


    NR_SOFTIRQS
};

软中断的API接口

  • 注册软中断
void open_softirq(int nr, void (*action)(struct softirq_action *));

注册对应类型的处理函数到全局数组softirq_vec中。 网络法宝的对应类型是NET_TX_SOFTIRQ的处理函数net_tx_action。

  • 触发软中断
void raise_softirq(unsigned int nr);

软中断类型nr作为偏移量置位每个CPU变量irq_stat[cpu_id]的成员变量_softirq_pending,这是同一类型的软中断可以在多个cpu核心上并行运行的根本原因。

  • 软中断执行函数
do_softirq() -> __do_softirq()

在执行软中断处理函数__do_softirq()前需要满足两个条件:

(1)不在中断中(包括硬中断,软中断和NMI)
(2)有软中断处于pending状态

这样设计的原因是为了避免软件中断在中断嵌套中调用,达到在单个CPU上软件中断不能被重入的目的。

ARM架构的CPU不存在中断嵌套中调用软件中断的问题,因为ARM架构的CPU在处理硬件中断的过程中关闭掉中断,在进入软中断处理过程之后才会重新开启硬件中断,如果在软件中断处理过程中有硬件中断嵌套,也不会再次调用软中断,因为硬中断是软中断处理过程中再次进入的。

软中断实现原理

中断处理过程图

img

软中断的调度时机

1.do_irq完成I/O中断时调用irq_exit

2.系统使用I/O APIC,在处理玩本地时钟中断时

3.local_bh_enable,开启本地软中断时

4.SMP系统中,cpu处理完备CALL_FUNCTION_VECTOR处理器间中断所触发的函数时。

5.ksoftirqd/n线程被唤醒时

软中断处理流程

asmlinkage __visible void __softirq_entry __do_softirq(void)
{
    unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
    unsigned long old_flags = current->flags;
    int max_restart = MAX_SOFTIRQ_RESTART;
    struct softirq_action *h;
    bool in_hardirq;
    __u32 pending;
    int softirq_bit;

    /*
     * Mask out PF_MEMALLOC as the 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 swapping.
     */
    current->flags &= ~PF_MEMALLOC;
    // 获取当前有哪些等待的软中断
    pending = local_softirq_pending();
    account_irq_enter_time(current);

    // 关闭软中断,实质是设置正在处理软件的中断标记,在同一个CPU上使得不能重入_do_softirq函数
    __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
    in_hardirq = lockdep_softirq_start();

restart:
    /* Reset the pending bitmask before enabling irqs */
    // 重新设置软中断标志为0,这样在重新烤漆中断后硬件中断中又可以设置软件中断位
    set_softirq_pending(0);

    // 开启硬件中断
    local_irq_enable();

    h = softirq_vec;

    // 遍历pending标志的每一位,如果这一位设置就会调用软件中断的处理函数
    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;
    }

    if (__this_cpu_read(ksoftirqd) == current)
        rcu_softirq_qs();

    // 关闭硬件中断
    local_irq_disable();
    // 查看是否有软件中断处于pending状态
    pending = local_softirq_pending();
          
    if (pending) {
        if (time_before(jiffies, end) && !need_resched() &&
            --max_restart)
            goto restart;
        // 如累积重复进入软件中断处理的次数超过max_restart=10次,就唤醒内核进程来处理软件中断
        wakeup_softirqd();
    }
    
    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);
}

软中断内核线程

触发软件中断的位置是在中断上下文,而软中断的内核线程就已经进入到进程的上下文。软中断的上下文是系统为每个CPU建立的ksoftirqd进程。软中断的内核进程中主要有两个大循环,外层的循环处理有软件中断就处理,没有软件中断就休眠。内层的循环处理软件中断,每循环一次都试探一次是否过长时间占据了CPU,需要调度就是放CPU给其他进程。

set_current_state(TASK_INTERRUPTIBLE);
    //外层大循环。
    while (!kthread_should_stop()) {
        preempt_disable();//禁止内核抢占,自己掌握cpu
        if (!local_softirq_pending()) {
            preempt_enable_no_resched();
            //如果没有软中断在pending中就让出cpu
            schedule();
            //调度之后重新掌握cpu
            preempt_disable();
        }

        __set_current_state(TASK_RUNNING);

        while (local_softirq_pending()) {
            /* Preempt disable stops cpu going offline.
               If already offline, we'll be on wrong CPU:
               don't process */
            if (cpu_is_offline((long)__bind_cpu))
                goto wait_to_die;
            //有软中断则开始软中断调度
            do_softirq();
            //查看是否需要调度,避免一直占用cpu
            preempt_enable_no_resched();
            cond_resched();
            preempt_disable();
            rcu_sched_qs((long)__bind_cpu);
        }
        preempt_enable();
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;

wait_to_die:
    preempt_enable();
    /* Wait for kthread_stop */
    set_current_state(TASK_INTERRUPTIBLE);
    while (!kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;










参考文章:

https://zhuanlan.zhihu.com/p/265705850

posted @ 2023-06-11 12:13  Emma1111  阅读(1646)  评论(0编辑  收藏  举报