linux tasklet函数,tasklet和work_struct详解

一、tasklet

1.1、tasklet介绍

tasklet的使用比较简单,只需要定义tasklet及其处理函数并将两者关联即可,在定义时可以采用两种形式。

例子:

struct tasklet_struct my_tasklet;

Void my_tasklet_func(unsigned long);

第一种:

DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)

代码DECLARE_TASKLET实现了定义名称为my_tasklet的tasklet并将其与my_tasklet_func这个函数绑定,而传入这个函数的参数为data。

第二种:

tasklet_init(&my_tasklet, my_tasklet_func, data);

需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度,如下所示

tasklet_schedule(&my_tasklet)

下面给出驱动模板

点击(此处)折叠或打开

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);

……

}

1.2、tasklet函数详解

tasklet主要用在中断函数中。它对于中断处理特别有用,由于硬件中断必须尽快处理, 但大部分的数据管理可以延后到以后安全的时间执行。所以可以使用tasklet。

tasklet以一个数据结构形式存在,使用前必须被初始化。初始化能够通过调用一个特定函数或者通过使用某些宏定义声明结构:

点击(此处)折叠或打开

#include

struct tasklet_struct {

struct tasklet_struct *next;

unsigned long state;

atomic_t count;

void (*func)(unsigned long);

unsigned long data;

};

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 }

1) void tasklet_disable(struct tasklet_struct*t); 函数暂时禁止给定的 tasklet被 tasklet_schedule 调度,直到这个 tasklet 再次被enable;若这个 tasklet 当前在运行,这个函数忙等待直到这个tasklet退出。

2) void tasklet_disable_nosync(struct tasklet_struct*t); 和tasklet_disable类似,但是tasklet可能仍然运行在另一个 CPU。

3) void tasklet_enable(struct tasklet_struct *t);

使能一个之前被disable的 tasklet。若这个 tasklet 已经被调度, 它会很快运行。tasklet_enable和tasklet_disable必须匹配调用, 因为内核跟踪每个 tasklet 的"禁止次数"。

4) void tasklet_schedule(struct tasklet_struct *t);

调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己。

5) void tasklet_hi_schedule(struct tasklet_struct *t);

和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时, 它处理高优先级 tasklet。在其他软中断之前,只有具有低响应周期要求的驱动才应使用这个函数, 可避免其他软件中断处理引入的附加周期。

6) void tasklet_kill(struct tasklet_struct *t);

保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet正在运行,

这个函数等待直到它执行完毕。若 tasklet 重新调度它自己,则必须阻止在调用 tasklet_kill前它重新调度它自己,如同使用

del_timer_sync。

二、工作队列

Linux中的Workqueue机制就是为了简化内核线程的创建。通过调用workqueue的接口就能创建内核线程。并且可以根据当前系统CPU的个数创建线程的数量,使得线程处理的事务能够并行化。workqueue是内核中实现简单而有效的机制,他显然简化了内核daemon的创建,方便了用户的编程。

2.1、Workqueue机制的实现

Workqueue机制中定义了两个重要的数据结构,分析如下:

1、cpu_workqueue_struct

结构。

该结构将CPU和内核线程进行了绑定。在创建workqueue的过程中,Linux根据当前系统CPU的个数创建

cpu_workqueue_struct。该结构主要维护了一个任务队列,以及内核线程需要睡眠的等待队列,另外还维护了一个任务上下文,即

task_struct。

2、work_struct结构是对任务的抽象。在该结构中需要维护具体的任务方法,需要处理的数据,以及任务处理的时间。该结构定义如下:

点击(此处)折叠或打开

struct work_struct {

unsigned long pending;

struct list_head entry;

void (*func)(void *);

void *data;

void *wq_data;

strut timer_list timer;

};

用户调用workqueue的初始化接口create_workqueue或者create_singlethread_workqueue对

workqueue队列进行初始化时,内核就开始为用户分配一个workqueue对象,并且将其链到一个全局的workqueue队列中。然后

Linux根据当前CPU的情况,为workqueue对象分配与CPU个数相同的cpu_workqueue_struct对象,每个

cpu_workqueue_struct对象都会存在一条任务队列。紧接着,Linux为每个cpu_workqueue_struct对象分配一个内

核thread,即内核daemon去处理每个队列中的任务。至此,用户调用初始化接口将workqueue初始化完毕,返回workqueue的指针。

在初始化workqueue过程中,内核需要初始化内核线程,注册的内核线程工作比较简单,就是不断的扫描对应cpu_workqueue_struct中的任务队列,从中获取一个有效任务,然后执行该任务。所以如果任务队列为空,那么内核daemon就在cpu_workqueue_struct中的等待队列上睡眠,直到有人唤醒daemon去处理任务队列。

Workqueue初始化完毕之后,将任务运行的上下文环境构建起来了,但是具体还没有可执行的任务,所以,需要定义具体的work_struct对象。然后将work_struct加入到任务队列中,Linux会唤醒daemon去处理任务。

上述描述的workqueue内核实现原理可以描述如下:

A094141906-62963.jpeg

在Workqueue机制中,提供了一个系统默认的workqueue队列——keventd_wq,这个队列是Linux系统在初始化的时候就创建的。用户可以直接初始化一个work_struct对象,然后在该队列中进行调度,使用更加方便。

2.2、Workqueue编程接口

序号

接口函数

说明

1

create_workqueue

用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程。输入参数:

@name:workqueue的名称

2

create_singlethread_workqueue

用于创建workqueue,只创建一个内核线程。输入参数:

@name:workqueue名称

3

destroy_workqueue

释放workqueue队列。输入参数:

@ workqueue_struct:需要释放的workqueue队列指针

4

schedule_work

调度执行一个具体的任务,执行的任务将会被挂入Linux系统提供的workqueue——keventd_wq输入参数:

@ work_struct:具体任务对象指针

5

schedule_delayed_work

延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间,输入参数:

@work_struct:具体任务对象指针

@delay:延迟时间

6

queue_work

调度执行一个指定workqueue中的任务。输入参数:

@ workqueue_struct:指定的workqueue指针

@work_struct:具体任务对象指针

7

queue_delayed_work

延迟调度执行一个指定workqueue中的任务,功能与queue_work类似,输入参数多了一个delay。

例子:

定义一个工作队列:

struct work_struct my_wq;

void my_wq_func(unsigned long);

通过INIT_WORK可以初始化这个工作队列并将工作队列与处理函数绑定

INIT_WORK(&my_wq,my_wq_func):

调度工作队列:

schedule_work(&my_wq)

三、Workqueue与tasklet的联系

工作队列类似 tasklets,允许内核代码请求在将来某个时间调用一个函数,不同在于:

(1)tasklet 在软件中断上下文中运行,所以 tasklet 代码必须是原子的。而工作队列函数在一个特殊内核进程上下文运行,有更多的灵活性,且能够休眠。

(2)tasklet 只能在最初被提交的处理器上运行,这只是工作队列默认工作方式。

(3)内核代码可以请求工作队列函数被延后一个给定的时间间隔。

(4)tasklet 执行的很快, 短时期, 并且在原子态, 而工作队列函数可能是长周期且不需要是原子的,两个机制有它适合的情形。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值