当前位置: 首页 > 工具软件 > Tasklet > 使用案例 >

【linux kernel】linux中断管理 | tasklet

程磊
2023-12-01

linux中断管理—tasklet

一、tasklet简介

tasklet是利用软中断实现的一种下半部机制。所以说,本质上也是软中断的一种,其运行在软中断上下文

tasklet使用struct tasklet_struct结构体来描述,如下定义(/include/linux/interrupt.h):

struct tasklet_struct
{
    //多个tasklet串接成一个链表
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
    //tasklet处理程序。
	void (*func)(unsigned long);
    //传递给tasklet处理函数的参数
	unsigned long data;
};
  • 上述state表示tasklet的状态:TASK_STATE_SCHED表示tasklet已经被调度。TASKLET_STATE_RUN表示tasklet正在运行(只支持SMP)
  • count为0表示tasklet处于激活状态;不为0表示该tasklet被禁止,不允许运行。

二、如何使用tasklet

​ 本小结描述在开发中如何使用tasklet。具体步骤如下:

​ (2-1)定义一个tasklet

​ (2-2)使用tasklet_init()函数初始化tasklet。函数原型如下(/kernel/softirq.c):

void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
  • t:要初始化的tasklet
  • func:tasklet的处理函数
  • data:需要传递给func函数的参数

也可以使用DECLARE_TASKLET(name, func, data)一次性完成tasklet的定义和初始化

  • name:tasklet的名字

  • func:tasklet的处理函数

  • data:需要传递给func函数的参数

​ (2-3)在中断上半部(即中断处理函数中),调用tasklet_schedule()函数使能tasklet在合适的时间运行。函数原型如下:

void tasklet_schedule(struct tasklet_struct *t)
  • t:需要调度的tasklet。也是DECLARE_TASKLET宏里面的name。

(2-4)一个代码示例

//定义一个tasklet
struct taskle_struct my_tasklet;

//定义一个处理函数
void tasklet_handler(unsigned long data)
{
    
}

//中断处理函数
irereturn_t my_irqHandler(int irq,void *dev_id)
{
    //....
    
    //调度tasklet
    tasklet_schedule(&my_tasklet);
    
    //....
}

//驱动入口
static int __init xxx_init(void)
{
    //....
    //初始化tasklet
    tasklet_init(&my_tasklet,tasklet_handler,data);
    
    //注册中断处理函数
    request_irq(xxx_irq, my_irqHandler, 0, "xxx", &xxx_dev);
    //...
}

//驱动出口
static void __exit xxx_exit(void)
{
	//...    
}

module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("iriczhao");

三、tasklet源码分析

​ 本小结简单分析一下tasklet的源码:

(3-1)在linux启动过程中tasklet的初始化

对于tasklet,在linux内核中每个cpu维护着两个链表,如下定义(/kernel/softirq.c):

struct tasklet_head {
	struct tasklet_struct *head;
	struct tasklet_struct **tail;
};

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
  • tasklet_vec使用软中断中的TASKLET_SOFTIRQ类型,其优先级是6;
  • tasklet_hi_vec使用软中断中的HI_SOFTIRQ,优先级是0。

在linux启动过程中,在start_kernel()函数中将调用softirq_init()函数初始化tasklet_vec、tasklet_hi_vec链表,另外还将注册TASKLET_SOFTIRQ(软中断回调函数:tasklet_action)和HI_SOFTIRQ(tasklet_hi_action)这两个软中断。如下代码(/kernel/ softirq.c):

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);
}
(3-2)tasklet_schedule()函数分析

tasklet_schdule()函数定义如下:

static inline void tasklet_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		__tasklet_schedule(t);
}

函数中调用了__tasklet_schedule(),函数定义如下(/kernel/softirq.c):

void __tasklet_schedule(struct tasklet_struct *t)
{
	unsigned long flags;

	local_irq_save(flags);
	t->next = NULL;
	*__this_cpu_read(tasklet_vec.tail) = t;
	__this_cpu_write(tasklet_vec.tail, &(t->next));
	raise_softirq_irqoff(TASKLET_SOFTIRQ);
	local_irq_restore(flags);
}
EXPORT_SYMBOL(__tasklet_schedule);

__tasklet_schdule()函数在关闭中断的情况下,把tasklet挂入到tasklet_vec链表中,然后触发一个TASKLET_SOFTIRQ 类型的软中断。

好啦,这儿有个疑问?——tasklet被挂入到tasklet_vec链表中去了,那什么时候执行呢。从上面代码片段可见,在中断函数中调用tasklet_schdule,tasklet并不会马上运行。

当一个tasklet被挂入到一个cpu的tasklet_vec链表中去后会设置TASK_STATE_SCHED标志位,只要tasklet还没有被执行,那么驱动程序中调用多次tasklet_schdule都无用。它必须在该CPU的软中断上下文中执行,直到执行完毕后清除了TASK_STATE_SCHED标志位后,才会有可能到其他CPU上运行。

在linux内核中,当中断发生,到中断得到处理整个过程中,最后会使用irq_exie()检查当前是否有pending等待的软中断。

tasklet的执行是基于软中断执行过程的。在软中断执行时,将按照中断状态__softirq_pending来依次执行pending状态的软中断。当轮到执行TASKLET_SOFTIRQ类型软中断时,其对应的回调函数tasklet_action才会被调用。

tasklet_action定义如下(/kernel/softirq.c):

static void tasklet_action(struct softirq_action *a)
{
	struct tasklet_struct *list;

	local_irq_disable();
	list = __this_cpu_read(tasklet_vec.head);
	__this_cpu_write(tasklet_vec.head, NULL);
	__this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
	local_irq_enable();

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

		list = list->next;

		if (tasklet_trylock(t)) {
			if (!atomic_read(&t->count)) {
				if (!test_and_clear_bit(TASKLET_STATE_SCHED,
							&t->state))
					BUG();
				t->func(t->data);
				tasklet_unlock(t);
				continue;
			}
			tasklet_unlock(t);
		}

		local_irq_disable();
		t->next = NULL;
		*__this_cpu_read(tasklet_vec.tail) = t;
		__this_cpu_write(tasklet_vec.tail, &(t->next));
		__raise_softirq_irqoff(TASKLET_SOFTIRQ);
		local_irq_enable();
	}
}

注:tasklet_action()softirq_init()函数中设置触发。


四、总结

tasklet在驱动程序设计中,还是会经常使用到。本文简单描述了tasklet的使用方法,以及对源码进行了些分析。


​ 由于小生知识与精力有限,如果文章存在问题,还望看官们多多批评,O(∩_∩)O哈哈~

 类似资料: