如何在内核和硬件/汇编级别实现conditional_wait()?

Shy*_*yam 15 c multithreading synchronization mutex linux-kernel

我理解等待条件变量的线程原子地释放锁并进入睡眠状态,直到被另一个线程的条件信号唤醒(当满足特定条件时).在它醒来之后,它以原子方式重新获得锁定(不知何故神奇地)并根据需要进行更新并解锁关键部分.

如果有人能解释如何在内核和硬件/汇编级别实现这个conditional_wait()过程,那会很棒吗?

锁如何以原子方式释放并重新获得?内核如何确保它?

这里的睡眠究竟意味着什么?这是否意味着上下文切换到另一个进程/线程?

在线程休眠期间,如何通过在内核级别实现的信号唤醒线程,以及是否为这些机制提供了任何硬件特定支持?

编辑:

似乎"futex"是管理这种等待/信号的人.缩小我的问题:futex系统如何调用等待和通知条件变量是如何实现/在低级别工作?

Ser*_*eyA 14

从高层次(因为你问这个问题,你需要高水平)它并不复杂.首先,您需要了解责任层面.基本上有3层:

  • 硬件级别 - 通常可以在单个ASM指令中编码
  • 内核级别 - 操作系统内核所做的事情
  • 应用程序级别 - 应用程序的功能

通常,这些职责并不重叠 - 内核不能只做硬件可以做的事情,硬件不能做只有内核才能做的事情.考虑到这一点,记住在锁定方面很少有硬件知道它是有用的.它几乎归结为

  • 原子算术 - 硬件可以锁定特定的内存区域(确保没有其他线程访问它),对其执行算术运算并解锁该区域.这只适用于芯片原生支持的算术(没有平方根!)和硬件原生支持的大小
  • 内存障碍或围栏 - 也就是说,在指令流中引入障碍,以便当CPU重新排序指令或使用内存缓存时,它们不会跨越这些围栏并且缓存将是新鲜的
  • 条件设置(比较和设置) - 如果是B,则将内存区域设置为值A并报告此操作的状态(是否设置)

这几乎是所有CPU都可以做到的.如您所见,此处没有futex,互斥或条件变量.这个东西是由具有CPU支持的操作的内核完成的.

让我们看一下内核如何实现futex调用的非常高的层次.实际上,futex有点复杂,因为它根据需要混合了用户级调用和内核级调用.让我们看看'纯'互斥体,仅在内核空间中实现.在较高的层面上,它将是足够的示范.

最初创建互斥锁时,内核会将内存区域与其关联.该区域将保持锁定或解锁的互斥锁值.之后,内核被要求锁定互斥锁,它首先指示CPU发出内存屏障.互斥锁必须充当屏障,以便在获取(或释放)互斥锁之后读取/写入的所有内容对其余CPU都是可见的.然后,如果它被设置为0,它使用CPU支持的比较和设置指令将内存区域值设置为1.(有更复杂的可重入互斥锁,但不要让它们与它们复杂化).CPU保证,即使多于一个线程同时尝试执行此操作,也只有一个会成功.如果操作成功,我们现在'持有互斥锁'.一旦要求内核释放互斥锁,内存区域设置为0(不需要有条件地执行此操作,因为我们知道我们持有互斥锁!)并发出另一个内存屏障.内核还会更新其表中的互斥锁状态 - 请参阅下文.

如果互斥锁定失败,内核会将线程添加到其表中,该表列出了等待特定互斥锁释放的线程.当互斥锁被释放时,内核会检查正在等待这个互斥锁的线程,以及"调度"(即准备执行)其中一个(如果有多个,哪个将被调度或唤醒取决于多种因素,在最简单的情况下,它只是随机的).调度的线程开始执行,再次锁定互斥锁(此时它可能再次失败!)并且生命周期继续.

希望它确实至少有一半意义:)

  • `sleep`表示该线程发出对内核支持的睡眠变量(例如,`nanosleep`)函数的调用.在看到该调用时,调度程序从执行中获取发出调用的线程,并在睡眠超时失效后调度下一次执行.注意,除非您在谈论真正的实时系统,否则超时后线程将不会被执行.它将在超时之前执行!Signal是内核中信号表的一个条目.一旦内核看到它,它就会调用线程中定义的信号处理函数. (2认同)