• 《驱动学习


    1.同步互斥阻塞的概念

    并发与竞态:

      并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race conditions)。

    在linux中,主要的竞态发生在如下几种情况:

      1、对称多处理器(SMP)多个CPU                特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器。

      2、单CPU内进程与抢占它的进程

      3、中断(硬中断、软中断、Tasklet、底半部)与进程之间

      只要并发的多个执行单元存在对共享资源的访问,竞态就有可能发生。

      如果中断处理程序访问进程正在访问的资源,则竞态也会会发生。

      多个中断之间本身也可能引起并发而导致竞态(中断被更高优先级的中断打断)。 

    解决竞态问题的途径:

      是保证对共享资源的互斥访问,所谓互斥访问就是指一个执行单元在访问共享资源的时候,其他的执行单元都被禁止访问。 

      访问共享资源的代码区域被称为临界区,临界区需要以某种互斥机制加以保护,如:中断屏蔽,原子操作,自旋锁,和信号量都是linux设备驱动中可采用的互斥途径。 

    临界区和竞争条件:

      所谓临界区(critical regions)就是访问和操作共享数据的代码段,为了避免在临界区中并发访问,编程者必须保证这些代码原子地执行——也就是说,代码在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样,如果两个执行线程有可能处于同一个临界区中,那么就是程序包含一个bug,如果这种情况发生了,我们就称之为竞争条件(race conditions),避免并发和防止竞争条件被称为同步。 

    阻塞与非阻塞:

      阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程,而是直接返回。

    2.为什么使用原子操作

    原子操作:

      顾名思义,就是说像原子一样不可再细分不可被中途打断。一个操作是原子操作,意思就是说这个操作是以原子的方式被执行,要一口气执行完,执行过程不能够被OS的其他行为打断,是一个整体的过程,在其执行过程中,OS的其它行为是插不进来的。

    为什么要使用原子操作?

    static int canopen = 1;
    
    static int drv_open(略)
    {
        if(--canopen != 0)
        {
             canopen++;
             return -EBUSY;
        }  
    }
    
    static int drv_close(略)
    {
        canopen++;    
    }

      根据上面的代码,一旦应用程序A调用open,那么canopen就会调用驱动程序drv_open,canopen自减等于0,if判断不成立,就接下来去执行其他的操作。当应用程序B调用open的时候,canopen自减等于-1,if判断成立,然后返回。驱动程序看起来就具备了每次只能有一个进程使用该设备的现象。

      但是实际不是这样。

      canopen的自减在汇编层面分为三个步骤:

      1.读出canopen的值

      2.canopen的值减1

      3.将canopen的值写回去

      因为linux是多任务系统,那么如果在应用程序A调用open的时候,canopen=1被读出来,这时候应用程序B也调用open,并且中断了应用程序A,应用程序B自减完,将canopen=0写回去。这时候应用程序A后继续上一步执行,将canopen=1自减,然后写回去。这样A和B都成功执行了open操作。

      因此需要原子操作来确保执行这些代码时不会被其他打断。

    static atomic_t canopen = ATOMIC_INIT(1);
    
    static int drv_open(略)
    {
        if(!atomic_dec_and_test(&canopen))
        {
            atomic_inc(&canopen);
            return -EBUSY;
        }  
    }
    
    static int drv_close(略)
    {
        atomic_inc(&canopen);
    }

      将canopen定义为原子变量,初始值为1

      atomic_dec_and_test(&canopen)将canopen进行减1后判断它是否为0,如果为0,则返回true,否则flase。

      atomic_inc就是自增。

    原子操作的函数在arch/arm/include/asm/atomic.h中有定义,基本根据函数名就知道什么意思,这边就不列举了。

    3.信号量

      信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码。当获取不到信号量时,进程进入休眠等待状态。

    //定义信号量
    struct semaphore sem;
    
    //初始化信号量
    void sema_init(struct semaphore *sem, int val);
    void init_MUTEX(struct semaphore *sem);   //初始化为0
    
    static DECLARE_MUTEX(button_lock);    //定义互斥锁,里面包含了定义和初始化。等价于上面两个函数
    
    //获取信号量
    void down(struct semaphore *sem); //获取信号量,获取不到就休眠
    int down_interruptible(struct semaphore *sem);//这是获取不到信号量就去休眠,休眠可以被打断
    int down_trylock(struct semaphore *sem);//尝试获取信号量,获取不到立即返回
    
    //释放信号量
    void up(struct semaphore *sem)

    相关函数可以在include/linux/semaphore.h中可以查看到

    4.阻塞与非阻塞模式

      阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。

      非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程,而是直接返回。

      open函数:用来打开一个设备,他返回的是一个整型变量,是文件的标识符。如果这个值等于-1,说明打开文件出现错误。

      open("/dev/xxx", O_RDWR );

      这是以读写方式打开某种设备文件,默认是阻塞操作。

      open("/dev/xxx", O_RDWR | O_NONBLOCK);

      O_NONBLOCK 如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。

      这时候在驱动程序中也应该添加对非阻塞的处理。

    static DECLARE_MUTEX(button_lock);     //定义互斥锁
    static
    int sixth_drv_open(struct inode *inode, struct file *file) { if (file->f_flags & O_NONBLOCK) { if (down_trylock(&button_lock)) return -EBUSY; } else { /* 获取信号量 */ down(&button_lock); }
      return 1; }

    int sixth_drv_close(struct inode *inode, struct file *file)
    {
       up(&button_lock);
      return 0;
    }

      获取file的标志位,判断是否为非阻塞。如果为非阻塞,就尝试获取信号量,没用获取到信号量则立即返回。否则为阻塞模式,没用获取到信号量进行休眠,直到获取信号量。

  • 相关阅读:
    代码审计之越权及越权
    代码审计之XSS及修复
    代码审计之SQL注入及修复
    代码审计之CSRF原理及修复
    挖穿各大SRC的短信轰炸
    Kerberoasting攻击
    SPN扫描
    Windows认证 | 域认证
    Windows认证 | 网络认证
    Ceph 纠删码介绍
  • 原文地址:https://www.cnblogs.com/zhuangquan/p/11645862.html
Copyright © 2020-2023  润新知