linux AArch64中断下半部之软中断softirq

1 软中断的概念

软中断是一种软件实现的机制,而非硬件实现的中断。软中断属于中断上下文,当软中断在执行时,task无法打断软中断执行。

  • 软中断的类型是静态定义的,内核不建议新增加软中断类型
  • 软中断的回调函数是在开中断的情况下执行的
  • 软中断的执行点:在硬中断处理函数返回之前irq_exit()
  • 软中断属于中断上下文,软中断可以抢占进程上下文
  • 同一类型的软中断可以在多个处理器上并行执行
  • tasklet属于一种特殊的软中断,相同的tasklet在整个系统上只有一个可以执行,但是不同的tasklet可以同时在不同的处理器上运行(tasklet
    is running only on one CPU simultaneously,different tasklets
    may be run simultaneously on different CPUs.)

2 软中断执行的时机

2.1 软中断执行时机

中断处理函数执行完成,返回中断中断现场之前(irq_exit)会去检查:

  • 检查当前是否处于中断上下文in_interrupt()
  • 检查系统是否有待处理的软中断local_softirq_pending()
  • 调用invoke_softirq()去处理软中断
    如果当前不处于中断上下文并且有待处理的软中断,则会调用invoke_softirq()去处理软中断。

2.2 irq_exit

代码路径:kernel/softirq.c

/*
 * 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! */
}

2.3 invoke_softirq

代码路径:kernel/softirq.c

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

3 软中断关键函数和数据结构

3.1 软中断的类型

代码路径:include/linux/interrupt.h

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

3.2 注册软中断

代码路径:kernel/softirq.c

3.2.1 软中断注册函数定义

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

3.2.2 软中断注册样例

示例代码路径:net/core/dev.c

/*
 *      Initialize the DEV module. At boot time this walks the device list and
 *      unhooks any devices that fail to initialise (normally hardware not
 *      present) and leaves us with a valid list of present and active devices.
 *
 */

/*
 *       This is called single threaded during boot, so no need
 *       to take the rtnl semaphore.
 */
static int __init net_dev_init(void)
{
		...
	    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
        open_softirq(NET_RX_SOFTIRQ, net_rx_action);
        ...
}

3.3 触发软中断

触发软中断其实是在中断中通过调用raise_softirq_irqoff或者raise_softirq设置_softirq_pending位图,当从异常返回执行到irq_exit()函数时,会检查_softirq_pending是否有设置的软中断待处理。

3.3.1 触发软中断处理的接口

代码路径:kernel/softirq.c

/*
 * This function must run with irqs disabled!
 */
inline void raise_softirq_irqoff(unsigned int nr)
{
        __raise_softirq_irqoff(nr);

        /*
         * If we're in an interrupt or softirq, we're done
         * (this also catches softirq-disabled code). We will
         * actually run the softirq once we return from
         * the irq or softirq.
         *
         * Otherwise we wake up ksoftirqd to make sure we
         * schedule the softirq soon.
         */
        if (!in_interrupt())
                wakeup_softirqd();
}

void raise_softirq(unsigned int nr)
{
        unsigned long flags;

        local_irq_save(flags);
        raise_softirq_irqoff(nr);
        local_irq_restore(flags);
}

3.3.2 触发软中断处理的示例

