提交异步 IO 操作

提交异步 IO 操作是通过 io_submit 函数完成的,io_submit 需要提供一个类型为 iocb 结构的数组,表示要进行的异步 IO 操作相关的信息,我们先来看看 iocb 结构的定义:

struct iocb {__u64   aio_data;       // 用户自定义数据, 可用于标识IO操作或者设置回调函数__u16   aio_lio_opcode; // IO操作类型, 如读(IOCB_CMD_PREAD)或者写(IOCB_CMD_PWRITE)操作__s16   aio_reqprio;__u32   aio_fildes;     // 进行IO操作的文件句柄__u64   aio_buf;        // 进行IO操作的缓冲区(如写操作的话就是写到文件的数据)__u64   aio_nbytes;     // 缓冲区的大小__s64   aio_offset;     // IO操作的文件偏移量
};

io_submit 函数最终会调用内核函数 sys_io_submit 来实现提供异步 IO 操作,我们来分析 sys_io_submit 函数的实现:

asmlinkage long
sys_io_submit(aio_context_t ctx_id, long nr, struct iocb __user **iocbpp)
{struct kioctx *ctx;long ret = 0;int i;ctx = lookup_ioctx(ctx_id); // 通过异步IO上下文标识符获取异步IO上下文对象for (i = 0; i < nr; i++) {struct iocb __user *user_iocb;struct iocb tmp;
​if (unlikely(__get_user(user_iocb, iocbpp+i))) {ret = -EFAULT;break;}
​// 从用户空间复制异步IO操作到内核空间if (unlikely(copy_from_user(&tmp, user_iocb, sizeof(tmp)))) {ret = -EFAULT;break;}
​// 调用 io_submit_one 函数提交异步IO操作ret = io_submit_one(ctx, user_iocb, &tmp);if (ret)break;}
​put_ioctx(ctx);return i ? i : ret;
}

sys_io_submit 函数的实现比较简单,主要从用户空间复制异步 IO 操作信息到内核空间,然后调用 io_submit_one 函数提交异步 IO 操作。我们重点分析 io_submit_one 函数的实现:

int io_submit_one(struct kioctx *ctx, struct iocb __user *user_iocb,struct iocb *iocb)
{struct kiocb *req;struct file *file;ssize_t ret;char *buf;file = fget(iocb->aio_fildes);      // 通过文件句柄获取文件对象req = aio_get_req(ctx);             // 获取一个异步IO操作对象req->ki_filp = file;                // 要进行异步IO的文件对象req->ki_user_obj = user_iocb;       // 指向用户空间的iocb对象req->ki_user_data = iocb->aio_data; // 设置用户自定义数据req->ki_pos = iocb->aio_offset;     // 设置异步IO操作的文件偏移量
​buf = (char *)(unsigned long)iocb->aio_buf; // 要进行异步IO操作的数据缓冲区
​// 根据不同的异步IO操作类型来进行不同的处理switch (iocb->aio_lio_opcode) {case IOCB_CMD_PREAD: // 异步读操作ret = -EINVAL;// 发起异步IO操作, 会根据不同的文件系统调用不同的函数:// 如ext3文件系统会调用 generic_file_aio_read 函数if (file->f_op->aio_read)ret = file->f_op->aio_read(req, buf, iocb->aio_nbytes, req->ki_pos);break;}// 异步IO操作或许会在调用 aio_read 时已经完成, 或者会被添加到IO请求队列中。// 所以, 如果异步IO操作被提交到IO请求队列中, 直接返回if (likely(-EIOCBQUEUED == ret)) return 0;
​aio_complete(req, ret, 0); // 如果IO操作已经完成, 调用 aio_complete 函数完成收尾工作return 0;
}

上面代码已经对 io_submit_one 函数进行了详细的注释,这里总结一下 io_submit_one 函数主要完成的工作:

  • 通过调用 fget 函数获取文件句柄对应的文件对象。

  • 调用 aio_get_req 函数获取一个类型为 kiocb 结构的异步 IO 操作对象,这个结构前面已经分析过。另外,aio_get_req 函数还会把异步 IO 操作对象添加到异步 IO 上下文的 active_reqs 队列中。

  • 根据不同的异步 IO 操作类型来进行不同的处理,如 异步读操作 会调用文件对象的 aio_read 方法来进行处理。不同的文件系统,其 aio_read 方法的实现不一样,如 Ext3 文件系统的 aio_read 方法会指向 generic_file_aio_read 函数。

  • 如果异步 IO 操作被添加到内核的 IO 请求队列中,那么就直接返回。否则就代表 IO 操作已经完成,那么就调用 aio_complete 函数完成收尾工作。

io_submit_one 函数的操作过程如 图5 所示:

所以,io_submit_one 函数的主要任务就是向内核提交 IO 请求。

异步 IO 操作完成

当异步 IO 操作完成后,内核会调用 aio_complete 函数来把处理结果放进异步 IO 上下文的环形缓冲区 ring_info 中,我们来分析一下 aio_complete 函数的实现:

