linux工作队列.docx

上传人:b****4 文档编号:4304380 上传时间:2022-11-29 格式:DOCX 页数:13 大小:25.82KB
下载 相关 举报
linux工作队列.docx_第1页
第1页 / 共13页
linux工作队列.docx_第2页
第2页 / 共13页
linux工作队列.docx_第3页
第3页 / 共13页
linux工作队列.docx_第4页
第4页 / 共13页
linux工作队列.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

linux工作队列.docx

《linux工作队列.docx》由会员分享,可在线阅读,更多相关《linux工作队列.docx(13页珍藏版)》请在冰豆网上搜索。

linux工作队列.docx

linux工作队列

 linux工作队列

INIT_DELAYED_WORK()是一个宏,我们给它传递了两个参数.&hub->leds和led_work.对设备驱动熟悉的人不会觉得INIT_DELAYED_WORK()很陌生,其实鸦片战争那会儿就有这个宏了,只不过从2.6.20的内核开始这个宏做了改变,原来这个宏是三个参数,后来改成了两个参数,所以经常在网上看见一些同志抱怨说最近某个模块编译失败了,说什么make的时候遇见这么一个错误:

error:

macro"INIT_DELAYED_WORK"passed3arguments,buttakesjust2

当然更为普遍的看到下面这个错误:

error:

macro"INIT_WORK"passed3arguments,buttakesjust2

于是就让我们来仔细看看INIT_WORK和INIT_DELAYED_WORK.其实前者是后者的一个特例,它们涉及到的就是传说中的工作队列.这两个宏都定义于include/linux/workqueue.h中:

    79#defineINIT_WORK(_work,_func)                                        /

    80        do{                                                           /

    81                (_work)->data=(atomic_long_t)WORK_DATA_INIT();      /

    82                INIT_LIST_HEAD(&(_work)->entry);                       /

    83                PREPARE_WORK((_work),(_func));                        /

    84        }while(0)

    85

    86#defineINIT_DELAYED_WORK(_work,_func)                        /

    87        do{                                                   /

    88                INIT_WORK(&(_work)->work,(_func));            /

    89                init_timer(&(_work)->timer);                   /

    90        }while(0)

有时候特怀念谭浩强那本书里的那些例子程序,因为那些程序都特简单,不像现在看到的这些,动不动就是些复杂的函数复杂的数据结构复杂的宏,严重挫伤了我这样的有志青年的自信心.就比如眼下这几个宏吧,宏里边还是宏,一个套一个,不是说看不懂,因为要看懂也不难,一层一层展开,只不过确实没必要非得都看懂,现在这样一种朦胧美也许更美,有那功夫把这些都展开我还不如去认认真真学习三个代表呢.总之,关于工作队列,就这么说吧,Linux内核实现了一个内核线程,直观一点,ps命令看一下您的进程,

localhost:

/usr/src/linux-2.6.22.1/drivers/usb/core#ps-el

FS  UID  PID PPID CPRI NIADDRSZWCHAN TTY         TIMECMD

4S    0    1    0 0 76  0-  195-     ?

       00:

00:

02init

1S    0    2    1 0-40  --    0migrat?

       00:

00:

00migration/0

1S    0    3    1 0 94 19-    0ksofti?

       00:

00:

00ksoftirqd/0

1S    0    4    1 0-40  --    0migrat?

       00:

00:

00migration/1

1S    0    5    1 0 94 19-    0ksofti?

       00:

00:

00ksoftirqd/1

1S    0    6    1 0-40  --    0migrat?

       00:

00:

00migration/2

1S    0    7    1 0 94 19-    0ksofti?

       00:

00:

00ksoftirqd/2

1S    0    8    1 0-40  --    0migrat?

       00:

00:

00migration/3

1S    0    9    1 0 94 19-    0ksofti?

       00:

00:

00ksoftirqd/3

1S    0   10    1 0-40  --    0migrat?

       00:

00:

00migration/4

1S    0   11    1 0 94 19-    0ksofti?

       00:

00:

00ksoftirqd/4

1S    0   12    1 0-40  --    0migrat?

       00:

00:

00migration/5

1S    0   13    1 0 94 19-    0ksofti?

       00:

00:

00ksoftirqd/5

1S    0   14    1 0-40  --    0migrat?

       00:

00:

00migration/6

1S    0   15    1 0 94 19-    0ksofti?

       00:

00:

00ksoftirqd/6

1S    0   16    1 0-40  --    0migrat?

       00:

00:

00migration/7

1S    0   17    1 0 94 19-    0ksofti?

       00:

00:

00ksoftirqd/7

5S    0   18    1 0 70 -5-    0worker?

       00:

00:

00events/0

1S    0   19    1 0 70 -5-    0worker?

       00:

00:

00events/1

5S    0   20    1 0 70 -5-    0worker?

       00:

00:

00events/2

5S    0   21    1 0 70 -5-    0worker?

       00:

00:

00events/3

5S    0   22    1 0 70 -5-    0worker?

       00:

00:

00events/4

1S    0   23    1 0 70 -5-    0worker?

       00:

00:

00events/5

5S    0   24    1 0 70 -5-    0worker?

       00:

00:

00events/6

5S    0   25    1 0 70 -5-    0worker?

       00:

00:

00events/7

瞅见最后这几行了吗,events/0到events/7,0啊7啊这些都是处理器的编号,每个处理器对应其中的一个线程.要是您的计算机只有一个处理器,那么您只能看到一个这样的线程,events/0,您要是双处理器那您就会看到多出一个events/1的线程.哥们儿这里DellPowerEdge2950的机器,8个处理器,所以就是events/0到events/7了.

那么究竟这些events代表什么意思呢?

或者说它们具体干嘛用的?

这些events被叫做工作者线程,或者说workerthreads,更确切的说,这些应该是缺省的工作者线程.而与工作者线程相关的一个概念就是工作队列,或者叫workqueue.工作队列的作用就是把工作推后,交由一个内核线程去执行,更直接的说就是如果您写了一个函数,而您现在不想马上执行它,您想在将来某个时刻去执行它,那您用工作队列准没错.您大概会想到中断也是这样,提供一个中断服务函数,在发生中断的时候去执行,没错,和中断相比,工作队列最大的好处就是可以调度可以睡眠,灵活性更好.

就比如这里,如果我们将来某个时刻希望能够调用led_work()这么一个我们自己写的函数,那么我们所要做的就是利用工作队列.如何利用呢?

第一步就是使用INIT_WORK()或者INIT_DELAYED_WORK()来初始化这么一个工作,或者叫任务,初始化了之后,将来如果咱们希望调用这个led_work()函数,那么咱们只要用一句schedule_work()或者schedule_delayed_work()就可以了,特别的,咱们这里使用的是INIT_DELAYED_WORK(),那么之后我们就会调用schedule_delayed_work(),这俩是一对.它表示,您希望经过一段延时然后再执行某个函数,所以,咱们今后会见到schedule_delayed_work()这个函数的,而它所需要的参数,一个就是咱们这里的&hub->leds,另一个就是具体自己需要的延时.&hub->leds是什么呢?

structusb_hub中的成员,structdelayed_workleds,专门用于延时工作的,再看structdelayed_work,这个结构体定义于include/linux/workqueue.h:

    35structdelayed_work{

    36        structwork_structwork;

    37        structtimer_listtimer;

    38};

其实就是一个structwork_struct和一个timer_list,前者是为了往工作队列里加入自己的工作,后者是为了能够实现延时执行,咱们把话说得更明白一点,您看那些events线程,它们对应一个结构体,structworkqueue_struct,也就是说它们维护着一个队列,完了您要是想利用工作队列这么一个机制呢,您可以自己创建一个队列,也可以直接使用events对应的这个队列,对于大多数情况来说,都是选择了events对应的这个队列,也就是说大家都共用这么一个队列,怎么用呢?

