当前位置: 首页 > news >正文

imx6ull-驱动开发篇31——Linux异步通知

目录

异步通知

异步通知​概念​​

信号

signal 函数

驱动中的信号处理

fasync_struct 结构体

fasync 函数

kill_fasync 函数

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


异步通知

Linux 应用程序可以通过阻塞或者非阻塞这两种方式来访问驱动设备:

  • 通过阻塞方式访问的话,应用程序会处于休眠态,等待驱动设备可以使用,
  • 非阻塞方式,会通过 poll 函数来不断的轮询,查看驱动设备文件是否可以使用。

这两种方式都需要应用程序主动地去查询设备的使用情况,这就需要用到异步通知,也就是驱动可以通过主动向应用程序发送信号

异步通知​概念​

  • 进程​​先发起I/O请求​​,内核在操作完成后​​主动通知进程​​(通过信号或回调),期间进程无需阻塞或轮询。

  • ​异步机制​​:操作结果通过回调/信号返回。

维度​

​说明​

​通知方式​

信号(SIGIO)、回调函数(aio_completion)或事件fd(eventfd

​设置函数​

fcntl(fd, F_SETOWN, pid)fcntl(fd, F_SETFL, O_ASYNC)

​优点​

资源利用率最高,适合高吞吐场景

​缺点​

编程复杂度高,需处理信号竞争

​适用场景​

高性能服务器(如Nginx)、磁盘I/O密集型应用

阻塞、非阻塞、异步通知,这三种是针对不同的场合提出来的不同的解决方法,在实际的工作和学习中,根据自己的实际需求选择合适的处理方法即可。

信号

异步通知的核心就是信号,在 arch/xtensa/include/uapi/asm/signal.h 文件中定义了 Linux 所支持的所有信号,这些信号如下所示:

/* 信号宏定义列表(Linux标准信号)*/
#define SIGHUP    1   // 终端挂起或控制进程终止
#define SIGINT    2   // 终端中断(Ctrl+C触发)
#define SIGQUIT   3   // 终端退出(Ctrl+\触发)
#define SIGILL    4   // 非法指令
#define SIGTRAP   5   // 调试断点
#define SIGABRT   6   // 进程中止(abort()触发)
#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  // 定时器信号(alarm()触发)
#define SIGTERM   15  // 进程终止(kill默认发送)
#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  // I/O就绪(异步I/O事件)
#define SIGPWR    30  // 电源故障/重启
#define SIGSYS    31  // 非法系统调用

这些信号中,除了 SIGKILL(9)和 SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以忽略。

这些信号就相当于中断号,不同的中断号代表了不同的中断,不同的中断所做的处理不同,因此,驱动程序可以通过向应用程序发送不同的信号来实现不同的功能。

signal 函数

在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用 signal 函数来设置指定信号的处理函数。

signal 函数原型如下所示:

sighandler_t signal(int signum, sighandler_t handler)
  • signum:要设置处理函数的信号。
  • handler: 信号的处理函数。
  • 返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR。

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

typedef void (*sighandler_t)(int)

使用这两个函数的示例代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>// SIGINT信号处理函数(Ctrl+C触发)
void sigint_handler(int signum) 
{printf("\nSIGINT signal received! (Signal number: %d)\n", signum);exit(EXIT_SUCCESS);  // 正常退出程序
}int main(void) 
{// 注册信号处理函数if (signal(SIGINT, sigint_handler) == SIG_ERR) {perror("Failed to register signal handler");return EXIT_FAILURE;}printf("Press Ctrl+C to trigger SIGINT...\n");// 保持程序运行while(1) {pause();  // 更推荐使用pause()替代空循环}return 0;
}

我们设置 SIGINT 信号的处理函数为 sigint_handler,当按下 CTRL+C向 signaltest 发送 SIGINT 信号以后 sigint_handler 函数就会执行。

此函数先输出一行“SIGINT signal!”字符串,然后调用 exit 函数关闭 signaltest 应用程序。

驱动中的信号处理

fasync_struct 结构体

fasync_struct 结构体内容如下:

/*** struct fasync_struct - 管理异步通知(SIGIO)的核心数据结构* @fa_lock:   自旋锁,保护结构体的并发访问* @magic:     魔数(FA_MAGIC),用于验证结构有效性* @fa_fd:     关联的文件描述符* @fa_next:   指向下一个异步通知结构的指针(形成链表)* @fa_file:   关联的file结构指针* @fa_rcu:    RCU(Read-Copy-Update)回调头,用于安全释放** 作用:将进程与设备文件关联,当设备事件发生时通过SIGIO通知进程*/
struct fasync_struct {spinlock_t fa_lock;          // 保护链表的自旋锁int magic;                   // 必须等于FA_MAGIC(0x4601)int fa_fd;                   // 用户空间的文件描述符struct fasync_struct *fa_next; // 形成单链表struct file *fa_file;        // 关联的file结构struct rcu_head fa_rcu;      // RCU释放机制
};

我们需要在驱动程序中,定义一个 fasync_struct 结构体指针变量,添加定义到设备结构体中:

struct fasync_struct *async_queue; /* 异步相关结构体 */

fasync 函数

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

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

fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针。

fasync_helper 函数原型如下:

/*** fasync_helper - 管理异步通知(SIGIO)的注册/注销* @fd:    用户空间文件描述符(实际未使用)* @filp:  关联的file结构指针* @on:    激活标志(1=注册,0=注销)* @fapp:  指向驱动中fasync_struct指针的指针** 返回值:*   ≥0: 成功(返回0表示无变化)*   <0: 错误码(如内存不足)** 作用:维护驱动中的异步通知链表,当设备事件发生时通过kill_fasync()发送SIGIO信号*/
int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp);

