原子操作&优化和内存屏障

原子操作

假定运行在两个CPU上的两个内核控制路径试图执行非原子操作同时“读-修改-写”同一存储器单元。首先,两个CPU都试图读同一单元,但是存储器仲裁器插手,只允许其中的一个访问而让另一个延迟。然而,当第一个读操作已经完成后,延迟的CPU从那个存储器单元正好读到同一个(旧)值。然后,两个CPU都试图向那个存储器单元写一新值,总线存储器访问再一次被存储器仲裁器串行化,最终,两个写操作都成功。但是,全局的结果是不对的,因为两个CPU写入同一(新)值。因此,两个交错的"读-修改-写"操作成了一个单独的操作。

避免由于“读-修改-写”指令引起竞争条件的最容易的办法,就是确保这样的操作在芯片级是原子的。任何一个这样的操作都必须一个单个指令执行,中间不能中断,且避免其他CPU访问同一存储单元。

在你编写C代码程序时,并不能保证编译器会为a++这样的操作使用一个原子指令。因此,Linux内核提供了一个专门的atomic_t类型和一些专门的函数和宏

Linux中的原子操作

atomic_read(v)      返回*v

atomic_set(v,i)     把*v置成i

atomic_add(i,v)     *v增加i

atomic_sub(i,v)     *v减去i

atomic_sub_and_test(i,v) *v减去i,如果结果0,则返回1;否则,返回0

atomic_inc(v)       *v加1

atomic_dec(v)       *v减1

atomic_dec_and_test(v)   *v减去1,如果结果0,则返回1;否则,返回0

atomic_inc_and_test(v)   *v加上1,如果结果0,则返回1;否则,返回0

atomic_add_negative(i,v) 把i加到*v,如果结果为负,则返回1;否则,返回0

atomic_add_return(i,v)   *v加i,返回*v的新值

atomic_sub_return(i,v)   *v减i,返回*v的新值

#ifdef CONFIG_SMP
#define LOCK "lock ; "
#else
#define LOCK ""
#endif
/**
 * atomic_add - add integer to atomic variable
 * @i: integer value to add
 * @v: pointer of type atomic_t
 *
 * Atomically adds @i to @v.
 */
static __inline__ void atomic_add(int i, atomic_t *v)
{
	__asm__ __volatile__(
		LOCK "addl %1,%0"
		:"=m" (v->counter)
		:"ir" (i), "m" (v->counter));
}

在多核处理器系统中,每条指令都有一个lock字节的前缀。当控制单元检测到这个前缀时,就“锁定”内存总线,直到这条指令执行完为止。因此,当加锁的指令执行时,其他处理器不能访问这个内存单元。

Linux中的原子位处理函数

test_bit(nr,addr)            返回*addr的第nr位的值

set_bit(nr,addr)             设置*addr的第nr位的值

clear_bit(nr,addr)           清*addr的第nr位

change_bit(nr,addr)          转换*addr的第nr位

test_and_set_bit(nr,addr)    设置*addr的第nr位,并返回它的原值

test_and_clear_bit(nr,addr)  清*addr的第nr位,并返回它的原值

test_and_change_bit(nr,addr) 转换*addr的第nr位,并返回它的原值

atomic_clear_mask(mask,addr) 清除mask指定的*addr的所有位

atomic_set_mask(mask,addr)   设置mask指定的*addr的所有位

优化和内存屏障

当使用优化的编译器时,千万不要认为指令会严格按照它们在源码中出现的顺序执行。编译器可能重新安排汇编语言指令以使寄存器以最优的方式使用。

优化屏障(optimization barrier)原语保证编译程序不会混淆放在原语操作之前的汇编语言指令和放在原语操作之后的汇编语言指令。

在Linux中,优化屏障就是barrier()宏,它展开为asm volatile("":::"memory")。

指令asm告诉编译器程序要插入汇编语言片段。volatile关键字禁止编译器把asm指令与程序中的其他指令重新组合。memory关键字强制编译器假定RAM中的所有内存单元已经被汇编语言指令修改;因此,编译器不能使用存放在CPU寄存器中的内存单元的值来优化asm指令前的代码。

