Linux 设备驱动I/O分析2(基于Linux6.6)---异步通知和异步I/O分析
一、异步通知的概念和作用
1. 异步通知 (Asynchronous Notification)
概念:
异步通知指的是设备或内核通过某种机制,向用户空间的进程发送信号或事件,告知其某些操作已完成或状态已发生变化。与阻塞操作不同,异步通知不要求进程等待操作完成,它允许进程在其他任务之间继续执行,直到设备或内核完成相关的操作。
工作原理:
在异步通知中,设备驱动不会让用户进程等待设备的操作完成,而是通过某些机制在设备准备好时通知进程。这些机制通常包括:
- 信号 (
signals
): 设备通过内核发送信号(如SIGIO
)给进程,提示设备状态变化。 - 等待队列 (
wait queues
): 驱动可以将进程挂起在等待队列中,直到设备准备好时唤醒进程。 - 回调函数 (
callback
): 驱动可以注册回调函数,当事件发生时,内核自动调用回调函数进行处理。
应用场景:
- 设备在后台处理数据并在处理完成后通知用户进程。例如,串口设备或网卡设备通常会在数据接收完成后通知进程。
- 异步通知常用于用户空间应用希望接收硬件事件的场景,如磁盘 I/O 操作完成、网络数据包接收等。
实现示例:
假设一个设备驱动需要在设备完成某项工作后通知用户进程,可以使用 poll()
或 select()
等系统调用机制来实现异步通知:
static unsigned int my_device_poll(struct file *file, struct poll_table_struct *pts)
{
poll_wait(file, &my_device_wait_queue, pts); // 进程等待队列,直到设备准备好
if (device_ready()) {
return POLLIN | POLLRDNORM; // 数据已准备好,返回可读事件
}
return 0; // 没有事件发生
}
2. 异步 I/O (Asynchronous I/O)
概念:
异步 I/O 是指进程发起 I/O 请求后,不需要阻塞等待操作完成,而是可以继续执行其他任务。当 I/O 操作完成时,内核会通知进程结果,通常是通过回调函数或信号机制。异步 I/O 的核心特征是进程不会被阻塞,I/O 操作在后台进行,完成后通知进程。
工作原理:
异步 I/O 操作与常规的同步 I/O 操作的最大区别在于,进程不需要等待 I/O 完成,而是通过以下方式获得通知:
- 内核回调:当异步 I/O 操作完成时,内核会调用注册的回调函数,通知进程。
- 信号机制:进程可以通过信号收到 I/O 操作完成的通知。
aio
接口:Linux 提供了专门的异步 I/O 接口,如libaio
库,通过io_submit()
等函数,应用程序可以发起异步 I/O 操作并获取通知。
应用场景:
- 高并发 I/O 操作:当需要处理大量并发的 I/O 请求时,异步 I/O 可以显著提高效率,因为进程不需要在等待 I/O 操作完成时阻塞。
- 长时间运行的 I/O 操作:如大文件的读写、远程服务器的网络 I/O 等,这些操作可能会非常耗时,异步 I/O 可以避免进程一直被阻塞,导致 CPU 资源浪费。
异步 I/O 的工作流程:
- 提交 I/O 请求:用户进程通过异步 I/O 接口提交 I/O 请求(例如,读取数据或写入数据)。
- 后台执行 I/O 操作:内核会在后台处理 I/O 操作,用户进程不需要等待。
- 完成通知:当 I/O 操作完成时,内核通过回调或信号机制通知进程。
实现示例:
假设一个设备驱动使用 aio
实现异步 I/O,进程发起异步读取请求,并在 I/O 完成后进行回调:
static int my_device_aio_read(struct kiocb *kiocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos)
{
// 启动异步 I/O 操作,假设设备数据准备好
if (device_ready()) {
copy_to_user(iov->iov_base, device_buffer, nr_segs); // 将数据传递给用户空间
kiocb->ki_complete(kiocb, 0); // 调用回调函数通知完成
} else {
return -EAGAIN; // 如果设备未准备好,返回错误
}
return 0;
}
static void my_aio_complete(struct kiocb *kiocb, long res)
{
// 异步 I/O 操作完成后通知用户空间
printk(KERN_INFO "Asynchronous I/O operation completed\n");
}
3. 异步通知与异步 I/O 的比较
特性 | 异步通知 | 异步 I/O |
---|---|---|
核心思想 | 通知进程某个事件发生或某项操作完成,进程可以继续执行其他任务 | 进程发起 I/O 操作后立即返回,等待操作完成时不会阻塞进程 |
工作方式 | 设备或内核通过信号、回调等机制通知进程 | 进程发起 I/O 操作后不等待,后台进行 I/O 操作并通知进程 |
进程行为 | 进程不会被挂起,等待事件发生 | 进程可以继续执行其他任务,I/O 操作在后台进行 |
通知方式 | 信号、回调函数、等待队列等 | 回调函数、信号或 aio 接口 |
典型应用场景 | 设备状态变化、硬件事件通知等 | 高并发 I/O 操作、大文件读写、网络通信等 |
优缺点 | 优点:实现简单;缺点:不能直接优化 I/O 性能 |
优点:提高并发、减少等待;缺点:实现复杂 |
二、linux异步通知编程
2.1、linux信号
作用:linux系统中,异步通知使用信号来实现
函数原型为:
void (*signal(int signum,void (*handler))(int)))(int)
原型比较难理解可以分解为
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
2.2、信号的处理函数
signal()函数
//启动信号机制
void sigterm_handler(int sigo)
{
char data[MAX_LEN];
int len;
len = read(STDIN_FILENO,&data,MAX_LEN);
data[len] = 0;
printf("Input available:%s\n",data);
exit(0);
}
int main(void)
{
int oflags;
//启动信号驱动机制
signal(SIGIO,sigterm_handler);
fcntl(STDIN_FILENO,F_SETOWN,getpid());
oflags = fcntl(STDIN_FILENO,F_GETFL);
fctcl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
//建立一个死循环,防止程序结束
whlie(1);
return 0;
}
2.3、信号的释放 (在设备驱动端释放信号)
为了是设备支持异步通知机制,驱动程序中涉及以下3项工作
(1)、支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应的进程ID。不过此项工作已由内核完成,设备驱动无须处理。
(2)、支持F_SETFL命令处理,每当FASYNC标志改变时,驱动函数中的fasync()函数得以执行。因此,驱动中应该实现fasync()函数
(3)、在设备资源中可获得,调用kill_fasync()函数激发相应的信号
设备驱动中异步通知编程比较简单,主要用到一项数据结构和两个函数。这个数据结构是fasync_struct 结构体,两个函数分别是:
a -- 处理FASYNC标志变更
int fasync_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);
b -- 释放信号用的函数
void kill_fasync(struct fasync_struct **fa,int sig,int band);
和其他结构体指针放到设备结构体中,模板如下
struct xxx_dev{
struct cdev cdev;
...
struct fasync_struct *async_queue;
//异步结构体指针
};
在设备驱动中的fasync()函数中,只需简单地将该函数的3个参数以及fasync_struct结构体指针的指针作为第四个参数传入fasync_helper()函数就可以了,模板如下
static int xxx_fasync(int fd,struct file *filp, int mode)
{
struct xxx_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
在设备资源可获得时应该调用kill_fasync()函数释放SIGIO信号,可读时第三个参数为POLL_IN,可写时第三个参数为POLL_OUT,模板如下
static ssize_t xxx_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
struct xxx_dev *dev = filp->private_data;
...
if(dev->async_queue)
kill_fasync(&dev->async_queue,GIGIO,POLL_IN);
...
}
最后在文件关闭时,要将文件从异步通知列表中删除
int xxx_release(struct inode *inode,struct file *filp)
{
xxx_fasync(-1,filp,0);
...
return 0;
}
3、下面是个实例:
hello.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/fcntl.h>
static int major = 250;
static int minor=0;
static dev_t devno;
static struct class *cls;
static struct device *test_device;
static char temp[64]={0};
static struct fasync_struct *fasync;
static int hello_open (struct inode *inode, struct file *filep)
{
return 0;
}
static int hello_release(struct inode *inode, struct file *filep)
{
return 0;
}
static ssize_t hello_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)
{
if(len>64)
{
len =64;
}
if(copy_to_user(buf,temp,len))
{
return -EFAULT;
}
return len;
}
static ssize_t hello_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos)
{
if(len>64)
{
len = 64;
}
if(copy_from_user(temp,buf,len))
{
return -EFAULT;
}
printk("write %s\n",temp);
kill_fasync(&fasync, SIGIO, POLL_IN);
return len;
}
static int hello_fasync (int fd, struct file * file, int on)
{
return fasync_helper(fd, file, on, &fasync);
}
static struct file_operations hello_ops=
{
.open = hello_open,
.release = hello_release,
.read =hello_read,
.write=hello_write,
};
static int hello_init(void)
{
int ret;
devno = MKDEV(major,minor);
ret = register_chrdev(major,"hello",&hello_ops);
cls = class_create(THIS_MODULE, "myclass");
if(IS_ERR(cls))
{
unregister_chrdev(major,"hello");
return -EBUSY;
}
test_device = device_create(cls,NULL,devno,NULL,"hello");//mknod /dev/hello
if(IS_ERR(test_device))
{
class_destroy(cls);
unregister_chrdev(major,"hello");
return -EBUSY;
}
return 0;
}
static void hello_exit(void)
{
device_destroy(cls,devno);
class_destroy(cls);
unregister_chrdev(major,"hello");
printk("hello_exit \n");
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);
test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
static int fd,len;
static char buf[64]={0};
void func(int signo)
{
printf("signo %d \n",signo);
read(fd,buf,64);
printf("buf=%s \n",buf);
}
main()
{
int flage,i=0;
fd = open("/dev/hello",O_RDWR);
if(fd<0)
{
perror("open fail \n");
return ;
}
fcntl(fd,F_SETOWN,getpid());
flage = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flage|FASYNC);
signal(SIGIO,func);
while(1)
{
sleep(1);
printf("%d\n",i++);
}
close(fd);
}