常见的死锁有以下4种状况:

  1. 进程重复请求同一个锁,称为AA死锁。例如,重复请求同一个自旋锁;运用读写锁,第一次请求读锁,第二次请求写锁。
  2. 进程请求自旋锁时没有禁止硬中止,进程获取自旋锁今后,硬中止抢占,请求同一个自旋锁。
  3. 两个进程都要获取锁L1与L2,进程1持有L1,再去获取锁L2,此刻进程2持有锁L2,测验获取L1,那么进程1与进程2就会死锁,成为AB-BA死锁。
  4. 在一个处理器上进程1持有锁L1,再去获取锁L2,在另一个处理器上进程2持有锁L2,硬中止抢占进程2今后获取锁L1,这种AB-BA死锁很隐蔽。

防止AB-BA死锁最简略的办法便是界说锁的请求次序,以破坏死锁的环形等候。可是假如一个体系拥有几百个甚至几千个锁,那么没法彻底界说一切锁的请求次序,更可行的办法是在开发阶段提前发现潜在的死锁危险。内核供给的死锁检测工具lockdep用来发现内核的死锁危险。

1、运用办法

死锁检测工具lockdep装备宏如下:

  1. CONFIG_LOCKDEP:在装备菜单中看不到这个装备宏,翻开装备宏CONFIG_PROVE_LOCKING或CONFIG_DEBUG_LOCK_ALLOC会主动翻开这个装备宏。
  2. CONFIG_PROVE_LOCKING:答应内核报告死锁问题。
  3. CONFIG_DEBUG_LOCK_ALLOC:查看内核是否过错地开释被持有的锁。
  4. CONFIG_DEBUG_LOCKINIG_API_SELFTESTS:内核在初始化的过程中运转一小段自我测试程序,自我测试程序查看调试机制是否能够发现常见的锁缺陷。

2、技能原理

死锁检测工具lockdep操作的根本目标是锁类,例如结构体里边的锁是一个锁类,结构体的每个实例里边的锁是锁类的一个实例。

lockdep盯梢每个锁类的自身状况,也盯梢各个锁类之间的依靠联系,经过一系列的验证规矩,确保锁类状况和锁类之间的依靠总是正确的。锁类一旦在初度运用时被注册就会一向存在,它的一切具体实例都会相关到它。

2.1 锁类状况

lockdep为锁类界说了(4n+1)种运用历史状况,其间4表明:

  1. 该锁曾在STATE上下文被持有过
  2. 该锁曾在STATE上下文中被以读的方法持有过
  3. 该锁曾在敞开STATE的状况下被持有过
  4. 该锁曾在敞开STATE的状况下被以读的方法持有过

其间n是STATE状况的个数,STATE状况包括硬中止、软中止和reclaim_fs(__GFP_FS分配,表明答应向下调用到文件体系,假如文件体系持有锁今后运用锁今后运用标志位__GFP_FS请求内存,在内存严重不足的状况下,需求回收文件页,把修改正的文件页写回到存储设备,递归调用文件体系的函数,或许会导致死锁)。

其间的1表明该锁从前被运用过。

2.2 查看规矩

单锁状况规矩如下:

  1. 一个软中止不安全的锁类也是硬中止不安全的锁类
  2. 任何一个锁类,不或许一起是硬中止安全的和硬中止不安全的,也不或许一起是软中止安全和软中止不安全的。也便是说,硬中止安全和硬中止不安满是互斥的,软中止安全和软中止不安全也是互斥的。

多锁依靠规矩如下:

  1. 同一个锁类不能被获取两次,不然或许导致递归死锁(AA死锁);
  2. 不能以不同次序获取两个锁类,不然导致AB-BA死锁;
  3. 不答应在获取硬中止安全的锁类之后获取硬中止不安全的锁类:例如,硬中止安全的锁类或许被硬中止获取,假定处理器0上的进程首先获取硬中止安全的锁类A,然后获取硬中止不安全的锁类B;处理器1上的进程获取锁类B,硬中止抢占进程,获取锁类A,或许导致AB-BA死锁。
  4. 不答应在获取软中止安全锁类之后获取软中止不安全的锁类:软中止安全的锁类或许被软中止获取,假定处理器0上的进程获取软中止安全的锁类A,然后获取软中止不安全的锁类B;处理器1上的进程获取锁类B,软中止抢占进程获取锁类A,或许导致AB-BA死锁。

当锁的状况发生变化时,查看下面的依靠规矩:

  1. 假如锁类的状况变成硬中止安全,查看曩昔是否在获取它之后获取硬中止不安全的锁;
  2. 假如锁类的状况变成软中止安全,查看曩昔是否在获取它之后获取软中止不安全的锁;
  3. 假如锁类的状况变成硬中止不安全,查看曩昔是否在获取硬中止安全的锁之后获取它;
  4. 假如锁类的状况变成软中止不安全,查看曩昔是否在获取软中止安全的锁之后获取它;

内核有时需求获取同一个锁类的多个实例,上面的查看规矩会导致误报“重复上锁”,需求运用spin_lock_nested()这类编程接口设置子类以区同类锁。

kernel/sched/sched.h
static inline void double_lock(spinlock_t *l1, spinlock_t *l2)
{
     if (l1 > l2)
          swap(l1, l2);
     spin_lock(l1);
     spin_lock_nested(l2, SINGLE_DEPTH_NESTING); /* 宏SINGLE_DEPTH_NESTING的值是1 */
}

3、代码剖析

3.1 spin_lock_init()初始化

以自旋锁为例,自旋锁的结构体嵌入了一个数据类型为lockdep_map的成员dep_map,用来把锁实例映射到锁类。

typedef struct spinlock {
	union {
		struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
		struct {
			u8 __padding[LOCK_PADSIZE];
			struct lockdep_map dep_map;
		};
#endif
	};
} spinlock_t;
typedef struct raw_spinlock {
	arch_spinlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned int magic, owner_cpu;
	void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} raw_spinlock_t;

数据类型lockdep_map的成员key是锁类的键值,同一个锁类的一切锁实例运用相同的键值,成员class_cache[0]指向锁类的主类(即子类号为0),class_cache[1]指向锁类的子类1。

/*
 * Map the lock object (the lock instance) to the lock-class object.
 * This is embedded into specific lock instances:
 */
struct lockdep_map {
	struct lock_class_key		*key;
	struct lock_class		*class_cache[NR_LOCKDEP_CACHING_CLASSES];
	const char			*name;
#ifdef CONFIG_LOCK_STAT
	int				cpu;
	unsigned long			ip;
#endif
};

运用函数spin_lock_init()初始化自旋锁的时分,界说一个数据类型为lock_class_key的静态局部变量,运用它的地址作为锁类的键值。

