/*
 * 2020/12/4    15:07    qing
 *
 * 原子操作需要靠硬件实现
 */

/*
 * atomic
 */
Linux内核中提供了各种各样的原子操作函数。除了最原始的读取和设置之外,还包括各种运算,以及位操作等等。而且有的原子操作还要返回操作过后变量的值,
有的要返回操作之前变量的值,如果再牵涉到内存屏障的问题,将这些因素组合起来,有非常多的原子操作函数。

/*
 * Bus Lock(锁总线)
 */

CPU执行原子指令时,给总线上锁,这样在释放前,可以防止其它CPU的内存操作。

/*
 * Cache Lock
 */

除了和IO紧密相关的(如MMIO),大部分的内存都是可以被cache的,由前面介绍的cache一致性原理,我们知道由cacheline处于Exclusive或Modified时,
    该变量只有当前CPU缓存了数据,因此当进行原子操作时,发出Read Invalidate消息,使其它CPU上的缓存无效,cacheline变成Exclusive状态然后将该

cacheline上锁,接着就可以取数据,修改并写入cacheline,如果这时有其它CPU也进行原子操作,发出read invalidate消息,但由于当前CPU的
    cacheline是locked状态,因此暂时不会回复消息,这样其它CPU就一直在等待,直到当前CPU完成,使cacheline变为unlocked状态。

/*
 * 基本型
 */

基本型包括非RMW(Read Modifiy Write)的读写操作,以及RMW的算术和位操作等。

非RMW的操作很简单,只有两个,即用来读取的atomic_read()和用来写入的atomic_set()。
    一般对单独变量的读取或写入操作本身都是原子的,如果代码中只对单个变量进行读写操作,而从来没对它使用RMW操作,那一般说明你用错了,
    没必要使用内核提供的原子操作。

算术操作:包括加、减、递增和递减,命名形式是atomic_{add,sub,inc,dec}();
    Bit位操作:包括与、或、异或和与非,命名形式是atomic_{and,or,xor,andnot}();
    交换操作:包括交换(atomic_xchg())、比较交换(atomic_cmpxchg())和添加了返回成功与否的比较交换(atomic_try_cmpxchg())。

除了atomic_t类型外,Linux内核还支持对64位和长整形的atomic64_t和atomic_long_t类型,它们也都有对应的基本原子操作函数,
    只不过函数前缀名分别是atomic64_和atomic_long_

static inline void atomic_add(int i, atomic_t *v)
{
    atomic_add_return(i, v);
}

#ifndef atomic_add_return
#define  atomic_add_return(...)                        \
    __atomic_op_fence(atomic_add_return, __VA_ARGS__)
#endif

#ifndef __atomic_op_fence
#define __atomic_op_fence(op, args...)                    \
({                                    \
    typeof(op##_relaxed(args)) __ret;                \
    smp_mb__before_atomic();                    \
    __ret = op##_relaxed(args);                    \
    smp_mb__after_atomic();                        \
    __ret;                                \
})
#endif

/*
 * 返回修改过后的值
 */

如果一个原子操作函数要返回修改过后的原子变量的值,那么该函数名会含有“_return”字符串,并且是在表示具体操作字符串的后面。
    
    例如,atomic_add_return、atomic_dec_return_relaxed等。

/*
 * 要返回修改之前的值
 */

如果一个原子操作函数要返回修改之前的原子变量的值,那么该函数名会含有“_fetch”字符串,并且是在表示具体操作字符串的前面。
    
    例如,atomic_fetch_and、atomic_fetch_or_acquire等。

/*
 * 有Acquire和Release单向屏障语义
 */

如果一个原子操作函数还需要包括Acquire或者Release单向屏障语义,那么该函数名会有“_acquire”或者“_release”后缀。
    
    例如,atomic_xchg_acquire、atomic_cmpxchg_release等。

相反的,如果一个原子操作函数名有“_relaxed”后缀,表示这个函数没有被任何内存屏障保护,可以被任意重排序。
    
    例如,atomic_add_return_relaxed、atomic_xchg_relaxed等。

只有当一个原子操作函数要返回值的时候才有可能添加_acquire、_release和_relaxed后缀。

/*
 * 原子操作的重排序规则
 */

Linux内核的原子操作只保证对单一变量的某个操作是原子的,多个CPU同时操作时,不会出现中间的错误状态。但是,对这个原子操作本身,
    并不一定保证其执行的顺序,在SMP系统下,有可能会出现重排序的问题。
    
    因此,前面也提到过,有些原子操作函数自己就带了一定的内存屏障的语义。具体有没有带,带了多少,可以通过函数名看出来,具体规则如下:

非RMW的原子操作可以被任意重排序;

RMW的原子操作,如果没有返回值可以被任意重排序;
    RMW的原子操作,如果有返回值,并且没有_relaxed、_acquire和_release后缀,是有内存屏障保护的,可以保证不被重排序;
    RMW的原子操作,如果有返回值,且有_relaxed后缀,没有任何内存屏障保护,可以被任意重排序;
    RMW的原子操作,如果有返回值,且有_acquire后缀,表示读(RMW中的R)操作是有Acquire单向屏障保护的;
    RMW的原子操作,如果有返回值,且有_release后缀,表示写(RMW中的W)操作是有Release单向屏障保护的;
    RMW的原子操作,如果有条件判断,那么条件是否的那部分会被任意重排序。

对于那些不提供内存屏障语义的原子操作来说,为了保证其本身不被重排序,还需要显式的在其前面或后面使用内存屏障。
    Linux内核提供了两个函数,分别是smp_mb__before_atomic()用于原子操作函数的前面,和smp_mb__after_atomic()用于原子操作函数的后面。

/*
 * ARMv8架构下原子操作的实现
 */

#define ATOMIC_INIT(i)    { (i) }

/* atomic_read  */

#define atomic_read(v)            READ_ONCE((v)->counter)

#define __READ_ONCE(x, check)                        \
({                                    \
    union { typeof(x) __val; char __c[1]; } __u;            \
    if (check)                            \
        __read_once_size(&(x), __u.__c, sizeof(x));        \
    else                                \
        __read_once_size_nocheck(&(x), __u.__c, sizeof(x));    \
    __u.__val;                            \
})
#define READ_ONCE(x) __READ_ONCE(x, 1)

static __always_inline
void __read_once_size(const volatile void *p, void *res, int size)
{
    __READ_ONCE_SIZE;
}

#define __READ_ONCE_SIZE                        \
({                                    \
    switch (size) {                            \
    case 1: *(__u8 *)res = *(volatile __u8 *)p; break;        \
    case 2: *(__u16 *)res = *(volatile __u16 *)p; break;        \
    case 4: *(__u32 *)res = *(volatile __u32 *)p; break;        \
    case 8: *(__u64 *)res = *(volatile __u64 *)p; break;        \
    default:                            \
        barrier();                        \
        __builtin_memcpy((void *)res, (const void *)p, size);    \
        barrier();                        \
    }                                \
})

/* atomic_set */
#define atomic_set(v, i)        WRITE_ONCE(((v)->counter), (i))

#define WRITE_ONCE(x, val) \
({                            \
    union { typeof(x) __val; char __c[1]; } __u =    \
        { .__val = (__force typeof(x)) (val) }; \
    __write_once_size(&(x), __u.__c, sizeof(x));    \
    __u.__val;                    \
})

static __always_inline void __write_once_size(volatile void *p, void *res, int size)
{
    switch (size) {
    case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
    case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
    case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
    case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
    default:
        barrier();
        __builtin_memcpy((void *)p, (const void *)res, size);
        barrier();
    }
}

/*
 * 废弃的函数,学习一下C语法 及 指令
 */

static inline int
    __ll_sc_atomic_add_return_acquire(int i, atomic_t *v)
    {
        unsigned long tmp;
        int result;
     
        asm volatile(
    "       // 将v->counter预取到CPU缓存\n"                                   \
    "    prfm    pstl1strm, %2\n"                    \
    "       // 将v->counter独占的读入result中\n"                              \
    "1:    ldaxr    %w0, %2\n"                            \
    "       // result = result + i\n"                                       \
    "    add    %w0, %w0, %w3\n"                                \
    "       // 将result的值独占的存入v->counter中\n"                           \
    "       // 如果存入时独占标记被清除则将tmp置1\n"                             \
    "    stxr    %w1, %w0, %2\n"                            \
    "       // 如果tmp被置1则从头再次执行一遍\n"                                \
    "    cbnz    %w1, 1b\n"                        \
            )                                \
        : "=&r" (result), "=&r" (tmp), "+Q" (v->counter)        \
        : __stringify(I) "r" (i)                        \
        : "memory");                                    \
                                        \
        return result;                            \
    }

