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机制融入软中断框架的。

时间: 2024-10-15 00:17:19

Linux内核源代码情景分析-中断下半部(软中断)的相关文章

Linux内核源代码情景分析-中断半

一.中断初始化 1.中断向量表IDT初始化 void __init init_IRQ(void) { int i; #ifndef CONFIG_X86_VISWS_APIC init_ISA_irqs(); #else init_VISWS_APIC_irqs(); #endif /* * Cover the whole vector space, no vector can escape * us. (some of these will be overridden and become *

Linux内核源代码情景分析-中断上半部

一.中断初始化 1.中断向量表IDT的初始化 void __init init_IRQ(void) { int i; #ifndef CONFIG_X86_VISWS_APIC init_ISA_irqs(); #else init_VISWS_APIC_irqs(); #endif /* * Cover the whole vector space, no vector can escape * us. (some of these will be overridden and become

Linux内核源代码情景分析-强制性调度

Linux内核中进程的强制性调度,也就是非自愿的.被动的.剥夺式的调度,主要是由时间引起的.前面讲过这种调度发生在中断,异常,系统调用从系统空间返回用户空间的前夕,也就是在ret_with_reschedule可以看出,此时是否真的调用schedule(),最终还要取决于当前进程task_struct结构中的need_resched是否为1(非0),因此,问题就结为当前进程的need_resched是在什么情况下才置成1的.主要有如下几种情况: 1.在时钟中断的服务程序中,发现当前进程(连续)运

Linux内核源代码情景分析-系统初始化

我们跳过boot,setup,直接来到head代码,内核映像的起点是stext,也是_stext,引导和解压缩以后的整个映像放在内存从0x100000即1MB开始的区间.CPU执行内核映像的入口startup_32就在内核映像开头的地方,因此其物理地址也是0x100000. 然而,在正常运行时整个内核映像都应该在系统空间中,系统空间的虚拟地址与物理地址间有个固定的位移,这就是0xC0000000,即3GB.所以,在连接内核映像时已经在所有的符号地址加了一个偏移量0xC0000000,这样star

Linux内核源代码情景分析-fork()

父进程fork出子进程: fork经过系统调用,来到了sys_fork,详细过程请参考Linux内核源代码情景分析-系统调用. asmlinkage int sys_fork(struct pt_regs regs) { return do_fork(SIGCHLD, regs.esp, &regs, 0); } int do_fork(unsigned long clone_flags, unsigned long stack_start, //stack_start为用户空间堆栈指针 str

Linux内核源代码情景分析-内存管理之slab-回收

在上一篇文章Linux内核源代码情景分析-内存管理之slab-分配与释放,最后形成了如下图的结构: 图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_shrink,代码如下: int kmem_cache_shrink(kmem_cache_t *cachep) { if (!cachep || in_interrupt() || !is_chaine

Linux内核源代码情景分析-系统调用mknod

普通文件可以用open或者create创建,FIFO文件可以用pipe创建,mknod主要用于设备文件的创建. 在内核中,mknod是由sys_mknod实现的,代码如下: asmlinkage long sys_mknod(const char * filename, int mode, dev_t dev) //比如filename为/tmp/server_socket,dev是设备号 { int error = 0; char * tmp; struct dentry * dentry;

Linux内核源代码情景分析-文件系统的安装

执行sudo mount -t ext2 /dev/sdb1 /mnt/sdb,将文件系统挂在到/mnt/sdb上.系统调用mount,映射到内核层执行的是sys_mount.假设/dev/sdb1和/mnt/sdb都位于ext2文件系统中. asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type, unsigned long flags, void * data)//dev_name指向了"/dev/sdb

Linux内核源代码情景分析-访问权限与文件安全性

在Linux内核源代码情景分析-从路径名到目标节点,一文中path_walk代码中,err = permission(inode, MAY_EXEC)当前进程是否可以访问这个节点,代码如下: int permission(struct inode * inode,int mask) { if (inode->i_op && inode->i_op->permission) { int retval; lock_kernel(); retval = inode->i_