关键词:。

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_buggy
unsigned long flags;
#endif
#if __LINUX_ARM_ARCH__ >= 6
unsigned int tmp;
#endif smp_mb();
prefetchw((const void *)ptr); switch (size) {
#if __LINUX_ARM_ARCH__ >= 6
case :
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 :
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
#endif
case :
raw_local_irq_save(flags);
ret = *(volatile unsigned char *)ptr;
*(volatile unsigned char *)ptr = x;
raw_local_irq_restore(flags);
break; case :
raw_local_irq_save(flags);
ret = *(volatile unsigned long *)ptr;
*(volatile unsigned long *)ptr = x;
raw_local_irq_restore(flags);
break;
#else
case :
asm volatile("@ __xchg1\n"
" swpb %0, %1, [%2]"
: "=&r" (ret)
: "r" (x), "r" (ptr)
: "memory", "cc");
break;
case :
asm volatile("@ __xchg4\n"
" swp %0, %1, [%2]"
: "=&r" (ret)
: "r" (x), "r" (ptr)
: "memory", "cc");
break;
#endif
default:
__bad_xchg(ptr, size), ret = ;
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], , 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
}

Linux并发与同步专题 (1)原子操作和内存屏障的更多相关文章

  1. Linux并发与同步专题 (4) Mutex互斥量

    关键词:mutex.MCS.OSQ. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步 ...

  2. Linux并发与同步专题 (3) 信号量

    关键词:Semaphore.down()/up(). <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Li ...

  3. Linux并发与同步专题 (2)spinlock

    关键词:wfe.FIFO ticket-based.spin_lock/spin_trylock/spin_unlock.spin_lock_irq/spin_lock_bh/spin_lock_ir ...

  4. Linux并发与同步专题

    并发访问:多个内核路径同时访问和操作数据,就有可能发生相互覆盖共享数据的情况,造成被访问数据的不一致. 临界区:访问和操作共享数据的代码段. 并发源:访问临界区的执行线程或代码路径. 在内核中产生并发 ...

  5. 一文读懂原子操作、内存屏障、锁(偏向锁、轻量级锁、重量级锁、自旋锁)、Disruptor、Go Context之上半部分

    我不想卷,我是被逼的 在做了几年前端之后,发现互联网行情比想象的差,不如赶紧学点后端知识,被裁之后也可接个私活不至于饿死.学习两周Go,如盲人摸象般不知重点,那么重点谁知道呢?肯定是使用Go的后端工程 ...

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

    转自:http://blog.csdn.net/goodluckwhh/article/details/9005585 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 一每 ...

  7. linx 内核 并发与同步 1

    内核并发来源: 1.硬件中断和异常:中断服务程序和被中断的进程可能发生并发访问资源 2.软中断和tasklet,软中断和taklet随时都可能倍调度执行,从而打断当前正在执行 进程的上下文. 3.内核 ...

  8. Linux Barrier I/O 实现分析与barrier内存屏蔽 总结

    一直以来.I/O顺序问题一直困扰着我.事实上这个问题是一个比較综合的问题,它涉及的层次比較多,从VFS page cache到I/O调度算法,从i/o子系统到存储外设.而Linux I/O barri ...

  9. LINUX内核内存屏障

    =================                          LINUX内核内存屏障                          ================= By ...

随机推荐

  1. 【代码笔记】Web-JavaScript-JavaScript 条件语句

    一,效果图. 二,代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  2. SAP MM 物料主数据采购视图中的字段'Var. OUn'的作用?

    SAP MM 物料主数据采购视图中的字段'Var. OUn'的作用? 物料主数据采购视图里有一个字段,叫做'Var. OUn'的, 如下图: 这个字段,笔者之前所参与的项目里,从来没有用过.所以,笔者 ...

  3. 小程序实践(二):swiper组件实现轮播图效果

    swiper组件类似于Android中的ViewPager,实现类似轮播图的效果,相对于Android的Viewpager,swiper实现起来更加方便,快捷. 效果图: 首先看下swiper支持的属 ...

  4. java StringBuffer读写文件

    java StringBuffer读写文件 StringBuffer的优势 较String:String每更新一次就会new一个新的对象出来,更新次数上去之后,内存开销太大.而StringBuffer ...

  5. 轻量简单好用的C++JSON库CJsonObject

    1. JSON概述 JSON: JavaScript 对象表示法( JavaScript Object Notation) .是一种轻量级的数据交换格式. 它基于ECMAScript的一个子集.许多编 ...

  6. CSS实现三列布局

    三列布局指的是两边两列定宽,中间的宽度自适应. 常用三种方法: 定位 浮动 弹性盒布局 定位方式 最直观和容易理解的一种方法,左右两栏选择绝对定位,固定于页面的两侧,中间的主体选择用margin确定位 ...

  7. Android内嵌PDF预览

    一.在对应模块的build.gradle文件中加入依赖 dependencies { implementation 'com.github.barteksc:android-pdf-viewer:3. ...

  8. Selenium Webdriver 中的 executeScript 使用方法

    1.使用executeScript 返回一个WebElement . 下例中我们将一个浏览器中的JavaScript 对象返回到客户端(C#,JAVA,Python等). IWebElement el ...

  9. [Hive_7] Hive 中的 DDL 操作

    0. 说明 DDL(Data Definition Languages)语句:数据定义语言 这些语句定义了不同的数据段.数据库.表.列.索引等数据库对象的定义. 常用的语句关键字主要包括 create ...

  10. linux上部署SpringBoot项目及遇到的问题

    打开sftp步骤, 在显示的已连接的窗口上右键, 选择connect SFTP Session就可以打开文件上传的窗口 从windows上传文件到linux, 首先linux需要先切换到想要保存文件的 ...