- 本节目标:
- 学习原子操作和互斥信号量,实现互斥机制,同一时刻只能一个应用程序使用驱动程序
- 学习阻塞和非阻塞操作
当设备被一个程序打开时,存在被另一个程序打开的可能,如果两个或多个程序同时对设备文件进行写操作,这就是说我们的设备资源同时被多个进程使用,对共享资源(硬件资源、和软件上的全局变量、静态变量等)的访问则很容易导致竞态。
显然这不是我们想要的,所以本节引入互斥的概念:实现同一时刻,只能一个应用程序使用驱动程序
互斥其实现很简单,就是采用一些标志,当文件被一个进程打开后,就会设置该标志,使其他进程无法打开设备文件。
1.其中的标志需要使用函数来操作,不能直接通过判断变量来操作标志
比如:
if (-- canopen != 0) //当canopen==0,表示没有进程访问驱动,当canopen<0:表示有进程访问
编译汇编来看,分了3段: 读值、减1、判断
如果刚好在读值的时候发生了中断,有另一个进程访问时,那么也会访问成功,也会容易导致访问竞态。
1.1所以采用某种函数来实现,保证执行过程不被其他行为打断,有两种类型函数可以实现:
原子操作(像原子一样不可再细分不可被中途打断)
当多个进程同时访问同一个驱动时,只能有一个进程访问成功,其它进程会退出
互斥信号量操作
比如:A、B进程同时访问同一个驱动时,只有A进程访问成功了,B进程进入休眠等待状态,当A进程执行完毕释放后,等待状态的B进程又来访问,保证一个一个进程都能访问
2. 原子操作详解
原子操作指的是在执行过程中不会被别的代码路径所中断的操作。
原子操作函数如下:
1)atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0 2)atomic_read(atomic_t *v); //返回原子变量的值 3)void atomic_inc(atomic_t *v); //原子变量增加1 4)void atomic_dec(atomic_t *v); //原子变量减少1 5)int atomic_dec_and_test(atomic_t *v); //自减操作后测试其是否为0,为0则返回true,否则返回false。
2.1修改驱动程序
定义原子变量:
/*定义原子变量canopen并初始化为1 */ atomic_t canopen = ATOMIC_INIT(1);
在.open成员函数里添加:
/*自减操作后测试其是否为0,为0则返回true,否则返回false */ if(!atomic_dec_and_test(&canopen)) { atomic_inc(&canopen); //++,复位 return -1; }
在. release成员函数里添加:
atomic_inc(&canopen); //++,复位
2.2修改测试程序:
int main(int argc,char **argv) { int oflag; unsigned int val=0; fd=open("/dev/buttons",O_RDWR); if(fd<0) {printf("can't open, fd=%d ",fd); return -1;} while(1) { read( fd, &ret, 1); //读取驱动层数据 printf("key_vale=0X%x ",ret); } return 0; }
2.3 测试效果
如下图,可以看到第一个进程访问驱动成功,后面的就再也不能访问成功了
3.互斥信号量详解
互斥信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码。
当获取不到信号量时,进程进入休眠等待状态。
信号量函数如下:
/*注意: 在2.6.36版本后这个函数DECLARE_MUTEX修改成DEFINE_SEMAPHORE了*/ 1)static DECLARE_MUTEX(button_lock); //定义互斥锁button_lock,被用来后面的down和up用
2)void down(struct semaphore * sem); // 获取不到就进入不被中断的休眠状态(down函数中睡眠)
3)int down_interruptible(struct semaphore * sem); //获取不到就进入可被中断的休眠状态(down函数中睡眠)
4)int down_trylock(struct semaphore * sem); //试图获取信号量,获取不到则立刻返回正数
5)void up(struct semaphore * sem); //释放信号量
3.1修改驱动程序(以down函数获取为例)
(1)定义互斥锁变量:
/*定义互斥锁button_lock,被用来后面的down()和up()使用 */ static DECLARE_MUTEX(button_lock);
(2)在.open成员函数里添加:
/* 获取不到就进入不被中断的休眠状态(down函数中睡眠) */ down(&button_lock);
(3)在. release成员函数里添加:
/* 释放信号量 */ up(&button_lock);
3.2修改测试程序:
int main(int argc,char **argv) { int oflag; unsigned int val=0; fd=open("/dev/buttons",O_RDWR); if(fd<0) {printf("can't open, fd=%d ",fd); return -1;} else { printf("can open,PID=%d ",getpid()); //打开成功,打印pid进程号 } while(1) { read( fd, &ret, 1); //读取驱动层数据 printf("key_vale=0X%x ",ret); } return 0; }
3.3 测试效果
如下图所示,3个进程同时访问时,只有一个进程访问成功,其它2个进程进入休眠等待状态
如下图所示,多个信号量访问时, 会一个一个进程来排序访问
4.阻塞与非阻塞
4.1阻塞操作
进程进行设备操作时,使用down()函数,若获取不到资源则挂起进程,将被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
在read读取按键时, 一直等待按键按下才返回数据
4.2非阻塞操作
进程进行设备操作时,使用down_trylock()函数,若获取不到资源并不挂起,直接放弃。
在read读取按键时, 不管有没有数据都要返回
4.3 怎么来判断阻塞与非阻塞操作?
在用户层open时,默认为阻塞操作,如果添加了” O_NONBLOCK”,表示使open()、read()、write()不被阻塞
实例:
fd=open("/dev/buttons",O_RDWR); //使用阻塞操作 fd = open("/dev/buttons ", O_RDWR | O_NONBLOCK); //使用非阻塞操作
然后在驱动设备中,通过file_operations成员函数.open、.read、.write带的参数file->f_flags 来查看用户层访问时带的参数
实例:
if( file->f_flags & O_NONBLOCK ) //非阻塞操作,获取不到则退出 { ... ... } else //阻塞操作,获取不到则进入休眠 { ... ... }
4.4修改应用程序,通过判断file->f_flags来使用阻塞操作还是非阻塞操作
(1)定义互斥锁变量:
/*定义互斥锁button_lock,被用来后面的down()和up()使用 */ static DECLARE_MUTEX(button_lock);
(2)在.open成员函数里添加:
if( file->f_flags & O_NONBLOCK ) //非阻塞操作 { if(down_trylock(&button_lock) ) //尝试获取信号量,获取不到则退出 return -1; }
else //阻塞操作 { down(&button_lock); //获取信号量,获取不到则进入休眠 }
(3)在. release成员函数里添加:
/*释放信号量*/ up(&button_lock);
4.5 写阻塞测试程序 fifth_blocktext.c
代码如下:
int main(int argc,char **argv) { int oflag; unsigned int val=0; fd=open("/dev/buttons",O_RDWR); //使用阻塞操作 if(fd<0) {printf("can't open, fd=%d ",fd); return -1;} else { printf("can open,PID=%d ",getpid()); //打开成功,打印pid进程号 } while(1) { val=read( fd, &ret, 1); //读取驱动层数据 printf("key_vale=0X%x,retrun=%d ",ret,val); } return 0; }
4.6 非阻塞测试效果
如下图所示:
4.7写阻塞测试程序 fifth_nonblock.c
代码如下:
int main(int argc,char **argv) { int oflag; unsigned int val=0; fd=open("/dev/buttons",O_RDWR | O_NONBLOCK); //使用非阻塞操作 if(fd<0) {printf("can't open, fd=%d ",fd); return -1;} else { printf("can open,PID=%d ",getpid()); //打开成功,打印pid进程号 }
while(1) { val=read( fd, &ret, 1); //读取驱动层数据 printf("key_vale=0X%x,retrun=%d ",ret,val); sleep(3); //延时3S } return 0;
}
4.8 阻塞测试效果
如下图所示:
接下来学习按键驱动使用定时器防抖