您的位置:首页 > 产品设计 > UI/UE

工作队列 ( workqueue )

2015-07-25 17:21 543 查看
1. 有些时候内核需要一个异步的进程执行上下文,而工作

队列(workqueue)可以满足这种需求。

工作队列中的每一个元素都是一个工作项(work item),

有一个函数与工作项相关,这个函数就是工作项所要处

理的任务。

内核中有一个专门的线程——被称作worker,来依次执行

工作队列中的每一个工作项对应的函数,当工作队列为

空时,这个worker就变为空闲状态(idle),当有新的

工作项加入到工作队列时,worker又重新开始执行。

2. 在最早的实现中包括两种实现方式,一种是整个系统只

有一个worker(single thread,ST),另一种是每个

CPU包含一个worker(multiple thread,MT),每一个

CPU包含一个属于自己的worker pool 。

这两种实现都引起了系统中对于这种异步上下文的竞争,

只不过是MT方式的竞争可能更小一些。

因此,内核人员对workqueue作了重新实现,新的实现

被称作concurrency managed workqueue(cmwq),新的

实现的特点如下:

* 与之前的API兼容

* 实现了统一的每CPU worker pool,减少了资源的浪

费,提高了并发的灵活性

* 可以自动调节worker pool和并发的级别

3. 为了简化执行这种异步上下文 ,引入了work item,它

是一个简单的结构体,包含一个函数指针,这个指针指

向的函数就是需要在异步上文中执行的函数。

当一个内核子系统或者驱动程序需要在异步上下文中执

行一个函数时,它首先需要创建一个work item结构体,

然后将这个结构体加入到工作队列中。

工作者线程(worker thread)负责从工作对列中取出work

item并执行,直到工作队列中的work item为空。工作

者线程是由worker-pool管理的。

cmwq的实现在用户接口(即子系统或者驱动程序的使用)

和后台支持上(即如何管理worker pool以及处理work

item)有差别。

每一个CPU上都有两个worker pool,一个是用来处理普

通的work item,另一个是用来处理高优先级的work item。

另外还有一些worker pool用来处理未添加到绑定到CPU

的wq上的work item,这些worker pool的数量是动态的。

可以通过修改工作队列的属性来改变添加到其上的work

item的执行行为,例如在哪个CPU上执行、并发限制、优

先级等。

当一个work item添加到workqueue时,根据函数的参数

以及要添加到的工作队列的属性就可以确定将由哪一个

worker pool来执行,并且会将该work item添加到该

worker pool共享的工作列表中。例如,当一个work

item被添加到一个workqueue时,它要么被添加到普通

的worker-pool的工作列表,要么被添加到高优先级的

worker-pool的工作列表,这里的两个worker-pool对应

于添加work item到工作队列的CPU。

管理一个工作者线程池的并发度一直是工作者线程池面

临的一个重要的问题。cmwq保持了最小的并发度,但是

又让CPU不会空闲,充分利用了CPU资源。

每一个绑定到CPU的线程池通过在调度器中添加钩子实

现了并发的控制。当一个工作项被唤醒或者睡眠的时候,

工作者线程池都会接到通知,以此来追踪当前并发的工

作者线程数。一般来说,当一个CPU上有一个或者多个

工作者线程在执行的时候,与该CPU绑定的工作者线程

池不会再起动其他的工作者线程,当该CPU上最后一个

工作者线程睡眠后,立刻启动一个新的工作,来保证CPU

不会空转。

对于未绑定到CPU的工作队列来说,它的线程池数量是

动态的。可以通过apply_workqueue_attrs函数来设置这

个未绑定的工作队列的参数,系统会自动生成与这些参

数对应的工作者线程池。另外对于绑定的工作队列可以

设置某些参数,让某个工作队列忽略并发的限制。

任何需要多个工作者线程同时执行的子系统或者驱动程

序都需要使用有急救工作者线程(rescuer worker)的

工作队列。例如,在内存回收的时候,回收内存的工作

项往往需要同时执行,因此如果没有使用这种工作队列

的话,就会造成死锁,因为后执行的工作者线程会等待

