找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

259

积分

0

好友

37

主题
发表于 13 小时前 | 查看: 1| 回复: 0

异步通知(或称信号驱动I/O)为Linux设备驱动提供了一种高效的通信机制。其核心在于:当设备事件(如数据到达)发生时,由驱动主动发送信号通知应用进程,从而将应用从轮询或阻塞等待中解放出来。

异步通知的工作原理

这是一种基于信号的机制,允许内核在设备事件(例如数据可用、缓冲区空闲)发生时,向用户空间进程发送一个特定的信号(通常是SIGIO)。

其核心流程分为用户空间与驱动空间两部分:

1. 用户空间的关键操作

  1. 使用fcntl()系统调用设置文件描述符的FASYNC标志,启用异步通知。
  2. 通过fcntl()F_SETOWN命令,指定接收信号的进程ID或进程组ID。
  3. 使用sigaction()等函数为SIGIO信号注册处理函数。

2. 驱动程序的核心实现

  1. 在驱动的file_operations结构中实现.fasync方法。
  2. .fasync方法中,调用内核的fasync_helper()函数来管理一个异步通知链表(struct fasync_struct *)。
  3. 当设备事件发生时(通常在中断或任务处理函数中),调用kill_fasync()函数向链表中所有已注册的进程发送SIGIO信号。

核心函数与结构

内核函数/结构 描述
fasync_helper() 负责添加或删除文件描述符到驱动的异步通知链表中。
kill_fasync() 向链表中所有注册的进程发送信号(如SIGIO),并可携带POLLIN/POLLOUT等事件标志。
struct fasync_struct 驱动内部用于管理所有异步通知请求的链表头。

总结起来,实现一个支持异步通知的字符设备驱动,主要涉及三个步骤:定义fasync_struct结构体指针,实现.fasync文件操作方法,在适当的时候调用kill_fasync()发送信号。

实战示例:一个简单的异步通知驱动

假设有一个字符设备,当它的缓冲区有新数据准备好时,需要主动通知用户空间进程。

1. 驱动程序 (async_dev.c)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/sched.h>  // for wake_up_interruptible
#include <linux/signal.h>  // for kill_fasync
#include <linux/poll.h>    // for poll/epoll/select (Good practice)
#include <asm/uaccess.h>

#define DEV_NAME "async_dev"
#define DEV_CNT   1

// 1. 定义 fasync 链表头
static struct fasync_struct *async_fasync_list = NULL;

static dev_t devno;
static struct cdev async_cdev;
static struct class *async_class;

// 缓冲区(简化,实际中可能是硬件寄存器或DMA缓冲区)
static char device_buffer[64];
static int buffer_data_size = 0;

/* --- 文件操作方法的实现 --- */

/**
 * @brief 异步通知回调函数:用于设置或清除 FASYNC 标志
 * @param inode 设备节点的 inode
 * @param filp 文件指针
 * @param mode FASYNC 标志 (0:清除, 1:设置)
 * @return 0 成功
 */
static int async_fasync(int fd, struct file *filp, int mode)
{
    // 调用内核的 fasync_helper 函数,负责维护 async_fasync_list 链表
    return fasync_helper(fd, filp, mode, &async_fasync_list);
}

static int async_open(struct inode *inode, struct file *filp)
{
    // 允许用户设置 F_SETOWN
    // 如果没有这一步,用户空间调用 fcntl(fd, F_SETOWN, pid) 会失败
    filp->private_data = &async_cdev;
    printk(KERN_INFO "Async device opened.\n");
    return 0;
}

static int async_release(struct inode *inode, struct file *filp)
{
    // 清除 fasync 标志,防止关闭后仍然发送信号
    async_fasync(-1, filp, 0);
    printk(KERN_INFO "Async device closed.\n");
    return 0;
}

static ssize_t async_read(struct file *filp, char __user *buf,
                         size_t count, loff_t *ppos)
{
    // 简化:读取操作
    if (buffer_data_size == 0)
        return -EAGAIN; // 非阻塞模式下,没有数据返回 -EAGAIN

    int len = min((int)count, buffer_data_size);
    if (copy_to_user(buf, device_buffer, len)) {
        return -EFAULT;
    }
    buffer_data_size = 0; // 假设数据被读取完
    return len;
}

