在内核程序开发中,我们经常会使用spin_lock对数据修改进行保护,常规模式如下:
int g_data = 0;
functiona()
{
spin_lock(&g_lock);
//modify global shared data
g_data = 1;
spin_unlock(&g_lock);
}
看上去是没问题了,但在某些情况下,出现了deadlock, 并且原因就是在同一个cpu核上执行functiona的spin_lock 2次导致死锁.
原因是因为functiona会在线程中被执行,也会在软中断被执行. 当functiona先在线程中执行,并且处于spin_lock()和spin_unlock()之间.
此刻,同cpu核上软中断被执行,抢占了当前线程,并且该软中断也调用函数functiona, 然后执行spin_lock()函数,便出现死锁了.
修改方法是把spin_lock()/spin_unlock()改为spin_lock_bh()/spin_unlock_bh(),因为spin_lock_bh()会关闭当前cpu核中断后半部(也就是软中断softirq).
还有spin_lock_irqsave(),会关闭当前cpu核的中断和软中断.
spinlock 是怎么实现的?
看一下源代码:
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_GENERIC_LOCKBREAK
unsigned int break_lock;
#endif
#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;
typedef struct {
union {
u32 slock;
struct __raw_tickets {
#ifdef __ARMEB__
u16 next;
u16 owner;
#else
u16 owner;
u16 next;
#endif
} tickets;
};
} arch_spinlock_t;
如果忽略 CONFIG_DEBUG_LOCK_ALLOC 话,spinlock 主要包含一个arch_spinlock_t
的结构,从名字可以看出,这个结构是跟体系结构有关的。
lock操作, 以spin_lock为例:
static inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
define raw_spin_lock(lock) _raw_spin_lock(lock)
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
__raw_spin_lock(lock);
}
{
__acquire(lock);
arch_spin_lock(&lock->raw_lock);
}
arch_spin_trylock
最终调用__ticket_spin_trylock
函数。其源代码如下:// 定义在arch/x86/include/asm/spinlock_types.h
typedef struct arch_spinlock {
union {
__ticketpair_t head_tail;
struct __raw_tickets {
__ticket_t head, tail; // 注意,x86使用的是小端模式,存在高地址空间的是tail
} tickets;
};
} arch_spinlock_t;
// 定义在arch/x86/include/asm中
static __always_inline int __ticket_spin_trylock(arch_spinlock_t *lock)
{
arch_spinlock_t old, new;
// 获取旧的ticket信息
old.tickets = ACCESS_ONCE(lock->tickets);
// head和tail不一致,说明锁正被占用,加锁不成功
if (old.tickets.head != old.tickets.tail)
return 0;
new.head_tail = old.head_tail + (1 << TICKET_SHIFT); // 将tail + 1
/* cmpxchg is a full barrier, so nothing can move before it */
return cmpxchg(&lock->head_tail, old.head_tail, new.head_tail) == old.head_tail;
}
从上述代码中可知,__ticket_spin_trylock
的核心功能,就是判断自旋锁是否被占用,如果没被占用,尝试原子性地更新lock
中的head_tail
的值,将 tail+1,返回是否加锁成功。
而对于spin_lock_bh()和spin_lock_irqsave(),则分别多了一个阻止软中断和中断.