在Linux内核4.18版本中新添加了一种新的内核轮询接口Linux AIO 方法IOCB_CMD_POLL。该补丁由Christoph Hellwig,还提议将Linux AIO接口配合网络套接字等一起使用。

Linux的AIO是最初是设计用于磁盘异步IO的接口。文件与网络套接字是大相径庭的东西,可以用Linux AIO 接口,将其统一起来呢?

在本文中,我们介绍如何使用Linux AIO API的优势来编写更好,更快的网络服务器。

Linux AIO简介

我们先来介绍一下Linux AIO。

Linux AIO用于将异步磁盘IO暴露给用户空间。在Linux上所有磁盘操作都是阻塞的。无论是open(),read(),write()还是fsync(),如果所需要的数据和元数据还没有在磁盘缓存准备好,则线程就会挂住。通常这不是问题。如果做少量的IO操作或者内存足够大,则磁盘syscall会逐渐填充高速缓存,这样整体来说速度还是会很快。

但是对于IO繁重的工作负载(例如数据库或缓存Web代理),IO操作性能会下降很多。在这类应用中,很可能由于一些read()系统调用等待磁盘导致卡顿那是致命的。

要解决此类问题,通常变通使用如下方法:

使用线程池并将卸载的系统调用到工作线程。这就是glibc POSIX AIO(不要与Linux AIO混淆)包装器的作用。

posix_fadvise使用预热磁盘缓存,并希望达到最佳效果。

将Linux AIO与XFS文件系统一起使用,使用O_DIRECT打开文件,并避免出现未说明的陷阱。

这些变通的方法都不是完美的。

即使不小心使用Linux AIO,也可能会阻塞io_submit()调用。

Linux异步I/O(AIO)自从产生以来争议就很多,大多数人希望的至少是异步的,实际上也没有实现。但是由于种种原因,AIO操作可能会在内核中阻塞,从而使AIO在调用线程确实无法承受的情况下难以使用。

最简单的AIO示例

要使用Linux AIO,首先需要定所需的系统调用。glibc不提供包装函数。要使用Linux AIO,需要:

首先调用io_setup()以设置aio_context数据结构。内核提供了一个不透明的指针。

然后调用io_submit()提交一个"I/O控制块"向量结构体 iocb进行处理。

最后,调用io_getevents() 块并等待一个向量结构体 io_event-iocb的完成通知。

一个iocb中可以提交8个命令。4.18内核中引入了两个读取,两个写入,两个fsync变体和一个POLL命令:

IOCB_CMD_PREAD = 0,

IOCB_CMD_PWRITE = 1,

IOCB_CMD_FSYNC = 2,

IOCB_CMD_FDSYNC = 3,

IOCB_CMD_POLL = 5, /* 4.18 */

IOCB_CMD_NOOP = 6,

IOCB_CMD_PREADV = 7,

IOCB_CMD_PWRITEV = 8,

在iocb结构体传递到io_submit,调整为磁盘IO。这是一个简化的版本:

struct iocb {

__u64 data; /* 用户数据 */

...

__u16 aio_lio_opcode; /* IOCB_CMD_ */

...

__u32 aio_fildes; /* 文件描述符 */

__u64 aio_buf; /* 缓冲指针 */

__u64 aio_nbytes; /* 缓冲大小*/

...

}

从io_getevents以下位置检索完成通知:

struct io_event {

__u64 data; /* 用户数据 */

__u64 obj; /* iocb请求指针 */

__s64 res; /* 事件结果码*/

__s64 res2; /* 第二结果*/

};

让我们举一个简单的例子,使用Linux AIO API读取/etc/passwd文件:

fd = open("/etc/passwd", O_RDONLY);

aio_context_t ctx = 0;

r = io_setup(128, &ctx);

char buf[4096];

struct iocb cb = {.aio_fildes = fd,

.aio_lio_opcode = IOCB_CMD_PREAD,

.aio_buf = (uint64_t)buf,

.aio_nbytes = sizeof(buf)};

struct iocb *list_of_iocb[1] = {&cb};

r = io_submit(ctx, 1, list_of_iocb);