#define spin_lock_init(_lock)				\
do {							\
	spinlock_check(_lock);				\
	raw_spin_lock_init(&(_lock)->rlock);		\
} while (0)
#define raw_spin_lock_init(lock)				\
do {								\
	static struct lock_class_key __key;			\
								\
	__raw_spin_lock_init((lock), #lock, &__key);		\
} while (0)
void __raw_spin_lock_init(raw_spinlock_t *lock, const char *name,
			  struct lock_class_key *key)
{
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	/*
	 * Make sure we are not reinitializing a held lock:
	 */
	debug_check_no_locks_freed((void *)lock, sizeof(*lock));
        lockdep_init_map(&lock->dep_map, name, key, 0);
#endif
	lock->raw_lock = (arch_spinlock_t)__ARCH_SPIN_LOCK_UNLOCKED;
	lock->magic = SPINLOCK_MAGIC;
	lock->owner = SPINLOCK_OWNER_INIT;
	lock->owner_cpu = -1;
}
/*
 * Initialize a lock instance's lock-class mapping info:
 */
void lockdep_init_map(struct lockdep_map *lock, const char *name,
		      struct lock_class_key *key, int subclass)
{
	int i;
	for (i = 0; i < NR_LOCKDEP_CACHING_CLASSES; i++)
		lock->class_cache[i] = NULL;
#ifdef CONFIG_LOCK_STAT
	lock->cpu = raw_smp_processor_id();
#endif
	......
	lock->name = name;
	......
	lock->key = key;
	if (unlikely(!debug_locks))
		return;
        ......
}

锁类的主要成员如下:

//lock_class:lockdep中的中心结构,维护了锁的before和after结构,便是锁之间的依靠联系.
//别的还经过链表结构维护,能够进行遍历操作,经过hash表结构进行查找操作,里边还记录锁的ip,能够经过kallsym翻译成可读方法的符号.
struct lock_class {
	//用来把锁类加入散列表,第一次请求锁的时分,需求把锁实例映射到锁类,依据锁实例的键值在散列表中查找锁类
	struct hlist_node		hash_entry;
	/用来把锁类加入全局的锁类链表
	struct list_head		lock_entry;
	//locks_after:记录从前在获取本锁类之后获取的一切锁类
        //locks_before:记录从前在获取本锁类之前获取的一切锁类
	struct list_head		locks_after, locks_before;
        //指向键值
	const struct lockdep_subclass_key *key;
	unsigned int			subclass;
	unsigned int			dep_gen_id;
	/*
	 * IRQ/softirq usage tracking bits:
	 */
	unsigned long			usage_mask;
	const struct lock_trace		*usage_traces[XXX_LOCK_USAGE_STATES];
	/*
	 * Generation counter, when doing certain classes of graph walking,
	 * to ensure that we check one node only once:
	 */
	int				name_version;
	const char			*name;
#ifdef CONFIG_LOCK_STAT
	unsigned long			contention_point[LOCKSTAT_POINTS];
	unsigned long			contending_point[LOCKSTAT_POINTS];
#endif
} __no_randomize_layout;

在进程描述符中增加了以下成员:

struct task_struct {
#ifdef CONFIG_LOCKDEP
#define MAX_LOCK_DEPTH			48UL
	u64				curr_chain_key;
        //进程持有的锁的数量
	int				lockdep_depth;
	unsigned int			lockdep_recursion;
        //表明进程持有的锁,按时间先后排列
	struct held_lock		held_locks[MAX_LOCK_DEPTH];
#endif
}

3.2 spin_lock()请求自旋锁

spin_lock()
    ↓
raw_spin_lock()
    ↓
_raw_spin_lock() @kernel/spinlock.c
    ↓
__raw_spin_lock() @include/linux/spinlock_api_smp.h
    → preempt_disable();
    → spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
            ↓
        #define lock_acquire_exclusive(l, s, t, n, i) lock_acquire(l, s, t, 0, 1, n, i)
        #define spin_acquire(l, s, t, i) lock_acquire_exclusive(l, s, t, NULL, i)
        static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
			  int trylock, int read, int check, int hardirqs_off,
			  struct lockdep_map *nest_lock, unsigned long ip,
			  int references, int pin_count)
            → __lock_acquire() 
                // __lock_acquire() 是 lockdep 死锁检测的中心,
                //一切原理中描述的死锁过错都是在这里检测的。假如犯错,最终会调用 print_xxx_bug() 函数。__lock_acquire()                    
    → LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
