中断子系统 ---下半部softirq tasklet以及workqueue

softirq

上节主要讲了Linux内核do_IRQ函数中中irq_enter以及generic_handle_irq(irq)函数,这是中断上半部。
这节要开始剖析中断下半部的实现原理

内核用一个无符号整型**__sofitirq_pending**来表示当前正在等待被处理的softirq,每一种softirq在__softirq_pending中占据一位
每个CPU有自己的__softirq_pending变量

typedef struct {
	unsigned int __softirq_pending;			//32bit无符号整型,足够用了,默认软中断只用了10bit
}__cacheline_aligned irq_cpustat_t;

//per-CPU变量  如上述所言,每个CPU都有自己的__softirq_pending变量
irq_cpustat_t irq_stat[NR_CPUS] __cacheline_aligned;

softirq调用在irq_exit中,irq_exit结束一个上下文,如果有需要的话执行softriqs

/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */
void irq_exit(void)
{
	account_system_vtime(current);
	trace_hardirq_exit();
	//首先把当前栈中的preempt_count变量减去IRQ_EXIT_OFFSET来标识一个HARDIRQ中断上下文的结束
	sub_preempt_count(IRQ_EXIT_OFFSET);
	//in_interrupt函数用于判断当前是否在一个中断上下文中执行
	//local_softirq_pending用于判断__softirq_pending是否有等待的softirq
	//保证如果中断在softirq部分发生,那么只会处理hardirq部分,然后直接返回到softirq被中断的部分继续执行。
	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();
}

//下面这个宏用来决定在hardirq部分结束时,有没有关闭处理器响应外部中断的能力。
//体系结构相关,有的结构没有这个能力,就需要调用do_softirq,它会先做一些中断屏蔽的事情然后再调用__do_softirq
//否则直接调用__do_softirq
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
static inline void invoke_softirq(void)
{
	if (!force_irqthreads)
		__do_softirq();
	else
		wakeup_softirqd();
}
#else
static inline void invoke_softirq(void)
{
	if (!force_irqthreads)
		do_softirq();
	else
		wakeup_softirqd();
}
#endif

下面要看do_softirq函数,先要了解内核中已经定义好的软中断类型有哪些:
中断子系统 ---下半部softirq tasklet以及workqueue_第1张图片
每一个softirq对应__softirq_pending中的一个位
通过枚举类型来静态声明软中断,并且每一种软中断都使用索引来表示一种相对的优先级,索引号越小,软中断优先级高,并在一轮软中断处理中得到优先执行。其中:
HI_SOFTIRQ,优先级为0,是最高优先级的软中断类型。
TIMER_SOFTIRQ,优先级为1,用于定时器的软中断。
NET_TX_SOFTIRQ,优先级为2,用于发送网络数据包的软中断。
NET_RX_SOFTIRQ,优先级为3,用于接收网络数据包的软中断。
BLOCK_SOFTIRQ和BLOCK_IOPOLL_SOFTIRQ,优先级分别是4和5,用于块设备的软中断。
TASKLET_SOFTIRQ,优先级为6,专门为tasklet机制准备的软中断。
SCHED_SOFTIRQ,优先级为7,进程调度以及负载均衡。
HRTIMER_SOFTIRQ,优先级为8,高精度定时器。
RCU_SOFTIRQ,优先级为9,专门为RCU服务的软中断。
SoftIRQ软中断的接口函数如下。
void open_softirq(int nr, void (*action)(struct softirq_action *))
void raise_softirq(unsigned int nr)
open_softirq()函数接口可以注册一个软中断,其中参数nr是软中断的序号。
raise_softirq()函数是主动触发一个软中断的API接口函数。

可以说每个softirq类型都是一个坑位,势必要将对应类型的softirq和实际的处理函数对应起来:
内核定义了一个struct softirq_action类型的数组softirq_vec来保存实际的处理函数:

struct softirq_action 
{
	void (*action)(struct softirq_action *);
};
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

__do_softirq的核心思想就是从当前CPU的__softirq_pending的最低位开始依次往高位遍历,如果bitn是1,这执行softirq_vec数组对应索引上的action函数:


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);
	//表示softirq上下文
	__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);
	//开中断执行
	local_irq_enable();

	h = softirq_vec;		//要对h操作,softirq_vec数组保存到h中,防止被改更
	//循环,__softirq_pending的低位先执行
	do {
		if (pending & 1) {
			unsigned int vec_nr = h - softirq_vec;
			int 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())) {
				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;
	} while (pending);

	local_irq_disable();

	pending = local_softirq_pending();
	if (pending && --max_restart)
		goto restart;

	if (pending)
		//唤醒ksoftirqd处理  防止softirq部分耗费太多时间,所以将这些处理放到进程上下文执行
		wakeup_softirqd();

	lockdep_softirq_exit();

	account_system_vtime(current);
	__local_bh_enable(SOFTIRQ_OFFSET);
}

tasklet

Linux系统初始化期间通过调用softirq_init为TASKLET_SOFTIRQ和HI_SOFTIRQ安装执行函数

void softirq_init(void)
{
	for_each_possible_cpu(cpu){
		...
		for(int i=0; i<NR_SOFTIRQS; i++)
			
	}
	//给softirq_vec上的TASKLET_SOFTIRQ和HI_SOFITIRQ位置上安装对应的执行函数
	//相当于:
	//softirq_vec[TASKLET_SOFTIRQ].action = tasklet_action;
	//softirq_vec[HI_SOFTIRQ].action = tasklet_hi_action;
	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

提交tasklet

一个tasklet对象被设计为struct tasklet_struct类

struct tasklet_struct 
{
	struct tasklet_struct *next;	//用于让tasklet成员构成链表
	unsigned long state;			//记录每个tasklet在系统中的状态:SCHED or RUN
	atomic count;					//count=0则tasklet是enabled
	void (*func)(unsigned long);	//tasklet上的执行函数或者延迟函数
	unsigned long data;				//传入func函数的参数
}

声明并静态初始化一个tasklet对象,内核已经为驱动程序准备了DECLARE_TASKLET宏:

#define DECLARE_TASKLET(name, func, data) \
	struct tasklet_struct name = {NULL, 0, ATOMIC_INIT(0), func, data}

如果驱动在运行过程中构建了一个tasklet对象,对齐初始化可以调用tasklet_init函数完成:

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
{
	t->next = NULL;
	...
}

向系统提交这个tasklet使用tasklet_shcedule也就是将tasklet对象加入到tasklet_vec管理的链表中。
HI_SOFTIRQ提交的tasklet对象的函数为tasklet_hi_schedule

工作队列work queue

流水线 客户 加工件打包 送到流水线 工人
worklist 需要使用工作队列的驱动 structure work_struct对象 queue_work函数调用 worker_thread

你可能感兴趣的:(单片机,嵌入式硬件)