自旋锁


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

引入问题:

(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

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

  1. 自旋锁替代互斥锁使用场景

    自旋锁与互斥锁 自旋锁与互斥锁 理论分析 互斥锁的问题 自旋锁应用场景 自旋锁实践 总结 自旋锁与互斥锁 自旋锁和互斥锁是多线程程序中的重要概念. 它们被用来锁住一些共享资源, 以防止并发访问这些共享 ...

  2. 本地自旋锁与信号量/多服务台自旋队列-spin wait风格的信号量

    周日傍晚,我去家附近的超市(...)买苏打水,准备自制青柠苏打.我感觉我做的比买的那个巴黎水要更爽口.由于天气太热,非常多人都去超市避暑去了,超市也不撵人,这仿佛是他们的策略.人过来避暑了,走的时候难 ...

  3. java中的锁(悲观锁、乐观锁、可重入锁、不可重入锁、公平锁、非公平锁、自旋锁、阻塞锁...)

    Lock接口 1.简介.地位.作用 ① 锁是一种工具,用于控制对共享资源的访问 ② Lock和synchronized,这两个是最常见的锁,它们都可以达到线程安全的目的,但是在使用和功能上又有较大的不 ...

  4. 自旋锁与互斥锁的使用场景分析

    本文不对自旋锁和互斥锁的概念做阐述,重点分析它们之间的区别和自旋锁的使用场景. 自旋锁和互斥锁的区别 a. 互斥锁加锁失败后,线程会释放 CPU,给其他线程:自旋锁加锁失败后,线程会忙等待,直到它拿到 ...

  5. 华为应用锁退出立即锁_面试官:你说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景...

    前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...

  6. 关抢占 自旋锁_互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景

    前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...

  7. 面试官:你说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景?

    前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...

  8. JVM-锁消除+锁粗化 自旋锁、偏向锁、轻量级锁 逃逸分析-30

    自旋锁 自旋锁其实就是一个线程自转,空转,什么都不操作,但也不挂起,在那里空循环.空循环的作用就是等待一把锁.自旋锁是明确的会产生竞争的情况下使用的. 当竞争存在时,如果线程可以很快获得锁,那么就没有 ...

  9. 互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景

    前言 在编程世界里,「锁」更是五花八门,多种多样,每种锁的加锁开销以及应用场景也可能会不同. 如何用好锁,也是程序员的基本素养之一了. 高并发的场景下,如果选对了合适的锁,则会大大提高系统的性能,否则 ...

最新文章

  1. (C++)自定义链表并写入
  2. android 上传pdf文件,Android 加载PDF文件
  3. Java 输入两个数并输出它们的和
  4. 丑憨批的爬虫笔记5信息标记与提取
  5. 如何找到Fiori Launchpad tile所属的catalog id
  6. [vue] 你了解axios的原理吗?有看过它的源码吗?
  7. 第一阶段站立会议02
  8. 如何对大数据进行数据分析
  9. python行数据转列数据_python – 如何转换数据框,以便列值是行值
  10. EtherCAT基于SOEM建立主站程序
  11. 放苹果(递归、动态规划、python)
  12. Win10下的外接显示器不能识别的解决方法
  13. 前端框架千千万,每隔两年翻一番
  14. HDU-6148 Valley Numer(数位DP)
  15. word中mathtype公式编辑
  16. 用js实现自动获取身份证里面的信息
  17. Python 打字小游戏开发,来体验不一样的打字游戏乐趣(第一篇)
  18. Muli3D 1 下载与编译
  19. 使用RDO Packstack在CentOS 8上安装OpenStack Victoria
  20. 去哪儿VS携程产品分析

热门文章

  1. 深入理解Spring IoC的原理(转发)
  2. echarts百分比柱形图
  3. 虚拟机重启服务器命令,虚拟机中重启命令
  4. sd和sem啥区别_生物统计学-标准差(SD)和标准误(SEM)有何区别.pdf
  5. pytho基础(6)
  6. ZOHO企业邮箱教程(试用30/15天)
  7. 2021 区块链行业融资:资本涌入,触发区块链应用的无限可能| 2021 区块链年报|Footprint Analytics
  8. 将服务器的EDT(美国时间)或者EST(英国时间)修改为CST(中国时间)
  9. 微信小程序开发后台篇(五)AWS EC2实例云部署---Windows环境通过PuTTY连接EC2实例部署SpringBoot工程
  10. OpenWrt/Lede 添加USB支持