工作队列 ( workqueue )

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

你可能感兴趣的:(linux,内核)