当前位置:首页 > 操作系统 > Linux

Linux内核源码分析 -- 同步原语 -- 自旋锁 spinlock

Linux内核源码分析 -- 同步原语 -- 自旋锁 spinlock_t

        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;

    

如果一个处理程序尝试执行受自旋锁保护的代码,那么代码将会被锁住,直到占有锁的处理程序释放掉。

自旋锁 一共有两种状态

  • acquired
  • released

自旋锁获取(spinlock acquire

自旋锁释放(spinlock released

raw_spinlock 结构

        typedef struct raw_spinlock {
        arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
        unsigned int break_lock;
#endif
} raw_spinlock_t;

    

x86arch_spinlock 结构

        typedef struct arch_spinlock {
        union {
                __ticketpair_t head_tail;
                struct __raw_tickets {
                        __ticket_t head, tail;
                } tickets;
        };
} arch_spinlock_t;

    

Linux内核在自旋锁上提供了一下主要的操作:

  • spin_lock_init ——给定的自旋锁进行初始化;
  • spin_lock ——获取给定的自旋锁
  • spin_lock_bh ——禁止软件中断并且获取给定的自旋锁
  • spin_lock_irqsavespin_lock_irq——禁止本地处理器上的中断,并且保存/不保存之前的中断状态的标识 (flag)
  • spin_unlock ——释放给定的自旋锁;
  • spin_unlock_bh ——释放给定的自旋锁并且启动软件中断;
  • spin_is_locked - 返回给定的自旋锁的状态;
  • 等等

spin_lock_init —— 对给定的自旋锁进行初始化

        #define spin_lock_init(_lock)        do {                                                spinlock_check(_lock);                            raw_spin_lock_init(&(_lock)->rlock);        } while (0)

    

spinlock_check 检查 _lock

返回已知的自旋锁raw_spinlock_t,来确保我们精确获得正常 (normal) 原生自旋锁

        static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock)
{
  return &lock->rlock;
}

    

raw_spin_lock_init

这个宏为给定的自旋锁执行初始化操作,并且将锁设置为释放 (released) 状态

        # define raw_spin_lock_init(lock)        do {                                                      *(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock);         } while (0)                                           
    

__RAW_SPIN_LOCK_UNLOCKED

        #define __RAW_SPIN_LOCK_UNLOCKED(lockname)               (raw_spinlock_t) __RAW_SPIN_LOCK_INITIALIZER(lockname)
#define __RAW_SPIN_LOCK_INITIALIZER(lockname)            {                                                                   .raw_lock = __ARCH_SPIN_LOCK_UNLOCKED,                          SPIN_DEBUG_INIT(lockname)                                       SPIN_DEP_MAP_INIT(lockname)                                 }
#define __ARCH_SPIN_LOCK_UNLOCKED       { { 0 } }

    

展开 __RAW_SPIN_LOCK_UNLOCKED 宏就是

        *(lock) = __ARCH_SPIN_LOCK_UNLOCKED;

    

展开 raw_spin_lock_init 就是

        *(&(_lock)->rlock) = __ARCH_SPIN_LOCK_UNLOCKED;

    

spin_lock_init 宏的扩展之后,给定的自旋锁将会初始化并且状态变为——解锁 (unlocked)

初始化操作其实就是把 给定的自旋锁 的 rlock 设置成 0 ,表示锁是 released 状态

spin_lock —— 获取给定的自旋锁

        static __always_inline void spin_lock(spinlock_t *lock)
{
    raw_spin_lock(&lock->rlock);
}

    

raw_spin_lock

        #define _raw_spin_lock(lock) __raw_spin_lock(lock)

    

__raw_spin_lock 函数

        static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
        preempt_disable(); // 禁用抢占(当程序正在自旋锁时,这个已经获取锁的程序必须阻止其他程序方法的抢占)
        spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
        LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

    

跳过 spin_acquire

分析 LOCK_CONTENDED

        LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);

    

LOCK_CONTENDED

        #define LOCK_CONTENDED(_lock, try, lock)          lock(_lock)

    

其实 lock 就是 do_raw_spin_lock

        static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{
        __acquire(lock); // [稀疏(sparse)]相关宏
         arch_spin_lock(&lock->raw_lock);
}
#define arch_spin_lock(l)               queued_spin_lock(l)

    

arch_spinlock

        typedef struct arch_spinlock {
        union {
                __ticketpair_t head_tail;
                struct __raw_tickets {
                        __ticket_t head, tail;
                } tickets;
        };
} arch_spinlock_t;

    

这个自旋锁的变体被称为——标签自旋锁 (ticket spinlock)

当锁被获取,如果有程序想要获取自旋锁,它就会将 tail 的值加 1,如果 tail != head ,那么程序就会被锁住,直到 tail == head

arch_spin_lock

        #define __TICKET_LOCK_INC       1
#define cpu_relax()     asm volatile("rep; nop")
static __always_inline void arch_spin_lock(arch_spinlock_t *lock)
{
        register struct __raw_tickets inc = { .tail = TICKET_LOCK_INC }; // tail = 1
        inc = xadd(&lock->tickets, inc); // 这个操作过后 &lock->tickets = inc (这是个原子操作,详细请自行搜索 xadd)
        // 锁的关键就在这里了,只要 inc.head == inc.tail 成立,就说明这个锁没有被其他进程获取
        if (likely(inc.head == inc.tail))
                goto out; // 这个锁没有被获取,直接跳到 out 去执行
        // inc.head != inc.tail 说明有线程获取了这个锁,进入这个循环,等待这个锁被释放
        for (;;) {
                 unsigned count = SPIN_THRESHOLD; // 这是类似于信号量的 timeout 的东西,这个变量定义了进程 "等多久" (while执行多少次)
                 do {
                       // 把 head 读出来
                       inc.head = READ_ONCE(lock->tickets.head);
                       // 对比 head 和 tail,相等就说明这个锁被释放了
                       if (__tickets_equal(inc.head, inc.tail))
                                goto clear_slowpath;
                        cpu_relax(); // #define cpu_relax()     asm volatile("rep; nop"),就是一个 nop 指令,啥都不做
                 } while (--count);
                 __ticket_lock_spinning(lock, inc.tail);
         }
clear_slowpath:
        __ticket_check_and_clear_slowpath(lock, inc.head);
out:
        barrier(); // 屏障指令(防止 CPU 乱序)
}

    

spin_unlock -- 释放给定的自旋锁

其实这个锁的释放就是让 head1

核心操作

        __add(&lock->tickets.head, TICKET_LOCK_INC, UNLOCK_LOCK_PREFIX);

    

这样的话所有的等待进程就形成一个队列

head 是当前获得锁的进程的编号

tail 就是正在等待的进程的编号

在锁没有被释放的时候, 一直有进程请求这个锁,请求一次 tail 就加 1

释放锁的时候是 head1, 这样对应的 tail (head == tail)的进程就能获得锁

就像是这样的

                  +-------+
head      |   3   |
          +-------+

                  +-------+-------+-------+-------+
tail              |   4   |   5   |   6   |   7   |
                  +-------+-------+-------+-------+

    

现在 tail 等于 3 , head 等于 3

释放锁后, head 等于 4

                  +-------+
head      |   4   |
          +-------+

          +-------+-------+-------+-------+
tail      |   4   |   5   |   6   |   7   |
          +-------+-------+-------+-------+

    

这样 tail == 4 的进程就能获得锁

本文参考

《Linux Inside》:https://github.com/0xAX/linux-insides

《内核揭秘(中文版)》:https://github.com/MintCN/linux-insides-zh

原文:https://www.cnblogs.com/crybaby/p/13062811.html


【说明】本文章由站长整理发布,文章内容不代表本站观点,如文中有侵权行为,请与本站客服联系(QQ:254677821)!

相关教程推荐

其他课程推荐