static inline int                            \
    __ll_sc_atomic_fetch_or_release(int i, atomic_t *v)            \
    {                                    \
        unsigned long tmp;                        \
        int val, result;                        \
                                        \
        asm volatile(                                            \
    "       // 将v->counter预取到CPU缓存\n"                                   \
    "    prfm    pstl1strm, %3\n"                    \
    "       // 将v->counter独占的读入result中\n"                              \
    "1:    ldxr    %w0, %3\n"                            \
    "       // val = result | i\n"                                          \
    "    orr    %w1, %w0, %w4\n"                        \
    "       // 将val的值独占的存入v->counter中\n"                              \
    "       // 如果存入时独占标记被清除则将tmp置1\n"                             \
    "    stlxr    %w2, %w1, %3\n"                            \
    "       // 如果tmp被置1则从头再次执行一遍\n"                                \
    "    cbnz    %w2, 1b\n"                        \
        : "=&r" (result), "=&r" (val), "=&r" (tmp), "+Q" (v->counter)    \
        : __stringify(K) "r" (i)                        \
        : "memory");                            \
                                        \
        return result;                            \
    }

/* arch_cmpxchg */
#define __cmpxchg_wrapper(sfx, ptr, o, n)                \
({                                    \
    __typeof__(*(ptr)) __ret;                    \
    __ret = (__typeof__(*(ptr)))                    \
        __cmpxchg##sfx((ptr), (unsigned long)(o),        \
                (unsigned long)(n), sizeof(*(ptr)));    \
    __ret;                                \
})

#define arch_cmpxchg(...)        __cmpxchg_wrapper( _mb, __VA_ARGS__)

arch_cmpxchg最终被宏定义成了__cmpxchg_wrapper,而它又调用了__cmpxchg_mb函数:

#define __CMPXCHG_GEN(sfx)                        \
static __always_inline unsigned long __cmpxchg##sfx(volatile void *ptr,    \
                       unsigned long old,        \
                       unsigned long new,        \
                       int size)            \
{                                    \
    switch (size) {                            \
    case 1:                                \
        return __cmpxchg_case##sfx##_8(ptr, old, new);        \
    case 2:                                \
        return __cmpxchg_case##sfx##_16(ptr, old, new);        \
    case 4:                                \
        return __cmpxchg_case##sfx##_32(ptr, old, new);        \
    case 8:                                \
        return __cmpxchg_case##sfx##_64(ptr, old, new);        \
    default:                            \
        BUILD_BUG();                        \
    }                                \
                                    \
    unreachable();                            \
}
 
__CMPXCHG_GEN()
__CMPXCHG_GEN(_acq)
__CMPXCHG_GEN(_rel)
__CMPXCHG_GEN(_mb)
 
#undef __CMPXCHG_GEN

