常见的死锁有以下4种状况:
- 进程重复请求同一个锁,称为AA死锁。例如,重复请求同一个自旋锁;运用读写锁,第一次请求读锁,第二次请求写锁。
- 进程请求自旋锁时没有禁止硬中止,进程获取自旋锁今后,硬中止抢占,请求同一个自旋锁。
- 两个进程都要获取锁L1与L2,进程1持有L1,再去获取锁L2,此刻进程2持有锁L2,测验获取L1,那么进程1与进程2就会死锁,成为AB-BA死锁。
- 在一个处理器上进程1持有锁L1,再去获取锁L2,在另一个处理器上进程2持有锁L2,硬中止抢占进程2今后获取锁L1,这种AB-BA死锁很隐蔽。
防止AB-BA死锁最简略的办法便是界说锁的请求次序,以破坏死锁的环形等候。可是假如一个体系拥有几百个甚至几千个锁,那么没法彻底界说一切锁的请求次序,更可行的办法是在开发阶段提前发现潜在的死锁危险。内核供给的死锁检测工具lockdep用来发现内核的死锁危险。
1、运用办法
死锁检测工具lockdep装备宏如下:
- CONFIG_LOCKDEP:在装备菜单中看不到这个装备宏,翻开装备宏CONFIG_PROVE_LOCKING或CONFIG_DEBUG_LOCK_ALLOC会主动翻开这个装备宏。
- CONFIG_PROVE_LOCKING:答应内核报告死锁问题。
- CONFIG_DEBUG_LOCK_ALLOC:查看内核是否过错地开释被持有的锁。
- CONFIG_DEBUG_LOCKINIG_API_SELFTESTS:内核在初始化的过程中运转一小段自我测试程序,自我测试程序查看调试机制是否能够发现常见的锁缺陷。
2、技能原理
死锁检测工具lockdep操作的根本目标是锁类,例如结构体里边的锁是一个锁类,结构体的每个实例里边的锁是锁类的一个实例。
lockdep盯梢每个锁类的自身状况,也盯梢各个锁类之间的依靠联系,经过一系列的验证规矩,确保锁类状况和锁类之间的依靠总是正确的。锁类一旦在初度运用时被注册就会一向存在,它的一切具体实例都会相关到它。
2.1 锁类状况
lockdep为锁类界说了(4n+1)种运用历史状况,其间4表明:
- 该锁曾在STATE上下文被持有过
- 该锁曾在STATE上下文中被以读的方法持有过
- 该锁曾在敞开STATE的状况下被持有过
- 该锁曾在敞开STATE的状况下被以读的方法持有过
其间n是STATE状况的个数,STATE状况包括硬中止、软中止和reclaim_fs(__GFP_FS分配,表明答应向下调用到文件体系,假如文件体系持有锁今后运用锁今后运用标志位__GFP_FS请求内存,在内存严重不足的状况下,需求回收文件页,把修改正的文件页写回到存储设备,递归调用文件体系的函数,或许会导致死锁)。
其间的1表明该锁从前被运用过。
2.2 查看规矩
单锁状况规矩如下:
- 一个软中止不安全的锁类也是硬中止不安全的锁类
- 任何一个锁类,不或许一起是硬中止安全的和硬中止不安全的,也不或许一起是软中止安全和软中止不安全的。也便是说,硬中止安全和硬中止不安满是互斥的,软中止安全和软中止不安全也是互斥的。
多锁依靠规矩如下:
- 同一个锁类不能被获取两次,不然或许导致递归死锁(AA死锁);
- 不能以不同次序获取两个锁类,不然导致AB-BA死锁;
- 不答应在获取硬中止安全的锁类之后获取硬中止不安全的锁类:例如,硬中止安全的锁类或许被硬中止获取,假定处理器0上的进程首先获取硬中止安全的锁类A,然后获取硬中止不安全的锁类B;处理器1上的进程获取锁类B,硬中止抢占进程,获取锁类A,或许导致AB-BA死锁。
- 不答应在获取软中止安全锁类之后获取软中止不安全的锁类:软中止安全的锁类或许被软中止获取,假定处理器0上的进程获取软中止安全的锁类A,然后获取软中止不安全的锁类B;处理器1上的进程获取锁类B,软中止抢占进程获取锁类A,或许导致AB-BA死锁。
当锁的状况发生变化时,查看下面的依靠规矩:
- 假如锁类的状况变成硬中止安全,查看曩昔是否在获取它之后获取硬中止不安全的锁;
- 假如锁类的状况变成软中止安全,查看曩昔是否在获取它之后获取软中止不安全的锁;
- 假如锁类的状况变成硬中止不安全,查看曩昔是否在获取硬中止安全的锁之后获取它;
- 假如锁类的状况变成软中止不安全,查看曩昔是否在获取软中止安全的锁之后获取它;
内核有时需求获取同一个锁类的多个实例,上面的查看规矩会导致误报“重复上锁”,需求运用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的查看过程如下:
- 调用函数check_deadlock查看重复上锁,即当时进程是否现已持有锁类L2,假如现已持有锁类L2,除非两次都请求读锁,不然存在死锁。
- 调用函数check_prevs_add,依据曾经记录到的锁类依靠联系查看死锁。
假定当时请求锁类L2,函数check_prevs_add针对当时进程的数组held_locks中的每个锁类L1,调用函数check_prev_add查看,查看过程如下:
- 调用函数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 ***
- 调用函数check_irq_usage,查看是否存在以下状况:“在获取硬中止安全的锁类之后获取硬中止不安全的锁类”或许“在获取软中止安全的锁类之后获取软中止不安全的锁类”。
- 假如锁类L1的链表locks_before中存在硬中止安全的锁类,并且锁类L2的链表locks_after中存在硬中止不安全的锁类,那么阐明在获取硬中止安全的锁类之后获取硬中止不安全的锁类,存在死锁危险。
- 记录锁类的依靠联系:把锁类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 死锁。
剖析到这里,自然知道死锁危险点和正确运用锁的规矩了,按照这个规矩去修正代码,防止死锁就能够了。解决办法:
- 剖析 resume_reason_lock 是否在其他当地中止上下文有运用这把锁。
- 假如没有,直接把获取这把锁的当地 wakeup_reason_pm_event+0x54/0x9c 从 spin_lock 改成 spin_lock_irqsave 就能够了。保持嵌套获取锁前后的状况一致。
5、参阅文档
1. Linux 死锁检测模块 Lockdep 简介
2. 《Linux内核深度解析》基于ARM64架构之lockdep章节
3. 死锁检测lockdep实现原理
4. 用crash剖析内核死锁的一次实践