前面一篇文章分析了中斷系統的框架,這篇文章主要講中斷的下半部份的機制;
(一)概述
linux將其分成了兩個部分,一個叫做中斷handler(top half),是全程關閉中斷的,另外一部分是deferable task(bottom half),屬於不那麼緊急需要處理的事情。在執行bottom half的時候,是開中斷的。系統可以把實時性的操作或者硬件的一些ACK放在上半部分來做,其他數據處理,上報操作等不是太緊要的都可以放到下半部分來做,這樣就大大減少了系統關中斷的時間,提高了系統的性能;
常見處理下半部份的機制有softirq、tasklet、workqueue;workqueue運行在process context,而softirq和tasklet運行在interrupt context,所以workqueue和softirq、tasklet是有本質區別的;
(二)softirq
(1)softirq和tasklet的區別
soft和irq都用於中斷的下半部處理,softirq更傾向於性能,而tasklet更傾向於易用性。但是tasklet是基於softirq機制來實現的;
同一個中斷觸發然後調用下半部的處理函數,並啓用softirq機制,這樣相同類型的softirq有可能在不同的CPU上併發執行,所以要考慮併發引入同步機制,當中斷處理的softirq同時在Processor A和B上運行的時候,中斷又來了。這時候,中斷分發給processor A,這時候,processor A上的handler仍然會raise softirq,但是並不會調度該softirq。也就是說,softirq在一個CPU上是串行執行的。這種情況下,系統性能瓶頸是CPU資源,需要增加更多的CPU來解決該問題。
同樣相同中斷觸發的tasklet在processor A上被調度執行,那麼它永遠也不會同時在processor B上執行,也就是說,tasklet是串行執行的(注:不同的tasklet還是會併發的),不需要考慮重入的問題。在檢查tasklet的狀態的時候,如果發現該tasklet在其他processor上已經正在運行,那麼該tasklet不會被處理,一直等到在processor A上的tasklet處理完,在processor B上的這個tasklet才能被執行;
(2)irq context
一個task的thread info數據結構定義如下:
struct thread_info {
……
int preempt_count; /* 0 => preemptable, <0 => bug */
……
};
preempt_count這個成員被用來判斷當前進程是否可以被搶佔。如果preempt_count不等於0(可能是代碼調用preempt_disable顯式的禁止了搶佔,也可能是處於中斷上下文等),說明當前不能進行搶佔,如果preempt_count等於0,說明已經具備了搶佔的條件(當然具體是否要搶佔當前進程還是要看看thread info中的flag成員是否設定了_TIF_NEED_RESCHED這個標記,可能是當前的進程的時間片用完了,也可能是由於中斷喚醒了優先級更高的進程);
preempt字段格式如下:
IRQ context其實就是hard irq context,也就是說明當前正在執行中斷handler(top half),只要preempt_count中的hardirq count大於0(=1是沒有中斷嵌套,如果大於1,說明有中斷嵌套),那麼就是IRQ context。
(3)softirq機制
a. 註冊softirq,註冊一個軟中斷,即在軟中斷向量數據中,增加軟中斷number爲nr的處理函數:
442 void open_softirq(int nr, void (*action)(struct softirq_action *))
443 {
444 softirq_vec[nr].action = action;
445 }
softirq_vec爲軟中斷向量表,針對不同的softirq number有不同的entry:
58 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
softirq number:
387 enum
388 {
389 HI_SOFTIRQ=0,//高優先級的tasklet;
390 TIMER_SOFTIRQ,//基於系統tick的software timer
391 NET_TX_SOFTIRQ,//網卡數據發送;
392 NET_RX_SOFTIRQ,//網卡數據接收;
393 BLOCK_SOFTIRQ,//block device
394 BLOCK_IOPOLL_SOFTIRQ,//block device
395 TASKLET_SOFTIRQ,//用於普通的tasklet;
396 SCHED_SOFTIRQ,
397 HRTIMER_SOFTIRQ,
398 RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
399
400 NR_SOFTIRQS
401 };
b. 觸發softirq :如果要啓動某個軟中斷,則需要置位irq_stat[cpu].__softirq_pending中的相應位,而設置__softirq_pending的操作則由函數__raise_softirq來實現。_softirq_pending是per cpu的,因此不需要考慮多個CPU的併發,只要disable本地中斷,就可以確保對__softirq_pending操作的原子性。
可以通過以下兩種方法啓動軟中斷:
1)在中斷上下文中,通過調用函數raise_softirq,置位irq_stat[cpu].__softirq_pending中的相應軟中斷位;則會在中斷結束後在函數irq_exit中調用invoke_softirq,實現軟中斷處理;
2)在非中斷上下文中,通過調用raise_softirq_irqoff,置位irq_stat[cpu].__softirq_pending中的相應軟中斷位;並喚醒軟中斷守護進程,通過軟中斷守護進程實現軟中斷的處理;
//大多數情況下都是在中斷handler被調用,是關cpu中斷的,但是也有可能在其他上下文中調用,這個時候是打開cpu中斷的,所以要用local_irq_save來保護softirq_pending的狀態;
427 void raise_softirq(unsigned int nr)
428 {
429 unsigned long flags;
430
431 local_irq_save(flags);
432 raise_softirq_irqoff(nr);
433 local_irq_restore(flags);
434 }
410 inline void raise_softirq_irqoff(unsigned int nr)
411 {
412 __raise_softirq_irqoff(nr);//設定本CPU上的__softirq_pending的某個bit等於1,具體的bit是由soft irq number(nr參數)指定的。;
413
423 if (!in_interrupt())
424 wakeup_softirqd();
//如果在中斷上下文,我們只要set __softirq_pending的某個bit就
//OK了,在中斷返回的時候自然會進行軟中斷的處理。但是,如果在
//context上下文調用這個函數的時候,我們必須要調用
//wakeup_softirqd函數用來喚醒本CPU上的softirqd這個內核線程。
425 }
c. 中斷處理:softirq 的執行有兩個時刻:在退出中斷 irq_exit() 時或者在 softirqd 線程當中,在中斷上下文的情況,通過退出中斷的時候會調用softirq註冊的處理函數,代碼如下:
389 void irq_exit(void)
390 {
399 if (!in_interrupt() && local_softirq_pending())
//確保沒有在中斷上下文,同時local_softirq_pending來判斷是否觸發了軟中斷執行;
400 invoke_softirq();
405 }
350 static inline void invoke_softirq(void)
351 {
352 if (!force_irqthreads) {//如果強制線程化,那麼系統中所有的軟中斷都在sofirq的softirqd進程中被調度執行。
353 #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
359 __do_softirq();
360 #else
366 do_softirq_own_stack();
367 #endif
368 } else {
369 wakeup_softirqd();//內核專門開了一個softirq服務線程來處理軟中斷;
370 }
371 }
233 asmlinkage __visible void __do_softirq(void)
234 {
235 unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
236 unsigned long old_flags = current->flags;
237 int max_restart = MAX_SOFTIRQ_RESTART;
238 struct softirq_action *h;
239 bool in_hardirq;
240 __u32 pending;
241 int softirq_bit;
242
248 current->flags &= ~PF_MEMALLOC;
249
250 pending = local_softirq_pending();
251 account_irq_enter_time(current);
252
253 __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);//通過__preempt_count_add;保證了在執行softirq的時候不被搶佔。
254 in_hardirq = lockdep_softirq_start();
255
256 restart:
257 /* Reset the pending bitmask before enabling irqs */
258 set_softirq_pending(0);//清除pending標誌;
259
260 local_irq_enable();//打開CPU中斷,softirq handler是開中斷執行的;
261
262 h = softirq_vec;
263
264 while ((softirq_bit = ffs(pending))) {//輪詢軟中斷向量表查看pending的軟中斷;
265 unsigned int vec_nr;
266 int prev_count;
267
268 h += softirq_bit - 1;
269
270 vec_nr = h - softirq_vec;
271 prev_count = preempt_count();
272
273 kstat_incr_softirqs_this_cpu(vec_nr);
274
275 trace_softirq_entry(vec_nr);
276 #ifdef CONFIG_MTPROF
277 mt_trace_SoftIRQ_start(vec_nr);
278 #endif
279 h->action(h);
280 #ifdef CONFIG_MTPROF
281 mt_trace_SoftIRQ_end(vec_nr);
282 #endif
283 trace_softirq_exit(vec_nr);
284 if (unlikely(prev_count != preempt_count())) {
285 pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
286 vec_nr, softirq_to_name[vec_nr], h->action,
287 prev_count, preempt_count());
288 preempt_count_set(prev_count);
289 }
290 h++;
291 pending >>= softirq_bit;
292 }
293
294 rcu_bh_qs();
295 local_irq_disable();//關閉cpu中斷;
296
297 pending = local_softirq_pending();//再次檢查softirq pending;如果又重新觸發了中斷,所以不能一直在這裏執行,判斷條件;
//一次運行最長時間的條件:
//運行時間超過2ms;
//或者輪詢軟中斷超過十次;
//或者本線程需要調度;
298 if (pending) {
299 if (time_before(jiffies, end) && !need_resched() &&
300 --max_restart)
301 goto restart;
302
303 wakeup_softirqd();//就喚醒softirqd來處理;
304 }
305
306 lockdep_softirq_end(in_hardirq);
307 account_irq_exit_time(current);
308 __local_bh_enable(SOFTIRQ_OFFSET);//表示softirq處理完畢;preempt_count_sub();
309 WARN_ON_ONCE(in_interrupt());
310 tsk_restore_flags(current, old_flags, PF_MEMALLOC);
311 }
這篇文章對softirq搶斷的解釋很詳細,可以查看:對問題“爲什麼執行softirq時不能被搶佔?”的解答
(二)tasklet
tasklet的兩個特點:(1)tasklet可以動態分配,也可以靜態分配,數量不限。(2)同一種tasklet在多個cpu上也不會並行執行,這使得程序員在撰寫tasklet function的時候比較方便,減少了對併發的考慮(當然損失了性能)。
(1) 定義tasklet
系統中的每個cpu都會維護一個tasklet的鏈表:
455 static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
456 static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
聲明一個tasklet:
474 #define DECLARE_TASKLET(name, func, data) \
475 struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
476
477 #define DECLARE_TASKLET_DISABLED(name, func, data) \
478 struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
這兩個宏都可以靜態定義一個struct tasklet_struct的變量,只不過初始化後的tasklet一個是處於eable狀態,一個處於disable狀態的。當然,也可以動態分配tasklet,然後調用tasklet_init來初始化該tasklet。
tasklet_struct:
465 struct tasklet_struct
466 {
467 struct tasklet_struct *next;//每個cpu都會維護一個tasklet鏈表,next這個成員指向了該鏈表中的下一個tasklet;
468 unsigned long state;//表示該tasklet的狀態;
469 atomic_t count;//如果count等於0那麼該tasklet是處於enable的,如果大於0,表示該tasklet是disable的。
470 void (*func)(unsigned long);//處理函數;
471 unsigned long data;//傳入的參數;
472 };
481 enum
482 {
483 TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */ //表示該tasklet以及被調度到某個CPU上執行;
484 TASKLET_STATE_RUN /* Tasklet is running (SMP only) */ //表示該tasklet正在某個cpu上執行;
485 };
(2) 調度tasklet
通過tasklet_schedule來調度一個tasklet:
511 static inline void tasklet_schedule(struct tasklet_struct *t)
512 {
//TASKLET_STATE_SCHED這個flag已經確保了__tasklet_schedule只會在一個cpu上執行;
513 if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
514 __tasklet_schedule(t);
515 }
458 void __tasklet_schedule(struct tasklet_struct *t)
459 {
460 unsigned long flags;
461
462 local_irq_save(flags);//禁止本地中斷
463 t->next = NULL;
464 *__this_cpu_read(tasklet_vec.tail) = t;
465 __this_cpu_write(tasklet_vec.tail, &(t->next));
466 raise_softirq_irqoff(TASKLET_SOFTIRQ);//調度
softirq numer爲TASKLET_SOFTIRQ;
467 local_irq_restore(flags);
468 }
469 EXPORT_SYMBOL(__tasklet_schedule);
(2) 執行tasklet
對於TASKLET_SOFTIRQ類型的softirq,其handler是tasklet_action:
在softirq init的時候會聲明:
649 void __init softirq_init(void)
650 {
651 int cpu;
652
653 for_each_possible_cpu(cpu) {
654 per_cpu(tasklet_vec, cpu).tail =
655 &per_cpu(tasklet_vec, cpu).head;
656 per_cpu(tasklet_hi_vec, cpu).tail =
657 &per_cpu(tasklet_hi_vec, cpu).head;
658 }
659
660 open_softirq(TASKLET_SOFTIRQ, tasklet_action);
661 open_softirq(HI_SOFTIRQ, tasklet_hi_action);
662 }
tasklet_action:
494 static void tasklet_action(struct softirq_action *a)
495 {
496 struct tasklet_struct *list;
497
498 local_irq_disable();//關中斷保護
499 list = __this_cpu_read(tasklet_vec.head);//取出該CPU全部的tasklet;
500 __this_cpu_write(tasklet_vec.head, NULL);//並清空;
501 __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
502 local_irq_enable();//開中斷;
503
504 while (list) {//遍歷tasklet鏈表;
505 struct tasklet_struct *t = list;
506
507 list = list->next;
508
509 if (tasklet_trylock(t)) {//設定該tasklet的state爲TASKLET_STATE_RUN;
510 if (!atomic_read(&t->count)) {
511 if (!test_and_clear_bit(TASKLET_STATE_SCHED,
512 &t->state))
//說明該tasklet可以真正進入執行狀態了。主要的動作就是清除
//TASKLET_STATE_SCHED狀態,執行tasklet func。
513 BUG();
514 #ifdef CONFIG_MTPROF
515 mt_trace_tasklet_start(t->func);
516 #endif
517 t->func(t->data);
518 #ifdef CONFIG_MTPROF
519 mt_trace_tasklet_end(t->func);
520 #endif
521 tasklet_unlock(t);
522 continue;
523 }
524 tasklet_unlock(t);
525 }
526
527 local_irq_disable();
528 t->next = NULL;
529 *__this_cpu_read(tasklet_vec.tail) = t;
530 __this_cpu_write(tasklet_vec.tail, &(t->next));
531 __raise_softirq_irqoff(TASKLET_SOFTIRQ);
532 local_irq_enable();
533 }
534 }
(三)workqueue
中斷handler以及bottom half(不包括workqueue)都是在interrupt context中執行,而中斷上下當中不允許休眠(休眠的意思就是掛起當前進程的意思,本來中斷就是中斷當前線程進入特殊模式處理中斷事件,如果這個時候還讓它休眠,也會導致當前進程休眠,所以對當前進程的執行是不公平的)
workqueue和其他的bottom half最大的不同是它是運行在進程上下文中的,它可以睡眠;各個中斷驅動通過創建defering work來處理bottom half,linux就是把這些work彙集起來,提供一個統一的機制,這就是work queue了。
(1) CMWQ
CMWQ(Concurrency Managed Workqueue)是對原有的工作隊列的一個重新實現,兼容以前的wq的同時,讓所有的workqueue共享系統中的worker pool,worker pool會根據情況靈活的創建thread來處理work;比如該workqueue是bounded(per-cpu)類型並且設定了high priority,那麼掛入該workqueue的work將由per cpu的highpri worker-pool來處理;
原理框圖:
workqueue_struct : 結構體代表的是工作隊列,工作隊列分unbound workqueue和bound workqueue。workqueue關聯到哪個worker pool,這是由workqueue_attrs決定的。
pool_workqueue:workqueue和thread pool沒有嚴格的對應關係,系統中的workqueue們共享一組thread pool,workqueue_struct有3個pool_workqueue成員:dfl_pwq是unbund類型的;cpu_pwqs是bound類型的;numa_pwq_tbl表示指向per node的pool workqueue;
掛入workqueue的work終究需要worker pool中的某個worker thread來處理,也就是說,workqueue要和系統中那些共享的worker thread pool進行連接,這是通過pool_workqueue(該數據結構會包含一個指向worker pool的指針)的數據結構來管理的。
worker_pool:是否創建新的work線程是由worker線程池來管理,線程池包括兩種:(1)bound 線程池,和特定CPU綁定,這種線程池有兩種,一種叫做normal thread pool,另外一種叫做high priority thread pool,這兩種thread分別用來處理普通的和高優先級的work。(2)unbound 線程池,可以運行在任意的cpu上,這種thread pool是動態創建的,是和thread pool的屬性相關;
(2) 創建工作隊列
創建工作隊列:
414 #define alloc_ordered_workqueue(fmt, flags, args...) \
415 alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ##args)
416
417 #define create_workqueue(name) \
418 alloc_workqueue("%s", WQ_MEM_RECLAIM, 1, (name))
419 #define create_freezable_workqueue(name) \
420 alloc_workqueue("%s", WQ_FREEZABLE | WQ_UNBOUND | WQ_MEM_RECLAIM, \
421 1, (name))
422 #define create_singlethread_workqueue(name) \
423 alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, name)
根據不同的類型創建不同的工作隊列:
WQ_UNBOUND:說明其work的處理不需要綁定在特定的CPU上執行,workqueue需要關聯一個系統中的unbound worker thread pool;
WQ_FREEZABLE:標記WQ_FREEZABLE的workqueue需要參與到進程凍結的過程中,worker thread被凍結的時候,會處理完當前所有的work,一旦凍結完成,那麼就不會啓動新的work的執行,直到進程被解凍。
WQ_MEM_RECLAIM:標記WQ_MEM_RECLAIM flag的work queue,系統都會創建一個rescuer thread,可以解決幾個work相互依賴的情況;
WQ_HIGHPRI:說明掛入該workqueue的work是屬於高優先級的work,需要高優先級(比較低的nice value)的worker thread來處理。
WQ_CPU_INTENSIVE:這個flag說明掛入該workqueue的work是屬於特別消耗cpu的那一類。
alloc_workqueue最終調用__alloc_workqueue_key來創建工作隊列:
4079 struct workqueue_struct *__alloc_workqueue_key(const char *fmt,
4080 unsigned int flags,
4081 int max_active,
4082 struct lock_class_key *key,
4083 const char *lock_name, ...)
4084 {
4085 size_t tbl_size = 0;
4086 va_list args;
4087 struct workqueue_struct *wq;
4088 struct pool_workqueue *pwq;
4089
4090 /* see the comment above the definition of WQ_POWER_EFFICIENT */
4091 if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)//通過WQ_POWER_EFFICIENT來標記自己在功耗方面的屬性;如果wq_power_efficient設定爲true,workqueue就會強制按照unbound workqueue來處理;
4092 flags |= WQ_UNBOUND;
4093
4094 /* allocate wq and format name */
4095 if (flags & WQ_UNBOUND)
4096 tbl_size = nr_node_ids * sizeof(wq->numa_pwq_tbl[0]);
4097
4098 wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL);
4099 if (!wq)
4100 return NULL;
4101
4102 if (flags & WQ_UNBOUND) {
4103 wq->unbound_attrs = alloc_workqueue_attrs(GFP_KERNEL);
4104 if (!wq->unbound_attrs)
4105 goto err_free_wq;
4106 }
4107
4108 va_start(args, lock_name);
4109 vsnprintf(wq->name, sizeof(wq->name), fmt, args);
4110 va_end(args);
4111
4112 max_active = max_active ?: WQ_DFL_ACTIVE;
4113 max_active = wq_clamp_max_active(max_active, flags, wq->name);//最大可以創建的worker thread的數目;
4114
4115 /* init wq */ //初始化wq成員;
4116 wq->flags = flags;
4117 wq->saved_max_active = max_active;
4118 mutex_init(&wq->mutex);
4119 atomic_set(&wq->nr_pwqs_to_flush, 0);
4120 INIT_LIST_HEAD(&wq->pwqs);
4121 INIT_LIST_HEAD(&wq->flusher_queue);
4122 INIT_LIST_HEAD(&wq->flusher_overflow);
4123 INIT_LIST_HEAD(&wq->maydays);
4124
4125 lockdep_init_map(&wq->lockdep_map, lock_name, key, 0);
4126 INIT_LIST_HEAD(&wq->list);
4127
4128 if (alloc_and_link_pwqs(wq) < 0)//分配pool workqueue的內存並建立workqueue和pool workqueue的關係;
4129 goto err_free_wq;
4130
4131 /*
4132 * Workqueues which may be used during memory reclaim should
4133 * have a rescuer to guarantee forward progress.
4134 */
4135 if (flags & WQ_MEM_RECLAIM) {//這種情況下會創建rescuer_thread;
4136 struct worker *rescuer;
4137
4138 rescuer = alloc_worker(NUMA_NO_NODE);
4139 if (!rescuer)
4140 goto err_destroy;
4141
4142 rescuer->rescue_wq = wq;
4143 rescuer->task = kthread_create(rescuer_thread, rescuer, "%s",
4144 wq->name);
4145 if (IS_ERR(rescuer->task)) {
4146 kfree(rescuer);
4147 goto err_destroy;
4148 }
4149
4150 wq->rescuer = rescuer;
4151 rescuer->task->flags |= PF_NO_SETAFFINITY;
4152 wake_up_process(rescuer->task);
4153 }
4154
4155 if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
4156 goto err_destroy;
4157
4163 mutex_lock(&wq_pool_mutex);
4164
4165 mutex_lock(&wq->mutex);
4166 for_each_pwq(pwq, wq)
4167 pwq_adjust_max_active(pwq);
4168 mutex_unlock(&wq->mutex);
4169
4170 list_add(&wq->list, &workqueues);//把該workqueue添加到workqueues;
4171
4172 mutex_unlock(&wq_pool_mutex);
4173
4174 return wq;
4183 }
4184 EXPORT_SYMBOL_GPL(__alloc_workqueue_key);
(3) 添加工作到隊列
聲明一個work來處理中斷的下半部,通過如下聲明創建一個work_struct:
//driver創建work struct
171 #define DECLARE_WORK(n, f) \
172 struct work_struct n = __WORK_INITIALIZER(n, f)
173
//指定時間之後在去處理;
174 #define DECLARE_DELAYED_WORK(n, f) \
175 struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
176
177 #define DECLARE_DEFERRABLE_WORK(n, f) \
178 struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, TIMER_DEFERRABLE)
//動態創建
223 #define INIT_WORK(_work, _func) \
224 do { \
225 __INIT_WORK((_work), (_func), 0); \
226 } while (0)
把work_struct加入工作隊列:
432 extern bool queue_work_on(int cpu, struct workqueue_struct *wq,
433 struct work_struct *work);
434 extern bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq,
435 struct delayed_work *work, unsigned long delay);
436 extern bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq,
437 struct delayed_work *dwork, unsigned long delay);
代碼:queue_work_on() -> __queue_work(cpu, wq, work)
1313 static void __queue_work(int cpu, struct workqueue_struct *wq,
1314 struct work_struct *work)
1315 {
1316 struct pool_workqueue *pwq;
1317 struct worker_pool *last_pool;
1318 struct list_head *worklist;
1319 unsigned int work_flags;
1320 unsigned int req_cpu = cpu;
1321
1328 WARN_ON_ONCE(!irqs_disabled());
1329
1330 debug_work_activate(work);
1331
//__WQ_DRAINING這個flag表示該workqueue正在進行draining(銷燬)的操作
1333 if (unlikely(wq->flags & __WQ_DRAINING) &&
1334 WARN_ON_ONCE(!is_chained_work(wq)))
1335 return;
1336 retry:
1337 if (req_cpu == WORK_CPU_UNBOUND)
1338 cpu = raw_smp_processor_id();
1339
1340 /* pwq which will be used unless @work is executing elsewhere */ //選擇pool workqueue ;
1341 if (!(wq->flags & WQ_UNBOUND))
1342 pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
1343 else
1344 pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));
1345
1351 last_pool = get_work_pool(work);//選定worker pool;
1352 if (last_pool && last_pool != pwq->pool) {
1353 struct worker *worker;
1354
1355 spin_lock(&last_pool->lock);
1356
1357 worker = find_worker_executing_work(last_pool, work);
1358
1359 if (worker && worker->current_pwq->wq == wq) {
1360 pwq = worker->current_pwq;
1361 } else {
1362 /* meh... not running there, queue here */
1363 spin_unlock(&last_pool->lock);
1364 spin_lock(&pwq->pool->lock);
1365 }
1366 } else {
1367 spin_lock(&pwq->pool->lock);
1368 }
1378 if (unlikely(!pwq->refcnt)) {
1379 if (wq->flags & WQ_UNBOUND) {
1380 spin_unlock(&pwq->pool->lock);
1381 cpu_relax();
1382 goto retry;
1383 }
1384 /* oops */
1385 WARN_ONCE(true, "workqueue: per-cpu pwq for %s on cpu%d has 0 refcnt",
1386 wq->name, cpu);
1387 }
1388
1389 /* pwq determined, queue */
1390 trace_workqueue_queue_work(req_cpu, pwq, work);
1391
1392 if (WARN_ON(!list_empty(&work->entry))) {
1393 spin_unlock(&pwq->pool->lock);
1394 return;
1395 }
1396
1397 pwq->nr_in_flight[pwq->work_color]++;
1398 work_flags = work_color_to_flags(pwq->work_color);
1399
1400 if (likely(pwq->nr_active < pwq->max_active)) {
1401 trace_workqueue_activate_work(work);
1402 pwq->nr_active++;
1403 worklist = &pwq->pool->worklist;
1404 } else {
1405 work_flags |= WORK_STRUCT_DELAYED;
1406 worklist = &pwq->delayed_works;
1407 }
1408
1409 insert_work(pwq, work, worklist, work_flags);
1410 //具體的掛入隊列的動作是在insert_work函數中完成的;
1411 spin_unlock(&pwq->pool->lock);
1412 }
(4) work處理
最後處理是通過worker_thread來處理:
2180 do {
2181 struct work_struct *work =
2182 list_first_entry(&pool->worklist,
2183 struct work_struct, entry);
2184
2185 if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) {
2186 /* optimization path, not strictly necessary */
2187 process_one_work(worker, work);
2188 if (unlikely(!list_empty(&worker->scheduled)))
2189 process_scheduled_works(worker);
2190 } else {
2191 move_linked_works(work, &worker->scheduled, NULL);
2192 process_scheduled_works(worker);
2193 }
2194 } while (keep_working(pool));
2195
從線程池的worklist中取一個work,然後調用process_one_work處理work;