异步通知教程 介绍在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用于保存原处理方式可为NULLsignal()的增强版功能更强大可精确控制信号处理行为fcntl()F_SETOWNint fcntl(int fd, int cmd, ... /* arg */);fd文件描述符cmdF_SETOWNarg进程IDgetpid()fd已打开的设备文件描述符cmdF_SETOWN表示设置文件拥有者arg指定接收信号的进程PID将当前进程设置为设备文件的“拥有者”告诉内核/驱动信号该发给谁fcntl()F_SETFL | FASYNCint fcntl(int fd, int cmd, ... /* arg */);fd文件描述符cmdF_SETFLargoflags | FASYNCfd已打开的设备文件描述符cmdF_SETFL表示设置文件状态标志arg在原标志基础上添加FASYNC位开启异步通知模式开启设备的异步通知功能。执行后触发驱动中的.fasync回调函数fcntl()F_GETFLint fcntl(int fd, int cmd, ... /* arg */);fd文件描述符cmdF_GETFLfd已打开的设备文件描述符cmdF_GETFL表示获取当前文件状态标志获取设备文件当前的状态标志通常与F_SETFL配合使用先获取原标志再添加FASYNC2.驱动程序内核空间函数与结构体函数/结构体函数原型/定义参数参数作用函数作用struct fasync_structstruct 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等信息异步通知队列的节点结构体用于维护所有“订阅”了该设备异步通知的进程信息.fasyncfile_operations成员int (*fasync) (int fd, struct file *filp, int on);fd文件描述符filp文件结构体指针onFASYNC标志值fd由fcntl传入的文件描述符filp由fcntl传入的struct file指针onFASYNC标志的开关状态1开启/0关闭当应用程序通过fcntl改变FASYNC标志时内核自动回调此函数驱动在此函数中调用fasync_helper()管理订阅队列fasync_helper()int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp);fd文件描述符filp文件结构体指针onFASYNC标志值fappfasync_struct指针的指针fd由.fasync传入filp由.fasync传入on由.fasync传入fapp指向设备驱动中定义的fasync_struct指针内核辅助函数用于向异步通知队列中添加或移除订阅者进程。开启时注册关闭或释放时移除kill_fasync()void kill_fasync(struct fasync_struct **fp, int sig, int band);fpfasync_struct指针的指针sig要发送的信号编号band事件类型标志fp指向设备驱动中定义的fasync_struct指针sig要发送的信号通常为SIGIObandPOLL_IN数据可读或POLL_OUT缓冲区可写遍历异步通知队列向所有已注册的进程发送指定信号通知它们设备资源已就绪.release中的清理在release函数中调用fasync_helper(-1, filp, 0, dev-async_queue)与fasync_helper()相同但fd传-1on传0fd-1表示要移除而非添加on0关闭FASYNC标志当设备文件被关闭时自动将当前进程从异步通知队列中移除防止向已退出的进程发送信号kill_fasync()的band参数取值标志宏含义使用场景POLL_IN设备数据可读告知应用程序有数据到达可以执行read()POLL_OUT设备缓冲区可写告知应用程序设备发送缓冲区有空闲可以执行write()