首页 > 其他分享 >自旋锁spinlock的实现

自旋锁spinlock的实现

时间:2024-05-05 16:13:05浏览次数:13  
标签:号码 实现 lock CPU preempt 自旋 spin spinlock

自旋锁,顾名思义:自己在原地打转,等待资源可用,一旦可用就上锁霸占它。

问题来了,假设别人已经上锁了,你原地打转会占住CPU资源了,别的程序怎么运行?它没有CPU怎么解锁?

这个问题,有2个答案:

① 原地打转的是CPU x,以后CPU y会解锁:这涉及多个CPU,适用于SMP系统;

② 对于单CPU系统,自旋锁的“自旋”功能就去掉了:只剩下禁止抢占、禁止中断

我先禁止别的线程来打断我(preempt_disable),我慢慢享用临界资源,用完再使能系统抢占(preempt_enable),这样别人就可以来抢资源了。

 

注意:SMP就是Symmetric Multi-Processors,对称多处理器;UP即Uni-Processor,系统只有一个单核CPU。

 

要理解spinlock,要通过2个情景来分析:

① 一开始,怎么争抢资源?不能2个程序都抢到。

这挺好解决,使用原子变量就可以实现。

 

② 某个程序已经获得资源,怎么防止别人来同时使用这个资源。

这是使用spinlock时要注意的地方,对应会有不同的衍生函数(_bh/_irq/_irqsave/_restore)。

1 自旋锁的内核结构体

spinlock对应的结构体如下定义,不同的架构可能有不同的实现:

 

上述__raw_tickets结构体中有owner、next两个成员,这是在SMP系统中实现spinlock的关键。

 

2 spinlockUP系统中的实现

对于“自旋锁”,它的本意是:如果还没获得锁,我就原地打转等待。等待谁释放锁?

① 其他CPU

② 其他进程/线程

 

对于单CPU系统,没有“其他CPU”;如果内核不支持preempt,当前在内核态执行的线程也不可能被其他线程抢占,也就“没有其他进程/线程”。所以,对于不支持preempt的单CPU系统,spin_lock是空函数,不需要做其他事情。

 

如果单CPU系统的内核支持preempt,即当前线程正在执行内核态函数时,它是有可能被别的线程抢占的。这时spin_lock的实现就是调用“preempt_disable()”:你想抢我,我干脆禁止你运行。

在UP系统中,spin_lock函数定义如下:

 

 

从以上代码可知,在UP系统中spin_lock()就退化为preempt_disable(),如果用的内核不支持preempt,那么spin_lock()什么事都不用做。

 

对于spin_lock_irq(),在UP系统中就退化为local_irq_disable()和preempt_disable(),如下图所示:

 

假设程序A要访问临界资源,可能会有中断也来访问临界资源,可能会有程序B也来访问临界资源,那么使用spin_lock_irq()来保护临界资源:先禁止中断防止中断来抢,再禁止preempt防止其他进程来抢。

 

对于spin_lock_bh(),在UP系统中就退化为禁止软件中断和preempt_disable(),如下图所示:

对于spin_lock_irqsave,它跟spin_lock_irq类似,只不过它是先保存中断状态再禁止中断,如下:

 

对应的spin_unlock函数就不再讲解。

3 spinlock在SMP系统中的实现

要让多CPU中只能有一个获得临界资源,使用原子变量就可以实现。但是还要保证公平,先到先得。比如有CPU0、CPU1、CPU2都调用spin_lock想获得临界资源,谁先申请谁先获得。

 

要想理解SMP系统中spinlock的实现,得举一个例子。感谢这篇文章:

Linux内核同步机制之(四):spin lock

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

wowotech真是一个神奇的网站,里面Linux文章的作者统一标为“linuxer”,牛!

 

我借用这篇文章的例子讲解,餐厅里只有一个座位,去吃饭的人都得先取号、等叫号。注意,有2个动作:顾客从取号机取号,电子叫号牌叫号。

① 一开始取号机待取号码为0

② 顾客A从取号机得到号码0,电子叫号牌显示0,顾客A上座;

取号机显示下一个待取号码为1。

③ 顾客B从取号机得到号码1,电子叫号牌还显示为0,顾客B等待;

取号机显示下一个待取号码为2。

④ 顾客C从取号机得到号码2,电子叫号牌还显示为0,顾客C等待;

取号机显示下一个待取号码为3。

⑤ 顾客A吃完离座,电子叫号牌显示为1,顾客B的号码等于1,他上座;

⑥ 顾客B吃完离座,电子叫号牌显示为2,顾客C的号码等于2,他上座;

在这个例子中有2个号码:取号机显示的“下一个号码”,顾客取号后它会自动加1;电子叫号牌显示“当前号码”,顾客离座后它会自动加1。某个客户手上拿到的号码等于电子叫号牌的号码时,该客户上座。

在这个过程中,即使顾客B、C同时到店,只要保证他们从取号机上得到的号码不同,他们就不会打架。

所以,关键点在于:取号机的号码发放,必须互斥,保证客户的号码互不相同。而电子叫号牌上号码的变动不需要保护,只有顾客离开后它才会变化,没人争抢它。

 

在ARMv6及以上的ARM架构中,支持SMP系统。它的spinlock结构体定义如下:

 

 

