Linux内核机制之等待队列

2023-11-01

Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在中,等待队列在源代码树中,这是一个通过连接的典型双循环链表,如下图所示。

  在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。由于我们只需要对队列进行添加和删除操作,并不会修改其中的对象(等待队列项),因此,我们只需要提供一把保护整个基础设施和所有对象的锁,这把锁保存在等待队列头中,为wq_lock_t类型。在实现中,可以支持读写锁(rwlock)或自旋锁(spinlock)两种类型,通过一个宏定义来切换。如果使用读写锁,将wq_lock_t定义为rwlock_t类型;如果是自旋锁,将wq_lock_t定义为spinlock_t类型。无论哪种情况,分别相应设置wq_read_lock、wq_read_unlock、 wq_read_lock_irqsave、wq_read_unlock_irqrestore、wq_write_lock_irq、 wq_write_unlock、wq_write_lock_irqsave和wq_write_unlock_irqrestore等宏。

  一、定义:

  /include/linux/wait.h

  struct __wait_queue_head {

  spinlock_t lock;

  struct list_head task_list;

  };

  typedef struct __wait_queue_head wait_queue_head_t;

  二、作用:

  在内核里面,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。可以使用等待队列在实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问等。

  三、字段详解:

  1、spinlock_t lock;

  在对task_list与操作的过程中,使用该锁实现对等待队列的互斥访问。

  2、srtuct list_head_t task_list;

  双向循环链表,存放等待的进程。

  三、操作:

  1、定义并初始化等待队列头:

  (1)

  wait_queue_head_t my_queue;

  init_waitqueue_head(&my_queue);

  直接定义并初始化。init_waitqueue_head()函数会将自旋锁初始化为未锁,等待队列初始化为空的双向循环链表。

  (2)

  DECLARE_WAIT_QUEUE_HEAD(my_queue);

  定义并初始化,相当于(1)。

  2、定义等待队列项:

  DECLARE_WAITQUEUE(name,tsk);

  注意此处是定义一个wait_queue_t类型的变量name,并将其private与设置为tsk。wait_queue_t类型定义如下:

  struct __wait_queue {

  unsigned int flags;

  #define WQ_FLAG_EXCLUSIVE 0x01

  void *private;

  wait_queue_func_t func;

  struct list_head task_list;

  };

  其中flags域指明该等待的进程是互斥进程还是非互斥进程。其中0是非互斥进程,WQ_FLAG_EXCLUSIVE(0x01)是互斥进程。等待队列(wait_queue_t)和等待对列头(wait_queue_head_t)的区别是等待队列是等待队列头的成员。也就是说等待队列头的task_list域链接的成员就是等待队列类型的(wait_queue_t)。

  3、(从等待队列头中)添加/移出等待队列项:

  (1)add_wait_queue()函数:

  void fastcall 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);

  }

  设置等待的进程为非互斥进程,并将其添加进等待队列头(q)的队头中。

  void fastcall add_wait_queue_exclusive(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_tail(q, wait);

  spin_unlock_irqrestore(&q->lock, flags);

  }

  该函数也和add_wait_queue()函数功能基本一样,只不过它是将等待的进程(wait)设置为互斥进程。

  (2)remove_wait_queue()函数:

  void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

  {

  unsigned long flags;

  spin_lock_irqsave(&q->lock, flags);

  __remove_wait_queue(q, wait);

  spin_unlock_irqrestore(&q->lock, flags);

  }

  在等待的资源或事件满足时,进程被唤醒,使用该函数被从等待头中删除。

  4、等待事件:

  (1)wait_event()宏:

  #define wait_event(wq, condition) \

  do { \

  if (condition) \

  break; \

  __wait_event(wq, condition); \

  } while (0)

  #define __wait_event_timeout(wq, condition, ret) \

  do { \

  DEFINE_WAIT(__wait); \

  \

  for (;;) { \

  prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \

  if (condition) \

  break; \

  ret = schedule_timeout(ret); \

  if (!ret) \

  break; \

  } \

  finish_wait(&wq, &__wait); \

  } while (0)

  在等待会列中睡眠直到condition为真。在等待的期间,进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值. 

(2)wait_event_interruptible()函数:

  和wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态.在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是condition为真,则返回0.

  (3)wait_event_timeout()宏:

  也与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0.

  (4)wait_event_interruptible_timeout()宏:

  与wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码.

  (5) wait_event_interruptible_exclusive()宏

  同样和wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程.

  5、唤醒队列:

  (1)wake_up()函数:

  #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)

  void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode,

  int nr_exclusive, void *key)

  {

  unsigned long flags;

  spin_lock_irqsave(&q->lock, flags);

  __wake_up_common(q, mode, nr_exclusive, 0, key);

  spin_unlock_irqrestore(&q->lock, flags);

  }

  static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,

  int nr_exclusive, int sync, void *key)

  {

  struct list_head *tmp, *next;

  list_for_each_safe(tmp, next, &q->task_list) {

  wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);

  unsigned flags = curr->flags;

  if (curr->func(curr, mode, sync, key) &&

  (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

  break;

  }

  }

  唤醒等待队列.可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程,和wait_event/wait_event_timeout成对使用.

  (2)wake_up_interruptible()函数:

  #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

  和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程.,与wait_event_interruptible/wait_event_interruptible_timeout/ wait_event_interruptible_exclusive成对使用.

  (3)

  #define wake_up_all(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, 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)

  这些也基本都和wake_up/wake_up_interruptible一样.

  6、在等待队列上睡眠:

  (1)sleep_on()函数:

  void __sched sleep_on(wait_queue_head_t *q)

  {

  unsigned long flags;

  wait_queue_t wait;

  init_waitqueue_entry(&wait, current);

  current->state = TASK_UNINTERRUPTIBLE;

  sleep_on_head(q, &wait, &flags);

  schedule();

  sleep_on_tail(q, &wait, &flags);

  }

  该函数的作用是定义一个等待队列(wait),并将当前进程添加到等待队列中(wait),然后将当前进程的状态置为TASK_UNINTERRUPTIBLE,并将等待队列(wait)添加到等待队列头(q)中。之后就被挂起直到资源可以获取,才被从等待队列头(q)中唤醒,从等待队列头中移出。在被挂起等待资源期间,该进程不能被信号唤醒。

  (2)sleep_on_timeout()函数:

  long __sched sleep_on_timeout(wait_queue_head_t *q, long timeout)

  {

  unsigned long flags;

  wait_queue_t wait

  init_waitqueue_entry(&wait, current);

  current->state = TASK_UNINTERRUPTIBLE;

  sleep_on_head(q, &wait, &flags);

  timeout = schedule_timeout(timeout);

  sleep_on_tail(q, &wait, &flags);

  return timeout;

  }

  与sleep_on()函数的区别在于调用该函数时,如果在指定的时间内(timeout)没有获得等待的资源就会返回。实际上是调用schedule_timeout()函数实现的。值得注意的是如果所给的睡眠时间(timeout)小于0,则不会睡眠。该函数返回的是真正的睡眠时间。

  (3)interruptible_sleep_on()函数:

  void __sched interruptible_sleep_on(wait_queue_head_t *q)

  {

  unsigned long flags;

  wait_queue_t wait;

  init_waitqueue_entry(&wait, current);

  current->state = TASK_INTERRUPTIBLE;

  sleep_on_head(q, &wait, &flags);

  schedule();

  sleep_on_tail(q, &wait, &flags);

  }

  该函数和sleep_on()函数唯一的区别是将当前进程的状态置为TASK_INTERRUPTINLE,这意味在睡眠如果该进程收到信号则会被唤醒。

  (4)interruptible_sleep_on_timeout()函数:

  long __sched

  interruptible_sleep_on_timeout(wait_queue_head_t *q, long timeout)

  {

  unsigned long flags;

  wait_queue_t wait;

  init_waitqueue_entry(&wait, current);

  current->state = TASK_INTERRUPTIBLE;

  sleep_on_head(q, &wait, &flags);

  timeout = schedule_timeout(timeout);

  sleep_on_tail(q, &wait, &flags);

  return timeout;

  }

  类似于sleep_on_timeout()函数。进程在睡眠中可能在等待的时间没有到达就被信号打断而被唤醒,也可能是等待的时间到达而被唤醒。

  以上四个函数都是让进程在等待队列上睡眠,不过是小有诧异而已。在实际用的过程中,根据需要选择合适的函数使用就是了。例如在对软驱数据的读写中,如果设备没有就绪则调用sleep_on()函数睡眠直到数据可读(可写),在打开串口的时候,如果串口端口处于关闭状态则调用interruptible_sleep_on()函数尝试等待其打开。在声卡驱动中,读取声音数据时,如果没有数据可读,就会等待足够常的时间直到可读取。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux内核机制之等待队列 的相关文章

  • 使用inotify监控文件

    我正在使用 inotify 来监视本地文件 例如使用 root temp inotify add watch fd root temp mask 删除该文件后 程序将被阻止read fd buf bufSize 功能 即使我创建一个新的 r
  • 堆栈独立的C/C++蓝牙API?

    我想知道是否有适用于 Windows XP Vista 7 x86 和 x64 的堆栈独立 C C 蓝牙 api 我的目标是创建连接并通过蓝牙发送 接收一些时间关键的数据 我的研究给了我以下选择以及这项任务的缺点 用于蓝牙的 Windows
  • 使用 std::string 导致 Windows“找不到入口点”[重复]

    这个问题在这里已经有答案了 当我用 G C C 编译它时 include
  • 混合 VS2012 平台工具集

    我们正在从 VS2005 切换到 VS2012 update 2 我们正在构建大量 主要是控制台 本机 C 无 MFC ATL 可执行文件 它们使用几个常见的静态链接库 这些可执行文件主要在 Win7 计算机上运行 但有些也部署在较旧的 X
  • 是否有更好(更简单)的方法来获取特定域 SID?

    我被指派修改 WinForms 应用程序 主要检查登录用户是否属于特定域 这是我到目前为止所想出的 byte domainSid var directoryContext new DirectoryContext DirectoryCont
  • 输入缓冲区刷新

    考虑下面的代码 include
  • 使用 microsoft word.interop 删除 Word 文档中的空白页

    我创建了一个Word文档 它使用以下命令生成动态内容词互操作 它有一些分页符之间使用 我面临的问题是 此分页符会创建我不想向用户显示的空白页面 在某些情况下 我需要在那里添加这些分页符以维护页面布局 因此我无法考虑删除这些分页符 但我想要的
  • 如何每周日运行 crontab 作业

    我想弄清楚如何每周周日运行 crontab 作业 我认为以下应该可行 但我不确定我是否理解正确 下面的说法正确吗 5 8 6 这是 crontab 格式的解释 1 Entry Minute when the process will be
  • MSBuild 将动态生成的文件复制为项目依赖项的一部分

    我有一个自定义 msbuild 任务 它正在生成一些输出文件到 ProjectA 的输出目录 TargetDir 当前的代码是这样的
  • 如何检查是否发生溢出? [复制]

    这个问题在这里已经有答案了 可能的重复 检测 C C 中整数溢出的最佳方法 https stackoverflow com questions 199333 best way to detect integer overflow in c
  • 为什么 C# 编译的正则表达式比等效的字符串方法更快?

    每次我必须对字符串执行简单的包含或替换操作 其中我正在搜索的术语是固定值 时 我发现如果我获取示例输入并对其进行一些分析 则使用编译的正则表达式是几乎 总是比使用 String 类中的等效方法更快 我尝试过比较多种方法 hs是要搜索的 干草
  • System.Drawing.Icon 构造函数抛出“操作成功完成”异常

    在 Windows XP 计算机上 以下代码抛出 System ComponentModel Win32Exception 并显示消息 操作成功完成 System Drawing Icon icon new System Drawing I
  • g++4.9 不支持 std::align

    在学习对齐问题等时 我意识到我的 g 4 9 macports OS X 实现不支持std align 如果我尝试编译 使用 std c 11 此示例代码来自http www cplusplus com reference memory a
  • 如何在OpenGL中像这样绘制连接的带状线

    我想用以下方式绘制一系列连接线 GL LINE STRIP 我尝试过自己编写代码 但没有得到想要的结果 所以我来到这里 帮助我找出我错在哪里 这里我只给出我的draw 函数 glBegin GL LINE STRIP glVertex2f
  • 为 C++ 类播种 rand()

    我正在开发一个 C 类 它使用rand 在构造函数中 我真的希望这个班级在几乎所有方面都能照顾好自己 但我不知道在哪里播种rand 如果我播种rand 在构造函数中 每次构造我的对象类型的新实例时都会对其进行播种 因此 如果我按顺序创建 3
  • 更改预处理到文件后出现错误 1 ​​错误 LNK1104

    我必须使用预处理器 所以我改变了 配置属性 gt C gt 预处理器 gt 预处理为文件 gt 是 并得到错误 错误 1 错误 LNK1104 无法打开文件 Debug asnreal obj 这个问题的解决办法 我必须在 lib 文件的路
  • TypeScript 中 C# 类虚拟成员的等效项

    因此 在 C 中 当我创建模型类和延迟加载内容时 我会执行以下操作 public int User ID get set public int Dept ID get set 然后在我的班级稍远一点的地方 我像这样弹出我的虚拟 public
  • 可选参数代码在 .NET 3.5 中编译。为什么?

    这段代码在 VS 2010 的框架 3 5 项目中编译正常 我三次检查过 public LoggingClient string uri net msmq localhost logging 为什么 我在 C 4 规范中没有看到任何内容 文
  • Android NDK - 仅用 C/C++ 编写

    有没有一种可能的方法可以使用 C C 编写整个 NDK 应用程序 而无需像 hello jni 示例项目 HelloJni java 中那样的 Java 入门 类 以某种方式创建一个 HelloJni c 来执行相同的操作 从 Androi
  • MsBuild 在 Visual Studio Online 上找不到恢复的 NuGet 包

    我尝试构建一个存储在 Visual Studio Online 上的外部 GIT 存储库中的解决方案 它有以下步骤 1 Git 恢复 有效 2 NuGet 恢复 有效 3 构建 不起作用 查看日志时我的第一个猜测是 MsBuild 没有查找

随机推荐

  • LoadRunner性能测试基本步骤

    前言 本文旨在指导初学者使用LoadRunner进行基础的性能测试 我们在接到一个性能测试任务的时候 需要从以下几点考虑 我们的测试对象是什么 测试要求是什么 测试环境怎么部署的 业务规模如何 哪些业务点是客户最关注的等等 下面将从性能测试
  • Breakpad(跨平台crash工具)

    最近海思平台在项目测试过程中 经常出现coredump的问题 尤其是那些的不经常挂的情况 光看日志定位问题真的很难 同时生成的core文件由于各种动态链接和静态链接 分析起来实在是比较困难 为解决coredump问题 有必要提供一个跨平台的
  • Docker Volume原理及使用

    1 Volume原理 想要了解Docker Volume 首先我们需要知道Docker的文件系统是如何工作的 Docker镜像是由多个文件系统 只读层 叠加而成 当我们启动一个容器的时候 Docker会加载只读镜像层并在其上 译者注 镜像栈
  • U3D 材质转换为URP渲染

    创建URP项目 然后导入已有的package 发现预览图为品红色的 材质显示不正确 如下图所示 在项目的菜单栏选择升级渲染材质为URP渲染即可 最后的效果如下
  • HJ0427分隔输出

    要求 第一个数N为字符串的数量 第二个以后为连续输入的字符串 字符串之间用空格隔开 若字符串长度大于等于8 则每八个为一组 之间用空格隔开 切割后余下部分不满八个则用0补位 字符串长度小于8 则后补零 补满八个为止 升序输出 输入 2 ab
  • NOI20102010年,世博会在中国上海举办,吸引了数以千万计的中外游客前来参观。暑假期间小Z也来到了上海世博园, 她对世博园的拥挤早有所闻,对有的展馆甚至要排上好几个小时的队才能进入也做好了充分

    NOI2010 2010年 世博会在中国上海举办 吸引了数以千万计的中外游客前来参观 暑假期间小Z也来到了上海世博园 她对世博园的拥挤早有所闻 对有的展馆甚至要排上好几个小时的队才能进入也做好了充分准备 但为了使得自己的世博之旅更加顺利舒畅
  • ios混编flutter相机相册不可用

    问题描述 ios项目混编flutter的时候根据官网的方法添加混编代码后 有时候会出现在flutter端使用image picker插件调用相机相册时 相机相册使用不了的现象 MethodChannelFilePicker Platform
  • linux学习笔记(字符串测试 for循环)

    例题 1 传递一用户名参数给脚本 判断此用户的用户名跟其基本组的组名是否一致 并将结果显示出来 bin bash A 1 B id u 1 C id g 1 if B eq C then echo A 用户名和组名一样 else echo
  • 30道MySQL基础面试题

    每天晚上 00 00 执行 mysql 数据备份 请写出 crontab 配置项 答案 0 0 mysqldump uroot p123456 flush logs 数据库名 gt 备份文件名 每天凌晨 1 点在 data 目录新建当天日期
  • Vue3状态管理库Pinia——实现简易版购物车

    个人简介 个人主页 前端杂货铺 学习方向 主攻前端方向 正逐渐往全干发展 个人状态 研发工程师 现效力于中国工业软件事业 人生格言 积跬步至千里 积小流成江海 推荐学习 前端面试宝典 Vue2 Vue3 Vue2 3项目实战 Node js
  • 为什么不能直接通过IP访问网站

    转 http www examw com java jichu 143769 为什么不能直接通过IP访问网站 为什么不能直接通过IP访问网站 从理论上说 在IE 或其他的Web浏览器 如Firefox 的地址栏中输入IP地址 都可能访问ww
  • AD个人常用快捷键记录

    避免以后太长时间没用忘记AD快捷键 快捷键 F2 拉线 F3 过孔 F4 敷铜 F9 矩形排列 7 切割铜皮 2 线选 3 框选 6 坐标移动 N 显示隐藏飞线 A 对齐 丝印位置 R M测量两个点之间的距离 R P测量两个物体边到边的最小
  • java 项目 远程调试_Java项目远程调试

    准备一个项目 比如我有个SpringBoot项目 打包好 上传服务器 服务器 1 开启端口 8080是项目端口 8000是远程调试端口 root localhost app firewall cmd zone public add port
  • gitlab代码上传方式1

    本地代码上传到gitlab上操作流程 自我总结 1 在gitlb上新建项目 点击New project 首次将代码上传到gitlab上时 先配置Settings中的SSH Keys 2 配置SSH Keys 那么SSH Keys是怎么获得的
  • 推荐几款可以直接在手机上编程的app(包含Java、C、Python等)

    这里介绍几款可以在手机上编程的app 分别是 1 java和Android AIDE集成开发环境 2 C语言 c语言编译器 C4droid 3 python QPython3 Termux 4 CSS HTML JavaScript HTM
  • python 预测任意天后股票数据_机器学习交易——如何使用回归预测股票价格?【翻译】...

    前几天 我读了一篇关于人工智能到目前为止是如何发展的以及它将走向何方的文章 我被吓了一跳 我也很难理解作者所描绘的未来的可能性 这是人工智能在医学领域应用的可能性之一 外科医生可以用她的运动皮层控制一个机器手术刀 而不是用她的手 她可以从手
  • 团队管理那点破事,OKR绩效、核心人才、面试、技术分享、研发流程

    微信搜索 微观技术 关注这个不喜欢内卷的程序员 精彩文章汇总 GitHub https github com aalansehaiyang technology talk Star 12K 汇总java生态圈常用技术框架 开源中间件 系统架
  • Redisson分布式锁

    目录 一 分布式锁 1 分布式锁的设计原则 2 分布式锁的实现方案 二 Redisson 三 Redisson分布式锁 1 引入Redisson依赖 2 配置Redis 3 注入RedissonClient 4 使用RLock 总结 一 分
  • 如何使用iAd在应用程序中展示Banner广告

    本文由 Da杯柠檬水 微博 翻译自 AppCoda 原文 Using iAd to Display Banner Ad in Your App 虽然 你可能即将开始开发下一个超级应用程序 你的一切规划和设计都已就绪 但还是有一件事可能你并没
  • Linux内核机制之等待队列

    Linux内核的等待队列是以双循环链表为基础数据结构 与进程调度机制紧密结合 能够用于实现核心的异步事件通知机制 在中 等待队列在源代码树中 这是一个通过连接的典型双循环链表 如下图所示 在这个链表中 有两种数据结构 等待队列头 wait