struct io_event events[1] = {{0}};

r = io_getevents(ctx, 1, 1, events, NULL);

bytes_read = events[0].res;

printf("read %lld bytes from /etc/passwd\n", bytes_read);

我们用strace追踪程序的执行

这一切都OK!但磁盘读取并非异步的,io_submit系统调用被阻止并完成了所有工作!io_getevents通话瞬间完成。

我们可以尝试使磁盘异步读取,但这需要O_DIRECT标志来跳过缓存。

让举另一个更好能说明io_submit普通文件的阻止性质。从文件中读取大的1GiB块时显示strace /dev/zero:

io_submit(0x7fe1e800a000, 1, [{aio_lio_opcode=IOCB_CMD_PREAD, aio_fildes=3, aio_buf=0x7fe1a79f4000, aio_nbytes=1073741824, aio_offset=0}]) = 1 <0.738380>

io_getevents(0x7fe1e800a000, 1, 1, [{data=0, obj=0x7fffb9588910, res=1073741824, res2=0}], NULL) = 1 <0.000015>

内核中io_submit花费738ms,只有15us io_getevents消耗的。内核的行为与网络套接字相同,所有工作都在io_submit中完成。

用Linux AIO处理Socket

io_submit的处理相当保守。除非传递的描述符是O_DIRECT文件,否则它将阻塞并执行请求的操作。对于网络套接字:

对于阻塞套接字,IOCB_CMD_PREAD将挂起,直到数据包到达为止。

对于非阻塞套接字,IOCB_CMD_PREAD返回-11(EAGAIN)。

这些语法与read()系统调用完全相同。对于网络套接字而言,io_submit并不比旧式读/写调用更好用。

重要的是要注意iocb传递给内核的请求是按顺序进行的。

尽管Linux AIO不能帮助异步操作,但它绝对可以用于系统调用批处理。如果有一个Web服务器需要从数百个网络套接字发送和接收数据,那么使用io_submit是个好主意。这可以避免不必的send和recv数百次调用,将提高性能。从用户空间和内核之间来回跳转是需要耗时的。

为了说明的io_submit批处理,我们创建一个小程序,将数据从一个TCP套接字转发到另一个。最简单的形式,如果没有Linux AIO,该程序将像下面这样简单:

while True:

d = sd1.read(4096)

sd2.write(d)

我们可以使用Linux AIO表达相同的逻辑。该代码将如下:

struct iocb cb[2] = {{.aio_fildes = sd2,

.aio_lio_opcode = IOCB_CMD_PWRITE,

.aio_buf = (uint64_t)&buf[0],

.aio_nbytes = 0},

{.aio_fildes = sd1,

.aio_lio_opcode = IOCB_CMD_PREAD,

.aio_buf = (uint64_t)&buf[0],

.aio_nbytes = BUF_SZ}};

struct iocb *list_of_iocb[2] = {&cb[0], &cb[1]};

while(1) {

r = io_submit(ctx, 2, list_of_iocb);

struct io_event events[2] = {};

r = io_getevents(ctx, 2, 2, events, NULL);

cb[0].aio_nbytes = events[1].res;

}

以上代码向io_submit提交两个作业。首先,请求将数据写入sd2,然后从sd1中读取数据。读取完成后,代码将确定写缓冲区的大小并再次循环。该代码用了一个很酷的技巧:第一次写入大小为0。之所以这样做,是因为我们可以在一个io_submit中融合写入+读取(但不能读取+写)。读取完成后,我们必须修复写入缓冲区的大小。

这代码比简单的读/写版本快吗?还不是。

两个版本都有两个系统调用:read + write和io_submit + io_getevents。

但是我们改善它。

摆脱io_getevents

当运行io_setup()时,内核为该进程分配几页内存。这是此内存块在/proc/<pid> /maps中的样子:

cat /proc/`pidof -s aio_passwd`/maps

...

7f7db8f60000-7f7db8f63000 rw-s 00000000 00:12 2314562 /[aio] (deleted)

...

