操作系统中,对共享资源的访问需要有同步互斥机制来保证其逻辑的正确性,而这一切的基础便是原子操作。

| 原子操作(Atomic Operations):

    原子操作从定义上理解,应当是类似原子的,不可再分的操作;然而实际上稍有不同,较为准确的定义应当是:不可被打断的一个或一系列操作。

  在单处理器系统中,能够在单条指令中完成的操作都可以认为是“原子操作”,因为中断只发生在指令边缘。在多处理器结构中就不同了,由于系统中有多个处理器独立运行,即使能在单条指令中完成的操作也有可能受到干扰。在X86平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU上有一根引线#HLOCK pin连到北桥芯片,如果在汇编程序的指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,直到这条指令执行结束时才放开,从而将总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中对内存访问的原子性。

| 1. 为什么需要原子操作 

  我们来看一个例子,假设我们用两个线程,完成对全局变量 index 的自增至10的功能,我们这样写线程函数:

  

  执行结果如下,可以看到执行了两次,但结果却不一样(不是想象的值为10),事实上每次执行的结果都可能不一样。理论上,两个线程分别同时执行5次自增操作,只要执行正确应该总是得到10的结果,但是可以看到两个线程虽然可以交叉执行,index的值却不是从1增长到10。图中mthread程序第一次执行时,线程一将index增加到5后,线程二居然得到了2的结果,这是怎么回事?

  

  我们知道,在计算机中,变量的值是存储在内存中的,要知道一个存储在内存中的变量的值就需要读内存,需要更新这个变量的值就必须写回去。如果这整个操作是不可打断的,那么是不会出现上面的结果的,所以中间肯定是出现了什么问题。事实上i++这种操作确实不是原子的,程序编译成汇编代码后就可以看到,i++实际上是由Read(读)-Modify(改)-Write(写) 3个操作实现的:

  

  

  如上图所示,两个线程分别在两个处理器核上执行,假设i的值刚开始是0,线程1先读取i的值,放到ax寄存器中,此时线程2也读取i的值,接着add eax,1,然后回写到i的地址中,此时i的值为1;之后线程1中再执行add eax,1,再回写到i的地址,所以i的值就是1,而不是2。如果需要将i++变成原子的就需要一些额外的方式。

| 2. Linux中原子操作的实现 

  有了上述的感官认识,现在来说实现就顺理成章了。Linux中对原子操作的实现主要在 linux-2.6.28\arch\x86\include\asm 目录下的 Atomic_32.h 中,包括了一系列内联函数(因为这些函数的代码都不长,基本都采用内嵌汇编代码编写)。首先来看原子类型的数据结构 atomic_t的定义:

1 /*
2 * Make sure gcc doesn't try to be clever and move things around
3 * on us. We need to use _exactly_ the address the user gave us,
4 * not some alias that contains the same information.
5 */
6 typedef struct {
7 int counter;
8 } atomic_t;

  接着来看如何实现原子操作:

 1 /**
2 * atomic_add - add integer to atomic variable
3 * @i: integer value to add
4 * @v: pointer of type atomic_t
5 *
6 * Atomically adds @i to @v.
7 */
8 static inline void atomic_add(int i, atomic_t *v)
9 {
10 asm volatile(LOCK_PREFIX "addl %1,%0"
11 : "+m" (v->counter)
12 : "ir" (i));
13 }

  上面这段代码截自Atomic_32.h,即原子的加操作。这个函数的实现采用了内嵌汇编的方式,首先声明LOCK前缀(实际上是一个宏定义),以原子的方式执行指令addl  %1,%0,表示将输入操作数和输出操作数编号,将输入和输出相加后输出。其中“+m”中加号表示输出是可读可写的,括号内注明了是原子变量v->counter;m表示输出存储在内存之中。“ir”中 i 表示输入是一个直接操作数,r表示这里输入 i 是存储在寄存器中的。

  原子的加有了,原子的减自然也不难:

 1 /**
2 * atomic_sub - subtract integer from atomic variable
3 * @i: integer value to subtract
4 * @v: pointer of type atomic_t
5 *
6 * Atomically subtracts @i from @v.
7 */
8 static inline void atomic_sub(int i, atomic_t *v)
9 {
10 asm volatile(LOCK_PREFIX "subl %1,%0"
11 : "+m" (v->counter)
12 : "ir" (i));
13 }

  其他的原子操作就不举例了,总之原理就是这样,在有竞争的访问中,有时需要保证操作最后执行的逻辑正确性,就必须将某些操作或者指令设置为原子的。原子操作是其他的一些同步互斥机制的基础,有了原子操作就可以实现多核系统中其他的同步互斥机制了。

  参考文献:

[1].http://edsionte.com/techblog/archives/1809

