linux內核等待隊列詳解


等待隊列用於使得進程等待某一特定事件的發生,無需頻繁的輪詢,進程在等待周期中睡眠,當時間發生后由內核自動喚醒。

1 數據結構

1.1 等待隊列頭

等待隊列結構如下,因為每個等待隊列都可以再中斷時被修改,因此,在操作等待隊列之前必須獲得一個自旋鎖。

定義位於:linux-3.10.73\include\linux\wait.h

1 struct __wait_queue_head {
2     spinlock_t lock;
3     struct list_head task_list;
4 };
5 typedef struct __wait_queue_head wait_queue_head_t;

1.2 等待隊列

等待隊列是通過task_list雙鏈表來實現,其數據成員是以下數據結構:

1 typedef struct __wait_queue wait_queue_t;
2 
3 struct __wait_queue {
4     unsigned int flags;
5 #define WQ_FLAG_EXCLUSIVE    0x01
6     void *private;
7     wait_queue_func_t func;
8     struct list_head task_list;
9 };

關系如下:

等待隊列使用分兩步:

(1)為了使得等待進程在一個等待隊列中睡眠,需要調用函數wait_event()函數。進程進入睡眠,將控制權釋放給調度器。

(2)在內核中另一處,調用wake_up()函數喚醒等待隊列中的睡眠進程。

注:使用wait_event()函數使得進程睡眠;而在內核另一處有一個對應的wake_up()函數被調用。

2 等待隊列的初始化

有兩種方法初始化隊列,分為動態初始化和靜態初始化。

2.1 靜態初始化

1 #define DEFINE_WAIT_FUNC(name, function)                \
2     wait_queue_t name = {                        \
3         .private    = current,                \
4         .func        = function,                \
5         .task_list    = LIST_HEAD_INIT((name).task_list),    \
6     }
7 
8 #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

2.2 動態初始化

1 static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
2 {
3     q->flags = 0;
4     q->private = p;
5     q->func = default_wake_function;
6 }

其中函數autoremove_wake_function()是用來喚醒進程的,該函數不經調用default_wake_function(),還將所屬等待隊列成員從等待隊列刪除。

3 進程睡眠

通過add_wait_queue()函數將一個進程添加到等待隊列,首先獲得隊列的自旋鎖,然后調用__add_wait_queue()實現將新的等待進程添加等待隊列(添加到等待隊列的頭部),然后解鎖;代碼如下:

1 static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
2 {
3     list_add(&new->task_list, &head->task_list);
4 }

另一個函數add_wait_queue_exclusive()的含義與add_wait_queue()函數類似,但是將等待進程添加到等待隊列的尾部,並設置WQ_EXCLUSIXE標志。

使得進程在等待隊列上睡眠的另一種方法是:prepare_to_wait(),除了有add_wait_queue()函數的參數外,還要設置進程的狀態。另一個函數prepare_to_wait_exclusive()語義類似。       

通常情況下,add_wait_queue()函數不會直接使用,因為add_wait_queue()函數不與具體的邏輯相管理,單純的一個等待隊列的模型是沒有意義的,因此通常使用的是wait_event()函數:

 1 #define wait_event(wq, condition)                     \
 2 do {                                    \
 3     if (condition)                             \
 4         break;                            \
 5     __wait_event(wq, condition);                    \
 6 } while (0)
 7 
 8 #define __wait_event(wq, condition)                     \
 9 do {                                    \
10     DEFINE_WAIT(__wait);                        \
11                                     \
12     for (;;) {                            \
13         prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \
14         if (condition)                        \
15             break;                        \
16         schedule();                        \
17     }                                \
18     finish_wait(&wq, &__wait);                    \
19 } while (0)

其中wq是等待進程需要加入的等待隊列,而condition是通過與所等待時間有關的一個C表達式形式給出。表示,條件滿足時,可以立即停止處理。

主要工作由__wait_event()來完成:

(1) 調用DEFINE_WAIT宏創建等待隊列成員;

(2) 使用一個無線循環,在循環體內,

1) 調用prepare_to_wait()使得進程在等待隊列上等待,並將進程狀態置為不可中斷TASK_UNINTERRUPTIBLE;

2) 當進程被喚醒時,檢查指定的條件condition是否滿足,如果滿足則跳出循環,否則將控制權交給調度器,然后進程繼續睡眠。

(3) 調用函數finish_wait()將進程狀態設置為TASK_RUNNING,並從等待隊列的鏈表中移除對應的成員。

其他與wait_event類似的函數:

1 wait_event_interrupable()函數 ,使得進程處於可中斷(TASK_INTERRUPTIBLE)狀態,從而睡眠進程可以通過接收信號被喚醒;
2 wait_event_timeout()函數,等待滿足指定的條件,但是如果等待時間超過指定的超時限制則停止睡眠,可以防止進程永遠睡眠;
3 wait_event_interruptible_timeout() 使得進程睡眠,不但可以通過接收信號被喚醒,也具有超時限制。

4 進程喚醒

內核中雖然定義了很多喚醒等待隊列中進程的函數,但是最終調用的都是__wake_up()

1 #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
2 #define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
3 #define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
4 #define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL)
5 #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
6 #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
7 #define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
8 #define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)

而__wake_up()函數在加鎖之后調用的是__wake_up_common()

 1 static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
 2             int nr_exclusive, int wake_flags, void *key)
 3 {
 4     wait_queue_t *curr, *next;
 5   
 6     list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
 7         unsigned flags = curr->flags;
 8   
 9         if (curr->func(curr, mode, wake_flags, key) &&
10                 (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
11             break;
12     }
13 }

其中:q是等待隊列,mode指定進程的狀態,用於控制喚醒進程的條件,nr_exclusive表示將要喚醒的設置了WQ_FLAG_EXCLUSIVE標志的進程的數目。然后掃描鏈表,調用func(注冊的進程喚醒函數,默認為default_wake_function)喚醒每一個進程,直至隊列為空,或者沒有更多的進程被喚醒,或者被喚醒的的獨占進程數目已經達到規定數目。

5 進程運用等待隊列的實例

  1 /*a simple wait_queue demo
  2  *task_1,task_2 added into the wait_queue, if condition is 0.
  3  *task_3 change condition to 1, and task_1 task_2 will be wake up
  4  */
  5 
  6 #include <linux/kernel.h>
  7 #include <linux/init.h>
  8 #include <linux/module.h>
  9 #include <linux/sched.h>
 10 #include <linux/kthread.h>
 11 #include <linux/delay.h>
 12 
 13 MODULE_LICENSE("GPL");
 14 MODULE_AUTHOR("cengku@gmail.com");
 15 
 16 static int condition;
 17 static struct task_struct *task_1;
 18 static struct task_struct *task_2;
 19 static struct task_struct *task_3;
 20 
 21 DECLARE_WAIT_QUEUE_HEAD(wq);
 22 
 23 
 24 static int thread_func_1(void *data)
 25 {
 26     int i = 0;
 27     while (i++ < 100) {
 28         wait_event(wq, condition == 1);
 29         msleep(1000);
 30         printk(">>>>>this task 1\n");
 31     }
 32     return 0;
 33 }
 34 
 35 static int thread_func_2(void *data)
 36 {
 37     int i = 0;
 38     while (i++ < 100) {
 39         wait_event(wq, condition == 1);
 40         msleep(1000);
 41         printk(">>>>>this task 2\n");
 42     }
 43     return 0;
 44 }
 45 static int thread_func_3(void *data)
 46 {
 47     int i = 0;
 48     while (i++ < 10) {
 49         condition = 0;
 50         msleep(2000);
 51         printk(">>>>>this task 3\n");
 52         condition = 1;
 53         wake_up(&wq);
 54         msleep(2000);
 55     }
 56     return 0;
 57 }
 58 
 59 
 60 
 61 static int __init mod_init(void)
 62 {
 63     printk("=====mod set up===\n");
 64     condition = 0;
 65 
 66     task_1 = kthread_run(thread_func_1, NULL, "thread%d", 1);
 67     if (IS_ERR(task_1))
 68         printk("**********create thread 1 failed\n");
 69     else
 70         printk("======success create thread 1\n");
 71 
 72     task_2 = kthread_run(thread_func_2, NULL, "thread%d", 2);
 73     if (IS_ERR(task_2))
 74         printk("**********create thread 2 failed\n");
 75     else
 76         printk("======success create thread 2\n");
 77 
 78     task_3 = kthread_run(thread_func_3, NULL, "thread%d", 3);
 79     if (IS_ERR(task_3))
 80         printk("**********create thread 3 failed\n");
 81     else
 82         printk("======success create thread 3\n");
 83     return 0;
 84 }
 85 
 86 static void __exit mod_exit(void)
 87 {
 88     int ret;
 89     if (!IS_ERR(task_1)) {
 90         ret = kthread_stop(task_1);
 91         if (ret > 0)
 92             printk("<<<<<<<<, ret);
 93     }
 94         
 95     if (!IS_ERR(task_2)) {
 96         ret = kthread_stop(task_2);
 97         if (ret > 0)
 98             printk("<<<<<<<<, ret);
 99     }
100 
101     if (!IS_ERR(task_3)) {
102         ret = kthread_stop(task_3);
103         if (ret > 0)
104             printk("<<<<<<<<, ret);
105     }
106 }
107 module_init(mod_init);
108 module_exit(mod_exit);

參考博文:http://blog.chinaunix.net/uid-27714502-id-3450323.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM