《中断学习 —— 内核中断触发用户中断(异步通知机制)》

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种功能:

  1. 复制一个现有的描述符(cmd=F_DUPFD或 F_DUPFD_CLOEXEC)
  2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
  4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  5. 获得/设置记录锁(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);

  

 

 

 

 

 

 

  

 

 

 

 

 

 

 

  

 

 

 

 

 

 

 

  

  

 

posted @ 2020-10-15 09:37  一个不知道干嘛的小萌新  阅读(550)  评论(0编辑  收藏  举报