Linux设备驱动基础04之异步通知

Posted PanGC2014

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux设备驱动基础04之异步通知相关的知识,希望对你有一定的参考价值。

一、基础简介

使用阻塞/非阻塞方式读写设备驱动时,都需要应用程序主动发起,对于非阻塞方式来说还需通过poll函数不断轮询。这种情况下更好的解决方案是,设备驱动主动向应用程序发出通知,报告自己可以访问,然后应用程序再读写设备驱动。
Linux中这种机制叫做异步通知,异步通知的核心是信号,类似硬件中断,信号是软件层次上的软中断。设备驱动可以主动向应用程序发送信号,通知应用程序可以访问,应用程序中需要注册信号的处理函数,并将本进程号告诉内核。
在arch/xtensa/include/uapi/asm/signal.h文件中定义了Linux所支持的所有信号,如下所示:

#define SIGHUP 		1  /* 终端挂起或控制进程终止 */
#define SIGINT 		2  /* 终端中断(Ctrl+C 组合键) */
#define SIGQUIT 	3  /* 终端退出(Ctrl+\\组合键) */
#define SIGILL 		4  /* 非法指令 */
#define SIGTRAP 	5  /* debug 使用,有断点指令产生 */
#define SIGABRT 	6  /* 由 abort(3)发出的退出指令 */
#define SIGIOT 		6  /* IOT 指令 */
#define SIGBUS 		7  /* 总线错误 */
#define SIGFPE 		8  /* 浮点运算错误 */
#define SIGKILL 	9  /* 杀死、终止进程 */
#define SIGUSR1 	10 /* 用户自定义信号 1 */
#define SIGSEGV 	11 /* 段违例(无效的内存段) */
#define SIGUSR2 	12 /* 用户自定义信号 2 */
#define SIGPIPE 	13 /* 向非读管道写入数据 */
#define SIGALRM 	14 /* 闹钟 */
#define SIGTERM 	15 /* 软件终止 */
#define SIGSTKFLT 	16 /* 栈异常 */
#define SIGCHLD 	17 /* 子进程结束 */
#define SIGCONT 	18 /* 进程继续 */
#define SIGSTOP 	19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 	20 /* 停止进程的运行(Ctrl+Z 组合键) */
#define SIGTTIN 	21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 	22 /* 后台进程需要向终端写数据 */
#define SIGURG 		23 /* 有"紧急"数据 */
#define SIGXCPU 	24 /* 超过 CPU 资源限制 */
#define SIGXFSZ 	25 /* 文件大小超额 */
#define SIGVTALRM 	26 /* 虚拟时钟信号 */
#define SIGPROF 	27 /* 时钟信号描述 */
#define SIGWINCH 	28 /* 窗口大小改变 */
#define SIGIO 		29 /* 可以进行输入/输出操作 */
#define SIGPOLL 	SIGIO
#define SIGPWR 		30 /* 断点重启 */
#define SIGSYS 		31 /* 非法的系统调用 */
#define SIGUNUSED 	31 /* 未使用信号 */

这些信号就相当于中断号,驱动程序可以通过向应用程序发送不同的信号来实现不同的功能,应用程序中必须设置信号所使用的信号处理函数。

二、设备驱动之异步通知

1、fasync_struct结构体

设备驱动中,异步通知机制是以struct fasync_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;
};

2、fasync函数

使用异步通知,需要在设备驱动程序中实现file_operations操作集中的fasync函数,函数格式如下:

int (*fasync) (int fd, struct file *filp, int on);

3、fasync_helper函数

在file_operations操作集中的fasync函数中,通过调用fasync_helper函数,来初始化自定义设备结构体中的fasync_struct结构体指针变量,该函数原型如下:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp);

该函数前三个参数对应fasync中三个参数,第四个参数struct fasync_struct **fapp就是要初始化的fasync_struct结构体指针变量。当应用程序通过"fcntl(fd, F_SETFL, flags | FASYNC)"改变fasync标记时,驱动程序中file_operations操作集中的fasync函数就会执行。

4、release函数

int (*release)(struct inode *inode, struct file *filp);

5、kill_fasync函数

当设备可以访问时,驱动程序需要向应用程序发出信号,相当于产生"中断";驱动程序中使用kill_fasync发送指定的信号,函数原型如下:

void kill_fasync(struct fasync_struct **fp, int sig, int band);

函数参数含义:
fp:要操作的fasync_struct结构体指针。
sig:要发送的信号,比如。
band:可读时设置为POLL_IN,可写时设置为POLL_OUT。

6、使用示例

struct xxx_dev {
	......
 	struct fasync_struct *async_queue; /* 异步相关结构体 */
};

static int xxx_fasync(int fd, struct file *filp, int on)
{
	/* 使能异步通知 */ 
	struct xxx_dev *dev = (xxx_dev)filp->private_data;
	if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
		return -EIO;
	return 0;
}

static int xxx_release(struct inode *inode, struct file *filp)
{
	/* 删除异步通知 */
	struct xxx_dev *dev = (xxx_dev)filp->private_data;
	if (fasync_helper(-1, filp, 0, &dev->async_queue) < 0)
	return -EIO;
	return 0;
}

static struct file_operations xxx_ops = {
	......
	.fasync = xxx_fasync,
	.release = xxx_release,
	......
};

三、应用程序之信号处理

1、基本处理流程

应用程序对异步通知的处理包括以下三步:
(1)、注册信号处理函数;使用signal函数来设置信号的处理函数。
(2)、将应用程序进程号告诉内核;使用fcntl(fd, F_SETOWN, gitpid())将本进程号告诉内核。
(3)、开启异步通知;开启的过程需要两步,如下所示:

flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 通过设置进程状态为FASYNC,开启当前进程异步通知功能 */

2、信号处理函数原型

typedef void (*sighandler_t)(int);

3、设置信号处理函数

信号处理函数定义完之后,需要与对应的信号进行绑定,Linux应用程序中使用signal函数来设置指定信号的处理函数。signal函数原型如下:

sighandler_t signal(int signum, sighandler_t handler);

其中,signum是要设置处理函数的信号;handler是信号处理函数。返回值:成功则返回信号的前一个处理函数,失败则返回SIG_ERR。

4、使用示例

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

static void signal_handler(int num)
{
	printf("\\r\\nSignal Handler Runing\\r\\n");
	exit(0);
}

int main(int argc, char *argv[])
{
	signal(SIGINT, signal_handler);
	while(1);
	return 0;
}

以上是关于Linux设备驱动基础04之异步通知的主要内容,如果未能解决你的问题,请参考以下文章

Linux设备驱动基础04之异步通知

Linux设备驱动基础04之异步通知

Linux通信之异步通知模式

Linux设备驱动程序 之 异步通知

Linux之异步通知机制分析

Linux驱动开发异步通知