我是靠谱客的博主 追寻宝马,最近开发中收集的这篇文章主要介绍linux设备驱动程序之并发和竞态(一)信号量的实现读写信号量completion自旋锁读写者自旋锁,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

        此blog就是总结下驱动中的并发和竞争的使用方法,和linux环境编程之多线程同步类似;


信号量的实现

        信号量的头文件在<asm/semaphore.h>,所以要使用信号量就必须包含这个头文件。
        声明:struct semaphore sem;
        初始化:
        1、静态初始化:
                        DECLARE_MUTEX(&sem);  //这个信号量初始化为1,可以马上使用;
                        DECLARE_MUTEX_LOCKED(&sem); //这个信号量初始化为0,如果要使用要先解锁,打开信号量;       
               
        2、动态初始化:
                        void init_MUTEX(&sem); //这个信号量初始化为1;
                        void init_MUTEX_LOCK(&sem);//这个信号量初始化为0;

        但是在2.6.xx后的内核版本init_MUTEX()好像被废除了,用sema_init()函数替代了。sema_init(&sem, val);其中val是初始化的值。
        记得在上《计算机操作系统》的时候,老师老是在讲P、V操作,一直以为还只是个概念,没想到还真有这函数;在linux中P操作函数就是down减少信号量的值,可能会让调用者进入休眠状态(如果信号量为0时,调用了P操作),然后等待别的进程、线程对信号量进行V操作,最后调用者才会醒来锁住资源;

        void  down(struct semaphore  *sem);
        int     down_interruptible(struct semaphore  *sem);
        int     down_trylock(struct semaphore  *sem);
        down()函数减少信号量值时,如果达到设定值则会一直睡眠等待,直到信号量可用这是不可以中断的。
        down_inerruptible()函数和down()函数类似,但其睡眠是可以中断的。所以调用down_interruptible()函数要注意检查返回值,如果返回非0值,表示为被中断返回,此时没有拥有信号量,不能对资源进行操作;如果返回0值,则表示等到了信号量返回。而down()函数返回时,一定拥有了信号量。
        down_trylock()函数不会休眠,如果信号量不可用则立即返回个非零值;

        相对的有P操作就有V操作,V操作对应于up()函数,当释放一个信号量时,调用   vvoid  up(sruct  semaphore  *sem);此后不再拥有信号量。
       

读写信号量

        和多线程同步一样,互斥量虽然可以保护资源的,但是降低了并行性能,所以也就出了读写锁这机制。同样的在linux内核中这种读写锁相应的被称为读写信号量。
        声明:struct rw_rw_semaphore  rwsem;
        初始化:void  init_rwsem(struct  rw_semaphore  *sem);

        只读函数:
                void  down_read(struct  rw_semaphore  *sem);
                void  down_read_trylock(struct rw_semaphore  *sem);
                void  up_read(struct  rw_semaphore  *sem);

        down_read()可能会让调用进程进入不可中断的睡眠。
        down_read_trylock()函数不会睡眠,不管成功与否都会马上返回。成功返回非0,表示获得了互斥量;返回0表示失败。
        所有读的互斥量都有up_read()函数来释放;

        写入函数:
                void   down_write(struct   rw_semaphore  *sem);
                int      down_write_trylock(struct rw_semaphore  *sem);
                void    up_write(struct  rw_semaphore  *sem);
                void    downgrade_write(struct  rw_semaphore  *sem);
        其他函数都和只读函数功能类似,只有downgrade_write()函数例外。如果一个操作只有前面要修改一下,而后面是比较长时间的只读,那么可以先上写锁,当把需要修改的部分修改完后可以调用downgrade_write()函数,来允许其他只读线程来访问;
        

completion

        completion是一种轻量级的机制,它允许一个线程告诉另外一个线程某个工作已经完成了。
        头文件  <linux/completion.h>

        静态创建completion:DECLARE_COMPLETION(my_completion);
        动态创建completion:struct  completion   my_completion;
                                                 init_completion(&my_completion);

        等待completion:void  wait_for_completion(struct  completion *c); //该函数执行一个不可中断的等待,如果没有人去完成这个任务,则将产生一个不可杀死的进程。
        触发completion函数:
                 void  complete(struct  completion  *c); // 只会被使用一次然后被丢弃
                 void  complete_all(struct  completion  *c); // 可以多次使用,但使用前必须初始化它。可以使用下面的宏来初始化:INIT_COMPLETION(struct completion c);
        

自旋锁


自旋锁基础

        和互斥量类似,自旋锁也可以保护资源,在驱动中使用自旋锁的概率会高点。如果一个代码段是不能休眠的,则只能使用自旋锁,比如中断处理例程;同样的,如果一个代码段是可以休眠的,那么使用互斥量来保护资源。
        自旋锁实现就是测试某个整数值中的单个位,并设置它。如果测试可用,则设置该位进入临界区;如果不可用,则循环等待(忙等待),直到该锁可以用,这就是自旋部分(当然这些操作都是原子操作)。这里的循环忙等待要和互斥量中的休眠等待区分开来,循环忙等待是占用CPU的,一直执行检查锁的操作;而休眠是释放掉了CPU的,当条件满足时,会被唤醒的。
        头文件 #include<linux/spinlock.h>
        定义:spinlock_t  my_lock;
        静态初始化:在编译时完成
        spinlock_t   my_lock  =  SPIN_LOCK_UNLOCKED;
    
        动态初始化:在运行时调用
        void  spin_lock_init(spinlock_t  *my_lock);

        获取自旋锁:在进入临界区之前,必须调用下面的函数来得到锁
        void   spin_lock(spinlock_t  *my_lock);
        自旋锁等待在本质上都是不可以中断的,一旦调用了spin_lock,在获取到锁之前将一直处于自选状态;
    
        释放自旋锁:void  spin_unlock(spinlock_t   *my_lock);

自旋锁规则

        1、拥有自旋锁代码不能睡眠;
        如果驱动程序获取了自旋锁,进入临界区运行,突然该程序丢掉了对处理器的控制权。可能是调用了某些函数进入休眠,或者是被高优先级的进程抢占了。而自旋锁已经被驱动程序获取到了,并且一时间无法释放该自旋锁。如果其他进程想访问该临界区,那么得自旋等待锁的释放。很显然好的情况下只需要等待一会(睡眠结束,驱动程序退出临界区),最坏的情况就是死锁(高优先级进程抢占了驱动程序对CPU的控制权,如果高优先级进程需要访问被锁住的临界区,则会死锁)。
        因此,任何拥有自旋锁的代码都必须是原子的,不能休眠,也不能因为任何原因放弃处理器(中断除外);
        自旋锁实现代码本身就会禁止相关处理器上的抢占,在加自旋锁时,也就禁止了处理器抢占;
        在拥有自旋锁的代码中不能调用会睡眠的函数,copy_from_user(拷贝内容所在的页在磁盘上,则要睡眠等待把该页交换到内存中) 和 kmalloc(无空闲页,则会放弃处理器)函数都可能会进入睡眠。

        2、拥有自旋锁代码禁止中断;
        如果一个驱动程序已经获取了一个自旋锁,该锁控制着对设备的访问,在拥有锁的期间发生设备中断,导致中断处理程序被调用。而中断处理程序必须先获取锁才能访问设备,这样驱动程序不能释放锁,而中断例程又在自旋等待锁的释放。为了防止这种情况,所以在 拥有 自旋锁时禁止中断。
        
        3、拥有自旋锁代码必须短小 ;
        自旋锁必须在可能的最短时间内拥有,拥有自旋锁的时间越长,其他处理器自旋等待锁释放的时间就越长,这会阻止处理器的调度,高优先级进程的也不得不等待。这样会降低系统的性能;

自旋锁函数

        void  spin_lock(spinlock_t  *lock);
        void  spin_lock_irqsave(spinlock_t  *lock,  unsigned  long flags);
        void  spin_lock_irq(spinlock_t  *lock);
        void  spin_lock_bh(spinock_t   *lock);
        spin_lock()函数是基本的函数;
        spin_lock_irqsave()函数是在获取自旋锁之前禁止中断,只在本地处理器上作用,把先前的中断状态保存在flags中;
        spin_lock_irq()函数是在确定没有其他代码禁止中断,则可以不用保存中断状态;
        spin_lock_bh()函数在获取锁之前禁止软件中断;

        非阻塞自旋锁操作
        int  spin_trylock(spinlock_t  *lock);
        int  spin_trylock_bh(spinlock_t  *lock);
        成功时,返回非零值;失败返回零值;

读写者自旋锁

        头文件<linux/spinlock.h>
        rwlock_t  my_rwlock  = RW_LOCK_UNLOCKED;
        rwlock_t  my_rwlock;
        rwlock_init(&my_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);

        写者
        void  write_lock(rwlock_t  *lock);
        void  write_lock_irqsave(rwlock_t  *lock,  unsigned long  flags);
        void  write_lock_irq(rwlock_t  *lock);
        void  write_lock_bh(rwlock_t  *lock);
        void  write_trylock(rwlock_t  *lock);

转载地址: http://blog.csdn.net/yuzhihui_no1/article/details/46670783


最后

以上就是追寻宝马为你收集整理的linux设备驱动程序之并发和竞态(一)信号量的实现读写信号量completion自旋锁读写者自旋锁的全部内容,希望文章能够帮你解决linux设备驱动程序之并发和竞态(一)信号量的实现读写信号量completion自旋锁读写者自旋锁所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部