Linux驱动开发--异步通知与异步I/O

3、异步通知与异步I/O

3.1 Linux信号

阻塞与非阻塞访问、poll()函数提供了较好的解决设备访问的机制,但是如果有了异步通知,整套机制则更加完整了。
异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O中使用poll()意味着查询设备是否可访问,而异步通知则意味着设备通知用户自身可访问,之后用户再进行I/O处理。由此可见,这几种I/O方式可以相互补充。

图9.1呈现了阻塞I/O、结合轮询的非阻塞I/O及基于SIGIO的异步通知在时间先后顺序上的不同。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里要强调的是:阻塞、非阻塞I/O、异步通知本身没有优劣,应该根据不同的应用场景合理选择。

异步通知的核心就是信号,在 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 SIGLOS 29 */
#define SIGPWR 		30 /* 断点重启 */
#define SIGSYS 		31 /* 非法的系统调用 */
#define SIGUNUSED 	31 /* 未使用信号 *//* These should not be considered constants from userland.  */
#define SIGRTMIN	32
#define SIGRTMAX	(_NSIG-1)

除了 SIGKILL(9)和 SIGSTOP(19)这两个信号不能被忽略外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。

3.2 信号的接收–应用端

我们使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用 signal 函数来设置指定信号的处理函数, signal 函数原型如下所示:

sighandler_t signal(int signum, sighandler_t handler)-signum:要设置处理函数的信号。
-handler: 信号的处理函数。若为SIGIGN,表示忽略该信号;若为SIGDFL,表示采用系统默认方式处理信号;若为用户自定义的函数,则信号被捕获到后,该函数将被执行。
-返回值: 设置成功的话返回信号的前一个处理函数handler,设置失败的话返回 SIG_ERR。

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

typedef void (*sighandler_t)(int)

先来看一个使用信号实现异步通知的例子,它通过signal(SIGIO,input_handler)对标准输入文件描述符STDIN_FILENO启动信号机制。用户输人后,应用程序将接收到SIGIO信号其处理函数input_handler()将被调用,如代码清单 9.2所示。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100void input_handler(int num)
{char data[MAX_LEN];int len;/* 读取并输出 STDIN_FILENO 上的输入 */len = read(STDIN_FILENO, &data, MAX_LEN);data[len] = 0;printf("input available:%s\n", data);
}int main(void)
{int oflags;/* 启动信号驱动机制 */signal(SIGIO, input_handler);fcntl(STDIN_FILENO, F_SETOWN, getpid());oflags = fcntl(STDIN_FILENO, F_GETFL);fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);/* 最后进入一个死循环,仅为保持进程不终止。如果程序中没有这个死循环会立即执行完毕 */while (1);
}

上述代码 24 行为 SIGIO 信号安装 input_handler() 作为处理函数,第 25 行设置本进程为 STDIN_FILENO 文件的拥有者,没有这一步,内核不会知道应该将信号发给哪个进程。而为了启用异步通知机制,还需对设备设置 FASYNC 标志,第 26 行、27 行代码可实现此目的。
整个程序的执行效果如下:

[root@localhost driver_study1# ./signal_test
I am Chinese.
input available: I am Chinese.
-> signal_test 程序打印I love Linux driver.
input available: I love Linux driver.
-> signal_test 程序打印

从中可以看出,当用户输入一串字符串后,标准输入设备释放 SIGIO 信号,这个信号“中断”与驱使对应的应用程序中的 input_handler() 得以执行,并将用户输入显示出来。
**由此可见,为了能在用户空间中处理一个设备释放的信号,它必须完成 3 项工作。**应用程序对异步通知的处理包括以下三步:
1、注册信号处理函数
应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来设置信号的处理函数。

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

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

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

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

3.3 信号的释放–设备驱动端

在设备驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,因为信号的源头在设备驱动端。因此,应该在合适的时机让设备驱动释放信号,在设备驱动程序中增加信号释放的相关代码。

为了使设备支持异步通知机制,驱动程序中涉及3项工作。

1)支持 F_SETOWN 命令:

  • 能在这个控制命令处理中设置 filp->f_owner 为对应进程 ID。不过此项工作已由内核完成,设备驱动无须处理。

2)支持 F_SETFL 命令的处理:

  • 每当 FASYNC 标志改变时,驱动程序中的 fasync() 函数将得以执行。因此,驱动中应该实现 fasync() 函数。

3)在设备资源可获得时,调用 kill_fasync() 函数激发相应的信号。

驱动中的上述3项工作和应用程序中的3项工作是一一对应的:

在这里插入图片描述

设备驱动中异步通知编程比较简单,主要用到一项数据结构和两个函数。

数据结构是 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;   		// RCU机制使用的头结构
};

和其他的设备驱动一样,fasync_struct 结构体指针放在设备结构体中仍然是最佳选择,支持异步通知的设备结构体模板,如下所示:

struct xxx_dev {struct cdev cdev; /* cdev 结构体 */...struct fasync_struct *async_queue; /* 异步结构体指针 */
};
  • 当一个进程对某个文件调用 fcntl() 系统调用并设置 FASYNC 标志时,内核会创建一个 fasync_struct 实例,并将其加入到文件的异步通知队列中。
  • 当文件状态发生变化时(如数据可读或可写),内核会遍历这个队列----上图中的fasync_struct列表,找到相关的 fasync_struct,并通过文件描述符通知对应的进程。

引出了**“异步通知队列”**:接下来再进一步分析

在 Linux 内核中,异步通知队列(由 fasync_struct 结构体组成的链表)是通过设备驱动程序的私有数据结构来维护的(也就是前面的struct fasync_struct *async_queue; /* 异步结构体指针 */

如何维护异步通知队列

  1. 私有数据结构:设备驱动程序通常会在其私有数据结构中维护一个指向 fasync_struct 的指针。这个私有数据结构可能是 struct inode 的一部分,或者是设备驱动程序定义的其他结构体。
  2. fasync_helper 函数:当需要处理异步通知时,内核会调用 fasync_helper 函数。这个函数会检查设备驱动程序的私有数据结构中是否有指向 fasync_struct 的指针,并据此决定是否需要创建或修改异步通知队列。
  3. kill_fasync 函数:当设备驱动程序需要通知应用程序文件状态发生变化时(如数据可读或可写),它会调用 kill_fasync 函数。这个函数会遍历由 fasync_struct 结构体组成的链表,并向每个注册的进程发送信号。

两个函数分别是:

1)处理 FASYNC 标志变更的函数。

int (*fasync) (int fd, struct file *filp, int on);
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);

2)释放信号用的函数。

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

如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 fasync 函数,fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针

当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行。 在设备驱动的 fasync() 函数中,只需要简单地将该函数的3个参数以及 fasync_struct 结构体指针的指针作为第4个参数传入 fasync_helper() 函数即可。下面给出了支持异步通知的设备驱动程序 fasync() 函数的模板。

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 struct file_operations xxx_ops = {.......fasync = xxx_fasync,......
};

在设备资源可以获得时,应该调用 kill_fasync() 释放 SIGIO 信号。在可读时,第3个参数设置为 POLL_IN,在可写时,第3个参数设置为 POLL_OUT。下面给出了释放信号的范例。

static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{struct xxx_dev *dev = filp->private_data;.../* 产生异步读信号 */if (dev->async_queue)kill_fasync(&dev->async_queue, SIGIO, POLL_IN);...
}

最后,在文件关闭时,即在设备驱动的 release() 函数中,应调用设备驱动的 fasync() 函数将文件从异步通知的列表中删除。给出了支持异步通知的设备驱动 release() 函数的模板。

static int xxx_release(struct inode *inode, struct file *filp)
{/* 将文件从异步通知列表中删除 */xxx_fasync(-1, filp, 0);...return 0;
}

调用前面代码中的 xxx_fasync 函数来完成 fasync_struct 的释放工作,但是,其最终还是通过 fasync_helper 函数完成释放工作。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/77694.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大语言模型推理能力的强化学习现状理解GRPO与近期推理模型研究的新见解

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

【Linux系统】Linux基础指令(详解Linux命令行常用指令,每一个指令都有示例演示)

文章目录 一、与文件路径相关的指令0.补充知识&#xff1a;路径的认识1.pwd 指令2.cd 指令&#xff08;含家目录的介绍&#xff09; 二、创建和删除文件的指令0.补充知识&#xff1a;普通文件和目录文件1.touch 指令&#xff08;可以修改文件的时间戳&#xff09;2.mkdir 指令3…

LangChain 单智能体模式示例【纯代码】

# LangChain 单智能体模式示例import os from typing import Anyfrom langchain.agents import AgentType, initialize_agent, Tool from langchain_openai import ChatOpenAI from langchain.tools import BaseTool from langchain_experimental.tools.python.tool import Pyt…

解决:VSCode C++ conan 安装第三方库后 头文件报错

文章目录 1 头文件include路径查找报错参考 1 头文件include路径查找报错 找到conan_toolchain.cmake中 INCLUDE_PATH list(PREPEND CMAKE_INCLUDE_PATH "/Users/hanliqiang/.conan2/p/b/fmte8c4f7a755477/p/include")生成C编译配置 CtrlShiftP 中选择C Edit Confi…

松灵Cobot Magic双臂具身遥操机器人(基于ROS的定位建图与协同导航技术)

摘要 本文以CobotMagic可移动协作机器人为研究对象&#xff0c;从硬件架构设计、软件系统架构、多传感器融合定位建图系统、智能导航系统协同机制四个维度&#xff0c;深入解析机器人系统工作原理。重点研究多传感器融合定位建图系统实现原理&#xff0c;结合实测数据验证系统…

回归,git 分支开发操作命令

核心分支说明 主分支&#xff08;master/production&#xff09;存放随时可部署到生产环境的稳定代码&#xff0c;仅接受通过测试的合并请求。 开发分支&#xff08;develop&#xff09;集成所有功能开发的稳定版本&#xff0c;日常开发的基础分支&#xff0c;从该分支创建特性…

ASP.NET Core 最小 API:极简开发,高效构建(下)

在上篇文章 ASP.NET Core 最小 API&#xff1a;极简开发&#xff0c;高效构建&#xff08;上&#xff09; 中我们添加了 API 代码并且测试&#xff0c;本篇继续补充相关内容。 一、使用 MapGroup API 示例应用代码每次设置终结点时都会重复 todoitems URL 前缀。 API 通常具有…

Spring之我见 - Spring Boot Starter 自动装配原理

欢迎光临小站&#xff1a;致橡树 Spring Boot Starter 的核心设计理念是 约定优于配置&#xff0c;其核心实现基于 自动配置&#xff08;Auto-Configuration&#xff09; 和 条件化注册&#xff08;Conditional Registration&#xff09;。以下是其生效原理&#xff1a; 约定…

精益数据分析(7/126):打破创业幻想,拥抱数据驱动

精益数据分析&#xff08;7/126&#xff09;&#xff1a;打破创业幻想&#xff0c;拥抱数据驱动 在创业的道路上&#xff0c;我们都怀揣着梦想&#xff0c;但往往容易陷入自我编织的幻想中。我希望通过和大家一起学习《精益数据分析》&#xff0c;能帮助我们更清醒地认识创业过…

牛客java练习题

[toc] 1.依赖注入 依赖注入是一种设计模式和编程思想,不依赖 具体的框架实现,可以通过多种方式和框架来实现可以通过Spring , Google Guice , PicoContainer 等都可以实现依赖注入,也可以通过手动编写实现目的: 为了解耦合,将对象之间的依赖关系从代码中解耦出来, 使系统更加…

大模型应用开发自学笔记

理论学习地址&#xff1a; https://zh.d2l.ai/chapter_linear-networks/index.html autodl学术加速&#xff1a; source /etc/network_turboconda常见操作: 删除&#xff1a; conda remove --name myenv --all -y导出&#xff1a; conda env export > environment.yml…

鸿蒙ArkUI实战之TextArea组件、RichEditor组件、RichText组件、Search组件的使用

本文接上篇继续更新ArkUI中组件的使用&#xff0c;本文介绍的组件有TextArea组件、RichEditor组件、RichText组件、Search组件&#xff0c;这几个组件的使用对应特定场景&#xff0c;使用时更加需要注意根据需求去使用 TextArea组件 官方文档&#xff1a; TextArea-文本与输…

除了`String`、`StringBuffer` 和 `StringBuilder`之外,还有什么处理字符串的方法?

一、标准库中的字符串处理类 1. StringJoiner&#xff08;Java 8&#xff09; 用途&#xff1a;用于在拼接字符串时自动添加分隔符、前缀和后缀。示例&#xff1a;StringJoiner sj new StringJoiner(", ", "[", "]"); sj.add("A").…

Qt中读写结构体字节数据

在Qt中读写结构体字节数据通常涉及将结构体转换为字节数组(QByteArray)或直接从内存中读写。以下是几种常见方法&#xff1a; 方法1&#xff1a;使用QDataStream读写结构体 cpp #include <QFile> #include <QDataStream>// 定义结构体 #pragma pack(push, 1) //…

Windows 10 上安装 Spring Boot CLI详细步骤

在 Windows 10 上安装 Spring Boot CLI 可以通过以下几种方式完成。以下是详细的步骤说明&#xff1a; 1. 手动安装&#xff08;推荐&#xff09; 步骤 1&#xff1a;下载 Spring Boot CLI 访问 Spring Boot CLI 官方发布页面。下载最新版本的 .zip 文件&#xff08;例如 sp…

Unity3D仿星露谷物语开发37之浇水动画

1、目标 当点击水壶时&#xff0c;实现浇水的动画。同时有一个水从水壶中流出来的特效。 假如某个grid被浇过了&#xff0c;则不能再浇水了。。 如果某个grid没有被dug过&#xff0c;也不能被浇水。 2、优化Settings.cs脚本 增加如下内容&#xff1a; public static float…

【2】Kubernetes 架构总览

Kubernetes 架构总览 主节点与工作节点 主节点 Kubernetes 的主节点&#xff08;Master&#xff09;是组成集群控制平面的关键部分&#xff0c;负责整个集群的调度、状态管理和决策。控制平面由多个核心组件构成&#xff0c;包括&#xff1a; kube-apiserver&#xff1a;集…

如何对docker镜像存在的gosu安全漏洞进行修复——筑梦之路

这里以mysql的官方镜像为例进行说明&#xff0c;主要流程为&#xff1a; 1. 分析镜像存在的安全漏洞具体是什么 2. 根据分析结果有针对性地进行修复处理 3. 基于当前镜像进行修复安全漏洞并复核验证 # 镜像地址mysql:8.0.42 安全漏洞现状分析 dockerhub网站上获取该镜像的…

【Tauri2】026——Tauri+Webassembly

前言 不多废话 直言的说&#xff0c;笔者看到这篇文章大佬的文章 【04】Tauri 入门篇 - 集成 WebAssembly - 知乎https://zhuanlan.zhihu.com/p/533025312尝试集成一下WebAssembly&#xff0c;直接开始 正文 准备工作 新建一个项目 安装 vite的rsw插件和rsw pnpm instal…

OpenHarmony Camera开发指导(五):相机预览功能(ArkTS)

预览是在相机启动后实时显示场景画面&#xff0c;通常在拍照和录像前执行。 开发步骤 创建预览Surface 如果想在屏幕上显示预览画面&#xff0c;一般由XComponent组件为预览流提供Surface&#xff08;通过XComponent的getXcomponentSurfaceId方法获取surfaceid&#xff09;&…