Linux中同步互斥机制研究之原子操作
操作系统中,对共享资源的访问需要有同步互斥机制来保证其逻辑的正确性,而这一切的基础便是原子操作。
| 原子操作(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中同步互斥机制研究之原子操作的更多相关文章
- 【原创】xenomai内核解析--同步互斥机制(一)--优先级倒置
版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 一.xenomai 资源管理简要 二.优先级倒 ...
- 浅谈Linux中的信号处理机制(二)
首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...
- Linux中的保护机制
Linux中的保护机制 在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了NX.PIE等机制,例如存在NX的话就不能直接执行栈上的数据,存在PIE 的话各个系统调用的地址就是随机化的. 一:ca ...
- LINUX中的RCU机制的分析
RCU机制是Linux2.6之后提供的一种数据一致性访问的机制,从RCU(read-copy-update)的名称上看,我们就能对他的实现机制有一个大概的了解,在修改数据的时候,首先需要读取数据,然后 ...
- linux中的阻塞机制及等待队列
阻塞与非阻塞是设备访问的两种方式.驱动程序需要提供阻塞(等待队列,中断)和非阻塞方式(轮询,异步通知)访问设备.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 一.阻塞与非阻塞 阻塞调用是没有获得资 ...
- linux中的阻塞机制及等待队列【转】
转自:http://www.cnblogs.com/gdk-0078/p/5172941.html 阻塞与非阻塞是设备访问的两种方式.驱动程序需要提供阻塞(等待队列,中断)和非阻塞方式(轮询,异步通知 ...
- Linux之同步互斥阻塞20160703
主要介绍一下Linux下的互斥与阻塞方面的知识: 1. 原子操作 原子操作指的是在执行过程中不会被别的代码路径所中断的操作. 常用原子操作函数举例: atomic_t v = ATOMIC_INIT( ...
- linux中的tasklet机制【转】
转自:http://blog.csdn.net/yasin_lee/article/details/12999099 转自: http://www.kerneltravel.net/?p=143 中断 ...
- 总结一下linux中的分段机制
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 这篇文章主要说一下linux对于分段机制的处理,虽然都说linux不使用分段机制,但是分段机制属于CPU的一个功 ...
随机推荐
- node与webpack的process.env.NODE_ENV
先看两篇文章 1.前端工程项目的NODE_ENV 2. Node 环境变量 process.env.NODE_ENV 之webpack应用 3.process.env.NODE_ENV 下面全部是在w ...
- Android开发之assets目录下资源使用总结
预前知识: Android资源文件分类: Android资源文件大致可以分为两种: 第一种是res目录下存放的可编译的资源文件: 这种资源文件系统会在R.Java里面自动生成该资源文件的ID,所以访问 ...
- QT5.8.0+MSVC2015安装以及环境配置(不需要安装VS2015)
原文:QT5.8.0+MSVC2015安装以及环境配置(不需要安装VS2015) 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/snow_rain_ ...
- elasticsearch-jdbc
jprante/elasticsearch-jdbc The Java Database Connection (JDBC) importer allows to fetch data from JD ...
- .net remoting 使用事件
原文:.net remoting 使用事件 在RPC如果需要使用事件,相对是比较难的.本文告诉大家如何在 .net remoting 使用事件. 目录 使用 Channel 序列化 开发建议 修复异常 ...
- 关于javascript中的深拷贝问题
一直在尝试为javascript找一个快捷可靠的对象深拷贝的方法,昨天突发奇想,把对象push到一个空数组里,然后对改数组通过concat()或slice()进行拷贝,然后取出数组的第一个元素复制给变 ...
- linux tesseract识别名片
用tesseract识别名片,无任何训练 数字,字母识别的准确率比较高,没有错误,规范的汉字识别的还可以,比如名片背面,正面的就错误比较多了: 没有任何训练,识别的还算可以了:我们主要要的电话和QQ ...
- uboot初体验-----趣谈nand设备发起的浅显理解
1 选择Uboot版本号 2 移植uboot至console正常work 3 制造uImage和使用uboot指南 4 写NFC驱动器 5 uboot从nand启动引导系统 1 选择Uboot版本号 ...
- aspnetboilerplate && .net core 使用原生sql
利用aspnetboilerplate提供的工具类IDbContextProvider private readonly IDbContextProvider<XXXDbContext> ...
- sql server & .net core 使用空间数据
使用的库 Microsoft.EntityFrameworkCore.SqlServer Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuit ...