一.概念
1.并发(concurrency)指多个执行单元同时,并行被执行。
2.并发的执行单元对共享资源,(比如说,硬件资源,全局变量,静态变量等)访问很容易导致竞态(race conditions)。
举个简单事例,古代皇帝有很多妃子,皇帝第一个时辰是宜妃的,第二个时辰是萱妃,最后时间是皇后的,这是原来正常的顺序。假如宜妃让皇帝赖床了,萱妃不爽了,然后萱妃时间冲突了,皇后也不爽了,竞争就产生了,然后就是一部血雨腥风的后宫大戏。在linux中我们不需要这种大戏。
3.造成竞态的主要原因:
1.对称多处理器(SMP)
多个CPU使用共同的系统总线,访问共同的外设和存储器。
2.单CPU内进程与抢占它的进程
高优先级抢占低优先级
3.中断与进程之间
4.中断与中断之间
4.解决方案
保证对共享资源的互斥访问。
访问共享资源的代码区域称为临界区。
方法:中断屏蔽,原子操作,自旋锁,信号量等
二.用法
1.中断屏蔽
单CPU范围内避免竞态的一种简单而省事的方法
一旦中断屏蔽,进程调度不会发生,中断和进程之间的并发不会发生
缺点:很多硬件事件不能得到及时处理,可能造成数据丢失,系统崩溃。
为什么呢,因为linux的异步I/O,进程调度等都依赖于中断,在屏蔽中断期间所有的中断都无法得到处理。
注意:单独使用中断屏蔽不是一个好方法,宜与自旋锁联合使用
用例
local_irq_disable();
...临界区...
local_irq_enable();
2.原子操作
在执行过程中不会被别的代码路径所中断的操作
函数分两类,分别针对 位和整型变量
用例:
static atomic_t XXX = ATOMIC_INIT(1); //定义原子变量并初始化为1
if(!atomic_dec_and_test(&XXX)) // atomic_dec_and_test 测试减一是否为零,为零返回true
{
atomic_inc(&XXX);
return -EBUSY;
}
atomic_inc(&XXX); //释放设备
3.自旋锁
自旋锁有“加锁”和“解锁”两种状态。
我的理解是,“加锁”一直在寻求“解锁”,“解锁”马上会寻求“加锁”,并原地打转,所以加锁位置的代码进入临界区执行,直到解锁。
注意:
1.占用临界区的时间必须短;
2.拥有自旋锁期间所在的CPU进程抢占被关闭
3.占有自旋锁期间不能调用引起阻塞的函数(原子上下文)比如:copy_from_user(), kmalloc(), msleep()
4.自旋锁可以被用于中断上下文
特殊化自旋锁:读写自旋锁,顺序锁,RCU锁
用例:
spinlock_t lock; //定义自旋锁
spin_lock_init(&lock); //初始化自旋锁
spin_lock(&lock); // 获取自旋锁
...临界区...
spin_unlock(&lock)
4.信号量
与自旋锁类似,不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。
注意:
1.信号量是进程级的,当进程占用资源时间较长时,信号量是较好的选择(因为进程上下文切换的开销很大)
2.临界区可以包含可能引起阻塞的代码,可以进入休眠
3.信号量不能被用于中断上下文
用例
DECLARE_MUTEX(XXX_sem); //定义一个 信号量并初始化为1
down(&XXX_sem); //获取信号量
...临界区...
up(&XXX_sem); //释放信号量