原子操作


原子操作,指的是一段不可打断的执行。也就是你不可分割的操作。

在 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;
}

所以,这里我们需要着重分析两条汇编:

LDREXSTREX 

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 内核同步(一):原子操作相关推荐

  1. linux 内核同步--理解原子操作、自旋锁、信号量(可睡眠)、读写锁、RCU锁、PER_CPU变量、内存屏障

    内核同步 内核中可能造成并发的原因: 中断–中断几乎可以在任何时刻异步发生,也就可以随时打断当前正在执行的代码. 软中断和tasklet–内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在 ...

  2. Linux内核同步机制之(四):spin lock【转】

    转自:http://www.wowotech.net/kernel_synchronization/spinlock.html 一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享 ...

  3. linux内核同步问题

    linux内核同步问题 Linux内核设计与实现 十.内核同步方法 [手把手教Linux驱动5-自旋锁.信号量.互斥体概述]() 基础概念: 并发:多个执行单元同时进行或多个执行单元微观串行执行,宏观 ...

  4. linux 内核互斥体,Linux 内核同步(六):互斥体(mutex)

    互斥体 互斥体是一种睡眠锁,他是一种简单的睡眠锁,其行为和 count 为 1 的信号量类似.(关于信号量参考:Linux 内核同步(四):信号量 semaphore). 互斥体简洁高效,但是相比信号 ...

  5. Linux内核同步机制之信号量与锁

    Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环境来提高操作系统效率.首先,看看我们最熟悉的两种机制--信号量.锁. 一.信号量 首先还是看看内核中是怎么 ...

  6. Linux内核同步 - Read/Write spin lock

    一.为何会有rw spin lock? 在有了强大的spin lock之后,为何还会有rw spin lock呢?无他,仅仅是为了增加内核的并发,从而增加性能而已.spin lock严格的限制只有一个 ...

  7. Linux内核同步:RCU

    linux内核 RCU机制详解 简介 RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用.RCU主要针对的数据对象是链表,目的是提高遍历读取数据的 ...

  8. Linux 内核同步(七):RCU机制

    简介 RCU 的全称是(Read-Copy-Update),意在读写-复制-更新,在 Linux 提供的所有内核互斥的设施当中属于一种免锁机制.在之前讨论过的读写自旋锁(rwlock).顺序锁(seq ...

  9. linux 内核 同步机制

    原子操作   原子操作是由编译器来保证的,保证一个线程对数据的操作不会被其他线程打断.    自旋锁 原子操作只能用于临界区只有一个变量的情况,实际应用中,临界区的情况要复杂的多.对于复杂的临界区,L ...

最新文章

  1. 服务器系统日志4625,win2008 r2 成千上万的“审核失败”日志 事件ID 4625
  2. iphone开发小技巧,转载
  3. [工具]sublime text2-前端开发利器
  4. 当思科交换机密码遗忘之后......(附图)
  5. Cannot find or open the PDB file
  6. 预告片:裸指关节SOA
  7. 负数比较大小_人教版六下【第一单元】负数比较负数的大小
  8. BZOJ 1444 [JSOI2009]有趣的游戏 (Trie图/AC自动机+矩阵求逆)
  9. Python import容易犯的一个错误
  10. 【Python实例第12讲】谱系共聚类法
  11. java断言的例子_Java 8 谓词/断言的例子
  12. 浅谈地下污水处理厂电气特点和能效管理系统的实际应用
  13. ndows 内存诊断工具,windows内存诊断工具有什么作用
  14. JS计算两个数组的交集、差集、并集、补集(多种实现方式)
  15. 26岁,2020 - 观《人生七年》
  16. 2022 iapp 小易工具箱源码
  17. ubuntu系统如何连接到服务器,远程ubuntu系统怎么连接到服务器
  18. 第六章 图论 AcWing 1635. 最大集团
  19. 为了让孩子入门编程,Scratch的设计者操碎了心!谈谈Scratch编程环境和语言中的设计理念
  20. 今日干货|如何自学视频剪辑(自学视频剪辑容易吗)

热门文章

  1. 随机信号的参数估计(AR模型)
  2. 使用isolinux制作Linux启动光盘
  3. 为什么需要开发X 波段带通滤波器
  4. ROS2 spin_some, spin_once, and spin_until_future的不同地方
  5. axios 美[æk‘sioʊz]
  6. 解决云服务器上go-cqhttp扫码登录QQ失败问题
  7. .NetCore之AutoMapper进阶篇
  8. 六年级计算机考试总结,六年级计算机考试卷.doc
  9. 赫夫曼树(Haffman)及其运用
  10. 2016中国高校计算机大赛——大数据挑战赛极客奖:COM团队