转载_linux中断之下半部

[导读]中断方式以异步方式执行并且有可能会打断期待其他重要代码,甚至包括其它中断处理程序的执行,因此为了避免被打断的代码停止的时间过长,中断处理程序应该执行得越快越好。

1.中断是内核不可缺少的一部分,但是中断处理程序本身存在一些局限性,

a.中断方式以异步方式执行并且有可能会打断期待其他重要代码,甚至包括其它中断处理程序的执行,因此为了避免被打断的代码停止的时间过长,中断处理程序应该执行得越快越好。

b.如果当前有一个中断处理程序在运行,在最好的情况下(如果设置了SA_INTERRUPT),与该中断同级的其它中断会被屏蔽,在最坏的情况下,当前处理器上所有其它中断都会被屏蔽,导致丢失中断,因此应当让他们执行得越快越好。

c.由于中断处理程序往往需要对硬件进行操作,所以他们通常有很高的时限要求。

d.中断处理程序不在进程上下文中运行,所以他们不能被阻塞。这限制了他们所做的事情。

内核将中断分为上下两个部分,第一部分是中断处理程序,用来完成对硬件中断的即使响应,第二部分就是我们所说的下半部,完成余下的相对宽松的任务。

上下部分的具体划分:

a.如果一个任务对时间非常敏感,将其放在中断处理程序中运行

b.如果一个任务和迎接爱你相关,放到中断处理程序

c.如果一个任务要保证不被其它中断特别的相同的中断打断,将其放到中断处理程序

d.余下任务放到下半部

中断程序在运行当中,当前的中断线在所有处理器上都会被屏蔽,如果一个处理程序是SA_INTERRUPT类型,它执行的时候还会禁止所有本地中断,把本地中短线全局地屏蔽掉,缩短中断被屏蔽的时间对系统的响应能力和性能至关重要。下半部执行的关键在于他们运行的时候,允许响应中断。

2.下半部的实现方式

2.6中内核提供了三种不同形式的下半部实现机制,软中断,tasklet和工作队列。还有一个特殊的延迟工具内核定时器。

tasklet通过软中断形式实现。利用了一个软中断进入口。

3.软中断

软中断在编程的时候金泰分配,有softirq_action结构表示,在<linux/interrupt.h>中,

struct softirq_action {

void (*action)(struct softirq_action *);

void *data;

}

kernel/softirq.c中定义了一个包含32个该结构的数组。

static struct softirq_action softirq_vec[32];

由此可知最多有32个软中断。

软中断处理程序原型如下:

void softirq_handler(struct softirq_action *);

一个软中断不会抢占另外一个软中断,唯一可抢占软中断的是中断处理程序,不过其它的软中断,或者相同类型的中断是可以在其它处理器上同时运行的。

软中断的执行,触发;

一个注册的软中断必须在被标记后才会执行,这被称作触发软中断,中断处理程序会返回前标记它的软中断,使其

稍后被执行,于是在合适的时刻,该软中断就会运行,这些时刻包括如下这些:

a.从一个硬件中断处理代码返回时

b.zai ksoftirqd内核线程中

c.在显示检查和执行待处理的软中断代码中,如网络子系统中。

这些方法最终都是调用do_softirq()也就是,触发软中断一定要调用该函数。do_softirq()会循环遍历每一个调用他们的处理程序。

软中断的使用

软中断保留给系统中时间要求最严格最重要的下半部,目前只有两个子系统--网络和scsi--直接使用软中断,

此外,内核定时器和tasklet都是简历在软中断上的,如果你想加入一个软中断,首先要考虑tasklet为什么不可以。

编译期间可以通过<linux/interrupt.h>中定义一个枚举类型来静态地声明软中断,内核用从0开始表示一种相对优先级,索引号小的软中断在索引大的软中断之前运行。

已有的tasklet类型,

HI_SOFTIRQ //0,优先级高的tasklet

TIMER_SOFTIRQ //1,定时器的下半部

NET_TX_SOFTIRQ //2,发送网络数据包

NET_RX_SOFTIRQ //3,接收网络数据包

SCSI_SOFTIRQ //4,SCSI的下半部

TASKLET_SOFTIRQ //5,tasklet

可以选择定义优先级在这几个中间,一般插在网络之后最后一项之前。

软中断的注册,

open_softirq(索引号,处理函数,相关数据);

软中断处理程序执行时候,允许响应中断,但它自己不能睡眠,在一个处理程序运行的时候,当前处理器上的软中断被禁止。其它处理器仍可以执行别的软中断。实际上如果同一个软中断在它被执行的时候再次被触发,那么另外一个处理器可以同事运行其处理程序,因为进入接口只有一个,所以任何共享数据即使内部的全局变量都要严格保护。

tasklet本身也是软中断,只不过同一个处理程序的多个实例不能在多个处理器上运行。

raise_softirq()可以将一个软中断设置为挂起,让他在下次调用do_softirq时候运行。

该函数在触发一个软中断之前要先禁止中断,触发后在回复回原来的状态,如果中断本来就被禁止了,可以使用raise_softirq_irqoff.

tasklet

tasklet是利用软中断实现的一种下半部机制,在执行频率很高和连续性要求很高的情况下使用软中断,否则请使用tasklet。

tasklet本身也是软中断,tasklet由tasklet_struct结构表示,每个结构体代表一个tasklet,在<linux/interrupt.h>中定义:

struct tasklet_struct{

struct tasklet_struct *next; //下一个tasklet

unsigned long state; //状态,0,TASKLET_STATE_SCHED,TASKLET_STATE_RUN.

atomic_t count; //引用计数,如果不为0则tasklet被禁止,不允许执行,如果为0,激活

void (*func)(unsigned long); //tasklet处理函数

unsigned long data; //处理函数参数

}

结构tasklet_vec代表普通tasklet,tasklet_hi_vec代表优先级高的tasklet,tasklet由tasklet_schedule()和

tasklet_hi_schedule()函数进行调度,他们接受一个指向tasklet_stuct结构的指针作为参数,看一下

tasklet_schedule()细节,

1.检查tasklet转台是否为tasklet_state_sched,如果是说明tasklet已经被调度过了,函数立即返回,此时可能

该tasklet被调度但还没有被执行。

2.保存中断状态,禁止本地中断。保证数据不会弄乱。

3.把要调度的tasklet加到对应的tasklet_VEC或者tasklet_hi_vec中去

4.唤起TASKLET_SOFTIRQ或者TASKLET_IRQ软中断,下次就会执行do_softirq调用到

5.恢复中断

其中tasklet_action()和tasklet_hi_action()是tasklet处理的核心:

1.禁止中断,

2.将当前处理器上的该链表清空

3.允许响应中断

4.循环遍历获得链表上的每一个待处理的tasklet

5.多处理器通过检测tasklet_state_run来判断是否在其它处理器上运行,如果运行那么现在该处理器不运行,同一 时间相同的tasklet只执行一个

6.如果当前的tasklet没有执行,将其状态标志位tasklet_state_run这样别的处理器就不会执行他

7.检查count是否为0,如果被禁止,跳到下一个

8.执行tasklet

9.清除tasklet的state状态

10.重复执行下一个tasklet,直到没有剩余的等待处理的tasklet。

所有的tasklet都是通过HI_SOFTIRQ和TASKLET_SOFTIRQ这两个软中断实现,在他们之上又延伸了一层。

tasklet的使用:

1.声明你的tasklet

可以静态也可以动态






Linux 2.6中断下半部机制分析

摘要    本文主要从使用者的角度对Linux 2.6内核的下半部机制softirq、tasklet和workqueue进行分析,对于这三种机制在内核中的具体实现并未进行深入分析,倘若读者有兴趣了解,可以直接阅读Linux内核源代码的相关部分。

说明    本文档由流星自网上收集整理,按照自由软件开放源代码的精神发布,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文档协议(GFDL)。


                  目 录
1 概述
2 Linux 2.6内核中断下半部机制
    2.1 softirq机制
    2.2 tasklet机制
    2.3 workqueue机制
3 几种下半部机制的比较
4 下半部机制的选择
5 Linux与NGSA的下半部机制比较
    5.1 NGSA中断下半部机制分析
    5.2 NGSA下半部机制缺陷分析


1 概述
中断服务程序往往都需要在CPU关中断的情况下运行,以避免中断嵌套而使控制复杂化,但是关中断的时间又不能太长,否则会造成中断信号的丢失。为此,在Linux中,将中断处理程序分为两部分,即上半部和下半部。上半部通常用于执行跟硬件关系密切的关键程序,这部分执行时间非常短,而且是在关中断的环境下运行的。对时间要求不是很严格,而且通常比较耗时的一些操作,则交给下半部来执行,这部分代码是在开中断中执行的。上半部处理硬件相关,称为硬件中断,这通常需要立即执行。下半部则可以延迟一定时间,在内核合适的时间段来执行程序,这就是我们这里要讨论的软中断。
本文以目前最新版本的Linux内核2.6.22为例,来讨论Linux的中断下半部机制。在2.6版本的内核中,下半部机制主要由softirq、tasklet和workqueue来实现,下面着重对这3种机制进行分析。


2 Linux 2.6内核中断下半部机制
老版本的Linux内核中,下半部是以一种叫做Bottom Half(简称为BH)的机制来实现的,最初它是借助中断向量来实现的,在系统中用一组(共32个)函数指针,分别表示32个中断向量,这种实现方式目前在2.4版本的内核中还可以看到它的身影。但是目前在2.6版本的内核中已经看不到它了。现在的Linux内核,一般以一种称为softirq的软中断机制来实现下半部。

2.1 softirq机制
原来的BH机制有两个明显的缺陷:一是系统中一次只能有一个CPU可以执行BH代码,二是BH函数不允许嵌套。这在单处理器系统中或许没关系,但在SMP系统中却是致命的缺陷。但是软中断机制就不一样了。Linux的softirq机制与SMP是紧密相连的,整个softirq机制的设计与实现始终贯穿着一个思想:“谁触发,谁执行”(Who marks, who runs),也就是说,每个CPU都单独负责它所触发的软中断,互不干扰。这就有效地利用了SMP系统的性能和特点,极大地提高了处理效率。
Linux在include/linux/interrupt.h中定义了一个softirq_action结构来描述一个softirq请求,如下所示:
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
其中,函数指针action指向软中断请求的服务函数,而data则指向由服务函数自行解释的参数数据。
基于上述结构,系统在kernel/softirq.c中定义了一个全局的softirq软中断向量表softirq_vec[32],对应32个softirq_action结构表示的软中断描述符。但实际上,Linux并没有使用到32个软中断向量,内核预定义了一些软中断向量的含义供我们使用:
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
#ifdef CONFIG_HIGH_RES_TIMERS
HRTIMER_SOFTIRQ,
#endif
};
其中HI_SOFTIRQ用于实现高优先级的软中断,比如高优先级的hi_tasklet,而TASKLET_SOFTIRQ则用于实现诸如tasklet这样的一般性软中断。关于tasklet,我们在后面会进行介绍。我们不需要使用到32个软中断向量,事实上,内核预定义的软中断向量已经可以满足我们绝大多数应用的需求。其他向量保留给今后内核扩展使用,我们不应去使用它们。
要使用softirq,我们必须先初始化它。我们使用open_softirq()函数来开启一个指定的软中断向量nr,初始化nr对应的描述符softirq_vec[nr],设置所有CPU的软中断掩码的相应位为1。函数do_softirq()负责执行数组softirq_vec[32]中设置的软中断服务函数。每个CPU都是通过执行这个函数来执行软中断服务的。由于同一个CPU上的软中断服务例程不允许嵌套,因此,do_softirq()函数一开始就检查当前CPU是否已经正处在中断服务中,如果是则立即返回。在同一个CPU上,do_softirq()是串行执行的。
使用open_softirq()注册完一个软中断之后,我们需要触发它。内核使用函数raise_softirq()来触发一个软中断。对于一个指定的softirq来说,只会有一个处理函数,这个处理函数是所有CPU共享的。由于同一个softirq的处理函数可能在不同的CPU上同时执行,并产生竞争条件,处理函数本身的同步机制是非常重要的。激活一个软中断一般在中断的上半部中执行。当一个中断处理程序想要激活一个软中断时,raise_softirq()就会被调用。在后来的某个时刻,当do_softirq()在某个CPU上运行时,就会调用相关的软中断处理函数。
需要注意的是,在softirq机制中,还包含有一个很小的内核线程ksoftirqd。这是为了平衡系统负载而设的。试想,如果系统一直不断触发软中断请求,CPU就会不断地去处理软中断,因为至少每次时钟中断都会执行一次do_softirq()。这样一来,系统中其他重要任务不是要因长期得不到CPU而一直处于饥饿状态吗?在系统繁忙的时候,这个小小的内核线程就显得特别有用了,过多的软中断请求会被放到系统合适的时间段执行,给其他进程更多的执行机会。
在2.6内核中,do_softirq()被放到irq_exit()中执行。在中断上半部的处理中,只在irq_exit()中才调用do_softirq()进行软中断的处理,这非常有利于软中断模块的升级和移植。如果需要在我们的NGSA中移植Linux的软中断,这样的处理确实给了我们许多便利,因为我们只需要对我们的中断上半部的执行作很小的改动。如果在中断上半部有许多软中断调用的入口,那我们的移植岂不是会很痛苦?
可能有人会产生这样的疑问:系统中最多可以有32个softirq,那么这么多softirq,CPU是如何查找的呢?显然,我们在执行raise_softirq()对软中断进行触发时,必须要有一个很好的机制保证这个触发动作能够快速准确地进行。在Linux中,我们使用一种结构irq_cpustat_t来组织软中断。它在include/asm-xxx/hardirq.h中定义,其中xxx表示相应的处理器体系结构。比如对于PowerPC处理器,这个结构在include/asm-powerpc/hardirq.h中定义如下:
typedef struct {
unsigned int __softirq_pending; /* set_bit is used on this */

unsigned int __last_jiffy_stamp;
} ____cacheline_aligned irq_cpustat_t;

extern irq_cpustat_t irq_stat[];   /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
其中,__softirq_pending成员使用bit map的方式来指示相应的softirq是否激活(即是否处于pending状态)。raise_softirq的主要工作就是在__softirq_pending中设置softirq的相应位,它的实现如下:
void fastcall raise_softirq(unsigned int nr)
{
unsigned long flags;

local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}

inline fastcall void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);

if (!in_interrupt())
   wakeup_softirqd();    
/* 唤醒内核线程ksoftirqd */
}

#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)

#define or_softirq_pending(x) (local_softirq_pending() |= (x))

#define local_softirq_pending() \
__IRQ_STAT(smp_processor_id(), __softirq_pending)
这里有一个宏函数local_softirq_pending(),其实就是用于返回当前cpu的相应irq_cpustat_t结构irq_stat[cpu]的__softirq_pending成员值。因此__raise_softirq_irqoff(nr)的作用就是把要触发的softirq在__softirq_pending中的相应位置1,在do_softirq()中则通过检查irq_stat[cpu]中相应的pending位是否设置来执行该softirq。

2.2 tasklet机制
tasklet实际上是一种较为特殊的软中断,软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。tasklet一词原意为“小片任务”,在这里指一小段可执行的代码。从某种程度上来讲,tasklet机制是Linux内核对BH机制的一种扩展,但是它和BH不同,不同的tasklet代码在同一时刻可以在多个CPU上并行执行。同时,它又和一般的softirq软中断不一样,一段tasklet代码在同一时刻只能在一个CPU上运行,而softirq中注册的软中断服务函数(即softirq_action结构中的action函数指针)在同一时刻可以被多个CPU并发地执行。
Linux内核用tasklet_struct结构来描述一个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;
};
其中,各个成员的含义如下:
(1)next指针指向下一个tasklet,它用于将多个tasklet连接成一个单向循环链表。为此,内核还专门在softirq.c中定义了一个tasklet_head结构用来表示tasklet队列:
struct tasklet_head
{
struct tasklet_struct *list;
};
(2)state定义了tasklet的当前状态,这是一个32位无符号整数,不过目前只使用了bit 0和bit 1,bit 0为1表示tasklet已经被调度去执行了,而bit 1是专门为SMP系统设置的,为1时表示tasklet当前正在某个CPU上执行,这是为了防止多个CPU同时执行一个tasklet的情况。内核对这两个位的含义也进行了预定义:
enum
{
TASKLET_STATE_SCHED/* Tasklet is scheduled for execution */
TASKLET_STATE_RUN    
 /* Tasklet is running (SMP only) */
};

(3)count是一个原子计数,对tasklet的引用进行计数。需要注意的是,只有当count的值为0的时候,tasklet代码段才能执行,即这个时候该tasklet才是enable的;如果count值非0,则该tasklet是被禁止的(disable)。因此,在执行tasklet代码段之前,必须先检查其原子值count是否为0。
(4)func是一个函数指针,指向一个可执行的tasklet代码段,data是func函数的参数。

tasklet的使用其实很简单:先定义一个tasklet执行函数,然后用该函数去初始化一个tasklet描述符,接着使用tasklet的软中断触发函数去登记定义好的tasklet,以便让系统在适当的时候调度它运行。
内核为tasklet准备了两个宏定义用于声明并初始化一个tasklet描述符:
#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 }
从上面的定义可以看出,DECLARE_TASKLET在初始化一个tasklet之后,该tasklet是enable的,而DECLARE_TASKLET_DISABLED则用于初始化并disable一个tasklet。
tasklet的enable和disable操作总是成对出现,分别使用tasklet_enable()函数和tasklet_disable()函数实现。
初始化指定tasklet描述符的一般操作是用tasklet_init()来实现的,而tasklet_kill()则用来将一个tasklet杀死,即恢复到未调度的状态。如果tasklet还未执行完,内核会先等待它执行完毕。需要注意的是,由于调用该函数可能会导致休眠,所以禁止在中断上下文中调用它。
尽管tasklet机制是特定于软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ的一种实现,但是tasklet机制仍然属于softirq机制的整体框架范围内的,因此,它的设计与实现仍然必须坚持“谁触发,谁执行”的思想。为此,Linux为系统中的每一个CPU都定义了一个tasklet队列头部,来表示应该由各个CPU负责执行的tasklet队列。
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec) = { NULL };
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec) = { NULL };
其中,软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ的执行分别由各自的软中断服务程序tasklet_action()函数和tasklet_hi_action()函数来实现,这是在softirq_init()函数中指定的。前面讲到tasklet初始化完毕必须使用触发函数去登记,系统才能在适当的时候执行它们,这两个软中断的触发,分别是由函数tasklet_schedule()和tasklet_hi_schedule()来执行的。

2.3 workqueue机制
由于BH机制本身的局限性,早在2.0内核中就开始使用task queue(任务队列)机制对其进行了扩充。而在2.6内核中,则使用了另外一种机制workqueue(工作队列)来替换任务队列。
workqueue看起来有点儿类似于tasklet,它也允许内核代码请求在将来某个时间调用一个函数,所不同的是,workqueue是运行于一个特殊的内核进程上下文中的,而tasklet是运行于中断上下文中的,它的执行必须是短暂的,而且是原子态的。另外一个和tasklet不同的是,你可以请求工作队列函数被延后一个明确的时间间隔后再执行。workqueue通常用来处理不是很紧急的事件,因此它往往有比tasklet更高的执行周期,但不需要是原子操作,而且允许睡眠。
workqueue机制在include/linux/workqueue.h和kernel/workqueue.c中定义和实现。工作队列由workqueue_struct结构来维护,定义如下:
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq;
struct list_head list;
const char *name;
int singlethread;
int freezeable;   /* Freeze threads during suspend */
};
其中,cpu_workqueue_struct结构是针对每个CPU定义的。对于每一个CPU,内核都为它挂接一个工作队列,这样就可以将新的工作动态放入到不同的CPU下的工作队列中去,以此体现对“负载平衡”的支持(将work分配到各个CPU)。该结构定义如下:
struct cpu_workqueue_struct {

spinlock_t lock; /* 结构锁 */

struct list_head worklist;     /* 工作列表 */
wait_queue_head_t more_work;        /* 要进行处理的等待队列 */
struct work_struct *current_work;  
 /* 处理完毕的等待队列   */

struct workqueue_struct *wq;    /* 工作队列节点 */
struct task_struct *thread;     
/* 工作者线程指针 */

int run_depth;   /* Detect run_workqueue() recursion depth */
} ____cacheline_aligned;
我们看到,在上面有一个work_struct结构,称作工作节点结构。要提交一个任务给一个工作队列,你必须填充一个工作节点。该结构定义如下:
struct work_struct {
atomic_long_t data;
#define WORK_STRUCT_PENDING 0   /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
struct list_head entry;   /* 连接所有工作的链表节点 */
work_func_t func;         /* 工作队列函数指针,指向具体需要处理的工作 */
};
为了方便对工作队列的维护,内核创建了一个工作队列链表,所有的工作队列都可以挂接到这个链表上来:
static LIST_HEAD(workqueues);
工作队列任务可以静态或动态地创建,它创建时需要填充一个work_struct结构。内核提供了一个宏定义用来方便地声明并初始化一个工作队列任务:
#define DECLARE_WORK(n, f)      \
struct work_struct n = __WORK_INITIALIZER(n, f)
如果你想在运行时动态地初始化工作队列任务,或者重新建立一个工作任务结构,你需要下面2个接口:
#define PREPARE_WORK(_work, _func)     \
do {        \
   (_work)->func = (_func);    \
} while (0)
#define INIT_WORK(_work, _func)       \
do {         \
   (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
   INIT_LIST_HEAD(&(_work)->entry);    \
   PREPARE_WORK((_work), (_func));     \
} while (0)
其实只要用到INIT_WORK即可,PREPARE_WORK在INIT_WORK中调用。
工作队列的使用,其实也很简单。首先你需要建立一个工作队列,这一般通过函数create_workqueue(name)来实现,其中name是工作队列的名字。它会为每个CPU创建一个工作线程。当然,如果你觉得单线程用来处理你的工作已经足够,你也可以使用函数create_singlethread_workqueue(name)来创建单线程的工作队列。然后你需要把你所要做的工作提交给该工作队列。首先创建工作队列的任务,这在上面已经讲过了,接着使用函数queue_work(wq, work)把创建好的任务提交给工作队列,其中wq是要提交任务的工作队列,work是一个work_struct结构,就是你所要提交的任务。当你想要延后一段时间再提交你的任务,那么你可以使用queue_delayed_work(wq, work, delay)来提交,delay是你要延后的时间,以tick为单位,delay保证你的任务至少在指定的最小延迟之后才可能得到执行。当然了,由于delay任务的提交需要用到timer,因此你应当用另外一个结构delayed_work来替代work_struct,它实际上是在work_struct结构的基础上再增加一个timer而已:
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
相应地,初始化工作任务的接口应该改为DECLARE_DELAYED_WORK和INIT_DELAYED_WORK:
#define DECLARE_DELAYED_WORK(n, f)     \
struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f)

#define PREPARE_DELAYED_WORK(_work, _func)    \
PREPARE_WORK(&(_work)->work, (_func))
#define INIT_DELAYED_WORK(_work, _func)     \
do {        \
   INIT_WORK(&(_work)->work, (_func));   \
   init_timer(&(_work)->timer);    \
} while (0)
工作队列中的任务由相关的工作线程执行,可能是在一个无法预期的时间段内执行,这要取决于系统的负载、中断等等因素,或者至少要在延迟一段时间以后执行。如果你的任务在一个工作队列中等待了无限长的时间都无法得到运行,那么你可以用下面的方法取消它:
int cancel_delayed_work(struct delayed_work *work);
如果当一个取消操作的调用返回时任务正在执行,那么这个任务将会继续执行下去,不会因为你的取消而终止,但是它不会再加入到工作队列中来。你可以使用下面的方法清除工作队列中的所有任务:
void flush_workqueue(struct workqueue_struct *wq);
如果工作队列中还有已经提交的任务还没执行完,那么内核会进入等待,直到所有提交的任务都执行完毕为止。flush_workqueue确保所有提交的任务都能执行完,这在设备驱动关闭时候的处理程序中特别有用。
当你用完了一个工作队列,你可以销毁它:
void destroy_workqueue(struct workqueue_struct *queue);
需要注意的是,destroy一个workqueue时,如果队列上还有未完成的任务,该函数首先会执行它们。destroy操作保证所有未处理的任务在工作队列被销毁之前都能顺利完成,所以你不必担心,当你想要销毁工作队列时,是否还有工作未完成。

由于工作队列运行在内核进程的上下文中,执行过程可能休眠,因此,工作队列处理的应该是那些不是很紧急的任务,通常在系统空闲时执行。
在workqueue的初始化函数中,定义了一个针对内核中所有线程可用的事件工作队列keventd_wq,其他内核线程建立的事件工作结构就都挂到该队列上来:
static struct workqueue_struct *keventd_wq __read_mostly;

void __init init_workqueues(void)
{
/* …… */
keventd_wq = create_workqueue("events");
/* …… */
}
使用内核提供的事件工作队列keventd_wq,事实上,你提交工作任务只需要使用schedule_work(work)或schedule_delayed_work(work)即可。
我们在编写设备驱动的时候,并非所有驱动程序都需要有自己的工作队列的。事实上,一个工作队列,在许多情况下,都不需要建立自己的工作队列。如果只偶尔提交任务给工作队列,简单地使用内核提供的共享的缺省工作队列,或许会更有效。不过,由于这个工作队列可能是由很多驱动程序共享的,任务可能会需要比较长的一段时间后才能开始执行。为了解决这个问题,工作函数的延迟应该保持最小,或者干脆不要。

对于工作队列,有必要补充说明的一点是,工作队列是在2.5内核开发版本中引入的用来替代任务队列的,它的数据结构比较复杂。或许到现在,你还对上面3个数据结构的关系感到混乱,理不出头绪来。在这里,我们把3个数据结构放在一起,对它们的关系进行一点说明。这3个数据结构的关系如下图所示:

Linux 2.6中断下半部机制分析[转] - 小不懂 - 小不懂的不懂

从上面的图可以看出,位于最高一层的是工作者线程(worker_thread),就是我们在cpu_workqueue_struct结构中看到的thread成员。内核为每个CPU创建了一个工作者线程,关联一个cpu_workqueue_struct结构。每个工作者线程都是一个特定的内核线程,它们都会执行worker_thread()函数,它初始化完毕后,就开始执行一个死循环并休眠。当有任务提交给工作队列时,线程会被唤醒,以便执行这些任务,否则就继续休眠。
工作处于最底层,用work_struct结构来描述。这个结构体最重要的一个部分是一个指针,它指向一个函数,正是该函数负责处理需要延后执行的具体任务。工作被提交给工作队列后,实际上是提交给某个具体的工作者线程,然后该线程会被唤醒并执行提交的工作。
我们编写设备驱动的时候,通常大部分的驱动程序都是使用系统默认的工作者线程,它们使用起来简单、方便。但是在有些要求更严格的情况下,驱动程序需要使用自己的工作者线程。在这种情况下,系统允许驱动程序根据需要来创建工作者线程。也就是说,系统允许有多个类型的工作者线程存在,对于每种类型,系统在每个CPU上都有一个该类的工作者线程,对应于一个cpu_workqueue_struct结构。而workqueue_struct结构则用于表示给定类型的所有工作者线程。这样,在一个CPU上就可能存在多个工作队列,每一个工作队列维护一个cpu_workqueue_struct结构,也就是关联一种类型的工作者线程。
举个例子,我们的驱动在系统已有的默认工作者events类型(这是在init_workqueues中创建的系统默认工作者)的基础上,再自己加入一个falcon工作者类型:
struct workqueue_struct *mydriver_wq;
mydriver_wq = create_workqueue("falcon");
并且我们在一台具有4个处理器的计算机上工作。那么现在系统中就有4个events类型的线程和4个falcon类型的线程(相应的,就有8个cpu_workqueue_struct结构体,分别对应2种类型的工作者。同时,会有一个对应events类型的workqueue_struct和一个对应falcon类型的workqueue_struct。在提交工作的时候,我们的工作会提交给一个特殊的falcon线程,由它进行处理。


3 几种下半部机制的比较
Linux内核提供的几种下半部机制都用来推后执行你的工作,但是它们在使用上又有诸多差异,各自有不同的适用范围,使用时应该加以区分。
Linux 2.6内核提供的几种软中断机制都贯穿着“谁触发,谁执行”的思想,但是它们各自有不同的特点。softirq是整个软中断框架体系的核心,是最底层的一种机制,内核程序员很少直接使用它,大部分应用,我们只需要使用tasklet就行了。内核提供了32个softirq,但是仅仅使用了其中的几个。softirq是在编译期间静态分配的,它不像tasklet那样能够动态地创建和删除。softirq的软中断向量通过枚举对其含义进行预定义,这我们在前面2.1节中可以看到。其中,HI_SOFTIRQ和TASKLET_SOFTIRQ这两个软中断都是通过tasklet来实现的,而且也是用得最普遍的软中断。在SMP系统中,不同的tasklet可以在多个CPU上并行执行,但是同一个tasklet在同一时刻只能在一个CPU上执行,这一点和softirq不一样,softirq都可以在多个CPU上同时执行,不管是不同的softirq还是同一softirq的不同实例。tasklet是利用软中断来实现的,它和softirq在本质上非常相近,行为表现也很接近,但是它的接口更简单,锁保护的要求也较低,因而也获得了更广泛的用途。通常,只有在那些执行频率很高和连续性要求很高的情况下,我们才需要使用softirq。
HI_SOFTIRQ和TASKLET_SOFTIRQ两个软中断依靠tasklet来实现,它们的差别仅仅在于HI_SOFTIRQ的优先级高于TASKLET_SOFTIRQ,因此它会优先执行。前者称为高优先级的tasklet,而后者则称为一般的tasklet。
workqueue是另外一种能够使你的工作延后执行的机制。实际上它不是一种软中断机制,因为它和前面的两种机制都不一样,softirq和tasklet通常运行于中断上下文中,而workqueue则运行于内核进程的上下文中。之所以把它们放在一起讨论,是因为它们都是用于把中断处理剩下的工作推后执行的一种下半部机制。工作队列可以把工作推后,交由一个内核线程来执行,因此它允许重新调度,甚至是睡眠,这在softirq和tasklet一般都是不允许的。如果你推后执行的任务不需要睡眠,那么你可以选择softirq或者tasklet,但是如果你需要一个可以重新调度的实体来执行你的下半部处理,你应该使用工作队列。这是一种唯一能在进程上下文中运行的下半部实现机制,也只有它才可以睡眠。除了上面所说的差异,工作队列和软中断还有一点明显的不同,就是它可以指定一个明确的时间间隔,用来告诉内核你的工作至少要延迟到指定的时间间隔之后才能开始执行。另外,工作队列在默认情况下和软中断一样,由最初提交工作的处理器负责执行延后的工作,但是它另外提供了一个接口queue_delayed_work_on(cpu, wq, work, delay)用来提交任务给一个特定的处理器(如果是使用默认的工作队列,相应的可以使用schedule_delayed_work_on(cpu, work, delay)来提交)。这一点,也是工作队列和软中断不一样的地方。


4 下半部机制的选择
在各种下半部实现机制之间作出选择是很重要的。在目前的2.6版本内核中,有3种可能的选择,就是本文讨论的3种机制:softirq,tasklet,以及工作队列。tasklet基于softirq实现,因此两者非常相近,而workqueue则不一样,它依靠内核线程来实现。
从设计的角度考虑,softirq提供的执行序列化保障是最少的,两个甚至更多个相同类别的softirq可能在不同的处理器上同时执行,因此你必须格外小心地采取一些步骤确保共享数据的安全。如果被考察的代码本身多线索化的工作就做得非常好,比如网络子系统,它完全使用单处理器变量,那么softirq就是一个非常好的选择。对于时间要求严格和执行频率很高的应用来说,它执行得也最快。如果代码多线索化考虑得并不充分,那么选择tasklet或许会更好一些,它的接口非常简单,而且由于同一类型的tasklet不能同时在多个CPU上执行,所以它实现起来也比较简单一些。驱动程序开发者应尽可能选择tasklet而非softirq。tasklet是有效的软中断,但是它不能并发运行。如果你可以确保软中断能够在多个处理器上安全运行,那么,你还是选择softirq比较合适。
当你需要将任务推迟到进程上下文中完成,毫无疑问,你只能使用工作队列。工作队列的开销太大,因为它牵涉到内核线程甚至是上下文切换。所以如果进程上下文不是必须的,更确切地说,如果不需要睡眠,那么工作队列就应该尽量避免,softirq和tasklet或许会更合适。这并不是说工作队列的工作效率就低,在大部分情况下,工作队列都能够提供足够的支持。只是,在诸如网络子系统这样的环境中,时常经历的每秒钟几千次的中断,那么采用softirq或者tasklet机制可能会更合适一些。
当然,从易于使用的角度来考虑,首推工作队列,其次才是tasklet。最后才是softirq,它必须静态地创建,并且需要慎重地考虑其实现,确保共享数据的安全。
一般来说,驱动程序编写者经常需要做两个选择:首先,你是不是需要一个可调度的实体来执行需要推后的工作——从根本上来讲,你有休眠的需要吗?如果有,那么,工作队列将是你唯一的选择。否则最好用tasklet。其次,如果你必须专注于性能的提高,那么就考虑用softirq吧。这个时候,你还要考虑的一点是,该如何采取有效的措施,才能保证共享数据的安全。





 

linux tasklet和workqueue的应用情景


当前的2.6版内核中,有三种可能的选择:softirq、tasklet和work queue。 tasklet基于softirq实现,所以两者很相近。work queue与它们完全不同,它靠内核线程实现。

1、softirq

       软中断支持SMP,同一个softirq可以在不同的CPU上同时运行,softirq必须是可重入的。软中断是在编译期间静态分配的,它不像tasklet那样能被动态的注册或去除。kernel/softirq.c中定义了一个包含32个softirq_action结构体的数组。每个被注册的软中断都占据该数组的一项。因此最多可能有32个软中断。2.6版本的内核中定义了六个软中断:HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、TASKLET_SOFTIRQ。

       软中断的特性:
       1).一个软中断不会抢占另外一个软中断。
       2).唯一可以抢占软中断的是中断处理程序。
       3).其他软中断(包括相同类型的)可以在其他的处理其上同时执行。
       4).一个注册的软中断必须在被标记后才能执行。
       5).软中断不可以自己休眠(即调用可阻塞的函数或sleep等)。
       6).索引号小的软中断在索引号大的软中断之前执行。

2、tasklet

      引入tasklet,最主要的是考虑支持SMP,提高SMP多个cpu的利用率;两个相同的tasklet决不会同时执行。tasklet可以理解为softirq的派生,所以它的调度时机和软中断一样。对于内核中需要延迟执行的多数任务都可以用tasklet来完成,由于同类tasklet本身已经进行了同步保护,所以使用tasklet比软中断要简单的多,而且效率也不错。tasklet把任务延迟到安全时间执行的一种方式,在中断期间运行,即使被调度多次,tasklet也只运行一次,不过tasklet可以在SMP系统上和其他不同的tasklet并行运行。在SMP系统上,tasklet还被确保在第一个调度它的CPU上运行,因为这样可以提供更好的高速缓存行为,从而提高性能。

       tasklet的特性:.不允许两个两个相同类型的tasklet同时执行,即使在不同的处理器上。

3、work queue

       如果推后执行的任务需要睡眠,那么就选择工作队列。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。work queue造成的开销最大,因为它要涉及到内核线程甚至是上下文切换。这并不是说work queue的低效,但每秒钟有数千次中断,就像网络子系统时常经历的那样,那么采用其他的机制可能更合适一些。 尽管如此,针对大部分情况工作队列都能提供足够的支持。

      工作队列特性:
      1).工作队列会在进程上下文中执行!
      2).可以阻塞。(前两种机制是不可以阻塞的)
      3).可以被重新调度。(前两种只可以被中断处理程序打断)
      4).使用工作队列的两种形式:
             1>缺省工作者线程(works threads)
             2>自建的工作者线程
      5).在工作队列和内核其他部分之间使用锁机制就像在其他的进程上下文一样。
      6).默认允许响应中断。
      7).默认不持有任何锁。

4、softirq和tasklet共同点

       软中断和tasklet都是运行在中断上下文中,它们与任一进程无关,没有支持的进程完成重新调度。所以软中断和tasklet不能睡眠、不能阻塞,它们的代码中不能含有导致睡眠的动作,如减少信号量、从用户空间拷贝数据或手工分配内存等。也正是由于它们运行在中断上下文中,所以它们在同一个CPU上的执行是串行的,这样就不利于实时多媒体任务的优先处理。

5、总结

表 1. 对下半部的比较

下半部上下文顺序执行保障
软中断 中断 没有
Tasklet 中断 同类型不能同时执行
工作队列 进程 没有(和进程上下文一样被调度)

       简单地说,一般的驱动程序的编写者需要做两个选择。 首先,你是不是需要一个可调度的实体来执行需要推后完成的工作――从根本上来说,有休眠的需要吗?要是有,工作队列就是你的惟一选择。 否则最好用tasklet。要是必须专注于性能的提高,那么就考虑softirq。

 

一、中断处理的tasklet(小任务)机制

中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,Linux内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。因此,内核把中断处理分为两部分:上半部(tophalf)和下半部(bottomhalf),上半部(就是中断服务程序)内核立即执行,而下半部(就是一些内核函数)留着稍后处理,

首先,一个快速的“上半部”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲区(如果你的设备用到了DMA,就不止这些)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。

下半部运行时是允许中断请求的,而上半部运行时是关中断的,这是二者之间的主要区别。

但是,内核到底什时候执行下半部,以何种方式组织下半部?这就是我们要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫做bottomhalf(简称bh),在2.4以后的版本中有了新的发展和改进,改进的目标使下半部可以在多处理机上并行执行,并有助于驱动程序的开发者进行驱动程序的开发。下面主要介绍常用的小任务(Tasklet)机制及2.6内核中的工作队列机制。


小任务机制    

这里的小任务是指对要推迟执行的函数进行组织的一种机制其数据结构为tasklet_struct,每个结构代表一个独立的小任务,其定义如下:

structtasklet_struct {
structtasklet_struct *next;         /*指向链表中的下一个结构*/
          unsignedlong state;                /* 小任务的状态*/
          atomic_tcount;        /* 引用计数器*/
          void(*func) (unsigned long);                /* 要调用的函数*/
          unsignedlong data;                 /* 传递给函数的参数*/
};
结构中的func域就是下半部中要推迟执行的函数,data是它唯一的参数。
State域的取值为TASKLET_STATE_SCHED或TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示小任务已被调度,正准备投入运行,TASKLET_STATE_RUN表示小任务正在运行。TASKLET_STATE_RUN只有在多处理器系统上才使用,单处理器系统什么时候都清楚一个小任务是不是正在运行(它要么就是当前正在执行的代码,要么不是)。
Count域是小任务的引用计数器。如果它不为0,则小任务被禁止,不允许执行;只有当它为零,小任务才被激活,并且在被设置为挂起时,小任务才能够执行。
1. 声明和使用小任务大多数情况下,为了控制一个寻常的硬件设备,小任务机制是实现下半部的最佳选择。小任务可以动态创建,使用方便,执行起来也比较快。
我们既可以静态地创建小任务,也可以动态地创建它。选择那种方式取决于到底是想要对小任务进行直接引用还是一个间接引用。如果准备静态地创建一个小任务(也就是对它直接引用),使用下面两个宏中的一个:
DECLARE_TASKLET(name,func, data)
DECLARE_TASKLET_DISABLED(name,func, data)
这两个宏都能根据给定的名字静态地创建一个tasklet_struct结构。当该小任务被调度以后,给定的函数func会被执行,它的参数由data给出。这两个宏之间的区别在于引用计数器的初始值设置不同。第一个宏把创建的小任务的引用计数器设置为0,因此,该小任务处于激活状态。另一个把引用计数器设置为1,所以该小任务处于禁止状态。例如:
DECLARE_TASKLET(my_tasklet,my_tasklet_handler, dev);
这行代码其实等价于
structtasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0),
                                                        tasklet_handler,dev};
这样就创建了一个名为my_tasklet的小任务,其处理程序为tasklet_handler,并且已被激活。当处理程序被调用的时候,dev就会被传递给它。
2.  编写自己的小任务处理程序小任务处理程序必须符合如下的函数类型:
voidtasklet_handler(unsigned long data)
由于小任务不能睡眠,因此不能在小任务中使用信号量或者其它产生阻塞的函数。但是小任务运行时可以响应中断。
3. 调度自己的小任务通过调用tasklet_schedule()函数并传递给它相应的tasklt_struct指针,该小任务就会被调度以便适当的时候执行:
tasklet_schedule(&my_tasklet);        /*把my_tasklet标记为挂起 */
在小任务被调度以后,只要有机会它就会尽可能早的运行。在它还没有得到运行机会之前,如果一个相同的小任务又被调度了,那么它仍然只会运行一次。
        可以调用tasklet_disable()函数来禁止某个指定的小任务。如果该小任务当前正在执行,这个函数会等到它执行完毕再返回。调用tasklet_enable()函数可以激活一个小任务,如果希望把以DECLARE_TASKLET_DISABLED()创建的小任务激活,也得调用这个函数,如:
tasklet_disable(&my_tasklet);        /*小任务现在被禁止,这个小任务不能运行*/
tasklet_enable(&my_tasklet);        /*  小任务现在被激活*/
也可以调用tasklet_kill()函数从挂起的队列中去掉一个小任务。该函数的参数是一个指向某个小任务的tasklet_struct的长指针。在小任务重新调度它自身的时候,从挂起的队列中移去已调度的小任务会很有用。这个函数首先等待该小任务执行完毕,然后再将它移去。
4.tasklet的简单用法
    下面是tasklet的一个简单应用,以模块的形成加载。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>

static struct  t asklet_struct my_tasklet;

static void tasklet_handler (unsigned long d ata)
{
        printk(KERN_ALERT,"tasklet_handler is running./n");
}

static int __init test_init(void)
{
        tasklet_init(&my_tasklet,tasklet_handler,0);
        tasklet_schedule(&my_tasklet);
        return0;
}

static  void __exit test_exit(void)
{
        tasklet_kill(&tasklet);
        printk(KERN_ALERT,"test_exit is running./n");
}
MODULE_LICENSE("GPL");

module_init(test_init);
module_exit(test_exit);

从这个例子可以看出,所谓的小任务机制是为下半部函数的执行提供了一种执行机制,也就是说,推迟处理的事情是由tasklet_handler实现,何时执行,经由小任务机制封装后交给内核去处理。

 

二、中断处理的工作队列机制

工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。

那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。


  1. 工作、工作队列和工作者线程

如前所述,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,自己也可以创建自己的工作者线程。

  1. 表示工作的数据结构

工作用<linux/workqueue.h>中定义的work_struct结构表示:

struct work_struct{

unsigned long pending; /* 这个工作正在等待处理吗?*/

struct list_head entry; /* 连接所有工作的链表 */

void (*func) (void *); /* 要执行的函数 */

void *data; /* 传递给函数的参数 */

void *wq_data; /* 内部使用 */

struct timer_list timer; /* 延迟的工作队列所用到的定时器 */

};

这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。

3. 创建推后的工作

要使用工作队列,首先要做的是创建一些需要推后完成的工作。可以通过DECLARE_WORK在编译时静态地建该结构:

DECLARE_WORK(name, void (*func) (void *), void *data);

这样就会静态地创建一个名为name,待执行函数为func,参数为data的work_struct结构。

同样,也可以在运行时通过指针创建一个工作:

INIT_WORK(struct work_struct *work, woid(*func) (void *), void *data);

这会动态地初始化一个由work指向的工作。

4. 工作队列中待执行的函数

工作队列待执行的函数原型是:

void work_handler(void *data)

这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中。默认情况下,允许响应中断,并且不持有任何锁。如果需要,函数可以睡眠。需要注意的是,尽管该函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。

5. 对工作进行调度

现在工作已经被创建,我们可以调度它了。想要把给定工作的待处理函数提交给缺省的events工作线程,只需调用

schedule_work(&work);

work马上就会被调度,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。

有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度它在指定的时间执行:

schedule_delayed_work(&work, delay);

这时,&work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。

6. 工作队列的简单应用

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>

static struct workqueue_struct *queue = NULL;
static struct work_struct work;

static void work_handler(struct work_struct *data)
{
        printk
(KERN_ALERT "work handler function./n");
}

static int __init test_init(void)
{
        
queue = create_singlethread_workqueue("helloworld"); /*创建一个单线程的工作队列*/
        
if (!queue)
                
goto err;

        INIT_WORK
(&work, work_handler);
        schedule_work
(&work);

        
return 0;
err
:
        
return -1;
}

static void __exit test_exit(void)
{
        destroy_workqueue
(queue);
}
MODULE_LICENSE
("GPL");
module_init
(test_init);
module_exit
(test_exit);



1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可 6私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值