死锁形成是因为以下四个必要条件,理论上只要破坏其中一个条件就可以避免死锁,但是在实际应用过程中一般会采用lock ordering破环循环等待条件。
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
但是对于大型系统而言,因为长调用链,多核调度,中断等一系列原因很难保证系统上锁遵循lock ordering,所以linux引用lockdep机制检查死锁。
在申请一把锁时将其他所有锁都申请,强制保证lock ordering,只用一把大锁不是更好?
lockdep检测单位:class
lockdep检查的是lock class,而不是具体的lock instance,这样可以将instance每个具体的场景都映射到class中,根据lockdep design所说,依靠这种方式,lockdep可以不依赖特定的multi-cpu /multi-task test case而检查死锁的风险。
lockdep在lock instance初始化时(相同class的instance在同一个地方显式初始化),会有一个静态局部变量__key作为lock.key,用于在register_lock_class中注册class,其key address作为class在hash表中的索引值,保证class的唯一性。
# define raw_spin_lock_init(lock) \
do { \
static struct lock_class_key __key; \
\
__raw_spin_lock_init((lock), #lock, &__key); \
} while (0)
static inline struct lock_class *
register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
{
... ...
/*在classhash_table hash表中查找class(lock->key->subkeys + subclass),其中subclass是nest lock用于不同的class*/
class = look_up_lock_class(lock, subclass);
... ...
/通过lock.key确定class在hash表中的位置/
key = lock->key->subkeys + subclass;
hash_head = classhashentry(key);
... ...
/*申请class,并初始化*/
class = lock_classes + nr_lock_classes++;
debug_atomic_inc(nr_unused_locks);
class->key = key;
class->name = lock->name;
class->subclass = subclass;
... ...
/*将class添加到hash中*/
list_add_tail_rcu(&class->hash_entry, hash_head);
... ...
}
lockdep检测
lockdep会通过usage_mask在class中统计lock所处的情形,总共有4 * nSTATEs + 1中情形。然后在上锁的过程中除了检查锁在当前的情形是否会产生死锁外,还会检查lock new_mask和之前的usage_mask组合是否产生死锁。
4 是指:
4 usage_mask ever held in STATE context (state,used,w) ever held as readlock in STATE context (state,used,r) ever held with STATE enabled (state,enable,w) ever held as readlock with STATE enabled (state,enable,r)
state 是指:
hardirq –> 硬中断
softirq –> 软中断
reclaim_fs –> fs 回收
1 是指:
ever used
首先会通过mark_irqflags检查现在lock new_mask是否会和lock之前所在的情形(usage_mask)产生冲突;其次会通过validate_chain检查lock和chain及其usage_mask产生循环上锁的情形。
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)
{
/*先从cacha中查找lock class,如果找不到,通过register_lock_class查找或申请class*/
if (subclass < NR_LOCKDEP_CACHING_CLASSES)
class = lock->class_cache[subclass];
if (unlikely(!class)) {
class = register_lock_class(lock, subclass, 0);
... ...
}
... ...
/*进程持有锁的深度*/
depth = curr->lockdep_depth;
... ...
/*nest lock有两种形式:
1.整个hash表用一把锁,每个bucket共用一个class,下面是检查之后bucket使用锁时,只递增bucket.hlock.references
2.另一中是有不同的subclass(normal,whole,partition),但是这种nest_lock对应的会选择同一个lock.key下的不同class
*/
class_idx = class - lock_classes + 1;
if (depth) {
hlock = curr->held_locks + depth - 1;
if (hlock->class_idx == class_idx && nest_lock) {
if (hlock->references)
hlock->references++;
else
hlock->references = 2;
return 1;
}
}
... ...
/*向进程申请hlock,并初始化*/
hlock = curr->held_locks + depth;
hlock->class_idx = class_idx;
hlock->instance = lock;
hlock->nest_lock = nest_lock;
hlock->trylock = trylock;
hlock->read = read;
hlock->check = check;
hlock->hardirqs_off = !!hardirqs_off;
hlock->references = references;
... ...
/*
功能分析:mark_irqflags将lock instance所处的(state,enable/uesed,r/w)信息归总到usage_mask,并通过mark_lock_irq检查是否会产生死锁
函数分析:mark_lock_irq中valid_state检查AA型死锁,check_usage_XX检查 AB-BA型死锁
1.excl_bit是和new_bit相同状态下irq有冲突的写锁。如果lock是uesed(0),其locks_after不能有相同状态下enable的锁(excl_bit),否则可能在该锁区域内发生中断,然后再锁上lock,和现在的lock ordering相反造成(AB-BA)死锁。同理当lock enable(1)时,其locks_before不能有used的锁。
2.valid_state用于检查lock本身也不能有excl_bit的情形,否则会造成AA死锁
3.死锁检查范围(linux4.4.0中只有STRICT_READ_CHECKS置上,所有的情形才都能检测的到):
-对于写锁,对于AA型死锁自身的读写锁都会检查;对于AB-BA型死锁,检查和其他读写锁的乱序
-对于读锁lock,对于AA性死锁只会检查自身写锁;对于AB-BA型死锁,会检查写锁(因为将所有属性都归纳到class,所以不知道这把锁是在读lock还是写lock之后,虽然在读lock之后没问题,但是在写lock之后就会存在问题)
*/
if (check && !mark_irqflags(curr, hlock))
return 0;
if (!mark_lock(curr, hlock, LOCK_USED))
return 0;
/*
1.chain_key:chain是后续validate_chain的基本单位,chain_key是其唯一标志,如果chain_key相同表示是同一条chain(lockdep design说这是lockdep 100%proof的条件之一),就不会在通过validate_chain验证,保证performance。
注:1.chain_key是利用之前chain的key和class_id计算hash,如果chain没有(chain_head置上),其chain_key默认是0
2.不在同一个irq_context中视为一个新chain的开始(chain_head)
3.curr.curr_chain_key指向当前进程所持有的chain,每个hlock都有一个prev_chain_key表征其所在的chain。chain与进程无关,只要chain_key相同就是一个chain
*/
... ...
/*第一种nest lock首个bucket上锁时要检查整个hash锁要锁上*/
if (nest_lock && !__lock_is_held(nest_lock))
return print_lock_nested_lock_not_held(curr, hlock, ip);
/*
功能分析:前面mark_irqflags是检查lock在之前出现情形下是否会出现死锁,现在是检查当前chain情景下是否会出现死锁
函数分析:
1.lookup_chain_cache检查当前hash中是否存在chain_key对应的chain,如果存在返回0表示不需要验证;如果不存在为其申请chain,并把其chain存在chain_hlocks[chain.base]中,返回1表示需要验证chain是否会产生死锁
2.check_deadlock验证lock是否会导致进程当前的held_locks产生死锁
3.check_prevs_add检查将lock加到chain中之后是否会因为chain上class之前出现的情形导致死锁
-如果是chain_head;递归读,或者第一种nest lock,将不会将lock加到locks_before/locks_after中并检查
-check_prev_add检查chain之前的因为irq产生的锁场景是否会产生死锁,如果不是try_lock只需要将进程一个prev添加关系就好了,因为prev和其之前锁的关系已经建立好了,通过bfs遍历整个graph时其他锁的关系自然会遍历到。
1.check_noncircular确定lock.locks_after上没有prev
2.check_prev_add_irq通过check_usage检查prev.locks_before和lock.locks_after是否存在(used,enable)这样的锁组合可能因为中断和当前的lock order冲突而导致死锁。
注:1.(读,写)出现死锁的逻辑是:bit检查只能确定有(next:enable_w,prev:uesed_r)的可能性,因为class丢失了instance的具体使用场景,所以当前的顺序可能是(prev: w; next: enable_w),这两者组合产生死锁。
2.这里只检查了(写,写),(读,写)两种情况,如果以(读,写)出现死锁的逻辑来说,其他两种情况也应该都检查?
3.add_lock_to_list将next加到prev.locks_after链表中,将prev加到next.locks_before链表中
*/
if (!validate_chain(curr, lock, hlock, chain_head, chain_key))
return 0;
/*更新进程所在的chain,已经其held_locks的深度*/
curr->curr_chain_key = chain_key;
curr->lockdep_depth++;
... ...
}
lockdep其他接口
lockdep_assert_held是一种防御型编程,表示在某种操作之前lock要被锁住。lockdep_pin_lock是上层为了检查在长调用链的应用场景中,底层是否有先unlock之后再回到上层的时候在lock。
/*如果lock没被锁上将报warn*/
#define lockdep_assert_held(l) do { \
WARN_ON(debug_locks && !lockdep_is_held(l)); \
} while (0)
static int
__lock_release(struct lockdep_map *lock, int nested, unsigned long ip)
{
... ...
prev_hlock = NULL;
/*从后向前遍历进程held_locks数组*/
for (i = depth-1; i >= 0; i--) {
hlock = curr->held_locks + i;
/*只处理一条chain*/
if (prev_hlock && prev_hlock->irq_context != hlock->irq_context)
break;
/*看是否能找到lock*/
if (match_held_lock(hlock, lock))
goto found_it;
prev_hlock = hlock;
}
... ...
found_it:
/*在__lock_pin_lock中递增pin_count,如果中途将其释放将会报warn*/
WARN(hlock->pin_count, "releasing a pinned lock\n");
/*递减referces,如果还存在nest lock,直接返回1*/
if (hlock->references) {
hlock->references--;
if (hlock->references) {
return 1;
}
}
/*如果lock被完全释放,更新lockdep_depth和chain*/
curr->lockdep_depth = i;
curr->curr_chain_key = hlock->prev_chain_key
/*如果不是栈式释放锁,将会产生新的chain,所以需要重新验证是否会产生死锁*/
for (i++; i < depth; i++) {
hlock = curr->held_locks + i;
if (!__lock_acquire(hlock->instance,
hlock_class(hlock)->subclass, hlock->trylock,
hlock->read, hlock->check, hlock->hardirqs_off,
hlock->nest_lock, hlock->acquire_ip,
hlock->references, hlock->pin_count))
return 0;
}
}