当前位置: 首页 > 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.dtcms.com/a/341206.html

相关文章:

  • 玩转QEMU硬件模拟器 - Raspberry Pi OS驱动开发
  • 【项目复盘】【四轴飞行器设计】驱动开发部分
  • Redis 安装教程
  • 【数据结构之二叉树】
  • 【openssl】openssl CA.pl 签发证书操作步骤
  • redis执行lua脚本的原子性和数据库原子性的区别
  • [激光原理与应用-315]:光学设计 - SolidWorks, 光机系统设计的神器,打通光学与机械设计的闭环
  • Tomcat部署与HTTP协议详解
  • 佳维视工业一体机在公共交通系统配套中的应用
  • 疯狂星期四文案网第45天运营日记
  • LTspice仿真电路:(三十五)LED恒流驱动仿真(LT3497)
  • burpsuite+captcha-killer插件识别图片验证码进行爆破
  • AiPy 文档自动化处理实践:从 docx 到结构化 db 的高效转换方案
  • 华为仓颉语言的class(类)初步
  • ES Modules +案例分析
  • 【C++】动态导入Windows系统API的简单方法
  • Docker复杂安装--最详细的MySQL主从复制与Redis集群安装、主从复制、主从扩容主从缩容实战版
  • 03-dockerfile
  • 8月7日国赛全真模拟!2025“华数杯”数学建模竞赛,常用模型及算法总结
  • 网络连接的核心机制
  • Python 数据可视化:Matplotlib 与 Seaborn 实战
  • [TryHackMe](知识学习)Hacking with PowerShell
  • 浅显易懂——Redis、SpringDataRedis
  • 充值系统开源版,支持对接码支付,支持三级分销
  • 深入解析 Containerd 的工作原理
  • K8S-Ingress资源对象
  • 【C2000常见问题】当板子处于强电噪声环境下,或带重载时C2000芯片的PWM发波会出现异常,导致炸管。
  • StarRocks学习4-查询优化与性能调优
  • 使用 FastAPI 的 WebSockets 和 Elasticsearch 来构建实时应用
  • 永磁同步电机谐波抑制算法(13)——传统预测控制与传统谐波抑制的碰撞