linux 软中断

linux 软中断

目录

  • linux 软中断
    • 环境
    • 软中断
      • 基本介绍
      • 软中断的结构体与触发的API
      • 软中断的时机
    • 结论

环境

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 查看软中断类型和当前的一些统计数值

软中断的结构体与触发的API

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 会调用软中断,下面三种情形也会调用软中断:

  1. 在多核系统中,每个cpu timer 中断,可以查看内核代码 timer_tick() -> update_process_times() -> run_local_timers()
/*
 * 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);
}
  1. local_bh_enable 函数的调用可能会触发软中断执行(local_bh_enable ,local_bh_disable 本来就是内核提供关闭软中断的锁机制,它们组成的临界区禁止本地CPU在中断返回前夕执行软中断 ,BH 表示 bootom half critical region)
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();
}
  1. ksoftirqd 线程触发,可以在终端控制台用top命令查看,从上述执行ksoftirqd 的条件来看,如果ksoftirqd 占用cpu的时间过长,表明系统负载过重。

结论

但通过上述分析能对系统加深理解,对 tasklet 的理解会很有帮助。

你可能感兴趣的:(linux驱动,linux,驱动开发)