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

时间:2015-10-30 08:50:51

标签: c multithreading linux-kernel synchronization mutex

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

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

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

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

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

编辑:

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

1 个答案:

答案 0 :(得分:14)

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

  • 硬件级别 - 通常可以在单个ASM指令中编码
  • 内核级别 - 操作系统内核执行的操作
  • 应用程序级别 - 应用程序执行的操作

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

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

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

让我们深入了解内核如何实现futex调用。实际上,futex有点复杂,因为它根据需要混合了用户级调用和内核级调用。让我们来看看' pure'互斥,仅在内核空间中实现。在很高的层面上,这将足够示范。

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

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

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