异步通知

异步通知:一旦设备准备就绪,则主动通知应用程序,这样引用程序根本不需要查询设备状态,由此引入了信号的概念,信号是在软件层次上的对中断机制的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,。

信号

Linux中用异步信号来实现进程通信(IPC)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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 /* 未使用信号 */

信号的接收

在应用程序中使用 signal 函数来设置指定信号的处理函数

1
2
3
sighandler_t signal(int signum, sighandler_t handler)
/*第一个参数处理的信号,第二个参数指定指针对前面信号值的处理函数,调用成功返回最后一次为信号signum绑定handler值,失败返回SIG_ERR*/
typedef void (*sighandler_t)(int)

为文件描述符的设置属主

通过 fcntl() 的 F_SETOWN 操作来完成:

1
fcntl(fd, F_SETOWN, pid)

属主是当文件描述符上可执行 I/O 时,会接收到通知信号的进程或进程组。

pid 为正整数时,代表了进程 ID 号。

pid 为负整数时,它的绝对值就代表了进程组 ID 号。

使用信号异步通知的应用程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100
void input_handler(int num)
{
char data[MAX_LEN];
int len;
len = read(STDIN_FILENO, &data, MAX_LEN);
data[len] = 0;
printf("input available :%s\n", data);
}
main()
{
int oflags;
signal(SIGIO, input_handler);
fcntl(STDIN_FILENO, F_SETOWN, getpid());//fcntl函数对描述符提供控制
oflags = fcntl(STDIN_FILENO,F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, oflags|FASYNC);
while(1);//为了保证进程不终止。
}

由此可以看到,为了在用户空间中能处理一个设备释放的信号,必须完成三项工作:

  1. 通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到;
  2. 通过F_SETFL IO控制命令设置设备文件支持FASYNC,即异步通知;
  3. 通过signal函数连结信号和信号处理函数。

信号的释放

在设备驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,因为信号没有的源头在设备驱动端,因此,应该在合适的时机让设备驱动释放信号, 在设备驱动程序中加信号释放的代码。

为支持异步通知机制,驱动程序中涉及以下三项工作。

支持F_SETOWN命令。能在这个控制命令处理中设置filp->f_owner为对应进程ID。不过此项工作已由内核完成,设备驱动无需处理。

支持S_SETFL命令处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将执行。

在设备可获得时候,调用kill_fasync()函数激发相应的信号

异步通知中用户空间和设备驱动之间的交互:

设备驱动中要用到一项数据结构和两个函数:

在驱动程序中定义一个 fasync_struct 结构体指针变量 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 结构体指针变量定义到设备结构体中
struct imx6uirq_dev {
struct device *dev;
struct class *cls;
struct cdev cdev;
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};

如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 fasync 函数 。

1
int (*fasync) (int fd, struct file *filp, int on)

fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针。

1
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行。

驱动中fasync参考实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 -> privata_data;
if(fasync_helper(fd, filp, on ,&dev->async_queue) < 0)
return 0;
return 0;
}
static int xxx_release(struct inode *inode ,struct file *filp)
{
return xxx_fasync( -1, filp, 0); //调用函数完成fasync_struct 的释放工作
}
static struct file_operations xxx_ops = {
....
.fasync = xxx_fasync,
.release = xxx_release,
....
};

kill_fasync 函数

当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。 kill_fasync函数负责发送指定的信号。

1
void kill_fasync(struct fasync_struct **fp, int sig, int band)

fp:要操作的 fasync_struct。
sig: 要发送的信号。
band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。
返回值: 无。

应用程序对异步通知的处理

应用程序对异步通知的处理包括以下三步:

  1. 注册信号处理函数(使用signal函数)
  2. 将本应用程序的进程号告诉给内核(使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核 )
  3. 开启异步通知
1
2
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

备注fcntl函数用法

1
2
3
4
5
#include<unistd.h>  
#include<fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);

cmd的值不同有对应功能如下:

命令名 描述
F_DUPFD 复制文件描述符
F_GETFD 获取文件描述符标志
F_SETFD 设置文件描述符标志
F_GETFL 获取文件状态标志
F_SETFL 设置文件状态标志
F_GETLK 获取文件锁
F_SETLK 设置文件锁
F_SETOWN 设置当前接收SIGIO和SIGURG信号的进程ID和进程组ID
F_SETLKW 类似F_SETLK,但等待返回
F_GETOWN 获取当前接收SIGIO和SIGURG信号的进程ID和进程组ID

应用程序对异步通知的处理

1、注册信号处理函数
应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来设置信号的处理函数。
2、将本应用程序的进程号告诉给内核
使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。
3、开启异步通知
使用如下两行程序开启异步通知:
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 /
fcntl(fd, F_SETFL, flags | FASYNC); /
开启当前进程异步通知功能 */
重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行 。