[读书笔记]Linux Inside: 同步原语01-Spinlocks

本系列博客为Linux Inside文章中Linux同步原语的阅读笔记. 主要包含了SpinlocksSemaphoresMutexReader/Writer semaphoresSeqLockRCULockdep.

Linux内核提供了多种同步原语为防止多进程或多线程情况下的Race Condition. 这些同步原语的使用在内核代码中随处可见. 他们主要有:

  • spinlocks;
  • mutex;
  • semaphores;
  • seqlocks;
  • atomic operations;

Spinlocks

Spinlocks是一种低阶的同步机制, 其用一个变量表示该锁的状态: acquiredreleased. 任何一个想要获得该锁的进程都必须向该变量写入值表示该锁被某进程acquired, 或想要释放锁的进程都必须向该变量写入值表示该锁被某进程released, 即其用变量表示锁的状态. 因此所有相关(写入变量)的操作必须是atomic的以防止出现数据竞争问题.

结构体定义

Linux内核中SpinLocksspinlock_t结构体来表示. 该结构体的定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
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;

该结构体的定义位于 include/linux/spinlock_types.h 头文件中. 如果内核中CONFIG_DEBUG_LOCK_ALLOC设置被禁用, 那么spintlock_t包含一个联合体, 该Union只有一个字段: raw_spinlock.

raw_spinlock的表示spinlock的具体实现结构体. 该结构体的定义如下:

1
2
3
4
5
6
7
8
9
10
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} raw_spinlock_t;

其中arch_spinlock_t代表架构相关的Spinlocks的具体实现. x86_64架构中的具体实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct qspinlock {
union {
atomic_t val;
struct {
u8 locked;
u8 pending;
};
struct {
u16 locked_pending;
u16 tail;
};
};
} arch_spinlock_t;

相关处理函数

内核提供的自旋锁相关的函数有:

  • spin_lock_init: 对给定的spinlock初始化.
  • spin_lock: 锁定给定的spinlock.
  • spin_unlock: 释放给定的spinlock.
  • spin_is_locked: 检查给定的spinlock的状态.
  • spin_lock_bh: 禁止软件中断并锁定给定的spinlock.
  • spin_unlock_bh: 释放给定的spinlock并允许软件中断.

初始化Spinlocks

其中spin_lock_init宏在include/linux/spinlock.h头文件中声明, 它的具体实现为:

1
2
3
4
5
# define spin_lock_init(_lock)			\
do { \
spinlock_check(_lock); \
*(_lock) = __SPIN_LOCK_UNLOCKED(_lock); \
} while (0)

该宏定义执行了两个操作: 检查给定的spinlock并且执行__SPIN_LOCK_UNLOCKED. 检查spinlock的具体实现很简单, 该函数仅仅返回raw_spinlock_t以保证我们得到normal raw spinlock.

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

正如__SPIN_LOCK_UNLOCKED的名字所示, 该宏初始化给定的spinlock并且将其中变量设置为released状态. 该宏定义在include/linux/spinlock_types.h头文件中, 具体实现为:

1
2
3
4
5
6
7
8
9
10
11
#define ___SPIN_LOCK_INITIALIZER(lockname)	\
{ \
.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED, \
SPIN_DEBUG_INIT(lockname) \
SPIN_DEP_MAP_INIT(lockname) }

#define __SPIN_LOCK_INITIALIZER(lockname) \
{ { .rlock = ___SPIN_LOCK_INITIALIZER(lockname) } }

#define __SPIN_LOCK_UNLOCKED(lockname) \
(spinlock_t) __SPIN_LOCK_INITIALIZER(lockname)

我们无需关系DEBUG相关的初始化(SPIN_DEBUG_INIT(lockname)SPIN_DEP_MAP_INIT(lockname)). 因此__SPIN_LOCK_UNLOCKED宏定义将会被展开成:

*(_lock) = (spinlock_t) { .rlock = {.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED} }.

其中x86_64架构的__ARCH_SPIN_LOCK_UNLOCKED的宏定义为:

#define __ARCH_SPIN_LOCK_UNLOCKED { { .val = ATOMIC_INIT(0) } }.

因此经过一系列的宏定义展开后, spin_lock_init宏将给定的spin_lockatomic状态变量设置为0, 表示unlocked状态.

对Spinlocks上锁

spin_lock函数定义在include/linux/spinlock.h文件中.

1
2
3
4
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)

如果允许SMP并且设置了CONFIG_INLINE_SPIN_LOCK. 那么_raw_spin_lock的定义为:

#define _raw_spin_lock(lock) __raw_spin_lock(lock)

该宏对应的函数定义为:

1
2
3
4
5
6
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);
}

该函数首先调用preempt_disable宏来禁用抢占. 当释放该自旋锁的时候, 抢占将会被允许:

1
2
3
4
5
6
static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
preempt_enable();
}

需要通过禁用抢占来防止其他进程抢占该锁. spin_acquire通过一系列的宏展开为:

#define spin_acquire(l, s, t, i) lock_acquire_exclusive(l, s, t, NULL, i)
#define lock_acquire_exclusive(l, s, t, n, i) lock_acquire(l, s, t, 0, 1, n, i)

其中lock_acquire函数的实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void lock_acquire(struct lockdep_map *lock, unsigned int subclass,
int trylock, int read, int check,
struct lockdep_map *nest_lock, unsigned long ip)
{
unsigned long flags;

if (unlikely(current->lockdep_recursion))
return;

raw_local_irq_save(flags);
check_flags(flags);

current->lockdep_recursion = 1;
trace_lock_acquire(lock, subclass, trylock, read, check, nest_lock, ip);
__lock_acquire(lock, subclass, trylock, read, check,
irqs_disabled_flags(flags), nest_lock, ip, 0, 0);
current->lockdep_recursion = 0;
raw_local_irq_restore(flags);
}

lock_acquire通过raw_local_irq_save宏来关闭硬件中断. 因为给定的自旋锁可能通过开启硬件中断来获取. 这样该进程就不会被抢占, 在该函数的最后通过raw_local_irq_restore来重新开启中断. 主要的功能在__lock_acquire函数中完成(作者说到会在之后的章节解读该函数).

__raw_spin_lock函数的最后, 会执行LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);

LOCK_CONTENDED该宏定义在include/linux/lockdep.h头文件中, 并只是调用给定的函数.

1
2
#define LOCK_CONTENDED(_lock, try, lock) \
lock(_lock)

此时lockdo_raw_spin_lock函数, _lock是给定的raw_spinlock_t:

1
2
3
4
5
static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{
__acquire(lock);
arch_spin_lock(&lock->raw_lock);
}

其中arch_spin_lock宏定义为:

#define arch_spin_lock(l) queued_spin_lock(l)

下一节中将深入了解queued spinlocks的工作原理和相关概念.

作者

Jsss

发布于

2022-04-20

更新于

2022-05-01

许可协议


评论