19 同步与互斥(五)自旋锁
1 简介
自旋锁:通俗的说就是自己在原地打转,一直等待资源可用。不会休眠
对临界资源的访问时,因为自旋锁的存在,某段临界资源被占用后。其他事件将无法访问,即进行所谓的自旋,“原地打转”
注意:
-
在自旋锁的临界区不能使用可能引起进程调度的函数(因为抢占已经被禁止了)。
示例:
copy_to_user、copy_from_user、kmalloc、msleep等
可能会导致内核崩溃
-
spin_lock_irqsave只能控制当前CPU的中断,并不能控制多核的所有中断屏蔽,因此当中断与进程并发时,可能出现竟态。4
所以对于SMP,在中断服务函数中还应使用spin_lock去屏蔽进程抢占
2 自旋锁的结构和API
2.1 自旋锁的结构
spinlock_t lock; // 定义一个自旋锁
整个spinlock_t -> raw_spinlock -> arch_spinlock_t
在实现自旋的功能时arch_spinlock_t中owner、next是实现SMP的关键
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 { // 注意这里使用的union
u32 slock;
struct __raw_tickets {
#ifdef __ARMEB__
u16 next;
u16 owner;
#else
u16 owner;
u16 next;
#endif
} tickets;
};
} arch_spinlock_t;
2.2 自旋的API
-
spin_lock_init(_lock)
初始化自旋锁为unlock状态
-
void spin_lock(spin_lockt *lock)
加锁
-
int spin_trylock(spinlock_t *lock)
尝试加锁。成功返回1;失败返回0
-
void spin_unlock(spinlock_t *lock)
解锁
-
int spin_is_locked(spinlock_t *lock)
返回自旋锁的状态
自旋锁可以在原有的基础上添加后缀表示在执行相关的操作时还会做额外的事情
-
_bh
加锁时会禁止中断下半部(softirq),解锁时使能中断下半部
-
_irq
加锁时禁止中断,解锁时使能中断
-
_irqsave/_irqrestore
加锁时禁止中断并记录此时状态,解锁时恢复中断为所记录的状态
3 自旋锁的实现机制
自旋的实现借助了ldrex,strex和ARM处理器内存屏障命令dmb、dsb、wfe、sev
3.1 单核
spin_lock -> raw_spin_lock -> _raw_spin_lock -> __LOCK - > ___LOCK ->
#define __LOCK(lock) \
do { preempt_disable(); ___LOCK(lock); } while (0)
# define __acquire(x) (void)0
从上述调用可看出spin_lock只是去禁用了抢占,同理spin_lock_irq,在禁止抢占的同时禁止irq
3.2 SMP
3.2.1 spin_lock
spin_lock -> raw_spin_lock -> _raw_spin_lock -> __raw_spin_lock -> do_raw_spin_lock -> arch_spin_lock
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
// 禁止抢占
preempt_disable();
// debug
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
在__raw_spin_lock中禁用抢占
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
unsigned long tmp;
u32 newval;
arch_spinlock_t lockval;
prefetchw(&lock->slock);
__asm__ __volatile__(
"1: ldrex %0, [%3]\n" @ lockval = &lock->slock 取出传入的自旋锁的next,owner
" add %1, %0, %4\n" @ newval = lockval + (1 << TICKET_SHIFT) 即next++;
" strex %2, %1, [%3]\n" @ (&lock->slock) = newval; 整个过程可能会被优先级更高的事件抢占,导致失败。从而需要再次排号next++
" teq %2, #0\n"
" bne 1b"
: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
while (lockval.tickets.next != lockval.tickets.owner) { // owner的号码与next不一致时,在下面去等待
wfe(); // 暂时中断挂起执行,CPU低功耗运行
lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner); // 更新owner
}
// owner与next一致了,执行下面命令获得自旋锁
smp_mb();
}
3.2.2 spin_unlock
static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
smp_mb();
lock->tickets.owner++;
dsb_sev(); // 执行sev指令,唤醒wfe等待的处理器
}