《中断学习 —— 内核中断触发用户中断(异步通知机制)》
1.异步通知机制
一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,是一种“信号驱动的异步I/O”。信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候会到达。
从用户程序的角度考虑:为了启动文件的异步通知机制,用户程序必须执行两个步骤。首先,他们指定一个进程作为文件的“属主(owner)”。当进程使用fcntl系统调用执行F_SETOWN命令时,属主进程的进程ID号就被保存在filp->f_owner中。这一步是必需的,目的是为了让内核知道应该通知哪个进程。 然后为了真正启动异步通知机制,用户程序还必须在设备中设置FASYNC标志,这通过fcntl的F_SETFL命令完成的。 执行完这两个步骤之后,输入文件就可以在新数据到达时请求发送一个SIGIO信号。该信号被发送到存放在filp->f_owner中的进程(如果是负值就是进程组)。
在用户程序中,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数:
void (*signal(int signum, void (*handler))(int)))(int);
该函数原型较难理解, 它可以分解为:
typedef void (*sighandler_t)(int); //消息处理函数 sighandler_t signal(int signum, sighandler_t handler)); //连接信号与消息处理函数 第一个参数:指定信号的值 第二个参数:指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义的函数,则信号被捕获到后,该函数将被执行。 返回值:如果signal()调用成功,它返回最后一次为信号signum绑定的处理函数的handler值,失败则返回SIG_ERR。
2.应用程序fcntl设置
#include <stdio.h> #include <fcntl.h> #include <errno.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> void signal_handler(int sig) { if(sig == SIGIO) { printf("Receive io signal from kernel!\n"); } } int main(int argc, char **argv) { int fd; fd = open("/dev/xxx", O_RDONLY); signal(SIGIO, signal_handler); fcntl(fd, F_SETOWN, getpid()); int oflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, oflags | FASYNC); while(1) { sleep(100); } return 0; }
fcntl函数原型:
功能描述:根据文件描述词来操作文件的特性。针对(文件)描述符提供控制,参数fd是被参数cmd操作(如下面的描述)的描述符。
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg);
fcntl函数有5种功能:
- 复制一个现有的描述符(cmd=F_DUPFD或 F_DUPFD_CLOEXEC)
- 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
- 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
- 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
- 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)
针对上面所使用的几个cmd做个详细说明:
F_SETOWN:设置接收SIGIO和SIGURG信号的进程ID或进程组ID。
F_GETFL:对应于fd的文件状态标志作为函数值返回。
F_SETFL:将文件状态标志设置为第3个参数的值。
fcntl(STDIN_FILENO, F_SETOWN, getpid()); //设置本进程为STDIN_FILENO文件的拥有者,没有这一步,内核不会知道应该将信号发给哪个进程 oflags = fcntl(STDIN_FILENO, F_GETFL); //获取设备文件的f_flags fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); //为了启用异步通知机制,还需对设备设置FASYNC标志
从驱动程序角度考虑:
应用程序在执行F_SETFL启用FASYNC时,调用驱动程序的fasync方法。只要filp->f_flags中的FASYNC标识发生了变化,就会调用该方法,以便把这个变化通知驱动程序,使其能正确响应。文件打开时,FASYNC标志被默认为是清除的。当数据到达时,所有注册为异步通知的进程都会被发送一个SIGIO信号。
Linux的这种通用方法基于一个数据结构和两个函数:
extern int fasync_helper(int, struct file *, int, struct fasync_struct **); //当一个打开的文件的FASYNC标志被修改时,调用驱动程序的fasync方法间接调用fasync_helper函数以便将当前进程加入到驱动程序的异步通知等待队列中。 extern void kill_fasync(struct fasync_struct **, int, int); //当设备可访问时,可使用kill_fasync函数发信号所有的相关进程。进程进而调用绑定的消息处理函数。
总结:应用程序使用fcntl()设置当前进程的pid和FASYNC标志。进而调用驱动程序的fasync(),即fasync_helper()。然后申请和设置fasync_struct结构,将此结构挂载到驱动程序的fasync_struct结构链表中。当设备可用时,驱动程序会使用kill_fasync(),从fasync_struct链表中,查找所有的等待进程,然后调用send_sigio发送相应的消息给进程。进程接收到消息,就会跳转到与消息绑定的消息处理函数中。
https://blog.csdn.net/lzs940320/article/details/108558785
3.实例编写
3.1实例编写思路
// 1,设置信号处理方法 signal(SIGIO,catch_signale); // 2,将当前进程设置成SIGIO的属主进程 fcntl(fd, F_SETOWN, getpid()); // 3,将io模式设置成异步模式 int flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | FASYNC ); 驱动--发送信号 1,需要和进程进行关联--记录信号该发送给谁 实现一个fasync的接口 static struct fasync_struct *async; //声明fasync_struct int key_drv_fasync(int fd, struct file *filp, int on) { //只需要调用一个函数记录信号该发送给谁 return fasync_helper(fd, filp, on, &aysnc); }
key_drv_fasync注册到file_operations 2,在某个特定的时候去发送信号,在有数据的时候 //发送信号 kill_fasync(&aysnc, SIGIO, POLL_IN);