Linux 设备驱动I/O分析2

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 的工作流程

  1. 提交 I/O 请求:用户进程通过异步 I/O 接口提交 I/O 请求(例如,读取数据或写入数据)。
  2. 后台执行 I/O 操作:内核会在后台处理 I/O 操作,用户进程不需要等待。
  3. 完成通知:当 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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值