Linux等待队列原理与实现指南教程_linux攻略教程

当进程要获取某些资源(例如从网卡读取数据)的时候,但资源并没有准备好(例如网卡还没接收到数据),这时候内核必须切换到其他进程运行,直到资源准备好再唤醒进程。waitqueue (等待队列) 就是内核用于管理等待资源的进程,当某个进程获取的资源没有准备好的时候,可以通过调用 add_wait_queue() 函数把进程添加到 waitqueue 中,然后切

Linux等待队列原理与实现指南教程

当进程要获取某些资源(例如从网卡读取数据)的时候,但资源并没有准备好(例如网卡还没接收到数据),这时候内核必须切换到其他进程运行,直到资源准备好再唤醒进程。

Linux等待队列原理与实现指南教程_linux攻略教程

waitqueue (等待队列) 就是内核用于管理等待资源的进程,当某个进程获取的资源没有准备好的时候,可以通过调用  add_wait_queue() 函数把进程添加到  waitqueue 中,然后切换到其他进程继续执行。当资源准备好,由资源提供方通过调用  wake_up() 函数来唤醒等待的进程。

等待队列初始化

要使用 waitqueue 首先需要声明一个  wait_queue_head_t 结构的变量, wait_queue_head_t 结构定义如下:

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};

waitqueue 本质上是一个链表,而  wait_queue_head_t 结构是  waitqueue 的头部, lock 字段用于保护等待队列在多核环境下数据被破坏,而  task_list 字段用于保存等待资源的进程列表。

可以通过调用 init_waitqueue_head() 函数来初始化  wait_queue_head_t 结构,其实现如下:

void init_waitqueue_head(wait_queue_head_t *q)
{
    spin_lock_init(&q->lock);
    INIT_LIST_HEAD(&q->task_list);
}

初始化过程很简单,首先调用 spin_lock_init() 来初始化自旋锁  lock ,然后调用  INIT_LIST_HEAD() 来初始化进程链表。

向等待队列添加等待进程

要向 waitqueue 添加等待进程,首先要声明一个  wait_queue_t 结构的变量, wait_queue_t 结构定义如下:

typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int sync, void *key);

struct __wait_queue {
    unsigned int flags;
    void *private;
    wait_queue_func_t func;
    struct list_head task_list;
};

下面说明一下各个成员的作用:

  1. flags : 可以设置为  WQ_FLAG_EXCLUSIVE ,表示等待的进程应该独占资源(解决惊群现象)。

  2. private : 一般用于保存等待进程的进程描述符  task_struct 。

  3. func : 唤醒函数,一般设置为  default_wake_function() 函数,当然也可以设置为自定义的唤醒函数。

  4. task_list : 用于连接其他等待资源的进程。

可以通过调用 init_waitqueue_entry() 函数来初始化  wait_queue_t 结构变量,其实现如下:

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;
}

也可以通过调用 init_waitqueue_func_entry() 函数来初始化为自定义的唤醒函数:

static inline void init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func)
{
    q->flags = 0;
    q->private = NULL;
    q->func = func;
}

初始化完 wait_queue_t 结构变量后,可以通过调用  add_wait_queue() 函数把等待进程添加到等待队列,其实现如下:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
    unsigned long flags;

    wait->flags &= ~WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&q->lock, flags);
    __add_wait_queue(q, wait);
    spin_unlock_irqrestore(&q->lock, flags);
}

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() 函数的实现很简单,首先通过调用  spin_lock_irqsave() 上锁,然后调用  list_add() 函数把节点添加到等待队列即可。

wait_queue_head_t 结构与  wait_queue_t 结构之间的关系如下图:

Linux等待队列原理与实现指南教程_linux攻略教程

休眠等待进程

当把进程添加到等待队列后,就可以休眠当前进程,让出CPU给其他进程运行,要休眠进程可以通过以 下方式:

set_current_state(TASK_INTERRUPTIBLE);
schedule();

代码 set_current_state(TASK_INTERRUPTIBLE) 可以把当前进程运行状态设置为  可中断休眠 状态,调用  schedule() 函数可以使当前进程让出CPU,切换到其他进程执行。