先初始化,比如调用INIT_DELAYED_WORK(),这么一初始化吧,实际上就是为一个structwork_struct结构体绑定一个函数,就比如咱们这里的两个参数,&hub->leds和led_work()的关系,就最终让hub_leds这个structwork_struct结构体和函数led_work()相绑定了起来,您问怎么绑定的?

您瞧,structwork_struct也是定义于include/linux/workqueue.h:

    24structwork_struct{

    25        atomic_long_tdata;

    26#defineWORK_STRUCT_PENDING0          

    27#defineWORK_STRUCT_FLAG_MASK(3UL)

    28#defineWORK_STRUCT_WQ_DATA_MASK(~WORK_STRUCT_FLAG_MASK)

    29        structlist_headentry;

    30        work_func_tfunc;

    31};

瞅见最后这个成员func了吗,初始化的目的就是让func指向led_work(),这就是绑定,所以以后咱们调用schedule_delayed_work()的时候,咱们只要传递structwork_struct的结构体参数即可,不用再每次都把led_work()这个函数名也给传递一次,一旦绑定,人家就知道了,对于led_work(),那她就嫁鸡随鸡,嫁狗随狗,嫁混蛋随混蛋了.您大概还有一个疑问,为什么只要这里初始化好了,到时候调用schedule_delayed_work()就可以了呢?

事实上,events这么一个线程吧,它其实和hub的内核线程一样,有事情就处理,没事情就睡眠,也是一个死循环,而schedule_delayed_work()的作用就是唤醒这个线程,确切的说,是先把自己的这个structwork_struct插入workqueue_struct这个队列里,然后唤醒昏睡中的events.然后events就会去处理,您要是有延时,那么它就给您安排延时以后执行,您要是没有延时,或者您设了延时为0,那好,那就赶紧给您执行.咱这里不是讲了两个宏吗,一个INIT_WORK(),一个INIT_DELAYED_WORK(),后者就是专门用于可以有延时的,而前者就是没有延时的,这里咱们调用的是INIT_DELAYED_WORK(),不过您别美,过一会您会看见INIT_WORK()也被使用了,因为咱们hub驱动中还有另一个地方也想利用工作队列这么一个机制,而它不需要延时,所以就使用INIT_WORK()进行初始化,然后在需要调用相关函数的时候调用schedule_work()即可.此乃后话,暂且不表.

基本上这一节咱们就是介绍了Linux内核中工作队列机制提供的接口,两对函数INIT_DELAYED_WORK()对schedule_delayed_work(),INIT_WORK()对schedule_work().

关于工作队列机制,咱们还会用到另外两个函数,它们是cancel_delayed_work(structdelayed_work*work)和flush_scheduled_work().其中cancel_delayed_work()的意思不言自明,对一个延迟执行的工作来说,这个函数的作用是在这个工作还未执行的时候就把它给取消掉.而flush_scheduled_work()的作用,是为了防止有竞争条件的出现,虽说哥们儿也不是很清楚如何防止竞争,可是好歹大二那年学过一门专业课,数字电子线路,尽管没学到什么有用的东西,怎么说也还是记住了两个专业名词,竞争与冒险.您要是对竞争条件不是很明白,那也不要紧,反正基本上每次cancel_delayed_work之后您都得调用flush_scheduled_work()这个函数,特别是对于内核模块,如果一个模块使用了工作队列机制,并且利用了events这个缺省队列,那么在卸载这个模块之前,您必须得调用这个函数,这叫做刷新一个工作队列,也就是说,函数会一直等待,直到队列中所有对象都被执行以后才返回.当然,在等待的过程中,这个函数可以进入睡眠.反正刷新完了之后,这个函数会被唤醒,然后它就返回了.关于这里这个竞争,可以这样理解,events对应的这个队列,人家本来是按部就班的执行,一个一个来,您要是突然把您的模块给卸载了,或者说你把你的那个工作从工作队列里取出来了,那events作为队列管理者,它可能根本就不知道,比如说它先想好了,下午3点执行队列里的第N个成员,可是您突然把第N-1个成员给取走了,那您说这是不是得出错?

