【发布时间】:2018-04-02 22:20:50
【问题描述】:
I have a very basic doubt.
当进程在等待信号量时,它会进入睡眠状态。 所以它不可能轮询信号量值。
内核是否轮询信号量值,如果可用,是否会向所有等待它的进程发送信号?如果是这样,对内核的开销会不会太大。
或者 signal() 调用是否在内部通知所有等待信号量的进程。 请让我知道这一点。
【问题讨论】:
I have a very basic doubt.
当进程在等待信号量时,它会进入睡眠状态。 所以它不可能轮询信号量值。
内核是否轮询信号量值,如果可用,是否会向所有等待它的进程发送信号?如果是这样,对内核的开销会不会太大。
或者 signal() 调用是否在内部通知所有等待信号量的进程。 请让我知道这一点。
【问题讨论】:
当操作系统被另一个进程告知它已经完成了信号量时,操作系统会再次调度该进程。
信号量只是与操作系统调度程序交互的一种方式。
内核不轮询信号量;它不需要。每次进程调用sem_post()(或等效)时,都涉及与内核的交互。内核在sem_post() 期间所做的是查找先前在同一信号量上调用sem_wait() 的任何进程。如果一个或多个进程调用了sem_wait(),它会选择具有最高优先级的进程并对其进行调度。这表明sem_wait() 最终返回并且该进程继续执行。
这是如何在后台实现的
从根本上说,内核需要实现一种称为“原子测试和设置”的东西。这是一种可以通过某个变量的值来测试的操作,如果满足某个条件(例如 value == 0),则变量值会被更改(例如 value = 1)。如果成功了,内核会做一件事,(比如调度一个进程),如果没有(因为条件 value==0 为假),内核会做一些不同的事情(比如把一个进程放在 do-not-日程表)。 “原子”部分是在没有其他任何东西能够同时查看和更改同一个变量的情况下做出这个决定。
有几种方法可以做到这一点。一种是暂停所有进程(或至少暂停内核中的所有活动),这样就没有其他任何东西可以同时测试变量的值。这不是很快。
例如,Linux 内核曾经有一个叫做 Big Kernel Lock 的东西。我不知道这是否用于处理信号量交互,但这是操作系统过去用于原子测试和设置的那种东西。
如今的 CPU 具有原子测试和设置操作码,速度要快得多。好的 ole' 摩托罗拉 68000 很久以前就拥有其中之一。像 PowerPC 和 x86 这样的 CPU 需要很多很多年才能获得相同类型的指令。
如果你在 linux 中搜索,你会发现提到了futexes。 futex 是一种快速互斥体 - 它依赖于 CPU 的测试/设置指令来实现快速mutex 信号量。
在硬件中发布信号量
一种变体是邮箱信号量。这是信号量的一种特殊变体,在某些系统类型中非常有用,在这些系统类型中,硬件需要在 DMA 传输结束时唤醒进程。邮箱是内存中的一个特殊位置,写入时会引发中断。这可以被内核转换为信号量,因为当该中断被引发时,它会经历与 sem_post() 相同的动作。
这非常方便;一个设备可以DMA 将大量数据发送到某个预先安排好的缓冲区,然后用一个小的DMA 传输到邮箱。内核处理中断,如果一个进程先前在邮箱信号量上调用了sem_wait(),则内核调度它。该进程也知道这个预先安排的缓冲区,然后可以处理数据。
在实时 DSP 系统上,这非常有用,因为它非常快且延迟非常低;它允许进程以非常小的延迟从某些设备接收数据。另一种方法是拥有一个完整的设备驱动程序堆栈,使用read() / write() 将数据从设备传输到进程,相比之下速度非常慢。
速度
信号量交互的速度完全取决于操作系统。
对于 Windows 和 Linux 等操作系统,上下文切换时间相当慢(如果不是几十微秒,则大约为几微秒)。基本上这意味着当一个进程调用像sem_post() 这样的东西时,内核正在做很多不同的事情,同时它有机会最终将控制权返回给进程。它在这段时间内所做的可能是,嗯,几乎任何事情!
如果一个程序使用了很多线程,并且它们都使用信号量在它们之间快速交互,那么sem_post() 和sem_wait() 会浪费大量时间。这强调了在进程从sem_wait() 返回后,在调用下一个sem_post() 之前做大量的工作。
但是在 VxWorks 等操作系统上,上下文切换时间快如闪电。也就是说,当调用 sem_post() 时,内核中运行的代码很少。结果是信号量交互更有效。此外,像 VxWorks 这样的操作系统是这样编写的,以保证完成所有这些sem_post() / sem_wait() 工作所花费的时间恒定。
这会影响一个人的软件在这些系统上的架构。在 VxWorks 上,上下文切换很便宜,让大量线程都执行非常小的任务几乎没有什么惩罚。在 Windows / Linux 上,更多的重点是相反的。
这就是为什么像 VxWorks 这样的操作系统非常适合硬实时应用程序,而 Windows / Linux 则不然。
Linux PREEMPT_RT 补丁集部分旨在改善 linux 内核在此类操作期间的延迟。例如,它将许多设备中断处理程序(设备驱动程序)推入内核线程;这些安排几乎就像任何其他线程一样。这个想法是减少内核正在完成的工作量(并由内核线程完成更多工作),以便它仍然必须自己完成的工作(例如处理sem_post() / sem_wait())花费更少时间,并且对于这需要多长时间更加一致。它仍然不是延迟的硬保证,但它是一个相当不错的改进。这就是我们所说的软实时内核。但影响是机器的整体吞吐量可能会更低。
信号
信号是令人讨厌的、可怕的东西,它们确实会妨碍使用 sem_post() 和 sem_wait() 之类的东西。我像躲避瘟疫一样避开它们。
如果您在 Linux 平台上并且必须使用信号,请认真查看 signalfd (man page)。这是一种更好的处理信号的方法,因为您可以选择在方便的时间接受它们(只需调用read()),而不必在它们出现时立即处理它们。当然,如果您在程序中的任何地方都使用epoll() 或select(),那么signalfd 是最好的选择。
【讨论】: