嵌入式Linux异步通知
阻塞式I/O是处于休眠态一直等待直到设备可以访问
非阻塞式I/O是定期轮询设备是否可以访问
异步通知是当设备可以访问时才主动通知应用程序
阻塞和非阻塞IO的按键驱动方式都是应用程序通过主动查询的方式获得
异步通知可以做到应用程序不用随时去查询按键的状态,而等待有按键被按下后,驱动程序主动发消息给应用程序,应用程序再去处理。
-
信号是软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据。
-
整个过程相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的。
-
异步通知的核心就是信号,在arch/xtensa/include/uapi/asm/signal.h 文件中定义了 Linux 所支持的所有信号
34 #define SIGHUP 1 /* 终端挂起或控制进程终止 */
35 #define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
36 #define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
37 #define SIGILL 4 /* 非法指令 */
38 #define SIGTRAP 5 /* debug 使用,有断点指令产生 */
39 #define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
40 #define SIGIOT 6 /* IOT 指令 */
41 #define SIGBUS 7 /* 总线错误 */
42 #define SIGFPE 8 /* 浮点运算错误 */
43 #define SIGKILL 9 /* 杀死、终止进程 */
44 #define SIGUSR1 10 /* 用户自定义信号 1 */
45 #define SIGSEGV 11 /* 段违例(无效的内存段) */
46 #define SIGUSR2 12 /* 用户自定义信号 2 */
47 #define SIGPIPE 13 /* 向非读管道写入数据 */
48 #define SIGALRM 14 /* 闹钟 */
49 #define SIGTERM 15 /* 软件终止 */
50 #define SIGSTKFLT 16 /* 栈异常 */
51 #define SIGCHLD 17 /* 子进程结束 */
52 #define SIGCONT 18 /* 进程继续 */
53 #define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
54 #define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
55 #define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
56 #define SIGTTOU 22 /* 后台进程需要向终端写数据 */
57 #define SIGURG 23 /* 有"紧急"数据 */
58 #define SIGXCPU 24 /* 超过 CPU 资源限制 */
59 #define SIGXFSZ 25 /* 文件大小超额 */
60 #define SIGVTALRM 26 /* 虚拟时钟信号 */
61 #define SIGPROF 27 /* 时钟信号描述 */
62 #define SIGWINCH 28 /* 窗口大小改变 */
63 #define SIGIO 29 /* 可以进行输入/输出操作 */
64 #define SIGPOLL SIGIO
65 /* #define SIGLOS 29 */
66 #define SIGPWR 30 /* 断点重启 */
67 #define SIGSYS 31 /* 非法的系统调用 */
68 #define SIGUNUSED 31 /* 未使用信号 */
-
- 除了 SIGKILL(9)和 SIGSTOP(19)这两个信号不能被
忽略外,其他的信号都可以忽略。
- 除了 SIGKILL(9)和 SIGSTOP(19)这两个信号不能被
-
- 驱动程序可以通过向应用程序发送不同的信号来实现不同的功能。
驱动中的信号处理
1、 fasync_struct 结构体
需要在驱动程序中定义一个 fasync_struct 结构体指针变量, fasync_struct 结构体(内核中)
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
};
一般将 fasync_struct 结构体指针变量定义到设备结构体中,比如在 imx6uirq_dev结构体中添加一个 fasync_struct 结构体指针变量(驱动代码中)
struct imx6uirq_dev {
struct device *dev;
struct class *cls;
struct cdev cdev;
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
2、 fasync 函数
要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 fasync 函数
int (*fasync) (int fd, struct file *filp, int on)
fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针, fasync_helper 函数原型如下:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
- fasync_helper 函数的前三个参数就是 fasync 函数的那三个参数,第四个参数就是要初始化的 fasync_struct 结构体指针变量。
- 当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行。
驱动程序中
struct xxx_dev {
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
static int xxx_fasync(int fd, struct file *filp, int on)
{
struct xxx_dev *dev = (xxx_dev)filp->private_data;
if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
return -EIO;
return 0;
}
static struct file_operations xxx_ops = {
......
.fasync = xxx_fasync,
......
};
- 在关闭驱动文件的时候需要在 file_operations 操作集中的 release 函数中释放 fasync_struct,fasync_struct 的释放函数同样为 fasync_helper
static int xxx_release(struct inode *inode, struct file *filp)
{
return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
}
static struct file_operations xxx_ops = {
......
.release = xxx_release,
};
3、kill_fasync 函数
当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。 kill_fasync函数负责向应用程序发送指定的信号
void kill_fasync(struct fasync_struct **fp, int sig, int band)
函数参数和返回值含义如下:
fp:要操作的 fasync_struct。
sig: 要发送的信号。
band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。
返回值: 无。
应用程序对异步通知的处理
1、注册信号处理函数
应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来设置信号的处理函数。
sighandler_t signal(int signum, sighandler_t handler)
函数参数和返回值含义如下:
signum:要设置处理函数的信号。
handler: 信号的处理函数。
返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR。
信号处理函数原型如下所示:
typedef void (*sighandler_t)(int)
2、将应用程序的进程号告诉给内核
使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。
3、开启异步通知
使用如下两行程序开启异步通知:
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */
通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。
实验
当按键按下以后驱动程序向应用程序发送 SIGIO 信号,应用程序获取到 SIGIO 信号以后读取并且打印出按键值。
1、驱动程序
新建名为“16_asyncnoti”的文件夹,然后在 16_asyncnoti 文件夹里面创建 vscode 工程,工作区命名为“asyncnoti”。将“15_noblockio”实验中的 noblockio.c 复制到 16_asyncnoti 文件夹中,并重命名为 asyncnoti.c。
基于非阻塞实验代码修改部分
- 添加头文件
#include <linux/fcntl.h>
- 修改设备名字
#define IMX6UIRQ_NAME "asyncnoti"
- imx6uirq 设备结构体添加异步结构体
struct imx6uirq_dev{
dev_t devid; /*设备号*/
struct cdev cdev; /*cdev*/
struct class *class; /*lei*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
struct device_node * nd; /*设备节点*/
atomic_t keyvalue; /*有效按键值*/
atomic_t releasekey; /*标记是否完成一次完整的按键*/
struct timer_list timer; /*定义一个定时器*/
struct irq_keydesc irqkey[KEY_NUM]; /*按键描述数组*/
unsigned char curkeynum; /*当前按键号*/
wait_queue_head_t r_wait; /*读等待队列头*/
struct fasync_struct *fasync_queue; /*异步相关结构体 */
};
- 定时器服务函数,按键有效,驱动向应用程序发送信息
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev=(struct imx6uirq_dev*)arg;
num=dev->curkeynum;
keydesc=&dev->irqkey[num];
value=gpio_get_value(keydesc->gpio);/* 读取 IO 值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue,keydesc->value);
}else{
atomic_set(&dev->keyvalue, 0x80|keydesc->value);/* 按键松开 */
atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
}
/*向应用程序发信号*/
if(atomic_read(&dev->releasekey)){ /* 完成一次按键过程 */
if(dev->fasync_queue)
kill_fasync(&dev->fasync_queue,SIGIO,POLL_IN);
}
}
- 添加imx6uirq_fasync 函数,调用一下 fasync_helper初始化fasync_struct 结构体指针
static int imx6uirq_fasync(int fd,struct file*filp,int on)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
return fasync_helper(fd,filp,on,&dev->fasync_queue);
}
- 添加release 函数,应用程序调用 close 函数关闭驱动设备文件的时候此函数就会执行,在此函数中释放掉 fasync_struct 指针变量
static int imx6uirq_release(struct inode *inode,struct file *filp)
{
return imx6uirq_fasync(-1,filp,0);
}
- 设置 file_operations 操作集中的 fasync 和 release 这两个成员变量
/* 设备操作函数 */
static struct file_operations imx6uirq_fops={
.owner=THIS_MODULE,
.open=imx6uirq_open,
.read=imx6uirq_read,
.poll=imx6uirq_poll,
.fasync=imx6uirq_fasync,
.release=imx6uirq_release,
};
2、应用程序
设置 SIGIO 信号的处理函数为 sigio_signal_func,当驱动程序向应用程序发送 SIGIO 信号以后 sigio_signal_func 函数就会执行。 sigio_signal_func 函数内容就是通过 read 函数读取按键值。
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#include "poll.h"
#include "sys/time.h"
#include "sys/select.h"
#include "signal.h"
/* 使用方法 :./asyncnotiApp /dev/asyncnoti 打开测试 App*/
static int fd =0; /* 文件描述符 */
/*SIGIO 信号处理函数*/
static void sigio_signal_func(int signum)
{
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);
}
}
int main(int argc, char* argv[])
{
int flags = 0;
char * filename;
if(argc!=2){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/*打开驱动*/
fd=open(filename,O_RDWR);
if(fd<0){
printf("file %s open failed!\r\n",argv[1]);
return -1;
}
/*设置信号SIGIO的处理函数*/
signal(SIGIO,sigio_signal_func);
fcntl(fd,F_SETOWN,getpid()); /*将当前进程的进程号告诉给内核 */
flags=fcntl(fd,F_GETFD); /* 获取当前的进程状态 */
fcntl(fd,F_SETFL,flags|FASYNC); /* 设置进程启用异步通知功能 */
while(1){
sleep(2);
}
close(fd);/*关闭文件*/
return 0;
}
3、运行测试
- 编译驱动程序和测试 APP
编写 Makefile 文件,将 obj-m 变量的值改为 asyncnoti.o
输入命令make -j32
编译出驱动模块文件:asyncnoti.ko
输入如命令arm-linux-gnueabihf-gcc asyncnotiApp.c -o asyncnotiApp
编译出 asyncnotiApp 测试程序 - asyncnoti.ko 和 asyncnotiApp 这两个文件拷贝到rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录 lib/modules/4.1.15 中,输入
insmod asyncnoti.ko
加载asyncnoti.ko 驱动模块
- 后台模式运行:
./asyncnotiApp /dev/asyncnoti &
,按下开发板上的 KEY0 键,终端就会输出按键值,按键值获取成功 - 输入“top”命令,查看 asyncnotiAPP 这个应用 APP 的 CPU 使用率
- asyncnotiApp 这个应用程序的 PID 为 88,使用“kill -9 PID”即可“杀死”指定 PID 的进程,“kill”命令关闭后台运行的应用程序