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

Posted scriptkid

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于Linux内核源码分析 -- 同步原语 -- 自旋锁 spinlock的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核源码分析 -- 同步原语 -- 信号量 semaphore

Linux内核源码分析 -- 同步原语 -- 信号量 semaphore

同步原语之信号量

Linux内核同步原语之原子操作

一文搞懂 , Linux内核—— 同步管理(下)

linux内核同步问题