Work queue 详解 (ZZ)
先简单快速总结一下,更详细的剖析后续用帖子编辑方式逐步完成。
分成两大部分,第一部分是用来执行work queue中每个节点上挂载的函数的内核线程,第二部分是从驱动程序的角度看work queue的使用。
第一部分 worker_thread内核线程
Linux系统启动期间会创建一名为worker_thread线程,该线程创建之后就处于sleep状态。这里所谓的内核线程,从调度器的角度就是一可以调度的进程,从代码的表现形式看,就是一函数。系统创建的这个worker_thread线程基于一workqueue_struct结构变量上(该结构体变量的成员name为"events").
第二部分 work queue的使用
1.只考虑使用系统的keventd管理的工作队列
驱动程序调用schedule_work向工作队列递交新的工作节点,schedule_work内部会唤醒worker_thread内核线程(使之进程状态为可调度)。在下一次进程调度时刻,worker_thread被调度执行,其主要任务便是调用它所管理工作队列中每个工作节点上挂载的函数,调用完毕该工作节点会从任务队列中被删除。当所有节点上的函数调用完毕,worker_thread继续sleep,直到schedule_work再次被某个驱动程序调用。
与使用驱动程序自己创建的工作对列的区别是:schedule_work内部是调用queue_work(keventd_wq, work),而使用驱动程序自己创建的工作队列在调用queue_work时的第一个参数是驱动程序自己创建的工作队列。
2.驱动程序使用自己创建的工作队列
这种情况驱动程序调用create_workqueue。该函数的原理跟1中基本是一样的,只不过再会创建一个内核进程,该内核进程的内核函数名字依然为worker_thread,只不过这个worker_thread工作在新的属于驱动程序自己的工作队列。
使用方法是:
a. 调用create_workqueue生成属于驱动程序自己的work queue. struct workqueue_struct *my_workqueue = create_workqueue("my_workqueue");
b.调用queue_work象a中生成的my_workqueue中注册工作节点, queue_work(my_workqueue, work)
这两种情况下的内核线程其实都是利用了一个名为kthreadd的内核线程来创建工作队列的worker_thread,其本质是向全局列表kthread_create_list中加入相应的内核线程函数节点,由kthreadd来负责创建进程框架,然后运行kthread_create_list中加入的节点上的内核线程函数。
向kthread_create_list中每加入一个内核线程函数节点,都会唤醒kthreadd线程。
当等待队列是空时,worker_thread会睡眠在该等待队列中。当driver调用queue_work时,再向等待队列中加入工作节点的同时,会唤醒睡眠在该等待队列上的worker_thread线程。
create_singlethread_workqueue(name)与create_workqueue(name)
相对于create_singlethread_workqueue, create_workqueue同样会分配一个wq的工作队列,但是不同之处在于,对于多CPU系统而言,对每一个CPU,都会为之创建一个per-CPU的cwq结构,对应每一个cwq,都会生成一个新的worker_thread进程。但是当用queue_work向cwq上提交work节点时,是哪个CPU调用该函数,那么便向该CPU对应的cwq上的worklist上增加work节点。 工作队列API函数调用注意点: 工作队列调用函数schedule_delayed_work(),只要调用它就可以使和工作队列绑定的函数执行。但是在执行函数中记得也要调用,目的是做到循环的使工作函数 不断的被调用。另外还有几个地方需要调用,即唤醒函数中,第一次允许设备工作的函数中,如bma250_set_enable()。 |