结合锁定技术
一个可以结合几种锁定技术。
所描述的使用RCU机制的无锁列表操作需要自旋锁,以确保编写者具有排他性。
后缀以相同的方式使用自旋锁。
结合锁定技术的另一个示例是我提出的修改,以使对页表条目的原子操作成为可能。
这是通过将锁定从仅依赖page_table_lock更改为原子操作的组合以及页表自旋锁的使用来实现的。
在Linux内核中,针对页表上的任何操作都需要获取page_table_spinlock以便序列化对页表的更新。
但是,当进程对内存映射进行小的更改(包括更改单个条目)时,系统也会在mmap_sem上获得区域锁定。
获取Mmap_sem是为了对内存映射进行较大的更改作为写锁定。
因此,我们可以依赖mmap_sem来保护页表条目的大规模重新映射。
因此,page_table_lock的角色仅对页面表项的少量修改必不可少。
现在可以重新定义page_table_lock的作用,以便仅在将一个有效条目替换为另一个条目的意义上防止修改页面表条目,而将空页面表条目替换为没有page_table_lock的有效条目。
然后,还必须确保即使保留了mmap_sem和page_table_lock的读锁,也始终可以填充空页面表项。
这意味着所有使用page_table_lock的代码现在都必须确保一个pagetable条目永远不会偶尔设置为空。
同样,如果页表条目为空,则使用page_table_lock的代码不得假定页表条目将为空,而必须使用原子操作替换值,以防止其他处理器同时更改该值而不获取该锁。
现在,保持page_table_lock仅可确保有效的页面条目不会更改。它不再保护防止填充空页表条目。
这种争用问题的解决方案允许从匿名故障处理程序中删除对page_table_lock的使用,这将使页面故障处理程序线性扩展,如图所示。
- llustration2 页面故障时间,以每页毫秒为单位,随着处理器数量的增加。
修改锁的语义要求更改内存子系统中重要锁的处理方式,并且通常将其视为侵入性更改。
到目前为止,进行此处建议的修改的补丁尚未被接受并包含在Linux内核中。
Linux中未使用的锁定方法
随着时间的推移,已经提出了各种各样的方法来解决争用问题。
这些范围从简单的退避算法到复杂的排队逻辑。
显而易见,第一个措施一直是退避算法(back off algorithm),因为在大型NUMA系统中遇到的主要问题是NUMA互连上的争用。
后退算法使互连保持自由,并允许进行有用的工作。
互连链路是网络的一种,这里的明显选择是使用指数以太网样式的退避算法。 然而,这带来了等待时间长的风险。
因此,需要对允许的最大退避期限设置上限。
有关锁定方法的详尽列表,请参阅参考书目中提到的Radovic的著作。
我们在这里只讨论两种方法。
MCS队列锁是因为它们经常被提及,而硬件特定的锁是因为它们可以带来一些希望。
队列锁
经常提到队列锁,因为队列锁可以通过序列化锁获取来解决争用问题。
队列锁允许对尝试获取该锁的进程进行适当的排序和优先级排序。
简单的自旋锁实现有利于最快。
谁先抢锁就可以先行。
远程节点可能是不利的。
队列锁可以确保按正确的顺序获取锁,以确保所有进程按顺序获取锁。
队列锁允许公平锁获取。
队列锁还可以确保每个处理器在不同的高速缓存行上旋转,从而大大减少了高速缓存行的跳动。但是,队列锁通常比简单的旋转锁需要更多的工作,并且会降低系统在无竞争情况下的运行速度。
已经努力将队列锁与自旋锁机制结合在一起,以简化无争议的情况。
基于队列的锁定方法中最广为人知的是MCS锁定。 JohnStultz在2002年完成了Linux的实现。
队列锁定的问题在于必须管理处理器列表。
这会严重损害无竞争的锁情况,并且由于复杂的列表处理,通常还会导致争用期间的性能降低。
我们不想用我们的修改来伤害这种无可争议的情况,我们也不想承受做复杂的列表处理的麻烦,而这又可能需要它自己的锁定方案来在多个处理器之间同步列表。
硬件专用锁
自旋锁的主要性能限制是由于使用MESI方案来协商高速缓存行而引起的。
高速缓存行包含的信息比自旋锁本身所需的信息多得多,因此,如果较小的实体可以由专用硬件处理而不必处理高速缓存行的跳动,则某些基于硬件的优化解决方案可能更具效率。
硬件逻辑还可以使用与基于硬件一致性协议的基于缓存行的方法不同的算法。
但是,这些操作的性能必须能够与Itanium芯片中可用的原子图元竞争,而鉴于处理器指令的速度优势,因为它们是在 I/O 映射的硬件设备中实现的,这很难用 I/O 映射的硬件设备来完成处理器核心。