当并发进程竞争使用同一资源时,它们之间就会发生冲突。如果操作系统将资源分配给其中的某一个进程使用,
另一个进程就必须等待,直到申请的资源可用时由操作系统分配给它。如果竞争某资源的进程太多,这些进程还必
须等待在一个队列中,如就绪队列、阻塞队列等。一种极端的情况是,被阻塞进程永久得不到申请的资源而死锁。
竞态发生的情况
----对称多处理器(SMP)的多个CPU
----单CPU内进程与抢占它的进程
----中断(硬中断、软中断、Tasklet、底半部)与进程之间
解决竞态问题的途径
----保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源的时候,
----其他的执行单元被禁止访问。
----访问共享资源的代码称为临界区,临界区需要被以某种互斥机制加以保护。
互斥机制
(1)中断屏蔽;(2)原子操作;(3)自旋锁;(4)信号量;(5)完成量;(6)互斥体
一、中断屏蔽
在中断屏蔽期间所有的中断都无法得到处理,因此长时间屏蔽中断是很危险的,有可能造成数据丢失乃至系统崩溃等后果。
----local_irq_disable() //屏蔽中断
----/*临界区*/
----local_irq_enable() //开中断
----这种方式并不能解决SMP多CPU引发的竞态
注:local_irq_disable()和local_irq_enable()只能禁止和使能本CPU内的中断.local_irq_save(flags);//禁止中断并保存当前CPU的中断位信息
local_irq_restore(flags);//恢复中断
local_bh_disable();//禁止中断底半部
local_bh_disable();//恢复中断底半部
二、原子操作
在执行的过程中不会被别的代码路径所中断的操作,原子变量所保护的共享资源通常是一个简单的整数值。
1、整形原子操作
1)设置原子变量的值
void atomic_set(atomic_t *v,int i);//设置原子变量的值为i
atomic_t v=ATOMIC_INIT(0);//定义原子变量V并初始化为0
2)获取原子变量的值
atomic_read(atomic_t *v);//返回原子变量的值
3)原子变量的加/减
void atomic_add(int i,atomic_t *v);//原子变量加i
void atomic_sub(int i,atomic_t *v);//原子变量减i
4)原子变量自增/减
void atomic_inc(atomic_t *v);//原子变量增加1
void atomic_dec(atomic_t *v);//原子变量减1
5)操作并测试
int atomic_inc_and_test(atomic_t *v);//操作并测试是否为0
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i,atomic_t *v);
6)操作并返回
int atomic_add_return(int i,atomic_t *v);
int atomic_sub_return(int i,atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
2、位原子操作
1)设置位
void set_bit(nr,void *addr);//将addr的第nr位设置为1
2)清楚位
void clear_bit(nr,void *addr);//将addr的第nr为设置为0
3)改变位
void change_bit(nr,void *addr);//将addr的第n位进行反置
4)测试位
test_bit(nr,void *addr);//返回addr的第nr位
5)测试并操作位
int test_and_set_bit(nr,void *addr);
int test_and_clear_bit(nr,void *addr);
int test_and_change_bit(nr,void *addr);
----实现方法:
----头文件asm/atomic.h
----atomic_t v=ATOMIC_INIT(1); //定义原子变量v并初始化为1
----if(! atomic_dec_and_test(v)) //自减1测试
----{ atomic_inc(&v); return –EBUSY; } //设备忙返回错误码
----/*临界区*/ //临界区代码
----atomic_inc(&c); //释放设备
三、自旋锁
如果进程A首先进入临界区,他将拥有自旋锁,当进程B试图进入同一临界区时,获知自旋锁已被持有,则在原地“自旋”,
等待进程A执行单元释放自旋锁后再进入临界区。
1、定义自旋锁
spinlock_t lock;
2、初始化自旋锁
spin_lock_init(lock);
3、获取自旋锁
spin_lock(lock);
spin_trylock(lock);//尝试获得自旋锁,如不能获得立即返回
4、释放自旋锁
spin_unlock(lock);
----实现方法:
----spinlock_t lock; //定义自旋锁
----spin_lock_init(&lock); //初始化自旋锁
----spin_lock(&lock); //获得自旋锁
---- /*临界区*/ //临界区代码
----spin_unlock(&lock); //释放自旋锁
一个例子:
static int cdev_open(struct inode*inode, struct file *file)
{
spin_lock(&lock);
if(flag== 1){
flag--;
spin_unlock(&lock);
printk(KERN_INFO"cdev_open !\n"); //临界区代码
return0;
}else{
spin_unlock(&lock);
return-EBUSY;
}
return0;
}
static int cdev_release(struct inode*inode, struct file *file)
{
flag++;
return 0;
}
----说明:自旋锁是一种忙等待锁,中间不能有可引起睡眠的操作;临界区不能太长;自旋
----锁可用在中断上下文中
四、信号量
信号量与自旋锁相同,只有得到信号量的进程才能执行临界区代码,但与自旋锁不同的是,获取不到信号量时,
进程不会原地“自旋”,而是进入休眠等待状态。
1、定义信号量
struct semaphore sem;
2、初始化信号量
void sem_init(struct semaphore *sem,int val);
3、获得信号量
void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem);//可被信号打断
int down_trylock(struct semaphore *sem);
4、释放信号量
void up(struct semaphore *sem);
5、信号量的使用
DECLARE_MUTEX(sem);
down(&sem);//获得信号量
...//临界操作
up(&sem);
----实现方法:
----struct semaphore sem; //定义信号量
----sema_init(&sem,1); //初始化信号量
----down_interruptible(&sem); //获取信号量
----/*临界区*/ //临界区代码
----up(&sem); //释放信号量
----说明:信号量不同于自旋锁,他是一种睡眠锁,不能用在中断上下文中。
一种简单的使用方法:
----if(down_trylock(&sem)) //尝试打开锁
----return –EBUSY; //设备忙
----/*临界区*/ //临界区代码
----up(&sem); //释放打开的锁
只有当进程占用资源时间较长时,用信号量才是最好的选择;
当对所要保护的临界区的访问时间比较短时,用自旋锁是非常方便的,因为他节省进程切换消耗的时间。
信号量和自旋锁作为解决互斥问题的主要手段, 无论是单处理系统还是多处理系统, 它们都可以不需修改
代码就进行移植, 通过以上分析总结出选择信号量和自旋锁的三项原则:
1) 当锁不能被获取时, 信号量的开销是进程上下文切换时间 Tsw, 自旋锁的开销是临界区的执行时间 Tcs,
若Tcs较小, 选自旋锁, 否则选信号量;
2) 信号量所保护的临界区可以包含引起阻塞的代码, 而自旋锁不可以, 否则将会发生死锁;
3) 如果共享资源在中断或软中断情况下使用,则只能选自旋锁, 如果一定要使用信号量应使用down_trylock( ),
不能获取就立即返回以避免阻塞。
注意信号量不同于自旋锁, 它不会关闭内核抢占, 所以持有信号量的代码可以被抢占。这意味着信号量不会对影响调度反应时间带来负面影响。
读写自旋锁和读写信号量分别是放宽了条件的自旋锁与信号量,它们允许多个进程对共享资源的并发读。
五、完成量
完成量用于同步机制,它用于一个执行单元等待另一个执行单元完成某事。
1、定义完成量
struct completion my_completion;
2、初始化完成量
init_completion(&my_completion);
3、等待完成量
void completion(struct completion *my_completion);
void completion_all(struct completion *my_completion);
----实现方法:
进程A 进程B
struct completionmy_completion;
init_completion(&my_completion);
代码区域a
wait_for_completion(&my_completion) 代码区域c
代码区域b complete(&my_completion); //唤醒完成量,进程A继续
六、互斥体
1、初始化互斥体
struct mutex my_mutex;
mutex_init(&my_mutex);
2、获取互斥体
void inline mutex_lock(struct mutex *my_mutex);
int mutex_lock_interuptible(struct mutex *my_mutex);
int mutex_trylock(struct mutex *my_mutex);
3、释放互斥体
void mutex_unlock(struct mutex *my_mutex);
----实现方法:
----struct mutex my_mutex;
----mutex_init(&my_mutex);
----mutex_lock(&my_mutex);
----/*临界区*/
----mutex_unlock(&my_mutex);