内核编程中常见的一种模式是,在当前线程之外初始化某个活动,然后等待该活动的结束;这个活动可能是,创建一个新的内核线程或者新的用户空间进程、对一个已有进程的某个请求,或者某种类型的硬件动作等;
内核提供了完成量(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 }