• 异步通知实验学习


    异步通知实验

    Linux 应用程序可以通过阻塞或者非阻塞这两种方式来访问驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻塞方式的话会通过 poll 函数来不断的轮询.查看驱动设备文件是否可以使用。这两种方式都需要应用程序主动的去查询设备的使用情况,
    “信号”为此应运而生,信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的。
    异步通知的核心就是信号,在 arch/xtensa/include/uapi/asm/signal.h 文件中定义了 Linux 所支
    持的所有信号,这些信号如下所示:

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

    在 Linux 和其他类 Unix 系统中,信号是一种进程间通信机制,用于通知进程某些事件的发生。大多数信号可以被进程捕获、忽略或默认处理,但有两个特殊的信号 —— SIGKILLSIGSTOP —— 不能被进程忽略或捕获。这两个信号的特殊性主要基于它们在系统中的作用和设计目的:

    SIGKILL (信号 9)

    • 目的: 强制终止进程。
    • 特性: SIGKILL 信号用于立即终止进程,不给进程任何清理或保存数据的机会。系统管理员和用户可以使用这个信号来终止那些无响应或不能正常关闭的进程。
    • 为什么不能被忽略或捕获: 如果进程能够忽略或捕获 SIGKILL,它们可能会阻止自身被终止,从而导致僵尸进程或无法控制的进程。这会使系统管理员难以管理系统,尤其是在进程不响应其他终止尝试时。

    SIGSTOP (信号 19)

    • 目的: 暂停进程的执行。
    • 特性: SIGSTOP 信号用于暂停(停止)进程的执行,直到收到 SIGCONT 信号。这对于调试和系统管理非常有用。
    • 为什么不能被忽略或捕获: 如果进程能够忽略或捕获 SIGSTOP,它们可能会继续执行,即使系统管理员或操作系统需要它们停止。这样的设计确保了系统管理员可以可靠地控制进程的执行,包括必要时暂停进程。
      使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用 signal 函数来设置指定信号的处理函数, signal 函数原型如下所示:
    sighandler_t signal(int signum, sighandler_t handler)
    函数参数和返回值含义如下:
    signum:要设置处理函数的信号。
    handler: 信号的处理函数。
    返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR
    

    信号处理函数原型如下所示:

    typedef void (*sighandler_t)(int)
    

    我们前面讲解的使用“ kill -9 PID”杀死指定进程的方法就是向指定的进程(PID)发送SIGKILL 这个信号。当按下键盘上的 CTRL+C 组合键以后会向当前正在占用终端的应用程序发出 SIGINT 信号, SIGINT 信号默认的动作是关闭当前应用程序。这里我们修改一下 SIGINT 信号的默认处理函数,当按下 CTRL+C 组合键以后先在终端上打印出“SIGINT signal!”这行字符串,然后再关闭当前应用程序。

    驱动中的信号处理

    1、 fasync_struct 结构体

    首先我们需要在驱动程序中定义一个 fasync_struct 结构体指针变量, fasync_struct 结构体内容如下:

    示例代码 53.1.2.1 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;
    };
    

    一般将 fasync_struct 结构体指针变量定义到设备结构体中,比如在上一章节的 imx6uirq_dev结构体中添加一个 fasync_struct 结构体指针变量,结果如下所示:struct fasync_struct *async_queue; /* 异步相关结构体 */
    如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 fasync 函数,此函数格式如下所示:

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

    fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针, fasync_helper 函数原型如下:

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

    fasync_helper 函数的前三个参数就是 fasync 函数的那三个参数,第四个参数就是要初始化的 fasync_struct 结构体指针变量。当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行:
    驱动程序中的 fasync 函数参考示例如下:

    示例代码 53.1.2.3 驱动中 fasync 函数参考示例
    1 struct xxx_dev {
    2 ......
    3 struct fasync_struct *async_queue; /* 异步相关结构体 */
    4 };
    5 6
    static int xxx_fasync(int fd, struct file *filp, int on)
    7 {
    8 struct xxx_dev *dev = (xxx_dev)filp->private_data;
    9
    10 if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
    11 return -EIO;
    12 return 0;
    13 }
    14
    15 static struct file_operations xxx_ops = {
    16 ......
    17 .fasync = xxx_fasync,
    18 ......
    19 };
    

    在关闭驱动文件的时候需要在 file_operations 操作集中的 release 函数中释放 fasync_struct,fasync_struct 的释放函数同样为 fasync_helper, release 函数参数参考实例如下:

    示例代码 53.1.2.4 释放 fasync_struct 参考示例
    1 static int xxx_release(struct inode *inode, struct file *filp)
    2 {
    3 return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
    4 }
    
    kill_fasync 函数

    当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。 kill_fasync
    函数负责发送指定的信号, kill_fasync 函数原型如下所示:

    void kill_fasync(struct fasync_struct **fp, int sig, int band)
    函数参数和返回值含义如下:
    fp:要操作的 fasync_struct。
    sig: 要发送的信号。
    band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。
    返回值: 无。
    

    应用程序对异步通知的处理

    应用程序对异步通知的处理:

    1、注册信号处理函数

    应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来
    设置信号的处理函数。前面已经详细的讲过了,这里就不细讲了。

    2、将本应用程序的进程号告诉给内核

    使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。

    3、开启异步通知

    使用如下两行程序开启异步通知:

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

    重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行:

    驱动程序编写

    先要在设备结构体中添加

    struct imx6uirq_dev{
    	dev_t devid;			/* 设备号 	 */	
    	struct cdev cdev;		/* cdev 	*/                 
    	struct class *class;	/* 类 		*/
    	struct device *device;	/* 设备 	 */
    	int major;				/* 主设备号	  */
    	int minor;				/* 次设备号   */
    	struct device_node	*nd; /* 设备节点 */	
    	atomic_t keyvalue;		/* 有效的按键键值 */
    	atomic_t releasekey;	/* 标记是否完成一次完成的按键,包括按下和释放 */
    	struct timer_list timer;/* 定义一个定时器*/
    	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键init述数组 */
    	unsigned char curkeynum;				/* 当前init按键号 */
    	wait_queue_head_t r_wait;				/* 读等待队列头 */
    	struct fasync_struct *async_queue;		/* 异步相关结构体 */
    };
    

    接下来就是处理异步通知函数:

    /*
     * @description     : fasync函数,用于处理异步通知
     * @param - fd		: 文件描述符
     * @param - filp    : 要打开的设备文件(文件描述符)
     * @param - on      : 模式
     * @return          : 负数表示函数执行失败
     */
    static int imx6uirq_fasync(int fd, struct file *filp, int on)
    {
    	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
    	return fasync_helper(fd, filp, on, &dev->async_queue);
    }
    
    /*
     * @description     : release函数,应用程序调用close关闭驱动文件的时候会执行
     * @param - inode	: inode节点
     * @param - filp    : 要打开的设备文件(文件描述符)
     * @return          : 负数表示函数执行失败
     */
    static int imx6uirq_release(struct inode *inode, struct file *filp)
    {
    	return imx6uirq_fasync(-1, filp, 0);
    }
    

    设备操作函数也要将这两个函数绑定:

    /* 设备操作函数 */
    static struct file_operations imx6uirq_fops = {
    	.owner = THIS_MODULE,
    	.open = imx6uirq_open,
    	.read = imx6uirq_read,
    	.poll = imx6uirq_poll,
    	.fasync = imx6uirq_fasync,
    	.release = imx6uirq_release,
    };
    

    接下来是测试APP函数:

    #include "stdio.h"
    #include "unistd.h"
    #include "sys/types.h"
    #include "sys/stat.h"
    #include "fcntl.h"
    #include "stdlib.h"
    #include "string.h"
    #include "poll.h"
    #include "sys/select.h"
    #include "sys/time.h"
    #include "linux/ioctl.h"
    #include "signal.h"
    /***************************************************************
    Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
    文件名		: asyncnotiApp.c
    作者	  	: 左忠凯
    版本	   	: V1.0
    描述	   	: 异步通知测试APP
    其他	   	: 无
    使用方法	:./asyncnotiApp /dev/asyncnoti 打开测试App
    论坛 	   	: www.openedv.com
    日志	   	: 初版V1.0 2019/8/13 左忠凯创建
    ***************************************************************/
    
    static int fd = 0;	/* 文件描述符 */
    
    /*
     * SIGIO信号处理函数
     * @param - signum 	: 信号值
     * @return 			: 无
     */
    static void sigio_signal_func(int signum)
    {
    	int err = 0;
    	unsigned int keyvalue = 0;
    
    	err = read(fd, &keyvalue, sizeof(keyvalue));
    	if(err < 0) {
    		/* 读取错误 */
    	} else {
    		printf("sigio signal! key value=%d\r\n", keyvalue);
    	}
    }
    
    /*
     * @description		: main主程序
     * @param - argc 	: argv数组元素个数
     * @param - argv 	: 具体参数
     * @return 			: 0 成功;其他 失败
     */
    int main(int argc, char *argv[])
    {
    	int flags = 0;
    	char *filename;
    
    	if (argc != 2) {
    		printf("Error Usage!\r\n");
    		return -1;
    	}
    
    	filename = argv[1];
    	fd = open(filename, O_RDWR);
    	if (fd < 0) {
    		printf("Can't open file %s\r\n", filename);
    		return -1;
    	}
    
    	/* 设置信号SIGIO的处理函数 */
    	signal(SIGIO, sigio_signal_func);
    	
    	fcntl(fd, F_SETOWN, getpid());		/* 设置当前进程接收SIGIO信号 	*/
    	flags = fcntl(fd, F_GETFL);			/* 获取当前的进程状态 			*/
    	fcntl(fd, F_SETFL, flags | FASYNC);	/* 设置进程启用异步通知功能 	*/	
    
    	while(1) {
    		sleep(2);
    	}
    
    	close(fd);
    	return 0;
    }
    
    

    编译测试即可。

  • 相关阅读:
    C++ 32盏灯,利用进制和 与 或 进行设计
    MySQL数据库管理
    7-8 梯云纵
    PE格式:手写PE结构解析工具
    HTTP 协议的定义,工作原理,Fiddler的原理和使用,请求的内容
    golang 自动生成文件头
    【Leetcode合集】1. 两数之和
    Tableau指标排行
    【Java基础】自增自减、关系、逻辑及三元<运算符
    Web应用基础
  • 原文地址:https://blog.csdn.net/qq_44219883/article/details/139853916