[2].http://blog.csdn.net/qb_2008/article/details/6840808

Linux中同步互斥机制研究之原子操作的更多相关文章

  1. 【原创】xenomai内核解析--同步互斥机制(一)--优先级倒置

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 一.xenomai 资源管理简要 二.优先级倒 ...

  2. 浅谈Linux中的信号处理机制(二)

    首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...

  3. Linux中的保护机制

    Linux中的保护机制 在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了NX.PIE等机制,例如存在NX的话就不能直接执行栈上的数据,存在PIE 的话各个系统调用的地址就是随机化的. 一:ca ...

  4. LINUX中的RCU机制的分析

    RCU机制是Linux2.6之后提供的一种数据一致性访问的机制,从RCU(read-copy-update)的名称上看,我们就能对他的实现机制有一个大概的了解,在修改数据的时候,首先需要读取数据,然后 ...

  5. linux中的阻塞机制及等待队列

    阻塞与非阻塞是设备访问的两种方式.驱动程序需要提供阻塞(等待队列,中断)和非阻塞方式(轮询,异步通知)访问设备.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 一.阻塞与非阻塞 阻塞调用是没有获得资 ...

  6. linux中的阻塞机制及等待队列【转】

    转自:http://www.cnblogs.com/gdk-0078/p/5172941.html 阻塞与非阻塞是设备访问的两种方式.驱动程序需要提供阻塞(等待队列,中断)和非阻塞方式(轮询,异步通知 ...

  7. Linux之同步互斥阻塞20160703

    主要介绍一下Linux下的互斥与阻塞方面的知识: 1. 原子操作 原子操作指的是在执行过程中不会被别的代码路径所中断的操作. 常用原子操作函数举例: atomic_t v = ATOMIC_INIT( ...

  8. linux中的tasklet机制【转】

    转自:http://blog.csdn.net/yasin_lee/article/details/12999099 转自: http://www.kerneltravel.net/?p=143 中断 ...

  9. 总结一下linux中的分段机制

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 这篇文章主要说一下linux对于分段机制的处理,虽然都说linux不使用分段机制,但是分段机制属于CPU的一个功 ...

随机推荐

  1. abmr:块恢复特性测试自己主动

    abmr:块恢复特性测试自己主动 参考原始: ABMR: How to test Automatic Block Recover Feature (Doc ID 1266059.1) 可适用: Ora ...

  2. 怎么会float交换器int

    最近突然想知道编译器整数浮球开关是如何实现的,现在很多信息,但遗憾的是甚至没有这方面的记录,所以我决定实现自己的简单的整数浮点转 随着float开启int为例  double转int类似 在做强转之前 ...

  3. muduo源代码分析--Reactor在模型muduo使用(两)

    一. TcpServer分类: 管理所有的TCP客户连接,TcpServer对于用户直接使用,直接控制由用户生活. 用户只需要设置相应的回调函数(消息处理messageCallback)然后TcpSe ...

  4. 狄拉克函数(Dirac delta function)

    1. 定义 δ(x)={∞0if x=0if x≠0 这样定义的目的在于使如下的积分式成立: ∫∞−∞δ(x)dx=1 2. 重要性质 sifting property ∫∞−∞f(x)δ(x−μ)d ...

  5. 移动端--web开展

    近期看到群里对关于 移动端 web开发非常是感兴趣.决定写一个关于 移动端的web开发 概念或框架(宝庆对此非常是纠结).也是由于自己一直从事pc 浏览器 web一直对 移动端的不是非常重视,所以趁此 ...

  6. freemarker写select包(四)

    freemarker写select包 1.宏定义 <#macro select id datas value="" key="" text="& ...

  7. Codeforces 458A Golden System

    经过计算两个字符串的大小对比 主要q^2=q+1 明明是斐波那契数 100000位肯定超LL 我在每一位仅仅取到两个以内 竟然ac了 #include<bits/stdc++.h> usi ...

  8. WPF: WrapPanel 容器的数据绑定(动态生成控件、遍历)

    原文:WPF: WrapPanel 容器的数据绑定(动态生成控件.遍历) 问题:        有一些CheckBox需要作为选项添加到页面上,但是数目不定.而为了方便排版,我选择用WrapPanel ...

  9. HDU 2686 Matrix 3376 Matrix Again(费用流)

    HDU 2686 Matrix 题目链接 3376 Matrix Again 题目链接 题意:这两题是一样的,仅仅是数据范围不一样,都是一个矩阵,从左上角走到右下角在从右下角走到左上角能得到最大价值 ...

  10. asp .net mvc 获得用户IP

    string strHostName = System.Net.Dns.GetHostName(); //clientIPAddress是一个数组,可能有多个数据 var clientIPAddres ...