自旋锁

上回,我们说到为了避免并发,防止竞争,内核提供了一些方法来实现对内核共享数据的保护。如果临界区只是一个变量,那么使用原子操作即可,但实际上临界区大多是一些数据操作的集合,这时候使用原子操作不太合理,我们就要用到锁机制。Linux内核中常见的锁机制有忙等待和睡眠锁两种,锁代表分别为自旋锁和信号量,本文主要介绍自旋锁。

自旋锁同一时刻只能被一个内核任务持有,一个内核任务试图获得一个自旋锁,如果该锁没有被其它内核任务争用,那么它就可以马上获得这个自旋锁,如果该锁被其他内核任务争用,那么它就一直在忙等待(自旋等待,占用CPU时间),直到另一个内核任务释放该自旋锁。


自旋锁常用于多处理器保护临界区资源,在内核中主要用于中断处理,它适用于短期轻量级锁定,若是长时间锁定最好用信号量。

自旋锁的特性

  • 忙等待
  • 同一时刻一个任务一把锁
  • 尽快完成临界区任务
  • 自旋锁可在中断上下文中使用

自旋锁的定义
自旋锁定义的数据结构考虑了不同处理器体系结构的支持和实时性内核的要求,定义了 arch_spinlock_t 数据结构和 raw_spinlock 数据结构。在4.19内核中,自旋锁 spinlock 数据结构定义在include\linux\spinlock_types.h中,spinlock 结构定义如下:

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 对 raw_spinlock 做了封装,再看raw_spinlock 结构:

typedef struct raw_spinlock {arch_spinlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCKunsigned int magic, owner_cpu;void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOCstruct lockdep_map dep_map;
#endif
} raw_spinlock_t;

可以看到 raw_spinlock 对 arch_spinlock_t 做了封装,再看 arch_spinlock_t 结构,在4.19内核中,arch_spinlock_t 结构定义在include\linux\spinlock_types_up.h中,arch_spinlock_t 结构定义如下:

#ifdef CONFIG_DEBUG_SPINLOCKtypedef struct {volatile unsigned int slock;
} arch_spinlock_t;#define __ARCH_SPIN_LOCK_UNLOCKED { 1 }#elsetypedef struct { } arch_spinlock_t;#define __ARCH_SPIN_LOCK_UNLOCKED { }#endiftypedef struct {/* no debug version on UP */
} arch_rwlock_t;

对于以上结构:

  • 不同的处理器架构需要自定义数据类型 arch_spinlock_t ;
  • 尽量使用 spinlock ;
  • 绝对不允许被抢占和睡眠的地方,使用raw_spinlock,否则使用spinlock ;
  • 如果临界区足够小,使用raw_spinlock。

自旋锁的使用
简单的使用举例

DEFINE_SPINLOCK(my_lock); //定义一个自旋锁
spin_lock(&my_lock); //加锁
/*
/*临界区*/
*/
spin_unlock(&my_locl); //解锁

自旋锁变种
请大家先设想这样一种情况,我们在内核中操作一个链表,此时操作链表的地方就是一个临界区,为了防止多处理器竞争,我们使用自旋锁来进行保护。当处于临界区时,突然发生了外部硬件中断,此时系统暂停当前进程的执行,转去处理该中断。没想到的是,这个外部中断也要操作这个链表,它首先要保护这个临界区,就去申请自旋锁了。可是,这个自旋锁已经被别人持有了,中断处理函数只能在那里忙等待,而自旋锁又被中断打断而不能尽快释放锁,这时候就发生了死锁。

这时候就出现了Linux内核自旋锁的变种spin_lock_irq() ,该函数在获取自旋锁时关闭本地CPU中断,防止本地中断处理程序和自旋锁持有者之间争用锁,其定义在4.19版内核include\linux\spinlock.h中,具体定义如下:

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

raw_spin_lock_irq() 函数定义如下:

#define raw_spin_lock_irq(lock)      _raw_spin_lock_irq(lock)

_raw_spin_lock_irq() 函数定义在4.19内核include\linux\spinlock_api_smp.h中:

#ifdef CONFIG_INLINE_SPIN_LOCK_IRQ
#define _raw_spin_lock_irq(lock) __raw_spin_lock_irq(lock)
#endif

__raw_spin_lock_irq() 函数定义如下:

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

spin_lock_irq() 函数比 spin_lock() 函数,多了一个 local_irq_disable() 函数,作用就是关闭本地处理器中断,这样就可以保证在获取到自旋锁时不会发生中断,从而避免发生死锁。

使用自旋锁的重要原则是:拥有自旋锁的临界区代码必须是原子执行,不能休眠或主动调度。

常见自旋锁操作函数如下:

