linux模組編程(三)——線程的約會completion__區塊鏈

來源:互聯網
上載者:User

        上節中我們已經掌握了建立大量核心線程的能力,可惜線程之間還缺乏配合。要知道學習ITC(inter thread communication),和學習IPC(inter process communication)一樣,不是件簡單的事情。本節就暫且解釋一種最簡單的線程同步手段—completion。

        開啟include/linux/completion.h,你就會看到completion使用的全部API。這裡簡單介紹一下。

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,使其內容如下。

#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 5static 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好了。

#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 5static 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背後的實現機制其實是等待隊列。等待隊列的實現會涉及到較多的調度問題,這裡先簡單略過。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.