• 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
    }
  • 相关阅读:
    MySQL数据库的优化
    PHP中获取文件扩展名
    PHP实现几种经典算法详解
    Linux服务器上crontab定时执行脚本文件
    LeetCode每日一题(五):加一
    巧妙利用枚举找出数组元素所在区间
    PHP实现几种经典算法详解
    _initialize() 区别 __construct()
    LeetCode每日一题(四):搜索插入位置
    LeetCode每日一题(三):移除元素
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/9236300.html
Copyright © 2020-2023  润新知