linux驅(qū)動(dòng)之workqueue

一、前言

在內(nèi)核驅(qū)動(dòng)中甸私,常常見(jiàn)到 工作隊(duì)列(workqueue)。對(duì)于熟悉內(nèi)核或者驅(qū)動(dòng)的工程師來(lái)說(shuō)飞傀,這個(gè)機(jī)制應(yīng)該是比較熟悉的皇型,經(jīng)常出現(xiàn)在 中斷上下文 中,用于執(zhí)行中斷后的操作砸烦。隨著內(nèi)核發(fā)展弃鸦,驅(qū)動(dòng)遇到越多越多的場(chǎng)景,而 工作隊(duì)列 也逐漸發(fā)展幢痘,現(xiàn)在我們常用的 工作隊(duì)列 稱為 并發(fā)管理工作隊(duì)列(concurrency managed workqueue)唬格,本文對(duì) 工作隊(duì)列 進(jìn)行簡(jiǎn)單的介紹和用法說(shuō)明,希望能夠幫助各位讀者熟悉其使用

二颜说、workqueue介紹

2.1 workqueue說(shuō)明

工作隊(duì)列 常常在內(nèi)核驅(qū)動(dòng)中會(huì)被使用到购岗,而內(nèi)核中也有對(duì)于其描述的文檔,其路徑為 Documentation/core-api/workqueue.rst

下面引用原文來(lái)看看 workqueue 的描述

There are many cases where an asynchronous process execution context is needed and the workqueue (wq) API is the most commonly used mechanism for such cases.
When such an asynchronous execution context is needed, a work item describing which function to execute is put on a queue.
An independent thread serves as the asynchronous execution context.
The queue is called workqueue and the thread is called worker.
While there are work items on the workqueue the worker executes the functions associated with the work items one after the other.
When there is no work item left on the workqueue the worker becomes idle.
When a new work item gets queued, the worker begins executing again.

簡(jiǎn)單來(lái)說(shuō)门粪,工作隊(duì)列 提供了一種 異步執(zhí)行任務(wù) 的機(jī)制藕畔。一般有以下幾個(gè)概念:

  • work:指需要異步執(zhí)行的任務(wù)
  • woker:指處理 work異步執(zhí)行上下文,通產(chǎn)可以理解為一個(gè)線程
  • workqueue:多個(gè) work 以節(jié)點(diǎn)的形式鏈接起來(lái)形成 workqueue庄拇,其可以通過(guò)調(diào)度讓 woker 來(lái)執(zhí)行 work

2.2 為什么使用workqueue

中斷的 bottom half機(jī)制 比如 tasklet 都是在 中斷上下文(softirq) 中執(zhí)行注服,而在 中斷上下文 中通常是不能執(zhí)行 休眠 操作中韭邓,假如有某些特殊的 bottom half 需要休眠,此時(shí)則不能使用 task溶弟。

workqueue 是運(yùn)行在進(jìn)程上下文中的女淑,因而可以執(zhí)行休眠操作,這是和其他 bottom half機(jī)制 有本質(zhì)的區(qū)別辜御,大大方便了在驅(qū)動(dòng)中處理中斷鸭你。

2.3 舊版本W(wǎng)orkqueue和Concurrency Managed Workqueue

在驅(qū)動(dòng)中我們常常使用 workqueue 來(lái)提供 異步執(zhí)行環(huán)境,此時(shí)我們定義 work 并鏈入 workqueue擒权,由 woker 不斷處理 work袱巨。處理完畢后 worker 進(jìn)入休眠。

其操作流程看起來(lái)簡(jiǎn)單碳抄,但這里面有不少細(xì)節(jié)需要跟讀者們說(shuō)明一下愉老,以讓各位好了解 舊版本workqueue 的不足之處。

我們可以先看看內(nèi)核原文:

In the original wq implementation, a multi threaded (MT) wq had one
worker thread per CPU and a single threaded (ST) wq had one worker
thread system-wide. A single MT wq needed to keep around the same
number of workers as the number of CPUs. The kernel grew a lot of MT
wq users over the years and with the number of CPU cores continuously
rising, some systems saturated the default 32k PID space just booting
up.

Although MT wq wasted a lot of resource, the level of concurrency
provided was unsatisfactory. The limitation was common to both ST and
MT wq albeit less severe on MT. Each wq maintained its own separate
worker pool. An MT wq could provide only one execution context per CPU
while an ST wq one for the whole system. Work items had to compete for
those very limited execution contexts leading to various problems
including proneness to deadlocks around the single execution context.

  • 內(nèi)核線程數(shù)量太多剖效。在錯(cuò)誤使用 workqueue機(jī)制 的情況下嫉入,容易消耗盡 PID space,而擴(kuò)大 PID space 則會(huì)導(dǎo)致系統(tǒng) task 過(guò)多而造成性能的負(fù)面影響璧尸。

  • 并發(fā)性不足咒林。如果是 single threaded workqueue,則沒(méi)有并發(fā)的概念爷光,任何的 work 都是按順序排隊(duì)執(zhí)行垫竞,沒(méi)有執(zhí)行到的 work 只能原地等待。如果是多核的 per-CPU multi threaded workqueue蛀序,雖然創(chuàng)建了 thread pool(即多個(gè)worker)欢瞪,但是其數(shù)目是固定的。且每個(gè) oneline cpu 上運(yùn)行一個(gè) 嚴(yán)格綁定的woker哼拔,從而到了每個(gè)線程都只能嚴(yán)格運(yùn)行在綁定的 CPU 上引有,這就造成了無(wú)法并發(fā)處理的情況瓣颅。例如 cpu0 上的 worker 進(jìn)入阻塞狀態(tài)倦逐,那么由該 worker 處理的 workqueue 中的 其他work 都會(huì)被阻塞,不能轉(zhuǎn)移到 其他CPU 去執(zhí)行宫补。

  • 死鎖問(wèn)題檬姥。舉個(gè)例子:比如驅(qū)動(dòng)中存在 work Awork B,且 work A 依賴 work B 的執(zhí)行結(jié)果粉怕。如果這兩個(gè) work 都被同一個(gè) CPU 的同一個(gè) worker 調(diào)度并執(zhí)行的時(shí)候會(huì)出現(xiàn)問(wèn)題健民。由于 worker 不能并發(fā)的執(zhí)行 work Awork B,因此該驅(qū)動(dòng)模塊會(huì)發(fā)生 死鎖贫贝。

  • 二元化的線程池機(jī)制秉犹。workqueue 創(chuàng)建的線程數(shù)目要么是 1蛉谜,要么是 number of CPU。無(wú)法自主設(shè)置崇堵,不夠靈活型诚。

