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_irqoff
和raise_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