內核編程中常見的一種模式是,在當前線程之外初始化某個活動,然后等待該活動的結束;這個活動可能是,創建一個新的內核線程或者新的用戶空間進程、對一個已有進程的某個請求,或者某種類型的硬件動作等;
內核提供了完成量(completion)來完成上述需求;完成量是一個輕量級的機制,它允許一個線程告訴另一個線程某個工作已經完成;為了使用完成量,代碼需要包含<linux/completion.h>;可以利用下面的宏靜態的創建和初始化完成量;
1 #define DECLARE_COMPLETION(work)
或者使用下面的方法動態的創建和初始化完成量;
1 struct completion my_completion; 2 /* 初始化函數 */ 3 static inline void init_completion(struct completion *x)
需要等待完成,可以調用下面的方法,這些方法都以wait_for_completion開頭,區別在於比如是否可以打斷,是否提供超時等;
1 extern void wait_for_completion(struct completion *); 2 extern void wait_for_completion_io(struct completion *); 3 extern int wait_for_completion_interruptible(struct completion *x); 4 extern int wait_for_completion_killable(struct completion *x); 5 extern unsigned long wait_for_completion_timeout(struct completion *x, 6 unsigned long timeout); 7 extern unsigned long wait_for_completion_io_timeout(struct completion *x, 8 unsigned long timeout); 9 extern long wait_for_completion_interruptible_timeout( 10 struct completion *x, unsigned long timeout); 11 extern long wait_for_completion_killable_timeout( 12 struct completion *x, unsigned long timeout); 13 extern bool try_wait_for_completion(struct completion *x);
實際的完成事件觸發則通過調用下面函數之一來完成;
1 extern void complete(struct completion *); 2 extern void complete_all(struct completion *);
這兩個函數在是否有多個線程在等待相同的完成事件上有所不同,complete只會喚醒一個等待線程,而complete_all允許喚醒所有等待線程;大多數情況下,只會有一個等待者,因此這兩個函數產生相同的結果;一個完成量通常是一個單次設備,也就是說,它只會被使用一次后就被丟棄;但是,完成量結構也可以重復使用,如果沒有使用complete_all,則我們可以重復使用一個完成量結構,只要那個將要觸發的事件是明確的,就不會有問題;但是如果使用了complete_all,則必須在重新使用該結構之前重新對它進行初始化;下面函數用來快速進行重新初始化;
1 static inline void reinit_completion(struct completion *x)
完成量的典型使用是在模塊退出時的內核線程終止;在這種原型中,某些驅動程序的內部工作由一個內核線程在while (1)循環中完成,當內核准備清除該模塊時,exit函數會告訴該線程退出並等待完成量;為了實現這個目的,內核包含了可用於這種線程的一個特殊函數;
1 void complete_and_exit(struct completion *comp, long code)
比如內核中下面代碼就說明這種場景:
1 static int ldlm_pools_thread_main(void *arg) 2 { 3 struct ptlrpc_thread *thread = (struct ptlrpc_thread *)arg; 4 int c_time; 5 6 thread_set_flags(thread, SVC_RUNNING); 7 wake_up(&thread->t_ctl_waitq); 8 9 CDEBUG(D_DLMTRACE, "%s: pool thread starting, process %d\n", 10 "ldlm_poold", current_pid()); 11 12 while (1) { 13 struct l_wait_info lwi; 14 15 /* 16 * Recal all pools on this tick. 17 */ 18 c_time = ldlm_pools_recalc(LDLM_NAMESPACE_CLIENT); 19 20 /* 21 * Wait until the next check time, or until we're 22 * stopped. 23 */ 24 lwi = LWI_TIMEOUT(cfs_time_seconds(c_time), 25 NULL, NULL); 26 l_wait_event(thread->t_ctl_waitq, 27 thread_is_stopping(thread) || 28 thread_is_event(thread), 29 &lwi); 30 31 if (thread_test_and_clear_flags(thread, SVC_STOPPING)) 32 break; 33 thread_test_and_clear_flags(thread, SVC_EVENT); 34 } 35 36 thread_set_flags(thread, SVC_STOPPED); 37 wake_up(&thread->t_ctl_waitq); 38 39 CDEBUG(D_DLMTRACE, "%s: pool thread exiting, process %d\n", 40 "ldlm_poold", current_pid()); 41 42 <strong>complete_and_exit(&ldlm_pools_comp, 0);</strong> 43 }
1 static void ldlm_pools_thread_stop(void) 2 { 3 if (!ldlm_pools_thread) 4 return; 5 6 thread_set_flags(ldlm_pools_thread, SVC_STOPPING); 7 wake_up(&ldlm_pools_thread->t_ctl_waitq); 8 9 /* 10 * Make sure that pools thread is finished before freeing @thread. 11 * This fixes possible race and oops due to accessing freed memory 12 * in pools thread. 13 */ 14 <strong>wait_for_completion(&ldlm_pools_comp);</strong> 15 kfree(ldlm_pools_thread); 16 ldlm_pools_thread = NULL; 17 }