唤醒等待队列

当资源准备好后,就可以唤醒等待队列中的进程,可以通过 wake_up() 函数来唤醒等待队列中的进程。 wake_up() 最终会调用  __wake_up_common() ,其实现如下:

static 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;
    }
}

可以看出,唤醒等待队列就是变量等待队列的等待进程,然后调用唤醒函数来唤醒它们。

海计划公众号
(0)
上一篇 2020/03/22 08:49
下一篇 2020/03/22 08:49

您可能感兴趣的内容

  • isotope小白帮助_过滤和排序神奇布局插件

    isotope小白帮助 官方网址:https://isotope.metafizzy.co GitHub:https://github.com/metafizzy/isotope …

    2020/03/10
  • html里a标签中href调用js的几种方法菜鸟攻略_标签入门基础教程

    我们常用的在a标签中有点击事件: a href=”javascript:js_method();”这种方法在传递this等参数的时候很容易出问题,而且javascript:协议作为a的href属性的时候不仅会导致不必要的触发window.onbeforeunload事件,在IE里面更会使gif动画图片停止播放。W3C标准不推荐在href里面执行javascr

    2020/03/29
  • javascript难点是什么?菜鸟教程下载_js知识小白攻略

    javascript难点是什么?下面本篇文章就来给大家介绍一下10个JavaScript难点,感兴趣的小伙伴们可以参考一下,希望对大家有所帮助。1、立即执行函数立即执行函数,即Immediately Invoked Function Expression (IIFE),正如它的名字,就是创建函数的同时立即执行。它没有绑定任何事件,也无需等待任何异步操作:(f

    2020/03/24
  • 比特币区块时间戳保护规则零基础入门_比特币新手入门

    人们可能认为时间对于比特币网络并不是一项重要的考虑因素,因为每个区块都引用前一个区块的哈希值,所以这些区块已经有先后顺序。比特币区块还包含交易(输入、输出和值)、推导区块头的默克尔树(Merkle Tree)和区块哈希值本身,用于证明工作量。从表面上看,这对于交易和一致性系统也许已经足够。但是,存在 调整难度 的问题如果太多的矿工加入网络,区块时间可能变得太

    2020/03/23
  • SpirationGrid攻略教程_创意灵感收录集合

    SpirationGrid攻略教程 官方网址:https://theinspirationgrid.com/ 简介描述:创意灵感收录集合 SpirationGrid是一个分享来自世…

    2020/03/10
  • 自定义 Vue 中的 v-model 双向绑定使用教程_绑定基础指南

    v-model 双向绑定实际上就是通过子组件中的 $emit 方法派发 input 事件,父组件监听 input 事件中传递的 value 值,并存储在父组件 data 中;然后父组件再通过 prop 的形式传递给子组件 value 值,再子组件中绑定 input 的 value 属性即可。我们着手实现一遍:子组件传值首先子组件需要一个 input 标签,这

    2020/03/30
  • React Diff 算法零基础入门_diff菜鸟教程下载

    React 是 facebook 出的一个前端框架. 设计的关键处就是性能问题。在本文中,我主要是介绍 Diff 算法以及 React 渲染 ,这样你可以更好的优化你的应用程序。Diff 算法再介绍算法之前,我们先来了解一下 react 是怎么工作的。var MyComponent = React.createClass({ render: function

    2020/04/06
  • css @keyframes属性 语法使用指南_动画小白教程

    css @keyframes属性 语法@keyframes是什么?@keyframes是CSS的一种规则,可以用来定义CSS动画的一个周期的行为,创建简单的动画。作用:通过 @keyframes 规则,您能够创建动画。说明:创建动画的原理是,将一套 CSS 样式逐渐变化为另一套样式。在动画过程中,您能够多次改变这套 CSS 样式。以百分比来规定改变发生的时间

    2020/03/23
  • 网络串流播放入门基础教程HTML5如何优化视频文件以便在网络上更快地串流播放_优化小白指南

    随着 Flash 的落寞 以及 移动设备的爆发性增长 ,越来越多的内容以 HTML5 视频的方式传递。在上一篇文章中你甚至能看到 使用 HTML5 视频替换 GIF 动图来优化网站访问速度 这样的技巧。然而事实上,视频格式本身就有一堆优化技巧可用来改善它们的性能表现。其中非常重要的一点,就是视频文件可以合理优化以便作为 HTML5 视频在网络上串流播放。否则

    2020/04/03
  • 前端Js排序算法:冒泡排序、 选择排序、快速排序新手入门_算法小白攻略

    冒泡排序典型的排序方法,命名来自鱼呼吸时吹出的气泡,上层的气泡总是最大的。思路:两层循环,内层循环对比相邻两个数据(j,j+1),假设j > j + 1则交换元素位置。外层循环为长度限制,在内层第一次循环完成后减少长度1(因为最后一个泡已经固定,为这个数组的最大值)function bubbleSort(arr){for(let i = 0; i < arr

    2020/03/29
  • Js先回显图片再上传菜鸟攻略_上传小白指南

    平时开发时可能会遇到上传图片问题,但如果是上传图片,多数是先进行上传然后才能回显,今天给大家介绍一个简单的上传前先对图片进行回显的方式,仅用一小部分js代码即可实现js代码部分// 关于上传封面图片的回显function getobj(obj) {return document.getElementById(obj);}function upload(f){

    2020/03/30
  • codelyzer小白攻略_一组用于Angular TypeScript项目静态代码分析的tslint规则

    codelyzer小白攻略 官方网址:http://codelyzer.com/ GitHub:https://github.com/mgechev/codelyzer 简介描述:…

    2020/03/07
  • JS事件循环机制入门基础_机制菜鸟教程下载

    js是一门单线程的编程语言,也就是说js在处理任务的时候,所有任务只能在一个线程上排队被执行,那如果某一个任务耗时比较长呢?总不能等到它执行结束再去执行下一个。所以在线程之内,又被分为了两个队列:同步任务队列异步任务队列举个例子来说:比如你去银行办理业务,都需要领号排队。银行柜员一个个办理业务,这时这个柜员就相当于一个js线程,客户排的队就相当于同步任务队列

    2020/03/26
  • HTTP缓存和浏览器的本地存储入门攻略_存储小白攻略

    一、HTTP缓存http请求做为影响前端性能极为重要的一环,因为请求受网络影响很大,如果网络很慢的情况下,页面很可能会空白很久。对于首次进入网站的用户可能要通过优化接口性能和接口数量来解决。但是,对于重复进入页面的用户,除了浏览器缓存,http缓存可以很大程度对已经加载过的页面进行优化。1.缓存位置从缓存位置上来看,分为4种,从上往下依次检查是否命中,如果但

    2020/03/29
  • 网站建设时要注意新广告法违反后将遭到重罚?使用说明_广告小白攻略

    随着2019年新广告法的推出,在网站宣传时一定要注意避免违禁词,网建科技小编最近接到几次客户因为网站中含有“最”“销量第一”等极端宣传字样被工商局开罚单事件。好像数额都不小从几万至几十万都有。网站建设中的几个重要部位不要出现违禁词,第一网站标题中不能含有违禁词,第二网站首页以及内页中不能含有违禁关键词,新闻内容中宣传自己的产品或服务时不要含违禁词,网站设计图

    Web前端 2020/03/23
  • 输入框失去焦点事件和按钮点击事件冲突零基础入门_冲突小白帮助

    场景是这样的:点击输入框失去焦点会触发验证方法,点击提交按钮的时候也会触发验证方法,如果用户点击输入框后点击提交按钮就会同时触发失去焦点方法和提交按钮方法,这样就会触发两次验证。我想写成只触发一次验证,在开发过程中我发现:在移动端当失去焦点和点击事件同时发生的时候,会先执行失去焦点事件,然后再执行点击事件,也就是说失去焦点事件的执行时间比点击事件快。当我按这

    2020/03/20