Linux内核同步机制spinlock详解

嵌入式技术

1372人已加入

描述

Linux内核同步机制spinlock

spin_lock是什么

在平时的工作中,作为开发人员经常碰到这样的问题:多线程或多进程共享的数据如何进行保护,如果发生进程上下文切换或中断上下文切换都可能使共享数据发生争抢问题。这时候就可以考虑用锁了。如果是进程上下文切换引起的可以考虑用信号量或mutex互斥锁,但如果发生在中断上下文,这时候信号量和mutex就无法使用了,因为这两种锁机制是可以睡眠的,而中断上下文又禁止睡眠,这时,spin_lock就是我们最好的选择了。

  • spin_lock是一种死等的锁机制。当发生资源访问冲突的时候,可以有两个选择:一个是死等,一个是挂起当前进程,调度其他进程执行。
  • spin_lock一次只允许一个线程进入共享资源区,信号量可以允许多个线程进入。
  • spin_lock执行的时间要短,因为spin_lock是死等锁,所以在共享资源区的执行时间不能太长,否则会造成CPU的资源浪费。
  • spin_lock最重要的一点是可以在中断上下文执行。

资源竞争

现代CPU一般都是多核多CPU的SMP架构,在这种情况下就有可能出现多个CPU要共同访问同一个资源的问题,此时就需要对资源进行保护,确保同一个时刻只有一个CPU正在访问修改资源变量。锁就是在这种背景下诞生的,锁的种类有很多,应用场景也不同,本篇我们主要介绍spinlock自旋锁。

进程上下文

如果一个全局的资源被多个进程上下文访问,此时,内核是如何执行的呢?对于没有开启内核可抢占选项的内核,所有的系统调用都是串行执行的,并不存在资源竞争的问题。但是。对于现在的大部分内核来说可抢占选项是开启的。

假如现在有一个共享资源S,有两个进程A和B,都需要访问共享资源S

执行流程大概是这样的:

进程A访问资源S的时候发生了中断,中断去唤醒优先级更高的进程B,中断返回的时候,发生进程切换,优先级更高的进程B执行,进程B访问共享资源S,如果没有加锁保护,此时进程A和进程B都访问了共享资源S,导致程序的执行结果不正确。如果通过spin_lock加锁保护,进程A访问共享资源S前获取spin_lock,此时发生了中断,优先级更高的进程B开始执行,进程B会去获取spin_lock,由于spin_lock被进程A所持有,导致进程B获取spin_lock失败,进程B会死等直到进程A释放了spin_lock,然后进程B就可以获取spin_lock,访问共享资源S。

中断上下文

  • 进程A运行在CPU0上访问共享资源S
  • 进程B运行在CPU1上访问共享资源S
  • 外部设备发生中断访问共享资源S 此时进程A在CPU0上获取spin_lock开始访问共享资源S,这时外部设备发生了中断,并且在CPU1上执行,假如中断执行了一段时间后被调度到了CPU0上执行,此时会怎样呢?由于CPU0上进程A已经获取了spin_lock,现在被中断上下文打断,中断上下文也要获取spin_lock,那么现在就进入了死胡同也就是死锁状态。而进程B在CPU1上也要获取spin_lock,也会进入死锁状态。所有中断上下文的切换CPU必须禁止本CPU上的中断。

实现原理

spinlock结构体定义在头文件include/linux/spinlock_types.h

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;

spinlock结构体变量的定义有两种,一种是静态定义,一种是动态定义。

#define DEFINE_SPINLOCK(x) spinlock_t x = __SPIN_LOCK_UNLOCKED(x)  //静态定义
spinlock_t  lock;
spin_lock_init (&lock);  //动态定义

spinlock接口函数介绍,这些函数是驱动编程内核编程的时候会用到的。

位于include/linux/spinlock.h头文件中
static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock)
{
 return &lock- >rlock;
}

#define spin_lock_init(_lock)    \\
do {       \\
 spinlock_check(_lock);    \\
 raw_spin_lock_init(&(_lock)- >rlock);  \\
} while (0)

static __always_inline void spin_lock(spinlock_t *lock)
{
 raw_spin_lock(&lock- >rlock);
}

static __always_inline void spin_lock_bh(spinlock_t *lock)
{
 raw_spin_lock_bh(&lock- >rlock);
}

static __always_inline int spin_trylock(spinlock_t *lock)
{
 return raw_spin_trylock(&lock- >rlock);
}

#define spin_lock_nested(lock, subclass)   \\
do {        \\
 raw_spin_lock_nested(spinlock_check(lock), subclass); \\
} while (0)

#define spin_lock_nest_lock(lock, nest_lock)    \\
do {         \\
 raw_spin_lock_nest_lock(spinlock_check(lock), nest_lock); \\
} while (0)

static __always_inline void spin_lock_irq(spinlock_t *lock)
{
 raw_spin_lock_irq(&lock- >rlock);
}

#define spin_lock_irqsave(lock, flags)    \\
do {        \\
 raw_spin_lock_irqsave(spinlock_check(lock), flags); \\
} while (0)

#define spin_lock_irqsave_nested(lock, flags, subclass)   \\
do {         \\
 raw_spin_lock_irqsave_nested(spinlock_check(lock), flags, subclass); \\
} while (0)

static __always_inline void spin_unlock(spinlock_t *lock)
{
 raw_spin_unlock(&lock- >rlock);
}

static __always_inline void spin_unlock_bh(spinlock_t *lock)
{
 raw_spin_unlock_bh(&lock- >rlock);
}

static __always_inline void spin_unlock_irq(spinlock_t *lock)
{
 raw_spin_unlock_irq(&lock- >rlock);
}

static __always_inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
 raw_spin_unlock_irqrestore(&lock- >rlock, flags);
}

static __always_inline int spin_trylock_bh(spinlock_t *lock)
{
 return raw_spin_trylock_bh(&lock- >rlock);
}

static __always_inline int spin_trylock_irq(spinlock_t *lock)
{
 return raw_spin_trylock_irq(&lock- >rlock);
}

#define spin_trylock_irqsave(lock, flags)   \\
({        \\
 raw_spin_trylock_irqsave(spinlock_check(lock), flags); \\
})

static __always_inline int spin_is_locked(spinlock_t *lock)
{
 return raw_spin_is_locked(&lock- >rlock);
}

static __always_inline int spin_is_contended(spinlock_t *lock)
{
 return raw_spin_is_contended(&lock- >rlock);
}

static __always_inline int spin_can_lock(spinlock_t *lock)
{
 return raw_spin_can_lock(&lock- >rlock);
}

#define assert_spin_locked(lock) assert_raw_spin_locked(&(lock)- >rlock)

spinlock底层实现源码分析

spin_lock()函数调用raw_spin_lock()函数,传入raw_spinlock_t指针变量。

static __always_inline void spin_lock(spinlock_t *lock)
{
 raw_spin_lock(&lock- >rlock);
}

raw_spin_lock()宏定义函数就是_raw_spin_lock()宏定义函数。

#define raw_spin_lock(lock) _raw_spin_lock(lock)

_raw_spin_lock()函数调用__raw_spin_lock()函数,并传入raw_spinlock_t指针变量。

void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
 __raw_spin_lock(lock);
}

__raw_spin_lock()函数调用preempt_disable()函数禁止内核抢占,调用spin_acquire()函数进行运行时检查锁的有效性,调用LOCK_CONTENDED()实际调用的就是do_raw_spin_lock()函数,并传入raw_spinlock_t指针变量。

static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
 preempt_disable();
 spin_acquire(&lock- >dep_map, 0, 0, _RET_IP_);
 LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

#define LOCK_CONTENDED(_lock, try, lock) \\
 lock(_lock)

do_raw_spin_lock()函数调用debug_spin_lock_before()函数进行静态检查,调用arch_spin_lock()函数具体实现针对arm体系架构的自旋锁机制,调用debug_spin_lock_after()函数进行静态检查。

void do_raw_spin_lock(raw_spinlock_t *lock)
{
 debug_spin_lock_before(lock);
 arch_spin_lock(&lock- >raw_lock);
 debug_spin_lock_after(lock);
}

arch_spin_lock()函数调用prefetchw()函数读取锁变量的值,该函数和cache有关,主要是为了提高性能,接下来是一段内嵌汇编代码,主要是实现原子操作,通过ldrex和strex这两个独占访问指令实现。接下来判断当前的spinlock是否已经被持有,如果已经被持有则调用wfe()函数进入等待状态,WFE(Wait for event)是让ARM核进入低功耗待机模式的指令,由ARM架构规范定义,由ARM核实现。接着判断本CPU是否是其它CPU唤醒执行的,如果是则说明owner的值发生了变化,将新的owner值赋给lockvalsmp_mb是memory barrier相关的操作。

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"
" add %1, %0, %4\\n"
" strex %2, %1, [%3]\\n"
" 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) {
  wfe();
  lockval.tickets.owner = ACCESS_ONCE(lock- >tickets.owner);
 }

 smp_mb();
}

总结

本篇主要介绍了Linux内核同步机制spinlock的原理并分析了底层源码是怎么实现的,以及spinlock的使用场景。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分