// 模拟数据到达(在实际驱动中,这通常发生在中断处理函数中)
void simulate_data_arrival(void)
{
    printk(KERN_INFO "Simulating data arrival.\n");
    // 假设有新数据写入 device_buffer
    buffer_data_size = snprintf(device_buffer, sizeof(device_buffer),
                                "Hello from kernel at %ld", jiffies);
    // 3. 发送 SIGIO 信号给所有注册的进程
    if (async_fasync_list) {
        printk(KERN_INFO "Sending SIGIO to user space.\n");
        // 发送信号:SIGIO,并设置 POLLIN (表示有数据可读)
        kill_fasync(&async_fasync_list, SIGIO, POLLIN);
    }
}

// 供用户空间写入的函数(在这里触发数据到达模拟)
static ssize_t async_write(struct file *filp, const char __user *buf,
                          size_t count, loff_t *ppos)
{
    // 用户写入时,我们模拟数据到达
    simulate_data_arrival();
    return count;
}

static const struct file_operations async_fops = {
    .owner   = THIS_MODULE,
    .open    = async_open,
    .release = async_release,
    .read    = async_read,
    .write   = async_write,
    .fasync  = async_fasync, // 关键:实现 fasync 方法
};

/* --- 模块加载与卸载 --- */
static int __init async_init(void)
{
    // 1. 动态申请设备号
    if (alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME) < 0) {
        return -EFAULT;
    }
    // 2. 注册 cdev
    cdev_init(&async_cdev, &async_fops);
    async_cdev.owner = THIS_MODULE;
    cdev_add(&async_cdev, devno, DEV_CNT);
    // 3. 创建设备节点 (可选,方便测试)
    async_class = class_create(THIS_MODULE, DEV_NAME);
    device_create(async_class, NULL, devno, NULL, DEV_NAME);
    printk(KERN_INFO "%s driver installed, Major: %d\n", DEV_NAME, MAJOR(devno));
    return 0;
}

static void __exit async_exit(void)
{
    device_destroy(async_class, devno);
    class_destroy(async_class);
    cdev_del(&async_cdev);
    unregister_chrdev_region(devno, DEV_CNT);
    printk(KERN_INFO "%s driver uninstalled.\n", DEV_NAME);
}
module_init(async_init);
module_exit(async_exit);
MODULE_LICENSE("GPL");

2. 用户空间测试程序 (async_user.c) 该程序会设置SIGIO信号处理机制,并配置文件描述符以接收信号。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>

#define DEV_FILE "/dev/async_dev"

int fd;

// SIGIO 信号处理函数
void sigio_handler(int signo)
{
    char buf[128];
    ssize_t len;

    // 尝试非阻塞读取数据
    len = read(fd, buf, sizeof(buf) - 1);
    if (len > 0) {
        buf[len] = '\0';
        printf(">>> 成功读取数据 (%zd 字节): %s\n", len, buf);
    } else if (len == 0) {
        printf(">>> 读取结束 (EOF).\n");
    } else {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            printf(">>> 尝试读取,但设备再次变为空 (非阻塞模式).\n");
        } else {
            perror(">>> 读取失败");
        }
    }
}

int main()
{
    // 1. 注册 SIGIO 信号处理器
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigio_handler;
    sigaction(SIGIO, &sa, NULL);

    // 2. 打开设备文件
    fd = open(DEV_FILE, O_RDWR); // O_RDWR 是为了能触发驱动的 write 函数
    if (fd < 0) {
        perror("打开设备文件失败");
        return -1;
    }
    printf("设备文件 %s 打开成功 (fd: %d).\n", DEV_FILE, fd);

    // 3. 设置接收信号的进程ID (F_SETOWN)
    // 获取当前进程 ID
    int pid = getpid();
    if (fcntl(fd, F_SETOWN, pid) < 0) {
        perror("F_SETOWN 失败");
        goto err_close;
    }
    printf("设置接收进程 ID 为 %d.\n", pid);

    // 4. 设置异步通知标志 (FASYNC)
    int flags = fcntl(fd, F_GETFL);
    if (flags < 0) {
        perror("F_GETFL 失败");
        goto err_close;
    }
    if (fcntl(fd, F_SETFL, flags | FASYNC) < 0) {
        perror("F_SETFL FASYNC 失败");
        goto err_close;
    }
    printf("设置 FASYNC 标志成功。\n");

    // 注意:通常也需要设置 O_NONBLOCK 才能在信号处理函数中进行非阻塞读取
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK | FASYNC) < 0) {
         perror("F_SETFL O_NONBLOCK/FASYNC 失败");
         goto err_close;
    }
    printf("已设置非阻塞模式。\n");

    printf("\n程序进入等待状态,请在另一个终端向设备写入数据来触发信号:\n");
    printf("  例如: echo 'trigger' > %s\n\n", DEV_FILE);

    // 持续运行,等待信号
    while (1) {
        sleep(1);
    }
    close(fd);
    return 0;

err_close:
    close(fd);
    return -1;
}

进阶实践:集成到 globalfifo 驱动

下面展示如何为globalfifo(一个典型的字符设备驱动)增加异步通知支持。

1. 扩展设备结构体 需要在globalfifo_dev结构体中添加fasync_struct来管理异步通知链表。

struct globalfifo_dev {
    struct cdev cdev;
    unsigned int current_len; // 实际存储的数据长度
    unsigned char mem[GLOBALFIFO_SIZE]; // 环形缓冲区
    spinlock_t lock; // 保护数据结构
    wait_queue_head_t r_wait; // 读等待队列
    wait_queue_head_t w_wait; // 写等待队列
    // **异步通知核心**
    struct fasync_struct *async_queue; // 异步通知链表头
};

2. 实现 .fasync 方法 在驱动的file_operations结构中实现.fasync成员。当用户空间调用fcntl(fd, F_SETFL, O_ASYNC)时,此函数负责将进程添加到通知链表中,或在关闭文件时将其移除。

static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
    struct globalfifo_dev *dev = filp->private_data;
    // 调用内核的 fasync_helper 函数来管理链表
    // &dev->async_queue 就是我们要维护的链表头
    return fasync_helper(fd, filp, mode, &dev->async_queue);
}

3. 在文件关闭时清除标志 当文件关闭时,必须清除异步通知标志,避免向无效的进程发送信号。

static int globalfifo_release(struct inode *inode, struct file *filp)
{
    // 在释放设备时,调用 fasync_helper 清除 fasync 标志 (mode=0)
    // -1 是一个占位符,因为我们只关心清除操作
    globalfifo_fasync(-1, filp, 0);
    return 0;
}

4. 在事件发生时发送信号 当数据状态发生改变(可读或可写)时,需要调用kill_fasync()发送信号。

  • 写入后(数据可读)

    static ssize_t globalfifo_write(struct file *filp, const char __user *buf,
                               size_t count, loff_t *ppos)
    {
    struct globalfifo_dev *dev = filp->private_data;
    // ... (处理写入数据,更新 dev->current_len)
    
    // 1. 唤醒等待写入的进程 (如果有)
    wake_up_interruptible(&dev->w_wait);
    // 2. **发送异步通知 (SIGIO)**
    if (dev->async_queue) {
        // 发送 SIGIO 信号,携带 POLLIN (数据可读) 事件
        kill_fasync(&dev->async_queue, SIGIO, POLLIN);
    }
    return written_count;
    }
  • 读取后(空间可写)

    static ssize_t globalfifo_read(struct file *filp, char __user *buf,
                              size_t count, loff_t *ppos)
    {
    struct globalfifo_dev *dev = filp->private_data;
    // ... (处理读取数据,更新 dev->current_len)
    
    // 1. 唤醒等待读取的进程 (如果有)
    wake_up_interruptible(&dev->r_wait);
    // 2. **发送异步通知 (SIGIO)**
    if (dev->async_queue) {
        // 发送 SIGIO 信号,携带 POLLOUT (空间可写) 事件
        kill_fasync(&dev->async_queue, SIGIO, POLLOUT);
    }
    return read_count;
    }

5. 更新 file_operations 结构体 最后,确保设备驱动的文件操作结构体包含了.fasync成员。

static const struct file_operations globalfifo_fops = {
    .owner   = THIS_MODULE,
    // ... 其他方法 (open, read, write, llseek, ioctl, poll)
    .release = globalfifo_release,
    .fasync  = globalfifo_fasync, // 启用异步通知
};

通过以上步骤,一个功能完整的支持异步通知的字符设备驱动就实现了。它结合了等待队列与信号机制,能高效地响应设备状态变化,是Linux系统编程中提升I/O效率的重要手段之一。

您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-3 14:20 , Processed in 0.996002 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表