linux设备驱动中的并发控制

来源:互联网 发布:淘宝网上有毛片买吗 编辑:程序博客网 时间:2024/06/07 04:42
并发控制的概念

        当并发进程竞争使用同一资源时,它们之间就会发生冲突。如果操作系统将资源分配给其中的某一个进程使用,

另一个进程就必须等待,直到申请的资源可用时由操作系统分配给它。如果竞争某资源的进程太多,这些进程还必

须等待在一个队列中,如就绪队列、阻塞队列等。一种极端的情况是,被阻塞进程永久得不到申请的资源而死锁。

竞态发生的情况

----对称多处理器(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);

 

0 0