zoukankan      html  css  js  c++  java
  • 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 "
    "            
    "1:    ldrex    %0, [%3]
    "                        ----------------------ldrex指令把原子变量v->counter的值加载到result变量中,然后在result变量中增加i值,使用strex指令把result变量的值存放到原子变量v->result中,其中变量tmp保存着strex指令更新后的结果。
    "    " #asm_op "    %0, %0, %4
    "                    
    "    strex    %1, %0, [%3]
    "                        
    "    teq    %1, #0
    "                        
    "    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
    "        
    "1:    ldrex    %0, [%3]
    "                        
    "    " #asm_op "    %0, %0, %4
    "                    
    "    strex    %1, %0, [%3]
    "                        
    "    teq    %1, #0
    "                        
    "    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 1:
            asm volatile("@    __xchg1
    "
            "1:    ldrexb    %0, [%3]
    "
            "    strexb    %1, %2, [%3]
    "
            "    teq    %1, #0
    "
            "    bne    1b"
                : "=&r" (ret), "=&r" (tmp)
                : "r" (x), "r" (ptr)
                : "memory", "cc");
            break;
        case 4:
            asm volatile("@    __xchg4
    "
            "1:    ldrex    %0, [%3]
    "
            "    strex    %1, %2, [%3]
    "
            "    teq    %1, #0
    "
            "    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 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;
    #else
        case 1:
            asm volatile("@    __xchg1
    "
            "    swpb    %0, %1, [%2]"
                : "=&r" (ret)
                : "r" (x), "r" (ptr)
                : "memory", "cc");
            break;
        case 4:
            asm volatile("@    __xchg4
    "
            "    swp    %0, %1, [%2]"
                : "=&r" (ret)
                : "r" (x), "r" (ptr)
                : "memory", "cc");
            break;
    #endif
        default:
            __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
    "
            "ldrex    %1, [%3]
    "
            "mov    %0, #0
    "
            "teq    %1, %4
    "
            "strexeq %0, %5, [%3]
    "
                : "=&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
    }
  • 相关阅读:
    Android 2.2 r1 API 中文文档系列(11) —— RadioButton
    Android API 中文 (15) —— GridView
    Android 中文 API (16) —— AnalogClock
    Android2.2 API 中文文档系列(7) —— ImageButton
    Android2.2 API 中文文档系列(6) —— ImageView
    Android 2.2 r1 API 中文文档系列(12) —— Button
    Android2.2 API 中文文档系列(8) —— QuickContactBadge
    [Android1.5]TextView跑马灯效果
    [Android1.5]ActivityManager: [1] Killed am start n
    Android API 中文(14) —— ViewStub
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/9236300.html
Copyright © 2011-2022 走看看