linux—原子操作
2016-04-18 22:30
477 查看
一、原子操作概念
原子操作是不可分割的操作,在执行完毕时它不会被任何事件中断。
在单处理器中,能够在单条指令中完成的操作都可以认为是原子操作,这是因为指令的中断只能发生在指令与指令之间。而多处理器系统中有多个处理器在独立的运行,即使在能单条指令中完成的操作,在指令执行期间就可能受其他的处理器的影响。这种在指令执行期间的影响的一种情况就是:有很多的汇编指令是“读—修改—写”类型,也就是内存单元作为目的操作数时,实际上这条指令访问了两次内存单元,一次读取,修改之后又有一次写入。这里就出现问题了:在一条指令读之后,另外的一个CPU执行的另一条指令就可能对其进行了一些意想不到的访问。也就是说该操作执行期间有事件中断,它影响了两条指令的执行,造成了结果与预想的可能不一样。
二、原子操作要解决的问题
1.CPU对共享的数据的访问都应该从所有CPU能访问到的地方—内存(寄存器是各自独有)得到。
2.在单指令对内存单元访问完成之前,不允许其他的指令来访问。
三、linux中原子操作的实现
针对上面的两个目标,软硬件分别提供了下面的支持:
1C语言的关键字volatile保证对每次从内存中读取该变量,不使用volatile时,编译器会将该变量优化成对寄存器操作。
2.LOCK前缀保证在执行该指令过程中对内存单元的锁定,其他CPU不能访问该内存单元。
下面是对于源码的分析,源码文件linux\include\asm-i386\atomic.h
原子操作的对象,一个名为atomic_t的结构类型,将atomic声明成结构体的原因:
1.首先,原子函数只能接受atomic_t类型的操作数,不接收像int这样的变量,防止程序使用原子操作对int型变量也这样操作。
2.其次,关键字volatile确保编译器不对相应的值进行访问的优化,也就是保证每次从内存中读取该变量。
3.最后,atomic_t屏蔽体系结构上的差异
读取变量的值:直接从内存单元进行单独地读取,只进行一次内存访问,保证了原子操作的特征
设置变量的值为i:向内存单元进行单独地写入,只进行一次内存访问,保证了原子操作的特征
将变量的值加i:LOCK锁定,并保持对变量是内存的访问,保证了原子操作的特征
将变量的值减i:LOCK锁定,并保持对变量是内存的访问,保证了原子操作的特征
将变量的值减i并测试结果是否为0,为0则返回1,否则返回0:通过sete指令将c设置成eflag寄存器中ZF(运算结果为0则设置1)的值。解释:从原子操作要达成的目的上来看:在该原子操作期间不允许发生时间中断,包括其它的CPU的干扰。但是这里的两条汇编指令很明显地告诉我们:这两条指令中间是可以发生中断的。但是即使在指令间发生了中断或者进程的切换,这两种情况下都会保存eflag的值,也就是在下次回到该进程的时候也可以从栈里面恢复,其中断的事件没有对结果造成影响。而对于多处理器的影响,由于两条指令分别都是原子的,该操作在中间中断之前的一条指令已经原子地完成了对共享变量的访问,也就是结果已经得到了,只等待赋值返回了,所以在第一条指令执行之后,其它CPU已经造成不了影响了。综上的表述,这样的操作跟我们想达到的目的的效果是一样的,我们可以说它就是原子操作。
将变量的值加1:单条指令锁定内存,保证了原子操作的特征
将变量的值减1:单条指令锁定内存,保证了原子操作的特征
将变量的值减1并测试结果是否为0,为0则返回1,否则返回0:与atomic_sub_and_test解释一样
将变量的值加1并测试结果是否为0,为0则返回1,否则返回0:解释参照atomic_sub_and_test
将变量的值减i并测试结果是否为负数,为负数则返回1,否则返回0:解释参照atomic_sub_and_test
将变量的值加i并返回新值,看下面486:xaddl指令是先交换两个操作数的值,然后j将两个操作数的和赋给目的操作数。这里的汇编指令完成了temp = i + v->count; i = v->count; v->count = temp;这样在该条指令执行的之后其值同样已经保存到了内存,而且要返回的结果是_i+i两个局部变量,再中断已经无济于事了,所以该操作也可以看作是原子操作
利用上面函数实现减i并返回:解释与atomic_add_return一样
利用上面函数实现加1并返回:解释与atomic_add_return一样
利用上面函数实现减1并返回:解释与atomic_add_return一样
位操作:清除*addr中mask二进制表示指定的所有位,单条指令加上锁定内存,保证原子操作的特征
位操作:设置*addr中mask二进制表示指定的所有位,单条指令加上锁定内存,保证原子操作的特征
这个定义了内存屏障,就是屏障前的代码不会优化到屏障后面,下一篇将会讲到
总结:linux提供了很好的方法来支持原子操作,其实“原子操作”有些名不副实,就如刚才分析到的,这些操作很多都使用了多条代码,这个代码之间还是可以发生中断等事件来打断它们的执行。操作函数中最关键的是那条LOCK锁定内存唯一访存并修改了内存的值的指令,在该指令之前的对变量的操作算作这个操作之前的操作,在该指令之后的读变量的操作算这个操作之后的操作。所以只要保证了这条指令的原子性,无论在哪里中断其结果都与没有发生中断达到同样的效果。所以linux下的原子操作能够保证原子性。
原子操作是不可分割的操作,在执行完毕时它不会被任何事件中断。
在单处理器中,能够在单条指令中完成的操作都可以认为是原子操作,这是因为指令的中断只能发生在指令与指令之间。而多处理器系统中有多个处理器在独立的运行,即使在能单条指令中完成的操作,在指令执行期间就可能受其他的处理器的影响。这种在指令执行期间的影响的一种情况就是:有很多的汇编指令是“读—修改—写”类型,也就是内存单元作为目的操作数时,实际上这条指令访问了两次内存单元,一次读取,修改之后又有一次写入。这里就出现问题了:在一条指令读之后,另外的一个CPU执行的另一条指令就可能对其进行了一些意想不到的访问。也就是说该操作执行期间有事件中断,它影响了两条指令的执行,造成了结果与预想的可能不一样。
二、原子操作要解决的问题
1.CPU对共享的数据的访问都应该从所有CPU能访问到的地方—内存(寄存器是各自独有)得到。
2.在单指令对内存单元访问完成之前,不允许其他的指令来访问。
三、linux中原子操作的实现
针对上面的两个目标,软硬件分别提供了下面的支持:
1C语言的关键字volatile保证对每次从内存中读取该变量,不使用volatile时,编译器会将该变量优化成对寄存器操作。
2.LOCK前缀保证在执行该指令过程中对内存单元的锁定,其他CPU不能访问该内存单元。
下面是对于源码的分析,源码文件linux\include\asm-i386\atomic.h
原子操作的对象,一个名为atomic_t的结构类型,将atomic声明成结构体的原因:
1.首先,原子函数只能接受atomic_t类型的操作数,不接收像int这样的变量,防止程序使用原子操作对int型变量也这样操作。
2.其次,关键字volatile确保编译器不对相应的值进行访问的优化,也就是保证每次从内存中读取该变量。
3.最后,atomic_t屏蔽体系结构上的差异
typedef struct { volatile int counter; } atomic_t;
读取变量的值:直接从内存单元进行单独地读取,只进行一次内存访问,保证了原子操作的特征
#define atomic_read(v) ((v)->counter)
设置变量的值为i:向内存单元进行单独地写入,只进行一次内存访问,保证了原子操作的特征
#define atomic_set(v,i) (((v)->counter) = (i))
将变量的值加i:LOCK锁定,并保持对变量是内存的访问,保证了原子操作的特征
static __inline__ void atomic_add(int i, atomic_t *v) { __asm__ __volatile__( LOCK "addl %1,%0" :"=m" (v->counter) :"ir" (i), "m" (v->counter)); }
将变量的值减i:LOCK锁定,并保持对变量是内存的访问,保证了原子操作的特征
static __inline__ void atomic_sub(int i, atomic_t *v) { __asm__ __volatile__( LOCK "subl %1,%0" :"=m" (v->counter) :"ir" (i), "m" (v->counter)); }
将变量的值减i并测试结果是否为0,为0则返回1,否则返回0:通过sete指令将c设置成eflag寄存器中ZF(运算结果为0则设置1)的值。解释:从原子操作要达成的目的上来看:在该原子操作期间不允许发生时间中断,包括其它的CPU的干扰。但是这里的两条汇编指令很明显地告诉我们:这两条指令中间是可以发生中断的。但是即使在指令间发生了中断或者进程的切换,这两种情况下都会保存eflag的值,也就是在下次回到该进程的时候也可以从栈里面恢复,其中断的事件没有对结果造成影响。而对于多处理器的影响,由于两条指令分别都是原子的,该操作在中间中断之前的一条指令已经原子地完成了对共享变量的访问,也就是结果已经得到了,只等待赋值返回了,所以在第一条指令执行之后,其它CPU已经造成不了影响了。综上的表述,这样的操作跟我们想达到的目的的效果是一样的,我们可以说它就是原子操作。
static __inline__ int atomic_sub_and_test(int i, atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "subl %2,%0; sete %1" :"=m" (v->counter), "=qm" (c) :"ir" (i), "m" (v->counter) : "memory"); return c; }
将变量的值加1:单条指令锁定内存,保证了原子操作的特征
static __inline__ void atomic_inc(atomic_t *v) { __asm__ __volatile__( LOCK "incl %0" :"=m" (v->counter) :"m" (v->counter)); }
将变量的值减1:单条指令锁定内存,保证了原子操作的特征
static __inline__ void atomic_dec(atomic_t *v) { __asm__ __volatile__( LOCK "decl %0" :"=m" (v->counter) :"m" (v->counter)); }
将变量的值减1并测试结果是否为0,为0则返回1,否则返回0:与atomic_sub_and_test解释一样
static __inline__ int atomic_dec_and_test(atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "decl %0; sete %1" :"=m" (v->counter), "=qm" (c) :"m" (v->counter) : "memory"); return c != 0; }
将变量的值加1并测试结果是否为0,为0则返回1,否则返回0:解释参照atomic_sub_and_test
static __inline__ int atomic_inc_and_test(atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "incl %0; sete %1" :"=m" (v->counter), "=qm" (c) :"m" (v->counter) : "memory"); return c != 0; }
将变量的值减i并测试结果是否为负数,为负数则返回1,否则返回0:解释参照atomic_sub_and_test
static __inline__ int atomic_add_negative(int i, atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "addl %2,%0; sets %1" :"=m" (v->counter), "=qm" (c) :"ir" (i), "m" (v->counter) : "memory"); return c; }
将变量的值加i并返回新值,看下面486:xaddl指令是先交换两个操作数的值,然后j将两个操作数的和赋给目的操作数。这里的汇编指令完成了temp = i + v->count; i = v->count; v->count = temp;这样在该条指令执行的之后其值同样已经保存到了内存,而且要返回的结果是_i+i两个局部变量,再中断已经无济于事了,所以该操作也可以看作是原子操作
static __inline__ int atomic_add_return(int i, atomic_t *v) { int __i; #ifdef CONFIG_M386 if(unlikely(boot_cpu_data.x86==3)) goto no_xadd; #endif /* Modern 486+ processor */ __i = i; __asm__ __volatile__( LOCK "xaddl %0, %1;" :"=r"(i) :"m"(v->counter), "0"(i)); return i + __i; #ifdef CONFIG_M386 no_xadd: /* Legacy 386 processor */ local_irq_disable(); __i = atomic_read(v); atomic_set(v, i + __i); local_irq_enable(); return i + __i; #endif }
利用上面函数实现减i并返回:解释与atomic_add_return一样
static __inline__ int atomic_sub_return(int i, atomic_t *v) { return atomic_add_return(-i,v); }
利用上面函数实现加1并返回:解释与atomic_add_return一样
#define atomic_inc_return(v) (atomic_add_return(1,v))
利用上面函数实现减1并返回:解释与atomic_add_return一样
#define atomic_dec_return(v) (atomic_sub_return(1,v))
位操作:清除*addr中mask二进制表示指定的所有位,单条指令加上锁定内存,保证原子操作的特征
#define atomic_clear_mask(mask, addr) \ __asm__ __volatile__(LOCK "andl %0,%1" \ : : "r" (~(mask)),"m" (*addr) : "memory")
位操作:设置*addr中mask二进制表示指定的所有位,单条指令加上锁定内存,保证原子操作的特征
#define atomic_set_mask(mask, addr) \ __asm__ __volatile__(LOCK "orl %0,%1" \ : : "r" (mask),"m" (*(addr)) : "memory")
这个定义了内存屏障,就是屏障前的代码不会优化到屏障后面,下一篇将会讲到
#define smp_mb__before_atomic_dec() barrier() #define smp_mb__after_atomic_dec() barrier() #define smp_mb__before_atomic_inc() barrier() #define smp_mb__after_atomic_inc() barrier()
总结:linux提供了很好的方法来支持原子操作,其实“原子操作”有些名不副实,就如刚才分析到的,这些操作很多都使用了多条代码,这个代码之间还是可以发生中断等事件来打断它们的执行。操作函数中最关键的是那条LOCK锁定内存唯一访存并修改了内存的值的指令,在该指令之前的对变量的操作算作这个操作之前的操作,在该指令之后的读变量的操作算这个操作之后的操作。所以只要保证了这条指令的原子性,无论在哪里中断其结果都与没有发生中断达到同样的效果。所以linux下的原子操作能够保证原子性。
相关文章推荐
- 开源应用巨头红帽发布第一个OpenSh…
- linux多线程函数pthread_cond_wait
- 我的Linux学习之路——1菜鸟篇——初识Linux
- linux进程创建
- Linux下htop的使用
- Linux C 学习
- Linux高级管理(一)
- linux 多线程环境下的几种锁机制
- CentOS7 使用yum命令安装Java SDK(openjdk)
- IBM与Linux和大银行共同创建开源区…
- Linux下 进度条
- Linux基础篇二
- 鸟哥私房菜linux基础学习笔记 2
- linux系统搭建(一)--简单定制linux系统
- linux 下载flash插件
- linux内核的make modules 有什么用
- Linux 进程的 Uninterruptible sleep(D) 状态
- Linux下装无线网卡(Ubuntu)
- linux命令大全
- linux窗口管理器学习