为什么自旋锁是 Linux 内核设计中的不错选择,而不是用户态代码中更常见的东西,例如信号量或互斥锁?

为什么自旋锁是 Linux 内核设计中的不错选择,而不是用户态代码中更常见的东西,例如信号量或互斥锁?

我知道自旋锁在 Linux 内核设计中是真正的浪费。

我想知道为什么自旋锁是 Linux 内核设计中的不错选择,而不是用户态代码中更常见的东西,例如信号量或互斥锁?

答案1

正如问题所暗示的,自旋锁是一种“浪费”,自旋锁应该只短暂持有。

自旋锁并不是同步多个线程的唯一方法。 Linux 内核中也使用互斥体/信号量,以及其他同步原语(例如等待队列、事件)。

然而,内核必须处理用户空间从未见过的情况,常见的一种情况是中断处理程序。 Linux 上的中断处理程序无法重新调度,但通常必须使用一些同步原语(例如,将工作项添加到其他线程将进一步处理的链接列表中)。由于中断处理程序无法休眠,因此它们无法使用互斥体、等待队列等。这几乎就只剩下自旋锁了。如果线程需要与中断处理程序同步访问,那么它也必须使用相同的自旋锁。

自旋锁不一定是浪费。它们针对非争用/非等待情况进行了优化,并且可以非常快速地获取和释放。在这种情况下,它们比其他同步原语更快并且涉及更少的开销。

答案2

自旋锁和另一个导致调用者阻塞并放弃 cpu 控制的构造之间的选择在很大程度上取决于执行上下文切换所需的时间(在锁定线程中保存寄存器/状态并恢复寄存器/状态)在另一个线程中)。执行此操作所需的时间以及缓存成本可能会很高。

如果使用自旋锁来保护对硬件寄存器或类似的访问,任何其他正在访问的线程在释放锁之前只需要几毫秒或更短的时间,那么更好地利用 cpu 时间来旋转等待而不是上下文切换并继续。

答案3

其他人已经回答了。我将总结使用自旋锁的情况以及使用自旋锁的规则。

1.什么时候使用自旋锁?

答:有以下几种情况。

  1. 持有锁的线程不允许休眠。
  2. 等待锁的线程不会休眠,而是在紧密循环中旋转。

如果使用得当,自旋锁可以提供比信号量更高的性能。例如:中断处理程序。

2. 自旋锁的使用规则是什么?

答:

规则 - 1:任何持有自旋锁的代码都不能以任何理由放弃处理器,除了服务中断(有时甚至不能)。所以持有自旋锁的代码不能休眠。

原因:假设持有自旋锁的驱动程序进入睡眠状态。例如:调用函数copy_from_user()copy_to_user(),或者内核抢占启动,因此更高优先级的进程将您的代码推到一边。该进程实际上放弃了持有自旋锁的 CPU。

现在我们不知道代码什么时候会释放锁。如果其他线程尝试获得相同的锁,它会旋转很长时间。在最坏的情况下,这会导致死锁。

内核抢占情况由自旋锁代码本身处理。只要内核代码持有自旋锁,相关处理器上的抢占就会被禁用。即使是单处理器系统也必须以这种方式禁用抢占。

规则 - 2:在保持自旋锁的同时禁用本地 CPU 上的中断。

原因:支持您的驱动程序采用自旋锁来控制对设备的访问,然后发出中断。这会导致中断处理程序运行。现在中断处理程序也需要锁来访问设备。如果中断处理程序在同一处理器上运行,它将开始旋转。驱动程序代码也无法运行来释放锁。所以处理器将永远旋转。

规则 - 3:自旋锁的持有时间必须尽可能短。

原因:锁保持时间过长也会导致当前处理器无法调度,这意味着更高优先级的进程可能必须等待才能获得 CPU。

因此它会影响内核延迟(进程可能必须等待调度的时间)。通常,自旋锁的持有时间应小于 CPU 在线程之间进行上下文切换所需的时间。

规则-4:如果你有信号量和自旋锁都需要使用。然后先取信号量,再取自旋锁。

答案4

自旋锁和互斥锁的使用是由于 CPU 资源不足。它们正在成为基于 8 位寄存器的冯诺依曼设计的冗余遗产,这是最糟糕的架构,在当今时代完全不合逻辑。

不幸的是,C / C++ 编译器的功能已经与硬件不成比例,这些功能根本无法被困在硬件中,而古老的片上资源至今仍然存在。在单处理器中,缓存根本无法重入超过第二级,因此耗时的环境节省和可重入 SMP 的负载仍在继续.. 未来属于 FPGA 器件,拥有优化的构建工具.. Xilinix 拥有新的 14nm 工艺,在 A9 四核和可编程门阵列之间有 3000 个互连,表中高达数百兆位的 SRAM,可利用先进的状态机设计,能够进行复杂的浮点算术/多维向量/特征表缩减..比旧的轮椅拴系编译器要多得多。

我 25 年前的硬件设计,将增强硬件集成到 DSP AD 21020 / CPU i960 接口中。很明显,增强设计解决了这款流密集型 1 米宽 160 喷嘴打印机中的许多软件问题。

邀请熟悉内核开发的人员组成一个小团队来评估/修改一个有潜力取代自旋锁/缓存未命中/可重入SMP条件的新架构。

相关内容