一、什么是并发
并发就是多个执行单元或多个进程并行执行,而这多个执行单元对资源进行共享,比如访问同一个变量或同一个硬件资源,这个时候就很容易出现竞态(说简单点就是竞争同一个"女朋友")。
二、如何处理并发带来的问题
为了处理并发带来的问题,Linux有几种处理方法:
1. 中断屏蔽
2. 原子操作
3. 自旋锁
4. 信号量
5. 互斥体
6. 完成量
以上几种处理并发的方式各有利弊,需要根据实际情况来选择使用哪一种。
三、并发处理方法介绍
- 中断屏蔽
使用方法:
local_irq_disable() //屏蔽中断
.......
代码
.......
local_irq_enable() //开启中断
它的原理就是让CPU不响应中断,一般使用这种方法的要求代码段要比较少,不能占用大量的时间。一般在驱动中不推荐使用这种方式。
- 原子操作
使用方法:
atomic v = ATOMIC_INIT(1); //定义原子量,初值为1
//如果原子量减1后为0就返回true
if(atomic_dec_and_test(&v)){
....
代码
...
atomic_inc(v); //原子量加1
}else{
atomic_inc(v);
}
原子量保证对一个整形数据的操作是排他性的。就是该操作绝不会在执行完毕前被任何其他任务或事件打断
- 自旋锁
使用方法:
spinlock_t lock = SPIN_LOCK_UNLOCKED; //初始化自旋锁
spinlock(&lock); //获取自旋锁
....
代码
....
spin_unlock(&lock); //释放自旋锁
自旋锁是一种典型的对临界资源进行互斥访问的手段。当一个自旋锁已经被其他线程持有,而另一个线程试图去获取自旋锁,这时候就会一直在那里等待(原地自旋等待)。如果递归调用自旋锁,就会导致系统死锁。
- 信号量
使用方法:
struct semaphore sem;
sema_init(&sem, 1); //将信号初始化为1,大于0,表示空闲
down(&sem); //对信号量减1,如果大于等于0,则继续执行,否则进入休眠
...
代码
...
up(&sem); //对信号量加1,如果大于0,就唤醒等待队列中的进程。
与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。新的Linux内核倾向于直接使用mutex作为互斥手段,信号量用作互斥不再被推荐使用。
- 互斥体
使用方法:
struct mutex my_mutex; //定义mutex
mutex_init(&my_mutex); //初始化mutex
mutex_lock(&my_mutex); //获取mutex
...
代码
...
mutex_unlock(&my_mutex); //释放mutex
当进程占用资源事件较长时,用互斥体会比较好。
- 完成量
DECLARE_COMPLETION(com); //定义并初始化完成量。
wait_for_completion(&com); //线程进入休眠
complete_all(&com); //唤醒所有等待队列
完成量的机制是实现一个线程发送一个信号通知另一个线程完成某个任务。一个线程调用wait_for_completion后就进入休眠,另一个线程执行完某个任务后就发送通知给进入休眠的线程,然后它就执行wait_for_completion后面的代码。