Concurrency Managed Workqueue (cmwq) 為了解決這些問(wèn)題實(shí)現(xiàn)了以下幾個(gè)功能:

  • 兼容舊版本的 workqueue API

  • 讓所有的 workqueue 共享 worker pool,以按需提供并發(fā)處理并節(jié)省資源

  • 自動(dòng)調(diào)節(jié) 并發(fā)性能鸳劳,用戶無(wú)需關(guān)心細(xì)節(jié)

三狰贯、并發(fā)管理工作隊(duì)列(cmwq)

3.1、與傳統(tǒng)workqueue的區(qū)別

對(duì)于 workqueue 的使用方法而言赏廓,前端的操作包括 2 種:

  • 創(chuàng)建 workqueue
  • 將指定的 work 添加到 workqueue涵紊。

傳統(tǒng)workqueue 中,工作線程(worker thread)workqueue 是密切聯(lián)系的幔摸。
對(duì)于 single thread workqueue摸柄,創(chuàng)建單個(gè) 系統(tǒng)范圍內(nèi)的worker thread。而對(duì)于 multi thread workqueue抚太,則創(chuàng)建 per-CPU worker thread塘幅,也就是每個(gè) worker thread 對(duì)應(yīng)一個(gè) CPU,一切都是固定死的尿贫。

一般情況下电媳,workqueue 是提供一個(gè) 異步執(zhí)行的環(huán)境,如果把 創(chuàng)建workqueue創(chuàng)建worker thread2 個(gè)操作固定在一起會(huì)大大限定了資源的使用庆亡。用戶并不關(guān)心后臺(tái)如何處理 work 匾乓、釋放后使用多線程等具體細(xì)節(jié)。
cmwq 中又谋,打破了 workqueueworker thread的綁定關(guān)系拼缝。其使用了 worker pool 的概念(可以理解為線程池)。在系統(tǒng)中已經(jīng)存在若干個(gè)不同類型的 worker pool彰亥,且它們不和 特定的workqueue 綁定咧七,所有的 workqueue 都可以根據(jù)自身的需要選擇使用其中的 worker pool

用戶可以在創(chuàng)建 workqueue時(shí)通過(guò)指定 flag 來(lái)約束在該 workqueue 上的 work 的處理方式任斋。workqueue 會(huì)根據(jù)指定的 flagwork 交付給系統(tǒng)中對(duì)應(yīng)的 worker pool 去處理勾邦。
通過(guò)這樣的方式讓所有的 workqueue 共享系統(tǒng)中的 worker pool旬牲,即減少了由于創(chuàng)建 worker thread 帶來(lái)的資源的浪費(fèi)颈渊,又因?yàn)?worker pool 可以根據(jù)自身的情況選擇是否 減少或增加woker thread 從而提高了靈活性和并發(fā)性慎式。

3.2、cmwq如何解決問(wèn)題

我們前面提到了澈蟆,傳統(tǒng)的 workqueue 有幾個(gè)問(wèn)題墨辛,下面我們看看如何解決

3.2.1、如何解決線程數(shù)目過(guò)多的問(wèn)題趴俘?

cmwq 中睹簇,用戶可以根據(jù)需求來(lái)創(chuàng)建 workqueue奏赘,但一般情況下不需要?jiǎng)?chuàng)建 worker thread,而且是否需要 新woker 也由 woker thread pool 本身決定太惠。也就是說(shuō) 創(chuàng)建workqueue 已經(jīng)和 后端的線程池操作沒(méi)有關(guān)系 了志珍。

系統(tǒng)中的 woker pool 包括 2大種

  • 和特定CPU綁定的線程池:也稱為 per CPU worker pool,這類 woker pool 也細(xì)分為 2 種:

    • normal thread pool:用于 管理普通優(yōu)先級(jí)的worker thread處理普通優(yōu)先級(jí)的work
    • high priority thread pool:用于 管理高優(yōu)先級(jí)的worker thread處理高優(yōu)先級(jí)的work

    此類 worker pool 的數(shù)量是 固定的垛叨,主要和 CPU數(shù)量 的數(shù)目伦糯。假設(shè)了 N個(gè)online CPU,則會(huì)創(chuàng)建 2N個(gè) per CPU worker pool嗽元。也就是每個(gè) CPU 都有 2 個(gè)綁定的 worker pool敛纲,分別為 normal thread poolhigh priority thread pool

  • unbound線程池:也就是 非綁定worker pool剂癌,此類 worker poolworker thread 可以運(yùn)行在 任意的CPU 上淤翔。這種 wokrer pool 可以動(dòng)態(tài)創(chuàng)建。如果系統(tǒng)中已經(jīng) 有了相同屬性的worker pool則不需要?jiǎng)?chuàng)建新的線程池佩谷,否則需要?jiǎng)?chuàng)建旁壮。worker pool屬性 包括該worker pool 中的 worker thread的優(yōu)先級(jí)可以運(yùn)行的CPU鏈表 等谐檀。

基于上面的講述抡谐,worker thread pool 創(chuàng)建 worker thread 的策略是怎樣呢?是否會(huì)導(dǎo)致 worker thread 數(shù)目過(guò)多呢桐猬?

在默認(rèn)情況下麦撵,創(chuàng)建 worker thread pool 的時(shí)候會(huì)創(chuàng)建 一個(gè)worker thread 來(lái)處理 workthread worker pool 會(huì) 根據(jù)work的提交以及work的執(zhí)行情況動(dòng)態(tài)創(chuàng)建 worker thread溃肪。
cmwq 使用了一個(gè)比較簡(jiǎn)單的策略:

  1. 當(dāng) worker thread pool處于運(yùn)行狀態(tài)的worker thread 的數(shù)量等于 0免胃,并且有 需要處理的work 的時(shí)候,thread worker pool 就會(huì)創(chuàng)建 新的worker線程惫撰。
  2. 當(dāng) worker線程處于idle的時(shí)候羔沙,不會(huì)立刻銷毀它,而是保持一段時(shí)間厨钻。如果這時(shí)候有創(chuàng)建 新的worker 的需求候扼雏,那么直接喚醒 idle worker 即可。
  3. 如果 一段時(shí)間內(nèi)過(guò)去仍然沒(méi)有事情處理莉撇,那么銷毀 worker thread 呢蛤。

3.2.2惶傻、如何解決并發(fā)問(wèn)題棍郎?

