浅谈对原子锁的理解

浅谈对原子锁的理解对原子 atomic 操作的理解 前言 我们知道 当我们修改某一个变量的时候 在汇编层面看来 至少需要细分为 读 gt 改 gt 写 三个过程 也就是说 他们访问存储单元两次 第一次读原值 第二次写新值 假设这样一种场景 两个 cpu 同时对同一个存储器单元做 读 gt

大家好,我是讯享网,很高兴认识大家。

对原子atomic操作的理解

前言

所以,避免由于“读->改->写”指令引起的竞争条件的最容易的办法,就是确保这样的操作在芯片级是原子的。任何一个这样的操作都必须以单个指令执行,中断不能中断,且避免其他的CPU访问同一存储器单元。这些很小的原子操作可以建立在其他更灵活进制的基础之上以创建临界区。
在x86平台上,总的来说,CPU提供三种独立的原子锁机制:原子
保证操作、加LOCK指令前缀和缓存一致性协议。

概念理解

原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作
(atomic operation)意为“不可被中断的一个或一系列操作”。对原子操作的简单描述就是:多个线程执行一个操作时,其中任何一个线程要么
完全执行完此操作,要么没有执行此操作的任何步骤,那么这个操作就
是原子的。原子操作是其他内核同步方法的基石。

api介绍

atomic_read(v) 返回*V atomic_set(v, i) 把*v置成i atomic_add(i,v) 给*v增加i atomic_add_return(i,v) 把i加到*v,返回*V的新值 atomic_sub(i, v) 从*v中减去i atomic_sub_reurn(i,v) 从*v减i,返回*v的新值 atomic_sub_and_test(i,v) 从*v中减去i,如果结果为0 则返回1;否则,返回0 atomic_inc(v) 把1加到*v atomic_dec(v) 从*v减 1 atomic_inc_return(v) 把1加到*v,返回*v新值 atomic_dec_return(v) 从*v减1,返回* V的新值 原子位操作 test_bit(nr, addr) 返回*add的第nr位的值 set_bit(nr,addr) 设置*addr的第nr位 clear_bit(nr,addr) 清*addr的第nr位 change_bit(nr, addr) 转换*addr的第nr位,并返回他的原值 

讯享网
  1. gcc提供的api
讯享网type __sync_fetch_and_add (type *ptr, type value); type __sync_fetch_and_sub (type *ptr, type value); type __sync_fetch_and_or (type *ptr, type value); type __sync_fetch_and_and (type *ptr, type value); type __sync_fetch_and_xor (type *ptr, type value); type __sync_fetch_and_nand (type *ptr, type value); type __sync_add_and_fetch (type *ptr, type value); type __sync_sub_and_fetch (type *ptr, type value); type __sync_or_and_fetch (type *ptr, type value); type __sync_and_and_fetch (type *ptr, type value); type __sync_xor_and_fetch (type *ptr, type value); type __sync_nand_and_fetch (type *ptr, type value); 

2.dpdk提供的原子操作

static inline int rte_atomic16_cmpset(volatile uint16_t *dst, uint16_t exp, uint16_t src); static inline uint16_t rte_atomic16_exchange(volatile uint16_t *dst, uint16_t val); static inline void rte_atomic16_init(rte_atomic16_t *v) static inline int16_t rte_atomic16_read(const rte_atomic16_t *v) static inline void rte_atomic16_set(rte_atomic16_t *v, int16_t new_value) static inline void rte_atomic16_add(rte_atomic16_t *v, int16_t inc) static inline void rte_atomic16_sub(rte_atomic16_t *v, int16_t dec) static inline void rte_atomic16_inc(rte_atomic16_t *v); static inline void rte_atomic16_dec(rte_atomic16_t *v); static inline int16_t rte_atomic16_add_return(rte_atomic16_t *v, int16_t inc) static inline int16_t rte_atomic16_sub_return(rte_atomic16_t *v, int16_t dec) static inline int rte_atomic16_inc_and_test(rte_atomic16_t *v); static inline int rte_atomic16_dec_and_test(rte_atomic16_t *v); static inline int rte_atomic16_test_and_set(rte_atomic16_t *v); static inline void rte_atomic16_clear(rte_atomic16_t *v) 

dpdk提供了16、 32和64位的原子操作API,主要实现原理是使用了LOCK指令+CMPXCHG指令


讯享网

对于LOCK指令前缀的总线锁,早期CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前
缀“LOCK”(这个前缀表示锁总线),经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指
令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
随着处理器的发展,对LOCK前缀的实现也在不断进行着性能改善。最近几代处理器中已经支持新的锁技术,若当前访问的内存已经被处理器缓存,LOCK#不会被触发,会用锁缓存的方式代替。这样处理原子操作的开销就在这些特定场景下进一步降低。

CMPXCHG这条指令,它的语义是比较并交换操作数(CAS,Compare And Set)。而用XCHG类的指令做内存操作,处理器会自动地遵循LOCK的语义,可见该指令是一条原子的CAS单指令操作。

源码如下

讯享网static inline int rte_atomic64_cmpset(volatile uint64_t *dst, uint64_t exp, uint64_t src) { uint8_t res; asm volatile( MPLOCKED "cmpxchgq %[src], %[dst];" "sete %[res];" : [res] "=a" (res), /* output */ [dst] "=m" (*dst) : [src] "r" (src), /* input */ "a" (exp), "m" (*dst) : "memory"); /* no-clobber list */ return res; } 

本人从dpdk移植了锁实现到自己的github里面 https://github.com/air5005/usg/tree/master/libs/liblock 有兴趣的可以参考.
3.linux kernel
内核的原子锁实现主要在x86结构里面,使用的是lock指令+内存屏障原理
提供的api基本一致,都是分为16、32、64位三种api。

小讯
上一篇 2025-03-25 13:42
下一篇 2025-02-11 12:49

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/60263.html