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

目录

异步通知

异步通知​概念​​

信号

[signal 函数](#signal 函数)

驱动中的信号处理

[fasync_struct 结构体](#fasync_struct 结构体)

[fasync 函数](#fasync 函数)

[kill_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 所支持的所有信号,这些信号如下所示:

cpp 复制代码
/* 信号宏定义列表(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 函数原型如下所示:

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

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

cpp 复制代码
typedef void (*sighandler_t)(int)

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

cpp 复制代码
#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 结构体内容如下:

cpp 复制代码
/**
 * 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 结构体指针变量,添加定义到设备结构体中:

cpp 复制代码
struct fasync_struct *async_queue; /* 异步相关结构体 */

fasync 函数

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

cpp 复制代码
int (*fasync) (int fd, struct file *filp, int on)

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

fasync_helper 函数原型如下:

cpp 复制代码
/**
 * 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

示例代码如下:

cpp 复制代码
#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 函数原型如下所示:

cpp 复制代码
void kill_fasync(struct fasync_struct **fp, int sig, int band)

函数参数和返回值含义如下:

  • fp:要操作的 fasync_struct。
  • sig: 要发送的信号。
  • band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。

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

应用程序对异步通知的处理包括以下三步:

  • 注册信号处理函数
  • 将应用程序的进程号告诉给内核
  • 开启异步通知

1、注册信号处理函数

应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来设置信号的处理函数。

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

使用如下命令将本应用程序的进程号告诉给内核:

cpp 复制代码
fcntl(fd, F_SETOWN, getpid())

3、开启异步通知

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

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

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

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

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

相关推荐
HLC++9 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室10 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy12 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕13 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫14 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生15 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1116 小时前
linux基础IO
linux·运维·服务器
BelongPanda18 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯18 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983418 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器