假設(shè)有 A、B银室、C涂佃、D 四個(gè)work 在某個(gè) CPU 上運(yùn)行励翼。在默認(rèn)情況下,worker thread pool 會(huì)創(chuàng)建 一個(gè)worker 來(lái)處理這四個(gè) work 辜荠。在 傳統(tǒng)workqueue 中汽抚,這四個(gè) work 會(huì)在 CPU串行執(zhí)行。也就是假如 work B阻塞了伯病,那么 work C造烁、work D 無(wú)法執(zhí)行下去,需要一直等到 work B 解除阻塞并執(zhí)行完畢午笛。

對(duì)于 cmwq 來(lái)說(shuō)惭蟋,當(dāng) work B 阻塞了,worker thread pool**可以通過(guò)判斷 worker thread 的執(zhí)行狀態(tài)來(lái)感知到這一事件药磺,此時(shí)它會(huì) 創(chuàng)建一個(gè)新的worker thread來(lái)處理work C告组、work D,從而解決了并發(fā)的問(wèn)題癌佩。由于解決了并發(fā)問(wèn)題木缝,實(shí)際上也解決了由于競(jìng)爭(zhēng)一個(gè) 進(jìn)程上下文 而引入的 死鎖問(wèn)題

3.3围辙、cmwq相關(guān)數(shù)據(jù)結(jié)構(gòu)

3.3.1 cmwq數(shù)據(jù)結(jié)構(gòu)及其關(guān)系

前面簡(jiǎn)單地提到過(guò) workqueue機(jī)制 的主要數(shù)據(jù)結(jié)構(gòu)我碟,下面我們?cè)僦匦抡w的看看:

  • work:工作
  • workqueue:工作的集合。workqueuework一對(duì)多 的關(guān)系姚建。
  • worker:工人怎囚。在程序中一個(gè) worker 對(duì)應(yīng)一個(gè) work_thread內(nèi)核線程
  • worker_pool:工人的集合桥胞。worker_poolworker一對(duì)多 的關(guān)系恳守。
  • pool_workqueue:可以理解為 workqueueworker pool 之間的橋梁,負(fù)責(zé)建立起兩者關(guān)系贩虾。workqueuepool_workqueue一對(duì)多 的關(guān)系催烘,pool_workqueueworker_pool一對(duì)一 的關(guān)系。以下簡(jiǎn)稱 pwq缎罢。

下面我們看看各個(gè)數(shù)據(jù)結(jié)構(gòu)在代碼中的主要成員吧

struct workqueue_struct {
    /* pwqs是指該workqueue所對(duì)接的pwq形成的鏈表 */
    struct list_head    pwqs;       
    /* list是全局workqueue鏈表節(jié)點(diǎn)伊群,每個(gè)workqueue使用這個(gè)成員鏈入全局workqueue鏈表 */
    struct list_head    list;
    /* 救援線程:可以在內(nèi)存壓力下執(zhí)行,這樣避免在內(nèi)存回收時(shí)出現(xiàn)死鎖 */
    struct worker       *rescuer;
    /* flags用于指定該workqueue上的work的處理方式 */
    unsigned int        flags ____cacheline_aligned;
    /* cpu_pwqs指向該workqueue的每CPUpwq策精,可以通過(guò)該成員找到每CPU的 worker pool */
    struct pool_workqueue __percpu *cpu_pwqs;
    /* numa_pwq_tbl指向該workqueue的非綁定pwq舰始,可以通過(guò)該成員找到flag所對(duì)應(yīng)的work pool */
    struct pool_workqueue __rcu *numa_pwq_tbl[];
    ......
};

struct pool_workqueue {
    /* 指向該pwq所連接的worker pool */
    struct worker_pool  *pool;
    /* 指向該pwq所屬的workqueue */
    struct workqueue_struct *wq;
    ......
};

struct worker_pool {
    /* 指定非綁定worker pool所在的CPU */
    int         cpu;
    /* workpool的ID */
    int         id;
    /* worker pool標(biāo)志,用于指定worker pool的處理方式 */
    unsigned int        flags;
    /* 待處理的work */
    struct list_head    worklist;   /* L: list of pending works */
    /* 該worker pool所有worker的數(shù)量 */
    int         nr_workers; /* L: total number of workers */
    /* 當(dāng)前idle狀態(tài)的worker數(shù)量 */
    int         nr_idle;    /* L: currently idle ones */
    /* 用于鏈入空閑worker的鏈表 */
    struct list_head    idle_list;  /* X: list of idle workers */
    /* 用于鏈入忙時(shí)worker的哈希表 */
    DECLARE_HASHTABLE(busy_hash, BUSY_WORKER_HASH_ORDER);
    /* 用于鏈入所有worker的鏈表 */
    struct list_head    workers;    /* A: attached workers */
    /* 
      用于管理worker的創(chuàng)建和銷毀的統(tǒng)計(jì)計(jì)數(shù)咽袜,表示運(yùn)行中的worker數(shù)量丸卷。
      該變量可能被多CPU同時(shí)訪問(wèn),因此獨(dú)占一個(gè)緩存行询刹,避免多核讀寫(xiě)造成 cache顛簸
    */
    atomic_t        nr_running ____cacheline_aligned_in_smp;
} ____cacheline_aligned_in_smp;

struct worker {
    /* 鏈表入口谜嫉,空閑時(shí)使用entry鏈入空閑鏈表萎坷,忙時(shí)使用hentry忙時(shí)哈希表 */
    union {
        struct list_head    entry;  /* L: while idle */
        struct hlist_node   hentry; /* L: while busy */
    };
    /* 當(dāng)前正在執(zhí)行的work */
    struct work_struct  *current_work;
    /* 當(dāng)前正在執(zhí)行的work的回調(diào)函數(shù) */
    work_func_t     current_func;
    /* 該worker所屬的pwq */
    struct pool_workqueue   *current_pwq; /* L: current_work's pwq */
    /* 該worker所使用的task結(jié)構(gòu) */
    struct task_struct  *task;
    /* 該worker所在的woker pool */
    struct worker_pool  *pool;
    /* 使用該成員鏈入所在的woker pool */
    struct list_head    node;
};

struct work_struct {
    /* 
      低比特位部分是work的標(biāo)志位。
      剩余比特位通常用于存放上一次運(yùn)行的worker_pool ID或pool_workqueue的指針沐兰。
      存放的內(nèi)容有WORK_STRUCT_PWQ標(biāo)志位來(lái)決定 
    */
    atomic_long_t data;
    /* 用于把work掛到其他隊(duì)列上 */
    struct list_head entry;
    /* 工作任務(wù)的處理函數(shù) */
    work_func_t func;
};

下圖是幾個(gè)數(shù)據(jù)結(jié)構(gòu)之間的簡(jiǎn)單關(guān)系示意圖:


workqueue數(shù)據(jù)結(jié)構(gòu)

3.3.2 cmwq數(shù)據(jù)結(jié)構(gòu)初始化

我們前面說(shuō)過(guò)哆档,worker pool 會(huì)在系統(tǒng)初始化 workqueue 機(jī)制時(shí)被創(chuàng)建。下面我們簡(jiǎn)單地看看代碼是如何初始化這些數(shù)據(jù)結(jié)構(gòu)的住闯,先看一下函數(shù)調(diào)用圖譜瓜浸。

workqueue_init_early
  ->init_worker_pool
  ->alloc_workqueue_attrs
  ->alloc_workqueue
    ->__alloc_workqueue_key
      ->alloc_and_link_pwqs
        ->apply_workqueue_attrs
          ->apply_workqueue_attrs_locked
            ->apply_wqattrs_prepare
              ->alloc_unbound_pwq
                ->get_unbound_pool
                  ->init_worker_pool
                  ->hash_add
init_workqueues
  ->create_worker(per CPU)
  ->create_worker(unbound pool)

PS:由于函數(shù)調(diào)用深度比較深,本文僅對(duì)關(guān)鍵地方進(jìn)行講述

/* workqueue第一階段初始化(早期初始化) */
int __init workqueue_init_early(void)
{
    int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
    int i, cpu;

    ......
    /* initialize CPU pools */
    for_each_possible_cpu(cpu) {
        struct worker_pool *pool;

        i = 0;
        for_each_cpu_worker_pool(pool, cpu) {
          /* 針對(duì)每個(gè)CPU的worker pool比原,即per CPU worker pool 進(jìn)行初始化 */
            BUG_ON(init_worker_pool(pool));
            pool->cpu = cpu;
            cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu));
            pool->attrs->nice = std_nice[i++];
            pool->node = cpu_to_node(cpu);

            /* alloc pool ID */
            mutex_lock(&wq_pool_mutex);
            BUG_ON(worker_pool_assign_id(pool));
            mutex_unlock(&wq_pool_mutex);
        }
    }

    /* create default unbound and ordered wq attrs */
    /* NR_STD_WORKER_POOLS應(yīng)該是系統(tǒng)標(biāo)準(zhǔn)worker pool數(shù)量 */
    for (i = 0; i < NR_STD_WORKER_POOLS; i++) {
        struct workqueue_attrs *attrs;
      /* 創(chuàng)建標(biāo)準(zhǔn)非綁定workqueue的屬性attrs */
        BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
        attrs->nice = std_nice[i];
      /* 注意:這里被初始化好的unbound_std_wq_attrs將在后面被使用到 */
        unbound_std_wq_attrs[i] = attrs;

        /*
         * An ordered wq should have only one pwq as ordering is
         * guaranteed by max_active which is enforced by pwqs.
         * Turn off NUMA so that dfl_pwq is used for all nodes.
         */
      /* 創(chuàng)建有序workqueue的屬性attrs */
        BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
        attrs->nice = std_nice[i];
        attrs->no_numa = true;
        /* 注意:這里被初始化好的ordered_wq_attrs將在后面被使用到 */
        ordered_wq_attrs[i] = attrs;
    }

    /* 以下都是開(kāi)辟標(biāo)準(zhǔn)workqueue */
    system_wq = alloc_workqueue("events", 0, 0);
    system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
    system_long_wq = alloc_workqueue("events_long", 0, 0);
    system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
                        WQ_UNBOUND_MAX_ACTIVE);
    system_freezable_wq = alloc_workqueue("events_freezable",
                          WQ_FREEZABLE, 0);
    system_power_efficient_wq = alloc_workqueue("events_power_efficient",
                          WQ_POWER_EFFICIENT, 0);
    system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient",
                          WQ_FREEZABLE | WQ_POWER_EFFICIENT,
                          0);
    BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq ||
           !system_unbound_wq || !system_freezable_wq ||
           !system_power_efficient_wq ||
           !system_freezable_power_efficient_wq);

    return 0;
}


struct workqueue_struct *__alloc_workqueue_key(const char *fmt,
                           unsigned int flags,
                           int max_active,
                           struct lock_class_key *key,
                           const char *lock_name, ...)
{
    size_t tbl_size = 0;
    va_list args;
    struct workqueue_struct *wq;
    struct pool_workqueue *pwq;

    /*
     * Unbound && max_active == 1 used to imply ordered, which is no
     * longer the case on NUMA machines due to per-node pools.  While
     * alloc_ordered_workqueue() is the right way to create an ordered
     * workqueue, keep the previous behavior to avoid subtle breakages
     * on NUMA.
     */
    /* 根據(jù)flag執(zhí)行不同的操作 */
    if ((flags & WQ_UNBOUND) && max_active == 1)
        flags |= __WQ_ORDERED;

    /* see the comment above the definition of WQ_POWER_EFFICIENT */
    if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
        flags |= WQ_UNBOUND;

    /* allocate wq and format name */
    if (flags & WQ_UNBOUND)
        tbl_size = nr_node_ids * sizeof(wq->numa_pwq_tbl[0]);

    /* 分配workqueue */
    wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL);
    if (!wq)
        return NULL;

    /* 給workqueue的屬性分配空間 */
    if (flags & WQ_UNBOUND) {
        wq->unbound_attrs = alloc_workqueue_attrs(GFP_KERNEL);
        if (!wq->unbound_attrs)
            goto err_free_wq;
    }

    va_start(args, lock_name);
    vsnprintf(wq->name, sizeof(wq->name), fmt, args);
    va_end(args);

    max_active = max_active ?: WQ_DFL_ACTIVE;
    max_active = wq_clamp_max_active(max_active, flags, wq->name);

    /* init wq */
    wq->flags = flags;
    wq->saved_max_active = max_active;
    mutex_init(&wq->mutex);
    atomic_set(&wq->nr_pwqs_to_flush, 0);
    INIT_LIST_HEAD(&wq->pwqs);
    INIT_LIST_HEAD(&wq->flusher_queue);
    INIT_LIST_HEAD(&wq->flusher_overflow);
    INIT_LIST_HEAD(&wq->maydays);

    lockdep_init_map(&wq->lockdep_map, lock_name, key, 0);
    INIT_LIST_HEAD(&wq->list);

    /*   
      給workqueue分配pool_workqueue結(jié)構(gòu)
      pool_workqueue會(huì)將workqueue和worker_pool鏈接起來(lái) 
    */
    if (alloc_and_link_pwqs(wq) < 0)
        goto err_free_wq;

    /*
     * Workqueues which may be used during memory reclaim should
     * have a rescuer to guarantee forward progress.
     */
    /* 創(chuàng)建救援線程 */
    if (flags & WQ_MEM_RECLAIM) {
        struct worker *rescuer;

        rescuer = alloc_worker(NUMA_NO_NODE);
        if (!rescuer)
            goto err_destroy;

        rescuer->rescue_wq = wq;
        rescuer->task = kthread_create(rescuer_thread, rescuer, "%s",
                           wq->name);
        if (IS_ERR(rescuer->task)) {
            kfree(rescuer);
            goto err_destroy;
        }

        wq->rescuer = rescuer;
        kthread_bind_mask(rescuer->task, cpu_possible_mask);
        wake_up_process(rescuer->task);
    }

    if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
        goto err_destroy;

    /*
     * wq_pool_mutex protects global freeze state and workqueues list.
     * Grab it, adjust max_active and add the new @wq to workqueues
     * list.
     */
    mutex_lock(&wq_pool_mutex);

    mutex_lock(&wq->mutex);
    for_each_pwq(pwq, wq)
        pwq_adjust_max_active(pwq);
    mutex_unlock(&wq->mutex);

    list_add_tail_rcu(&wq->list, &workqueues);

    mutex_unlock(&wq_pool_mutex);

    return wq;