第四个参数就是要初始化的 fasync_struct 结构体指针变量。

当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行。

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

示例代码如下:

#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/sched/signal.h>MODULE_LICENSE("GPL");// 设备私有数据结构
struct my_device {struct fasync_struct *fasync_queue;  // 异步通知队列头// 其他设备字段...
};// fasync函数实现
static int my_fasync(int fd, struct file *filp, int on)
{struct my_device *dev = filp->private_data;int ret;// 调用fasync_helper管理异步通知队列ret = fasync_helper(fd, filp, on, &dev->fasync_queue);if (ret < 0)return ret;printk(KERN_INFO "fasync %s for fd %d\n", on ? "added" : "removed", fd);return 0;
}// release函数实现
static int my_release(struct inode *inode, struct file *filp)
{// 强制移除所有异步通知监听my_fasync(-1, filp, 0);printk(KERN_INFO "Device released, fasync queue cleared\n");return 0;
}// 中断处理函数(示例)
static irqreturn_t data_irq(int irq, void *dev_id)
{struct my_device *dev = dev_id;// 当数据就绪时通知监听进程if (dev->fasync_queue) {kill_fasync(&dev->fasync_queue, SIGIO, POLL_IN);printk(KERN_INFO "Sent SIGIO to listeners\n");}return IRQ_HANDLED;
}// file_operations结构体
static const struct file_operations my_fops = {.owner =    THIS_MODULE,.fasync =   my_fasync,   // 异步通知方法.release =  my_release,  // 必须清理fasync队列// 其他操作...
};// 设备初始化(示例)
static int __init my_init(void)
{struct my_device *dev;dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (!dev)return -ENOMEM;// 注册设备等操作...return 0;
}module_init(my_init);

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 函数就会执行。

用户空间配置流程大概如图:

下一讲实验,我们编写相应的驱动和测试代码。

http://www.xdnf.cn/news/1335223.html

相关文章:

  • 极其简单二叉树遍历JAVA版本
  • 虚拟机部署HDFS集群
  • Redisson最新版本(3.50.0左右)启动时提示Netty的某些类找不到
  • VR交通安全学习机-VR交通普法体验馆方案
  • 从防抖节流到链表树:编程世界中的抽象优化艺术
  • C++智能指针详解:告别内存泄漏,拥抱安全高效
  • Flask高效数据库操作指南
  • C++ MFC/BCG编程:文件对话框(CFileDialog、CFolderPickerDialog)
  • CFBench评测
  • (一)关于步进电机的FOC控制
  • DeepSeek大模型如何重塑AI Agent?从技术突破到行业落地
  • 意象框架:连接感知与认知的统一信息结构分析——基于上古汉语同源词意义系统的词源学与认知语言学探索
  • (认识异常)
  • SED项目复现学习实录
  • JSON::Value 功能详解:从三目运算符到高级用法
  • Git Commit 提交信息标准格式
  • 48 C++ STL模板库17-容器9-关联容器-映射(map)多重映射(multimap)
  • C++进阶-----C++11
  • 【数据结构】线性表——顺序表
  • Linux Shell 常用操作与脚本示例详解
  • CAMEL-Task1-CAMEL环境配置及你的第一个Agent
  • rsync + inotify 数据实时同步
  • 吴恩达 Machine Learning(Class 3)
  • Spring Boot 实战:从项目搭建到部署优化
  • (Python)[特殊字符] 基于Flask/FastAPI的RESTful API服务 + 数据库 + 缓存 + 简单前端 (Python项目)
  • Altium Designer 22使用笔记(8)---PCB电气约束设置
  • PyTorch API 3 - distributed
  • Flink双流join
  • 三极管单电源供电中电阻关系的理解
  • Non-stationary Diffusion For Probabilistic Time Series Forecasting论文阅读笔记