linux异步通知
异步通知
异步通知:一旦设备准备就绪,则主动通知应用程序,这样引用程序根本不需要查询设备状态,由此引入了信号的概念,信号是在软件层次上的对中断机制的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,。
信号
Linux中用异步信号来实现进程通信(IPC)。
1 | 34 |
信号的接收
在应用程序中使用 signal 函数来设置指定信号的处理函数
1 | sighandler_t signal(int signum, sighandler_t handler) |
为文件描述符的设置属主
通过 fcntl() 的 F_SETOWN 操作来完成:
1 | fcntl(fd, F_SETOWN, pid) |
属主是当文件描述符上可执行 I/O 时,会接收到通知信号的进程或进程组。
pid 为正整数时,代表了进程 ID 号。
pid 为负整数时,它的绝对值就代表了进程组 ID 号。
使用信号异步通知的应用程序
1 |
|
由此可以看到,为了在用户空间中能处理一个设备释放的信号,必须完成三项工作:
- 通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到;
- 通过F_SETFL IO控制命令设置设备文件支持FASYNC,即异步通知;
- 通过signal函数连结信号和信号处理函数。
信号的释放
在设备驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,因为信号没有的源头在设备驱动端,因此,应该在合适的时机让设备驱动释放信号, 在设备驱动程序中加信号释放的代码。
为支持异步通知机制,驱动程序中涉及以下三项工作。
支持F_SETOWN命令。能在这个控制命令处理中设置filp->f_owner为对应进程ID。不过此项工作已由内核完成,设备驱动无需处理。
支持S_SETFL命令处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将执行。
在设备可获得时候,调用kill_fasync()函数激发相应的信号
异步通知中用户空间和设备驱动之间的交互:
设备驱动中要用到一项数据结构和两个函数:
在驱动程序中定义一个 fasync_struct 结构体指针变量 。
1 | struct fasync_struct { |
如果要使用异步通知,需要在设备驱动中实现 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 | struct xxx_dev{ |
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。
返回值: 无。
应用程序对异步通知的处理
应用程序对异步通知的处理包括以下三步:
- 注册信号处理函数(使用signal函数)
- 将本应用程序的进程号告诉给内核(使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核 )
- 开启异步通知
1 | flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */ |
备注fcntl函数用法
1 |
|
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 函数就会执行 。