//调用者必须确保在调用它之前禁用 IRQ,不然咱们或许会得到一个想要获取锁的中止,这将再次进入 lockdep。
static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
			  int trylock, int read, int check, int hardirqs_off,
			  struct lockdep_map *nest_lock, unsigned long ip,
			  int references, int pin_count)
{
	struct task_struct *curr = current;
	struct lock_class *class = NULL;
	struct held_lock *hlock;
	unsigned int depth;
	int chain_head = 0;
	int class_idx;
	u64 chain_key;
	......
        //假如类不存在,则在哈希表中注册锁的类。,不然咱们查找一下。 
        //咱们将成果缓存在锁目标自身lock->class_cache中,因此哈希的实践查找应该针对每个锁目标进行一次。
	if (unlikely(!class)) {
		class = register_lock_class(lock, subclass, 0);
		if (!class)
			return 0;
	}
	......
	//将锁增加到当时进程持有的锁列表中。(咱们还没有增加深度,直到完结依靠性查看)
	depth = curr->lockdep_depth;
	......
        //lock_classes是一切lock_class集合
        //#define MAX_LOCKDEP_KEYS_BITS		13
        //#define MAX_LOCKDEP_KEYS		(1UL << MAX_LOCKDEP_KEYS_BITS)=8192
        //struct lock_class lock_classes[MAX_LOCKDEP_KEYS];
	class_idx = class - lock_classes;
        //当时进程持有的锁数量不为0
	if (depth) {
		hlock = curr->held_locks + depth - 1;
                //spin_lock时nest_lock=0
		if (hlock->class_idx == class_idx && nest_lock) {
			......
		}
	}
        //当时进程要持有的锁held_lock结构体填充
	hlock = curr->held_locks + depth;
	hlock->class_idx = class_idx;
	hlock->acquire_ip = ip;
	hlock->instance = lock;
	hlock->nest_lock = nest_lock;
	hlock->irq_context = task_irq_context(curr);
	hlock->trylock = trylock;
	hlock->read = read;
	hlock->check = check;
	hlock->hardirqs_off = !!hardirqs_off;
	hlock->references = references;
#ifdef CONFIG_LOCK_STAT
	hlock->waittime_stamp = 0;
	hlock->holdtime_stamp = lockstat_clock();
#endif
	hlock->pin_count = pin_count;
	/* Initialize the lock usage bit */
	if (!mark_usage(curr, hlock, check))
		return 0;
	......
        //查看死锁
	if (!validate_chain(curr, hlock, chain_head, chain_key))
		return 0;
	curr->curr_chain_key = chain_key;
        //当时进程要持有的锁数量加1
	curr->lockdep_depth++;
	check_chain_key(curr);
        ......
	return 1;
}
static int validate_chain(struct task_struct *curr,
			  struct held_lock *hlock,
			  int chain_head, u64 chain_key)
{
	if (!hlock->trylock && hlock->check &&
	    lookup_chain_cache_add(curr, hlock, chain_key)) {
		//查看当时task_struct的held_locks栈是否有AA锁
		int ret = check_deadlock(curr, hlock);
		if (!ret)
			return 0;
		//符号递归读取
		if (ret == 2)
			hlock->read = 2;
		//增加依靠联系
		if (!chain_head && ret != 2) {
			if (!check_prevs_add(curr, hlock))
				return 0;
		}
		graph_unlock();
	} else {
		/* after lookup_chain_cache_add(): */
		if (unlikely(!debug_locks))
			return 0;
	}
	return 1;
}
static int
check_prevs_add(struct task_struct *curr, struct held_lock *next)
{
	struct lock_trace *trace = NULL;
	int depth = curr->lockdep_depth;
	struct held_lock *hlock;
        ......
        //遍历当时进程held_locks的栈,增加和next的相相联系
	for (;;) {
		int distance = curr->lockdep_depth - depth + 1;
		hlock = curr->held_locks + depth - 1;
		if (hlock->read != 2 && hlock->check) {
                        //死锁查看,增加prev和next的相相联系
			int ret = check_prev_add(curr, hlock, next, distance,
						 &trace);
			if (!ret)
				return 0;
			if (!hlock->trylock)
				break;
		}
		depth--;
		if (!depth)
			break;
		//假如咱们进入另一个上下文,则中止搜索
		if (curr->held_locks[depth].irq_context !=
				curr->held_locks[depth-1].irq_context)
			break;
	}
	return 1;
out_bug:
	......
	return 0;
}
static int
check_prev_add(struct task_struct *curr, struct held_lock *prev,
	       struct held_lock *next, int distance,
	       struct lock_trace **const trace)
{
	struct lock_list *entry;
	int ret;
	......
	//广度优先搜索,查看是否构成环形,有环则代表死锁
	ret = check_noncircular(next, prev, trace);
	if (unlikely(ret <= 0))
		return 0;
        //hardirq-safe(-read) 锁与 hardirq-unsafe 锁 死锁判别
        //softirq-safe(-read) 锁与 softirq-unsafe 锁 死锁判别
	if (!check_irq_usage(curr, prev, next))
		return 0;
        ......
	//一切验证都经过了,没有死锁,将新锁增加到之前锁的依靠列表中
        //next lock增加到prev的locks_after链表上,不是直接挂到链表,而是新请求一个lock_list挂到locks_after上
	ret = add_lock_to_list(hlock_class(next), hlock_class(prev),
			       &hlock_class(prev)->locks_after上,
			       next->acquire_ip, distance, *trace);
	if (!ret)
		return 0;
        //prev lock增加到next的locks_before链表上
	ret = add_lock_to_list(hlock_class(prev), hlock_class(next),
			       &hlock_class(next)->locks_before,
			       next->acquire_ip, distance, *trace);
	if (!ret)
		return 0;
	return 2;
}

3.3 查看死锁的原理

假定当时请求锁类L2,函数validate_chain的查看过程如下:

  1. 调用函数check_deadlock查看重复上锁,即当时进程是否现已持有锁类L2,假如现已持有锁类L2,除非两次都请求读锁,不然存在死锁。
  2. 调用函数check_prevs_add,依据曾经记录到的锁类依靠联系查看死锁。

