Linux 内核同步(一):原子操作
原子操作
原子操作,指的是一段不可打断的执行。也就是你不可分割的操作。
在 Linux 上提供了原子操作的结构,atomic_t :
typedef struct {int counter;
} atomic_t;
把整型原子操作定义为结构体,让原子函数只接收atomic_t类型的参数进而确保原子操作只与这种特殊类型数据一起使用,同时也保证了该类型的数据不会被传递给非原子函数。
初始化并定义一个原子变量:
atomic_t v = ATOMIC_INIT(0);
基本调用
Linux 为原子操作提供了基本的操作宏函数:
atomic_inc(v); // 原子变量自增1
atomic_dec(v); // 原子变量自减1
atomic_read(v) // 读取一个原子量
atomic_add(int i, atomic_t *v) // 原子量增加 i
atomic_sub(int i, atomic_t *v) // 原子量减少 i
这里定义的都是通用入口,真正的操作和处理器架构体系相关,这里分析 ARM 架构体系的实现:
static inline void atomic_inc(atomic_t *v)
{atomic_add_return(1, v);
}
具体实现
对于 add 来说:
#define atomic_inc_return(v) atomic_add_return(1, (v))
static inline int atomic_add_return(int i, atomic_t *v)
{unsigned long tmp;int result;smp_mb();__asm__ __volatile__("@ atomic_add_return\n""1: ldrex %0, [%3]\n" /*【1】独占方式加载v->counter到result*/" add %0, %0, %4\n" /*【2】result加一*/" strex %1, %0, [%3]\n" /*【3】独占方式将result值写回v->counter*/" teq %1, #0\n" /*【4】判断strex更新内存是否成*/" bne 1b" /*【5】不成功跳转到1:*/: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter) /*输出部*/: "r" (&v->counter), "Ir" (i) /*输入部*/: "cc"); /*损坏部*/smp_mb();return result;
}
所以,这里我们需要着重分析两条汇编:
LDREX 和 STREX
LDREX和STREX指令,是将单纯的更新内存的原子操作分成了两个独立的步骤。
1)LDREX 用来读取内存中的值,并标记对该段内存的独占访问:
LDREX Rx, [Ry]
上面的指令意味着,读取寄存器Ry指向的4字节内存值,将其保存到Rx寄存器中,同时标记对Ry指向内存区域的独占访问。如果执行LDREX指令的时候发现已经被标记为独占访问了,并不会对指令的执行产生影响。
2)STREX 在更新内存数值时,会检查该段内存是否已经被标记为独占访问,并以此来决定是否更新内存中的值:
STREX Rx, Ry, [Rz]
如果执行这条指令的时候发现已经被标记为独占访问了,则将寄存器Ry中的值更新到寄存器Rz指向的内存,并将寄存器Rx设置成0。指令执行成功后,会将独占访问标记位清除。
而如果执行这条指令的时候发现没有设置独占标记,则不会更新内存,且将寄存器Rx的值设置成1。
一旦某条STREX指令执行成功后,以后再对同一段内存尝试使用STREX指令更新的时候,会发现独占标记已经被清空了,就不能再更新了,从而实现独占访问的机制。
在ARM系统中,内存有两种不同且对立的属性,即共享(Shareable)和非共享(Non-shareable)。共享意味着该段内存可以被系统中不同处理器访问到,这些处理器可以是同构的也可以是异构的。而非共享,则相反,意味着该段内存只能被系统中的一个处理器所访问到,对别的处理器来说不可见。
为了实现独占访问,ARM系统中还特别提供了所谓独占监视器(Exclusive Monitor)的东西,
一共有两种类型的独占监视器。每一个处理器内部都有一个本地监视器(Local Monitor),且在整个系统范围内还有一个全局监视器(Global Monitor)。
如果要对非共享内存区中的值进行独占访问,只需要涉及本处理器内部的本地监视器就可以了;而如果要对共享内存区中的内存进行独占访问,除了要涉及到本处理器内部的本地监视器外,由于该内存区域可以被系统中所有处理器访问到,因此还必须要由全局监视器来协调。
对于本地监视器来说,它只标记了本处理器对某段内存的独占访问,在调用LDREX指令时设置独占访问标志,在调用STREX指令时清除独占访问标志。
而对于全局监视器来说,它可以标记每个处理器对某段内存的独占访问。也就是说,当一个处理器调用LDREX访问某段共享内存时,全局监视器只会设置针对该处理器的独占访问标记,不会影响到其它的处理器。当在以下两种情况下,会清除某个处理器的独占访问标记:
1)当该处理器调用LDREX指令,申请独占访问另一段内存时;
2)当别的处理器成功更新了该段独占访问内存值时。
对于第二种情况,也就是说,当独占内存访问内存的值在任何情况下,被任何一个处理器更改过之后,所有申请独占该段内存的处理器的独占标记都会被清空。
另外,更新内存的操作不一定非要是STREX指令,任何其它存储指令都可以。但如果不是STREX的话,则没法保证独占访问性。
现在的处理器基本上都是多核的,一个芯片上集成了多个处理器。而且对于一般的操作系统,系统内存基本上都被设置上了共享属性,也就是说对系统中所有处理器可见。因此,我们这里主要分析多核系统中对共享内存的独占访问的情况。
为了更加清楚的说明,我们可以举一个例子。假设系统中有两个处理器内核,而一个程序由三个线程组成,其中两个线程被分配到了第一个处理器上,另外一个线程被分配到了第二个处理器上。且他们的执行序列如下:
大致经历的步骤如下:
1)CPU2上的线程3最早执行LDREX,锁定某段共享内存区域。它会相应更新本地监视器和全局监视器。
2)然后,CPU1上的线程1执行LDREX,它也会更新本地监视器和全局监视器。这时在全局监视器上,CPU1和CPU2都对该段内存做了独占标记。
3)接着,CPU1上的线程2执行LDREX指令,它会发现本处理器的本地监视器对该段内存有了独占标记,同时全局监视器上CPU1也对该段内存做了独占标记,但这并不会影响这条指令的操作。
4)再下来,CPU1上的线程1最先执行了STREX指令,尝试更新该段内存的值。它会发现本地监视器对该段内存是有独占标记的,而全局监视器上CPU1也有该段内存的独占标记,则更新内存值成功。同时,清除本地监视器对该段内存的独占标记,还有全局监视器所有处理器对该段内存的独占标记。
5)下面,CPU2上的线程3执行STREX指令,也想更新该段内存值。它会发现本地监视器拥有对该段内存的独占标记,但是在全局监视器上CPU1没有了该段内存的独占标记(前面一步清空了),则更新不成功。
6)最后,CPU1上的线程2执行STREX指令,试着更新该段内存值。它会发现本地监视器已经没有了对该段内存的独占标记(第4步清除了),则直接更新失败,不需要再查全局监视器了。
所以,可以看出来,这套机制的精髓就是,无论有多少个处理器,有多少个地方会申请对同一个内存段进行操作,保证只有最早的更新可以成功,这之后的更新都会失败。失败了就证明对该段内存有访问冲突了。实际的使用中,可以重新用LDREX读取该段内存中保存的最新值,再处理一次,再尝试保存,直到成功为止。
还有一点需要说明,LDREX和STREX是对内存中的一个字(Word,32 bit)进行独占访问的指令。如果想独占访问的内存区域不是一个字,还有其它的指令:
1)LDREXB和STREXB:对内存中的一个字节(Byte,8 bit)进行独占访问;
2)LDREXH和STREXH:中的一个半字(Half Word,16 bit)进行独占访问;
3)LDREXD和STREXD:中的一个双字(Double Word,64 bit)进行独占访问。
它们必须配对使用,不能混用。
相关参考:https://blog.csdn.net/Roland_Sun/article/details/47670099
Linux 内核同步(一):原子操作相关推荐
- linux 内核同步--理解原子操作、自旋锁、信号量(可睡眠)、读写锁、RCU锁、PER_CPU变量、内存屏障
内核同步 内核中可能造成并发的原因: 中断–中断几乎可以在任何时刻异步发生,也就可以随时打断当前正在执行的代码. 软中断和tasklet–内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在 ...
- Linux内核同步机制之(四):spin lock【转】
转自:http://www.wowotech.net/kernel_synchronization/spinlock.html 一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享 ...
- linux内核同步问题
linux内核同步问题 Linux内核设计与实现 十.内核同步方法 [手把手教Linux驱动5-自旋锁.信号量.互斥体概述]() 基础概念: 并发:多个执行单元同时进行或多个执行单元微观串行执行,宏观 ...
- linux 内核互斥体,Linux 内核同步(六):互斥体(mutex)
互斥体 互斥体是一种睡眠锁,他是一种简单的睡眠锁,其行为和 count 为 1 的信号量类似.(关于信号量参考:Linux 内核同步(四):信号量 semaphore). 互斥体简洁高效,但是相比信号 ...
- Linux内核同步机制之信号量与锁
Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环境来提高操作系统效率.首先,看看我们最熟悉的两种机制--信号量.锁. 一.信号量 首先还是看看内核中是怎么 ...
- Linux内核同步 - Read/Write spin lock
一.为何会有rw spin lock? 在有了强大的spin lock之后,为何还会有rw spin lock呢?无他,仅仅是为了增加内核的并发,从而增加性能而已.spin lock严格的限制只有一个 ...
- Linux内核同步:RCU
linux内核 RCU机制详解 简介 RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用.RCU主要针对的数据对象是链表,目的是提高遍历读取数据的 ...
- Linux 内核同步(七):RCU机制
简介 RCU 的全称是(Read-Copy-Update),意在读写-复制-更新,在 Linux 提供的所有内核互斥的设施当中属于一种免锁机制.在之前讨论过的读写自旋锁(rwlock).顺序锁(seq ...
- linux 内核 同步机制
原子操作 原子操作是由编译器来保证的,保证一个线程对数据的操作不会被其他线程打断. 自旋锁 原子操作只能用于临界区只有一个变量的情况,实际应用中,临界区的情况要复杂的多.对于复杂的临界区,L ...
最新文章
- 服务器系统日志4625,win2008 r2 成千上万的“审核失败”日志 事件ID 4625
- iphone开发小技巧,转载
- [工具]sublime text2-前端开发利器
- 当思科交换机密码遗忘之后......(附图)
- Cannot find or open the PDB file
- 预告片:裸指关节SOA
- 负数比较大小_人教版六下【第一单元】负数比较负数的大小
- BZOJ 1444 [JSOI2009]有趣的游戏 (Trie图/AC自动机+矩阵求逆)
- Python import容易犯的一个错误
- 【Python实例第12讲】谱系共聚类法
- java断言的例子_Java 8 谓词/断言的例子
- 浅谈地下污水处理厂电气特点和能效管理系统的实际应用
- ndows 内存诊断工具,windows内存诊断工具有什么作用
- JS计算两个数组的交集、差集、并集、补集(多种实现方式)
- 26岁,2020 - 观《人生七年》
- 2022 iapp 小易工具箱源码
- ubuntu系统如何连接到服务器,远程ubuntu系统怎么连接到服务器
- 第六章 图论 AcWing 1635. 最大集团
- 为了让孩子入门编程,Scratch的设计者操碎了心!谈谈Scratch编程环境和语言中的设计理念
- 今日干货|如何自学视频剪辑(自学视频剪辑容易吗)
热门文章
- 随机信号的参数估计(AR模型)
- 使用isolinux制作Linux启动光盘
- 为什么需要开发X 波段带通滤波器
- ROS2 spin_some, spin_once, and spin_until_future的不同地方
- axios 美[æk‘sioʊz]
- 解决云服务器上go-cqhttp扫码登录QQ失败问题
- .NetCore之AutoMapper进阶篇
- 六年级计算机考试总结,六年级计算机考试卷.doc
- 赫夫曼树(Haffman)及其运用
- 2016中国高校计算机大赛——大数据挑战赛极客奖:COM团队