驱动开发,竞发并态控制

来源:互联网 发布:淘宝上买电动车靠谱吗 编辑:程序博客网 时间:2024/06/18 12:44

防止竞态的机制有:

semaphore(信号量)   spinlock(自旋锁)   somletion,原子操作

原子操作的意思是:

操作不可分割性(代码执行是不能被打断的)

 

并发的概念:多个执行单元同时,并行被执行

竞态:并发的执行单元对共享资源的访问则很容易导致竞态。共享资源就包括:硬件资源,软件上的全局变量

 

为什么会发生竞态:

1.多个cpu之间

2.单个cpu内进程间的竞态

3.中断

 

1.中断屏蔽:

解决中断与进程之间的并发;

解决内核抢占进程之间的并发;

主要函数:

local_irq_disable()

local_irq_disable()

local_irq_save(flags)

local_irq_restore(flags)

local_bh_disable()

local_bh_enable()

这3组都要成对的出现

注意:不要长时间屏蔽中断,由于linux系统的异步i/o,等很多重要操作都依赖于中断,在屏蔽中断期间所有的中断都无法得到处理,因此最好不要长时间这样做。

模版函数:

local_irq_save(flags)//保护现场

dat=_raw_read(base+0x04);

dar&=~(1<<offs)

dat|=to<<offs;

_raw_write(dat,base+0x04)

local_irq_restore(flags);//恢复现场

 

 

2.信号量(semaphore)

本质上是一个整数

一对操作函数通常称为p和v.

进入临界区:

    相关信号量上调用p

    如果信号量》0,该值会减小一,而进程可以继续。

    如果信号量的值=0(或更小),进程必须等待知道其他人释放该信号量。

退出临界区:

    信号量的解锁通过调用v完成(+1)

    该函数增加信号量的值

     并在必要时唤醒等待的进程

信号量用于互斥

   避免多个进程同时在一个临界区中运行(已经打开一个临界区保护起来了)

   区别就在于信号量的取值

   信号量的值应初始化为1

        只能由单个进程或线程拥有

        一个信号量有时也称为一个互斥体(取值不同)

        它是互斥(mutual exclusion)的简称

         linux内核中几乎所有信号量均用于互斥。

初始化信号量

    信号量类型为struct semaphore 在<linux/semaphore.h>

    动态的初始化信号量:

    void sema_init(struct semaphore *sem, int val);

    静态的声明互斥信号量:

     DECLARE_MUTEX(name);(初始化为1)

     DECLARE_MUTEX(name);  (初始化为0)

    动态的初始化互斥信号量:

     void init_MUTEX(struct semaphore *sem);

     void init_MUTEX_LOCKED(struct semaphore *sem)

获得信号量

     void down(struct semaphore *sem);(减小信号量的值,如果不能获得信号量就一直等待)

     int down_interruptible(struct semaphore *sem);(完成同样的工作,但是可以中断)

     int down_trylock(struct semaphore *sem);(尝试去锁一下,永远不会休眠,如果信号量在调用时不可获得,就会立即返回一个非零值)

释放信号量

     void up(struct semaphore *sem)

模版:

     DELARE_MUTEX(sem);   定义信号量

     if(down_intterupt(&sem))    获取信号量,保护临界区

    {

          return -RERSTARTSYS;

    }

    临界区

    up(&sem);    释放

 

 

读取者和写入者信号量

   1.读操作并发,写操作互斥。即一个rwsem可允许一个写入者或无限多个读取者拥有该信号者

    2.写入者优先级别更高,当有大量写入者竞争该信号量时,会导致读取者“饿死”,即长时间拒绝读者的访问。

   3.但是驱动程序很少使用该机制。一般在很少需要写访问且写入者只会短期拥有信号量的时候用rwsem''''

初始化:

   void init_rwsem(struct rw_semaphore *sem);

  主要函数:

    void down_read(struct rw_semaphore *sem);

    void down_read_trylock(struct rw_sempahore *sem);

    void up_read(struct rw_semaphore *sem)

 

     

    void down_write(struct rw_semaphore *sem);

    void down_write_trylock(struct rw_sempahore *sem);

    void up_write(struct rw_semaphore *sem)

 

实例:

    rw_semaphore_t rw_sem;  //定义读写信号量

    init_rwsem(&rw_sem);//初始化读写信号量

    down read(&rw_sem)  //获取临界资源

     、、临界区、、

   up_read(&rw_sem);//释放

    //写时获取信号量

    down_write(&rw_sem);

     //临界

     up_write(&rw_sem);

 

 

completion

    1.一种轻量级的机制

     2.它允许一个线程告诉另一个线程某个工作已经完成

     初始化:

        DECLARE_COMPLETION(XXX_completion)   //静态

      

         struct completion  xxx_completion;   //动态初始化

         init_completion (&xxx_completion)   //初始化

     3.等待completion

         void wait_for_completion(struct comletion *c);

      4.触发完成

       void complete (struct completion *c);   //唤醒一个等待线程

       void complete_all(struct completion *c);  //唤醒所有等待线程

如果使用complete_all并向重复使用completion结构,则必须在使用该结构之前重新初始化它。下面这个宏可用来快速执行重新初始化:

      INT_COMPLETION(struct completion c);

      5.使用实例

      DECLARE_COMPLETION(xxx_comp);//初始化

     ssize_t  complete_read(struct file *filp....)

     {

       wait_for_completion(&xxx_comp);//等待

       return 0;

     }

    ssize_t complete_write(struct file *filp......)

    {

      complete(&xxx_comp);表示完成

      return count;

     }

 

3-5自旋锁

    一个自旋锁是一个互斥设备

   只有两个值,锁定和解锁

       通常实现为一个一个整数值中的单个位,希望获得某特定锁的代码,则测试相关的位。

       如果锁可用,则“锁定"位被设置,而代码继续进入临界区

       如果锁被其他人获得,则代码进入忙循环并重复检查这个锁,知道该锁可用为止。这个循环就是自旋锁的自旋。

初始化自旋锁:

    编译时初始化:

        spinlock_t  xxx_loca=SPIN_LOCK_UNLOCKED;

    运行时初始化:

       void spin_lock_init(spinlock_t  *lock);

锁定函数:

       void spin_lock(spinlock_t *lock);

      //获得自旋锁之前禁止中断

        void spin_lock_irq(spinlock_t *lock);

      void spin_lock_irq(spinlock_t *lock,unsigned long flags);

     //获得自旋锁之前禁止软件中断,硬件中断保持打开

       void spin_lock_bh(spinlock_t *lock);

释放自旋锁

      void spin_unlock(spinlock_t *lock);

     void spin_unlock_irq(spinlock_t *lock);

       void spin_unlock_irqrestore(spinlock_t *lock,unsigned long flags);

     void spin_unlock_bh(spinlock_t *lock);(禁止软件中断,硬件中断保持打开)

    使用模版:

     spinlock_t lock;//定义一个自旋锁

    spin_lock_init(&lock);//初始化自旋锁

       spin_lock(&lock);//获取自旋锁,保护临界区)

 

     spin_unlock(&lock);

读写自旋锁:

    任意数量的读取者可以同时进入临界区

    写入者必须互斥访问

    变量类型:rwlock_t

初始化

        #include<linux/spinlock.h>

     rwlock_t xxx_rwlock=RW_LOCK_UNLKED;

     或:

     rwlock_t  xxx_rwlock;

     rwlock_init(&xxx_rwlock);

读取者获得锁:

     void read_lock(rwlock_t *lock);

     void read_lock_irqsave(rwlock_t *lock,unsigned long flags);

    void read_lock_irq(rwlock_t *lock);

     void read_lock_bh(rwlock_t *lock);

 

读写自旋锁实例:

    unsigned long flags;//定义rwlock

    rwlock_t rwlock;//初始化rwlock

     rwlock_init(&rwlock);//获取锁

      read_lock(&rwlock)l

 

      read_unlock(&rwlock);

     //写时获得锁

     write_lock_irqsave(&rwlock,flags);

     //临界资源

     write_unlock_irqrestore(&rwlock,flags);   

自旋锁vs信号量

     开销成本:

            使用信号量的开销是进程上下文切换时间

            自旋锁的开销是忙等待获取自旋锁

    等待机制不同

            信号量可能导致阻塞,所以在步允许阻塞的代码中不可能用可能引起阻塞的信号量处理方式

            自旋锁是忙等待

    自旋锁的使用规则:

             任何拥有自旋锁的代码都必须是原子的;(不能被打断,要不就会一直在这里面选转出不来)

            如果中断处理函数中也要获得自旋锁,那么驱动程序需要在拥有自旋锁时禁止中断(防止一切的打断)。

            自旋锁必须在肯的最短时间内拥有(获取的时间尽可能的短,减小忙等待的时间)

    死锁规则:

             避免某个获得锁的函数调用其他同样试图获取这个锁的函数,否则代码会死锁

             不论是信号量还是自旋锁,都不允许锁拥有者第二次获得这个锁,如果试图,系统将挂起。

   锁的顺序规则:

             在必须获取多个锁时,应该始终以相同的顺序获得,这样能有效避免死锁

            如果必须获得一个局部锁和一个属于内核更中心位置的锁,则应该首先获取自己的局部锁。

            如果我们拥有信号量和自旋锁的组合,则必须首先获得信号量;在拥有自旋锁时调用down(可导致休眠)是个严重的错误,

原子操作。

            类型:atomic_t

             设置原子变量的值

            void atomic_set(atomic_t *v,int i)    //动态初始化

             atomic_t v=ATOMIC_INIT(0);    //静态初始化

              获取原子变量的值:

             int atomic_read(atomic_t *v);// 返回v当前值

              原子变量加减:

            void atomic_add(int i ,atomic_t *v)

            void atomic_sub(ini i,atomic_t *v)

            操作并测试:

           //操作结束后,原子值为0,则返回true,否则为false

           int atomic_inc_and_test

             

 

     

 

 

  

 

0 0