精华内容
下载资源
问答
  • 这些机制基本都从代码上理解了,但是唯有一个不是非常理解的是内核对于ARM构架中原子变量底层支持,这个机制其实在自旋锁、互斥锁以及读写锁等内核机制中都有类似的使用。这里将学习的结果写出,请大家指正。  ...

     前段时间重新研究了一下Linux的并发控制机制,对于内核的自旋锁、互斥锁、信号量等机制及其变体做了底层代码上的研究。因为只有从原理上理解了这些机制,在编写驱动的时候才会记得应该注意什么。这些机制基本都从代码上理解了,但是唯有一个不是非常理解的是内核对于ARM构架中原子变量的底层支持,这个机制其实在自旋锁、互斥锁以及读写锁等内核机制中都有类似的使用。这里将学习的结果写出,请大家指正。

        假设原子变量的底层实现是由一个汇编指令实现的,这个原子性必然有保障。但是如果原子变量的实现是由多条指令组合而成的,那么对于SMP和中断的介入会不会有什么影响呢?我在看ARM的原子变量操作实现的时候,发现其是由多条汇编指令(ldrex/strex)实现的。在参考了别的书籍和资料后,发现大部分书中对这两条指令的描诉都是说他们是支持在SMP系统中实现多核共享内存的互斥访问。但在UP系统中使用,如果ldrex/strex和之间发生了中断,并在中断中也用ldrex/strex操作了同一个原子变量会不会有问题呢?就这个问题,我认真看了一下内核的ARM原子变量源码和ARM官方对于ldrex/strex的功能解释,总结如下:

     

    一、ARM构架的原子变量实现结构

        对于ARM构架的原子变量实现源码位于:arch/arm/include/asm/atomic.h

        其主要的实现代码分为ARMv6以上(含v6)构架的实现和ARMv6版本以下的实现。

    该文件的主要结构如下:

    1. #if __LINUX_ARM_ARCH__ >= 6

    2. ......(通过ldrex/strex指令的汇编实现)

    3. #else /* ARM_ARCH_6 */

    4. #ifdef CONFIG_SMP
    5. #error SMP not supported on pre-ARMv6 CPUs
    6. #endif

    7. ......(通过关闭CPU中断的C语言实现)

    8. #endif /* __LINUX_ARM_ARCH__ */
    9. ...... 

    10.  #ifndef CONFIG_GENERIC_ATOMIC64

    11. ......(通过ldrexd/strexd指令的汇编实现的64bit原子变量的访问)

    12. #else /* !CONFIG_GENERIC_ATOMIC64 */

    13. #include <asm-generic/atomic64.h>

    14. #endif

    15. #include <asm-generic/atomic-long.h>

          这样的安排是依据ARM核心指令集版本的实现来做的:

    (1)在ARMv6以上(含v6)构架有了多核的CPU,为了在多核之间同步数据和控制并发,ARM在内存访问上增加了独占监测(Exclusive monitors)机制(一种简单的状态机),并增加了相关的ldrex/strex指令。请先阅读以下参考资料(关键在于理解local monitor和Global monitor):

    1.2.2. Exclusive monitors

    4.2.12. LDREX  STREX

    (2)对于ARMv6以前的构架不可能有多核CPU,所以对于变量的原子访问只需要关闭本CPU中断即可保证原子性。 

    对于(2),非常好理解。

    但是(1)情况,我还是要通过源码的分析才认同这种代码,以下我仅仅分析最具有代表性的atomic_add源码,其他的API原理都一样。如果读者还不熟悉C内嵌汇编的格式,请参考ARM GCC 内嵌汇编手册》

     

    二、内核对于ARM构架的atomic_add源码分析


    1. /*
    2. * ARMv6 UP 和 SMP 安全原子操作。 我们是用独占载入和
    3. * 独占存储来保证这些操作的原子性。我们可能会通过循环
    4. * 来保证成功更新变量。
    5. */

    6. static inline void atomic_add(int i, atomic_t *v)
    7. {
    8. unsigned long tmp;
    9. int result;
    10. __asm__ __volatile__("@ atomic_add\n"
    11. "1: ldrex %0, [%3]\n"
    12. " add %0, %0, %4\n"
    13. " strex %1, %0, [%3]\n"
    14. " teq %1, #0\n"
    15. " bne 1b"
    16. : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
    17. : "r" (&v->counter), "Ir" (i)
    18. : "cc");
    19. }

    源码分析: 

    注意:根据内联汇编的语法,result、tmp、&v->counter对应的数据都放在了寄存器中操作。如果出现上下文切换,切换机制会做寄存器上下文保护。

     (1)ldrex %0, [%3]

    意思是将&v->counter指向的数据放入result中,并且(分别在Local monitor和Global monitor中)设置独占标志。

    (2)add %0, %0, %4

    result = result + i

    (3)strex %1, %0, [%3]

    意思是将result保存到&v->counter指向的内存中,此时 Exclusive monitors会发挥作用,将保存是否成功的标志放入tmp中。

    (4) teq %1, #0

    测试strex是否成功(tmp == 0 ??)

    (5)bne 1b

    如果发现strex失败,从(1)再次执行。

          通过上面的分析,可知关键在于strex的操作是否成功的判断上。而这个就归功于ARM的Exclusive monitors和ldrex/strex指令的机制。以下通过可能的情况分析ldrex/strex指令机制。(请阅读时参考4.2.12. LDREX  STREX

     

    1、UP系统或SMP系统中变量为非CPU间共享访问的情况 

        此情况下,仅有一个CPU可能访问变量,此时仅有Local monitor需要关注。

        假设CPU执行到(2)的时候,来了一个中断,并在中断里使用ldrex/strex操作了同一个原子变量。则情况如下图所示:

    • A:处理器标记一个物理地址,但访问尚未完毕
    • B:再次标记此物理地址访问尚未完毕(与A重复)
    • C:进行存储操作,清除以上标记,返回0(操作成功)
    • D:不会进行存储操作,并返回1(操作失败) 

    也就是说,中断例程里的操作会成功,被中断的操作会失败重试。 

     

    2、SMP系统中变量为CPU间共享访问的情况

      

        此情况下,需要两个CPU间的互斥访问,此时ldrex/strex指令会同时关注Local monitor和Global monitor。

    (i)两个CPU同时访问同个原子变量(ldrex/strex指令会关注Global monitor。)

    • A:将该物理地址标记为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。
    • B:标记此物理地址为CPU1独占访问,并清除CPU1对其他任何物理地址的任何独占访问标记。
    • C:没有标记为CPU0独占访问,不会进行存储,并返回1(操作失败)。
    • D:已被标记为CPU1独占访问,进行存储并清除独占访问标记,并返回0(操作成功)。

     也就是说,后执行ldrex操作的CPU会成功。

     

    (ii)同一个CPU因为中断,“嵌套”访问同个原子变量(ldrex/strex指令会关注Local monito)

    • A:将该物理地址标记为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。
    • B:再次标记此物理地址为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。
    • C:已被标记为CPU0独占访问,进行存储并清除独占访问标记,并返回0(操作成功)。
    • D:没有标记为CPU0独占访问,不会进行存储,并返回1(操作失败)。

    也就是说,中断例程里的操作会成功,被中断的操作会失败重试。

     

    (iii)两个CPU同时访问同个原子变量,并同时有CPU因中断“嵌套”访问改原子变量(ldrex/strex指令会同时关注Local monitor和Global monitor)

    虽然对于人来说,这种情况比较BT。但是在飞速运行的CPU来说,BT的事情随时都可能发生。

    • A:将该物理地址标记为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。
    • B:标记此物理地址为CPU1独占访问,并清除CPU1对其他任何物理地址的任何独占访问标记。
    • C:再次标记此物理地址为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。
    • D:已被标记为CPU0独占访问,进行存储并清除独占访问标记,并返回0(操作成功)。
    • E:没有标记为CPU1独占访问,不会进行存储,并返回1(操作失败)。
    • F:没有标记为CPU0独占访问,不会进行存储,并返回1(操作失败)。

     

        当然还有其他许多复杂的可能,也可以通过ldrex/strex指令的机制分析出来。从上面列举的分析中,我们可以看出:ldrex/strex可以保证在任何情况下(包括被中断)的访问原子性。所以内核中ARM构架中的原子操作是可以信任的。

    展开全文
  • 原子变量相对于锁的优势你应该很清楚,那么直奔主题.....先看源码AtomicInteger类的定义:public class AtomicInteger extends Number implements java.io.Serializable { private static final long ...

    原子变量相对于锁的优势你应该很清楚,那么直奔主题.....

    先看源码AtomicInteger类的定义:

    public class AtomicInteger extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 6214790243416807050L;

        // setup to use Unsafe.compareAndSwapInt for updates

        private static final Unsafe unsafe = Unsafe.getUnsafe();

        private static final long valueOffset;  //成员变量的地址偏移量

        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
        private volatile int value;
        /**
         * Creates a new AtomicInteger with the given initial value.
         *
         * @param initialValue the initial value
         */
        public AtomicInteger(int initialValue) {
            value = initialValue;
        }

    加红的特别留意,首先是声明了一个成员 value  ,采用volatile修饰,保证了其在不同线程间的可见性。那么为什么要有unsafe这个对象,它的作用是什么呢,首先我们看AtomicInteger中的一个重要方法compareAndSet。

    /**
         * Atomically sets the value to the given updated value
         * if the current value {@code ==} the expected value.
         *
         * @param expect the expected value
         * @param update the new value
         * @return {@code true} if successful. False return indicates that
         * the actual value was not equal to the expected value.
         */
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

        }

    我们知道,java是不能直接访问操作系统底层,而是采取本地方法访问,而Unsafe类提供了硬件级别的原子操作。到这里你应该就明白了valueOffset就是成员value的地址偏移量。而unsafe就相当于访问操作系统底层的一个工具。而UnSafe类的源代码可以参考网上的其他资料https://www.cnblogs.com/mickole/articles/3757278.html。

    此外,volatile关键字只是保证修饰的成员在各线程间保持可见性,它与原子操作并没有任何关系。

    希望能帮助你。



    展开全文
  • 交替打印奇数偶数,关键在于状态的改变,可以使用synchronized关键字来...而原子变量底层实现是基于cas,代价较小,代码如下 package TreadCommunicate; import java.util.concurrent.atomic.AtomicInteger; pu...

    交替打印奇数偶数,关键在于状态的改变,可以使用synchronized关键字来约束,也可以使用wait(),notify()方法约束,但是上述方法要求持有对象锁和释放对象锁,代价较大。而原子变量底层实现是基于cas,代价较小,代码如下

    package TreadCommunicate;

    import java.util.concurrent.atomic.AtomicInteger;

    public class AtomicPrint {

        public static void main(String[] args) {
            //定义一个整型原子变量
            AtomicInteger flag = new AtomicInteger(0);
            
            //打印偶数作业
            Runnable a = new Runnable() {
                
                @Override
                public void run() {
                    while(true) {
                        if(flag.get() % 2 == 0) {
                            System.out.println(flag.get());    //偶数判断
                            flag.incrementAndGet();   //原子操作  变量加一
                        }
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            
            //打印奇数作业
            Runnable b = new Runnable() {
                
                @Override
                public void run() {
                    while(true) {
                        if(flag.get() % 2 == 1) {   //奇数判断
                            System.out.println("---"+flag.get());
                            flag.incrementAndGet();   //原子操作  变量加一
                        }
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            };
            
            //提交作业, 开启线程
            new Thread(a).start();
            new Thread(b).start();
        }

    }

     

    展开全文
  • 原子类AtomicInteger 在Java中,有很多方法可以保证多线程下数据的安全,AtomicXXXX这些...//类变量 unsafe类【java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作】 //这里...

    原子类AtomicInteger

    在Java中,有很多方法可以保证多线程下数据的安全,AtomicXXXX这些类就是其中的一种,原子类,可以保证每一步操作都是原子操作。这次就对AtomicInteger的源码进行学习。

    首先看一下这个类的类变量和成员变量:

    //类变量 unsafe类【java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作】
    //这里的这个变量就是用来进行cpu级别的原子操作。
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //这个变量实际上是由下面的static块来赋值的,可以由赋值看出来,这个值是下面的value属性在每个AtomikInteger对象中的位置偏移量,用这个值既可以找到在具体每个对象的内存中的内存地址。
    private static final long valueOffset;
    
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //volatile修饰,AtomicInteger的值,在直接内存中,多个线程下可以直接获取值。
    private volatile int value;
    

    看完了这个类的内部的变量,其实大概可以猜到这个类怎么完成的原子操作了,使用volatile修饰的value来存储值,保证每个线程都可以随时读到值,然后每一步操作都使用CAS(compare and swap)这样即可以保证一直能原子写入,下面来看看源码到底是不是这样。

    来看一下incrementAndGet这个方法,实际上就是++i的操作,但是保证原子性。

     public final int incrementAndGet() {
        //用get出来的值+1,前面的方法是unsafe中实现的 i++。对value属性进行操作。
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    
    //Unsafe中的。这个方法可以看到一直在做do-while,直到CAS成功(获取AtomicInteger对象上的value属性,然后CAS检查保证值是var5的时候将他变成var5+1)。
    //其中getIntVolatile和compareAndSwapInt 都是native方法,用C写的。CAS底层貌似是使用了cpu的cpxchg(compare*change)。
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
        return var5;
    }
    

    总结

    所有其他的方法都大同小异,使用volatile修饰的value来存储值,保证每个线程都可以随时读到值,然后每一步操作都使用CAS(compare and swap)这样即可以保证原子写入。

    展开全文
  • 原子变量

    2018-05-29 17:34:00
    原子变量类(例如java.util.concurrent.atomic中的AtomicXxx)中使用了这些底层的JVM支持为数字类型和引用类型提供了一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时则直接或间接的使用了这些原子...
  • Java 原子语义同步的底层实现 原子语义同步的底层实现 volatile volatile只能保证变量对各个线程的可见性,但不能保证原子性。关于 Java语言 volatile 的使用方法就不多说了,我的建议是 除了 配合...
  • 而有些操作,底层需要通过多个字节码来完成,这样的操作就不是原子的,因此不是线程安全的。举个例子,a+=1 。反编译这个语句,发现它由4个字节码组成:>>> dis.dis(compile('a+=1', '', 'exec'))1 0 LOAD_...
  • Java JUC3 原子变量与CAS算法3.1 原子变量3.1.1 i++的原子性问题3.1.2 原子变量3.2 CAS算法3.2.1 ABA问题3.2.2 CAS在JAVA中底层实现3.3 原子性与可见性区别 3 原子变量与CAS算法 3.1 原子变量 3.1.1 i++的原子性...
  • 使用原子变量能够比使用synchronized实现原子性提高性能,synchronized减少获取锁,等待锁的时间,上下文切换时间。 原子变量的原理 CAS在有两者说法一种是compareAndSet,另一种说法是compareAndSwap,我这边做了调查...
  • 注意,这里有三个条件:简单,意味着程序员尽可能少的操作底层或者实现起来要比较容易;高效意味着耗用资源要少,程序处理速度要快;线程安全也非常重要,这个在多线程下能保证数据的正确性。这三个条件看起来比较...
  • Atomic的介绍和使用(原子变量

    千次阅读 2018-11-28 10:54:03
    开始之前,我们来看一下上一篇文章中《CAS ...也就是说,atomic类首先是一个乐观锁,然后底层实现也是根据CAS操作和Volatile关键字实现的。 Atomic 在JDK1.5之后,JDK的(concurrent包)并发包里提供了一些类来支持...
  • 现在并发算法领域多数研究都侧重于非阻塞算法,这种算法...原子变量不光可以用在构建非阻塞算法上,它还可以当做volatile同时还可以支持原子更新操作。 锁的劣势 现在JVM对非竞争的锁获取和释放等操作进行了极大的优
  • 注意,这里有三个条件:简单,意味着程序员尽可能少的操作底层或者实现起来要比较容易;高效意味着耗用资源要少,程序处理速度要快;线程安全也非常重要,这个在多线程下能保证数据的正确性。这三个条件看起来比较...
  • 注意,这里有三个条件:简单,意味着程序员尽可能少的操作底层或者实现起来要比较容易;高效意味着耗用资源要少,程序处理速度要快;线程安全也非常重要,这个在多线程下能保证数据的正确性。这三个条件看起来比较...
  • ( 小编注: 独占锁:是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的...注意,这里有三个条件:简单,意味着程序员尽可能少的操作底层或者实现起来要比较容易;高效...
  • 第十五章:原子变量与非阻塞机制——Java并发编程实战 非阻塞算法:使用底层的原子机器指令(例如比较并交换指令)代替锁来确保数据在并发访问中的一致性 应用于在操作系统和JVM中实现线程 / 进程调度...
  • 原子变量在非阻塞算法的应用实现基础用底层的原子机器指令(例如比例并交换指令)代替锁来确保数据在并发访问中的一致性。缺点非阻塞算法在设计与实现上比阻塞算法都要复杂得多。优点 在可伸缩性和活跃性上拥有巨大...
  • 尝试Java加锁新思路:原子变量和非阻塞同步算法 进年以来,并发算法领域的重点都围绕在非拥塞算法,该种算法依赖底层硬件对于原子性指令的支持,避免使用锁来维护数据一致性和多线程安全。非拥塞算法虽然...
  • volatile,synchronized,CAS原子操作 定义及原理Volatile的定义和实现原理定义:原理synchronized的定义和实现原理,锁的存储结构定义:原理:CAS定义: Volatile的定义和实现原理 定义: java内存模型保证定义为volatile...
  • 进年以来,并发算法领域的重点都围绕在非拥塞算法,该种算法依赖底层硬件对于原子性指令的支持,避免使用锁来维护数据一致性和多线程安全。非拥塞算法虽然在设计上更为复杂,但是拥有更好的可伸缩性和性能,被广泛...
  • 原子变量与非阻塞同步机制Java.util.concurrent包中的许多类,如Semaphore 和 ConcurrentLinkedQueue,都提供了比使用synchronized更好的性能和可伸缩性,这些性能提升的原始来源是:原子变量和非阻塞的同步机制。...
  • 前面讲线程同步时,我们对多线程容易出现的问题进行了分析,在那个例子中,问题的根源在于c++和c--这两个操作在底层处理的时候被分成了若干步执行。当时我们用的是synchronized关键字来解决这个问题,而从...
  • 非阻塞算法:使用底层的原子机器指令(例如比较并交换指令)...原子变量:提供了与volatile类型变量相同的内存语义,并支持原子的更新操作,比基于锁的方法提供更高的可伸缩性 一、锁的劣势 锁:独占方式访问...
  • 因此,Java 中所使用的并发机制其实是依赖于 JVM 的实现和 CPU 的指令,所以了解 Java 并发机制的底层实现原理也是很有必要的 volatile 的应用 volatile 在多处理器开发中保证了共享变量的可见性。可见性的意思是当...

空空如也

空空如也

1 2 3 4 5 ... 14
收藏数 264
精华内容 105
关键字:

原子变量底层实现