err_free_wq:
    free_workqueue_attrs(wq->unbound_attrs);
    kfree(wq);
    return NULL;
err_destroy:
    destroy_workqueue(wq);
    return NULL;
}

static int alloc_and_link_pwqs(struct workqueue_struct *wq)
{
    bool highpri = wq->flags & WQ_HIGHPRI;
    int cpu, ret;

    /* 給workqueue的per CPU pool_workqueue進(jìn)行創(chuàng)建和初始化 */
    if (!(wq->flags & WQ_UNBOUND)) {
        wq->cpu_pwqs = alloc_percpu(struct pool_workqueue);
        if (!wq->cpu_pwqs)
            return -ENOMEM;

        for_each_possible_cpu(cpu) {
            struct pool_workqueue *pwq =
                per_cpu_ptr(wq->cpu_pwqs, cpu);
            struct worker_pool *cpu_pools =
                per_cpu(cpu_worker_pools, cpu);

      /* 這里將per CPU worker_pool綁定到pool_workqueue上 */
            init_pwq(pwq, wq, &cpu_pools[highpri]);

            mutex_lock(&wq->mutex);
    /* 使用pool_workqueue鏈接綁定好的workqueue和per CPU wokrer pool */
            link_pwq(pwq);
            mutex_unlock(&wq->mutex);
        }
        return 0;
    } else if (wq->flags & __WQ_ORDERED) {
        ret = apply_workqueue_attrs(wq, ordered_wq_attrs[highpri]);
        /* there should only be single pwq for ordering guarantee */
        WARN(!ret && (wq->pwqs.next != &wq->dfl_pwq->pwqs_node ||
                  wq->pwqs.prev != &wq->dfl_pwq->pwqs_node),
             "ordering guarantee broken for workqueue %s\n", wq->name);
        return ret;
    } else {
        /* 
          將在外面設(shè)置的workqueue屬性u(píng)nbound_std_wq_attrs賦予workqueue 
          在這里面workqueue會(huì)創(chuàng)建和鏈接對(duì)應(yīng)的worker pool
        */
        return apply_workqueue_attrs(wq, unbound_std_wq_attrs[highpri]);
    }
}

static int apply_workqueue_attrs_locked(struct workqueue_struct *wq,
                    const struct workqueue_attrs *attrs)
{
    struct apply_wqattrs_ctx *ctx;

    /* only unbound workqueues can change attributes */
    if (WARN_ON(!(wq->flags & WQ_UNBOUND)))
        return -EINVAL;

    /* creating multiple pwqs breaks ordering guarantee */
    if (!list_empty(&wq->pwqs)) {
        if (WARN_ON(wq->flags & __WQ_ORDERED_EXPLICIT))
            return -EINVAL;

        wq->flags &= ~__WQ_ORDERED;
    }

    /* workqueue進(jìn)行屬性apply */
    ctx = apply_wqattrs_prepare(wq, attrs);
    if (!ctx)
        return -ENOMEM;

    /* the ctx has been prepared successfully, let's commit it */
    apply_wqattrs_commit(ctx);
    apply_wqattrs_cleanup(ctx);

    return 0;
}

static struct apply_wqattrs_ctx *
apply_wqattrs_prepare(struct workqueue_struct *wq,
              const struct workqueue_attrs *attrs)
{
    struct apply_wqattrs_ctx *ctx;
    struct workqueue_attrs *new_attrs, *tmp_attrs;
    int node;

    lockdep_assert_held(&wq_pool_mutex);

    ctx = kzalloc(sizeof(*ctx) + nr_node_ids * sizeof(ctx->pwq_tbl[0]),
              GFP_KERNEL);

    /* 開(kāi)辟屬性attrs的新空間 */
    new_attrs = alloc_workqueue_attrs(GFP_KERNEL);
    tmp_attrs = alloc_workqueue_attrs(GFP_KERNEL);
    if (!ctx || !new_attrs || !tmp_attrs)
        goto out_free;

    /*
     * Calculate the attrs of the default pwq.
     * If the user configured cpumask doesn't overlap with the
     * wq_unbound_cpumask, we fallback to the wq_unbound_cpumask.
     */
    /* 將屬性拷貝到new_attrs */
    copy_workqueue_attrs(new_attrs, attrs);
    cpumask_and(new_attrs->cpumask, new_attrs->cpumask, wq_unbound_cpumask);
    if (unlikely(cpumask_empty(new_attrs->cpumask)))
        cpumask_copy(new_attrs->cpumask, wq_unbound_cpumask);

    /*
     * We may create multiple pwqs with differing cpumasks.  Make a
     * copy of @new_attrs which will be modified and used to obtain
     * pools.
     */
    copy_workqueue_attrs(tmp_attrs, new_attrs);

    /*
     * If something goes wrong during CPU up/down, we'll fall back to
     * the default pwq covering whole @attrs->cpumask.  Always create
     * it even if we don't use it immediately.
     */
    /* 根據(jù)屬性創(chuàng)建pool_workqueue斟叼,讓pool_workqueue鏈接到對(duì)應(yīng)屬性的woker pool */
    ctx->dfl_pwq = alloc_unbound_pwq(wq, new_attrs);
    if (!ctx->dfl_pwq)
        goto out_free;

    for_each_node(node) {
        if (wq_calc_node_cpumask(new_attrs, node, -1, tmp_attrs->cpumask)) {
            ctx->pwq_tbl[node] = alloc_unbound_pwq(wq, tmp_attrs);
            if (!ctx->pwq_tbl[node])
                goto out_free;
        } else {
            ctx->dfl_pwq->refcnt++;
            ctx->pwq_tbl[node] = ctx->dfl_pwq;
        }
    }

    /* save the user configured attrs and sanitize it. */
    copy_workqueue_attrs(new_attrs, attrs);
    cpumask_and(new_attrs->cpumask, new_attrs->cpumask, cpu_possible_mask);
    ctx->attrs = new_attrs;

    ctx->wq = wq;
    free_workqueue_attrs(tmp_attrs);
    return ctx;

out_free:
    free_workqueue_attrs(tmp_attrs);
    free_workqueue_attrs(new_attrs);
    apply_wqattrs_cleanup(ctx);
    return NULL;
}