前面的工作者线程被释放。

4. 应用程序接口(API)



alloc_workqueue负责创建一个工作队列,之前的create_*workqueue

之类的接口已经被丢弃了。该函数有三个参数@name,

@flags以及@max_active,@name表示该工作队列的名字,

如果急救工作者线程也存在的话,那么@name也是该急救

工作者线程的名字。

工作队列已经不再管理执行资源,但是它作为一个域用

来管理工作项,例如保证工作项往前执行、flush以及

工作项的属性。@flags和@max_active参数控制工作项

的执行资源分配,如何被调度以及如何执行。

@flags:

WQ_UNBOUND

未绑定的工作队列对应的工作者线程池数是动态的。该

工作者线程池未绑定到任何的CPU,因此相当于是一个简

单的执行上下文的提供者,它会立即执行添加到该工作

队列工作项。一般在下面两种情况会用到:



* 工作者线程的并发级别波动很大,且将工作项添加到

工作队列的发起者会在不同的CPU之间来回执行,因

此如果不使用未绑定工作队列的话,就会造成在各个

CPU的工作者线程池上创建了许多不会使用的工作者

线程。



* 当CPU的负担很重时,最好将工作项添加到位绑定的工

作队列,由调度器来进行调度。

WQ_FREEZABLE

当系统暂停时,工作队列会进入到冻结状态。并且会清

空该工作队列中的工作项,直到被解冻才可以执行新的

工作。

WQ_MEM_RECLAIM

任何可能在内存回收路径上使用的工作队列都必须设置

该标记,它保证了不管内存压力有多大,都至少有一个

执行上下文与之对应。

WQ_HIGHPRI

高优先级工作队列的工作项被加入到相应CPU的高优先

级工作者线程池。高优先级工作者线程池由提高了nice

级别的线程来服务。



普通的工作者线程池和高优先级的工作者线程池是隔离

的,他们之间不进行任何的交互。对于并发级别的控制

也是各自独立进行控制的。

WQ_CPU_INTENSIVE

这种工作队列中的工作项并不受并发级别的控制,因为

该类型的工作项通常会需要很多的CPU使用量,因此最

好的办法就是由系统调度器来进行调度。

另外,并发级别的限制会影响密集型工作项的执行,例

如当前正在运行的非密集型的工作会延迟密集型工作的

执行。

该标记对未绑定的工作队列是无效的。

@max_active:

表示每个工作队列同时最多能有几个工作项在同一个CPU

上运行。例如,max_active=16,表示同时最多能有16个

该工作队列的工作项在每一个CPU上运行。

对于绑定的工作队列,@max_active的最大值为512,默

认情况下,该参数传值为0,此时它的最大值为256。对

于未绑定的工作队列,max_active的最大值要大于512。

工作队列的同时处于活跃状态的工作项的数目是由用户

调节的,例如用户同时添加到工作队列中的工作项的数

目。除非有需求调节活跃工作项的数目,否则,推荐该

参数赋值为0。



有些需要依赖ST工作队列的顺序,因此可以使WQ_UNBOUND

和@max_active为1。这样就模拟了ST,每一个该类型的

工作项都应该被添加到未绑定的工作队列中,且一次只

能有一个工作项执行就限定了执行的顺序。

5. 指导方针

* 如果工作队列中的工作项可能用在内存回收代码路径

中,一定要设置WQ_MEM_RECLAIM,每一个该类型的队

列都有一个保留的执行上下文。另外,如果在内存回

收路径中有多个工作项相互依赖的话,应该将这些工

作项添加到不同的工作对列中。

* 如果没有特殊要求的话,推荐@max_active参数的值

为0。

* 一般如果没有执行次序要求的话,不会用到ST。

* 工作队列作为工作项一个域,用来WQ_MEM_RECLAIM,

flush以及某些工作项的共有属性。如果工作项不会

用到上面的任何一个特性,可以使用系统工作队列。

* 除非一个工作项要耗费很多的CPU,一般将工作项添

加到绑定的工作队列中。



ref

===

1. Documentation/workqueue.txt
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: