Linux设备驱动开发详解:第7章 Linux设备驱动中的并发控制

7.1并发与竞态

(1)、竞态的发生场景:CPU0的进程与CPU1的进程之间、CPU0的中断与CPU1的进程之间、CPU0的中断与CPU1的中断之间;

(2)、解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问,访问共享资源的代码区域被称为临界区,临界区需要被以某种互斥机制加以保护。中断屏蔽、原子操作、自旋锁、信号量、互斥体等是Linux设备驱动中可采用的互斥途径;

7.2编译乱序与执行乱序

(1)、编译乱序:打开编译器优化后,生成的代码并没有严格按照代码逻辑顺序执行;

        解决方法:在代码中设置barrier()编译屏障

(2)、执行乱序:处理器执行顺序不同;

        解决方法:在代码中设置内存屏障;Linux内核的自旋锁、互斥体等互斥逻辑,需要用到上述                             逻辑,在请求获得锁时,调用屏障指令,解锁时,也需要调用屏障指令

7.3中断屏蔽

(1)、中断屏蔽的使用方法为:

        local_irq_disable() /*屏蔽中断*/

        critical section       /*临界区*/

        local_irq_enable() /*开中断*/

(2)、 local_irq_disable()与local_irq_enable()都只能禁止和使能本CPU内的中断,因此并不能解决SMP多CPU引发的竞态,使用上述接口意味着bug,它适合于自旋锁联合使用;

(3)、 local_irq_save(flags)除了进行禁止中断的操作以外,还保存目前CPU的中断位信息, local_irq_restore(flags)与之相反,对于ARM而言,就是保存和恢复CPSR;

(4)、如果只是禁止中断的底半部,应使用local_bh_disable()与local_bh_enable()

7.4原子操作

(1)、原子操作可以保证对一个整型数据的修改是排他性的,ldrex指令跟strex配对使用,可以让总线监控ldrex到strex之间有无其他的实体存取该地址;

(2)、设置原子变量的值

        void atomic_set(atomic_t *v,int i);  /*设置原子变量的值为i*/

        atomic_t  v = ATOMIC_INIT(0);          /*定义原子变量v并初始化为0*/

(3)、获取原子变量的值

        atomic_read(atomic_t *v);  /*返回原子变量的值*/

(4)、原子变量加减

        void atomic_add(int i ,atomic_t *v);  /*原子变量增加i*/

       void atomic_sub(int i ,atomic_t *v);  /*原子变量减少i*/

(5)、原子变量自增/自减

        void atomic_inc(atomic_t *v);  /*原子变量增加1*/

       void atomic_dec(atomic_t *v);  /*原子变量减少1*/

(6)、操作并测试

       int atomic_inc_and_test(atomic_t *v);

       int atomic_dec_and_test(atomic_t *v);

       int atomic_sub_and_test(int i ,atomic_t *v);

       上述操作对原子变量执行自增、自减和减操作后(注意没有加),测试其是否为0,为0返回

        true,否则返回false;

(7)、操作并返回

      int atomic_add_return(int i ,atomic_t *v);

      int atomic_dec_return(int i ,atomic_t *v);

      int atomic_inc_return(atomic_t *v);

      int atomic_dec_return(atomic_t *v);

       上述操作对原子变量进行加/减和自增/自减操作,并返回新的值;

7.5自旋锁

(1)、自旋锁:是一种典型的 对临界资源进行互斥访问的手段,其名称来源于它的工作方式。为了获得一个自旋锁,在某CPU上运行的代码需要先执行一个原子操作,该操作测试并设置某个内存变量。由于它是原子操作,所以在该操作完成之前其他执行单元不能访问这个内存变量。如果测试结果表明锁已经空闲,则程序获得这个锁并继续执行;如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个"测试并设置"操作,即进行所谓的“自旋”;

(2)、定义自旋锁

        spinlock_t        lock;

(3)、初始化自旋锁

        spin_lock_init(&lock);

(4)、获得自旋锁

        spin_lock(&lock);

        该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则它将在那里自旋,直到该自旋锁的保持者释放。

        spin_trylock(&lock);

        该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回true,否则立即返回false,实际上不再原地打转。

(5)、释放自旋锁

         spin_unlock(&lock);

(6)、自旋锁的使用

        spinlock_t        lock;

        spin_lock_init(&lock);

        spin_lock(&lock);

        /*临界区*/

        spin_unlock(&lock);

