自旋锁使用场景和实现分析(转载)

自旋锁


最近看到的一篇文章,觉得写的很清晰,通过场景应用解答了我对自旋锁使用的一些疑问,推荐给大家。

引入问题:

(1)如果cpu0持有锁,cpu1一直不释放锁怎么办?

(2)什么场景下必须要用自旋锁,而不能用互斥量?

(3)互斥量或者自旋锁,他们会被多个进程使用,那么它属于进程的一部分?

内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:

  • 一个是原地等待
  • 一个是挂起当前进程,调度其他进程执行(睡眠)

Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方式解决资源冲突的,即,一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到,只能够原地“打转”(忙等待)。由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制 —— 自旋锁不应该被长时间的持有(消耗 CPU 资源)。

自旋锁的使用

在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机制,但是现在中断上下文也参和进来,那些可以导致睡眠的lock就不能使用了,这时候,可以考虑使用spin lock。

这里为什么把中断上下文标红加粗呢?因为在中断上下文,是不允许睡眠的(原因详见文章《Linux 中断之中断处理浅析》中的第四章),所以,这里需要的是一个不会导致睡眠的锁——spinlock。

换言之,中断上下文要用锁,首选 spinlock

使用自旋锁,有两种方式定义一个锁:

动态的:

 
  
  1. spinlock_t lock;

  2. spin_lock_init (&lock);

静态的:

DEFINE_SPINLOCK(lock);

自旋锁的死锁和解决

自旋锁不可递归,自己等待自己已经获取的锁,会导致死锁。

自旋锁可以在中断上下文中使用,但是试想一个场景:一个线程获取了一个锁,但是被中断处理程序打断,中断处理程序也获取了这个锁(但是之前已经被锁住了,无法获取到,只能自旋),中断无法退出,导致线程中后面释放锁的代码无法被执行,导致死锁。(如果确认中断中不会访问和线程中同一个锁,其实无所谓)

一、考虑下面的场景(内核抢占场景):

(1)进程A在某个系统调用过程中访问了共享资源 R

(2)进程B在某个系统调用过程中也访问了共享资源 R

会不会造成冲突呢?假设在A访问共享资源R的过程中发生了中断,中断唤醒了沉睡中的,优先级更高的B,在中断返回现场的时候,发生进程切换,B启动执行,并通过系统调用访问了R,如果没有锁保护,则会出现两个thread进入临界区,导致程序执行不正确。OK,我们加上spin lock看看如何:A在进入临界区之前获取了spin lock,同样的,在A访问共享资源R的过程中发生了中断,中断唤醒了沉睡中的,优先级更高的B,B在访问临界区之前仍然会试图获取spin lock,这时候由于A进程持有spin lock而导致B进程进入了永久的spin……怎么破?linux的kernel很简单,在A进程获取spin lock的时候,禁止本CPU上的抢占(上面的永久spin的场合仅仅在本CPU的进程抢占本CPU的当前进程这样的场景中发生)。如果A和B运行在不同的CPU上,那么情况会简单一些:A进程虽然持有spin lock而导致B进程进入spin状态,不过由于运行在不同的CPU上,A进程会持续执行并会很快释放spin lock,解除B进程的spin状态

二、再考虑下面的场景(中断上下文场景):

(1)运行在CPU0上的进程A在某个系统调用过程中访问了共享资源 R

(2)运行在CPU1上的进程B在某个系统调用过程中也访问了共享资源 R

(3)外设P的中断handler中也会访问共享资源 R

在这样的场景下,使用spin lock可以保护访问共享资源R的临界区吗?我们假设CPU0上的进程A持有spin lock进入临界区,这时候,外设P发生了中断事件,并且调度到了CPU1上执行,看起来没有什么问题,执行在CPU1上的handler会稍微等待一会CPU0上的进程A,等它立刻临界区就会释放spin lock的,但是,如果外设P的中断事件被调度到了CPU0上执行会怎么样?CPU0上的进程A在持有spin lock的状态下被中断上下文抢占,而抢占它的CPU0上的handler在进入临界区之前仍然会试图获取spin lock,悲剧发生了,CPU0上的P外设的中断handler永远的进入spin状态,这时候,CPU1上的进程B也不可避免在试图持有spin lock的时候失败而导致进入spin状态。为了解决这样的问题,linux kernel采用了这样的办法:如果涉及到中断上下文的访问,spin lock需要和禁止本 CPU 上的中断联合使用

三、再考虑下面的场景(底半部场景)

linux kernel中提供了丰富的bottom half的机制,虽然同属中断上下文,不过还是稍有不同。我们可以把上面的场景简单修改一下:外设P不是中断handler中访问共享资源R,而是在的bottom half中访问。使用spin lock+禁止本地中断当然是可以达到保护共享资源的效果,但是使用牛刀来杀鸡似乎有点小题大做,这时候disable bottom half就OK了

四、中断上下文之间的竞争

同一种中断handler之间在uni core和multi core上都不会并行执行,这是linux kernel的特性。

如果不同中断handler需要使用spin lock保护共享资源,对于新的内核(不区分fast handler和slow handler),所有handler都是关闭中断的,因此使用spin lock不需要关闭中断的配合。

bottom half又分成softirq和tasklet,同一种softirq会在不同的CPU上并发执行,因此如果某个驱动中的softirq的handler中会访问某个全局变量,对该全局变量是需要使用spin lock保护的,不用配合disable CPU中断或者bottom half

tasklet更简单,因为同一种tasklet不会多个CPU上并发。

自旋锁的实现

1、文件整理

和体系结构无关的代码如下:

(1) include/linux/spinlock_types.h

这个头文件定义了通用spin lock的基本的数据结构(例如spinlock_t)和如何初始化的接口(DEFINE_SPINLOCK)。这里的“通用”是指不论SMP还是UP都通用的那些定义。

(2)include/linux/spinlock_types_up.h

这个头文件不应该直接include,在include/linux/spinlock_types.h文件会根据系统的配置(是否SMP)include相关的头文件,如果UP则会include该头文件。这个头文定义UP系统中和spin lock的基本的数据结构和如何初始化的接口。当然,对于non-debug版本而言,大部分struct都是empty的。

(3)include/linux/spinlock.h

这个头文件定义了通用spin lock的接口函数声明,例如spin_lock、spin_unlock等,使用spin lock模块接口API的驱动模块或者其他内核模块都需要include这个头文件。

(4)include/linux/spinlock_up.h

这个头文件不应该直接include,在include/linux/spinlock.h文件会根据系统的配置(是否SMP)include相关的头文件。这个头文件是debug版本的spin lock需要的。

(5)include/linux/spinlock_api_up.h

同上,只不过这个头文件是non-debug版本的spin lock需要的

(6)linux/spinlock_api_smp.h

SMP上的spin lock模块的接口声明

(7)kernel/locking/spinlock.c

SMP上的spin lock实现。

对UP和SMP上spin lock头文件进行整理:

UP需要的头文件 SMP需要的头文件

linux/spinlock_type_up.h: 
linux/spinlock_types.h: 
linux/spinlock_up.h: 
linux/spinlock_api_up.h: 
linux/spinlock.h

asm/spinlock_types.h 
linux/spinlock_types.h: 
asm/spinlock.h 
linux/spinlock_api_smp.h: 
linux/spinlock.h

2、数据结构

首先定义一个 spinlock_t 的数据类型,其本质上是一个整数值(对该数值的操作需要保证原子性),该数值表示spin lock是否可用。初始化的时候被设定为1。当thread想要持有锁的时候调用spin_lock函数,该函数将spin lock那个整数值减去1,然后进行判断,如果等于0,表示可以获取spin lock,如果是负数,则说明其他thread的持有该锁,本thread需要spin。

内核中的spinlock_t的数据类型定义如下:

 
  
  1. typedef struct spinlock {

  2. struct raw_spinlock rlock;

  3. } spinlock_t;

  4. typedef struct raw_spinlock {

  5. arch_spinlock_t raw_lock;

  6. } raw_spinlock_t;

通用(适用于各种arch)的spin lock使用spinlock_t这样的type name,各种arch定义自己的struct raw_spinlock。听起来不错的主意和命名方式,直到linux realtime tree(PREEMPT_RT)提出对spinlock的挑战。real time linux是一个试图将linux kernel增加硬实时性能的一个分支(你知道的,linux kernel mainline只是支持soft realtime),多年来,很多来自realtime branch的特性被merge到了mainline上,例如:高精度timer、中断线程化等等。realtime tree希望可以对现存的spinlock进行分类:一种是在realtime kernel中可以睡眠的spinlock,另外一种就是在任何情况下都不可以睡眠的spinlock。分类很清楚但是如何起名字?起名字绝对是个技术活,起得好了事半功倍,可维护性好,什么文档啊、注释啊都素那浮云,阅读代码就是享受,如沐春风。起得不好,注定被后人唾弃,或者拖出来吊打(这让我想起给我儿子起名字的那段不堪回首的岁月……)。最终,spin lock的命名规范定义如下:

(1)spinlock,在rt linux(配置了PREEMPT_RT)的时候可能会被抢占(实际底层可能是使用支持PI(优先级翻转)的mutext)。

(2)raw_spinlock,即便是配置了PREEMPT_RT也要顽强的spin

(3)arch_spinlock,spin lock是和architecture相关的,arch_spinlock是architecture相关的实现

对于UP平台,所有的arch_spinlock_t都是一样的,定义如下:

typedef struct { } arch_spinlock_t;

什么都没有,一切都是空啊。当然,这也符合前面的分析,对于UP,即便是打开的preempt选项,所谓的spin lock也不过就是disable preempt而已,不需定义什么spin lock的变量。

对于SMP平台,这和arch相关,我们在下面描述。

在具体的实现面,我们不可能把每一个接口函数的代码都呈现出来,我们选择最基础的spin_lock为例子,其他的读者可以自己阅读代码来理解。

spin_lock的代码如下:

 
  
  1. static inline void spin_lock(spinlock_t *lock)

  2. {

  3. raw_spin_lock(&lock->rlock);

  4. }

当然,在linux mainline代码中,spin_lock和raw_spin_lock是一样的,在这里重点看看raw_spin_lock,代码如下:

#define raw_spin_lock(lock)    _raw_spin_lock(lock)

UP中的实现:

 
  
  1. #define _raw_spin_lock(lock) __LOCK(lock)

  2. #define __LOCK(lock) \

  3. do { preempt_disable(); ___LOCK(lock); } while (0)

SMP的实现:

 
  
  1. void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)

  2. {

  3. __raw_spin_lock(lock);

  4. }

  5. static inline void __raw_spin_lock(raw_spinlock_t *lock)

  6. {

  7. preempt_disable();

  8. spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);

  9. LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);

  10. }

UP中很简单,本质上就是一个preempt_disable而已,SMP中稍显复杂,preempt_disable当然也是必须的,spin_acquire可以略过,这是和运行时检查锁的有效性有关的,如果没有定义CONFIG_LOCKDEP其实就是空函数。如果没有定义CONFIG_LOCK_STAT(和锁的统计信息相关),LOCK_CONTENDED就是调用 do_raw_spin_lock 而已,如果没有定义CONFIG_DEBUG_SPINLOCK,它的代码如下:

 
  
  1. static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)

  2. {

  3. __acquire(lock);

  4. arch_spin_lock(&lock->raw_lock);

  5. }

__acquire和静态代码检查相关,忽略之,最终实际的获取spin lock还是要靠arch相关的代码实现。

针对 ARM 平台的 arch_spin_lock

代码位于arch/arm/include/asm/spinlock.h和spinlock_type.h,和通用代码类似,spinlock_type.h定义ARM相关的spin lock定义以及初始化相关的宏;spinlock.h中包括了各种具体的实现。

1. 回到2.6.23版本的内核中

和arm平台相关spin lock数据结构的定义如下(那时候还是使用raw_spinlock_t而不是arch_spinlock_t):

 
  
  1. typedef struct {

  2. volatile unsigned int lock;

  3. } raw_spinlock_t;

一个整数就OK了,0表示unlocked,1表示locked。配套的API包括__raw_spin_lock和__raw_spin_unlock。__raw_spin_lock会持续判断lock的值是否等于0,如果不等于0(locked)那么其他thread已经持有该锁,本thread就不断的spin,判断lock的数值,一直等到该值等于0为止,一旦探测到lock等于0,那么就设定该值为1,表示本thread持有该锁了,当然,这些操作要保证原子性,细节和exclusive版本的ldr和str(即ldrex和strexeq)相关,这里略过。立刻临界区后,持锁thread会调用__raw_spin_unlock函数是否spin lock,其实就是把0这个数值赋给lock。

这个版本的spin lock的实现当然可以实现功能,而且在没有冲突的时候表现出不错的性能,不过存在一个问题:不公平。也就是所有的thread都是在无序的争抢spin lock,谁先抢到谁先得,不管thread等了很久还是刚刚开始spin。在冲突比较少的情况下,不公平不会体现的特别明显,然而,随着硬件的发展,多核处理器的数目越来越多,多核之间的冲突越来越剧烈,无序竞争的spinlock带来的performance issue终于浮现出来,根据Nick Piggin的描述:

 
  
  1. On an 8 core (2 socket) Opteron, spinlock unfairness is extremely noticable, with a

  2. userspace test having a difference of up to 2x runtime per thread, and some threads are

  3. starved or "unfairly" granted the lock up to 1 000 000 (!) times.

多么的不公平,有些可怜的thread需要饥饿的等待1000000次。本质上无序竞争从概率论的角度看应该是均匀分布的,不过由于硬件特性导致这么严重的不公平,我们来看一看硬件block:

lock本质上是保存在main memory中的,由于cache的存在,当然不需要每次都有访问main memory。在多核架构下,每个CPU都有自己的L1 cache,保存了lock的数据。假设CPU0获取了spin lock,那么执行完临界区,在释放锁的时候会调用smp_mb invalide其他忙等待的CPU的L1 cache,这样后果就是释放spin lock的那个cpu可以更快的访问L1cache,操作lock数据,从而大大增加的下一次获取该spin lock的机会。

2、回到现在:arch_spinlock_t

ARM平台中的arch_spinlock_t定义如下(little endian):

 
  
  1. typedef struct {

  2. union {

  3. u32 slock;

  4. struct __raw_tickets {

  5. u16 owner;

  6. u16 next;

  7. } tickets;

  8. };

  9. } arch_spinlock_t;

本来以为一个简单的整数类型的变量就搞定的spin lock看起来没有那么简单,要理解这个数据结构,需要了解一些ticket-based spin lock的概念。如果你有机会去九毛九去排队吃饭(声明:不是九毛九的饭托,仅仅是喜欢面食而常去吃而已)就会理解ticket-based spin lock。大概是因为便宜,每次去九毛九总是无法长驱直入,门口的笑容可掬的靓女会给一个ticket,上面写着15号,同时会告诉你,当前状态是10号已经入席,11号在等待。

回到arch_spinlock_t,这里的owner就是当前已经入席的那个号码,next记录的是下一个要分发的号码。下面的描述使用普通的计算机语言和在九毛九就餐(假设九毛九只有一张餐桌)的例子来进行描述,估计可以让吃货更有兴趣阅读下去。最开始的时候,slock被赋值为0,也就是说owner和next都是0,owner和next相等,表示unlocked。当第一个个thread调用spin_lock来申请lock(第一个人就餐)的时候,owner和next相等,表示unlocked,这时候该thread持有该spin lock(可以拥有九毛九的唯一的那个餐桌),并且执行next++,也就是将next设定为1(再来人就分配1这个号码让他等待就餐)。也许该thread执行很快(吃饭吃的快),没有其他thread来竞争就调用spin_unlock了(无人等待就餐,生意惨淡啊),这时候执行owner++,也就是将owner设定为1(表示当前持有1这个号码牌的人可以就餐)。姗姗来迟的1号获得了直接就餐的机会,next++之后等于2。1号这个家伙吃饭巨慢,这是不文明现象(thread不能持有spin lock太久),但是存在。又来一个人就餐,分配当前next值的号码2,当然也会执行next++,以便下一个人或者3的号码牌。持续来人就会分配3、4、5、6这些号码牌,next值不断的增加,但是owner岿然不动,直到欠扁的1号吃饭完毕(调用spin_unlock),释放饭桌这个唯一资源,owner++之后等于2,表示持有2那个号码牌的人可以进入就餐了。 

3、ARM 结构体系 arch_spin_lock 接口实现

3.1 加锁

同样的,这里也只是选择一个典型的API来分析,其他的大家可以自行学习。我们选择的是 arch_spin_lock,其ARM32的代码如下:

 
  
  1. static inline void arch_spin_lock(arch_spinlock_t *lock)

  2. {

  3. unsigned long tmp;

  4. u32 newval;

  5. arch_spinlock_t lockval;

  6. prefetchw(&lock->slock);------------------------(0)

  7. __asm__ __volatile__(

  8. "1: ldrex %0, [%3]\n"-------------------------(1)

  9. " add %1, %0, %4\n" --------------------------(2)

  10. " strex %2, %1, [%3]\n"------------------------(3)

  11. " teq %2, #0\n"----------------------------(4)

  12. " bne 1b"

  13. : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)

  14. : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)

  15. : "cc");

  16. while (lockval.tickets.next != lockval.tickets.owner) {-------(5)

  17. wfe();--------------------------------(6)

  18. lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);----(7)

  19. }

  20. smp_mb();---------------------------------(8)

  21. }

(0)和preloading cache相关的操作,主要是为了性能考虑

(1)lockval = lock->slock (如果lock->slock没有被其他处理器独占,则标记当前执行处理器对lock->slock地址的独占访问;否则不影响)

(2)newval = lockval + (1 << TICKET_SHIFT)

(3)strex tmp, newval, [&lock->slock] (如果当前执行处理器没有独占lock->slock地址的访问,不进行存储,返回1给temp;如果当前处理器已经独占lock->slock内存访问,则对内存进行写,返回0给temp,清除独占标记) lock->tickets.next = lock->tickets.next + 1

(4)检查是否写入成功 lockval.tickets.next

(5)初始化时lock->tickets.owner、lock->tickets.next都为0,假设第一次执行arch_spin_lock,lockval = *lock,lock->tickets.next++,lockval.tickets.next 等于 lockval.tickets.owner,获取到自旋锁;自旋锁未释放,第二次执行的时候,lock->tickets.owner = 0, lock->tickets.next = 1,拷贝到lockval后,lockval.tickets.next != lockval.tickets.owner,会执行wfe等待被自旋锁释放被唤醒,自旋锁释放时会执行 lock->tickets.owner++,lockval.tickets.owner重新赋值

(6)暂时中断挂起执行。如果当前spin lock的状态是locked,那么调用wfe进入等待状态。更具体的细节请参考ARM WFI和WFE指令中的描述。

(7)其他的CPU唤醒了本cpu的执行,说明owner发生了变化,该新的own赋给lockval,然后继续判断spin lock的状态,也就是回到step 5。

(8)memory barrier的操作,具体可以参考memory barrier中的描述。

3.1 释放锁

 
  
  1. static inline void arch_spin_unlock(arch_spinlock_t *lock)

  2. {

  3. smp_mb();

  4. lock->tickets.owner++; ---------------------- (0)

  5. dsb_sev(); ---------------------------------- (1)

  6. }

(0)lock->tickets.owner增加1,下一个被唤醒的处理器会检查该值是否与自己的lockval.tickets.next相等,lock->tickets.owner代表可以获取的自旋锁的处理器,lock->tickets.next你一个可以获取的自旋锁的owner;处理器获取自旋锁时,会先读取lock->tickets.next用于与lock->tickets.owner比较并且对lock->tickets.next加1,下一个处理器获取到的lock->tickets.next就与当前处理器不一致了,两个处理器都与lock->tickets.owner比较,肯定只有一个处理器会相等,自旋锁释放时时对lock->tickets.owner加1计算,因此,先申请自旋锁多处理器lock->tickets.next值更新,自然先获取到自旋锁

(1)执行sev指令,唤醒wfe等待的处理器

自旋锁的变体

接口API的类型 spinlock中的定义 raw_spinlock的定义
定义spin lock并初始化 DEFINE_SPINLOCK DEFINE_RAW_SPINLOCK
动态初始化spin lock spin_lock_init raw_spin_lock_init
获取指定的spin lock spin_lock raw_spin_lock
获取指定的spin lock同时disable本CPU中断 spin_lock_irq raw_spin_lock_irq
保存本CPU当前的irq状态,disable本CPU中断并获取指定的spin lock spin_lock_irqsave raw_spin_lock_irqsave
获取指定的spin lock同时disable本CPU的bottom half spin_lock_bh raw_spin_lock_bh
释放指定的spin lock spin_unlock raw_spin_unlock
释放指定的spin lock同时enable本CPU中断 spin_unlock_irq raw_spin_unock_irq
释放指定的spin lock同时恢复本CPU的中断状态 spin_unlock_irqstore raw_spin_unlock_irqstore
获取指定的spin lock同时enable本CPU的bottom half spin_unlock_bh raw_spin_unlock_bh
尝试去获取spin lock,如果失败,不会spin,而是返回非零值 spin_trylock raw_spin_trylock
判断spin lock是否是locked,如果其他的thread已经获取了该lock,那么返回非零值,否则返回0 spin_is_locked raw_spin_is_locked
 
  
  1. static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)

  2. {

  3. unsigned long flags;

  4. local_irq_save(flags);

  5. preempt_disable();

  6. spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);

  7. /*

  8. * On lockdep we dont want the hand-coded irq-enable of

  9. * do_raw_spin_lock_flags() code, because lockdep assumes

  10. * that interrupts are not re-enabled during lock-acquire:

  11. */

  12. #ifdef CONFIG_LOCKDEP

  13. LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);

  14. #else

  15. do_raw_spin_lock_flags(lock, &flags);

  16. #endif

  17. return flags;

  18. }

  19. static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)

  20. {

  21. local_irq_disable();

  22. preempt_disable();

  23. spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);

  24. LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);

  25. }

  26. static inline void __raw_spin_lock_bh(raw_spinlock_t *lock)

  27. {

  28. local_bh_disable();

  29. preempt_disable();

  30. spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);

  31. LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);

  32. }

  33. static inline void __raw_spin_lock(raw_spinlock_t *lock)

  34. {

  35. preempt_disable();

  36. spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);

  37. LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);

  38. }

  39. #endif /* CONFIG_PREEMPT */

  40. static inline void __raw_spin_unlock(raw_spinlock_t *lock)

  41. {

  42. spin_release(&lock->dep_map, 1, _RET_IP_);

  43. do_raw_spin_unlock(lock);

  44. preempt_enable();

  45. }

  46. static inline void __raw_spin_unlock_irqrestore(raw_spinlock_t *lock,

  47. unsigned long flags)

  48. {

  49. spin_release(&lock->dep_map, 1, _RET_IP_);

  50. do_raw_spin_unlock(lock);

  51. local_irq_restore(flags);

  52. preempt_enable();

  53. }

  54. static inline void __raw_spin_unlock_irq(raw_spinlock_t *lock)

  55. {

  56. spin_release(&lock->dep_map, 1, _RET_IP_);

  57. do_raw_spin_unlock(lock);

  58. local_irq_enable();

  59. preempt_enable();

  60. }

  61. static inline void __raw_spin_unlock_bh(raw_spinlock_t *lock)

  62. {

  63. spin_release(&lock->dep_map, 1, _RET_IP_);

  64. do_raw_spin_unlock(lock);

  65. preempt_enable_no_resched();

  66. local_bh_enable_ip((unsigned long)__builtin_return_address(0));

  67. }

  68. static inline int __raw_spin_trylock_bh(raw_spinlock_t *lock)

  69. {

  70. local_bh_disable();

  71. preempt_disable();

  72. if (do_raw_spin_trylock(lock)) {

  73. spin_acquire(&lock->dep_map, 0, 1, _RET_IP_);

  74. return 1;

  75. }

  76. preempt_enable_no_resched();

  77. local_bh_enable_ip((unsigned long)__builtin_return_address(0));

  78. return 0;

  79. }

 

小结

spin_lock 的时候,禁止内核抢占

如果涉及到中断上下文的访问,spin lock需要和禁止本 CPU 上的中断联合使用(spin_lock_irqsave / spin_unlock_irqstore

涉及 half bottom 使用:spin_lock_bh / spin_unlock_bh

你可能感兴趣的:(自旋锁使用场景和实现分析(转载))