原子操作
自旋锁
自旋锁(spin lock)和互斥锁类似,但是自旋锁在失败时不会放弃 CPU,而是不断尝试获取锁。直至成功。Linux 内核中的 自旋锁是不可递归的。
读写自旋锁
读写自旋锁和普通自旋锁类似,但是区分了读写操作。下面是相关的 API:
API | 作用 |
---|---|
DEFINE_RWLOCK(x) | 定义一个名为 x 的读写自旋锁 |
rwlock_init | 初始化读写锁 |
read_lock | 读加锁 |
read_unlock | 读解锁 |
write_unlock | 写加锁 |
write_unlock | 写解锁 |
内核中的读写自旋锁是一种读优先自旋锁,这意味着即使是在尝试获取写锁的过程中依然可以加读锁。
信号量
信号量分为两种:计数信号量和二值信号量。二值信号量可简单等价于可跨线程的互此锁,计数信号量的资源可以为任意值。
信号量的 PV 操作在内核中被称为 down 和 Up 操作。
信号量相关的 API 如下:
API | 作用 |
---|---|
DECLARE_MUTEX(x) | 声明并初始化一个名为 x 的信号量。 |
sema_init | 初始化一个信号量。 |
init_MUTEX(sem) | 初始化一个互斥信号量。 |
down_interruptible | 尝试获取信号量,允许中断。 |
down | 尝试获取信号量,不允许中断。 |
down_trylock | 无阻塞地尝试获取信号量。 |
up | 释放一个信号量,如果休眠队列不为空,唤醒其中一个任务。 |
读写信号量
顺序锁
seqlock 和读写锁类似,都是针对读多写少的场景,但是顺序锁的读锁不会阻塞写锁。
由于读锁不会阻塞写锁,因此在进入临界区的时候并不会加锁,为了解决读脏数据的问题,seqlock 引入了 seqcount:
typedef struct {
struct seqcount seqcount;
spinlock_t lock;
} seqlock_t;
写锁在进入临界区后首先使用自旋锁进行加锁(因为写锁是互斥的),然后将 seqcount 加一,在退出临界区后再将 seqcount 加一。这样若 seqcount 为奇数,则表明有写锁在临界区。
读锁在进入临界区的时候会检查 seqcount,当 seqcount 为奇数的时候会陷入自旋状态,当 seqcount 为偶数时才会进入临界区。
当读锁退出临界区时会检查序号是否一致,若不一致,则需要重新读取数据。
顺序锁相关的 API 如下:
API | 作用 |
---|---|
DEFINE_SEQLOCK(x) | 定义一个名为 x 的 seq 锁。 |
write_seqlock | 获取写锁。 |
write_sequnlock | 解锁写锁。 |
read_seqbegin | 读临界区开始。 |
read_seqretry | 尝试读取数据。 |
互斥锁
互斥锁和二值信号量功能类似。互斥锁的 API 如下:
API | 作用 |
---|---|
DECLARE_MUTEX(x) | 定义一个名为 x 的互斥锁。 |
mutex_init | 初始化一个已有的互斥锁。 |
mutex_lock | 尝试加锁,如果失败则睡眠。 |
mutex_unlock | 为指定 mutex 解锁。 |
mutex_trylock | 尝试加锁,失败返回 0,成功返回 1。 |
mutex_is_locked | 检查是否一个锁已经加锁。 |
内核中的互斥锁不是递归的。
加锁者和解锁者必须是同一个人。否则结果未定义。
持有 mutex 时进程不能退出。
内存顺序
see ../cpp/线程.html