任何编程语言的锁操作本质都依赖于 CPU 底层锁指令。操作系统封装 CPU 底层锁指令实现了自旋锁和互斥锁两种基础锁,并通过系统调用接口提供给用户程序。
Linux 中的自旋锁由 spinlock_t 结构体表示,相应的系统调用函数包括
- spin_lock():获取自旋锁
- spin_unlock():释放自旋锁
Linux 中的互斥锁由 mutex_t 结构体表示,相应的系统调用函数包括
- mutex_lock():获取互斥锁
- mutex_unlock():释放互斥锁
x86 锁指令
锁指令在单核显然是原子的,在多核情况下会通过锁总线的方式保证原子性
# TAS指令,将destination的值和source的值进行交换,可以理解成写destination并返回原值
lock xchg destination source
# CAS指令,将destination的值与compare的值进行比较,如果相等就给destination赋exchange的值,如果不相等就不变
lock cmpxchg destination, exchange, compare
# 将value加到destination
lock add value, destination
# 将destination减去value
lock sub value, destination
# destination加1
lock inc destination
# destination减1
lock dec destination
TAS 指令实现忙等待锁
lock:
lock xchg lock_var, %eax; # 将lock_var值和eax寄存器值交换
test %eax, %eax; # 判断eax寄存器的值是否为0
jnz lock; # 如果不为0则继续自旋
# ...执行临界区代码
CAS 指令实现无锁计数器
retry:
mov count %eax; # count值写入eax寄存器
mov %eax %ebx; # eax寄存器值写入ebx寄存器
inc %eax; # eax寄存器加1
lock cmpxchg count, %eax, %ebx; # 如果count和ebx想等,就将eax寄存器的值写入count
jne retry; # 如果上一步失败就跳转到retry
# ...执行临界区代码
CAS 指令实现无等待锁
下面的过程可以看作是一个系统调用函数
# 全局变量
int mutex = 0;
# 加锁过程
while(!cas(mutex,1,0)) {
# 保存当前线程上下文
# 将当前线程从运行态切换到阻塞态
# 跳转到线程调度函数,根据调度算法选择一个就绪线程将其切换到运行态
# 加载该线程的线程上下文
# 将该线程第一条指令的地址写入程序计数器
}
# 解锁过程
mutex = 0;
# 查看阻塞在mutex上的所有线程
# 按照某种算法选择一个线程并将其从阻塞态切换到就绪态