Linux内核-spin_lock()

spin_lock()

在Linux2.6中,spin_lock()宏有两种实现方式,一种是具有内核抢占的spin_lock(),一种是非抢占式内核中的spin_lock(),下面先看下自旋锁的数据结构,在Linux中,每个自旋锁都用spinlock_t结构表示,如下:

    typedef struct {
        /**
         * 该字段表示自旋锁的状态,值为1表示未加锁,任何负数和0都表示加锁
         */
        volatile unsigned int slock;

    #ifdef CONFIG_PREEMPT
        /**
         * 表示进程正在忙等待自旋锁。
         * 只有内核支持SMP和内核抢占时才使用本标志。
         */
        unsigned int break_lock;
    #endif
    } spinlock_t;

spin_lock()定义如下:

    #define spin_lock(lock)     _spin_lock(lock)

具有内核抢占的_spin_lock宏

    /**
     * 通过BUILD_LOCK_OPS(spin, spinlock);定义了_spin_lock,进而实现了spin_lock
     * 这是在具有内核抢占时,spin_lock的实现。
     */
    #define BUILD_LOCK_OPS(op, locktype)                    \
    void __lockfunc _##op##_lock(locktype##_t *lock)            \
    {                                   \
        /**
         * preempt_disable禁用内核抢占。
         * 必须在测试spinlock的值前,先禁止抢占,原因很简单:
         * 下面的循环中有可能会成功获得自旋锁,如果获得锁之后被抢占了,将造成死锁
         */
        preempt_disable();                      \
        for (;;) {                          \
            /**
             * 调用_raw_spin_trylock,它对自旋锁的slock字段进行原子性的测试和设置。
             * 本质上它执行以下代码:
             *     movb $0,%al
             *     xchgb %al, slp->slock
             * xchgb原子性的交换al和slp->slock内存单元的内容。如果原值>0,就返回1,否则返回0
             * 换句话说,如果原来的锁是开着的,就关掉它,它返回成功标志。如果原来就是锁着的,再次设置锁标志,并返回0。
             */
            if (likely(_raw_##op##_trylock(lock)))          \
                /**
                 * 如果旧值是正的,表示锁是打开的,宏结束,已经获得自旋锁了。
                 * 注意:返回后,本函数的一个负作用就是禁用抢占了。配对使用unlock时再打开抢占。
                 * 请想一下禁用抢占的必要性。
                 */
                break;                      \
            /**
             * 否则,无法获得自旋锁,就循环一直到其他CPU释放自旋锁。
             * 在循环前,暂时打开preempt_enable。也就是说,在等待自旋锁的中间,进程是可能被抢占的。
             */
            preempt_enable();                   \
            /**
             * break_lock表示有其他进程在等待锁。
             * 拥有锁的进程可以判断这个标志,如果进程把持锁的时间太长,可以提前释放锁。
             */
            if (!(lock)->break_lock)                \
                (lock)->break_lock = 1;             \
            /**
             * 执行等待循环,cpu_relax简化成一条pause指令,对应rep;nop,即空操作
             * 为什么要加入cpu_relax,是有原因的,表面上看,可以用一段死循环的汇编来代替这个循环
             * 但是实际上是不能那样的的,那样会锁住总线,unlock想设置值都不能了。
             * cpu_relax就是要让CPU休息一下,把总线暂时让出来。
             */
            while (!op##_can_lock(lock) && (lock)->break_lock)  \
                cpu_relax();                    \
            /**
             * 上面的死循环lock的值已经变化了。那么关抢占后,再次调用_raw_spin_trylock
             * 真正的获得锁还是在_raw_spin_trylock中。
             */
            preempt_disable();                  \
        }                               \
    }                   

_raw_spin_trylock如下:

    static inline int _raw_spin_trylock(spinlock_t *lock)
    {
        char oldval;
        __asm__ __volatile__(
            "xchgb %b0,%1"
            :"=q" (oldval), "=m" (lock->slock)
            :"0" (0) : "memory");
        return oldval > 0;
    }

非抢占式内核中的_spin_lock()

 void __lockfunc _spin_lock(spinlock_t *lock)
    {
        preempt_disable();
        _raw_spin_lock(lock);
    }

_raw_spin_lock(lock)如下:

    /**
     * 对自旋锁的slock字段执行原子性的测试和设置操作。
     */
    static inline void _raw_spin_lock(spinlock_t *lock)
    {
    #ifdef CONFIG_DEBUG_SPINLOCK
        if (unlikely(lock->magic != SPINLOCK_MAGIC)) {
            printk("eip: %p\n", __builtin_return_address(0));
            BUG();
        }
    #endif
        __asm__ __volatile__(
            spin_lock_string
            :"=m" (lock->slock) : : "memory");
    }

    /* spin_lock_string如下: */

    #define spin_lock_string \
        "\n1:\t" \
        /**
         * %0对应上面的lock->slock
         * decb递减自旋锁的值。它有lock前缀,因此是原子的。
         */
        "lock ; decb %0\n\t" \
        /**
         * 如果结果为0(不是负数),说明锁是打开的,跳到3f处继续执行。
         */
        "jns 3f\n" \
        /**
         * 否则,结果为负,说明锁是关闭的。就执行死循环,等待它的值变化。
         */
        "2:\t" \
        "rep;nop\n\t" \
        /**
         * 比较lock值,直到它变化,才跳到开头,试图再次获得锁。
         * 否则,继续死循环等lock值变化。
         */
        "cmpb $0,%0\n\t" \
        "jle 2b\n\t" \
        "jmp 1b\n" \
        "3:\n\t"

你可能感兴趣的:(Linux,自旋锁)