static struct pool_workqueue *alloc_unbound_pwq(struct workqueue_struct *wq,
                    const struct workqueue_attrs *attrs)
{
    struct worker_pool *pool;
    struct pool_workqueue *pwq;

    lockdep_assert_held(&wq_pool_mutex);
    /* 根據(jù)屬性獲取對(duì)應(yīng)的worker pool */
    pool = get_unbound_pool(attrs);
    if (!pool)
        return NULL;

    /* 創(chuàng)建pool_workqueue數(shù)據(jù)結(jié)構(gòu) */
    pwq = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL, pool->node);
    if (!pwq) {
        put_unbound_pool(pool);
        return NULL;
    }

    /* 鏈接對(duì)應(yīng)的workqueue和worker pool */
    init_pwq(pwq, wq, pool);
    return pwq;
}

static struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs)
{
    u32 hash = wqattrs_hash(attrs);
    struct worker_pool *pool;
    int node;
    int target_node = NUMA_NO_NODE;

    lockdep_assert_held(&wq_pool_mutex);

    /* do we already have a matching pool? */
    /* 如果屬性所對(duì)應(yīng)的woker pool已經(jīng)被創(chuàng)建,則直接返回對(duì)應(yīng)的worker pool */
    hash_for_each_possible(unbound_pool_hash, pool, hash_node, hash) {
        if (wqattrs_equal(pool->attrs, attrs)) {
            pool->refcnt++;
            return pool;
        }
    }

    /* if cpumask is contained inside a NUMA node, we belong to that node */
    if (wq_numa_enabled) {
        for_each_node(node) {
            if (cpumask_subset(attrs->cpumask,
                       wq_numa_possible_cpumask[node])) {
                target_node = node;
                break;
            }
        }
    }

    /* nope, create a new one */
    /* 如果沒(méi)有則創(chuàng)建新的worker pool */
    pool = kzalloc_node(sizeof(*pool), GFP_KERNEL, target_node);
    if (!pool || init_worker_pool(pool) < 0)
        goto fail;

    lockdep_set_subclass(&pool->lock, 1);   /* see put_pwq() */
    /* 賦予worker pool對(duì)應(yīng)的值 */
    copy_workqueue_attrs(pool->attrs, attrs);
    pool->node = target_node;

    /*
     * no_numa isn't a worker_pool attribute, always clear it.  See
     * 'struct workqueue_attrs' comments for detail.
     */
    pool->attrs->no_numa = false;

    if (worker_pool_assign_id(pool) < 0)
        goto fail;

    /* create and start the initial worker */
    if (wq_online && !create_worker(pool))
        goto fail;

    /* install */
    /* 將worker pool加入對(duì)應(yīng)的hash表用于遍歷春寿,在后面會(huì)使用到 */
    hash_add(unbound_pool_hash, &pool->hash_node, hash);

    return pool;
fail:
    if (pool)
        put_unbound_pool(pool);
    return NULL;
}

/* 第二階段初始化 */
int __init workqueue_init(void)
{
    ......

    /* create the initial workers */
    for_each_online_cpu(cpu) {
        for_each_cpu_worker_pool(pool, cpu) {
            pool->flags &= ~POOL_DISASSOCIATED;
            /* 可以從這里看到朗涩,per CPU woker pool是在這里創(chuàng)建worker的 */
            BUG_ON(!create_worker(pool));
        }
    }

    /* 
      前面已經(jīng)講到,標(biāo)準(zhǔn)非綁定的worker pool會(huì)鏈入該哈希表
      這里是遍歷哈希表來(lái)為每個(gè)work pool創(chuàng)建worker
    */
    hash_for_each(unbound_pool_hash, bkt, pool, hash_node)
        BUG_ON(!create_worker(pool));
    ......
    return 0;
}

總的來(lái)說(shuō)绑改,在 系統(tǒng)初始化 時(shí)會(huì)創(chuàng)建默認(rèn)的 workqueueworker pool谢床。在創(chuàng)建 workqueue 時(shí),會(huì)根據(jù) flag 創(chuàng)建對(duì)應(yīng)的 pool_workqueue厘线,并將對(duì)應(yīng)屬性的 woker pool 鏈接到 pool_workqueue识腿。如果是 bound workqueue,則會(huì)直接指定 per CPU pool_workqueueworker pool 為已經(jīng)默認(rèn)定義好的 per CPU woker pool造壮。

3.4渡讼、cmwq相關(guān)API

3.4.1 API使用流程

cmwq 的一般使用方法比較簡(jiǎn)單,一遍為以下流程:
1. 如果需要自行創(chuàng)建 workqueue耳璧,則需要調(diào)用 alloc_workqueue()
2. 接著創(chuàng)建一個(gè) **struct work_struct **成箫,并使用 INIT_WORK 進(jìn)行初始化
3. 調(diào)用 queue_work_onwork 放入 workqueue

  1. API使用方法
  2. 舉例cmwq的工作過(guò)程,內(nèi)核文檔中的例子

3.4.1 API說(shuō)明

3.4.1 創(chuàng)建workqueue

