介绍
在Linux系统编程中,异步通知(Asynchronous Notification,也称信号驱动I/O)是一种让驱动程序主动“敲门”通知应用程序的通信机制。
简单来说,它不是让应用程序去“询问”设备是否有数据,而是让设备在数据就绪时“主动告诉”应用程序:“数据来了,快来取!”——这种模式非常类似于硬件中的中断
从广义理论层面来看,异步通知就是一种软件中断,触发标志就是kill_fasync()函数
如何使用
在linux系统编程中,想要使用异步通知涉及应用程序和驱动程序两端需要协同完成的步骤,下面是各自需要执行的具体操作及其作用。
应用程序端操作
编写并注册信号处理函数:使用
signal()或sigaction()系统调用,将SIGIO信号与一个自定义的处理函数绑定。作用:当驱动发来信号时,内核会自动调用此函数,应用程序在此函数中执行
read、write等操作来响应设备事件。
打开设备文件:使用
open()系统调用打开目标设备的文件描述符。作用:获取一个文件描述符,它是后续所有操作的基础,也明确了要接收哪个设备的通知。
设置设备文件所有者:使用
fcntl(fd, F_SETOWN, getpid()),将当前进程的PID设置为该设备文件的“所有者”。作用:告诉驱动程序信号应该发给哪个进程。这是实现精准通知的关键一步。
使能设备的异步通知模式:使用
fcntl(fd, F_SETFL, flags | FASYNC),为设备文件描述符开启FASYNC标志。作用:向驱动程序“订阅”异步通知服务,表示“我已准备好接收你的信号了”。执行这一步会触发驱动中
fasync()函数的调用。
// 1. 注册SIGIO信号处理函数 signal(SIGIO, sigio_signal_func); //当接收到这个信号时,运行这个sigio_signal_func函数,类似与中断函数 // 2. 设置进程归属:告诉内核当前进程接收SIGIO信号 fcntl(fd, F_SETOWN, getpid()); //设置设备文件拥有者为当前进程 // 3. 开启异步通知功能 flags = fcntl(fd, F_GETFL); //获取当前文件状态标志 fcntl(fd, F_SETFL, flags | FASYNC); //设置FASYNC标志,触发驱动fasync()
驱动程序端操作
驱动程序需要完成“提供服务”和“主动通知”两项核心功能。
定义异步通知结构体指针:在设备的结构体中,增加一个
struct fasync_struct *类型的指针。作用:这个指针是驱动管理异步通知请求的核心,它指向一个用于维护所有“订阅者”(即应用程序)信息的数据结构。
实现
fasync()文件操作:在驱动的file_operations结构中实现.fasync函数,其主体是调用内核函数fasync_helper()。作用:
fasync_helper()负责将当前应用程序的fd等信息注册到第一步定义的fasync_struct队列中。它相当于驱动端的“订阅管理”接口。
在合适的时机发送信号:在设备资源就绪时(如中断处理函数中),调用内核函数
kill_fasync()。作用:遍历
fasync_struct队列,向所有“订阅”了该设备事件的应用程序发送SIGIO信号。这是实现主动通知的最终动作。参数说明:
kill_fasync的band参数用于指明事件类型,可读时传POLL_IN,可写时传POLL_OUT。
(可选)在
release()中清理:在驱动的release函数中,调用一次fasync_helper()。作用:当设备文件被关闭时,自动将对应的应用程序从通知队列中移除,避免出现“野指针”或通知已失效的进程。
函数列表
1. 用户程序(应用程序端)核心函数
| 函数 | 函数原型 | 参数 | 参数作用 | 函数作用 |
|---|---|---|---|---|
signal() | sighandler_t signal(int signum, sighandler_t handler); | signum:信号编号handler:信号处理函数指针 | signum:指定要捕获的信号,异步通知中使用SIGIOhandler:可为SIG_IGN(忽略)、SIG_DFL(默认处理)或用户自定义函数指针 | 注册信号处理函数,将指定信号与处理函数绑定。当驱动发送SIGIO信号时,内核自动调用此函数 |
sigaction() | int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); | signum:信号编号act:新行为配置指针oldact:旧行为保存指针 | signum:指定要捕获的信号act:指向sigaction结构体,在其中指定处理函数oldact:用于保存原处理方式,可为NULL | signal()的增强版,功能更强大,可精确控制信号处理行为 |
fcntl()(F_SETOWN) | int fcntl(int fd, int cmd, ... /* arg */); | fd:文件描述符cmd:F_SETOWNarg:进程ID(getpid()) | fd:已打开的设备文件描述符cmd:F_SETOWN表示设置文件拥有者arg:指定接收信号的进程PID | 将当前进程设置为设备文件的“拥有者”,告诉内核/驱动信号该发给谁 |
fcntl()(F_SETFL | FASYNC) | int fcntl(int fd, int cmd, ... /* arg */); | fd:文件描述符cmd:F_SETFLarg:oflags | FASYNC | fd:已打开的设备文件描述符cmd:F_SETFL表示设置文件状态标志arg:在原标志基础上添加FASYNC位,开启异步通知模式 | 开启设备的异步通知功能。执行后触发驱动中的.fasync回调函数 |
fcntl()(F_GETFL) | int fcntl(int fd, int cmd, ... /* arg */); | fd:文件描述符cmd:F_GETFL | fd:已打开的设备文件描述符cmd:F_GETFL表示获取当前文件状态标志 | 获取设备文件当前的状态标志,通常与F_SETFL配合使用,先获取原标志再添加FASYNC |
2.驱动程序(内核空间)函数与结构体
| 函数/结构体 | 函数原型/定义 | 参数 | 参数作用 | 函数作用 |
|---|---|---|---|---|
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; }; | —(结构体成员) | fa_lock:自旋锁,保护结构体fa_fd:文件描述符fa_next:指向下一个节点的指针(单链表)fa_file:指向struct file的指针,内含进程PID等信息 | 异步通知队列的节点结构体,用于维护所有“订阅”了该设备异步通知的进程信息 |
.fasync(file_operations成员) | int (*fasync) (int fd, struct file *filp, int on); | fd:文件描述符filp:文件结构体指针on:FASYNC标志值 | fd:由fcntl传入的文件描述符filp:由fcntl传入的struct file指针on:FASYNC标志的开关状态(1开启/0关闭) | 当应用程序通过fcntl改变FASYNC标志时,内核自动回调此函数,驱动在此函数中调用fasync_helper()管理订阅队列 |
fasync_helper() | int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp); | fd:文件描述符filp:文件结构体指针on:FASYNC标志值fapp:fasync_struct指针的指针 | fd:由.fasync传入filp:由.fasync传入on:由.fasync传入fapp:指向设备驱动中定义的fasync_struct指针 | 内核辅助函数,用于向异步通知队列中添加或移除订阅者(进程)。开启时注册,关闭或释放时移除 |
kill_fasync() | void kill_fasync(struct fasync_struct **fp, int sig, int band); | fp:fasync_struct指针的指针sig:要发送的信号编号band:事件类型标志 | fp:指向设备驱动中定义的fasync_struct指针sig:要发送的信号,通常为SIGIOband:POLL_IN(数据可读)或POLL_OUT(缓冲区可写) | 遍历异步通知队列,向所有已注册的进程发送指定信号,通知它们设备资源已就绪 |
.release中的清理 | 在release函数中调用fasync_helper(-1, filp, 0, &dev->async_queue) | 与fasync_helper()相同,但fd传-1,on传0 | fd=-1:表示要移除而非添加on=0:关闭FASYNC标志 | 当设备文件被关闭时,自动将当前进程从异步通知队列中移除,防止向已退出的进程发送信号 |
kill_fasync()的band参数取值
| 标志宏 | 含义 | 使用场景 |
|---|---|---|
POLL_IN | 设备数据可读 | 告知应用程序有数据到达,可以执行read() |
POLL_OUT | 设备缓冲区可写 | 告知应用程序设备发送缓冲区有空闲,可以执行write() |