linux模块编程(三)——线程的约会completion [转]

http://blog.csdn.net/qb_2008/article/details/6837262

 

 上节中我们已经掌握了创建大量内核线程的能力,可惜线程之间还缺乏配合。要知道学习ITC(inter thread communication),和学习IPC(inter process communication)一样,不是件简单的事情。本节就暂且解释一种最简单的线程同步手段—completion。

        打开include/linux/completion.h,你就会看到completion使用的全部API。这里简单介绍一下。

 

  1. struct completion{ 
  2.     unsigned int done; 
  3.     wait_queue_head_t wait; 
  4. }; 
  5.  
  6. void init_completion(struct completion *x); 
  7. void wait_for_completion(struct completion *x); 
  8. void wait_for_completion_interruptible(struct completion *x); 
  9. void wait_for_completion_killable(struct completion *x); 
  10. unsigned long wait_for_completion_timeout(struct completion *x,  
  11.     unsigned long timeout); 
  12. unsigned long wait_for_completion_interruptible_timeout(struct completion *x,  
  13.     unsigned long timeout); 
  14. bool try_wait_for_completion(struct completion *x); 
  15. bool completion_done(struct completion *x); 
  16. void complete(struct completion *x); 
  17. void complete_all(struct completion *x); 
struct completion{
	unsigned int done;
	wait_queue_head_t wait;
};

void init_completion(struct completion *x);
void wait_for_completion(struct completion *x);
void wait_for_completion_interruptible(struct completion *x);
void wait_for_completion_killable(struct completion *x);
unsigned long wait_for_completion_timeout(struct completion *x, 
	unsigned long timeout);
unsigned long wait_for_completion_interruptible_timeout(struct completion *x, 
	unsigned long timeout);
bool try_wait_for_completion(struct completion *x);
bool completion_done(struct completion *x);
void complete(struct completion *x);
void complete_all(struct completion *x);

       首先是struct completion的结构,由一个计数值和一个等待队列组成。我们就大致明白,completion是类似于信号量的东西,用completion.done来表示资源是否可用,获取不到的线程会阻塞在completion.wait的等待队列上,直到其它线程释放completion。这样理解在实现上不错,但我认为completion不是与具体的资源绑定,而是单纯作为一种线程间同步的机制,它在概念上要比信号量清晰得多。以后会逐渐看到,线程间事件的同步大多靠completion,而资源临界区的保护大多靠信号量。所以说,completion是一种线程间的约会。

           init_completion初始化completion结构。初此之外,linux当然还有在定义变量时初始化的方法,都在completion.h中。

      wait_for_completion等待在completion上。如果加了interruptible,就表示线程等待可被外部发来的信号打断;如果加了killable,就表示线程只可被kill信号打断;如果加了timeout,表示等待超出一定时间会自动结束等待,timeout的单位是系统所用的时间片jiffies(多为1ms)。

      try_wait_for_completion则是非阻塞地获取completion。它相当于wait_for_completion_timeout调用中的timeout值为0。

      completion_done检查是否有线程阻塞在completion上。但这个API并不准确,它只是检查completion.done是否为0,为0则认为有线程阻塞。这个API并不会去检查实际的等待队列,所以用时要注意。

      complete唤醒阻塞在completion上的首个线程。

      complete_all唤醒阻塞在completion上的所有线程。它的实现手法很粗糙,把completion.done的值设为UINT_MAX/2,自然所有等待的线程都醒了。所以如果complete_all之后还要使用这个completion,就要把它重新初始化。

 

      好,completion介绍完毕,下面就来设计我们的模块吧。

      我们模拟5个周期性线程的运行。每个周期性线程period_thread的周期各不相同,但都以秒为单位,有各自的completion变量。period_thread每个周期运行一次,然后等待在自己的completion变量上。为了唤醒period_thread,我们使用一个watchdog_thread来模拟时钟,每隔1s watchdog_thread就会检查哪个period_thread下一周期是否到来,并用相应的completion唤醒线程。

      下面就动手实现吧。

1、把上节建立的kthread子目录,复制为新的completion子目录。

 

2、修改hello.c,使其内容如下。

  1. #include <linux/init.h> 
  2. #include <linux/module.h> 
  3. #include <linux/kthread.h> 
  4. #include <linux/completion.h> 
  5.  
  6. MODULE_LICENSE("Dual BSD/GPL"); 
  7.  
  8. #define PERIOD_THREAD_NUM 5 
  9.  
  10. staticint periods[PERIOD_THREAD_NUM] =  
  11.     { 1, 2, 4, 8, 16 }; 
  12.  
  13. staticstruct task_struct *period_tsks[PERIOD_THREAD_NUM]; 
  14.  
  15. staticstruct task_struct watchdog_tsk; 
  16.  
  17. staticstruct completion wakeups[PERIOD_THREAD_NUM]; 
  18.  
  19.  
  20. staticint period_thread(void *data) 
  21.     int k = (int)data; 
  22.     int count = -1; 
  23.  
  24.     do
  25.         printk("thread%d: period=%ds, count=%d\n", k, periods[k], ++count); 
  26.         wait_for_completion(&wakeups[k]); 
  27.     }while(!kthread_should_stop()); 
  28.     return count; 
  29.  
  30. staticint watchdog_thread(void *data) 
  31.     int k; 
  32.     int count = 0; 
  33.      
  34.     do
  35.         msleep(1000); 
  36.         count++; 
  37.         for(k=0; k<PERIOD_THREAD_NUM; k++){ 
  38.             if (count%periods[k] == 0) 
  39.                 complete(&wakeups[k]); 
  40.         } 
  41.     }while(!kthread_should_stop()); 
  42.     return count; 
  43.  
  44. staticint hello_init(void
  45.     int k; 
  46.  
  47.     printk(KERN_INFO "Hello, world!\n"); 
  48.  
  49.     for(k=0; k<PERIOD_THREAD_NUM; k++){ 
  50.         init_completion(&wakeups[k]); 
  51.     } 
  52.  
  53.     watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread"); 
  54.  
  55.     if(IS_ERR(watchdog_tsk)){ 
  56.         printk(KERN_INFO "create watchdog_thread failed!\n"); 
  57.         return 1; 
  58.     } 
  59.  
  60.     for(k=0; k<PERIOD_THREAD_NUM; k++){ 
  61.         period_tsks[k] = kthread_run(period_thread, (void*)k, "period_thread%d", k); 
  62.         if(IS_ERR(period_tsks[k])) 
  63.             printk(KERN_INFO "create period_thread%d failed!\n", k); 
  64.     } 
  65.     return 0; 
  66.  
  67. staticvoid hello_exit(void
  68.     int k; 
  69.     int count[5], watchdog_count; 
  70.  
  71.     printk(KERN_INFO "Hello, exit!\n"); 
  72.     for(k=0; k<PERIOD_THREAD_NUM]; k++){ 
  73.         count[k] = 0; 
  74.         if(!IS_ERR(period_tsks[k])) 
  75.             count[k] = kthread_stop(period_tsks[k]); 
  76.     } 
  77.     watchdog_count = 0; 
  78.     if(!IS_ERR(watchdog_tsk)) 
  79.         watchdog_count = kthread_stop(watchdog_tsk); 
  80.  
  81.     printk("running total time: %ds\n", watchdog_count); 
  82.     for(k=0; k<PERIOD_THREAD_NUM; k++) 
  83.         printk("thread%d: period %d, running %d times\n", k, periods[k], count[k]); 
  84.  
  85. module_init(hello_init); 
  86. module_exit(hello_exit); 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/completion.h>

MODULE_LICENSE("Dual BSD/GPL");

#define PERIOD_THREAD_NUM 5

static int periods[PERIOD_THREAD_NUM] = 
	{ 1, 2, 4, 8, 16 };

static struct task_struct *period_tsks[PERIOD_THREAD_NUM];

static struct task_struct watchdog_tsk;

static struct completion wakeups[PERIOD_THREAD_NUM];


static int period_thread(void *data)
{
	int k = (int)data;
	int count = -1;

	do{
		printk("thread%d: period=%ds, count=%d\n", k, periods[k], ++count);
		wait_for_completion(&wakeups[k]);
	}while(!kthread_should_stop());
	return count;
}

static int watchdog_thread(void *data)
{
	int k;
	int count = 0;
	
	do{
		msleep(1000);
		count++;
		for(k=0; k<PERIOD_THREAD_NUM; k++){
			if (count%periods[k] == 0)
				complete(&wakeups[k]);
		}
	}while(!kthread_should_stop());
	return count;
}

static int hello_init(void)
{
	int k;

	printk(KERN_INFO "Hello, world!\n");

	for(k=0; k<PERIOD_THREAD_NUM; k++){
		init_completion(&wakeups[k]);
	}

	watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread");

	if(IS_ERR(watchdog_tsk)){
		printk(KERN_INFO "create watchdog_thread failed!\n");
		return 1;
	}

	for(k=0; k<PERIOD_THREAD_NUM; k++){
		period_tsks[k] = kthread_run(period_thread, (void*)k, "period_thread%d", k);
		if(IS_ERR(period_tsks[k]))
			printk(KERN_INFO "create period_thread%d failed!\n", k);
	}
	return 0;
}

static void hello_exit(void)
{
	int k;
	int count[5], watchdog_count;

	printk(KERN_INFO "Hello, exit!\n");
	for(k=0; k<PERIOD_THREAD_NUM]; k++){
		count[k] = 0;
		if(!IS_ERR(period_tsks[k]))
			count[k] = kthread_stop(period_tsks[k]);
	}
	watchdog_count = 0;
	if(!IS_ERR(watchdog_tsk))
		watchdog_count = kthread_stop(watchdog_tsk);

	printk("running total time: %ds\n", watchdog_count);
	for(k=0; k<PERIOD_THREAD_NUM; k++)
		printk("thread%d: period %d, running %d times\n", k, periods[k], count[k]);
}

module_init(hello_init);
module_exit(hello_exit);

3、编译运行模块,步骤参照前例。为保持模块的简洁性,我们仍然使用了kthread_stop结束线程,这种方法虽然简单,但在卸载模块时等待时间太长,而且这个时间会随线程个数和周期的增长而增长。

 

4、使用统一的exit_flag标志来表示结束请求,hello_exit发送completion信号给所有的周期线程,最后调用kthread_stop来回收线程返回值。这样所有的周期线程都是在被唤醒后看到exit_flag,自动结束,卸载模块时间大大缩短。下面是改进过后的hello.c,之前的那个姑且叫做hello-v1.c好了。

  1. #include <linux/init.h> 
  2. #include <linux/module.h> 
  3. #include <linux/kthread.h> 
  4. #include <linux/completion.h> 
  5.  
  6. MODULE_LICENSE("Dual BSD/GPL"); 
  7.  
  8. #define PERIOD_THREAD_NUM 5 
  9.  
  10. staticint periods[PERIOD_THREAD_NUM] =  
  11.     { 1, 2, 4, 8, 16 }; 
  12.  
  13. staticstruct task_struct *period_tsks[PERIOD_THREAD_NUM]; 
  14.  
  15. staticstruct task_struct watchdog_tsk; 
  16.  
  17. staticstruct completion wakeups[PERIOD_THREAD_NUM]; 
  18.  
  19. staticint exit_flag = 0; 
  20.  
  21. staticint period_thread(void *data) 
  22.     int k = (int)data; 
  23.     int count = -1; 
  24.  
  25.     do
  26.         printk("thread%d: period=%ds, count=%d\n", k, periods[k], ++count); 
  27.         wait_for_completion(&wakeups[k]); 
  28.     }while(!exit_flag); 
  29.     return count; 
  30.  
  31. staticint watchdog_thread(void *data) 
  32.     int k; 
  33.     int count = 0; 
  34.      
  35.     do
  36.         msleep(1000); 
  37.         count++; 
  38.         for(k=0; k<PERIOD_THREAD_NUM; k++){ 
  39.             if (count%periods[k] == 0) 
  40.                 complete(&wakeups[k]); 
  41.         } 
  42.     }while(!exit_flag); 
  43.     return count; 
  44.  
  45. staticint hello_init(void
  46.     int k; 
  47.  
  48.     printk(KERN_INFO "Hello, world!\n"); 
  49.  
  50.     for(k=0; k<PERIOD_THREAD_NUM; k++){ 
  51.         init_completion(&wakeups[k]); 
  52.     } 
  53.  
  54.     watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread"); 
  55.  
  56.     if(IS_ERR(watchdog_tsk)){ 
  57.         printk(KERN_INFO "create watchdog_thread failed!\n"); 
  58.         return 1; 
  59.     } 
  60.  
  61.     for(k=0; k<PERIOD_THREAD_NUM; k++){ 
  62.         period_tsks[k] = kthread_run(period_thread, (void*)k, "period_thread%d", k); 
  63.         if(IS_ERR(period_tsks[k])) 
  64.             printk(KERN_INFO "create period_thread%d failed!\n", k); 
  65.     } 
  66.     return 0; 
  67.  
  68. staticvoid hello_exit(void
  69.     int k; 
  70.     int count[5], watchdog_count; 
  71.  
  72.     printk(KERN_INFO "Hello, exit!\n"); 
  73.     exit_flag = 1; 
  74.     for(k=0; k<PERIOD_THREAD_NUM]; k++) 
  75.         complete_all(&wakeups[k]); 
  76.  
  77.     for(k=0; k<PERIOD_THREAD_NUM]; k++){ 
  78.         count[k] = 0; 
  79.         if(!IS_ERR(period_tsks[k])) 
  80.             count[k] = kthread_stop(period_tsks[k]); 
  81.     } 
  82.     watchdog_count = 0; 
  83.     if(!IS_ERR(watchdog_tsk)) 
  84.         watchdog_count = kthread_stop(watchdog_tsk); 
  85.  
  86.     printk("running total time: %ds\n", watchdog_count); 
  87.     for(k=0; k<PERIOD_THREAD_NUM; k++) 
  88.         printk("thread%d: period %d, running %d times\n", k, periods[k], count[k]); 
  89.  
  90. module_init(hello_init); 
  91. module_exit(hello_exit); 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/completion.h>

MODULE_LICENSE("Dual BSD/GPL");

#define PERIOD_THREAD_NUM 5

static int periods[PERIOD_THREAD_NUM] = 
	{ 1, 2, 4, 8, 16 };

static struct task_struct *period_tsks[PERIOD_THREAD_NUM];

static struct task_struct watchdog_tsk;

static struct completion wakeups[PERIOD_THREAD_NUM];

static int exit_flag = 0;

static int period_thread(void *data)
{
	int k = (int)data;
	int count = -1;

	do{
		printk("thread%d: period=%ds, count=%d\n", k, periods[k], ++count);
		wait_for_completion(&wakeups[k]);
	}while(!exit_flag);
	return count;
}

static int watchdog_thread(void *data)
{
	int k;
	int count = 0;
	
	do{
		msleep(1000);
		count++;
		for(k=0; k<PERIOD_THREAD_NUM; k++){
			if (count%periods[k] == 0)
				complete(&wakeups[k]);
		}
	}while(!exit_flag);
	return count;
}

static int hello_init(void)
{
	int k;

	printk(KERN_INFO "Hello, world!\n");

	for(k=0; k<PERIOD_THREAD_NUM; k++){
		init_completion(&wakeups[k]);
	}

	watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread");

	if(IS_ERR(watchdog_tsk)){
		printk(KERN_INFO "create watchdog_thread failed!\n");
		return 1;
	}

	for(k=0; k<PERIOD_THREAD_NUM; k++){
		period_tsks[k] = kthread_run(period_thread, (void*)k, "period_thread%d", k);
		if(IS_ERR(period_tsks[k]))
			printk(KERN_INFO "create period_thread%d failed!\n", k);
	}
	return 0;
}

static void hello_exit(void)
{
	int k;
	int count[5], watchdog_count;

	printk(KERN_INFO "Hello, exit!\n");
	exit_flag = 1;
	for(k=0; k<PERIOD_THREAD_NUM]; k++)
		complete_all(&wakeups[k]);

	for(k=0; k<PERIOD_THREAD_NUM]; k++){
		count[k] = 0;
		if(!IS_ERR(period_tsks[k]))
			count[k] = kthread_stop(period_tsks[k]);
	}
	watchdog_count = 0;
	if(!IS_ERR(watchdog_tsk))
		watchdog_count = kthread_stop(watchdog_tsk);

	printk("running total time: %ds\n", watchdog_count);
	for(k=0; k<PERIOD_THREAD_NUM; k++)
		printk("thread%d: period %d, running %d times\n", k, periods[k], count[k]);
}

module_init(hello_init);
module_exit(hello_exit);

5、编译运行改进过后的模块。可以看到模块卸载时间大大减少,不会超过1s。

 

 

      经过本节,我们学会了一种内核线程间同步的机制—completion。线程们已经开始注意相互配合,以完成复杂的工作。相信它们会越来越聪明的。

 

     

附注:

     completion的实现在kernel/sched.c中。这里的每个API都较短,实现也较为简单。completion背后的实现机制其实是等待队列。等待队列的实现会涉及到较多的调度问题,这里先简单略过。

 

posted @ 2013-09-01 16:37  kuangniuniu  阅读(360)  评论(0编辑  收藏  举报