并发与竞态控制

来源:互联网 发布:淘宝卖iphone推荐 编辑:程序博客网 时间:2024/05/18 01:59

防止竞态的机制有:semaphore(信号量),spinlock(自旋锁),completion(完成量),原子操作等

原子操作的意义:操作的不可分割。

1、  什么是并发?

1.1多个执行单元同时、并行被执行。

1.2竞态:

1.2.1并发的执行单元对共享资源的访问则很容易导致竞态。

1.2.2 共享资源:硬件资源,软件上的全局变量、静态变量等。

2、linux内核中,什么情况会发生竞态

   2.1对称多处理器(SMP)的多个CPU之间的竞态

   2.2单CPU内进程间的竞态

   2.3中断(硬中断、软中断、Tasklet、低半步)与进程之间的竞态。

3、如何解决竞态问题

   保证对共享资源的互斥访问(指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问)

4、linux设备驱动中可采用的互斥途径

   4.1 中断屏蔽

   4.2 原子操作

   4.3 自旋锁

   4.4 信号量

   4.5 completion(完成量)

 

(1)中断屏蔽

   (1.1)可以解决中断与进程之间的并发

   (1.2)也可以解决内核抢占进程之间的并发

   (1.3)主要函数

local_irq_disable() //禁止中断

local_irq_enable()  //打开中断 

local_irq_save(flags) //禁止中断并保存中断  寄存器flags中

local_irq_restore(flags) //打开中断,并恢复flags到中断寄存器中

local_bh_disable()  //仅禁止中断低半部中断

local_bh_enable() //打开

  注意:不要长时间屏蔽中断,由于Linux系统的异步I/O、进程调度等很多重要操作都依赖于中断,在屏蔽中单期间所有的中断都无法得到处理,因此长时间屏蔽是很危险的,有可能造成数据对视甚至系统崩溃。

具体用法:

local_irq_disable() //屏蔽中断

……

critical section //临界区

……

Local_irq_enable() //打开中断

 

(2)信号量(semaphore)

    (2.1)信号量本质是一个整数值

   (2.2)一对操作函数通常称为P和V

   (2.3)进入临界区

       (2.3.1)相关的信号量上调用P

         (2.3.2)如果信号量>零,则该值会减小一,而进程可以继续。

        (2.3.3)如果信号量的值=零(或者更小),进程必修等待直到其他进程释放该信号量。

   (2.4)退出临界区

        (2.4.1)信号量的解锁通过调用V完成

        (2.4.2)该函数增加信号量的值

        (2.4.3)并在必要时唤醒等待的进程。

   (2.5)信号量用于互斥

        (2.5.1)避免多个进程同时在一个临界区中运行

        (2.5.2)信号量的值应初始化为1

             只能有一个进程或线程拥有

             一个信号量有时也称为一个“互斥体(mutex)”

             它是互斥(mutual exclusion)的简称。

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

             互斥体的取值只能为:1、0

    (2.6)初始化信号量

        (2.6.1)信号量类型为struct semaphore,定义在<linux/semaphore.h>;

         (2.6.2) 信号量可通过几种途径来声明和初始化

        (2.6.3)动态的初始化信号量 void sema_init(struct semaphore *sem,int val);

val:信号量的初始值。

            (2.6.4)静态的声明互斥信号量:

                    DECLARE_MUTEX(name);//声明互斥信号量“name”,并初始化为1

                    DECLARE_MUTEX_LOCKED(name);//声明互斥信号量“name”,并初始化为0

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

                   void init_MUTEX(struct semaphore *sem);

                   void init_MUTEX_LOCKED(struct semaphore *sem);

            (2.7)获得信号量

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

                int down_interruptible(struct semaphore *sem);//完成down()相同的工作,但可操作中断的

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

            (2.8)释放信号量

                void up(struct semaphore *sem)

             (2.9)使用信号量模板

                 

(2.10)互斥体实例讲解

           

 (2.11)读取者/写入者信号

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

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

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

        (2.11.4)数据类型:struct rw_semaphore;

        (2.11.5)初始化: void init_rwsem(struct rw_semaphore *sem);

         (2.11.6) 主要函数:

            void down_read(structrw_semaphore*sem);

           int down_read_trylock(structrw_semaphore*sem);

          void up_read(structrw_semaphore*sem);

         void down_write(structrw_semaphore*sem);

         int down_write_trylock(structrw_semaphore*sem);

           

void up_write(structrw_semaphore*sem);

  (3) completion (完成量)

   (3.1)一种轻量级的机制

    (3.2)它允许一个线程告诉另一线程某个工作已经完成。

   (3.3)初始化:

       DECLARE_COMPLETION(XXX_completion);//静态创建

      

       struct completion xxx_cmmpletion;//动态创建

       init_completion(&xxx_completion);//初始化

    (3.4)等待completion

     void wait_for_completion(struct completion *c);

    (3.5)触发完成

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

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

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

       INIT_CoMPLETION(struct completion c);

        

(4)自旋锁

(4.1)概念:一个自旋锁是个互斥设备,只有两个值:“锁定”和“解锁”。

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

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

         如果锁被其他人获得,则代码进入忙循环并重复见擦这个锁,直到该锁可用为止。这个循环就是自旋锁的“自选”。

 (4.2)初始化自旋锁

     (4.2.1)编译时初始化:

        spinlock_t  xxx_lock=SPIN_LOCK_UNLOCKED;

       (4.2.2)运行时初始化:

        void spin_lock_init(spinlock_t *lock);

  (4.3)锁定函数

      void spin_lock(spinlock_t *lock);

        

      //获得自旋锁之前应禁止中断(包括软中断和硬中断)

      void spin_lock_irq(spinlock_t *lock);

   

     //获得自旋锁之前禁止中断(包括软中断和硬中断),中断状态保存在状态字flags中。

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

 

//获得自旋锁志强静止软中断,硬件中断保持打开。

 void spin_lock_bh(spinlock_t *lock)

               (4.4)释放自旋锁:

                    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);

                     

                       

(4.5)读写自旋锁:

        任意数量的读取者可以同时进入临界区,写入者必修互斥访问,变量类型:rwlock_t

(4.5.1)初始化:

      #include<linux/spinlock.h>

       rwlock_t xxx_rwlock = RW_LOCK_UNLOCKED;

       或者:

         rwlock_t xxx_rwlock;

         rwlock_int(&xxx_rwlock);

(4.5.2)读取者获得

      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);

(4.5.3)读取者释放锁

      void read_unlock(rwlock_t *lock);

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

      void read_unlock_irq(rwlock_t* lock);

      void read_unlock_bh(rwlock_t *lock);

(4.5.4)写入者获得锁

      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);

(4.5.5)写入者释放锁

       void write_unlock(rwlock_t*lock);

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

       void write_unlock_irq(rwlock_t*lock);

       void write_unlock_bh(rwlock_t *lock);

            

(4.6)自旋锁VS信号量

(4.6.1)开销成本:

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

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

 (4.6..2)等待机制不同

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

           自旋锁是忙等待。(独占处理器)

 (4.7)自旋锁的使用规则

    (4.7.1)任何拥有自旋锁的代码都必须是原子。

    (4.7.2)如果中断处理函数中也要活得自旋锁,那么驱动程序需要在拥有自旋锁是禁止中断

    (4.7.3)自旋锁必修在可能短的时间内拥有。

   (4.8)其他规则

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

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

   (4.9)锁的顺序规则

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

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

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

(5)原子操作

   (5.1)整型原子操作

       (5.1.1)类型:atomic_t

       (5.1.2)设置原子变量的值。

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

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

       (5.1.3)获取原子变量的值: int atomic_raad(atomic_t *v); //放回v当前值。

       (5.1.4)原子变量加减

       void atomic_add(int i,atomic_t *v); //*v累计i,无返回值。

       void atomic_sub(int I,atomic_t *v);//*v递减i,无返回值。

       (5.1.5)原子变量自增/自减

       void atomic_inc(atomic_t *v);

       void atomic_dec(atomic_t *v);

       (5.1.6)操作并测试

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

        int atomic_inc_and_test(atomic_t *v);

        int atomic_dec_and_test(atomic_t *v);

        int atomic_sub_and_tes(int I,atomic_t *v)

        (5.1.7)操作并返回

          //操作结束后,返回新值

          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);

(5.1)位原子操作

      void set_bit(nr,void* addr);//设置第nr个bit的值

      void clear_bit(nr,void* addr);//清除第nr个bit的值

      void change_bit(nr,void* addr); //改变第nr个bit的值

      void test_bit(nr,void *addr); //检测第nrbit是否被设置

       //一下是先检测,并返回先前的值,在进行对应的操作

       int test_and_set_bit(nr,void* addr);

       int test_and_clear_bit(nr,void* addr);

       int test_and_change_bit(nr,void *addr);

      声明:本文非原创,整理自申嵌

原创粉丝点击