zoukankan      html  css  js  c++  java
  • 同步互斥按键驱动

    目标:实现同一时刻只能有一个进程使用同一个设备,例如:只能有一个进程,在同一时刻里使用/dev/buttons这个设备。

    使用linux互斥机制实现同一时刻只能有一个进程使用某个设备。

    linux互斥机制有原子变量、互斥锁、信号量、自旋锁、读写锁等等

    一、原子操作:

    原子操作指的是在执行过程中不会被别的代码路径所中断的操作。

    整型原子操作:

    1.设置原子变量的值

    void atomic_set(atomic_t *v, int i); //设置原子变量的值为i
    atomic_t v = ATOMIC_INIT(0); //定义原子变量v 并初始化为0

    2.获取原子变量的值

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

    3.原子变量加/减

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

    4.原子变量自增/自减

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

    5.操作并测试

    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。

    6.操作并返回

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

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

    实现同一时刻只能有一个进程使用同一个设备,在上一个驱动程序中加入:

    一个全局原子变量

    static atomic_t canopen = ATOMIC_INIT(1);     //定义原子变量并初始化为1

    open函数中:

    if (!atomic_dec_and_test(&canopen))
        {
            atomic_inc(&canopen);
            return -EBUSY;
        }
    ...

    close函数中:

      atomic_inc(&canopen);

    加载驱动执行./a.out

    # ./a.out &
    # driver: buttons_fasync

    #
    # ps
    PID Uid VSZ Stat Command

    802 0 1308 S ./a.out

    # ./a.out &
    # can't open!

    二、信号量

    信号量(semaphore)是用于保护临界区(访问共享资源的代码区域称为临界区(critical sections),临界区需要以某种互斥机制加以保护。)的一种常用方法,只有得到信号量的进程才能执行临界区代码。当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。!



    Linux 系统中与信号量相关的操作主要有如下4 种。

    1.定义信号量

    下列代码定义名称为sem 的信号量。

    struct semaphore sem;

    2.初始化信号量

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

    该函数初始化信号量,并设置信号量sem 的值为val。尽管信号量可以被初始化为大于1 的值从而成
    为一个计数信号量,但是它通常不被这样使用。

    void init_MUTEX(struct semaphore *sem);

    该函数用于初始化一个用于互斥的信号量,它把信号量sem的值设置为1,等同于sema_init (struct semaphore
    *sem, 1)。

    void init_MUTEX_LOCKED (struct semaphore *sem); 

    该函数也用于初始化一个信号量,但它把信号量sem 的值设置为0,等同于sema_init (struct semaphore
    *sem, 0)。

    此外,下面两个宏是定义并初始化信号量的“快捷方式”。

    DECLARE_MUTEX(name)
    DECLARE_MUTEX_LOCKED(name)

    前者定义一个名为name 的信号量并初始化为1,后者定义一个名为name 的信号量并初始化为0。

    3.获得信号量

    void down(struct semaphore * sem);

    该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文使用

    int down_interruptible(struct semaphore * sem);

    该函数功能与down()类似,不同之处为,因为down()而进入睡眠状态的进程不能被信号打断,而因为
    down_interruptible()而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值
    非0。

    在使用 down_interruptible()获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回
    -ERESTARTSYS,如:

    if (down_interruptible(&sem))
    {
    return - ERESTARTSYS; }
    int down_trylock(struct semaphore * sem);

    该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0 值。
    它不会导致调用者睡眠,可以在中断上下文使用

    4.释放信号量

    void up(struct semaphore * sem);

    该函数释放信号量sem,唤醒等待者。
    信号量一般这样被使用,如下所示:

    信号量一般这样被使用,如下所示:

    //定义信号量
    DECLARE_MUTEX(mount_sem);
    down(&mount_sem);//获取信号量,保护临界区
    ...
    critical section //临界区
    ...
    up(&mount_sem);//释放信号量

    驱动代码中:

    定义信号量

    static DECLARE_MUTEX(button_lock); 
    

      

    在open函数中获取信号量

    /* 获取信号量 */
            down(&button_lock);

    当第一次执行open函数时就会获取到信号量 

    当第二次执行open函数是就会不能获取到信号量就会陷入到休眠。

    在close函数中释放掉信号量

    up(&button_lock);

    当第一个程序用完后close后就会将信号量进行释放掉  这时候其他进程就可以使用 。 

    结果

     其中pid 805处于僵死状态 

    第二次无法打开的时候就会在open函数中的down(&button_lock);时陷入休眠 ,当第一个应用程序释放掉这个信号量时才会唤醒这个进程的。

    ---恢复内容结束---

    目标:实现同一时刻只能有一个进程使用同一个设备,例如:只能有一个进程,在同一时刻里使用/dev/buttons这个设备。

    使用linux互斥机制实现同一时刻只能有一个进程使用某个设备。

    linux互斥机制有原子变量、互斥锁、信号量、自旋锁、读写锁等等

    一、原子操作:

    原子操作指的是在执行过程中不会被别的代码路径所中断的操作。

    整型原子操作:

    1.设置原子变量的值

    void atomic_set(atomic_t *v, int i); //设置原子变量的值为i
    atomic_t v = ATOMIC_INIT(0); //定义原子变量v 并初始化为0

    2.获取原子变量的值

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

    3.原子变量加/减

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

    4.原子变量自增/自减

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

    5.操作并测试

    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。

    6.操作并返回

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

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

    实现同一时刻只能有一个进程使用同一个设备,在上一个驱动程序中加入:

    一个全局原子变量

    static atomic_t canopen = ATOMIC_INIT(1);     //定义原子变量并初始化为1

    open函数中:

    if (!atomic_dec_and_test(&canopen))
        {
            atomic_inc(&canopen);
            return -EBUSY;
        }
    ...

    close函数中:

      atomic_inc(&canopen);

    加载驱动执行./a.out

    # ./a.out &
    # driver: buttons_fasync

    #
    # ps
    PID Uid VSZ Stat Command

    802 0 1308 S ./a.out

    # ./a.out &
    # can't open!

    二、信号量

    信号量(semaphore)是用于保护临界区(访问共享资源的代码区域称为临界区(critical sections),临界区需要以某种互斥机制加以保护。)的一种常用方法,只有得到信号量的进程才能执行临界区代码。当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。!



    Linux 系统中与信号量相关的操作主要有如下4 种。

    1.定义信号量

    下列代码定义名称为sem 的信号量。

    struct semaphore sem;

    2.初始化信号量

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

    该函数初始化信号量,并设置信号量sem 的值为val。尽管信号量可以被初始化为大于1 的值从而成
    为一个计数信号量,但是它通常不被这样使用。

    void init_MUTEX(struct semaphore *sem);

    该函数用于初始化一个用于互斥的信号量,它把信号量sem的值设置为1,等同于sema_init (struct semaphore
    *sem, 1)。

    void init_MUTEX_LOCKED (struct semaphore *sem); 

    该函数也用于初始化一个信号量,但它把信号量sem 的值设置为0,等同于sema_init (struct semaphore
    *sem, 0)。

    此外,下面两个宏是定义并初始化信号量的“快捷方式”。

    DECLARE_MUTEX(name)
    DECLARE_MUTEX_LOCKED(name)

    前者定义一个名为name 的信号量并初始化为1,后者定义一个名为name 的信号量并初始化为0。

    3.获得信号量

    void down(struct semaphore * sem);

    该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文使用

    int down_interruptible(struct semaphore * sem);

    该函数功能与down()类似,不同之处为,因为down()而进入睡眠状态的进程不能被信号打断,而因为
    down_interruptible()而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值
    非0。

    在使用 down_interruptible()获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回
    -ERESTARTSYS,如:

    if (down_interruptible(&sem))
    {
    return - ERESTARTSYS; }
    int down_trylock(struct semaphore * sem);

    该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0 值。
    它不会导致调用者睡眠,可以在中断上下文使用

    4.释放信号量

    void up(struct semaphore * sem);

    该函数释放信号量sem,唤醒等待者。
    信号量一般这样被使用,如下所示:

    信号量一般这样被使用,如下所示:

    //定义信号量
    DECLARE_MUTEX(mount_sem);
    down(&mount_sem);//获取信号量,保护临界区
    ...
    critical section //临界区
    ...
    up(&mount_sem);//释放信号量

    驱动代码中:

    定义信号量

    static DECLARE_MUTEX(button_lock); 
    

      

    在open函数中获取信号量

    /* 获取信号量 */
            down(&button_lock);

    当第一次执行open函数时就会获取到信号量 

    当第二次执行open函数是就会不能获取到信号量就会陷入到休眠。

    在close函数中释放掉信号量

    up(&button_lock);

    当第一个程序用完后close后就会将信号量进行释放掉  这时候其他进程就可以使用 。 

    结果

     其中pid 805处于僵死状态 

    第二次无法打开的时候就会在open函数中的down(&button_lock);时陷入休眠 ,当第一个应用程序释放掉这个信号量时才会唤醒这个进程的。

    三、Linux 设备驱动中的阻塞与非阻塞I/O

    通俗的讲 如按键 阻塞:如果按键没有按下就会一直等待,直到有按键按下时才会返回值;非阻塞:如果没有按键按键就会立即返回,返回一个错误。

    如何分辨阻塞和非阻塞open时

    open("/dev/xxx", O_RDWR | O_NONBLOCK);如果有O_NONBLOCK这个标志位就是非阻塞,不传入这个标记即默认open时是阻塞。

    原来的按键驱动中并没有这个非阻塞O_NONBLOCK标志位操作,如果需要用这个非阻塞就要驱动程序中做些修改

    static int button_open (struct inode *inode, struct file *filep)  
    {   
        int err;
    #  if 0
        if (!atomic_dec_and_test(&canopen))
            {
                atomic_inc(&canopen);
                return -EBUSY;
            }
    #endif
        
    
        if (filep->f_flags & O_NONBLOCK)//判断有没有用到O_NONBLOCK标记
        {
            if (down_trylock(&button_lock))//获取不到信号量立刻返回一个错误 
                return -EBUSY;
        }
        else
        {
            /* 获取信号量 */
            down(&button_lock);
        }
    .........
    }

    同时read函数中

    ssize_t button_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
    {
         unsigned long err;
        
        if (file->f_flags & O_NONBLOCK)//判断这个标志位有没有写入O_NONBLOCK
          {
              if (!ev_press)
                  return -EAGAIN;
          }
          else
          {
                 /* 如果ev_press等于0,休眠 */
        wait_event_interruptible(button_waitq, ev_press);//阻塞   
          }
    
         /* 执行到这里时,ev_press等于1,将它清0 */
        ev_press = 0;
         /* 将按键状态复制给用户,并清0 */
        err = copy_to_user(buf, (const void *)&press_cnt,  count);
        //memset((void *)&press_cnt, 0, sizeof(press_cnt));
        return  err ? -EFAULT : 0;
    }

     完整代码如下:

      1 #include <linux/module.h>
      2 #include <linux/kernel.h>
      3 #include <linux/fs.h>
      4 #include <linux/init.h>
      5 #include <linux/delay.h>
      6 #include <asm/uaccess.h>
      7 #include <linux/interrupt.h>
      8 #include <asm/irq.h>
      9 #include <asm/io.h>
     10 #include <asm/arch/regs-gpio.h>
     11 #include <asm/hardware.h>
     12 #include <linux/poll.h>
     13 #define DEVICE_NAME "mybutton"   /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
     14 static struct class *button_class;
     15 static struct class_device *button_dev_class;
     16 int major;
     17 static struct fasync_struct *button_async;
     18 static DECLARE_MUTEX(button_lock); 
     19 
     20 static volatile int press_cnt=0;/* 按键被按下的次数(准确地说,是发生中断的次数) */
     21 
     22 
     23 static DECLARE_WAIT_QUEUE_HEAD(button_waitq);//定义等待队列
     24 
     25 /* 中断事件标志, 中断服务程序将它置1,s3c24xx_buttons_read将它清0 */
     26 static volatile int ev_press = 0;
     27 static atomic_t canopen = ATOMIC_INIT(1);     //定义原子变量并初始化为1
     28 
     29 static irqreturn_t buttons_interrupt(int irq, void *dev_id)
     30 {
     31     // volatile int *press_cnt = (volatile int *)dev_id;
     32      press_cnt =press_cnt + 1; /* 按键计数加1 */
     33      ev_press = 1;                /* 表示中断发生了 */
     34      wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
     35      kill_fasync(&button_async, SIGIO, POLL_IN);
     36 
     37      return IRQ_RETVAL(IRQ_HANDLED);//中断处理程序应该返回一个值,用来表明是否真正处理了一个中断,如果中断例程发现其设备的确要处理,则应该返回IRQ_HANDLED, //否则应该返回IRQ_NONE,我们可以通过这个宏来产生返回值,不是本设备的中断应该返回IRQ_NONE
     38 
     39 }
     40 /* 应用程序对设备文件/dev/xxx 执行open(...)时,
     41  * 就会调用button_open函数
     42  * 就会调用button_open函数
     43  */
     44 static int button_open (struct inode *inode, struct file *filep)  
     45 {   
     46     int err;
     47 #  if 0
     48     if (!atomic_dec_and_test(&canopen))
     49         {
     50             atomic_inc(&canopen);
     51             return -EBUSY;
     52         }
     53 #endif
     54     
     55 
     56     if (filep->f_flags & O_NONBLOCK)//判断有没有用到O_NONBLOCK标记
     57     {
     58         if (down_trylock(&button_lock))//获取不到信号量立刻返回一个错误 
     59             return -EBUSY;
     60     }
     61     else
     62     {
     63         /* 获取信号量 */
     64         down(&button_lock);
     65     }
     66 
     67 
     68     err=request_irq(IRQ_EINT2,buttons_interrupt,IRQF_TRIGGER_FALLING,"KEY3",NULL);
     69     
     70     if (err) {
     71         // 释放已经注册的中断
     72          free_irq(IRQ_EINT2, NULL);
     73         return -EBUSY;
     74     }
     75     
     76     return 0;  
     77 }  
     78 
     79 /* 应用程序对设备文件/dev/buttons执行close(...)时,
     80  * 就会调用buttons_close函数
     81  */
     82  static int buttons_close(struct inode *inode, struct file *file)
     83 {
     84     up(&button_lock);
     85 
     86    /// atomic_inc(&canopen);
     87     free_irq(IRQ_EINT2, NULL);
     88     return 0;
     89 }
     90 
     91 ssize_t button_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
     92 {
     93      unsigned long err;
     94     
     95     if (file->f_flags & O_NONBLOCK)
     96       {
     97           if (!ev_press)
     98               return -EAGAIN;
     99       }
    100       else
    101       {
    102              /* 如果ev_press等于0,休眠 */
    103     wait_event_interruptible(button_waitq, ev_press);//阻塞   
    104       }
    105 
    106      /* 执行到这里时,ev_press等于1,将它清0 */
    107     ev_press = 0;
    108      /* 将按键状态复制给用户,并清0 */
    109     err = copy_to_user(buf, (const void *)&press_cnt,  count);
    110     //memset((void *)&press_cnt, 0, sizeof(press_cnt));
    111     return  err ? -EFAULT : 0;
    112 }
    113 
    114 
    115 
    116 static unsigned buttons_poll(struct file *file, poll_table *wait)
    117 {
    118         unsigned int mask = 0;
    119         poll_wait(file, &button_waitq, wait); // 不会立即休眠    将进程挂接到button_waitq队列中
    120           /* 当没有按键按下时,即不会进入按键中断处理函数,此时ev_press = 0  
    121      * 当按键按下时,就会进入按键中断处理函数,此时ev_press被设置为1 
    122      */  
    123       if(ev_press)  
    124     {  
    125        mask |= POLLIN | POLLRDNORM;  /* POLLIN表示有数据可读   POLLRDNORM表示有普通数据可读*/  
    126     } 
    127  /* 如果有按键按下时,mask |= POLLIN | POLLRDNORM,否则mask = 0 */      
    128     return mask;
    129 }
    130 static int buttons_fasync (int fd, struct file *filp, int on)
    131 {
    132     printk("driver: buttons_fasync
    ");
    133     return fasync_helper (fd, filp, on, &button_async);//fasync_helper这个函数用来初始化button_async结构体
    134 }
    135 
    136 /* 这个结构是字符设备驱动程序的核心
    137  * 当应用程序操作设备文件时所调用的open、read、write等函数,
    138  * 最终会调用这个结构中指定的对应函数
    139  */
    140 static struct file_operations button_ops=  
    141 {    
    142     .owner    =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    143     .open     =  button_open,  
    144     .read      =     button_read,       
    145     .release  =  buttons_close, 
    146     .poll     =  buttons_poll,
    147     .fasync      =  buttons_fasync,
    148 }; 
    149  
    150 /*
    151  * 执行insmod命令时就会调用这个函数 
    152  */
    153   
    154 static int button_init(void)  
    155 {  
    156     
    157     /* 注册字符设备
    158      * 参数为主设备号、设备名字、file_operations结构;
    159      * 这样,主设备号就和具体的file_operations结构联系起来了,
    160      * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
    161      * LED_MAJOR可以设为0,表示由内核自动分配主设备号
    162      */
    163      major = register_chrdev(0, DEVICE_NAME, &button_ops);
    164       if (major < 0) 
    165       {
    166       printk(DEVICE_NAME  " can't register major number number::%d
    ",major);
    167       return 0;
    168       }
    169     printk(DEVICE_NAME " initialized1
    ");
    170     button_class = class_create(THIS_MODULE, "button");
    171     if (IS_ERR(button_class))
    172         return PTR_ERR(button_class);
    173     button_dev_class = class_device_create(button_class, NULL, MKDEV(major, 0), NULL, "my_button"); /* /dev/my_button */ 
    174     
    175     
    176     return 0;
    177 
    178 }
    179 
    180 /*
    181  * 执行rmmod命令时就会调用这个函数 
    182  */
    183 static void button_exit(void)
    184 {
    185     class_device_unregister(button_dev_class);
    186     class_destroy(button_class);       
    187      /* 卸载驱动程序 */
    188     unregister_chrdev(major, DEVICE_NAME);
    189 }
    190 
    191 /* 这两行指定驱动程序的初始化函数和卸载函数 */
    192 module_init(button_init);  
    193 module_exit(button_exit);  
    194 
    195 /* 描述驱动程序的一些信息,不是必须的 */
    196 MODULE_AUTHOR("http://www.100ask.net");// 驱动程序的作者
    197 MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");// 一些描述信息
    198 MODULE_LICENSE("GPL");   // 遵循的协议

    test.c

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <poll.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    
    int fd;
    
    int main(int argc, char **argv)
    {
        unsigned char key_val;
        int ret;
        int Oflags;
        
        fd = open("/dev/my_button", O_RDWR | O_NONBLOCK);
        if (fd < 0)
        {
            printf("can't open!
    ");
            return -1;
        }
    
    
        while (1)
        {
            ret = read(fd, &key_val, 1);
            printf("key_val: 0x%x, ret = %d
    ", key_val, ret);
            sleep(5);
        }
        
        return 0;
    }
     open时 open("/dev/my_button", O_RDWR | O_NONBLOCK);   arm-linux-gcc test1.c -o no_block
     
    open("/dev/my_button", O_RDWR)       //arm-linux-gcc test1.c -o block

    效果:

    当执行 no_block  时效果:

    当执行 block  时效果:

      参考:  韦东山一期的视频

          Linux设备驱动开发详解

  • 相关阅读:
    漫谈怎样学习操作系统原理
    二分图的最大匹配、完美匹配和匈牙利算法
    Web报表工具FineReport中JavaScript的使用
    Java Web -- Servlet(1) 必备知识
    xxxxxxclub系统模块分类
    经典排序算法——选择排序
    github+hexo+node.js搭建个人博客基本过程及遇到的问题
    自己做小项目的流程(慢慢完善)
    二分查找
    Eclipse中遇到The type XXX cannot be resolved. It is indirectly referenced from required .class files错误
  • 原文地址:https://www.cnblogs.com/zhaobinyouth/p/6255515.html
Copyright © 2011-2022 走看看