owner就相当于电子叫号牌,现在谁在吃饭。next就当于于取号机,下一个号码是什么。每一个CPU从取号机上取到的号码保存在spin_lock函数中的局部变量里。

 

spin_lock函数调用关系如下,核心是arch_spin_lock:

 

arch_spin_lock代码如下:

 

 

图中的注释把原理讲得非常清楚了,即使不同的个体去同时取号,也可以保证取到的号码各不相同。

假设第1个程序取到了号码,它访问了临界资源后,调用spin_unlock,代码如下:

 

假如有其他程序正在spin_lock函数中循环等待,它就会立刻判断自己手上的next是否等于lock->tickets.owner,如果相等就表示输到它获得了锁。

 

 

深入分析_linux_spinlock_实现机制

https://blog.csdn.net/electrombile/article/details/51289813

 

深入分析Linux自旋锁

http://blog.chinaunix.net/uid-20543672-id-3252604.html

 

Linux内核同步机制之(四):spin lock

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

标签:号码,实现,lock,CPU,preempt,自旋,spin,spinlock
From: https://www.cnblogs.com/liusiluandzhangkun/p/18173563

相关文章

  • UE4 -- 实现用于网络连接的插件
    插件UE中的插件就相当于一个模块,在引擎界面点击创建新的插件后,会在项目文件夹中生成插件的文件夹,在该文件夹内,只需要像游戏项目一样编写插件逻辑,最后在插件选择界面开启该插件即可当新建插件后,UE会自动生成继承于IModuleInterface的类,说明该文件夹的内容为插件。在InsideUE4中......
  • SSM教务管理系统设计与实现(附源码下载地址)
    @目录01项目背景02使用技术03运行环境04功能分析05数据库设计06项目工程结构07部分功能展示及源码7.1登录页7.2管理员端--首页7.3管理员端--课程管理7.4管理员端--学生管理7.5教师端--首页7.6教师端--个人信息7.7学生端--已修课程7.8学生端--公告管理08运行教程09......
  • 网页布局------轮播图效果实现
    纯css实现轮播图可以看这里:纯css实现轮播图(自动轮播和手动轮播)效果_☆*往事随風*☆的博客_css轮播图-CSDN博客代码来源:html+css+jquery实现轮播图自动切换、左右切换、点击切换_jquery图片轮播幻灯片效果实现左右滚动图片切换代码-CSDN博客JS实现轮播图的三种简单方法。_js轮播......
  • arduino uno+LCD12864(ST7735S)+蓝牙模块实现贪吃蛇
    1.前言:1.1本实验实现的贪吃蛇能穿越边界,结束游戏的唯一条件是贪吃蛇到达指定长度1.2本实验所用LCD可能不是LCD12864,LCD12864所用库为u8glib,笔者在词库中并没有找到型号为ST77355的初始化函数,而是在ucglib中找到,其方法为Ucglib_ST7735_18x128x160_SWSPIucg(/*sclk=*/13,/*data......
  • 通过劫持线程arena实现任意地址分配 n1ctf2018_null
    通过劫持线程arena,当堆开了一个线程之后,如果没有做好保护随之的危险也悄然而至❗BUU上的n1ctf2018_null很好的说明了这个问题题目链接:BUUCTF在线评测(buuoj.cn)看一下保护:除了pie保护剩下的保护全开了,64位ida载入看一下上来是一个输入密码,密码是i'mreadyforchallenge......
  • 如何使用ISqlSugarClient进行数据访问,并实现了统一的批量依赖注入
    仓储层当前有接口IRepository<T>抽象类 BaseRepository<T>业务逻辑层有抽象类BaseBusiness<M,E>接口IBusiness<M,E>,其中使用ISqlSugarClient,其中还有E表示BaseEntity,M为BaseDto请用C#给出一个案例,支持不同表对应不同的业务逻辑层,然后不同仓储实例,不同表的业务,都实现......
  • 利用两个栈实现队列结构——“先进先出”
    请利用两个找sl和s2来模拟一个队列,假设栈中元素为int型,栈中元素最多为maxSize。已知栈的3个运算定义如下。push(ST,x):元素x入ST栈。pop(ST,&x):ST栈顶元素出栈,赋给变量x。isEmpty(ST):判断ST栈是否为空。如何利用栈的运算来实现该队列的3个运算:enQueue(元......
  • 基于Lua实现类的继承
    --参考Lua官方给的代码http://lua-users.org/wiki/ClassesAndMethodsExample学习如何基于Lua脚本实现继承和面向对象的机制。下面是测试代码。--objecttest.luarequire("INC_Class")--===========================localcAnimal=SETclass("Animal")functioncAni......
  • 指针实现字符串匹配
    #include<stdio.h>voidcomp(char*sub,char*str){inti=0,j=0;//通过子串指针移动的次数等于字串的长度,实现匹配成功与否//下面代码是直接使用子串和主串是否同时用完子串长度的循环实现while(*str){for(i=0;*(sub+i)==*(str+i);i++)//判断子串......
  • 2024-05-04 css实现鼠标移动至盒子,盒子在约定时间内进行放大缩小
    放大缩小css@keyframesscaleAnimation{0%{transform:scale(1);}50%{transform:scale(1.2);}100%{transform:scale(1);}}完整代码:<!DOCTYPEhtml><htmllang="en"><head><metacharset=&q......