【Linux基礎系列之】中斷系統(2)-下半部

前面一篇文章分析了中斷系統的框架,這篇文章主要講中斷的下半部份的機制;


(一)概述

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)

根據不同的類型創建不同的工作隊列:

  1. WQ_UNBOUND:說明其work的處理不需要綁定在特定的CPU上執行,workqueue需要關聯一個系統中的unbound worker thread pool;

  2. WQ_FREEZABLE:標記WQ_FREEZABLE的workqueue需要參與到進程凍結的過程中,worker thread被凍結的時候,會處理完當前所有的work,一旦凍結完成,那麼就不會啓動新的work的執行,直到進程被解凍。

  3. WQ_MEM_RECLAIM:標記WQ_MEM_RECLAIM flag的work queue,系統都會創建一個rescuer thread,可以解決幾個work相互依賴的情況;

  4. WQ_HIGHPRI:說明掛入該workqueue的work是屬於高優先級的work,需要高優先級(比較低的nice value)的worker thread來處理。

  5. 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;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章