假定当时请求锁类L2,函数check_prevs_add针对当时进程的数组held_locks中的每个锁类L1,调用函数check_prev_add查看,查看过程如下:

  1. 调用函数check_noncircular以查看AB-BA死锁。
  • 查看锁类L1是否呈现在锁类L2的链表locks_after中,假如呈现,阐明曾经有过的请求次序是L2–>L1,而现在的请求次序是L1–>L2,存在死锁危险。
        CPU1                            CPU2
        process1                        process2
        ----                            ----
        [ L1 ]
        process1的held_locks中有L1  
                                        [ L2 ]
                                        process2的held_locks中有L2
                                        [ L1 ]
                                        process2的held_locks中有L2-->L1
                                        L1呈现在锁类L2的链表locks_after中
        [ L2 ]
        遍历process1的held_locks,
        查看发现process1中L1-->L2
        然后调用函数check_prev_add查看发现有L2-->L1(有环)
        则process1持有L1,proecss2持有L2等L1,process1又去请求L2
        *** DEADLOCK ***
  • 递归查看,针对锁类L2的链表locks_after中的每个锁类L3,查看锁类L1是否呈现在锁类L3的链表locks_after中,假如呈现,阐明存在死锁危险。
        CPU1                            CPU2                                  CPU3
        process1                        process2                              process3
        ----                            ----                                  ----
        [ L1 ]
        process1的held_locks中有L1  
                                        [ L2 ]
                                        process2的held_locks中有L2
                                                                              [ L3 ]
                                                                              process3的held_locks中有L3
                                        [ L3 ]
                                        process2的held_locks中有L2-->L3
                                        L3呈现在锁类L2的链表locks_after中   
                                                                              [ L1 ]
                                                                              process3的held_locks中有L3-->L1
                                                                              L1呈现在锁类L3的链表locks_after中
        [ L2 ]
        遍历process1的held_locks,
        查看发现process1中L1 --> L2
        然后调用函数check_prev_add经过广度优先遍历发现有L2->L3,L3->L1(有环)
        则process1持有L1,proecss2持有L2等L3,proecss3持有L3等L1,process1又去请求L2
        *** DEADLOCK ***
  1. 调用函数check_irq_usage,查看是否存在以下状况:“在获取硬中止安全的锁类之后获取硬中止不安全的锁类”或许“在获取软中止安全的锁类之后获取软中止不安全的锁类”。
  • 假如锁类L1的链表locks_before中存在硬中止安全的锁类,并且锁类L2的链表locks_after中存在硬中止不安全的锁类,那么阐明在获取硬中止安全的锁类之后获取硬中止不安全的锁类,存在死锁危险。
  1. 记录锁类的依靠联系:把锁类L2增加到锁类L1的链表locks_after中,把锁类L1增加到锁类L2的链表locks_before中。

4、运用实例

lockdep的日志非常人性化,咱们能够经过过错提示就能知道发生了哪些过错,唯一难了解的是它的状况显式,它运用了’.-+?’,下面进行摘自内核文档Documentation/locking/lockdep-design.txt

modprobe/2287 is trying to acquire lock:
(&sio_locks[i].lock){-.-...}, at: [<c02867fd>] mutex_lock+0x21/0x24            
but task is already holding lock:  
(&sio_locks[i].lock){-.-...}, at: [<c02867fd>] mutex_lock+0x21/0x24

留意大括号内的符号,一共有6个字符,别离对应STATE和STATE-read这六种(由于目前每个STATE有3种不同意义)状况,各个字符代表的意义别离如下:

  • ‘.’ 表明在进程上下文,在 irq 关闭时取得一把锁
  • ‘-‘ 表明在中止上下文,取得一把锁
  • ‘+’ 表明在 irq 翻开时取得一把锁
  • ‘?’ 表明在中止上下文,在 irq 翻开时取得一把锁

Lockdep 每次都只检测并 report 第一次犯错的当地。由于第一个报出来的或许会引发其他的危险提示,就像编译过错相同。并且,这只是一个 warning info, 在实时运转的体系中,LOG 或许一下子就被冲掉了。能够把 lockdep warning 转化为BUG_ON(),使机器在遇到死锁危险就主动重启来引起开发人员的关注,然后不放过每一个或许存在的漏洞。

下面是参阅文档中魅族实践开发遇到的lockdep报的死锁危险log:

(0)[1132:system_server]======================================================
(0)[1132:system_server][ INFO: HARDIRQ-safe -> HARDIRQ-unsafe lock order detected ]
(0)[1132:system_server]3.18.22-eng-01315-gea95810-cIb68b198-dirty #2 Tainted: G        W
(0)[1132:system_server]------------------------------------------------------
(0)[1132:system_server]system_server/1132 [HC0[0]:SC0[0]:HE0:SE1] is trying to acquire:
(0)[1132:system_server]lockdep: [ffffffc0013a6b18] (resume_reason_lock){+.+...}
(0)[1132:system_server]lockdep: , at:
(0)[1132:system_server][<ffffffc00011a2e0>] log_wakeup_reason+0x40/0x17c
(0)[1132:system_server]
and this task is already holding:
(0)[1132:system_server]lockdep: [ffffffc001401440] (__spm_lock){-.....}
(0)[1132:system_server]lockdep: , at:
(0)[1132:system_server][<ffffffc000492164>] spm_go_to_sleep+0x200/0x948
(0)[1132:system_server]which would create a new lock dependency:
(0)[1132:system_server] (__spm_lock){-.....} -> (resume_reason_lock){+.+...}
(0)[1132:system_server]
but this new dependency connects a HARDIRQ-irq-safe lock:
(0)[1132:system_server] (__spm_lock){-.....}
... which became HARDIRQ-irq-safe at:
(0)[1132:system_server]  [<ffffffc00010b834>] mark_lock+0x180/0x770
(0)[1132:system_server]  [<ffffffc00010e868>] __lock_acquire+0xaf8/0x243c
(0)[1132:system_server]  [<ffffffc000110b08>] lock_acquire+0xe8/0x1a8
(0)[1132:system_server]  [<ffffffc000c73eb4>] _raw_spin_lock_irqsave+0x54/0x84
(0)[1132:system_server]  [<ffffffc00048f880>] spm_irq0_handler+0x2c/0x12c
(0)[1132:system_server]  [<ffffffc00011f948>] handle_irq_event_percpu+0xc0/0x338
(0)[1132:system_server]  [<ffffffc00011fc08>] handle_irq_event+0x48/0x78
(0)[1132:system_server]  [<ffffffc000122d68>] handle_fasteoi_irq+0xe0/0x1a4
(0)[1132:system_server]  [<ffffffc00011eee0>] generic_handle_irq+0x30/0x4c
(0)[1132:system_server]  [<ffffffc00011effc>] __handle_domain_irq+0x100/0x2a4
(0)[1132:system_server]  [<ffffffc000081568>] gic_handle_irq+0x54/0xe0
(0)[1132:system_server]  [<ffffffc000085290>] el0_irq_naked+0x14/0x24
(0)[1132:system_server]
to a HARDIRQ-irq-unsafe lock:
(0)[1132:system_server] (resume_reason_lock){+.+...}
... which became HARDIRQ-irq-unsafe at:
(0)[1132:system_server]...  [<ffffffc00010b834>] mark_lock+0x180/0x770
(0)[1132:system_server]  [<ffffffc00010e65c>] __lock_acquire+0x8ec/0x243c
(0)[1132:system_server]  [<ffffffc000110b08>] lock_acquire+0xe8/0x1a8
(0)[1132:system_server]  [<ffffffc000c73e48>] _raw_spin_lock+0x38/0x50
(0)[1132:system_server]  [<ffffffc00011a258>] wakeup_reason_pm_event+0x54/0x9c
(0)[1132:system_server]  [<ffffffc0000c4d88>] notifier_call_chain+0x84/0x2d4
(0)[1132:system_server]  [<ffffffc0000c5400>] __blocking_notifier_call_chain+0x40/0x74
(0)[1132:system_server]  [<ffffffc0000c5444>] blocking_notifier_call_chain+0x10/0x1c
(0)[1132:system_server]  [<ffffffc000115ed4>] pm_notifier_call_chain+0x1c/0x48
(0)[1132:system_server]  [<ffffffc000117b68>] pm_suspend+0x36c/0x70c
(0)[1132:system_server]  [<ffffffc000115e40>] state_store+0xb0/0xe0
(0)[1132:system_server]  [<ffffffc0003b1f28>] kobj_attr_store+0x10/0x24
(0)[1132:system_server]  [<ffffffc000266f88>] sysfs_kf_write+0x50/0x64
(0)[1132:system_server]  [<ffffffc0002662c8>] kernfs_fop_write+0x110/0x180
(0)[1132:system_server]  [<ffffffc0001f6570>] vfs_write+0x98/0x1b8
(0)[1132:system_server]  [<ffffffc0001f678c>] SyS_write+0x4c/0xb0
(0)[1132:system_server]  [<ffffffc0000854ac>] el0_svc_naked+0x20/0x28
(0)[1132:system_server]
other info that might help us debug this:
(0)[1132:system_server] Possible interrupt unsafe locking scenario:
(0)[1132:system_server]       CPU0                    CPU1
(0)[1132:system_server]       ----                    ----
(0)[1132:system_server]  lock(resume_reason_lock);
(0)[1132:system_server]                         local_irq_disable();
(0)[1132:system_server]                         lock(__spm_lock);
(0)[1132:system_server]                         lock(resume_reason_lock);
(0)[1132:system_server]  <Interrupt>
(0)[1132:system_server]  lock(__spm_lock);
(0)[1132:system_server]  *** DEADLOCK ***

从上面的 LOG 信息能够知道:system_server 现已合了一个 HARDIRQ-safe 的锁 __spm_lock, 此刻再去拿一个 HARDIRQ-unsafe 的锁 resume_reason_lock,违反了嵌套获取锁前后的状况需求保持一致的规矩。

记得上面说过一条规矩吗?

if a new hardirq-unsafe lock is discovered, we check whether any hardirq-safe lock took it in the past.(当要获取一个 hardirq-unsafe lock 时,lockdep 就会查看该进程是否在之前现已获取 hardirq-safe lock)

HARDIRQ-safe 是不答应 irq 的锁,如:spin_lock_irqsave(&lock, flags);

HARDIRQ-unsafe 是答应 irq 的锁,如:spin_lock(&lock);

在之前现已运用 spin_lock_irqsave 的方法拿了 __spm_lock, 再以 spin_lock 的方法拿 resume_reason_lock。再来看看或许发生死锁的情形:

(0)[1132:system_server] Possible interrupt unsafe locking scenario:
(0)[1132:system_server]       CPU0                    CPU1
(0)[1132:system_server]       ----                    ----
(0)[1132:system_server]  lock(resume_reason_lock);
(0)[1132:system_server]                         local_irq_disable();
(0)[1132:system_server]                         lock(__spm_lock);
(0)[1132:system_server]                         lock(resume_reason_lock);
(0)[1132:system_server]  <Interrupt>
(0)[1132:system_server]  lock(__spm_lock);
(0)[1132:system_server]  *** DEADLOCK ***

Lockdep 列出一个或许发生死锁的设想:

  • CPU0 先获取了一个 HARDIRQ-unsafe 的锁 lock(resume_reason_lock),CPU0 本地 irq 是敞开的。
  • 接着 CPU1 再获取了 HARDIRQ-safe 的锁 lock(__spm_lock),此刻 CPU1 本地 irq 是关闭的。
  • 接着 CPU1 又去获取 lock(resume_reason_lock),但此刻该锁正在被 CPU0 锁持有,CPU1 唯有等候 lock(resume_reason_lock) 开释而无法持续履行。
  • 假如此刻 CPU0 来了一个中止,并且在中止里去获取 lock(__spm_lock),CPU0 也会由于该锁被 CPU1 持有而未被开释而一向等候无法持续履行。
  • CPU0, CPU1 都由于相互等候对方开释锁而不能持续履行,导致 AB-BA 死锁。

剖析到这里,自然知道死锁危险点和正确运用锁的规矩了,按照这个规矩去修正代码,防止死锁就能够了。解决办法:

  1. 剖析 resume_reason_lock 是否在其他当地中止上下文有运用这把锁。
  2. 假如没有,直接把获取这把锁的当地 wakeup_reason_pm_event+0x54/0x9c 从 spin_lock 改成 spin_lock_irqsave 就能够了。保持嵌套获取锁前后的状况一致。

5、参阅文档

1. Linux 死锁检测模块 Lockdep 简介

2. 《Linux内核深度解析》基于ARM64架构之lockdep章节

3. 死锁检测lockdep实现原理

4. 用crash剖析内核死锁的一次实践