int aio_complete(struct kiocb *iocb, long res, long res2)
{struct kioctx *ctx = iocb->ki_ctx;struct aio_ring_info *info;struct aio_ring *ring;struct io_event *event;unsigned long flags;unsigned long tail;int ret;info = &ctx->ring_info; // 环形缓冲区对象
​spin_lock_irqsave(&ctx->ctx_lock, flags);         // 对异步IO上下文进行上锁ring = kmap_atomic(info->ring_pages[0], KM_IRQ1); // 对内存页进行虚拟内存地址映射
​tail = info->tail;                           // 环形缓冲区下一个空闲的位置event = aio_ring_event(info, tail, KM_IRQ0); // 从环形缓冲区获取空闲的位置保存结果tail = (tail + 1) % info->nr;                // 更新下一个空闲的位置
​// 保存异步IO结果到环形缓冲区中event->obj = (u64)(unsigned long)iocb->ki_user_obj;event->data = iocb->ki_user_data;event->res = res;event->res2 = res2;info->tail = tail;ring->tail = tail; // 更新环形缓冲区下一个空闲的位置
​put_aio_ring_event(event, KM_IRQ0); // 解除虚拟内存地址映射kunmap_atomic(ring, KM_IRQ1);       // 解除虚拟内存地址映射
​// 释放异步IO对象ret = __aio_put_req(ctx, iocb);spin_unlock_irqrestore(&ctx->ctx_lock, flags);return ret;
}

aio_complete 函数的 iocb 参数是我们通过调用 io_submit_once 函数提交的异步 IO 对象,而参数 resres2 是用内核进行 IO 操作完成后返回的结果。

aio_complete 函数的主要工作如下:

  • 根据环形缓冲区的 tail 指针获取一个空闲的 io_event 对象来保存 IO 操作的结果。

  • 对环形缓冲区的 tail 指针进行加一操作,指向下一个空闲的位置。

当把异步 IO 操作的结果保存到环形缓冲区后,用户层就可以通过调用 io_getevents 函数来读取 IO 操作的结果,io_getevents 函数最终会调用 sys_io_getevents 函数。

我们来分析 sys_io_getevents 函数的实现:

asmlinkage long sys_io_getevents(aio_context_t ctx_id,long min_nr,long nr,struct io_event *events,struct timespec *timeout)
{struct kioctx *ioctx = lookup_ioctx(ctx_id);long ret = -EINVAL;if (likely(NULL != ioctx)) {// 调用 read_events 函数读取IO操作的结果ret = read_events(ioctx, min_nr, nr, events, timeout);put_ioctx(ioctx);}return ret;
}

从上面的代码可以看出,sys_io_getevents 函数主要调用 read_events 函数来读取异步 IO 操作的结果,我们接着分析 read_events 函数:

static int read_events(struct kioctx *ctx,long min_nr, long nr,struct io_event *event,struct timespec *timeout)
{long start_jiffies = jiffies;struct task_struct *tsk = current;DECLARE_WAITQUEUE(wait, tsk);int ret;int i = 0;struct io_event ent;struct timeout to;
​memset(&ent, 0, sizeof(ent));ret = 0;
​while (likely(i < nr)) {ret = aio_read_evt(ctx, &ent); // 从环形缓冲区中读取一个IO处理结果if (unlikely(ret <= 0))        // 如果环形缓冲区没有IO处理结果, 退出循环break;
​ret = -EFAULT;// 把IO处理结果复制到用户空间if (unlikely(copy_to_user(event, &ent, sizeof(ent)))) {break;}
​ret = 0;event++;i++;}
​if (min_nr <= i)return i;if (ret)return ret;
}

read_events 函数主要还是调用 aio_read_evt 函数来从环形缓冲区中读取异步 IO 操作的结果,如果读取成功,就把结果复制到用户空间中。

aio_read_evt 函数是从环形缓冲区中读取异步 IO 操作的结果,其实现如下:

static int aio_read_evt(struct kioctx *ioctx, struct io_event *ent)
{struct aio_ring_info *info = &ioctx->ring_info;struct aio_ring *ring;unsigned long head;int ret = 0;
​ring = kmap_atomic(info->ring_pages[0], KM_USER0);
​// 如果环形缓冲区的head指针与tail指针相等, 代表环形缓冲区为空, 所以直接返回if (ring->head == ring->tail) goto out;
​spin_lock(&info->ring_lock);
​head = ring->head % info->nr;if (head != ring->tail) {// 根据环形缓冲区的head指针从环形缓冲区中读取结果struct io_event *evp = aio_ring_event(info, head, KM_USER1);
​*ent = *evp;                  // 将结果保存到ent参数中head = (head + 1) % info->nr; // 移动环形缓冲区的head指针到下一个位置ring->head = head;            // 保存环形缓冲区的head指针ret = 1;put_aio_ring_event(evp, KM_USER1);}
​spin_unlock(&info->ring_lock);
​
out:kunmap_atomic(ring, KM_USER0);return ret;
}

aio_read_evt 函数的主要工作就是判断环形缓冲区是否为空,如果不为空就从环形缓冲区中读取异步 IO 操作的结果,并且保存到参数 ent 中,并且移动环形缓冲区的 head 指针到下一个位置。

2021SC@SDUSC Linux内核—原生异步I/O(2)相关推荐

  1. 操作系统与存储:解析Linux内核全新异步IO引擎io_uring设计与实现

    作者:draculaqian,腾讯后台开发工程师 引言 存储场景中,我们对性能的要求非常高.在存储引擎底层的IO技术选型时,可能会有如下讨论关于IO的讨论. http://davmac.org/dav ...

  2. linux内核分析--异步io(二)

    该分析sys_io_submit函数了,这个函数有点复杂,但是条理很清晰,先说一句就是提交异步io,具体怎么提交呢?我们知道,对于异步io,一次性可以提交多个请求,那么可以想象的就是在sys_io_s ...

  3. linux内核分析--异步io(一)

    linux2.6的内核增加了异步io,这个改动可以体现内核架构的重要性,连同epoll的内核实现,提升了io性能.碰巧的是,这两个特性都源自于同 一个本源,那就是睡眠队列的唤醒函数中增加了回调函数,这 ...

  4. 开箱即用!Linux 内核首个原生支持,让你的容器体验飞起来!

    容器化是最近几年 DevOps 界流行的趋势,通过业务的容器化我们将创建一个完全打包.自包含的计算环境,让软件开发人员能够更加快速地创建和部署自己的应用程序.然而长期以来,由于镜像格式的限制,容器启动 ...

  5. 开箱即用!Linux 内核首个原生支持,让你的容器体验飞起来!| 龙蜥技术

    作者:高性能存储 SIG 容器化是最近几年 devops 界流行的趋势,通过业务的容器化我们将创建一个完全打包.自包含的计算环境,让软件开发人员能够更加快速地创建和部署自己的应用程序.然而长期以来,由 ...

  6. Linux 原生异步 IO 原理与使用

    目录 什么是异步 IO? Linux 原生 AIO 原理 Linux 原生 AIO 使用 什么是异步 IO? 异步 IO:当应用程序发起一个 IO 操作后,调用者不能立刻得到结果,而是在内核完成 IO ...

  7. linux 内核io操作,关于Linux内核中的异步IO的使用

    我们都知道异步IO的作用,就是可以提高我们程序的并发能力,尤其在网络模型中.在linux中有aio的一系列异步IO的函数接口,但是这类函数都是glibc库中的函数,是基于多线程实现,不是真正的异步IO ...

  8. Linux内核中断引入用户空间(异步通知机制)【转】

    转自:http://blog.csdn.net/kingdragonfly120/article/details/10858647 版权声明:本文为博主原创文章,未经博主允许不得转载. 当Linux内 ...

  9. Linux内核开发之异步通知与异步I/O《来自linux设备开发详解》

    阻塞I/O意味着一直等待设备可访问再访问,非阻塞I/O意味着使用poll()来查询是否可访问,而异步通知则意味着设备通知应用程序自身可访问.(异步通知:很简单,一旦设备准备好,就主动通知应用程序,这种 ...

最新文章

  1. Linux(CentOS 7_x64位)系统下安装RDkit(修正)
  2. java构造方法基础_Java 基础:构造方法
  3. SpringCloud学习成长之 十一 Docker部署cloud项目
  4. java imageview的使用_Android使用控件ImageView加载图片的方法
  5. Spring IOC容器组件注入的几种方式
  6. 信息学奥赛一本通C++语言——1031:反向输出一个三位数
  7. R语言爬虫系列(1)XML抓取表格数据
  8. 中根遍历二叉查找树所得序列一定是有序序列_学习数据结构--第六章:查找(查找)
  9. 【重点:DP 双指针 栈】LeetCode 42. Trapping Rain Water
  10. Java面试题-集合框架篇三
  11. 电脑管理器地址栏 按右键会有的功能
  12. 什么是PXE及PXE启动
  13. “黑暗潜伏者” -- 手机病毒新型攻击方式
  14. Delphi控件安装方法
  15. Win10开机时怎么跳过磁盘检查?
  16. linux中将文本中的单词换掉的指令_干货:Linux常用命令全称及讲解
  17. Firefox插件开发-入门篇
  18. 项目经理常用软件大全
  19. cad抛物线曲线lisp_CAD能画抛物线吗?
  20. c语言程序编译与烧录,一种解释自定义脚本并烧录的方法与流程

热门文章

  1. springmvc页面跳转错误404/找不到页面/拒绝访问
  2. XV6 RISCV源码阅读报告之 锁
  3. SaaS-HRM中的认证授权
  4. php unix时间戳 秒,UNIX时间戳怎么在php项目中使用
  5. 拿到阿里,网易游戏,腾讯,smartx的offer的过程
  6. 工商总局抽检电商 天猫1号店等仍存售假
  7. rx 6700xt性能相当于什么水平 rx 6700XT参数
  8. 微信小程序通过云函数生成带参数的小程序码
  9. 分析网站数据的好处有哪些?
  10. 提升研发效率的基本工作原则