Linux内核源代码情景分析-中断下半部(软中断)

    Tasklet机制是一种较为特殊的软中断。Tasklet一词的原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。  
    从某种程度上讲,tasklet机制是Linux内核对BH机制的一种扩展。在2.4内核引入了softirq机制后,原有的BH机制正是通过tasklet机制这个桥梁来纳入softirq机制的整体框架中的。正是由于这种历史的延伸关系,使得tasklet机制与一般意义上的软中断有所不同,而呈现出以下两个显著的特点:  
    1. 与一般的软中断不同,某一段tasklet代码在某个时刻只能在一个CPU上运行,而不像一般的软中断服务函数(即softirq_action结构中的action函数指针)那样——在同一时刻可以被多个CPU并发地执行。  
    2. 与BH机制不同,不同的tasklet代码在同一时刻可以在多个CPU上并发地执行,而不像BH机制那样必须严格地串行化执行(也即在同一时刻系统中只能有一个CPU执行BH函数)。 

    软中断是中断下半部总体的说法,Tasklet机制是软中断的特殊例子,软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。  最后BH是通过Tasklet机制融入软中断框架的。


    一、软中断的初始化

    1、初始化Tasklet机制

void __init softirq_init()
{
	int i;

	for (i=0; i<32; i++)
		tasklet_init(bh_task_vec+i, bh_action, i);

	open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};
struct tasklet_struct bh_task_vec[32];
enum
{
	HI_SOFTIRQ=0,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	TASKLET_SOFTIRQ
};

    

    tasklet_init函数如下:

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


    open_softirq函数如下:

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
	unsigned long flags;
	int i;

	spin_lock_irqsave(&softirq_mask_lock, flags);
	softirq_vec[nr].data = data;
	softirq_vec[nr].action = action;

	for (i=0; i<NR_CPUS; i++)//NR_CPUS为1
		softirq_mask(i) |= (1<<nr);//irq_stat[0].__softirq_mask第1位和第4位都置1
	spin_unlock_irqrestore(&softirq_mask_lock, flags);
}
struct softirq_action
{
	void	(*action)(struct softirq_action *);
	void	*data;
};
static struct softirq_action softirq_vec[32] __cacheline_aligned;
#define softirq_mask(cpu)	__IRQ_STAT((cpu), __softirq_mask)
#define __IRQ_STAT(cpu, member)	((void)(cpu), irq_stat[0].member)
typedef struct {
	unsigned int __softirq_active;
	unsigned int __softirq_mask;
	unsigned int __local_irq_count;
	unsigned int __local_bh_count;
	unsigned int __syscall_count;
	unsigned int __nmi_count;	/* arch dependent */
} ____cacheline_aligned irq_cpustat_t;
extern irq_cpustat_t irq_stat[];


    2、初始化bh

void __init sched_init(void)
{
	/*
	 * We have to do a little magic to get the first
	 * process right in SMP mode.
	 */
	......

	init_bh(TIMER_BH, timer_bh);
	init_bh(TQUEUE_BH, tqueue_bh);
	init_bh(IMMEDIATE_BH, immediate_bh);

	/*
	 * The boot idle thread does lazy MMU switching as well:
	 */
	atomic_inc(&init_mm.mm_count);
	enter_lazy_tlb(&init_mm, current, cpu);
}
enum {
	TIMER_BH = 0,
	TQUEUE_BH,
	DIGI_BH,
	SERIAL_BH,
	RISCOM8_BH,
	SPECIALIX_BH,
	AURORA_BH,
	ESP_BH,
	SCSI_BH,
	IMMEDIATE_BH,
	CYCLADES_BH,
	CM206_BH,
	JS_BH,
	MACSERIAL_BH,
	ISICOM_BH
};
void init_bh(int nr, void (*routine)(void))
{
	bh_base[nr] = routine;
	mb();
}

    二、将tasklet_struct结构的(bh_task_vec+0)链入tasklet_hi_vec[0]

    在时钟处理函数中,do_timer中执行了mark_bh(TIMER_BH),代码如下:

static inline void mark_bh(int nr)
{
	tasklet_hi_schedule(bh_task_vec+nr);
}
static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
		int cpu = smp_processor_id();
		unsigned long flags;

		local_irq_save(flags);
		t->next = tasklet_hi_vec[cpu].list;//将tasklet_struct结构的(bh_task_vec+0)链入tasklet_hi_vec[0]
		tasklet_hi_vec[cpu].list = t;
		__cpu_raise_softirq(cpu, HI_SOFTIRQ);
		local_irq_restore(flags);
	}
}
struct tasklet_head
{
	struct tasklet_struct *list;
} __attribute__ ((__aligned__(SMP_CACHE_BYTES)));
extern struct tasklet_head tasklet_hi_vec[NR_CPUS];//NR_CPUS为0
static inline void __cpu_raise_softirq(int cpu, int nr)
{
	softirq_active(cpu) |= (1<<nr);//irq_stat[0].__softirq_active第1位置1
}

    三、软中断响应

    内核每当在do_IRQ()中执行完一个通道中的中断服务程序以后,以及每当从系统调用返回时,都要检查是否有软中断请求在等待执行。

       if (softirq_active(cpu) & softirq_mask(cpu))//irq_stat[0].__softirq_active第1位置1 & irq_stat[0].__softirq_mask第1位和第4位都置1 结果不为0 
          do_softirq();  


    do_softirq代码如下:

asmlinkage void do_softirq()
{
	int cpu = smp_processor_id();
	__u32 active, mask;

	if (in_interrupt())//如果已经增加了irq_stat[0].__local_bh_count计数,还没有减去,那么直接返回,也就是说
		return;

	local_bh_disable();//增加irq_stat[0].__local_bh_count计数

	local_irq_disable();//cli关中断
	mask = softirq_mask(cpu);
	active = softirq_active(cpu) & mask;

	if (active) {
		struct softirq_action *h;

restart:
		/* Reset active bitmask before enabling irqs */
		softirq_active(cpu) &= ~active;

		local_irq_enable();//sti开中断

		h = softirq_vec;
		mask &= ~active;

		do {
			if (active & 1)
				h->action(h);//执行tasklet_hi_action
			h++;
			active >>= 1;
		} while (active);

		local_irq_disable();

		active = softirq_active(cpu);
		if ((active &= mask) != 0)
			goto retry;
	}

	local_bh_enable();////减少irq_stat[0].__local_bh_count计数

	/* Leave with locally disabled hard irqs. It is critical to close
	 * window for infinite recursion, while we help local bh count,
	 * it protected us. Now we are defenceless.
	 */
	return;

retry:
	goto restart;
}
#define local_bh_disable()	cpu_bh_disable(smp_processor_id())
#define cpu_bh_disable(cpu)	do { local_bh_count(cpu)++; barrier(); } while (0)
#define local_bh_count(cpu)	__IRQ_STAT((cpu), __local_bh_count)
#define __IRQ_STAT(cpu, member)	((void)(cpu), irq_stat[0].member)
#define in_interrupt() ({ int __cpu = smp_processor_id(); 	(local_irq_count(__cpu) + local_bh_count(__cpu) != 0); })
    如果do_softirq执行的过程中,发生了中断,在同一个cpu上,又执行了do_softirq,此时在in_interrupt时,会立即返回。所以防止了tasklet机制执行程序的嵌套。


    tasklet_hi_action,函数如下:

static void tasklet_hi_action(struct softirq_action *a)
{
	int cpu = smp_processor_id();
	struct tasklet_struct *list;

	local_irq_disable();
	list = tasklet_hi_vec[cpu].list;
	tasklet_hi_vec[cpu].list = NULL;
	local_irq_enable();

	while (list != NULL) {
		struct tasklet_struct *t = list;

		list = list->next;

		if (tasklet_trylock(t)) {
			if (atomic_read(&t->count) == 0) {
				clear_bit(TASKLET_STATE_SCHED, &t->state);

				t->func(t->data);
				tasklet_unlock(t);
				continue;
			}
			tasklet_unlock(t);
		}
		local_irq_disable();
		t->next = tasklet_hi_vec[cpu].list;
		tasklet_hi_vec[cpu].list = t;
		__cpu_raise_softirq(cpu, HI_SOFTIRQ);
		local_irq_enable();
	}
}

    

    刚刚将tasklet_struct结构的(bh_task_vec+0)链入tasklet_hi_vec[0],这里取出来,执行t->func(t->data),也就是bh_action(0),代码如下:

static void bh_action(unsigned long nr)
{
	int cpu = smp_processor_id();

	if (!spin_trylock(&global_bh_lock))//全局锁,同一时刻系统中只能有一个CPU执行BH函数
		goto resched;

	if (!hardirq_trylock(cpu))
		goto resched_unlock;

	if (bh_base[nr])
		bh_base[nr]();//最后执行的是bh_base[0],也就是timer_bh

	hardirq_endlock(cpu);
	spin_unlock(&global_bh_lock);
	return;

resched_unlock:
	spin_unlock(&global_bh_lock);
resched:
	mark_bh(nr);
}


    如果t->func(t->data) 不是bh_action(0),也就不是bh函数,那么tasklets机制满足:
    1、某一段tasklet代码在某个时刻只能在一个CPU上运行。

    2、不同的tasklet代码在同一时刻可以在多个CPU上并发地执行。

    如果t->func(t->data) 是bh_action(0),也就是bh函数,那么必须像BH机制那样,严格地串行化执行(也即在同一时刻系统中只能有一个CPU执行BH函数)。 

    所以BH是通过Tasklet机制融入软中断框架的。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。