深入分析Tasklet机制
一、tasklet使用
Tasklet的使用比较简单,每个Tasklet结构体有一个函数指针,指向你自己定义的函数。当我们要使用 tasklet ,首先新定义一个tasklet_struct结构,并初始化好要执行函数指针,然后将它挂接到 task_vec 链表中,并触发一个软中断就可以等着被执行了。
1.1.定义tasklet_struct结构
Void my_tasklet_func(unsigned long)
DECLARE_TASKLET(my_tasklet.my_tasklet_func,data)
代码DECLARE_TASKLET实现了定义名称为my_tasklet的tasklet并将其与my_tasklet_func这个函数绑定,而传入这个函数的参数为data。需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度,如下所示:
1.2.调度tasklet
Tasklet_schedule(&my_tasklet)
此函数将定义后的tasklet挂接到cpu的tasklet_vec链表,具体是哪个cpu的tasklet_vec链表,是根据当前线程是运行在哪个cpu来决定的。此函数不仅会挂接tasklet ,而且会引起一个软tasklet的软中断 , 即把tasklet对应的中断向量挂起 (pend) 。
1.3.驱动模板
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);
void xxx_do_tasklet(unsigned long)
{
……
}
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
……
tasklet_schedule(&xxx_tasklet);
……
}
int _init xxx_init(void)
{
……
result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL)
……
}
void _exit xxx_exit(void)
{
……
free_irq(xxx_irq,xxx_irq_interrupt);
……
}
二、tasklet函数详解
2.1.tasklet_struct
tasklet对于中断处理特别有用:硬件中断必须尽快处理, 但大部分的数据管理可以延后到以后安全的时间执行。tasklet 以一个数据结构形式存在,使用前必须被初始化。初始化能够通过调用一个特定函数或者通过使用某些宏定义声明结构:
#include <linux/interrupt.h>
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
在这个结构体中,第一个成员代表链表中的下一个tasklet。第二个变量代表此刻tasklet的状态,一般为TASKLET_STATE_SCHED,表示此tasklet已被调度且正准备运行;此变量还可取TASKLET_STATE_RUN,表示正在运行,但只用在多处理器的情况下。count成员是一个引用计数器,只有当其值为0时候,tasklet才会被激活;否则被禁止,不能被执行。而接下来的func变量很明显是一个函数指针,它指向tasklet处理函数,这个处理函数的唯一参数为data。
2.2.tasklet操作函数
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
void tasklet_disable(struct tasklet_struct *t);
/*函数暂时禁止给定的tasklet被tasklet_schedule调度,直到这个tasklet被再次被enable;若这个tasklet当前在运行, 这个函数忙等待直到这个tasklet退出*/
void tasklet_disable_nosync(struct tasklet_struct *t);
/*和tasklet_disable类似,但是tasklet可能仍然运行在另一个 CPU */
void tasklet_enable(struct tasklet_struct *t);
/*使能一个之前被disable的tasklet;若这个tasklet已经被调度, 它会很快运行。tasklet_enable和tasklet_disable必须匹配调用, 因为内核跟踪每个tasklet的"禁止次数"*/
void tasklet_schedule(struct tasklet_struct *t);
/*调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己*/
void tasklet_hi_schedule(struct tasklet_struct *t);
/*和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时, 它处理高优先级 tasklet 在其他软中断之前,只有具有低响应周期要求的驱动才应使用这个函数, 可避免其他软件中断处理引入的附加周期*/
void tasklet_kill(struct tasklet_struct *t);
/*确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行, 这个函数等待直到它执行完毕。若 tasklet 重新调度它自己,则必须阻止在调用 tasklet_kill 前它重新调度它自己,如同使用 del_timer_sync*/
三、实现分析
我们就从上面这个实例入手来分析tasklet的实现,主要分析tasklet_schedule()函数的实现。
static inline void tasklet_schedule(struct tasklet_struct *t)
{
/*如果需要调度的tasklet的state不为TASKLET_STATE_SCHED,则触发之。这样,就保证了多个cpu不可能同时运行同一个tasklet,因为如果一个tasklet被调度过一次,那么它的state字段就会被设置TASKLET_STATE_SCHED标记,然后插入per-cpu变量的链表中。如果这时另外一个cpu也去调度该tasklet,那么就会在下面的if语句中被挡掉,不会运行到__tasklet_schedule(),从而不会插入到另外这个cpu的per-cpu变量的链表中,就不会被运行到。所以这里是保证了tasklet编写的函数不用是可重入的,这样就方便了编程人员。(注意,softirq机制需要编写可重入的函数)*/
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
我们来看__tasklet_schedule()的实现:
void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags);
/*把需要添加进系统的自己编写的struct tasklet_struc加入到per-cpu变量tasklet_vec的本地副本的链表的表头中*/
t->next = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = t;
raise_softirq_irqoff(TASKLET_SOFTIRQ); /*触发softirq的TASKLET_SOFTIRQ*/
local_irq_restore(flags);
}
这段代码也非常简单,只是把自己要注册到系统中的tasklet_struct挂入到per-cpu变量tasklet_vec的list中而已,这里是挂到链表首部。因为需要修改per-cpu变量tasklet_vec的list的值,为了防止中断处理程序也去修改这个值,为了保持数据的一致性,所以关闭中断。然后通过raise_softirq_irqoff()设置低优先级的tasklet对应的softirq标记,以便cpu在运行softirq的时候运行到tasklet,因为tasklet是凌驾在softirq机制之上的。
这里就完成了我们自己的my_tasklet的注册和触发对应的softirq,那我们现在就应该分析tasklet的运行了。我们前面提到,tasklet是凌驾在softirq机制之上的。Linux有六种softirq,优先级最高的是HI_SOFTIRQ,优先级最低的是TASKLET_SOFTIRQ,一般情况下我们是利用TASKLET_SOFTIRQ来实现tasklet的功能。在open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL)中定义了处理tasklet的处理函数tasklet_action。所以我们要分析这个函数的实现:
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
/*把per-cpu变量tasklet_vec的本地副本上的list设置为NULL,由于这里要修改per-cpu变量,为了防止中断处理程序或者内核抢占造成该数据的不一致性,所以这里禁止中断再修改数据,然后再开启中断.(注意,关闭本地中断的副作用就是禁止内核抢占,因为内核抢占只有两个时间点: 1.中断返回到内核态;2.手动使能内核抢占。明显不会在临界区内手动使能内核抢占,所以关闭本地中断的副作用就是禁止内核抢占)*/
local_irq_disable();
list = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = NULL;
local_irq_enable();
/*遍历tasklet链表,让链表上挂入的函数全部执行完成*/
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); /*真正运行user注册的tasklet函数的地方*/
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
/*这里相当于把tasklet的list指针从链表中后移了(可以自行画图分析),所以刚才运行过的tasklet回调函数以后不会再次运行,除非用于再次通过tasklet_schedule()注册之*/
local_irq_disable();
t->next = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = t;
__raise_softirq_irqoff(TASKLET_SOFTIRQ); /*再一次触发tasklet对应的softirq,使下次系统运行softirq时能运行到tasklet*/
local_irq_enable();
}
}
分享到:
相关推荐
1. Tasklet机制分析 上面我们介绍了软中断机制,linux内核为什么还要引入tasklet机制呢?主要原因是软中断的pending标志位也32位,一般情况是不随意增加软中断处理的。而且内核也没有提供通用的增加软中断的接口...
中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。...
在编写设备驱动时, tasklet 机制是一种比较常见的机制,通常用于减少中断处理的时间,将本应该是在中断服务程序中完成的任务转化成软中断完成。 为了最大程度的避免中断处理时间过长而导致中断丢失,有时候我们...
重试机制:不同的Manager实现提供了不同的重试策略。 示例程序hiapk通过使用StackManager实现了即时重试(详情参见Step01.java) 目录结构 tasklet: tasklet源工程, 可以从target文件夹中下载...
深入分析Linux内核源码 前言 第一章 走进linux 1.1 GNU与Linux的成长 1.2 Linux的开发模式和运作机制 1.3走进Linux内核 1.3.1 Linux内核的特征 1.3.2 Linux内核版本的变化 1.4 分析Linux内核的意义 ...
2.分析对比Tasklet和工作队列的差异。 二、实验环境 Linux 3.14.0 嵌入式开发板 三、实验内容及实验原理 写一个简单的驱动程序,要求: ①定义一个Tasklet和一个工作队列,实现打印输出 ②定义两个定时器,...
#NAME tasklet - 用于协作任务调度的 Java 库#描述##Tasklet Tasklet 是与 tasklet 调度程序合作来决定如何(同步或异步)以及何时执行它们的小型工作单元。 Tasklet 实现了一个task()方法,调度程序调用该方法来...
tasklet.pdf
驱动2440Q3按键中断方式(tasklet delaywork work)
lab1_tasklet实验说明在第5章的虚拟FIFO设备中,我们并没有考虑多个进程同时访问设备驱动的情况,请使用互斥锁对虚拟FIFO设备驱动程序进行并发保护基
Linux系统中的Tasklet及其在ALSA中的应用.pdf
这个流程图粗略地描述了softirq, tasklet, bottomhalt, task queue这些对象之间的联系及调用流程。 主要依据:《Linux内核的Softirq机制》和《软中断概况》 图中可能存在错误,希望您的指正!
描述了linux中断的上半部即中断的注册、中断处理。中断的下半部机制,包括软中断、tasklet和工作队列
如果你的任务不涉及读,处理,写 这种特点,只是简单的任务处理 springbatch 也提供了 tasklet 来处理任务。所以在demo中 整合这两者 ,实现在不修改原有任务, 进行任务step 的添加 ,删除,修改等。
如果中断下半部使用tasklet机制的话,实际上是利用软件中断来处理中断下半部。因此tasklet中不允许使用可能引起进行睡眠的函数调用。而work机制则是在一个独立的进程中来处理中断下半部,所以它允许调用可能引起进程...
本文档介绍在linux内核中两个不同进程或者过程访问和使用同一...其中处理竟态条件的同步机制在不同内核过程中处理的应用(中断,softirq,tasklet,timer,用户上下文)。 然后介绍了内核抢占,内核可能睡眠的函数。
Linux操作系统分析与实践 北大课件(关于网卡驱动和tasklet这部分可以看看)
通用群集/负载平衡平台(通过LAN或Internet),使用基于Java的P2P Aorta工作者执行Java“任务集”。 可以实施各种小任务来解决分形,处理图像,渲染网页,破解RSA“蛮力”的问题。
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...