linux内核等待队列详解
https://www.cnblogs.com/xinghuo123/p/13347964.html
等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。
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);