函数 作用
DEFINE_SPINLOCK(lock) 定义并且初始化静态自旋锁
spin_lock_init(spinlock_t *lock) 在运行时动态初始化自旋锁
spin_lock(spinlock_t *lock) 申请自旋锁,如果锁被其他处理器占有,当前处理器自旋等待
spin_unlock(spinlock_t *lock) 释放自旋锁
spin_lock_bh(spinlock_t *lock) 申请自旋锁,并且禁止当前处理器的软中断
spin_unlock_bh(spinlock_t *lock) 释放自旋锁,并且开启当前处理器的软中断
spin_lock_irq(spinlock_t *lock) 申请自旋锁,并且禁止当前处理器的硬中断
spin_unlock_irq(spinlock_t *lock) 释放自旋锁,并且开启当前处理器的硬中断
spin_lock_irqsave(spinlock_t *lock,unsigned long flags) 申请自旋锁,保存当前处理器的硬中断状态,并且禁止当前处理器的硬中断
spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) 释放自旋锁,并且恢复当前处理器的硬中断状态
spin_trylock(spinlock_t *lock) 申请自旋锁,如果申请成功,返回1;如果锁被其他处理器占有,当前处理器不等待,立即返回0

Linux内核之内核同步(三)——自旋锁相关推荐

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

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

  2. Linux内核的同步机制---自旋锁

    自旋锁的思考:http://bbs.chinaunix.net/thread-2333160-1-1.html 近期在看宋宝华的<设备驱动开发具体解释>第二版.看到自旋锁的部分,有些疑惑. ...

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

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

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

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

  5. Linux线程同步(三)---互斥锁源码分析

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

  6. Linux环境编程之同步(三):读写锁

    概述 相互排斥锁把试图进入我们称之为临界区的全部其它线程都堵塞住.该临界区通常涉及对由这些线程共享一个或多个数据的訪问或更新.读写锁在获取读写锁用于读某个数据和获取读写锁用于写直接作差别. 读写锁的分 ...

  7. Linux内核中的同步原语:自旋锁,信号量,互斥锁,读写信号量,顺序锁

    Linux内核中的同步原语 自旋锁,信号量,互斥锁,读写信号量,顺序锁 rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. 1. Linux 内核中的同步原 ...

  8. linux内核自旋锁解释,LINUX内核笔记:自旋锁

    目录 1.自旋锁作用与基本使用方法? 与其他锁一样,自旋锁也用于保护临界区,但是自旋锁主要是用于在SMP上保护临界区.在SMP上,自旋锁最多只能被一个可执行线程持有,如果一个线程尝试获得一个被争用的自 ...

  9. linux 内核 死锁 检查,一种linux内核自旋锁死锁检测报告系统和方法与流程

    本发明涉及内核死锁检测领域,具体的说是一种linux内核自旋锁死锁检测报告系统和方法. 背景技术: linux内核死锁是长期困扰内核开发人员的问题之一,但自内核引入lockdep调试模块之后,内核死锁 ...

最新文章

  1. 《从0到1学习Flink》—— Flink Data transformation(转换)
  2. 【机器学习】关联规则代码练习
  3. go mod引用git仓库中的包:拉取存放在gitee中的package
  4. iti axi dsp_ITI的完整形式是什么?
  5. 洛谷P4831 Scarlet loves WenHuaKe
  6. javascript textContent与innerText的异同分析
  7. Axure中引入Echarts图表并制作元件库
  8. linux系统运行3dmax,Linux下3D桌面的效果的实现
  9. CDR插件开发之Addon插件004 - VS2022开发环境简介及个性化配置
  10. 论文重复率太高怎么降重修改
  11. 脱光解决方案——一枚大佬一枚白骨精
  12. 《浊酒一杯忆往昔, 似水流年探追忆》
  13. 很「佛系」的商用本 — ThinkPad S2 2020 长测
  14. java定义一个日期类 包括年 月 日_【说明】 设计一个日期类Date包括年、月、日等私有数据成员。要求实现日期..._考试资料网...
  15. 《C Primer Plus》—第九章:函数(指针间接,函数及其定义方式,ANSI C原型,递归,函数调用的底层原理)
  16. 百度网盘网页端视频倍速方法
  17. Windows7+Ubuntu10.04双系统安装指南
  18. arpspoof实现内网欺骗
  19. NachOS线程ID的实现、最大线程数的实现和优先级的添加
  20. 瓦力机器人故障维修_机器人瓦力让人无力吐槽的坑爹剧情!

热门文章

  1. 浅析制造业物料编码在ERP系统中的实施
  2. 网站故障排查常用命令
  3. 一秒完成充电,超级量子电池即将问世
  4. CentOS7重新生成 /boot/grub2/grub.cfg
  5. 变量声明和定义有什么区别
  6. iphone6 iphone6 plus 放大显示模式高分辨率模式问题
  7. Hlink的Analysis基本搞定了
  8. photoshop ps 钢笔工具抠出图 复制出来 方法
  9. linux 加密库 libsodium 安装
  10. golang map嵌套struct 结构体字段 不能直接修改 解决方法