2019独角兽企业重金招聘Python工程师标准>>>

内核中提供了等待队列,作用是实现阻塞操作。比如,当一个应用程序去读取设备上的数据时,可能设备驱动中暂时没有数据,那么此时可以把当前进程suspend,等待有数据输入了,即条件满足时,在将此进程唤醒继续执行。

1. 创建一个等待队列

在Linux内核中,wait_queue_head_t代表一个等待队列,只需要定义一个wait_queue_head_t类型的变量,就表示创建一个等待队列,还需要调用如下接口来初始化此队列:

staitc wait_queue_head_t prod_wq;
init_waitqueue_head(&prod_wq);

具体看一下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;

就是一个链表和一把自旋锁,链表是用于保存等待该队列的wait_queue_t类型waiter对象(此类型对象内部的private成员保存了当前的任务对象task_struct *),自旋锁是为了保证对链表操作的原子性。这里简单的看一下wait_queue_t数据类型:

typedef struct __wait_queue wait_queue_t;
struct __wait_queue {unsigned int flags;
#define WQ_FLAG_EXCLUSIVE   0x01void *private; // 保存当前任务的task_struct对象地址wait_queue_func_t func; // 用于唤醒被挂起任务的回调函数struct list_head task_list; // 连接到wait_queue_head_t中的task_list链表
};

    2. 让当前进程开始等待

内核提供了如下的接口来让当前进程在条件不满足的情况下,阻塞等待:

wait_event(wq, condition)
wait_event_timeout(wq, condition, timeout)
wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wq, condition, timeout)

返回值如下:

1)    -ERESTARTSYS: 表示被信号激活唤醒

2)    > 0: 表示condition满足,返回值表示距离设定超时还有多久

3)    = 0: 表示超时发生

其内部实现源码都很类似,只是有些细节不太一样,这里以wait_event_interruptible()为例子,看看其源码:

#define __wait_event_interruptible(wq, condition, ret)          \
do {                                    \// 定义一个waiter对象DEFINE_WAIT(__wait);                        \\
for (;;) {                          \// 将waiter对象加入到等待链表中,并设置当前task的状态为TASK_INTERRUPTIBLEprepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);  \                                                                            if (condition)                      \break;                      \if (!signal_pending(current)) {             \// 进行任务调度,schedule();                 \continue;                   \}                           \ret = -ERESTARTSYS;                 \break;                          \
}                               \// 将waiter对象从等待链表中删除finish_wait(&wq, &__wait);                  \
} while (0)

当我们调用wait_event_interruptible()接口时,会先判断condition是否满足,如果不满足,则会suspend当前task。

这里再看一下DEFINE_WAIT宏的源码,可以发现其private成员总是保存这当前task对象的地址current,还有一个成员func也是非常重要的,保存着task被唤醒前的操作方法,这里暂不说明,待下面的wait_up唤醒等待队列时再进行分析:

#define DEFINE_WAIT(name)                       \wait_queue_t name = {                       \.private    = current,              \.func       = autoremove_wake_function,     \.task_list  = LIST_HEAD_INIT((name).task_list), \}

 3. 唤醒此等待队列上的进程:

内核提供了如下的接口:

void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_head_t *q);
void wake_up_interruptible_all(wait_queue_head_t *q);

这里以分析wake_up_interruptible()函数的源码进行说明唤醒task的原理,因为其他的唤醒过程都是类似的。最后都会调用到__wake_up_common()这个函数:

void __wake_up_common(wait_queue_head_t *q, unsigned int mode,                                                                           int nr_exclusive, int sync, 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, sync, key) &&(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)break;}
}

从上面的源码可以看出最终就是调用了等待队列q上的task_list链表上的waiter对象的func方法,在前面又提到过这个方法就是autoremove_wake_function():

int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{// 将wait对象private成员保存的task添加到run queue中,便于系统的调度int ret = default_wake_function(wait, mode, sync, key);// 将此wait对象从链表中删除if (ret)                                                                                                                             list_del_init(&wait->task_list);return ret;
}

defailt_wake_function()的源码如下,又看到我们熟悉的private成员

int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,void *key)
{return try_to_wake_up(curr->private, mode, sync);
}

try_to_wake_up()函数的源码比较长,这里就截取能体现其大致逻辑的代码进行说明:

static int try_to_wake_up(struct task_struct *p, unsigned int state, int sync)
{old_state = p->state;if (!(old_state & state)) // 进行状态的判断goto out;// 如果task对象没有处于running state,则跳到out_activate处if (unlikely(task_running(rq, p)))goto out_activate;......out_activate:schedstat_inc(p, se.nr_wakeups);if (sync)schedstat_inc(p, se.nr_wakeups_sync);if (orig_cpu != cpu)schedstat_inc(p, se.nr_wakeups_migrate);if (cpu == this_cpu)schedstat_inc(p, se.nr_wakeups_local);elseschedstat_inc(p, se.nr_wakeups_remote);update_rq_clock(rq);activate_task(rq, p, 1); // 将此task对象加入到run queuesuccess = 1;out_running:trace_sched_wakeup(rq, p);check_preempt_curr(rq, p, sync);p->state = TASK_RUNNING; // 设置task对象的状态为TASK_RUNNINGif (p->sched_class->task_wake_up)                                                                                                     p->sched_class->task_wake_up(rq, p);
out:current->se.last_wakeup = current->se.sum_exec_runtime;task_rq_unlock(rq, &flags);return success;
}static void activate_task(struct rq *rq, struct task_struct *p, int wakeup)
{if (task_contributes_to_load(p))rq->nr_uninterruptible--;enqueue_task(rq, p, wakeup);inc_nr_running(rq);
}

内核调度任务,总是从就绪列表run queue中选择优先级最高的任务来运行。等待队列的唤醒操作,实际上就是把阻塞在此等待队列上的进程,加入到run queue中,等待调度器在下次调度时对其继续运行。

4. 例子:

一个简单的例子,我们常见的生产者-消费者模型:生产者每生产一个任务,就等待消费者将此任务处理掉,然后再生产下一个任务;消费者每接收到一个任务,就将其消耗掉,并通知生产者继续生产;

