Linux并发与同步专题 (1)原子操作和内存屏障
关键词:。
《Linux并发与同步专题 (1)原子操作和内存屏障》
《Linux并发与同步专题 (2)spinlock》
《Linux并发与同步专题 (3) 信号量》
《Linux并发与同步专题 (4) Mutex互斥量》
《Linux并发与同步专题 (5) 读写锁》
《Linux并发与同步专题 (6) RCU》
《Linux并发与同步专题 (7) 内存管理中的锁》
《Linux并发与同步专题 (8) 最新更新与展望》
1. 原子操作
1.1 一个原子操作例子思考
1.2 原子操作API
atomic_t数据结构表示原子变量,它的实现依赖于不同的体系结构。
typedef struct {int counter; } atomic_t;
Linux提供了很多操作原子变量的API。以arch/arm/include/asm/atomic.h为例。
#define ATOMIC_INIT(i) { (i) }----------------------------------声明一个原子变量并初始化为i。#define atomic_read(v) ACCESS_ONCE((v)->counter)----------------读取原子变量的值。 #define atomic_set(v,i) (((v)->counter) = (i))------------------设置变量v的值为i。#define atomic_xchg(v, new) (xchg(&((v)->counter), new))-----------把new赋值给原子变量v,返回原子变量v的旧值。#define atomic_cmpxchg---------------------------------------------比较old和原子变量v的值,如果相等则把new赋值给v,返回原子变量v的旧值。#define atomic_inc(v) atomic_add(1, v)----------------------原子地给v加1 #define atomic_dec(v) atomic_sub(1, v)----------------------原子地给v减1#define atomic_inc_and_test(v) (atomic_add_return(1, v) == 0)---原子地给v加1,并且返回最新v的值 #define atomic_dec_and_test(v) (atomic_sub_return(1, v) == 0)---原子地给v减1,并且返回最新v的值 #define atomic_inc_return(v) (atomic_add_return(1, v))----------原子地给v加1,结果为0返回true,否则返回false。 #define atomic_dec_return(v) (atomic_sub_return(1, v))----------原子地给v减1,结果为0返回true,否则返回fasle。 #define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)---原子地给v减i,结果为0返回true,否则返回false。#define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)
上面虽然有多种API但是基础的只有atomic_add()/atomic_sub()/atomic_add_return()/atomic_sub_return()四种。
他们通过ATOMIC_OPS定义,产生atomic_add()/atomic_sub()/atomic_add_return()/atomic_sub_return()四个函数。
ARM使用ldrex和strex指令来保证add操作的原子性,指令后缀ex表示exclusive。
ldrex Rt, [Rn] - 把Rn寄存器只想内存地址的内容加载到Rt寄存器中。
strex Rd, Rt, [Rn] - 把Rt寄存器的值保存到Rn寄存器指向的内存地址中,Rd保存更新的结果,0表示更新成功,1表示失败。
GCC嵌入汇编的格式如下:
__asm__ __volatile__(指令部 : 输出部 : 输入部 : 损坏部)
__volatile__防止编译器优化,@符号标识是注释。
#define ATOMIC_OPS(op, c_op, asm_op) \ATOMIC_OP(op, c_op, asm_op) \ATOMIC_OP_RETURN(op, c_op, asm_op)ATOMIC_OPS(add, +=, add) ATOMIC_OPS(sub, -=, sub)#define ATOMIC_OP(op, c_op, asm_op) \ static inline void atomic_##op(int i, atomic_t *v) \ { \unsigned long tmp; \int result; \\prefetchw(&v->counter); \----------------------提前把原子变量的值加载到cache中,以便提高性能。__asm__ __volatile__("@ atomic_" #op "\n" \ "1: ldrex %0, [%3]\n" \----------------------ldrex指令把原子变量v->counter的值加载到result变量中,然后在result变量中增加i值,使用strex指令把result变量的值存放到原子变量v->result中,其中变量tmp保存着strex指令更新后的结果。 " " #asm_op " %0, %0, %4\n" \ " strex %1, %0, [%3]\n" \ " teq %1, #0\n" \ " bne 1b" \: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter) \: "r" (&v->counter), "Ir" (i) \--------------------最后比较该结果是否为0,为0则表示strex指令更新成功。如果不为0,那么跳转到标签“1”处重新再来一次。: "cc"); \ } \#define ATOMIC_OP_RETURN(op, c_op, asm_op) \ static inline int atomic_##op##_return(int i, atomic_t *v) \ { \unsigned long tmp; \int result; \\smp_mb(); \prefetchw(&v->counter); \\__asm__ __volatile__("@ atomic_" #op "_return\n" \ "1: ldrex %0, [%3]\n" \ " " #asm_op " %0, %0, %4\n" \ " strex %1, %0, [%3]\n" \ " teq %1, #0\n" \ " bne 1b" \: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter) \: "r" (&v->counter), "Ir" (i) \: "cc"); \\smp_mb(); \\return result; \ }
除了上面的API还有atomic_xchg和atomic_cmpxchg()。
static inline unsigned long __xchg(unsigned long x, volatile void *ptr, int size) {extern void __bad_xchg(volatile void *, int);unsigned long ret; #ifdef swp_is_buggyunsigned long flags; #endif #if __LINUX_ARM_ARCH__ >= 6unsigned int tmp; #endifsmp_mb();prefetchw((const void *)ptr);switch (size) { #if __LINUX_ARM_ARCH__ >= 6case 1:asm volatile("@ __xchg1\n""1: ldrexb %0, [%3]\n"" strexb %1, %2, [%3]\n"" teq %1, #0\n"" bne 1b": "=&r" (ret), "=&r" (tmp): "r" (x), "r" (ptr): "memory", "cc");break;case 4:asm volatile("@ __xchg4\n""1: ldrex %0, [%3]\n"" strex %1, %2, [%3]\n"" teq %1, #0\n"" bne 1b": "=&r" (ret), "=&r" (tmp): "r" (x), "r" (ptr): "memory", "cc");break; #elif defined(swp_is_buggy) #ifdef CONFIG_SMP #error SMP is not supported on this platform #endifcase 1:raw_local_irq_save(flags);ret = *(volatile unsigned char *)ptr;*(volatile unsigned char *)ptr = x;raw_local_irq_restore(flags);break;case 4:raw_local_irq_save(flags);ret = *(volatile unsigned long *)ptr;*(volatile unsigned long *)ptr = x;raw_local_irq_restore(flags);break; #elsecase 1:asm volatile("@ __xchg1\n"" swpb %0, %1, [%2]": "=&r" (ret): "r" (x), "r" (ptr): "memory", "cc");break;case 4:asm volatile("@ __xchg4\n"" swp %0, %1, [%2]": "=&r" (ret): "r" (x), "r" (ptr): "memory", "cc");break; #endifdefault:__bad_xchg(ptr, size), ret = 0;break;}smp_mb();return ret; }#define xchg(ptr,x) \((__typeof__(*(ptr)))__xchg((unsigned long)(x),(ptr),sizeof(*(ptr))))static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new) {int oldval;unsigned long res;smp_mb();prefetchw(&ptr->counter);do {__asm__ __volatile__("@ atomic_cmpxchg\n""ldrex %1, [%3]\n""mov %0, #0\n""teq %1, %4\n""strexeq %0, %5, [%3]\n": "=&r" (res), "=&r" (oldval), "+Qo" (ptr->counter): "r" (&ptr->counter), "Ir" (old), "r" (new): "cc");} while (res);smp_mb();return oldval; }
1.3 ARM32如何保证原子性
2. 内存屏障
2.1 内存屏障3条指令DMB/DSB/ISB
ARM体系架构中常见的3条内存屏障指令:
数据内存屏障DMB:Data Memory Barrier,它可确保会先检测到程序中位于DMB指令前的所有显示内存访问指令,然后再检测到程序中位于DMB指令后的显式内存访问指令。它不影响其它指令在处理器上的执行顺序。
数据同步屏障DSB:Data Synchronization Barrier,是一种特殊的内存屏障,只有当此指令执行完毕后,才会执行程序中位于此指令后的指令。
当满足以下条件时,此指令才会完成:
- 位于此指令前的所有显示内存访问均完成。
- 位于此指令前的所有缓存、跳转预测和TLB维护操作全部完成。
指令同步屏障ISB:Instruction Synchronization Barrier,可刷新处理器中的管道,因此可确保在ISB指令完成后,才从高速缓存或内存中提取位于该指令后的其他所有指令。
这可确保提取时间晚于ISB指令的指令,能够检测到ISB指令执行前就已经执行的上下文更改操作的执行效果。
2.2 内存屏障API
API | 描述 | |
barrier() | 编译优化屏障,阻止编译器为了性能优化而进行指令重排。 | |
mb() | 内存屏障(包括读和写),用于SMP和UP。 | |
rmb() | 读内存屏障,用于SMP和UP。 | |
wmb() | 写内存屏障,用于SMP和UP。 | |
smp_mb() | 用于SMP场合的内存屏障。对于UP不存在memory order的问题,在UP上就是一个优化屏障,确保汇编和C代码的memory order一致。 | |
smp_rmb() | 用于SMP场合的读内存屏障。 | |
smp_wmb() | 用于SMP场合的写内存屏障。 | |
smp_read_barrier_depends() | 读依赖屏障。 | |
#define isb(option) __asm__ __volatile__ ("isb " #option : : : "memory") #define dsb(option) __asm__ __volatile__ ("dsb " #option : : : "memory") #define dmb(option) __asm__ __volatile__ ("dmb " #option : : : "memory")#define mb() do { dsb(); outer_sync(); } while (0) #define rmb() dsb() #define wmb() do { dsb(st); outer_sync(); } while (0)--------------------------------ST:DSB operation that waits only for stores to complete#define smp_mb() dmb(ish)----------------------------------------------------------------ISH:DMB operation only to the inner shareable domain #define smp_rmb() smp_mb() #define smp_wmb() dmb(ishst)-------------------------------------------------------------ISHST:DMB operation that waits only for stores to complete, and only to the inner shareable domain#define smp_read_barrier_depends() do { } while(0)
2.3 内存屏障例子
2.3.1 一个网卡驱动中发送数据包
网络数据包写入buffer后交给DMA负责发送,wmb()保证在DMA传输之前,数据被完全写入到buffer中。
static netdev_tx_t rtl8139_start_xmit (struct sk_buff *skb,struct net_device *dev) { .../* Note: the chip doesn't have auto-pad! */if (likely(len < TX_BUF_SIZE)) {if (len < ETH_ZLEN)memset(tp->tx_buf[entry], 0, ETH_ZLEN);skb_copy_and_csum_dev(skb, tp->tx_buf[entry]);dev_kfree_skb_any(skb);} else {dev_kfree_skb_any(skb);dev->stats.tx_dropped++;return NETDEV_TX_OK;}spin_lock_irqsave(&tp->lock, flags);/** Writing to TxStatus triggers a DMA transfer of the data* copied to tp->tx_buf[entry] above. Use a memory barrier* to make sure that the device sees the updated data.*/wmb();------------------------------------------------------------------确保之前的数据已经进入buffer,将buffer操作和DMA操作隔开。RTL_W32_F (TxStatus0 + (entry * sizeof (u32)),tp->tx_flag | max(len, (unsigned int)ETH_ZLEN)); ... }
2.3.2 睡眠唤醒API中的内存屏障
通常一个进程因为等待某系时间需要睡眠,调用wait_event()。
在wait_event()中有prepare_to_wait_event()调用了set_current_state()。
#define set_current_state(state_value) \set_mb(current->state, (state_value))#define set_mb(var, value) do { var = value; smp_mb(); } while (0)--------------此处smp_mb()确保current->state的值已经得到更新。
唤醒者通常调用__set_task_cpu()来设置当前进程对应的调度实体
static inline void __set_task_cpu(struct task_struct *p, unsigned int cpu) {set_task_rq(p, cpu); #ifdef CONFIG_SMP/** After ->cpu is set up to a new value, task_rq_lock(p, ...) can be* successfuly executed on another CPU. We must ensure that updates of* per-task data have been completed by this moment.*/smp_wmb();-----------------------------------确保之前的的写操作已经得到更新。task_thread_info(p)->cpu = cpu;p->wake_cpu = cpu; #endif }
转载于:https://www.cnblogs.com/arnoldlu/p/9236300.html
Linux并发与同步专题 (1)原子操作和内存屏障相关推荐
- Linux并发与同步专题 (4) Mutex互斥量
关键词:mutex.MCS.OSQ. 信号量是在并行处理环境中对多个处理器访问某个公共资源进行保护的机制,mutex用于互斥操作. 信号量的count初始化为1,down()/up()也可以实现类似m ...
- Linux中的同步机制
1.背景 编写内核代码或驱动代码时需要留意共享资源的保护,防止共享资源被并发访问.所谓并发访问,就是指多个内核路径同时访问和操作相同地址的数据,有可能发生相互覆盖共享数据的情况,造成被访问数据的不一致 ...
- 聊聊高并发(三十六)Java内存模型那些事(四)理解Happens-before规则
在前几篇将Java内存模型的那些事基本上把这个域底层的概念都解释清楚了,聊聊高并发(三十五)Java内存模型那些事(三)理解内存屏障 这篇分析了在X86平台下,volatile,synchronize ...
- LINUX内核内存屏障
================= LINUX内核内存屏障 ================= By: David Howells <dhowells@redhat.com> Paul E ...
- 原子操作,内存屏障,锁
原文地址:https://m.2cto.com/os/201604/503190.html 文章目录 1.引言 1.1 为什么会引起异步操作 2.内存屏障 4.锁 4.1 锁的重要性 4.2 锁的实现 ...
- Linux并发与竞争介绍(原子操作、自旋锁、信号量、互斥体)
目录 并发与竞争 并发与竞争简介 保护内容是什么 原子操作 原子操作简介 原子整形操作API函数(atomic_t 结构体) 原子位操作API 函数 自旋锁 自旋锁简介 自旋锁API函数 线程与线程 ...
- 深入解析Linux并发同步
并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理多个任务的能力.并发和并行看起来很像,但实际上是有区别的,如下图: 在这里插入图片描述 上图的意思是,有两条在排队买咖啡的 ...
- linux 内核同步--理解原子操作、自旋锁、信号量(可睡眠)、读写锁、RCU锁、PER_CPU变量、内存屏障
内核同步 内核中可能造成并发的原因: 中断–中断几乎可以在任何时刻异步发生,也就可以随时打断当前正在执行的代码. 软中断和tasklet–内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在 ...
- Linux海量数据高并发实时同步架构方案杂谈
不论是Redhat还是CentOS系统,除去从CDN缓存或者数据库优化.动静分离等方面来说,在架构层面上,实 现海量数据高并发实时同步访问概括起来大概可以从以下几个方面去入手,当然NFS的存储也可以是 ...
最新文章
- 帝国cms百度小程序接入自然搜索小程序新资源提交自动同步的操作方法和原理
- Python中的argparse模块
- 腾讯云大数据套件Hermes-MR索引插件使用总结
- 视觉SLAM十四讲(1):预备知识
- VBA各种查询方法介绍和应用举例
- 数据库表结构设计方法
- R语言︱H2o深度学习的一些R语言实践——H2o包
- 2022-2028全球昼夜节律性睡眠障碍行业调研及趋势分析报告
- C#的DataTable详解
- ubuntu nvidia驱动更新
- CUDA实现focal_loss
- 苹果app退款_苹果 App Store 已购买的应用如何申请退款?
- JavaSE-day01
- Vue elementUI中的Breadcrumb面包屑
- 影响MRP计算的因素———提前期
- OA系统资产管理借力工作流,覆盖从采购申请-报废的全过程
- 【SQLite】C++链接SQLite数据库
- 刷题汇总(三)leetcode 精选50题 C++答案总结
- 使用 JADE 平台进行智能体开发
- Mybatis事务(一)事务管理方式
热门文章
- hibernate同时插入两条相同数据(偶尔)原因调查
- PyQt5 技术篇-调用文件对话框获取文件、文件夹路径。文件对话框返回选中的多个文件路径
- ACM-ICPC国际大学生程序设计竞赛北京赛区(2017)网络赛 A题 Visiting Peking University
- 在Ubuntu中安装VMD
- 爬取广州所有停车场数据(Python)
- C++:顺序表的基本操作(待完善)
- 基于 Ubuntu 系统安装 CUDA 和 cuDNN
- You are what you say!
- 【BZOJ3700】发展城市 [LCA][RMQ]
- cdoj 1070 秋实大哥打游戏 带权并查集