在触发软中断处理时,通常会使用raise_softirq_irqoffraise_softirq函数

   6     51  block/blk-softirq.c <<<unknown>>>
             raise_softirq_irqoff(BLOCK_SOFTIRQ);
   7     94  block/blk-softirq.c <<<unknown>>>
             raise_softirq_irqoff(BLOCK_SOFTIRQ);
   8    148  block/blk-softirq.c <<<unknown>>>
             raise_softirq_irqoff(BLOCK_SOFTIRQ);
  12    784  drivers/irqchip/irq-gic.c <<<unknown>>>
             static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
  13   1157  drivers/irqchip/irq-gic.c <<<unknown>>>
             set_smp_cross_call(gic_raise_softirq);
  14    279  drivers/irqchip/irq-hip04.c <<<unknown>>>
             static void hip04_raise_softirq(const struct cpumask *mask, unsigned int irq)
  16   2288  drivers/net/wireless/rockchip_wlan/cywdhd/bcmdhd/dhd_linux.c <<<unknown>>>
              * This function will essentially invoke __raise_softirq_irqoff(NET_RX_SOFTIRQ)
  17    131  drivers/net/wireless/rockchip_wlan/cywdhd/bcmdhd/include/linuxver.h <<<unknown>>>
             cpu_raise_softirq(smp_processor_id(), NET_RX_SOFTIRQ)
  18    123  drivers/net/wireless/rockchip_wlan/cywdhd/bcmdhd/wl_iw.c <<<unknown>>>
             cpu_raise_softirq(smp_processor_id(), NET_RX_SOFTIRQ)
  19   1025  drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd/dhd_linux_lb.c <<<unknown>>>
              * This function will essentially invoke __raise_softirq_irqoff(NET_RX_SOFTIRQ)
  20   1042  drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd/dhd_linux_lb.c <<<unknown>>>
             raise_softirq(NET_RX_SOFTIRQ);

3.4 软中断处理函数__do_softirq

__do_softirq函数需要重点关注一下几个部分:

  • pending = local_softirq_pending();获取有哪些软中断被置位
  • h = softirq_vec;
  • h += softirq_bit - 1;
  • h->action(h);
  • 当中断不满足跳出条件时,会一直执行goto restart去执行软中断处理函数;软中断退出的条件如下所示:
    • time_before(jiffies, end) /* end = jiffies + MAX_SOFTIRQ_TIME 软中断允许的最长占用时间为2s */
    • !need_resched() /* 检查TIF_NEED_RESCHED*/
    • –max_restart /* max_restart = MAX_SOFTIRQ_RESTART; 调度次数最多为MAX_SOFTIRQ_RESTART */
  • 当退出软中断时还有需要处理的软中断则会通过调用wakeup_softirqd()函数去唤醒softirqd线程去处理剩余的软中断。
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 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();
        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);

        local_irq_enable();

        h = softirq_vec;

        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() &&	/* 软中断调度时间最长为2s, 当前系统不需要调度并且最大执行次数没有超过MAX_SOFTIRQ_RESTART的限制 */
                    --max_restart)
                        goto restart;	/* 继续处理剩余的软中断 */

                wakeup_softirqd();	/* 剩余尚未来的及处理的软中断通过唤醒softirqd线程去处理 */
        }

        lockdep_softirq_end(in_hardirq);
        account_irq_exit_time(current);
        __local_bh_enable(SOFTIRQ_OFFSET);
        WARN_ON_ONCE(in_interrupt());
        tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}

4 tasklet

tasklet是一种特殊的软中断TASKLET_SOFTIRQ

4.1 tasklet数据结构

代码路径:include/linux/interrupt.h

/* Tasklets --- multithreaded analogue of BHs.

   Main feature differing them of generic softirqs: tasklet
   is running only on one CPU simultaneously.

   Main feature differing them of BHs: different tasklets
   may be run simultaneously on different CPUs.

   Properties:
   * If tasklet_schedule() is called, then tasklet is guaranteed
     to be executed on some cpu at least once after this.
   * If the tasklet is already scheduled, but its execution is still not
     started, it will be executed only once.
   * If this tasklet is already running on another CPU (or schedule is called
     from tasklet itself), it is rescheduled for later.
   * Tasklet is strictly serialized wrt itself, but not
     wrt another tasklets. If client needs some intertask synchronization,
     he makes it with spinlocks.
 */

struct tasklet_struct
{
        struct tasklet_struct *next;
        unsigned long state;
        atomic_t count;
        void (*func)(unsigned long);
        unsigned long data;
};

4.2 声明一个tasklet

4.2.1 DECLARE_TASKLET

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