linux代码之atomic相关推荐

  1. 如何在vs 下敲linux代码且具有提示功能

    本文使用的是vs2010 如何在windows 下敲linux代码时具有提示功能那? 1 先安装vs2010 2 安装西红柿插件VA_X_Setup.官网现在地址https://www.wholeto ...

  2. 香蕉派 banana pi github 最新Linux 代码升级到kernel 4.2.

    2019独角兽企业重金招聘Python工程师标准>>> 香蕉派 banana pi github 最新Linux 代码升级到kernel 4.2. https://github.co ...

  3. VSCode 阅读 Linux 代码怎么才不卡顿?这样做才能快的飞起!

    Linux 内核代码用什么编辑器? 分享过怎么学习 Linux 内核代码的思路,当时顺便提了一点,奇伢是用 vscode 看内核代码.有同学对此提出了疑问: vscode 看 Linux 代码不卡吗? ...

  4. Socket 编程 windows到Linux代码移植遇到的问题

    Socket 编程 windows到Linux代码移植遇到的问题 1.一些常用函数的移植 http://www.vckbase.com/document/viewdoc/?id=1586 2.网络 - ...

  5. linux代码、数据库备份

    1.linux代码备份脚本 #!/bin/bash #指定运行的脚本shell #运行脚本要给用户执行权限 bakdir=/bakdir/web month=`date +%m` day=`date ...

  6. tar在linux编译为exe,将Linux代码移植到Windows的简单方法 1

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 一.前言 Linux拥有丰富各种源代码资源,但是大部分代码在Windows平台情况是无法正常编译的.Windows平台根本无法直接利用这些源代码资源.如果 ...

  7. 将Linux代码移植到Windows的简单方法

    将Linux代码移植到Windows的简单方法 一.前言 Linux拥有丰富各种源代码资源,但是大部分代码在Windows平台情况是无法正常编译的.Windows平台根本无法直接利用这些源代码资源.如 ...

  8. 数据简化社区Google和Linux代码风格指南(附PDF公号发“代码风格”下载)

    数据简化社区Google和Linux代码风格指南(附PDF公号发"代码风格"下载) 秦陇纪2019代码类 数据简化DataSimp 昨天 数据简化DataSimp导读:数据简化社区 ...

  9. linux在代码中表示什么意思,Linux代码中的unlikely和likely分别是什么意思?

    Linux代码中的unlikely和likely分别是什么意思? 我在Linux代码中没有找unlikely和likely的定义. 在源代码中搜索出来很多无法找. 请各位赐教! 谢谢! | 内核里有很 ...

  10. linux代码动态分析软件,举例分析Linux动态库和静态库

    函数库分为静态库和动态库两种.创建Linux静态库和Linux动态库和使用它们在这里将以举例的形式详述一下.静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库.动态库在程序编译时并不 ...

最新文章

  1. vim如何在底部打开新窗口
  2. MyBatis 流式查询
  3. Centos调整时间时区
  4. ffplay命令汇总
  5. Oracle之PLSQL总结
  6. 消息队列重要机制讲解以及MQ设计思路(kafka、rabbitmq、rocketmq)
  7. 解决由于sz rz导致抓包时文件容量增加
  8. 强悍的命令行 —— 路径相关
  9. 制作数据集---labelImg和labelme
  10. 印章识别软件_一种印章识别方法及系统技术方案
  11. fclose用法matlab,fclose和close之间的区别
  12. cadence破译时显示服务器失败,在服务器开启cadence失败 报错如下
  13. CODOSYS之结构化文本(ST)—— 初级篇(二)简介与基本概念
  14. Magento创建主题
  15. urllib3爬取网页源代码(爬虫)
  16. 计算机音乐谱东演员,抖音计算机乐谱有哪些 抖音计算机乐谱分享
  17. 只有一行VNC server running on ’::1:5900' 没有其他输出
  18. 教你炒股票22:将8亿的大米装到5个庄家的肚里
  19. 机器自动翻译古文拼音 - 十大宋词 - 满江红 怒发冲冠 南宋·岳飞
  20. 测试/开发程序员幽默的 “自嘲“?印象流派......

热门文章

  1. MYSQL 视图 触发器 存储过程 事务 索引
  2. C++系列中的一些修修补补
  3. establish connection
  4. Android苹方圆三合一字体,橘色主题-圆形图标 内置苹方+googlesans字体 透明文件夹...
  5. js拆分百分数_计算百分比Javascript
  6. java 聊天室 私聊_Java WebSocket实现网络聊天室(群聊+私聊)
  7. 洛谷P3366【模板】最小生成树-克鲁斯卡尔Kruskal算法详解附赠习题
  8. 该如何去认知Level 2 十档行情数据?
  9. AngularJS总结
  10. HTML5游子吟网页的完整代码,《游子吟》教学设计(5页)-原创力文档