在內核程序開發中,我們經常會使用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(),則分別多了一個阻止軟中斷和中斷.