进程如何知道信号量可用

How does a process know that semaphore is available

I have a very basic doubt. 

当进程等待信号量时,它会进入睡眠状态。 所以它无法轮询信号量值。

内核是否轮询信号量值并在可用时向所有等待它的进程发送信号?如果是这样,内核的开销会不会太大。

或者 signal() 调用在内部通知所有等待信号量的进程。 请让我知道这一点。

当另一个进程告诉操作系统它已经完成了信号量时,操作系统再次调度该进程。

信号量只是与 OS 调度程序交互的方式之一。

内核不轮询信号量;它不需要。每次进程调用 sem_post()(或等价物)时,都会涉及与内核的交互。内核在 sem_post() 期间所做的是查找先前在同一信号量上调用 sem_wait() 的任何进程。如果一个或多个进程调用了 sem_wait(),它会选择优先级最高的进程并对其进行调度。这显示为 sem_wait() 最终返回并且该进程继续执行。

这是如何实现的

从根本上说,内核需要实现一个叫做 "atomic test and set" 的东西。这是一个可以测试某个变量值的操作,如果满足某个条件(例如值 == 0),则变量值会改变(例如值 = 1)。如果成功,内核将做一件事(比如安排一个进程),如果不成功(因为条件 value==0 为假)内核将做一些不同的事情(比如把一个进程放在 do-not-日程表)。 'atomic' 部分是在没有其他任何东西能够同时查看和更改同一变量的情况下做出此决定。

有几种方法可以做到这一点。一种是挂起所有进程(或至少挂起内核中的所有 activity),以便没有其他进程同时测试变量的值。那不是很快。

例如,Linux 内核曾经有一个叫做 Big Kernel Lock 的东西。我不知道这是否用于处理信号量交互,但这是 OSes 过去用于原子测试和设置的那种东西。

现在 CPUs 有原子测试和设置操作码,速度快多了。好老摩托罗拉 68000 很久以前就拥有其中之一; PowerPC 和 x86 需要 CPUs 很多很多年才能获得相同类型的指令。

如果你在 linux 里面搜索,你会发现提到 futexes。 futex 是一种快速互斥体 - 它依赖于 CPU 的 test/set 指令来实现快速 mutex 信号量。

Post 硬件中的信号量

一个变体是邮箱信号量。这是信号量的一种特殊变体,在某些系统类型中非常有用,在这些系统类型中,硬件需要在 DMA 传输结束时唤醒进程。邮箱是内存中的一个特殊位置,写入时会引发中断。这可以由内核转换为信号量,因为当该中断被引发时,它会经历与它有一个叫做 sem_post() 的东西相同的动作。

这非常方便;设备可以 DMA 将大量数据存储到某个预先安排的缓冲区,然后再将少量数据 DMA 传输到邮箱。内核处理中断,如果进程先前在邮箱信号量上调用了 sem_wait(),则内核会调度它。该进程也知道这个预先安排的缓冲区,然后可以处理数据。

在实时 DSP 系统上这非常有用,因为它非常快且延迟非常低;它允许进程以很少的延迟从某些设备接收数据。相比之下,使用 read() / write() 将数据从设备传输到进程的完整设备驱动程序堆栈的替代方案非常慢。

速度

信号量交互的速度完全取决于OS。

对于像Windows和Linux这样的OS,上下文切换时间相当慢(如果不是几十微秒,也只有几微秒)。基本上这意味着当一个进程调用类似 sem_post() 的东西时,内核正在做很多不同的事情,同时它有机会最终将控制权返回给进程。这段时间它在做什么,好吧,几乎可以做任何事情!

如果一个程序使用了很多线程,并且它们之间都使用信号量快速交互,那么 sem_post()sem_wait() 会浪费大量时间。这强调一旦进程从 sem_wait() 返回,然后调用下一个 sem_post().

然而在像 VxWorks 这样的 OSes 上,上下文切换时间快如闪电。那就是在调用 sem_post() 时,内核中只有很少的代码会获得 运行。结果是信号量交互更加有效。此外,像 VxWorks 这样的 OS 是以这样一种方式编写的,以保证完成所有这些 sem_post() / sem_wait() 工作所花费的时间是 常数 .

这会影响这些系统上的软件架构。在 VxWorks 上,上下文切换很便宜,让大量线程都执行非常小的任务几乎没有什么损失。在 Windows / Linux 上,更多的是强调相反的内容。

这就是为什么像 VxWorks 这样的 OS 非常适合硬实时应用程序,而 Windows / Linux 则不然。

Linux PREEMPT_RT 补丁集部分旨在改善 linux 内核在此类操作期间的延迟。例如,它将大量设备中断处理程序(设备驱动程序)推入内核线程;这些几乎就像任何其他线程一样被安排。这个想法是减少内核正在完成的工作量(并由内核线程完成更多工作),以便它仍然必须自己完成工作(例如处理 sem_post() / sem_wait()) 花费的时间更少,并且对花费的时间更加一致。它仍然不是延迟的硬性保证,但这是一个相当不错的改进。这就是我们所说的软实时内核。但影响是机器的整体吞吐量可能会降低。

信号

信号是讨厌的、可怕的东西,它们真正妨碍了使用 sem_post() 和 sem_wait() 之类的东西。我像避开瘟疫一样避开它们。

如果您使用的是 Linux 平台并且您确实需要使用信号,请仔细阅读 signalfd (man page)。这是处理信号的好得多的方法,因为您可以选择在方便的时间接受它们(只需调用 read()),而不必在它们出现时立即处理它们。当然,如果您在程序中的任何地方都使用 epoll()select(),那么 signalfd 是正确的选择。