(7)、尽管用了自旋锁可以临界不受到别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候,还可能受到中断和底半部的影响。为了防止这种影响,就需要用到自旋锁的衍生:

spin_lock_irq() = spin_lock() + local_irq_disable() 

spin_unlock_irq() = spin_unlock() + local_irq_enable() 

spin_lock_irqsave() = spin_lock() + local_irq_save() 

spin_unlock_irqrestore() = spin_unlock() + local_irq_restore() 

spin_lock_bh() = spin_lock() + local_bh_disable() 

spin_unlock_bh() = spin_unlock() + local_bh_enable() 

(8)、在多核编程的时候,如果进程和中断可能访问同一片临界资源,我们需要在进程上下文中调用

spin_lock_irqsave() = spin_lock() + local_irq_save() 

spin_unlock_irqrestore() = spin_unlock() + local_irq_restore() 

在中断上下文调用

 spin_lock(&lock)

 spin_unlock(&lock);

7.6读写自旋锁

(1)、定义和初始化读写自旋锁

rwlock_t  my_rwlock;

rwlock_init(&my_rwlock);

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

(3)、读解锁

void read_unlock(rwlock_t *lock)

void read_unlock_irqsave(rwlock_t *lock,unsigned long flags)

void read_unlock_irq(rwlock_t *lock)

void read_unlock_bh(rwlock_t *lock)

(4)、写锁定

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)

(6)、写解锁

void write_unlock(rwlock_t *lock)

void write_unlock_irqsave(rwlock_t *lock,unsigned long flags)

void write_unlock_irq(rwlock_t *lock)

void write_unlock_bh(rwlock_t *lock)

(7)、读写锁使用

rwlock_t   lock

rwlock_init(&lock)

read_lock(&lock)

......

read_unlock(&lock)

write_lock_irqsave(&lock,flags)

......

write_unlock_irqrestore(&lock,flags)

7.7信号量

(1)、信号量与操作系统的经典概念PV操作对应

        P(S):1、如果信号量s的值大于零,该进程继续执行

                2、如果s的值为0,该进程置为等待状态,排入信号量的等待队列,直到V操作唤醒

(2)、定义信号量

        struct  semaphore sem;

(3)、初始化信号量

        void sema_init(struct  semaphore *sem,int val)

(4)、获得信号量

        void down(struct  semaphore *sem)该函数会导致睡眠,因此不能在中断上下文中使用

        void down_interruptible(struct  semaphore *sem)

        void down_trylock(struct  semaphore *sem)

(5)、释放信号量

        void up(struct  semaphore *sem)

7.8互斥体

(1)、struct mutex  my_mutex

        mutex_init(&my_mutex)

        void mutex_lock(struct mutex  *lock)

        int  mutex_lock_interruptible(struct mutex  *lock)

        int  mutex_trylock(struct mutex  *lock)

        void mutex_unlock(struct mutex  *lock)

        

        

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Linux 设备驱动开发详解 4.0》是由马上飞鱼科技有限公司的紫金福老师所著的一本经典的书籍,该书是目前国内关于 Linux 设备驱动开发方面的最好的教材之一。该书主要介绍了 Linux 设备驱动的基本知识和开发方法,对于想学习 Linux 设备驱动开发或者对于嵌入式系统开发有兴趣的工程师是非常有价值的一本入门书籍。 该书从 Linux 内核的基本架构入手,介绍了 Linux 设备驱动的编写和调试方法。该书结合了实战案例,分步骤地开展了驱动开发的实践,详细分析了 Linux 的 I/O 模型、断和 DMA、并发性问题等。同时,还详细介绍了外部总线的驱动,如 SPI、I2C、PCI、USB 等,并给出了完整的 demo 程序和相应的测试方法。 另外,该书还介绍了常见的设备驱动,如字符设备驱动、块设备驱动和网络设备驱动的编写方法,并且还介绍了常见设备驱动的硬件架构和硬件接口,使得读者能够更加深入地理解设备驱动的本质。 除此之外,《Linux 设备驱动开发详解 4.0》还介绍了如何通过编写 Linux 独立模块来实现设备驱动,如何使用内核 API 接口和系统调用等技术,并且还引导读者了解一些不常见的驱动开发技术,如用户空间驱动程序和内核模块参数。 总之,《Linux 设备驱动开发详解 4.0》是一本非常实用的 Linux 设备驱动开发指南,它涵盖了 Linux 设备驱动开发的方方面面,包括理论知识和实际操作,对于想深入了解 Linux 设备驱动开发的工程师来说是不可多得的一本书。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值