同步互斥按键驱动
Posted 铅笔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同步互斥按键驱动相关的知识,希望对你有一定的参考价值。
目标:实现同一时刻只能有一个进程使用同一个设备,例如:只能有一个进程,在同一时刻里使用/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\\n"); 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\\n",major); 167 return 0; 168 } 169 printk(DEVICE_NAME " initialized1\\n"); 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!\\n"); return -1; } while (1) { ret = read(fd, &key_val, 1); printf("key_val: 0x%x, ret = %d\\n", 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设备驱动开发详解
以上是关于同步互斥按键驱动的主要内容,如果未能解决你的问题,请参考以下文章