linux 内核版本 linux 4.15.2
linux 内核开发者不希望用户再扩充新的软中断类型,关于软中断,下面做一个了解,有助于linux中断的理解;
关于软中断相关的代码在softirq.c中
在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
};
softirq.c中有
const char * const softirq_to_name[NR_SOFTIRQS] = {
"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "IRQ_POLL",
"TASKLET", "SCHED", "HRTIMER", "RCU"
};
可以在系统中 cat /proc/softirqs 查看软中断类型和当前的一些统计数值
raise_softirq_irqoff 和raise_softirq 区别是:是否主动关闭本地中断,因此raise_softirq_irqoff 允许在进程上下文中调用;
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);
}
软中断的结构体如下
typedef struct {
unsigned int __softirq_pending;
#ifdef CONFIG_SMP
unsigned int ipi_irqs[NR_IPI];
#endif
} ____cacheline_aligned irq_cpustat_t;
触发软中断,其实就是设置__softirq_pending 的比特位,nr 传上面的枚举;
当__softirq_pending 不为0时表示有软中断需要处理
or_softirq_pending(1UL << nr);
#define or_softirq_pending(x) (local_softirq_pending() |= (x))
中断发生-> irq_handle->gic_handle_irq()->handle_domain_irq()->irq_exit()
顺便提一下irq_handle 是写在汇编中的
/*
* Interrupt handling.
*/
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
badr lr, 9997f
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
lockdep_assert_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中的注释 invoke_softirq 的条件
!in_interrupt() && local_softirq_pending()
也就是说硬件中断退出时不能处理硬件中断上下文和软中断上下文中(因为ARM 中断控制器流程来看,硬件中断处理过程一般是关中断的,且不支持中断嵌套,所以!in_interrupt() 是满足条件的,另一个local_softirq_pending() 则说明如果当前硬件中断抢占软件中断,则执行新的软件中断调用),从这里可以看出,如果本次硬件中断点发生在一个软中断处理过程中,那么中断退时出时不允许重新调试软中断,所以软中断在一个CPU上总是串行执行的。
static inline void invoke_softirq(void)
{
if (ksoftirqd_running())
return;
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();
}
}
执行 __do_softirq,下面重点分析一下这个函数,分析的结果写在代码注释中
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);
// 增加preempt_count 中的SOFTIRQ域的计数,表明现在在软中断上下文中
__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;
/*
ffs()函数会找到pending 中第一个置位的比特位,然后找到对应的软中断描述符和软中断序号,
最后调用action()函数指针来执行中断处理,依次循环直到所有中断都处理完成
*/
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();
/*
再次检查__softirq_pending 是否又产生了软中断,
因为软中断执行过程中开中断的,有可能这个过程又发生了中断(硬件)以及触发了软中断
*/
pending = local_softirq_pending();
if (pending) {
/*
不是一检测到软中断就马上调转到restart标签外进行软中断处理,这里需要一个系统平衡的考虑,
1.软中断处理时间不超过2ms (MAX_SOFTIRQ_TIME)
2.当前没进程要求调试
3.这种循环不能多于10次 (max_restart)
*/
if (time_before(jiffies, end) && !need_resched() &&
--max_restart)
goto restart;
// 否则唤醒ksoftirqd 内核线程来处理软中断
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);
}
从上述代码中我们也可以看出,先__local_bh_disable_ip(RET_IP, SOFTIRQ_OFFSET); 再 set_softirq_pending,然后再打开中断(打开硬件中断后可能),打开硬件中断后,又会在退出中断时判断是否调试进行软件中断,从源码中可以得出以下几点:
1. 软中断永远都不会嵌套软中断,但硬件中断能嵌套软中断。
2. 当执行软中断服务程序时,在此CPU中其它的软中断被禁止执行(与1同理),但是其它的cpu可以同时执行软中断(也包括同一个类型的软中断),这也算是软中断相对硬中断的一个优势。网络通信使用软中断,多个CPU同进工作,在功耗上消耗会比较高。
3. 对于多cpu来说,同一个软中断能同一时间运行在不同cpu上,所以使用spin_lock() 和spin_unlock(). 就显得很必要。
除了irq_exit 会调用软中断,下面三种情形也会调用软中断:
/*
* Called by the local, per-CPU timer interrupt on SMP.
*/
void run_local_timers(void)
{
struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
hrtimer_run_queues();
/* Raise the softirq only if required. */
if (time_before(jiffies, base->clk)) {
if (!IS_ENABLED(CONFIG_NO_HZ_COMMON))
return;
/* CPU is awake, so check the deferrable base. */
base++;
if (time_before(jiffies, base->clk))
return;
}
raise_softirq(TIMER_SOFTIRQ);
}
static inline void local_bh_enable(void)
{
__local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
/*
* Special-case - softirqs can safely be enabled in
* cond_resched_softirq(), or by __do_softirq(),
* without processing still-pending softirqs:
*/
void _local_bh_enable(void)
{
WARN_ON_ONCE(in_irq());
__local_bh_enable(SOFTIRQ_DISABLE_OFFSET);
}
EXPORT_SYMBOL(_local_bh_enable);
void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
{
WARN_ON_ONCE(in_irq());
lockdep_assert_irqs_enabled();
#ifdef CONFIG_TRACE_IRQFLAGS
local_irq_disable();
#endif
/*
* Are softirqs going to be turned on now:
*/
if (softirq_count() == SOFTIRQ_DISABLE_OFFSET)
trace_softirqs_on(ip);
/*
* Keep preemption disabled until we are done with
* softirq processing:
*/
preempt_count_sub(cnt - 1);
if (unlikely(!in_interrupt() && local_softirq_pending())) {
/*
* Run softirq if any pending. And do it in its own stack
* as we may be calling this deep in a task call stack already.
*/
do_softirq();
}
preempt_count_dec();
#ifdef CONFIG_TRACE_IRQFLAGS
local_irq_enable();
#endif
preempt_check_resched();
}
但通过上述分析能对系统加深理解,对 tasklet 的理解会很有帮助。