#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/wait.h>#define ENTER() printk(KERN_DEBUG "%s() Enter", __func__)
#define EXIT() printk(KERN_DEBUG "%s() Exit", __func__)
#define ERR(fmt, args...) printk(KERN_ERR "%s()-%d: " fmt "\n", __func__, __LINE__, ##args)
#define DBG(fmt, args...) printk(KERN_DEBUG "%s()-%d: " fmt "\n", __func__, __LINE__, ##args)MODULE_LICENSE("GPL");struct work {char name[64];void (*work_func)(void *data);void *data;
};static void do_work(void *data)
{int num = (int)data;DBG("work num is %d", num);msleep_interruptible(1000);
}static struct task_struct *producer = NULL;
static struct task_struct *consumer = NULL;
static wait_queue_head_t prod_wq;
static wait_queue_head_t cons_wq;static struct work *work = NULL;static int producer_thr(void *arg)
{int num = 0;ENTER();while (!kthread_should_stop()) {int ret = wait_event_interruptible(prod_wq, (work == NULL));if (ret == -ERESTARTSYS) {DBG("wake up by signal");continue;}// DBG("ret = %d", ret);work = kzalloc(sizeof(struct work), GFP_KERNEL);if (!work) {ERR("kzalloc fail");break;}num++;snprintf(work->name, sizeof(work->name), "debug-work");work->work_func = do_work;work->data = (void *)num;wake_up_interruptible(&cons_wq);}EXIT();return 0;
}static int consumer_thr(void *arg)
{ENTER();wake_up_interruptible(&prod_wq);while (!kthread_should_stop()) {int ret = wait_event_interruptible(cons_wq, (work != NULL));if (ret == -ERESTARTSYS) {DBG("wait_up by signal");continue;}// DBG("ret = %d", ret);DBG("excute work: %s", work->name);work->work_func(work->data);kfree(work);work = NULL;wake_up_interruptible(&prod_wq);}EXIT();return 0;
}static __init int wq_demo_init(void)
{ENTER();init_waitqueue_head(&prod_wq);init_waitqueue_head(&cons_wq);producer = kthread_run(producer_thr, NULL, "producer-thr");if (!producer) {ERR("kthread_run fail");goto _fail;}consumer = kthread_run(consumer_thr, NULL, "consumer-thr");if (!consumer) {ERR("kthread_run fail");goto _fail;}EXIT();return 0;_fail:if (producer)kthread_stop(producer);if (consumer)kthread_stop(consumer);return -ECHILD;
}static __exit void wq_demo_exit(void)
{ENTER();if (producer)kthread_stop(producer);if (consumer)kthread_stop(consumer);EXIT();
}module_init(wq_demo_init);
module_exit(wq_demo_exit);

转载于:https://my.oschina.net/kaedehao/blog/621963

等待队列wait queue相关推荐

  1. linux等待队列wait queue

    linux内核的等待队列是在内核中运用非常广泛的数据结构,它是以双循环链表为基础的数据结构,与进程的休眠---唤醒机制紧密相连,可以用来同步对系统资源的访问.异步事件通知.跨进程通信等. 假设进程A想 ...

  2. java 线程等待队列_Java多线程学习(五)——等待通知机制

    等待通知机制的实现 方法wait()的作用是使当前线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程放到"预执行队列",并在wait()所在的代码处停止执行 ...

  3. Java面试题及答案整理( 2022最新版,持续更新)

    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~ Java面试永远是程序员迈向成功的第一个门槛,想要面试成功,各种面试题的洗礼是必不可少 ...

  4. Java面试题及答案整理 140道( 2021年持续更新)

    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~ 本人发现网上虽然有不少Java相关的面试题,但第一未必全,第二未必有答案,第三虽然有答 ...

  5. 为什么重新new两个线程线程号相同_面试官每次问我关于线程间通信方法,我都回答的很糟糕...

    线程的生命周期 废话不多写.首先我们先回顾回顾,理解下线程的生命周期,以及不同的阶段的区别: 新建状态(NEW) 当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配 ...

  6. 我工作三年了,该懂并发了(干货)

    本文的组织形式如下,主要会介绍到同步容器类,操作系统的并发工具,Java 开发工具包(只是简单介绍一下,后面会有源码分析).同步工具类有哪些. 下面我们就来介绍一下 Java 并发中都涉及哪些模块,这 ...

  7. 今天,进程告诉我线程它它它它不想活了

    来自:Java建设者 上一篇文章我们解剖了进程和线程的本质,进程和线程的实现方式,这篇文章我们来探讨它们是如何通信的,进程告诉我说线程不想活了,我不管它死活,我只想知道我是谁?进程是怎么告诉我的?进程 ...

  8. linux 内核阻塞,linux内核阻塞IO

    阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作.被挂起的进程进入休眠状态,被从调度器的运行队列移走,知道等待的条件被满足.而非阻塞的进程在不能进行设备操作时, ...

  9. java iterator如何知道数量_Java开发岗面试题基础篇(二)

    点击蓝字 关注wo们 老哥们,接上篇<Java开发岗面试题--基础篇(一)>,本期推出Java开发岗面试题--基础篇(二),来看看Java中的集合.多线程.异常体系等知识在面试中是怎么体现 ...

最新文章

  1. 品牌网络推广方案浅析网站改版时如何更好地规避降权风险?
  2. mysql navicat 组合索引_Navicat设置MySQL索引+MySQL索引知识
  3. JBoss Drools –入门
  4. 使用DOM操纵样式表
  5. 如果把钱存入余额宝时,所有人都在受益,那么谁在亏损呢?
  6. jquery判断自己是父节点的第几个子节点
  7. python将list转为数组_python如何将list中的字符转为数字
  8. webstormjs文件全部报错_springboot启动报错org.yaml.snakeyaml.error.YAMLException...
  9. 高效记忆/形象记忆(05)110数字编码表 0-9
  10. 原生ajax调用,JavaScript进阶之原生AJAX接口请求的方式
  11. 树莓派基于motion的usb摄像头监控
  12. 强智教务系统验证码识别 OpenCV
  13. 程开甲院士和他的TFDC模型
  14. 《笨方法学python》第三版 来自Percal25号行星的哥顿人
  15. 如何快速实现在网页中调用文档扫描仪 (2)
  16. 【jenkins】Synopsys Detect入门简介
  17. 商业银行业务学习(一)
  18. Python爬虫入门教程:博客园首页推荐博客排行的秘密
  19. 山东大学数据结构课程设计实验五(低风险出行系统)
  20. gitlab:修改project下载地址的默认域名和端口

热门文章

  1. 【Verilog HDL 训练】第 04 天(竞争、冒险、译码等)
  2. Python学习之路 (一)开发环境搭建
  3. 强化深度学习把医疗AI推向新的高潮
  4. 完全卸载SQL Server 2008 R2(转)
  5. UVa 11059 - Maximum Product
  6. css 行内元素设置宽高
  7. 微信公众平台开发 - 动手篇。使用weinxinFundation开始一个微信公众平台的开发
  8. 螃蟹学PHP设计模式之解释器模式
  9. iBatis 配置文件详解
  10. MATLAB 画图时插入图例