HACK#62 使用lockdep查找系统的死锁
本节使用lockdep,检查系统是否可能发生死锁。
内核中存在自旋锁(spinlock)、Mutex、信号量(semaphore)等各种锁。这些锁用于内核中的资源的排他控制,但是如果使用方法错误,就会造成称为死锁的状态,等待锁的进程就永远无法恢复,最严重的情况会导致内核死机。
典型的情况包括自己递归地获取锁的情况,以及多个处理顺序不当引起死锁的情况。例如,下面就是递归死锁的例子。
int func1(struct object*a)
{
mutex_lock(&a->lock);
……
ret=func1(a);
……
mutex_unlock(&a->lock);
}
下面是AB-BA死锁的例子。
proc1(){
mutex_lock(&lockA);
mutex_lock(&lockB);
……
}
proc2(){
mutex_lock(&lockB);
mutex_lock(&lockA);
……
}
lockdep的正式名称为“Runtime locking correctness validator”,其负责检查内核运行时锁之间的依存性和使用方法,检测系统发生死锁的可能性。
lockdep的结构
lockdep记录内核启动后各个锁的获取状态(中断)、锁之间的获取顺序,将其作为锁的规则,对于这个规则以外的锁显示警告。
中断处理和单个锁
一般来说,递归死锁很容易就能通过程序的运行发现,在开发过程中也并不是非常难以发现。但是在编写操作系统时,中断处理是非常复杂的。也就是说,执行原来的程序时,处理过程中有时会收到硬件发出的中断(hardirq)或者软件中断(softirq)。在这些中断处理程序的内部获取与原来的程序相同的锁,就有可能发生意想不到的死锁。特别是像Linux这样,多个开发人员对一个程序进行增量式(incremental)开发时,就有可能由于相互之间未能沟通好而造成这样的死锁。
为了避免产生这样的死锁,对于中断处理程序中使用的锁,一般会在原来的程序上禁止hardirp和softirq以避免中断处理的运行。lockdep记录在获取各锁时的禁止中断状态,在softirq或hardirq未禁止的地方获取的锁一旦用在禁止softirq或hardirq的地方,就会发出警告。像这样在不同情况下使用锁,很有可能是因为某一个锁的使用方法发生错误。另外,也因为禁止hardirq或softirq后使用的锁,一般在它们的中断处理程序内部使用的可能性较高。
在lockdep中,根据获取锁时的情况,将这些锁进行了如下的分类(见表7-20)。
实际上禁止hardirq时softirq也会自动禁止,因此softirq-unsafe的意义与hardirg-unsafe相同,不存在仅允许softirq的情况。
多个锁之间的依存关系
另一方面,多个锁之间也有可能发生死锁。例如,称为AB-BA锁的死锁就非常有名,这是在某个处理路径与另一个处理路径上两个锁的获取顺序不同的情况下发生的。这样的处理看起来不太可能发生,但其实像Linux这样必须在别人制作的子系统上加入自己的代码时,就有可能弄错API的调用顺序,造成这样的死锁。
lockdep会动态记录所有锁的依存关系(获取顺序),每次获得锁时都会与在此之前的获取模式进行比较,就可以确认会不会实际发生这样的问题。
在多个锁之间,还需要与单个锁时一样检查是否可以中断。
·获取了hardirq-safe的锁后,获取hardirq-unsafe的锁时
·获取了softirq-safe的锁后,获取softirq-unsafe的锁时
在这些情况下,第一个锁有可能在中断处理程序内部使用,但依存于后面仅在中断处理程序外部使用的锁。也就是说,在这个状态下进行处理并发生中断时,中断处理程序内有可能不获取irq-safe的锁就发生死锁。
锁的嵌套
在Linux内核内部,有时需要按照某个顺序获取相同种类的类(数据结构)的不同实例(instance)的锁。例如,从块设备(sda)机器内部的分区(sda1)来看,应当先获取块设备的锁,再获取分区的锁,才是正确的顺序。
lockdep为了把握这个顺序,准备了专用的锁API。对于mutex,要向mutex_lock_nested(mutex, subclass)的subclass传递1以上的值。subclass的值越大,嵌套的层次就一定越深(顺序必须是后获取的)。
在bdev结构的示例中,将传递给subclass的变量按如下方式定义。
enum bdev_bd_mutex_lock_class
{
BD_MUTEX_NORMAL=0,
BD_MUTEX_WHOLE=1,
BD_MUTEX_PARTITION=2
};
mutex_lock_nested(&bdev->bd_contains->bd_mutex, BD_MUTEX_PARTITION);
如果在使用BD_MUTEX_PARTITION锁定以后,再使用BD_MUTEX_WHOLE锁定,就是未按照应有的顺序锁定。
lockdep的系统开销
lockdep必须记录所有的锁定操作,并与到目前为止的执行结果进行比较,因此这是非常重要的处理。但是lockdep将锁的使用方法设置为对于锁的各个使用模式仅处理一次,从而减轻负担。因此,lockdep针对锁的每个使用模式,生成64位的散列值并记录下来,散列值相同的使用模式则从第二次以后不进行处理。