自旋锁


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

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

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

参考文档:


http://www.wowotech.net/kernel_synchronization/spinlock.html

https://blog.csdn.net/arm7star/article/details/77092650

Linux 内核同步(二):自旋锁(Spinlock)相关推荐

  1. Linux内核同步机制--自旋锁【转】

    本文转载自:http://www.cppblog.com/aaxron/archive/2013/04/12/199386.html 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已 ...

  2. 探秘最新Linux内核中的自旋锁

    一.前言 目前最新内核中的自旋锁已经进化成queued spinlock,因此需要一篇新的自旋锁文档来跟上时代.此外,本文将不再描述基本的API和应用场景,主要的篇幅将集中在具体的自旋锁实现上.顺便说 ...

  3. LINUX内核之普通自旋锁

    LINUX内核之普通自旋锁 @CopyLeft by ICANTH,I Can do ANy THing that I CAN THink!~ Author:WenHui,WuHan Universi ...

  4. 自旋锁 Linux内核,Linux内核中的自旋锁

    自旋锁不会引起睡眠,当一个进程在访问内核无法获取自旋锁时,会进入忙循环,一直等待下去. 实例: Pcilynx.c (\linux-2.6.30.4\drivers\ieee1394) 1 声明 st ...

  5. 内核同步机制——自旋锁

    由于关键代码区可以跨越了多个函数或数据结构,需要有更通用的同步方法:锁. 内核中最常见的一种锁就是自旋锁.相同的锁可用于多处. 自旋锁可用在不可睡眠的场景,如中断处理函数.自旋锁是一种互斥设备,只有两 ...

  6. linux内核 task_struct 中自旋锁的应用

    自旋锁是用于保护短的代码片段,其中只包含少量C语句,因此会很快执行完毕.大多数内核数据结构都有自身的自旋锁,在处理结构中的关键成员时,必须获得相应的自旋锁. 定义自旋锁 struct task_str ...

  7. Linux线程同步(二)---互斥锁实现线程同步

    一 why 先给自己打个广告,本人的微信公众号:嵌入式Linux江湖,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题. 在博客&l ...

  8. linux驱动22:自旋锁spinlock

    自旋锁: 和信号量不同的是自旋锁可在不能休眠的代码中使用,如中断处理例程.在正确使用时,自旋锁通常比信号量具有更高的性能. 如果锁可用,则锁定位被设置,代码继续进入临界区:相反则代码进入忙循环并重复检 ...

  9. linux线程同步(4)-自旋锁

    自旋锁与互斥量功能一样,唯一一点不同的就是互斥量阻塞后休眠让出cpu,而自旋锁阻塞后不会让出cpu,会一直忙等待,直到得到锁!!! 自旋锁在用户态使用的比较少,在内核使用的比较多!自旋锁的使用场景:锁 ...

  10. linux 进程调度 运行队列 自旋锁,linux内核进程调度(自旋锁)

    2.1首先让我们了解,操作系统分为两类:一类是实时操作系统,一类是分时操作系统.它们的共同特点是都是多任务的 .多任务操作系统分为两类:非抢占式多任务和抢占式多任务. 非抢占式多任务,就是指进程不断的 ...

最新文章

  1. 逆向知识十三讲,汇编中数组的表现形式,以及还原数组
  2. docker 如何删除<none>镜像
  3. VC6中用DOM遍历网页中的元素
  4. 【TypeScript系列教程14】Array数组对象的常见的方法
  5. emoji表情引发的JNI崩溃
  6. 前端学习(1355) 子模板
  7. 初窥Spring中的注释
  8. 电磁计算仿真方法和FEKO软件简介
  9. 【工作技巧】WinRAR去除广告
  10. C#实现百度地图瓦片下载器(更新无水印版下载地址)
  11. 非常好用的节假日查询接口
  12. android手写计算器,MyScript Calculator(高级手写计算器) V1.2.2.479 安卓版
  13. 【ZZULIOJ】1116: 删除元素
  14. 省级税务大数据平台应用建设的分析与思考
  15. 程序员实习期馒头加酸菜,转正后月薪10K起步:走路都带风
  16. virtualbox安装ubuntu时,not syncing: attempt to kill the idle task
  17. iOS、iPadOS、macOS屏蔽系统更新
  18. Android通知栏微技巧,8.0系统中通知栏的适配
  19. 论文“Matrix Formulation for Minimum Response of Undamped Structures”参考代码
  20. 任务栏没有显示解决方案

热门文章

  1. Key-Value Store Indexer(Lily HBase Indexer) 小型采坑
  2. 记一次与为知笔记的客服沟通
  3. list集合去重复元素
  4. 深入解读Python的unittest并拓展HTMLTestRunner
  5. discuz uc密码修改
  6. 【Sprint3冲刺之前】TD学生助手测试用例
  7. oracle锁表查询_专业解决 MySQL 查询速度慢与性能差
  8. 神经网络迭代次数与Lambert定律
  9. 学习率对神经网络迭代次数和准确率的影响以及近似数学表达式
  10. 基于mqtt协议的消息推送服务器,基于 MQTT 协议的推送服务