#define alloc_workqueue(fmt, flags, max_active, args...)        \
    __alloc_workqueue_key((fmt), (flags), (max_active),     \
                  NULL, NULL, ##args)
struct workqueue_struct *__alloc_workqueue_key(const char *fmt,
                           unsigned int flags,
                           int max_active,
                           struct lock_class_key *key,
                           const char *lock_name, ...)
  • fmt:指 workqueue 的名稱

  • flag:決定 workqueue 的執(zhí)行模式及其 work 的處理模式(決定 workqueue 綁定那些 worker pool)旨枯,主要使用的標(biāo)志如下:

    • WQ_MEM_RECLAIM:如果 workqueuework 相互之間有相關(guān)性蹬昌,則需要設(shè)置該 flag。如果設(shè)置了這個(gè) flag攀隔,那么工作隊(duì)列在創(chuàng)建的時(shí)候會(huì)創(chuàng)建一個(gè) 救助者內(nèi)核線程 備用皂贩,線程的名稱為工作隊(duì)列的名字。
      舉例說(shuō)明:假設(shè)有 A work昆汹、B work明刷、C work、D work满粗,當(dāng)正在處理的 B work 被阻塞后辈末,worker pool 會(huì)創(chuàng)建一個(gè)新的 worker thread 來(lái)處理其他的 work ,但是,在內(nèi)存資源比較緊張的時(shí)候本冲,創(chuàng)建 worker thread 未必能夠成功。而如果此時(shí) B work 是依賴 C或者D work 的執(zhí)行結(jié)果的時(shí)候劫扒,系統(tǒng)進(jìn)入死鎖檬洞。這種狀態(tài)是由于不能創(chuàng)建新的 worker thread 導(dǎo)致的。對(duì)于每一個(gè)設(shè)置 WQ_MEM_RECLAIMworkqueue沟饥,系統(tǒng)都會(huì)創(chuàng)建一個(gè) rescuer thread添怔。當(dāng)發(fā)生這種情況的時(shí)候,C或者D work 會(huì)被 rescuer thread 接手處理贤旷,從而解除了死鎖广料。
    • WQ_FREEZABLE:該標(biāo)志是一個(gè)和 電源管理 相關(guān)的。在系統(tǒng) suspend 的時(shí)候幼驶,需要凍結(jié)用戶空間的進(jìn)程以及部分標(biāo)注 freezable 的內(nèi)核線程(包括 worker thread )艾杏。設(shè)置該標(biāo)志的 workqueue 需要參與到進(jìn)程凍結(jié)的過(guò)程中。其 worker thread 被凍結(jié)的時(shí)候盅藻,會(huì)處理完當(dāng)前所有的 work购桑。一旦凍結(jié)完成,那么就不會(huì)啟動(dòng)新的 work 的執(zhí)行氏淑,直到進(jìn)程被解凍勃蜘。缺省情況下,所有的內(nèi)核線程 都是 非freezable
    • WQ_UNBOUND:設(shè)置該標(biāo)志的 workqueue 說(shuō)明其 work 的處理不需要綁定在特定的 CPU 上執(zhí)行假残,workqueue 需要關(guān)聯(lián)一個(gè)系統(tǒng)中的 unbound worker pool缭贡。如果系統(tǒng)中能根據(jù) workqueue 的屬性找到匹配的線程池,那么就選擇一個(gè)辉懒。如果找不到適合的線程池阳惹,workqueue 就會(huì)創(chuàng)建一個(gè) worker pool 來(lái)處理 work
    • WQ_HIGHPRI:說(shuō)明掛入該 workqueuework 是屬于高優(yōu)先級(jí)的 work眶俩,需要高優(yōu)先級(jí)的 worker thread(一般指nice value比較低的worker)穆端。
    • WQ_CPU_INTENSIVE:該標(biāo)志說(shuō)明掛入 workqueuework 是屬于 特別消耗CPU 的任務(wù)。這類 work 會(huì)得到系統(tǒng)進(jìn)程調(diào)度器的監(jiān)管仿便,排在這類 work 后面的 non-cpu intensive 類型 work 可能會(huì)推遲執(zhí)行体啰。也就是說(shuō)設(shè)置該標(biāo)志會(huì)影響并發(fā)性。
    • WQ_POWER_EFFICIENT:由于 cache 的原因嗽仪,per CPU 線程池 的性能會(huì)好一些荒勇,但是對(duì)功耗 有一些影響。一般情況下闻坚,workqueue 需要在 性能功耗 之間平衡沽翔。想要更好的性能,最好讓 per CPU上的 worker thread 來(lái)處理 work。此時(shí) cache命中率 會(huì)比較高仅偎,性能會(huì)更好跨蟹。但如果從 功耗的角度來(lái)看,最好的策略是讓空閑狀態(tài)的 CPU 盡可能的保持空閑橘沥,而不是反復(fù)進(jìn)行 空閑-工作-空閑 的循環(huán)窗轩。
      舉例說(shuō)明。在 T1時(shí)刻座咆,work 被調(diào)度到 CPU A 上執(zhí)行痢艺。在 T2時(shí)刻work 執(zhí)行完畢介陶。 CPU A 進(jìn)入空閑狀態(tài)堤舒。在 T3時(shí)刻, 有一個(gè)新的 work 需要處理哺呜。這時(shí)候調(diào)度 workCPU A 上運(yùn)行舌缤。由于之前 CPU A處理過(guò)work,其 cache 中的內(nèi)容還沒(méi)被刷洗掉某残,處理起 work 速度很快友驮。但會(huì)如果將 CPU A 從空閑狀態(tài)中喚醒。如果選擇 CPU B 這個(gè)一直運(yùn)行的 CPU驾锰,則不存在將 CPU 從空閑狀態(tài)喚醒的操作卸留,從而獲取 功耗方面的好處。
  • max_active:該參數(shù)決定了 每個(gè)CPU 上分配到 workqueuework最大數(shù)量椭豫。例如 max_active 是16耻瑟,那么 同一個(gè)CPU上同一時(shí)刻最大只有16工作項(xiàng)可以工作
    對(duì)于 **bound workqueue赏酥,其最大限制值為 512喳整,并且 默認(rèn)值為0 的時(shí)候則自動(dòng)設(shè)置為 256
    對(duì)于 unbound workqueue裸扶,其最大限制值為 512框都,并且 默認(rèn)值為0 的時(shí)候則自動(dòng)設(shè)置為 4
    workqueue 中的 active work 的數(shù)量一般來(lái)說(shuō)是由 用戶 來(lái)進(jìn)行調(diào)節(jié)的呵晨。除非一些特殊的要求需要限制工作項(xiàng)的數(shù)量魏保,一般推薦 默認(rèn)的0
    一些用戶需要依賴 single thread workqueue嚴(yán)格順序執(zhí)行 的特點(diǎn)摸屠。那么 將其設(shè)置成1并且將flag設(shè)置成WQ_UNBOUND可以將workqueue 指定為 single thread workqueue谓罗。這類 workqueue 上的 work 總是會(huì)被放入 unbound worker pool,并且在 任意時(shí)刻都只有一個(gè)工作項(xiàng)被執(zhí)行季二。這樣就能夠?qū)崿F(xiàn) 類似single thread workqueue相同順序執(zhí)行 的特性檩咱。

3.4.2 初始化work

#define INIT_WORK(_work, _func)                     \
    __INIT_WORK((_work), (_func), 0)

#define __INIT_WORK(_work, _func, _onstack)             \
    do {                                \
        __init_work((_work), _onstack);             \
        (_work)->data = (atomic_long_t) WORK_DATA_INIT();   \
        INIT_LIST_HEAD(&(_work)->entry);            \
        (_work)->func = (_func);                \
    } while (0)hile (0)
  • work:需要被初始化的 struct work_struct 結(jié)構(gòu)體
  • func:該 work 需要執(zhí)行的函數(shù)

3.4.2 入列work

bool queue_work_on(int cpu, struct workqueue_struct *wq,
           struct work_struct *work)
  • cpu:指定該 work 所運(yùn)行的 CPU
  • wq:指定該 work 需要入的 workqueue
  • work:需要執(zhí)行的 work

除了上面的 alloc_workqueue 接口之外揭措,還有 alloc_ordered_workqueue 接口來(lái)創(chuàng)建一個(gè) 嚴(yán)格串行 執(zhí)行 work 的一個(gè) workqueue,并且該 **workqueue **是 unbound類型刻蚯。

create_*接口都是為了兼容過(guò)去接口而設(shè)立的绊含,大家可以自行理解,這里就不多說(shuō)了炊汹。

了解了上面的基礎(chǔ)內(nèi)容之后躬充,我們?cè)倩仡^看看 per CPU thread poolunbound thread pool。當(dāng) workqueue 收到一個(gè) work

  • 如果是 unbound workqueue 的話兵扬,那么該 workunbound thread pool 處理并由系統(tǒng)的調(diào)度器模塊決定該 work 所執(zhí)行的 CPU 麻裳。對(duì)于 調(diào)度器 而言口蝠,它會(huì)考慮 CPU的空閑狀態(tài)器钟,盡可能的讓 CPU保持在空閑狀態(tài),從而節(jié)省了功耗妙蔗。因此傲霸,對(duì)于 unbound workqueue 來(lái)說(shuō),處理掛入的 work 是考慮到 power saving 的眉反。
  • 如果是 bound workqueue 昙啄,即沒(méi)有設(shè)置 WQ_UNBOUND。則說(shuō)明該 workqueueper CPU 的寸五。此時(shí) work 的調(diào)度不由 調(diào)度器 決定梳凛,因此會(huì)間接影響功耗。

我們通過(guò)下面的例子來(lái)看看 cmwq 在不同的配置下的操作梳杏。假設(shè)有工作項(xiàng) w0韧拒,w1,w2 被放入 同一個(gè)CPU 上的一個(gè) bound workqueue 中十性。w0消耗了CPU 5ms的時(shí)間然后進(jìn)入休眠10ms叛溢,然后再次消耗CPU 5ms。w1和w2消耗cpu 5ms然后進(jìn)入休眠10ms劲适。其中不考慮其他任務(wù)或者工作的開(kāi)銷楷掉,并且使用的是 FIFO調(diào)度

如果 max_active = 1霞势。如下所示烹植,所有任務(wù)都是串行執(zhí)行。

TIME IN MSECS EVEN
0 w0 starts and burns CPU
5 w0 sleeps
15 w0 wakes up and burns CPU
20 w0 finishes
20 w1 starts and burns CPU
25 w1 sleeps
35 w1 wakes up and finishes
35 w2 starts and burns CPU
40 w2 sleeps
50 w2 wakes up and finishes

如果 max_active >= 3愕贡。如下所示刊橘,在某些任務(wù)休眠的同時(shí)會(huì)執(zhí)行其他任務(wù)。

TIME IN MSECS EVEN
0 w0 starts and burns CPU
5 w0 sleeps
5 w1 starts and burns CPU
10 w1 sleeps
10 w2 starts and burns CPU
15 w2 sleeps
15 w0 wakes up and burns CPU
20 w0 finishes
20 w1 wakes up and finishes
25 w2 wakes up and finishes

如果 max_active = 2颂鸿。如下所示促绵,w2 只能最后才運(yùn)行。

TIME IN MSECS EVEN
0 w0 starts and burns CPU
5 w0 sleeps
5 w1 starts and burns CPU
10 w1 sleeps
15 w0 wakes up and burns CPU
20 w0 finishes
20 w1 wakes up and finishes
20 w2 starts and burns CPU
25 w2 sleeps
35 w2 wakes up and finishes

如果我們現(xiàn)在講隊(duì)列設(shè)置了 WQ_CPU_INTENSIVE 標(biāo)志,則如下

TIME IN MSECS EVEN
0 w0 starts and burns CPU
5 w0 sleeps
5 w1 and w2 start and burn CPU
10 w1 sleeps
15 w2 sleeps
15 w0 wakes up and burns CPU
20 w0 finishes
20 w1 wakes up and finishes
25 w2 wakes up and finishes

四败晴、參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尖坤,隨后出現(xiàn)的幾起案子稳懒,更是在濱河造成了極大的恐慌,老刑警劉巖慢味,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件场梆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡纯路,警方通過(guò)查閱死者的電腦和手機(jī)或油,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)驰唬,“玉大人顶岸,你說(shuō)我怎么就攤上這事〗斜啵” “怎么了辖佣?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)搓逾。 經(jīng)常有香客問(wèn)我卷谈,道長(zhǎng),這世上最難降的妖魔是什么霞篡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任世蔗,我火速辦了婚禮,結(jié)果婚禮上寇损,老公的妹妹穿的比我還像新娘凸郑。我一直安慰自己,他們只是感情好矛市,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布芙沥。 她就那樣靜靜地躺著,像睡著了一般浊吏。 火紅的嫁衣襯著肌膚如雪而昨。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,245評(píng)論 1 299
  • 那天找田,我揣著相機(jī)與錄音歌憨,去河邊找鬼。 笑死墩衙,一個(gè)胖子當(dāng)著我的面吹牛务嫡,可吹牛的內(nèi)容都是我干的甲抖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼心铃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼准谚!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起去扣,我...
    開(kāi)封第一講書(shū)人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤柱衔,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后愉棱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體唆铐,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年奔滑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艾岂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡档押,死狀恐怖澳盐,靈堂內(nèi)的尸體忽然破棺而出祈纯,到底是詐尸還是另有隱情令宿,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布腕窥,位于F島的核電站粒没,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏簇爆。R本人自食惡果不足惜癞松,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望入蛆。 院中可真熱鬧响蓉,春花似錦、人聲如沸哨毁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扼褪。三九已至想幻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間话浇,已是汗流浹背脏毯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幔崖,地道東北人食店。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓渣淤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親吉嫩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子砂代,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容