1 基本概念
临界区:访问和操作共享数据的代码段
竞争条件:多个执行线程处于同一临界区中同时执行
同步:避免并发和防止竞争条件
2 造成并发执行的原因
(1)中断:中断随时会发生,也就会随时打断当前执行的代码。
(2)软中断和tasklet:软中断和tasklet也会随时被内核唤醒执行,也会像中断一样打断正在执行的代码
(3)内核抢占:内核具有抢占性,发生抢占时,如果抢占的线程和被抢占的线程在相同的临界区,就产生了竞争条件
(4)睡眠及用户空间的同步:用户进程睡眠后,调度程序会唤醒一个新的用户进程,新的用户进程和睡眠的进程可能在同一个临界区中
(5)对称多处理:2个或多个处理器可以同时执行相同的代码
注意:要给数据加锁而不是给代码加锁。
3 死锁
(1)产生原因
一个或多个执行线程和一个或多个资源,每个线程都在等待其中一个资源,但所有资源已经都被占了。所有线程相互等待,但他们永远不会释放已经占有的资源。
(2)避免死锁
1)按顺序加锁,使用嵌套的锁时,必须保证以相同的顺序获取锁。
2)防止发生饥饿。即设置一个超时时间,防止一直等待下去。
3)不要重复请求同一个锁。
4)设计应力求简单。加锁的方案越复杂就越容易出现死锁。
4 内核同步方法
4.1 原子操作
atomic_t类型定义在<linux/types.h>
32位:atomic_t
64位:atomic64_t
4.2 自旋锁
自旋锁(spin lock),只能被一个线程持有,某个线程试图获取锁,该线程会忙-旋转-等待。
基本使用:
DEFINE_SPINLOCK(mr_lock);
spin_lock(&mr_lock);
//临界区
spin_unlock(&mr_lock);
注意:
1)可用在中断处理程序中
2)在获取锁前,首先禁止当前处理器上的本地中断
4.3 读写锁
多个读任务可以并发地持有锁,只能有一个任务可以持有写锁
使用
DEFINE_RWLOCK(mr_rwlock);
read_lock(&mr_rwlock);
/* 临界区(只读).... */
read_unlock(&mr_rwlock);
write_lock(&mr_lock);
/* 临界区(读写)... */
write_unlock(&mr_lock);
4.4 信号量
是一种睡眠锁,当任务不能获取信号量时,睡眠。
适用于长时间持有的情况
两种:计数信号量、二值信号量
/* 定义并声明一个信号量,名字为mr_sem,用于信号量计数 */
static DECLARE_MUTEX(mr_sem);
/* 试图获取信号量....*/
if(down_interruptible(&mr_sem)){
...
}
/* 临界区 ... */
/* 释放给定的信号量 */
up(&mr_sem);
4.5 读写信号量
与读写自旋锁类似。
4.6 互斥体
类似二值信号量。
相对信号量,优先选择互斥体。
自旋锁和互斥量
需求 | 建议的加锁方法 |
---|---|
低开销加锁 | 优先使用自旋锁 |
短期锁定 | 优先使用自旋锁 |
长期加锁 | 优先使用互斥体 |
中断上下文中加锁 | 使用自旋锁 |
持有锁需要睡眠 | 使用互斥体 |
4.7 完成变量
一个任务执行工作,另一个任务在完成变量上等待,类似信号量。
方法
方法 | 描述 |
---|---|
init_completion(struct completion *) | 初始化指定的动态创建的完成变量 |
wait_for_completion(struct completion *) | 等待指定的完成变量接受信号 |
complete(struct completion *) | 发信号唤醒任何等待任务 |
4.8 BLK:大内核锁
不鼓励使用,部分代码中依然沿用。
特性:
1)持有BLK的任务可以睡眠
2)递归锁
3)只可以用在进程上下文中
4.9 顺序锁
读锁被获取时,写锁仍然可以被获取。依靠一个序列计数器,当有疑义的数据被写入时,序列值增加。
使用
//定义
seqlock_t mr_seq_lock = DEFINE_SEQLOCK(mr_seq_lock);
//写
write_seqlock(&mr_seq_lock);
/*写锁被获取*/
write_sequnlock(&mr_seq_lock);
//读
unsigned long seq;
do
{
seq = read_seqbegin(&mr_seq_lock);
...
} while(read_seqretry(&mr_seq_lock, seq));
适用:数据存在多个读者,写者很少,希望写优先于读,数据简单
4.10 禁止抢占
对于SMP,某些情况不需要自旋锁,但仍需要关闭内核抢占。
preempt_disable()
/*抢占被禁止*/
preempt_enable()
4.11 顺序和屏障
目的:确保指令顺序执行,不被处理器重排序