内存区域由io_setup分配。内存范围用于存储完成事件的环形缓冲区。在大多数情况下,没有任何理由对io_geteventssyscall真正的调用。可以从环形缓冲区轻松检索完成数据,而无需请求内核。下面是一个无需内核调用的修订版本:

int io_getevents(aio_context_t ctx, long min_nr, long max_nr,

struct io_event *events, struct timespec *timeout)

{

int i = 0;

struct aio_ring *ring = (struct aio_ring*)ctx;

if (ring == NULL || ring->magic != AIO_RING_MAGIC) {

goto do_syscall;

}

while (i < max_nr) {

unsigned head = ring->head;

if (head == ring->tail) {

break;

} else {

/* There is another completion to reap */

events[i] = ring->events[head];

read_barrier();

ring->head = (head + 1) % ring->nr;

i++;

}

}

if (i == 0 && timeout != NULL && timeout->tv_sec == 0 && timeout->tv_nsec == 0) {

return 0;

}

if (i && i >= min_nr) {

return i;

}

do_syscall:

return syscall(__NR_io_getevents, ctx, min_nr-i, max_nr-i, &events[i], timeout);

}

通过此代码修复了该io_getevents功能, Linux AIO版本的TCP代理每个循环仅只需要一个系统调用,并且读写版本代码快一点。

替代Epoll

通过在内核4.18中添加IOCB_CMD_POLL,io_submit还可以用于替代select/poll/epoll。例如,以下代码在Socket监听等待数据:

struct iocb cb = {.aio_fildes = sd,

.aio_lio_opcode = IOCB_CMD_POLL,

.aio_buf = POLLIN};

struct iocb *list_of_iocb[1] = {&cb};

r = io_submit(ctx, 1, list_of_iocb);

r = io_getevents(ctx, 1, 1, events, NULL);

strace追踪显示:

io_submit(0x7fe44bddd000, 1, [{aio_lio_opcode=IOCB_CMD_POLL, aio_fildes=3}]) = 1 <0.000015>

io_getevents(0x7fe44bddd000, 1, 1, [{data=0, obj=0x7ffef65c11a8, res=1, res2=0}], NULL) = 1 <1.000377>

如上,该程序"异步"部分运行良好,io_submit立即完成,io_getevents等待数据成功阻塞了1秒钟。这是非常强大的功能,可以代替epoll_wait()系统调用使用。

另外,通常情况下,epoll杂项处理需要epoll_ctl系统调用。应用程序开发人员竭尽全力避免过多地调用调用。使用io_submit轮询可以解决整个的复杂工作,并且过程中不需要任何虚假的系统调用。只需将套接字推给iocb请求向量,只调用io_submit一次并等待完成即可。

总结

本文中,我们回顾了Linux AIO接口。尽管最初被认为是仅用于磁盘的接口API,但它似乎与网络套接字上的常规读/写系统调用的工作方式相同。但是与读/写不同的是,它允许系统调用批处理io_submit,从而可以提高性能。从4.18版内核开始io_submit,io_getevents可用于等待网络套接字上的事件如POLLIN和POLLOUT,用于替代epoll()事件循环。

