linux内核源码分析之中断tasklet

目录

1、前言

2、中断服务例程ISR

3、tasklet

注册tasklet

执行tasklet


1、前言

硬件中断(hardware interrupt ):
        由系统自身和与之连接的外设自动产生。它们用于支持更高效地实现设备驱动程序,也用于引起处理器自身对异常或错误的关注
软中断(SoftIRQ ):
        用于有效实现内核中的延期操作。
同步中断和异常:

        这些由CPU自身产生,针对当前执行的程序 触发原因 1)运行时发生的程序设计错误(典型的例子是除0) ;2)出现了异常的情况或条件。

异步中断:
        这是经典的中断类型,由外部设备产生,可能发生在任意时间。

2、中断服务例程ISR

        
        在处理程序执行期间,发生了其他中断。尽管可以通过在处理程序执行期间禁用中断来防止,但这会引起其他问题,如遗漏重要的中断,因而只能短时间使用。
1. 注册IRQ
        由设备驱动程序动态注册ISR的工作
int request_irq(unsigned int irq, 
                    irqreturn_t handler, 
                        unsigned long irqflags, const char *devname, void *dev_id)

linux内核源码分析之中断tasklet_第1张图片

        内核首先生成一个新的irqaction实例,然后用函数参数填充其内容,特别重要的是处理程序函数handler
        如果安装的处理程序是该IRQ编号对应链表中的第一个,则调用handler->startup初始化函数。如果该IRQ此前已经安装了处理程序,则没有必要再调用该函数。
        register_irq_proc在proc文件系统中建立目录/proc/irq/NUM。而register_handler_proc生成proc/irq/NUM/name

        在设备请求一个系统中断,而中断控制器通过引发中断将该请求转发到处理器的时候,内核
将调用该处理程序函数

        ISR必须满足:尽可能少的代码,以支持快速处理;不能彼此干扰。
        那么延时较长的处理在什么地方执行呢? 内核中提供了下半部 软中断、tasklet、工作队列
软中断类型为
enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	IRQ_POLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
};
linux内核源码分析之中断tasklet_第2张图片

                                                ISR 软中断 tasklet之间的关系 

3、tasklet

  • 更易于扩展,因而更适合于设备驱动程序
  • 同一个tasklet只能在一个CPU上运行,不同的tasklet可以在不同的CPU上运行
  • tasklet使用了软中断枚举的TASKLET_SOFTIRQ和HI_SOFTIRQ
struct tasklet_struct
{
	struct tasklet_struct *next;
    //TASKLET_STATE_SCHED:在tasklet注册到内核,等待调度执行
    //TASKLET_STATE_RUN:当前正在执行
	unsigned long state;
	atomic_t count;//原子计数
	void (*func)(unsigned long);//tasklet的函数执行体
	unsigned long data;
};
使用 tasklet_init 函数初始化 tasklet
void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
{
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}
EXPORT_SYMBOL(tasklet_init);

注册tasklet

tasklet_schedule将一个tasklet注册到系统中
tasklet_schedule在什么时候调用呢? 在ISR中调用
irqreturn_t xxx_handler(int irq, void *dev_id) {
 ......
 /* 调度 tasklet */
 tasklet_schedule(&testtasklet);
 ......
}
tasklet_schedule做了什么工作?
tasklet_schedule->__tasklet_schedule->__tasklet_schedule_common
static void __tasklet_schedule_common(struct tasklet_struct *t,
				      struct tasklet_head __percpu *headp,
				      unsigned int softirq_nr)
{
	struct tasklet_head *head;
	unsigned long flags;

	local_irq_save(flags);
	head = this_cpu_ptr(headp);
	t->next = NULL;
	*head->tail = t;
	head->tail = &(t->next);
	raise_softirq_irqoff(softirq_nr);
	local_irq_restore(flags);
}
  • 如果设置了TASKLET_STATE_SCHED标志位,则结束注册过程,因为该tasklet此前已经注册了。
  • 否则,将该tasklet置于一个链表的起始,其表头是特定于CPU的变量tasklet_vec。
  • 该链表包含了所有注册的tasklet,使用next成员作为链表元素。
  • 在注册了一个tasklet之后,tasklet链表即标记为即将进行处理。

执行tasklet

        tasklet执行体的注册,基于软中断

softirq_init->open_softirq->tasklet_action->tasklet_action_common->while循环执行

void __init softirq_init(void)
{
	int cpu;

	for_each_possible_cpu(cpu) {
		per_cpu(tasklet_vec, cpu).tail =
			&per_cpu(tasklet_vec, cpu).head;
		per_cpu(tasklet_hi_vec, cpu).tail =
			&per_cpu(tasklet_hi_vec, cpu).head;
	}

	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
	tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
}

static __latent_entropy void tasklet_hi_action(struct softirq_action *a)
{
	tasklet_action_common(a, this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ);
}

        tasklet占用了了软中断的两个中断类型(TASKLET_SOFTIRQ和HI_SOFTIRQ),优先级有高低之分,分别对应tasklet_action()和tasklet_hi_action(),需要执行的tasklet保存在tasklet_vec和tasklet_hi_vec链表中

循环执行的过程

static void tasklet_action_common(struct softirq_action *a,
				  struct tasklet_head *tl_head,
				  unsigned int softirq_nr)
{
	struct tasklet_struct *list;
    //保持中断状态寄存器并关闭本地CPU中断
	local_irq_disable();
	list = tl_head->head;
	tl_head->head = NULL;
	tl_head->tail = &tl_head->head;

    //恢复中断寄存器并开本地中断
	local_irq_enable();

	while (list) {
        //循环执行tasklet链表上每个tasklet的处理函数
		struct tasklet_struct *t = list;

		list = list->next;
        //如果tasklet没被执行,执行设备tasklet的state字段为RUNNING
		if (tasklet_trylock(t)) {
            //如果tasklet的锁计数器为0,执行
			if (!atomic_read(&t->count)) {
				if (!test_and_clear_bit(TASKLET_STATE_SCHED,
							&t->state))
					BUG();
				t->func(t->data);//*****函数的执行*****
                //如果不为0 则表示禁用,清楚RUNNING状态
				tasklet_unlock(t);
				continue;
			}
			tasklet_unlock(t);
		}
        //关本地中断,并把没有处理的tasklet 重新挂载到tasklet_vec上
		local_irq_disable();
		t->next = NULL;
		*tl_head->tail = t;
		tl_head->tail = &t->next;
        //把本地CPU上的TASKLET_SOFTIRQ标记为挂起,并使能中断
		__raise_softirq_irqoff(softirq_nr);
		local_irq_enable();
	}
}
        在while循环中执行tasklet,类似于处理软中断使用的机制。
        因为一个tasklet只能在一个处理器上执行一次,但其他的tasklet可以并行运行,所以需要特定于 tasklet 的锁。
        state状态用作锁变量。在执行一个 tasklet 的处理程序函数之前,内核使用tasklet_trylock检查tasklet的状态是否为TASKLET_STATE_RUN
static inline int tasklet_trylock(struct tasklet_struct *t) 
{ 
    return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state); 
}

        除了普通的tasklet之外,内核还使用了另一种tasklet,它具有“较高”的优先级。除以下修改之
外,其实现与普通的tasklet完全相同。
  • 使用HI_SOFTIRQ作为软中断,相关的action函数tasklet_hi_action。
  • 注册的tasklet在CPU相关的变量tasklet_hi_vec中排队。这是使用tasklet_hi_schedule完成的。

上半部与下半部选择建议
  1. 如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
  2. 如果要处理的任务对时间敏感,可以放到上半部。
  3. 如果要处理的任务与硬件有关,可以放到上半部。
  4. 除了上述三点以外的其他任务,优先考虑放到下半部。

参考:
《深入理解linux内核》
《Linux内核深度解析》

你可能感兴趣的:(linux内核分析,中断taslket)