内存屏障(memory barrier)原语确保,在原语之后的操作开始执行之前,原语之前的操作已经完成。也即内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。

80x86处理器,下列种类的汇编语言是串行的,因为它们内存屏障的作用:

1、I/O端口进行操作的所有指令

2、有lock前缀的所有指令

3、写控制器、系统寄存器或调试寄存器的所有指令(如,cli和sti,用于修改eflags寄存器的IF标志的状态)

4、在Pentium 4微处理器中引入的汇编语言指令lfence、sfence和mfence

LFENCE Serializes load operations

SFENCE Serializes store operations

MFENCE Serializes load and store operations

它们分别有效地实现读内存屏障、写内存屏障和读-写内存屏障

Linux使用六个内存屏障原语。这些原语也被当作优化屏障,因为我们必须保证编译程序不在屏障前后移动汇编语言指令。

读内存屏障 仅仅作用于从内存读的指令

写内存屏障 仅仅作用于写内存的指令

内存屏障既用于多处理器系统,也用于单处理器系统。当内存屏障应该防止仅出现于多处理器系统上的竞争条件时,就使用smp_xxx()原语,在单处理器系统上,它们什么也不做。

Linux中的内存屏障

mb()      适用于MP和UP的内存屏障

rmb()     适用于MP和UP的读内存屏障

wmb()     适用于MP和UP的写内存屏障

smp_mb()  仅适用于MP的内存屏障

smp_rmb() 仅适用于MP的读内存屏障

smp_wmb() 仅适用于MP的写内存屏障

内存屏障原语的实现依赖于系统体系结构。在80x86微处理器上,如果CPU支持lfence汇编指令,就把rmb()宏展开为asm volatile("lfence"),否则就展开为

asm volatile("lock;addl $0,0(%%esp)":::"memory").

lock;addl $0,0(%%esp)汇编指令把0加到栈顶的内存单元,这条指令本身没有价值,但是,lock前缀使这条指令称为CPU的一个内存屏障。

Intel上的wmb()宏实际上更简单,因为它展开为barrier()。这是因为Intel处理器从来不对写内容访问重新排序,因此,没有必要在代码中插入一条串行化汇编指令。不过,这个宏禁止编译器重新组合指令。

在多核处理器上,在之前的原子操作中描述的所有原子操作都起到内存屏障的作用,因为他们使用了lock字节。

原子操作&优化和内存屏障,布布扣,bubuko.com

时间: 2024-10-26 13:54:47

原子操作&优化和内存屏障的相关文章

linux内核同步之每CPU变量、原子操作、内存屏障、自旋锁【转】

转自:http://blog.csdn.net/goodluckwhh/article/details/9005585 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 一每CPU变量 二原子操作 三优化和内存屏障 四自旋锁 自旋锁 自旋锁的数据结构和宏函数 读写自旋锁 读写自旋锁的相关函数 linux内核中的各种“任务”都能看到内核地址空间,因而它们之间也需要同步和互斥.linux内核支持的同步/互斥手段包括: 技术 功能 作用范围 每CPU变量 为每个CPU复制一份数据

线程同步(1):原子操作,内存屏障,锁综述

原子操作,内存屏障,锁 1.原理:CPU提供了原子操作.关中断.锁内存总线,内存屏障等机制:OS基于这几个CPU硬件机制,就能够实现锁:再基于锁,就能够实现各种各样的同步机制(信号量.消息.Barrier等等等等). 2.所有的同步操作最基础的理论就是原子操作.内存屏障,锁都是为了保证在不同的平台或者是CPU类型下的原子操作. 3.原子操作在单核,单线程/无中断,且编译器不优化的情况下是确定的,是按照C/C++代码顺序执行的,所以不存在异步问题 解释一下这几个知识点为什么会引起异步操作: 首先了

内核同步之优化屏障与内存屏障

目的:避免指令重新排序. 优化屏障:保证编译器程序不会混淆放在原语操作之前的汇编语言指令和放在原语操作之后的汇编语言指令.在linux中,优化屏障是barrier().展开为asm volatile("":::"memory").指令asm告诉编译器程序要插入汇编语言片段,volatile禁止编译器把asm指令与程序中的其他指令重新组合.使得编译器不能使用存放在cpu寄存器中的内存单元的值来优化asm指令前的代码,但并不保证不使当前cpu把汇编语言指令混在一起执行-

LINUX内核内存屏障

================= ================= By: David Howells <[email protected]> Paul E. McKenney <[email protected]> 译: kouu <[email protected]> 出处: Linux内核文档 -- Documentation/memory-barriers.txt 文件夹: (*) 内存訪问抽象模型. - 操作设备. - 保证. (*) 什么是内存屏障? -

大话Linux内核中锁机制之内存屏障、读写自旋锁及顺序锁

大话Linux内核中锁机制之内存屏障.读写自旋锁及顺序锁 在上一篇博文中笔者讨论了关于原子操作和自旋锁的相关内容,本篇博文将继续锁机制的讨论,包括内存屏障.读写自旋锁以及顺序锁的相关内容.下面首先讨论内存屏障的相关内容. 三.内存屏障 不知读者是是否记得在笔者讨论自旋锁的禁止或使能的时候,提到过一个内存屏障函数.OK,接下来,笔者将讨论内存屏障的具体细节内容.我们首先来看下它的概念,Memory Barrier是指编译器和处理器对代码进行优化(对读写指令进行重新排序)后,导致对内存的写入操作不能

内存屏障

原文地址:http://ifeve.com/memory-barriers-or-fences/ 本文我将和大家讨论并发编程中最基础的一项技术:内存屏障或内存栅栏,也就是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术. CPU使用了很多优化技术来实现一个目标:CPU执行单元的速度要远超主存访问速度.在上一篇文章 “Write Combing (合并写)”中我已经介绍了其中的一项技术.CPU避免内存访问延迟最常见的技术是将指令管道化,然后尽量重排这些管道的执行以最大化利用缓存,从而把

[百度空间] [转]内存屏障 - MemoryBarrier

处理器的乱序和并发执行 目前的高级处理器,为了提高内部逻辑元件的利用率以提高运行速度,通常会采用多指令发射.乱序执行等各种措施.现在普遍使用的一些超标量处理器通常能够在一个指令周期内并发执行多条指令.处理器从L1 I-Cache预取了一批指令后,就会分析找出那些互相没有关联可以并发执行的指令,然后送到几个独立的执行单元进行并发执行.比如下面这样的代码(假定编译器不做优化): z = x + y;p = m + n; CPU就有可能将这两行无关代码分别送到两个算术单元去同时执行.像Freescal

Linux内核中的内存屏障解析

缓存一致性 之前一直认为linux中很多东西是用来保证缓存一致性的,其实不是.缓存一致性绝大部分是靠硬件机制实现的,只有在带lock前缀的指令执行时才与cache有一点关系.(这话说得绝对,但我目前看来就是这样)我们更多的时候是为了保证顺序一致性. 所谓缓存一致性,就是在多处理器系统中,每个cpu都有自己的L1 cache.很可能两个不同cpu的L1 cache中缓存的是同一片内存的内容,如果一个cpu更改了自己被缓存的内容,它要保证另一个cpu读这块数据时也要读到这个最新的.不过你不要担心,这

谈乱序执行和内存屏障【转】

谈乱序执行和内存屏障 10多年前的程序员对处理器乱序执行和内存屏障应该是很熟悉的,但随着计算机技术突飞猛进的发展,我们离底层原理越来越远,这并不是一件坏事,但在有些情况下了解一些底层原理有助于我们更好的工作,比如现代高级语言多提供了多线程并发技术,如果不深入下来,那么有些由多线程造成问题就很难排查和理解. 今天准备来聊聊乱序执行技术和内存屏障.为了能让大多数人理解,这里省略了很多不影响理解的旁枝末节,但由于我个人水平有限,如果不妥之处,希望各位指正. 按顺执行技术 在开始说乱序执行之前,得先把按