• 【linux】spinlock 的实现


    一、什么是spinlock

    spinlock又称自旋锁,是实现保护共享资源而提出一种锁机制。自旋锁与互斥锁比较类似,都是为了解决对某项资源的互斥使用

    无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名

    二、spinlock的原理

    跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:死锁和过多占用cpu资源

    • a. 在用户态尝试竞争一个共享资源. 如果竞争不到, 则不断尝试竞争. 但是不借助内核提供的mutex等变量机制. 因为涉及到内核,就意味这效率低下
    • b. 要想在用户态实现竞争一个共享资源, 必须借助cpu提供的原子操作指令. 如果是SMP多cpu,还需要lock指令锁总线
    • c. 为了避免在长时间竞争却一直得不到资源导致的不断尝试浪费cpu, 在每两次尝试之间间隔一段时间. 并且随着尝试次数的增加,间隔时间也增加.间隔期间可以让cpu稍加休息(注意,绝不是让出cpu),这依赖于cpu提供pausse指令. (当然如果cpu没有提供pause也没关系,只是会很消耗电力资源)PAUSE指令提升了自旋等待循环(spin-wait loop)的性能
    • d. 在等待相当长时间还是得不到锁之后,只好让出cpu. 但必须让出很小一会. 否则就不叫自旋锁了
    如何让出cpu,却有可以很快的回来? 内核提供了 sched_yield()函数,sched_yield()主要功能: 简单的讲,可以使用另一个级别等于或高于当前线程的线程先运行。如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序,如果系统不支持sched_yield, nginx被迫使用了usleep()休息1u秒.

    三、spinlock的适用情况

    自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁

    信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。另外格外注意一点:自旋锁不能递归使用

    四、spinlock与mutex对比

    spinlock不会使线程状态发生切换,mutex在获取不到锁的时候会选择sleep

    mutex获取锁分为两阶段,第一阶段在用户态采用spinlock锁总线的方式获取一次锁,如果成功立即返回;否则进入第二阶段,调用系统的futex锁去sleep,当锁可用后被唤醒,继续竞争锁。

    Spinlock优点:没有昂贵的系统调用,一直处于用户态,执行速度快

    Spinlock缺点:一直占用cpu,而且在执行过程中还会锁bus总线,锁总线时其他处理器不能使用总线

    Mutex优点:不会忙等,得不到锁会sleep

    Mutex缺点:sleep时会陷入到内核态,需要昂贵的系统调用

    五、关于spinlock的定义以及相应的API

    自旋锁定义:  linux/Spinlock.h

    typedef struct spinlock {
              union { //联合
                 struct raw_spinlock rlock;
    #ifdef CONFIG_DEBUG_LOCK_ALLOC
    #define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
                 struct{
                         u8 __padding[LOCK_PADSIZE];
                         struct lockdep_map dep_map;
                 };
    #endif
             };
    } spinlock_t;

     定义和初始化

    spinlock_t my_lock = SPIN_LOCK_UNLOCKED; 
    void spin_lock_init(spinlock_t *lock); 
    

    自旋锁操作

    //加锁一个自旋锁函数
    void spin_lock(spinlock_t *lock);                                   //获取指定的自旋锁
    void spin_lock_irq(spinlock_t *lock);                               //禁止本地中断获取指定的锁
    void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);      //保存本地中断的状态,禁止本地中断,并获取指定的锁
    void spin_lock_bh(spinlock_t *lock)                                 //安全地避免死锁, 而仍然允许硬件中断被服务
    
    
    //释放一个自旋锁函数
    void spin_unlock(spinlock_t *lock);                                 //释放指定的锁
    void spin_unlock_irq(spinlock_t *lock);                             //释放指定的锁,并激活本地中断
    void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags); //释放指定的锁,并让本地中断恢复到以前的状态
    void spin_unlock_bh(spinlock_t *lock);                              //对应于spin_lock_bh
    
    
    //非阻塞锁
    int spin_trylock(spinlock_t *lock);                  //试图获得某个特定的自旋锁,如果该锁已经被争用,该方法会立刻返回一个非0值,
                                                         //而不会自旋等待锁被释放,如果成果获得了这个锁,那么就返回0.
    int spin_trylock_bh(spinlock_t *lock);                           
    //这些函数成功时返回非零( 获得了锁 ), 否则 0. 没有"try"版本来禁止中断.
    
    //其他
    int spin_is_locked(spinlock_t *lock);               //和try_lock()差不多

    六、nginx中的实现

    在nginx中 spinlock的使用场景是,nginx借助spinlock的技术,实现了用户态的进程间的mutex. 由于spinlock是阻塞的

     #define ngx_shmtx_lock(mtx)   ngx_spinlock((mtx)->lock, ngx_pid, 1024) 

    具体分析一个spinlock的开源实现

    // 输入参数 
    //    lock:一个整形变量的指针
    //    value:将lock设置新的值
    //    spin: 自旋的次数. 该值越大会尝试更多次获得锁. 然后才会转入让内核调度线程暂时让出cpu.
    void
    ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
    {
    #if (NGX_HAVE_ATOMIC_OPS)
        ngx_uint_t  i, n;
        for ( ;; ) {
            if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
                return;
            }
            // 为何只在多个cpu的时候才多尝试spin几次
            // 呵呵,很简单,如果是单核的话,既然自己没有拿到锁,那说明别的线程/进程正在使用锁,就这么一个cpu,咱就不占着自旋了,否则别人没机会得到cpu,更不会释放锁了.
            if (ngx_ncpu > 1) {
                for (n = 1; n < spin; n <<= 1) {
                    // 空转的时间随着n的变大而变大
                    for (i = 0; i < n; i++) {
                        ngx_cpu_pause(); // 在空转的同时, 降低cpu功耗,提高效率
                    }
                    if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
                        return;
                    }
                }
            }
            // 已经尝试这么久了还没有得到锁, 让cpu忙别人的事情吧. 让出cpu等待一下.
            ngx_sched_yield();
        }
    #else
    #if (NGX_THREADS)
    #error ngx_spinlock() or ngx_atomic_cmp_set() are not defined !
    #endif
    #endif
    }
    
    #if (NGX_HAVE_SCHED_YIELD)
    #define ngx_sched_yield()  sched_yield()
    #else
    #define ngx_sched_yield()  usleep(1)
    #endif

    7、PHP扩展中的实现

    /* GCC support */
     
    #include <stdlib.h>
    #include "spinlock.h"
     
    extern int ncpu;
     
     
    void spin_lock(atomic_t *lock, int which)
    {
        int i, n;
     
        for ( ;; ) {
     
            if (*lock == 0 &&.
                __sync_bool_compare_and_swap(lock, 0, which)) {
                return;
            }
     
            if (ncpu > 1) {
     
                for (n = 1; n < 129; n << 1) {
    
                    for (i = 0; i < n; i++) {
                        __asm("pause");
                    }
    
                    if (*lock == 0 &&.
                        __sync_bool_compare_and_swap(lock, 0, which)) {
                        return;
                    }
                }
            }
     
            sched_yield();
        }
    }
     
     
    void spin_unlock(atomic_t *lock, int which)
    {
        __sync_bool_compare_and_swap(lock, which, 0);
    }
    

    参考文章

    https://www.ibm.com/developerworks/cn/linux/l-cn-mcsspinlock/
    http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html
    http://www.360doc.com/content/11/0302/14/3038654_97459411.shtml
    http://ifeve.com/practice-of-using-spinlock-instead-of-mutex/
    http://www.cnblogs.com/FrankTan/archive/2010/12/11/1903377.html

  • 相关阅读:
    20172315 2017-2018-2 《程序设计与数据结构》第一周学习总结
    预备作业03
    寒假作业02
    寒假作业01
    2017-2018-2 20172310『Java程序设计』课程 结对编程练习_四则运算_第二周
    20172310 2017-2018-2 《程序设计与数据结构》第八周学习总结
    2017-2018-2 20172310『Java程序设计』课程 结对编程练习_四则运算_第一周
    20172310 《程序设计与数据结构》实验二报告
    20172310 2017-2018-2 《程序设计与数据结构》第七周学习总结
    20172310 2017-2018-2 《程序设计与数据结构》第六周学习总结
  • 原文地址:https://www.cnblogs.com/chenpingzhao/p/5043746.html
Copyright © 2020-2023  润新知