所以,为了防止您这种唯恐天下不乱的人做出冒天下之大不韪的事情来,提供了一个函数,flush_scheduled_work(),给您调用,以消除所谓的竞争条件,其实说竞争太专业了点,说白了就是防止混乱吧.

Ok,关于这些接口就讲到这里,日后咱们自然会在hub驱动里见到这些接口函数是如何被使用的.到那时候再来看.这就是蝴蝶效应.当我们看到INIT_WORK/INIT_DELAYED_WORK()的时候,我们是没法预测未来会发生什么的.所以我们只能拭目以待.又想起了那句老话,大学生活就像被强奸,如果不能反抗,那就只能静静的去享受它.

 

 

工作队列(workqueue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。

工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。

这样,通过工作队列执行的代码能占尽进程上下文的所有优势。

最重要的就是工作队列允许被重新调度甚至是睡眠。

那么,什么情况下使用工作队列,什么情况下使用tasklet。

如果推后执行的任务需要睡眠,那么就选择工作队列。

如果推后执行的任务不需要睡眠,那么就选择tasklet。

另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。

它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。

这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。

如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

 

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

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

系统默认的工作者线程为events,自己也可以创建自己的工作者线程。

1.表示工作的数据结构

工作用中定义的work_struct结构表示:

structwork_struct{

unsignedlongpending;

structlist_headentry;

void(*func)(void*);

void*data;

void*wq_data;

structtimer_listtimer;

};

这些结构被连接成链表。

当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。

工作被执行完毕,它就将相应的work_struct对象从链表上移去。

当链表上不再有对象的时候,它就会继续休眠。

3. 创建推后的工作

要使用工作队列,首先要做的是创建一些需要推后完成的工作。

可以通过DECLARE_WORK在编译时静态地建该结构:

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

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

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

INIT_WORK(structwork_struct*work,woid(*func)(void*),void*data);

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

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

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

voidwork_handler(void*data)

这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中。

默认情况下,允许响应中断,并且不持有任何锁。

如果需要,函数可以睡眠。

需要注意的是,尽管该函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。

通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。

5. 对工作进行调度

现在工作已经被创建,我们可以调度它了。

想要把给定工作的待处理函数提交给缺省的events工作线程,只需调用

schedule_work(&work);

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

有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。

在这种情况下,可以调度它在指定的时间执行:

schedule_delayed_work(&work,delay);

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

6. 工作队列的简单应用

#include 

#include 

#include 

static struct workqueue_struct *queue = NULL;

static struct work_structwork;

static void work_handler(struct work_struct *data)

{

        printk(KERN_ALERT "workhandlerfunction./n");

}

static int __inittest_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 __exittest_exit(void)

{

        destroy_workqueue(queue);

}

MODULE_LICENSE("GPL");

module_init(test_init);

module_exit(test_exit);

上文来源:

 

 

linux工作队列(补充)

走入Linux的殿堂已经有一年有余了,在这里我想将Linux的各种实现机制分析一遍,一方面对自己来说也是温故而知新,另一方面,促进大家的交流,最好能够给大家一些抛砖引玉的启迪。

我是硬件出身,搞硬件已经好多年了,从是专门软件开发也接近两年了,在这一段时间内我越发认为软硬件协同设计是未来发展的主流,软硬件的界限越来越模糊,软硬件的设计思想是相通的,实现方法是各异的,实现的结果上当然也存在较大差别,因此,很有必要做好软硬件的协同设计。

本着这样的想法,我想将我所认识的Linux分析一遍,特别是一些

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1