linux aio简介相关推荐

  1. Linux 交叉编译简介

    Linux 交叉编译简介 主机,目标,交叉编译器 主机与目标 编译器是将源代码转换为可执行代码的程序.像所有程序一样,编译器运行在特定类型的计算机上,输出的新程序也运行在特定类型的计算机上. 运行编译 ...

  2. 【Android 逆向】Linux 文件权限 ( Linux 权限简介 | 系统权限 | 用户权限 | 匿名用户权限 | 读 | 写 | 执行 | 更改组 | 更改用户 | 粘滞 )

    文章目录 一.Linux 权限简介 二.系统权限 / 用户权限 / 匿名用户权限 1.系统权限 2.用户权限 3.匿名用户权限 一.Linux 权限简介 Linux 是基于文件的系统 , 内存 , 设 ...

  3. linux AIO (异步IO) 那点事儿

    在高性能的服务器编程中,IO 模型理所当然的是重中之重,需要谨慎选型.对于网络套接字,我们可以采用epoll 的方式来轮询,尽管epoll也有一些缺陷,但总体来说还是很高效的,尤其来大量套接字的场景下 ...

  4. Alpine Linux 使用简介

    Alpine Linux使用简介 目录: 一.Alpine简要介绍 二.Alpine本地安装 三.Alpine在Docker下运行 四.Alpine的配置和使用 4.1网络相关文件 4.2更新国内源 ...

  5. WSL:WSL(Windows Subsystem for Linux)的简介、安装、使用方法之详细攻略

    WSL:WSL(Windows Subsystem for Linux)的简介.安装.使用方法之详细攻略 目录 WSL的简介 WSL的安装 WSL的使用方法 WSL的简介 Windows Subsys ...

  6. linux快捷命令补齐,Linux Shell简介——自动补齐/命令行的历史记录/编辑命令行/可用的 Shell 快捷方式.doc...

    Linux Shell简介--自动补齐/命令行的历史记录/编辑命令行/可用的 Shell 快捷方式 Unix (及后继者 Linux)在命令行下面诞生,因此,Unix 中的命令行有许多非常实用的功能. ...

  7. Linux的简介与虚拟机的管理

    Linux的简介: 严格的来讲,Linux不算是一个操作系统,只是一个Linux系统中的内核,Linux的全称是GUN/Linux,这才算是一个真正意义上的Linux系统. Linux是一个多用户多任 ...

  8. linux内核体系学习路径_Linux内核分析(一)linux体系简介|内核源码简介|内核配置编译安装...

    从本篇博文开始我将对linux内核进行学习和分析,整个过程必将十分艰辛,但我会坚持到底,同时在博文中如果那些地方有问题还请各位大神为我讲解. 今天我们会分析到以下内容: 1. Linux体系结构简介 ...

  9. LinuxStudyNote(39)-Linux软件包管理(1)-Linux软件包简介之源码包与RPM包、源码包的优缺点、RPM二进制包的优缺点

    Linux软件包简介 1.软件包分类 a.源码包 源码包顾名思义,就是开放源代码的安装包 脚本安装包 脚本安装包是在源码包的基础上加上了安装的图形界面, 这种软件包很少见,原因在于它需要专门的人员来进 ...

最新文章

  1. java 栈 泛型_java 泛型栈(数组实现) | 学步园
  2. python实现路由功能_python 实现重启路由器
  3. 《构建之法》阅读笔记03
  4. 【ACL2021】分享三篇值得推荐的情感分析文章 -- 风格分析、幽默计算、情感类别...
  5. matlab2015a安装秘钥_MATLAB2015a(2015b)安装教程
  6. 八大黑盒测试方法总结【超详细】
  7. 等级保护2.0三级通用要求测评方法
  8. java txt中文乱码_Java读写txt文件中文乱码问题的解决
  9. 猪齿鱼 SaaS 版效能平台发布
  10. C语言编程题:平方数
  11. java梯形_如何绘制梯形?
  12. rhel6.cacti的安装与配置
  13. 【洛谷】P1428:小鱼比可爱
  14. 2022-2027(新版)全球与中国鱼藤酮行业发展动态及前景展望报告
  15. java jsr命令_Java系列:JVM指令详解(下)(zz)
  16. R语言:批量获取指定股票代码的股票数据
  17. asp 遇到过的问题集锦,附加asp语句添加数据库和生成表,asp命令更改指定文件的文件名,asp值传递的应用091116小结...
  18. Python爬取股票数据存入mysql数据库,获取股票(最新、最高、今开、成交量、成交额、量比、换手率、涨幅等)支持多线程+数据库连接池
  19. Java--反编译软件
  20. 前端SEO优化的一些解决方案

热门文章

  1. php utf-8汉字转拼音
  2. C#操作Excel模板
  3. div搜索框与按钮不在一行_高效搜索神器 Everything 搜索技巧汇总
  4. nginx upstream配置aws alb域名导致timeout报错
  5. jq判断文件是否存在
  6. 递归递推区别分析与例题总结
  7. 使用组件的render函数 改写el-table el-table-column
  8. 到底啥是JavaScript Mock
  9. 开店使用独立网店系统的13个好处!
  10. 最新重庆某公司面试题