linux内核中等待队列(wait_event,wake_up...)
根据内核3.1.6版本源码、书籍和网上资料,对几个函数进行分析
介绍这几个函数,不得不先介绍等待队列wait_queue_head_t
等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。
等待队列
(一)数据结构
等待队列结构如下,因为每个等待队列都可以再中断时被修改,因此,在操作等待队列之前必须获得一个自旋锁。
- struct __wait_queue_head {
- spinlock_t lock;
- struct list_head task_list;
- };
- typedef struct__wait_queue_head wait_queue_head_t;
等待队列是通过task_list双链表来实现,其数据成员是以下数据结构:
- typedef struct__wait_queue wait_queue_t;
- struct __wait_queue {
- unsigned int flags;
- #defineWQ_FLAG_EXCLUSIVE 0x01 /* 表示等待进程想要被独占地唤醒 */
- void *private; /* 指向等待进程的task_struct实例 */
- wait_queue_func_t func; /* 用于唤醒等待进程 */
- struct list_head task_list; /* 用于链表元素,将wait_queue_t链接到wait_queue_head_t */
- };
其图如下:
等待队列如何使用哪?分两步:
1. 为了使得等待进程在一个等待队列中睡眠,需要调用函数wait_event()函数。进程进入睡眠,将控制权释放给调度器。
2. 在内核中另一处,调用wake_up()函数唤醒等待队列中的睡眠进程。
注:使用wait_event()函数使得进程睡眠;而在内核另一处有一个对应的wake_up()函数被调用。
(二)初始化等待队列元素
有两种方法初始化队列:
1. 动态初始化init_waitqueue_entry()
- static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
- {
- q->flags = 0;
- q->private = p;
- q->func = default_wake_function;
- }
2. 静态初始化DEFINE_WAIT()
- #define DEFINE_WAIT_FUNC(name, function) \
- wait_queue_t name = { \
- .private = current, \
- .func = function, \
- .task_list = LIST_HEAD_INIT((name).task_list), \
- }
- #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
其中函数autoremove_wake_function()是用来唤醒进程的,该函数不经调用default_wake_function(),还将所属等待队列成员从等待队列删除。
(三)进程睡眠
1. 通过add_wait_queue()函数将一个进程添加到等待队列,首先获得队列的自旋锁,然后调用__add_wait_queue()实现将新的等待进程添加等待队列(添加到等待队列的头部),然后解锁;代码如下:
- static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
- {
- list_add(&new->task_list, &head->task_list);
- }
另一个函数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()函数:
- /**
- * wait_event - sleep until a condition gets true
- * @wq: the waitqueue to wait on
- * @condition: a C expression for the event to wait for
- *
- * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the
- * @condition evaluates to true. The @condition is checked each time
- * the waitqueue @wq is woken up.
- *
- * wake_up() has to be called after changing any variable that could
- * change the result of the wait condition.
- */
- #define wait_event(wq, condition) \
- do { \
- if (condition) \
- break; \
- __wait_event(wq, condition); \
- } while (0)
函数__wait_event()
- #define __wait_event(wq, condition) \
- do { \
- DEFINE_WAIT(__wait); \
- \
- for (;;) { \
- prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
- if (condition) \
- break; \
- schedule(); \
- } \
- finish_wait(&wq, &__wait); \
- } while (0)
其中wq是等待进程需要加入的等待队列,而condition是通过与所等待时间有关的一个C表达式形式给出。表示,条件满足时,可以立即停止处理。
主要工作由__wait_event()来完成:
(1) 调用DEFINE_WAIT宏创建等待队列成员;
(2) 使用一个无线循环,在循环体内,
(a) 调用prepare_to_wait()使得进程在等待队列上等待,并将进程状态置为不可中断TASK_UNINTERRUPTIBLE;
(b) 当进程被唤醒时,检查指定的条件condition是否满足,如果满足则跳出循环,否则将控制权交给调度器,然后进程继续睡眠。
(3) 调用函数finish_wait()将进程状态设置为TASK_RUNNING,并从等待队列的链表中移除对应的成员。
其他与wait_event类似的函数:
1. wait_event_interrupable()函数 ,使得进程处于可中断(TASK_INTERRUPTIBLE)状态,从而睡眠进程可以通过接收信号被唤醒;
2. wait_event_timeout()函数,等待满足指定的条件,但是如果等待时间超过指定的超时限制则停止睡眠,可以防止进程永远睡眠;
3. wait_event_interruptible_timeout() 使得进程睡眠,不但可以通过接收信号被唤醒,也具有超时限制。
(四)进程唤醒
内核中虽然定义了很多唤醒等待队列中进程的函数,但是最终调用的都是__wake_up()
- #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
- #define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
- #define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
- #define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL)
- #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
- #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
- #define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
- #define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)
而__wake_up()函数在加锁之后调用的是__wake_up_common()
- static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
- int nr_exclusive, int wake_flags, void *key)
- {
- wait_queue_t *curr, *next;
- list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
- unsigned flags = curr->flags;
- if (curr->func(curr, mode, wake_flags, key) &&
- (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
- break;
- }
- }
其中:q是等待队列,mode指定进程的状态,用于控制唤醒进程的条件,nr_exclusive表示将要唤醒的设置了WQ_FLAG_EXCLUSIVE标志的进程的数目。
然后扫描链表,调用func(注册的进程唤醒函数,默认为default_wake_function)唤醒每一个进程,直至队列为空,或者没有更多的进程被唤醒,或者被唤醒的的独占进程数目已经达到规定数目。
简单的demo:
点击(此处)折叠或打开
- /*a simple wait_queue demo
- *task_1,task_2 added into the wait_queue, if condition is 0.
- *task_3 change condition to 1, and task_1 task_2 will be wake up
- */
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/sched.h>
- #include <linux/kthread.h>
- #include <linux/delay.h>
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("cengku@gmail.com");
- static int condition;
- static struct task_struct *task_1;
- static struct task_struct *task_2;
- static struct task_struct *task_3;
- DECLARE_WAIT_QUEUE_HEAD(wq);
- static int thread_func_1(void *data)
- {
- int i = 0;
- while (i++ < 100) {
- wait_event(wq, condition == 1);
- msleep(1000);
- printk(">>>>>this task 1\n");
- }
- return 0;
- }
- static int thread_func_2(void *data)
- {
- int i = 0;
- while (i++ < 100) {
- wait_event(wq, condition == 1);
- msleep(1000);
- printk(">>>>>this task 2\n");
- }
- return 0;
- }
- static int thread_func_3(void *data)
- {
- int i = 0;
- while (i++ < 10) {
- condition = 0;
- msleep(2000);
- printk(">>>>>this task 3\n");
- condition = 1;
- wake_up(&wq);
- msleep(2000);
- }
- return 0;
- }
- static int __init mod_init(void)
- {
- printk("=====mod set up===\n");
- condition = 0;
- task_1 = kthread_run(thread_func_1, NULL, "thread%d", 1);
- if (IS_ERR(task_1))
- printk("**********create thread 1 failed\n");
- else
- printk("======success create thread 1\n");
- task_2 = kthread_run(thread_func_2, NULL, "thread%d", 2);
- if (IS_ERR(task_2))
- printk("**********create thread 2 failed\n");
- else
- printk("======success create thread 2\n");
- task_3 = kthread_run(thread_func_3, NULL, "thread%d", 3);
- if (IS_ERR(task_3))
- printk("**********create thread 3 failed\n");
- else
- printk("======success create thread 3\n");
- return 0;
- }
- static void __exit mod_exit(void)
- {
- int ret;
- if (!IS_ERR(task_1)) {
- ret = kthread_stop(task_1);
- if (ret > 0)
- printk("<<<<<<<<<thread 1="" has="" run="" %ds\n"<="" span="" style="word-wrap: break-word;">, ret);
- }
- if (!IS_ERR(task_2)) {
- ret = kthread_stop(task_2);
- if (ret > 0)
- printk("<<<<<<<<<thread 2="" has="" run="" %ds\n"<="" span="" style="word-wrap: break-word;">, ret);
- }
- if (!IS_ERR(task_3)) {
- ret = kthread_stop(task_3);
- if (ret > 0)
- printk("<<<<<<<<<thread 3="" has="" run="" %ds\n"<="" span="" style="word-wrap: break-word;">, ret);
- }
- }
- module_init(mod_init);
- module_exit(mod_exit);
Makefile:
点击(此处)折叠或打开
- KERNEL_DIR:=/lib/modules/`uname -r`/build
- PWD:=`pwd`
- obj-m:= wq_mod.o
- default:
- make -C $(KERNEL_DIR) M=$(PWD) modules
- clean:
- make -C $(KERNEL_DIR) M=$(PWD) clean
linux内核中等待队列(wait_event,wake_up...)相关推荐
- linux内核中等待队列
根据内核3.1.6版本源码.书籍和网上资料,对几个函数进行分析 介绍这几个函数,不得不先介绍等待队列wait_queue_head_t 等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程 ...
- 你真的懂Linux内核中的阻塞和异步通知机制吗?(花了五天整理,墙裂推荐!)
工科生一枚,热衷于底层技术开发,有强烈的好奇心,感兴趣内容包括单片机,嵌入式Linux,Uboot等,欢迎学习交流! 爱好跑步,打篮球,睡觉. 欢迎加我QQ1500836631(备注CSDN),一起学 ...
- Linux 内核中RAID5源码详解之守护进程raid5d
Linux 内核中RAID5源码详解之守护进程raid5d 对于一个人,大脑支配着他的一举一动:对于一支部队,指挥中心控制着它的所有活动:同样,对于内核中的RAID5,也需要一个像大脑一样的东西来支配 ...
- Linux内核中锁机制之完成量、互斥量
在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...
- Linux内核中无名管道pipe和有名管道fifo的分析
1.管道(pipe) 管道是进程间通信的主要手段之一.一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端.管道是一种特殊的文件,它不属于某一种 ...
- linux 信号量锁 内核,Linux内核中锁机制之信号量、读写信号量
在上一篇博文中笔者分析了关于内存屏障.读写自旋锁以及顺序锁的相关内容,本篇博文将着重讨论有关信号量.读写信号量的内容. 六.信号量 关于信号量的内容,实际上它是与自旋锁类似的概念,只有得到信号量的进程 ...
- linux内核中锁有哪些,Linux内核中有哪些锁
Linux内核中的各种锁 在LInux操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问.尤其是在多处理器系统上,更需 ...
- 探秘最新Linux内核中的自旋锁
一.前言 目前最新内核中的自旋锁已经进化成queued spinlock,因此需要一篇新的自旋锁文档来跟上时代.此外,本文将不再描述基本的API和应用场景,主要的篇幅将集中在具体的自旋锁实现上.顺便说 ...
- 简单谈一点linux内核中套接字的bind机制--数据结构以及端口确定
众所周知,创建一个套接字可以bind到一个特定的ip地址和端口,实际上套接字这一概念代表了TCP/IP协议栈的应用层标识,协议栈中的应用层就是通过一个ip地址和一个端口号标识的,当然这仅仅是对于TCP ...
- Linux 内核中的 Device Mapper 机制
本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...
最新文章
- 百度智能云一口气发布 14 个新产品,三大视频解决方案,产品最高降价 50%
- 五年引用量最高的10大论文:Adam第一,Transfromer上榜!竟然没有ResNet?
- Smartform 动态打印选择屏幕上传的图片
- 关于地理信息系统(GIS)的几个问题
- OpenCV imgcodecs写入的实例(附完整代码)
- linux 多域名绑定
- 比特币到底是不是货币?
- asp.net ajax1.0基础回顾(七):综合应用
- [渝粤教育] 西南科技大学 高频电子线路 在线考试复习资料2021版
- 前端学习(1734):前端系列javascript之添加动画
- 阶乘的java编程_java编程求n的阶乘
- 最齐全的骨头VRay材质球素材,速来收藏
- PS中新建文件的一些常用预设信息
- (可远程)开源手机app控制c51单片机,附微信小程序控制
- cdrx8如何批量导出jpg_cdr怎么保存jpg格式
- 【RSLogix5000】—(1.1)—厂房ControlLogix系统介绍(硬件介绍)——原理
- 一个屌丝程序猿的人生(五十)
- odbc配置mysql SSL报错_odbc数据库的安装
- 薛非《品悟C-抛弃C程序设计中的谬误与恶习》读后感part1【转】
- 人脸识别之FaceNet