`
xitong
  • 浏览: 6313639 次
文章分类
社区版块
存档分类
最新评论

深入分析Tasklet机制

 
阅读更多

深入分析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();
       }
}
分享到:
评论

相关推荐

    tasklet.pdf

    ### Tasklet原理与源码分析 #### 一、Tasklet基本概念 Tasklet是一种用于Linux内核中的中断下半部机制,它主要建立在softirq的基础...此外,掌握tasklet的原理及其源码分析也有助于更深入地理解Linux内核的工作机制。

    深入分析Linux内核源码

    深入分析Linux内核源码 前言 第一章 走进linux 1.1 GNU与Linux的成长 1.2 Linux的开发模式和运作机制 1.3走进Linux内核 1.3.1 Linux内核的特征 1.3.2 Linux内核版本的变化 1.4 分析Linux内核的意义 ...

    深入分析Linux内核源码.pdf

    通过上述内容,我们不仅对Linux内核有了更深入的理解,还掌握了分析和研究Linux内核源代码的基本方法和技巧。这对于从事操作系统开发、计算机科学研究以及高级软件开发等领域的人来说都是非常宝贵的资源。

    Linux操作系统分析与实践

    《Linux操作系统分析与实践》是北京大学的一门课程资料,主要关注的是Linux操作系统的内部机制,特别是网卡驱动程序和tasklet的实现与应用。在学习这门课程时,你将深入理解Linux内核的工作原理,并通过实践提升你的...

    SpringBatchSample:Spring Batch 示例 4 书

    通过分析和运行源代码,开发者可以深入理解Spring Batch的工作机制,从而在自己的项目中有效应用这一强大的批处理框架,解决大数据处理的挑战。无论是Java开发新手还是经验丰富的专业人士,都能从中受益,提升批处理...

    pc104 arm linux 驱动

    - 使用`tasklet`机制实现延时处理,避免阻塞中断上下文。 5. **文件操作接口** - 定义`file_operations`结构体,实现读写、打开关闭等操作。 #### 五、总结 本篇文档提供了基于ARM架构下的PC104 Linux驱动的...

    Linux2.6内核标准教程(共计8-- 第1个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第6个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Spring Batch 实例代码

    通过分析和运行`batch_sample`中的代码,你可以深入理解Spring Batch如何处理数据,如何定义和配置批处理作业,以及如何自定义Readers、Processors和Writers来满足特定需求。此外,你还可以学习到如何利用Spring ...

    Linux2.6内核标准教程(共计8--第3个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第4个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第2个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第7个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第5个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux2.6内核标准教程(共计8--第8个)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...

    Linux驱动开发探究_Part2.rar

    4. **Tasklet**:Tasklet是一种轻量级的软中断处理机制,它在中断上下文之外运行,可以用于快速响应硬件事件。Tasklet的使用需要理解其注册、调度和执行的原理。 5. **工作队列**:工作队列是Linux内核中的一种异步...

Global site tag (gtag.js) - Google Analytics