tasklet(小任务):

tasklet在很多方面类似内核定时器:他们始终在中断期间运行,始终会在调度他们的同一CPU上运行,而且都接收一个unsigned long参数。不同的是,不能要求tasklet在某个给定的时间执行,调度一个tasklet,表明我们只是希望内核选择某个其后的时间来执行给定的函数。这种行为对中断例程来说是尤为有用。和内核定时器类似,tasklet也会在“软件中断”上下文以原子模式执行,软件中断是打开硬件中断的同时执行某些异步任务的一种内核机制。


tasklet以数据结构的形式存在,并在使用前必须初始化,调用特定函数或使用特定宏:

#include<linux/interrupt.h>

struct tasklet_struct{

void (*func)(unsigned long);

unsigned long data;

}


void tasklet_init(struct tasklet_struct t, void (*func) (unsigned long), unsigned long data);

DECLARE_TASKLET(name, func, data);

DECLARE_TASKLET_DISABLED(name, func, data);

tasklet特性:

一个tasklet可以稍后禁止或者重新启用,只有启用和禁止的次数相同时,tasklet才会被执行

和定时器类似,tasklet也可以注册自己

tasklet可被调度以在通常的优先级或者高优先级执行。高优先级的tasklet总会首先执行

如果系统负荷不重,则tasklet会立即得到执行,但始终不会晚于下一个定时器滴答

一个tasklet可以和其他tasklet并发,但对自身来讲是严格串行处理的,也就是同一个tasklet永远不会在多个处理器上同时运行


tasklet相关的内核接口:

void tasklet_disable(struct tasklet_struct *t);

禁止指定的tasklet,该tasklet仍然可以用tasklet_schedule调度,但其执行被推迟,直到该tasklet被重新启用。如果tasklet当前正在运行,该函数会进入忙等待直到tasklet退出为止。因此,在调用tasklet_disable之后,我们可以确信该tasklet不会在系统中任何地方运行。


void tasklet_disable_nosync(struct tasklet_struct *t);

禁用指定的tasklet,但不会等待任何运行的tasklet推出。

void tasklet_enable(struct tasklet_struct *t);

启用一个先前被禁止的tasklet,如果tasklet已经被调度,那么会很快运行,对tasklet_enable的调用必须和每个对tasklet_disable的调用匹配,因为内核对每个tasklet保存有一个计数器。


void tasklet_schedule(struct tasklet_struct *t);

调度执行指定的tasklet。如果在获得运行机会之前,某个tasklet被再次调度,则该tasklet只会运行一次。但是如果在该tasklet运行时被调度,就会在完成后再次运行。这样,可确保正在处理时间时发生的其他事件也会被接收并注意到。这种行为也允许tasklet重新调度自身。


void tasklet_hi_schedule(struct tasklet_struct *t);

调度指定的tasklet以高优先级执行,当前软件中断处理例程运行时,它会在处理其它软件中断任务(包括通常的tasklet)之前处理高优先级的tasklet。理想状态下只有具备低延迟需求的任务才能使用这个函数,这样可以避免由其他软件中断处理例程引入的额外延迟。


void tasklet_kill(struct tasklet_struct *t);

该函数确保指定的tasklet不会被再次调度。如果tasklet正被调度执行,该函数会等待其退出,如果tasklet重新调度自身,则应该避免在调用tasklet_kill之前完成重新调度,这个del_timer_sync的处理类似。


tasklet的实现在kernel/softirq.c中,其中有两个tasklet链表,他们作为per-CPU数据结构而声明。



工作队列(warkqueue):

表面上和tasklet没什么区别,但两者之间存在重要区别:

tasklet在软件中断上下文中运行,所有tasklet代码都必须是原子的。工作队列在特殊的内核进程上下文,可以休眠。

tasklet始终运行在被初始提交的同一处理器上,但这只是工作队列的默认方式。

内核代码可以请求工作队列函数的执行延迟给定的时间间隔。

区别关键在于,tasklet会在很短时间内很快执行,并且以原子模式执行,而工作队列函数可具有更长的延迟且不必原子化。


工作队列有struct workqueue_struct 类型,该结构定义在<linux/workqueue.h>中,在使用之前 ,必须显式创建一个工作队列,可使用下面两个函数之一:

struct workqueue_struct *create_workqueue(const char *name);

struct workqueue_struct *create_singlethread_workqueue(const char *name);

每个工作队列有一个或者多个专用的进程(“内核线程”),这些进程运行提交到该队列的函数,create_workqueue会给在系统中的每个处理器上为工作队列创建专用的线程。如果单个线程够用,应使用create_singlethread_workqueue。


向一个工作队列提交一个任务,需要填充一个work_struct结构,编译时构造可以使用宏:

DECLARE_WORK(name, void (*function) (void *), void *data):

其中name是结构名称

运行时构造:
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);

PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);

INIT_WORK完成更加彻底的结构初始化工作;首次构造该结构时,应该使用这个宏。

PREPARE_WORK完成几乎相同的工作,但不会初始化用来将work_struct结构链接到工作队列的指针。如果结构已经被提交到工作队列,而只需修改该结构,则应该使用PREPARE_WORK而不是INIT_WORK。


提交到工作队列,使用下两函数之一:
int queue_work(struct workqueue_struct *queue, struct work_struct *work);

int queue_delayed_work(struct workqueue_struct *queue, struct work_strcut *work, unsigned long delay);

delayed版本会在至少经过指定的jiffies之后才执行,成功添加返回0,如果非0表示已经在该队列不能两次添加。

工作函数在工作线程上下文运行,必要时可以休眠,应该仔细考虑休眠会不会影响提交到同一工作队列的其他任务。该函数不能访问用户空间,因为是在内核线程,没有用户空间可以访问。

取消挂起的任务队列入口项可调用:

int cancel_delayed_work(struct work_queue *work);

如果在执行前被取消则返回非零值,返回0表示已经在其他处理器上运行。应此在cancel_delayed_work返回后,工作函数仍可能在运行。为了绝对确保在cancel_delayed_work返回0后,工作函数不会在系统任何地方运行,则应该随后调用下面的函数:

void flush_workqueue(struct workqueue_struct *queue);

在flush_workqueue返回后,任何在该调用之前被提交的工作函数都不会在系统任何地方运行。


在结束对工作队列的使用后,可调用下面的函数释放相关资源:

void destroy_workqueue(struct workqueue *queue);



共享队列:

可以使用内核提供的共享的默认工作队列。共享意味着不应长时间独占该队列,即不能长时间休眠,而我们的人物可能需要更长的时间才能的到执行。