软中断与Bottom Half

/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */
void irq_exit(void)
{
	account_system_vtime(current);
	trace_hardirq_exit();
	sub_preempt_count(IRQ_EXIT_OFFSET);
	if (!in_interrupt() && local_softirq_pending())
		invoke_softirq();

	rcu_irq_exit();
#ifdef CONFIG_NO_HZ
	/* Make sure that timer wheel updates are propagated */
	if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
		tick_nohz_stop_sched_tick(0);
#endif
	preempt_enable_no_resched();
}

在do_IRQ的最后,调用irq_exit()以退出中断上下文的时候,可能会激活softirq():

static inline void invoke_softirq(void)
{
	if (!force_irqthreads)  //如果没有强制中断线程化
		do_softirq();   //调用do_softirq()
	else {                  //如果强制中断线程化
		__local_bh_disable((unsigned long)__builtin_return_address(0), //屏蔽掉本CPU上的其它软中断
				SOFTIRQ_OFFSET);
		wakeup_softirqd();  //唤醒中断守护进程
		__local_bh_enable(SOFTIRQ_OFFSET);                             //打开其它软中断
	}
}

何为中断守护进程?中断守护进程的任务是与其余内核代码异步执行软中断,系统中的每个处理器都分配了自身的守护进程,名为ksoftirqd(《深入Linux内核架构》)。

在唤醒softirq的时候,如果我们没有强制中断线程化,那么调用do_softirq,而且此对应的软中断服务会立即被执行,直到其完成或者自己阻塞,而其他进程得不到调度的机会,因为中断任务的优先级是很高的。如果这个过程的很长的话,那就很糟糕了(参看博文《中断队列初始化》中有关中断线程化的解释)。

如果强制了中断线程化,那么调用wakeup_softirqd,我们来看一下做了什么?不过,我们应该留心的是在wakeup_softirq前后发生的事情。在调用wakeup_softirq之前会屏蔽掉其他软中断,直到这个软中断执行结束,说明每个CPU上同一时间只能处理一个softirq。


/*
 * we cannot loop indefinitely here to avoid userspace starvation,
 * but we also don't want to introduce a worst case 1/HZ latency
 * to the pending events, so lets the scheduler to balance
 * the softirq load for us.
 */
static void wakeup_softirqd(void)
{
	/* Interrupts are disabled: no need to stop preemption */
	struct task_struct *tsk = __this_cpu_read(ksoftirqd);

	if (tsk && tsk->state != TASK_RUNNING)
		wake_up_process(tsk);
}

将tsk指向ksoftirqd,之后尝试唤醒tsk,但是tsk不一定会被执行哦,这样看调度器的调度结果(最好好好理解一下注释给出的解释)。

让我们来看一下ksoftirqd,与以往不同的是,在3.0的内核中ksoftirqd表示的是一个进程,而不是之前版本的内核中的函数,这个进程的声明如下:

/*
 * CPU type, hardware bug flags, and per-CPU state.  Frequently used
 * state comes earlier:
 */
struct cpuinfo_ia64 {

        。。。。。。

	unsigned int ptce_stride[2];
	struct task_struct *ksoftirqd;	/* kernel softirq daemon for this CPU */

        。。。。。。
};

可见,ksoftirq是percpu结构的。那它的初始化是在哪个地方呢?

static int __cpuinit cpu_callback(struct notifier_block *nfb,
				  unsigned long action,
				  void *hcpu)
{

            。。。。。。

case CPU_UP_PREPARE_FROZEN:
		p = kthread_create_on_node(run_ksoftirqd,
					   hcpu,
					   cpu_to_node(hotcpu),
					   "ksoftirqd/%d", hotcpu);
		if (IS_ERR(p)) {
			printk("ksoftirqd for %i failed\n", hotcpu);
			return notifier_from_errno(PTR_ERR(p));
		}
		kthread_bind(p, hotcpu);
  		per_cpu(ksoftirqd, hotcpu) = p;
 		break;
            。。。。。。
	return NOTIFY_OK;
}

可见,ksoftirqd被指向了run_ksoftirqd:

static int run_ksoftirqd(void * __bind_cpu)
{
	set_current_state(TASK_INTERRUPTIBLE); //softirq是可以被中断(调度)的

	while (!kthread_should_stop()) {  //当CPU被移除或者其他原因导致softirqd被停止时,退出本循环 
		preempt_disable();        //不允许内核抢占
		if (!local_softirq_pending()) { //如果没有待决软中断,则将CPU转与其他任务。
			preempt_enable_no_resched();
			schedule();
			preempt_disable();
		}

		__set_current_state(TASK_RUNNING); //设置进程状态为TASK_RUNNING

		while (local_softirq_pending()) {  //尚有待决软中断
			/* Preempt disable stops cpu going offline. 因为处于preempt_disable状态,因此CPU是不能让出CPU的,否则一定是出错了。
			   If already offline, we'll be on wrong CPU:
			   don't process */
			if (cpu_is_offline((long)__bind_cpu))
				goto wait_to_die;
			local_irq_disable(); //关闭中断。因为每个CPU在一个时刻之允许处理一个中断。
			if (local_softirq_pending())
				__do_softirq();  //***************************************************************
			local_irq_enable(); //开中断
			preempt_enable_no_resched();
			cond_resched();     //允许比该进程优先级高的进程抢占该进程
			preempt_disable();
			rcu_note_context_switch((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;
}

现在,有一个问题:在这段代码中看到,在__do_softirq()是在关中断的前提下执行的,那么,也就是说如果中断A的软中断正在执行,它会阻止同一个中断源上的另一个中断B的发生么?当然,我们说的是在同一个CPU上。如果是这样的话,是不是说某一中断的softirq一旦开始执行,就必须执行完毕才行呢?



上面我们讲到了在强制中断线程化的情况下,软中断的处理情况,回过去看,如果没有强制中断线程化:

	if (!force_irqthreads)  //如果没有强制中断线程化
		do_softirq();   //调用do_softirq()

看到,不过是否强制了中断线程化,最终都要调用__do_softirq函数。这之前,我们先来看do_softirq:

asmlinkage void do_softirq(void)
{
	unsigned long flags;
	struct thread_info *curctx; //当前进程上下文
	union irq_ctx *irqctx;      //中断上下文
	u32 *isp;

	if (in_interrupt())
		return;

	local_irq_save(flags);   //禁止中断,这样可以避免与软中断冲突  ???

	if (local_softirq_pending()) {  //存在尚未被处理的softirq
		curctx = current_thread_info();
		irqctx = __this_cpu_read(softirq_ctx);
		irqctx->tinfo.task = curctx->task;
		irqctx->tinfo.previous_esp = current_stack_pointer; //因为我们用的是8K的thread_union,因此中断上下文会借用当前进程的上下文。

		/* build the stack frame on the softirq stack */
		isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));

		call_on_stack(__do_softirq, isp);
		/*
		 * Shouldn't happen, we returned above if in_interrupt():
		 */
		WARN_ON_ONCE(softirq_count());
	}

	local_irq_restore(flags);
}


最终处理软中断的函数是__do_softirq,如下:

asmlinkage void __do_softirq(void)
{
	struct softirq_action *h;
	__u32 pending;
	int max_restart = MAX_SOFTIRQ_RESTART;
	int cpu;

	pending = local_softirq_pending();  
	account_system_vtime(current);

	__local_bh_disable((unsigned long)__builtin_return_address(0),  // 这里加上软中断计数,这样在本函数中开中断后,发生中断后不会再重入本函数
				SOFTIRQ_OFFSET);
	lockdep_softirq_enter();  //调试用

	cpu = smp_processor_id();
restart:
	/* Reset the pending bitmask before enabling irqs */
	set_softirq_pending(0);  //将软中断对应的掩码清0

	local_irq_enable();      //开中断,避免长时间不能中断

	h = softirq_vec;    

	do {
		if (pending & 1) {
			unsigned int vec_nr = h - softirq_vec;   //获取对应的软中断的编号
			int prev_count = preempt_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())) {  //如果软中断破坏了抢占计数器,那么恢复原来的preempt_count
				printk(KERN_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() = prev_count;
			}

			rcu_bh_qs(cpu);
		}
		h++;              //处理下一个软中断
		pending >>= 1;    //右移pending
	} while (pending);        //如果没有挂起的软中断,那么结束循环
 
	local_irq_disable();      //关中断
 
	pending = local_softirq_pending();
	if (pending && --max_restart) //max_restart = 10;因此,最多轮询10次。以防止执行处理软中断的时间过长,而将应用程序饿死。
		goto restart;

	if (pending)                  //如果还是没有将所有软中断处理完,那么唤醒softirqd,放弃CPU占用权,让其它应用程序进来。
		wakeup_softirqd();

	lockdep_softirq_exit();

	account_system_vtime(current);
	__local_bh_enable(SOFTIRQ_OFFSET);
}



参考文章:http://blog.chinaunix.net/space.php?uid=25845340&do=blog&id=2983385



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值