并发问题
现代CPU大都支持对称多处理(symmetric multiprocessing,SMP),在开发内核的时候不得不考虑并发因素。
锁
信号量
信号量(Semaphore)是计算机科学一个通用的概念,它是一个整数值,并和一对函数P
,V
一起使用。希望进入临界值的进程调用P
,如果值大于1,则减小1并执行进程;如果值为0或更小,则必须等待他人释放。释放调用V
,他会增加信号量的值,并必要时唤醒等待的进程。
如果信号量的值一开始为1,也叫做互斥锁(Mutex)。
Linux中的相关代码在<asm/semaphore.h>
自旋锁
自旋锁和Mutex的区别在于,前者如果获取不到锁会忙等待,后者则会让出CPU。无论Mutex还是自旋锁。其最底层的锁状态竞争都是通过原子指令实现的。以atomic_test_and_set
这个原子指令为例,给出示例代码如下:
void spin_lock(spinlock_t *lock) {
while (atomic_test_and_set(&lock->flag) == LOCKED) { // CAS循环
cpu_relax(); // 提示CPU降低功耗(如x86的PAUSE指令)
}
}
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/wait.h>
// 互斥锁结构体
struct kernel_mutex {
atomic_t lock; // 锁状态:0=空闲, 1=锁定
wait_queue_head_t wait; // 等待队列
struct task_struct *owner; // 当前持有者(用于调试/死锁检测)
};
// 初始化互斥锁
void mutex_init(struct kernel_mutex *m)
{
atomic_set(&m->lock, 0); // 初始状态为空闲
init_waitqueue_head(&m->wait); // 初始化等待队列
m->owner = NULL;
}
// 获取锁
void mutex_lock(struct kernel_mutex *m)
{
// 尝试快速获取锁
if (atomic_test_and_set(&m->lock, 0, 1) == 0) {
m->owner = current; // 记录当前持有者
return;
}
// 慢路径:锁已被占用
DEFINE_WAIT(wait); // 定义等待项
while (true) {
prepare_to_wait(&m->wait, &wait, TASK_UNINTERRUPTIBLE);
// 再次尝试获取锁(避免唤醒后竞争)
if (atomic_test_and_set(&m->lock, 0, 1) == 0) {
break;
}
// 让出CPU - 核心差异点!
schedule();
}
finish_wait(&m->wait, &wait);
m->owner = current; // 设置新持有者
}
// 释放锁
void mutex_unlock(struct kernel_mutex *m)
{
// 清除锁状态
atomic_set(&m->lock, 0);
m->owner = NULL;
// 唤醒一个等待者
wake_up(&m->wait);
}