4.2.2 tasklet_init

void tasklet_init(struct tasklet_struct *t,
                  void (*func)(unsigned long), unsigned long data)
{
        t->next = NULL;
        t->state = 0;
        atomic_set(&t->count, 0);
        t->func = func;
        t->data = data;
}
EXPORT_SYMBOL(tasklet_init);

4.2.3 tasklet_init使用样例

   1   1843  drivers/atm/eni.c <<<unknown>>>
             tasklet_init(&eni_dev->task,eni_tasklet,(unsigned long) dev);
   2   2043  drivers/atm/fore200e.c <<<unknown>>>
                 tasklet_init(&fore200e->tx_tasklet, fore200e_tx_tasklet, (unsigned long)fore200e);
   3   2044  drivers/atm/fore200e.c <<<unknown>>>
                 tasklet_init(&fore200e->rx_tasklet, fore200e_rx_tasklet, (unsigned long)fore200e);
   4    386  drivers/atm/he.c <<<unknown>>>
             tasklet_init(&he_dev->tasklet, he_tasklet, (unsigned long) he_dev);
   5   1304  drivers/atm/solos-pci.c <<<unknown>>>
             tasklet_init(&card->tlet, solos_bh, (unsigned long)card);
   6    903  drivers/block/umem.c <<<unknown>>>
             tasklet_init(&card->tasklet, process_page, (unsigned long)card);
   7    986  drivers/block/xsysace.c <<<unknown>>>
             tasklet_init(&ace->fsm_tasklet, ace_fsm_tasklet, (unsigned long)ace);
   8   2820  drivers/char/ipmi/ipmi_msghandler.c <<<unknown>>>
             tasklet_init(&intf->recv_tasklet,
   9    838  drivers/char/mmtimer.c <<<unknown>>>
             tasklet_init(&timers[node].tasklet, mmtimer_tasklet,
  10   1198  drivers/crypto/amcc/crypto4xx_core.c <<<unknown>>>
             tasklet_init(&core_dev->tasklet, crypto4xx_bh_tasklet_cb,
  11   1357  drivers/crypto/atmel-aes.c <<<unknown>>>
             tasklet_init(&aes_dd->done_task, atmel_aes_done_task,
  12   1359  drivers/crypto/atmel-aes.c <<<unknown>>>
             tasklet_init(&aes_dd->queue_task, atmel_aes_queue_task,
  13   1370  drivers/crypto/atmel-sha.c <<<unknown>>>
             tasklet_init(&sha_dd->done_task, atmel_sha_done_task,
  14   1378  drivers/crypto/atmel-tdes.c <<<unknown>>>
             tasklet_init(&tdes_dd->done_task, atmel_tdes_done_task,
  15   1380  drivers/crypto/atmel-tdes.c <<<unknown>>>
             tasklet_init(&tdes_dd->queue_task, atmel_tdes_queue_task,
  16    587  drivers/crypto/bfin_crc.c <<<unknown>>>
             tasklet_init(&crc->done_task, bfin_crypto_crc_done_task, (unsigned long)crc);

4.3 调度一个tasklet

__tasklet_schedule会通过调用raise_softirq_irqoff(TASKLET_SOFTIRQ);去设置_softirq_pending,

void __tasklet_schedule(struct tasklet_struct *t)
{
        unsigned long flags;

        local_irq_save(flags);
        t->next = NULL;
        *__this_cpu_read(tasklet_vec.tail) = t;
        __this_cpu_write(tasklet_vec.tail, &(t->next));
        raise_softirq_irqoff(TASKLET_SOFTIRQ);
        local_irq_restore(flags);
}
EXPORT_SYMBOL(__tasklet_schedule);

4.4 如何在驱动里面定义一个tasklet

  • 首先要初始化一个tasklet,可以通过DECLARE_TASKLET或者tasklet_init去定义一个tasklet
  • 实现tasklet的处理函数
  • 调度自己的tasklet
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值