目录
一、异步通知简介
阻塞、非阻塞、异步通知区别
信号与信号修改测试
测试
二、驱动编写
1、定义fasync_struct 结构体指针变量
2、操作集添加fasync
3、实现imx6uirq_fasync 函数
4、关闭驱动文件操作
编辑
5、定时器处理函数
三、编写APP
1、编写信号的处理函数
2、信号处理与异步通知
代码如下
imx6uirq设备结构体
操作集
imxuirq_fasync函数
imx6uirq_release函数
定时器处理函数
APP
验证
在前面使用阻塞或者非阻塞的方式来读取驱动中按键值都是应用程序主动读取的,对于非阻塞方式来说还需要应用程序通过 poll 函数不断的轮询。最好的方式就是驱动程序能主动向应用程序发出通知,报告自己可以访问,然后应用程序在从驱动程序中读取或写入数据,Linux 提供了异步通知个机制来完成此功能。
一、异步通知简介
首先来回顾一下“中断”,中断是处理器提供的一种异步机制,我们配置好中断以后就可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,在中断服务函数中做具体的处理。采用中断以后处理器就不需要时刻的去查看也没有出发中断的事件发生,因为会触发中断的事件发生以后会自动触发中断。同样的, Linux 应用程序可以通过阻塞或者非阻塞这两种方式来访问驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻塞方式的话会通过 poll 函数来不断的轮询,查看驱动设备文件是否可以使用。这两种方式都需要应用程序主动的去查询设备的使用情况。如果能提供一种类似中断的机制,当驱动程序可以
访问的时候主动告诉应用程序那就最好。
“信号”为此应运而生,信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的
阻塞、非阻塞、异步通知区别
阻塞: 进程等待设备资源可用, 等待过程中, 进程休眠(CPU切换去执行别的进程)
:(我睡觉等你,好了就叫醒我执行)
非阻塞: 不等待,轮询设备
:(你好了没有啊?你好了没有啊?你好了没有啊?你好了没有啊?... ... ...)
异步通知: 让设备主动去通知进程
:(你叫我)
阻塞、非阻塞、异步通知,这三种是针对不同的场合提出来的不同的解决方法,没有优劣
之分,在实际的工作和学习中,根据自己的实际需求选择合适的处理方法即可
信号与信号修改测试
异步通知的核心就是信号,在 arch/xtensa/include/uapi/asm/signal.h 文件中定义了 Linux 所支持的所有信号,这列出几个,如下
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */#define SIGIO 29 /* 可以进行输入/输出操作 */
在信号里,除了 SIGKILL(9)和 SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以忽略。这些信号就相当于中断号,不同的中断号代表了不同的中断,不同的中断所做的处理不同,因此,驱动程序可以通过向应用程序发送不同的信号来实现不同的功能。
使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用 signal 函数来设置指定信号的处理函数, signal 函数原型如下所示:
sighandler_t signal(int signum, sighandler_t handler)
signum:要设置处理函数的信号。
handler: 信号的处理函数。
返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR
信号处理函数原型如下所示
typedef void (*sighandler_t)(int)
前面讲解的使用“ kill -9 PID”杀死指定进程的方法就是向指定的进程(PID)发送SIGKILL 这个信号。当按下键盘上的 CTRL+C 组合键以后会向当前正在占用终端的应用程序发出 SIGINT 信号, SIGINT 信号默认的动作是关闭当前应用程序。这里我们修改一下 SIGINT 信号的默认处理函数,当按下 CTRL+C 组合键以后先在终端上打印出“SIGINT signal!”这行字符串,然后再关闭当前应用程序。
测试
下面用vim编写修改一个终端中断(Ctrl+C 组合键)处理
13行,调用signal 函数,SIGINT是要设置处理函数的信号。sigint_handler: 信号的处理函数。
这里简单一些就不接收返回值
5-9行,信号的处理函数,这里就直接打印字符串,然后就关闭退出当前应用程序
执行结果如下
运行程序之后,按ctrl+c就会打印字符串并且退出运行
二、驱动编写
复制利用之前的中断实验工程,修改makefile和把app名字修改,留下文件如下左,其余文件删除
添加头文件
#include <linux/poll.h>
#include <linux/ide.h>
#include <linux/fcntl.h>
1、定义fasync_struct 结构体指针变量
一般需要将fasync_struct 结构体指针变量定义到设备结构体中
50行就是在 imx6uirq_dev 中添加了一个 fasync_struct 结构体指针变量
2、操作集添加fasync
如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 fasync 函数
3、实现imx6uirq_fasync 函数
89行,设置私有数据
90行,通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针, 原型如下
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
前三个参数就是 imx6uirq_fasync 函数的那三个参数第四个参数就是要初始化的 fasync_struct 结构体指针变量。
4、关闭驱动文件操作
在关闭驱动文件的时候需要在 file_operations 操作集中的 release 函数中释放 fasync_struct,
fasync_struct 的释放函数同样为 fasync_helper 函数
97行,删除异步通知,把fd设置为-1(fd文件描述符从0开始,错误为负)
5、定时器处理函数
132-134行,判断按键是否释放,是的话使用kill_fasync 函数发送SIGIO信号,也就是可以进行输入/输出操作。当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。 kill_fasync函数负责发送指定的信号, 原型如下所示:
void kill_fasync(struct fasync_struct **fp, int sig, int band)
fp:要操作的 fasync_struct。
sig: 要发送的信号。
band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。
返回值: 无
驱动到这就添加好了,下面编写APP
三、编写APP
添加头文件
#include <signal.h> #include <fcntl.h>
1、编写信号的处理函数
18行,利用读操作,把驱动的keyvalue值读出来
22行,读取成功就打印
2、信号处理与异步通知
52行,使用 signal 函数来设置指定信号的处理函数,在上面测试ctrl+c组合键的时候有介绍
53行,利用fcntl函数设置当前进程接收信号,函数原型如下
int fcntl(int fd, int cmd,... );
参数fd是被参数cmd操作的文件描述符
cmd有很多选项,这里就使用F_GETFL通过 getpid()获取当前的进程ID接收SIGIO信号
54行,用flags接收fcntl函数获取当前文件状态
55行,开启当前进程异步通知功能,重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行
代码如下
imx6uirq设备结构体
struct imx6uirq_dev{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
struct irq_keydesc irqkey[KEY_NUM];
struct timer_list timer;
atomic_t keyvalue;
atomic_t releasekey;
struct fasync_struct *fasync_queue;
}imx6uirq;
操作集
static const struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.write = imx6uirq_write,
.release = imx6uirq_release,
.fasync = imx6uirq_fasync,
};
imxuirq_fasync函数
static int imx6uirq_fasync(int fd, struct file *filp,int on)
{
struct imx6uirq_dev *dev = filp->private_data;
return fasync_helper(fd, filp , on , &dev->fasync_queue);
}
imx6uirq_release函数
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
return imx6uirq_fasync(-1, filp, 0);
}
定时器处理函数
/*定时器处理函数*/
static void timer_func(unsigned long arg){
int value = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
value = gpio_get_value(dev->irqkey[0].gpio);
if(value == 0){
/*按下*/
atomic_set(&dev->keyvalue,dev->irqkey[0].value);
}else if(value == 1){
/*释放*/
atomic_set(&dev->keyvalue,0x80 | (dev->irqkey[0].value));
atomic_set(&dev->releasekey,1);
}
if(atomic_read(&dev->releasekey))/*有效按键过程*/
{
kill_fasync(&dev->fasync_queue,SIGIO , POLL_IN);
}
}
APP
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <fcntl.h>
static int fd;
static void sigio_signal_func(int num)
{
int err =0;
unsigned int keyvalue = 0;
err = read(fd , &keyvalue, sizeof(keyvalue));
if(err < 0){
}else{
printf(" sigio signal! key value = %d\r\n",keyvalue);
}
}
/*
argc:应用程序参数个数(argv数组元素个数)
argv:具体参数,也可以写作char **argv
./asyncnotiAPP <filename>
./asyncnotiAPP /dev/imx6uirq
*/
int main(int argc, char *argv[])
{
int ret,flags=0;
char *filename;
unsigned char data;
/*判断命令行输入参数是否正确*/
if(argc != 2){
printf("error usage!\r\n");
return -1;
}
/*用指针指向文件*/
filename = argv[1];
/*打开文件*/
fd = open(filename , O_RDWR);
if(fd < 0){
printf("file open failed\r\n",filename);
return -1;
}
/*设置信号处理函数*/
signal(SIGIO , sigio_signal_func);
fcntl(fd , F_SETOWN,getpid());/*设置当前进程接收SIGIO信号*/
flags = fcntl(fd , F_SETFL);
fcntl(fd , F_SETFL , flags | FASYNC);/*异步通知*/
while (1)
{
sleep(2);
}
/*关闭文件*/
close(fd);
return 0;
}
验证
启动之后按下按键就会打印出值