*https://lwn.net/Articles/709849/

据德

Lockdep 是一个运行时的锁有效性检查工具。它不仅仅是在死锁事件发生后上报bug,而且支持潜在的死锁检测,非常有用。细节请参考kernel文档:https://www.kernel.org/doc/Documentation/locking/lockdep-design.txt

但是目前的实现有一些限制,死锁检测只是用于一些经典锁:例如spinlock,mutex,要求锁的持有释放均是在同一个上下文中。所以,lockdep可能会漏掉一些死锁场景的检测。例如,page lock 或者completions这类同步原语,它们允许在不同上下文中释放(称之为crosslock)。本次提交的crossrelease(指在不同的上下文中释放)特性支持上述场景的死锁检测。

pagelock的一个死锁案例如下:

    CONTEXT X      CONTEXT Y         CONTEXT Z
                   mutex_lock(A)
    lock_page(B)
                   lock_page(B)	 	
                                     mutex_lock(A) /* DEADLOCK */
                                     mutex_unlock(A)
                                     unlock_page(B) /* acquired by X */
                   unlock_page(B)
                   mutex_unlock(A)

首先在上下文Y中持有mutex A,等待page lock B;上下文X首先持有page lock B,在上下文Z中释放,但Z在释放page lock B前,需要先持有metex A,形成死锁。

首先看一下lockdep的原理。定义A->B表示事件A依赖于事件B的发生,如果同时存在B->A,那么则形成了一个闭环,也就意味着死锁。比如一个依赖图如下所示: A-> B -> E <- D <- C 图中的A,B,C,D,E都是lock class,箭头表示依赖关系。当lockdep检测到一种新的依赖关系时,比如E->C加入图中,则形成了一个CDE闭环,即发生死锁。lockdep往图中添加的依赖关系越多,那么检测到死锁的概率越高。如果去除传统lockdep的约束:锁的持有、释放必须在同一个上下文中,那么可以检测到的依赖关系则将更加完善。

对比传统实现,crossrelease新增了一步commit操作,整体的操作步骤如下所示:

  1. Acquisition: 对于经典锁,与之前一样,放在task_struct的所属队列中。对于crosslock,则是添加至一个全局链表中。

  2. Commit:对于经典锁,无副作用。对于crosslock,则对上一步收集到的数据进行依赖检查。

  3. Release:对于经典锁,与之前一样。对于crosslock,则从全局链表中删除。

所以crossrelease实现的关键点在于把per task的锁队列扩展到了一个全局的队列,从而支持cross context release支持。当然,具体的实现还得考虑不同锁的申请释放时间点来判断依赖关系,看作者的patch是使用了全局递增的id来实现这一点。

结论:crossrelease特性为lockdep模块增加了更多的依赖检测场景,完善了死锁检查体系。相应的,比传统实现也会更复杂。死锁的场景很多,目前所能支持检测的场景并不完备,相关的完善工作仍在继续中。