前段时间在自研的基于iSCSI的SAN 上跑mysql,CPU的iowait很大,后面改用Native AIO,有了非常大的改观。这里简单总结一下Native AIO的实现。对于以IO为最大瓶颈的数据库,native AIO几乎不二的选择,仅仅依靠多线程,显然无法解决磁盘和网络的问题。

1 API 与data struct

AIO的主要接口:

System call

Description

io_setup( )

Initializes an asynchronous context for the current process

io_submit( )

Submits one or more asynchronous I/O operations

io_getevents( )

Gets the completion status of some outstanding asynchronous I/O operations

io_cancel( )

Cancels an outstanding I/O operation

io_destroy( )

Removes an asynchronous context for the current process

1.1 AIO上下文

使用AIO的第一步就是创建AIO上下文,AIO上下文用于跟踪进程请求的异步IO的运行情况。AIO上下文在用户空间对应数据结果aio_context_t:

//linux/aio_abi.h

typedef unsigned long    aio_context_t;

//创建AIO上下文

int io_setup(unsigned nr_events, aio_context_t *ctxp);

Io_setup创建接收nr_events事件的AIO上下文。

kioctx

AIO上下文在内核空间对应数据结构kioctx,它保存异步IO的所有信息:

//AIO环境

struct kioctx {

atomic_t      users;

int        dead;

struct mm_struct  *mm;

/* This needs improving */

unsigned long     user_id; //ring_info.mmap_base,AIO环的起始地址

struct kioctx     *next; //下一个aio环境

wait_queue_head_t wait; //等待进程队列

spinlock_t    ctx_lock;

int        reqs_active;

struct list_head  active_reqs;  /* used for cancellation */

struct list_head  run_list;  /* used for kicked reqs,正在运行的IO请求链表 */

unsigned      max_reqs;//异步IO操作的最大数量

struct aio_ring_info ring_info; //AIO Ring

struct work_struct   wq;

};

一个进程可以创建多个AIO上下文,这些AIO上下文构成一个单向链表。

struct mm_struct {

...

/* aio bits */

rwlock_t      ioctx_list_lock;

struct kioctx     *ioctx_list; //进程的AIO上下文链表

struct kioctx     default_kioctx;

}

AIO Ring

AIO上下文kioctx对象包含一个重要的数据结构AIO Ring:

//aio.h

//AIO环

#define AIO_RING_PAGES   8

struct aio_ring_info {

unsigned long     mmap_base; //AIO ring用户态起始地址

unsigned long     mmap_size; //缓冲区长度

struct page       **ring_pages;//AIO环页框指针数组

spinlock_t    ring_lock;

long          nr_pages;

unsigned      nr, tail;

struct page       *internal_pages[AIO_RING_PAGES];

};

AIO Ring对应用户态进程地址空间的一段内存缓存区,用户态进程可以访问,内核也可访问。实际上,内核先调用kmalloc函数分配一些页框,然后通过do_mmap映射到用户态地址空间,详细请参考aio_setup_ring函数。

AIO Ring是一个环形缓冲区,内核用它来报告异步IO的完成情况,用户态进程也可以直接检查异步IO完成情况,从而避免系统调用的开销。

AIO结构很简单:aio_ring + io_event数组:

struct aio_ring {

unsigned   id; /* kernel internal index number */

unsigned   nr; /* number of io_events */

unsigned   head;

unsigned   tail;

unsigned   magic;

unsigned   compat_features;

unsigned   incompat_features;

unsigned   header_length;    /* size of aio_ring */

struct io_event      io_events[0];

}; /* 128 bytes + ring size */

系统调用io_setup有2个参数:(1) nr_events确认最大的异步IO请求数,这将确定AIO Ring大小,即io_event数量;(2) ctxp:AIO上下文句柄的指针,实际上也是AIO Ring的起始地址aio_ring_info.mmap_base,参见函数aio_setup_ring。

1.2 提交IO请求

想要进行异步IO,需要通过系统调用io_submit提交异步IO请求。

//提交异步IO请求/aio.c

asmlinkage long sys_io_submit(aio_context_t ctx_id, long nr,

struct iocb __user * __user *iocbpp)

参数:

(1)ctx_id:AIO上下文句柄,内核通过它查找对应的kioctx对象;

(2)iocb数组,每个iocb描述一个异步IO请求;

(3)nr:iocb数组的大小。

iocb

//用户态异步IO请求描述符/aio_abi.h

struct iocb {

/* these are internal to the kernel/libc. */

__u64  aio_data;  /* data是留给用来自定义的指针:可以设置为IO完成后的callback函数 */

__u32  PADDED(aio_key, aio_reserved1);

/* the kernel sets aio_key to the req # */

/* common fields */

__u16  aio_lio_opcode;   /* see IOCB_CMD_ above,操作的类型:IO_CMD_PWRITE | IO_CMD_PREAD */

__s16  aio_reqprio;

__u32  aio_fildes; //IO操作的文件描述符

__u64  aio_buf; //IO的buffer

__u64  aio_nbytes; //IO请求字节数

__s64  aio_offset;//偏移

/* extra parameters */

__u64  aio_reserved2;    /* TODO: use this for a (struct sigevent *) */

__u64  aio_reserved3;

}; /* 64 bytes */

数据结构iocb用来描述用户空间的异步IO请求,对应的内核数据结构为kiocb。

io_submit的流程:

函数io_submit_one对每个iocb分配一个kiocb对象,加入到AIO上下文kioctx的IO请求队列run_list;然后调用aio_run_iocb发起IO操作,它实际上调用kiocb的ki_retry方法(aio_pread/aio_pwrite)。

如果ki_retry方法返回-EIOCBRETRY,表明异步IO请求已经提交,但是还没全部完成,稍后kiocb的ki_retry方法还会被继续调用,来继续完成IO请求;否则,调用aio_complete,在AIO Ring加入一个表示一个IO完成的io_event。

1.3 收集完成的IO请求

asmlinkage long sys_io_getevents(aio_context_t ctx_id,

long min_nr,

long nr,

struct io_event __user *events,

struct timespec __user *timeout)

参数:

(1)ctx_id:AIO上下文句柄;

(2)min_nr:至少收集min_nr个已经完成的IO请求才返回;

(3)nr:最多收集nr个已经完成的IO请求;

(4)timeout:等待的时间

(5)events:由应用层分配,内核将完成的io_event拷贝到该缓冲区,所以,events数组要保证至少有nr个io_event。

io_event

//aio_abi.h

struct io_event {

__u64      data;      /* the data field from the iocb */

__u64      obj;       /* what iocb this event came from */

__s64      res;       /* result code for this event */

__s64      res2;      /* secondary result */

};

io_event是用来描述返回结果的:

(1)data对应iocb的aio_data,返回用户定义的指针;

(2)obj就是之前提交IO任务时的iocb;

(3)res和res2来表示IO任务完成的状态。

io_getevents的流程:

比较简单,扫描AIO上下文kiocxt的AIO Ring,检查是否有完成的io_event。如果至少有min_nr个完成IO事件(或者超时),则将完成的io_event拷贝到events,并返回io_event的个数或者错误;否则,将进程本身加入到kiocxt的等待队列,挂起进程。

2 AIO工作队列

2.1 创建AIO工作队列

//aio.c

static struct workqueue_struct *aio_wq;//AIO工作队列

static int __init aio_setup(void)

{

...

aio_wq = create_workqueue("aio");

...

2.2 创建work_struct

static struct kioctx *ioctx_alloc(unsigned nr_events)

{

...

INIT_WORK(&ctx->wq, aio_kick_handler, ctx);

函数aio_kick_hanlder由aio内核线程处理aio work时调用:

static void aio_kick_handler(void *data)

{

requeue =__aio_run_iocbs(ctx);

...

/*

* we're in a worker thread already, don't use queue_delayed_work,

*/

if (requeue)

queue_work(aio_wq, &ctx->wq);

}

逻辑很简单,调用__aio_run_iocbs继续处理kioctx中的待完成异步IO,如果需要,则将aio work继续加入aio工作队列,下一次再处理。

2.3 调度工作

函数aio_run_iocbs发起异步IO请求后,如果kioctx的run_list还有未完成的IO,则调用queue_delayed_work将work_struct(kioctx->wq)加入到AIO工作队列aio_wq,由aio内核线程继续发起异步IO。

3 AIO与epoll

在使用AIO时,需要通过系统调用io_getevents获取已经完成的IO事件,而系统调用io_getevents是阻塞的,所以有2种方式:(1)使用多线程,用专门的线程调用io_getevents,参考MySQL5.5及以上版本;(2)对于单线程程序,可以通过epoll来使用AIO;不过,这需要系统调用eventfd的支持,而该系统调用只在2.6.22之后的内核才支持。

eventfd 是 Linux-native aio 其中的一个 API,用来生成 file descriptors,这些 file descriptors 可为应用程序提供更高效 “等待/通知” 的事件机制。和 pipe 作用相似,但比 pipe 更好,一方面它只用到一个 file descriptor(pipe 要用两个),节省了内核资源;另一方面,eventfd 的缓冲区管理要简单得多,pipe 需要不定长的缓冲区,而 eventfd 全部缓冲只有定长 8 bytes。

关于AIO与epoll的结合,请参考:

nginx 0.8.x稳定版对linux aio的支持(http://www.pagefault.info/?p=76)

4 AIO与direct IO

AIO需要与direct IO结合。

关于direct IO的简单实现,可以参考:

Linux 中直接 I/O 机制的介绍

http://www.ibm.com/developerworks/cn/linux/l-cn-directio/index.html

5 案例

(1)同步IO

(2)Native AIO

浅析Linux Native AIO的实现的更多相关文章

  1. Linux Native Aio 异步AIO的研究

    Linux Native Aio 异步AIO的研究 http://rango.swoole.com/archives/282 首先声明一下epoll+nonblock从宏观角度可以叫做全异步,但从微观 ...

  2. 浅析 Linux 初始化 init 系统

    近年来,Linux 系统的 init 进程经历了两次重大的演进,传统的 sysvinit 已经逐渐淡出历史舞台,新的 UpStart 和 systemd 各有特点,越来越多的 Linux 发行版采纳了 ...

  3. 浅析Linux下进程间通信:共享内存

    浅析Linux下进程间通信:共享内存 共享内存允许两个或多个进程共享一给定的存储区.因为数据不需要在客户进程和服务器进程之间复制,所以它是最快的一种IPC.使用共享内存要注意的是,多个进程之间对一给定 ...

  4. 浅析 Linux 初始化 init 系统,第 1 部分: sysvinit 第 2 部分: UpStart 第 3 部分: Systemd

    浅析 Linux 初始化 init 系统,第 1 部分: sysvinit  第 2 部分: UpStart 第 3 部分: Systemd http://www.ibm.com/developerw ...

  5. 浅析Linux操作系统工作的基础

    环境:lubuntu 13.04   kernel 3.9.7 作者:SA12226265 katao 简介: 本文根据 Linux™ 系统工作基础的分析,对存储程序计算机.堆栈(函数调用堆栈)机制和 ...

  6. 浅析Linux服务器集群系统技术

    浅析Linux服务器集群系统技术 目录 前言 常用的服务器集群 集群系统的优势 LVS集群的通用体系结构 为什么使用层次的体系结构 为什么是共享存储 可伸缩Web服务 前言 总结两篇技术文章,努力学习 ...

  7. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程(转自http://blog.chinaunix.net/uid-20564848-id-73480.html)

    浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...

  8. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程【转】

    转自:http://blog.chinaunix.net/uid-20564848-id-73480.html 浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_time ...

  9. 浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程【转】

    本文转载自:http://www.cnblogs.com/qingchen1984/p/7007631.html 本篇文章主要介绍了"浅析 Linux 中的时间编程和实现原理一—— Linu ...

随机推荐

  1. Linux下Apache2.2和PHP5的安装配置

    Linux下Apache2.2和PHP5的安装配置 环境介绍 我安装使用的Linux版本为CentOS6.5最精简版,Apache为2.2.29,PHP版本为5.4.28. 系统安装 首先安装Cent ...

  2. HDU 5652 India and China Origins 二分+并查集

    India and China Origins 题目连接: http://acm.hdu.edu.cn/showproblem.php?pid=5652 Description A long time ...

  3. 2015 百度之星 1004 KPI STL的妙用

    KPI Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acdream.info/problem?pid=1754 Description 你 ...

  4. MYSQL学习笔记 (三)JOIN用法

    数据库的操作分开增删改查,其中查询操作基本占系统的90%,大家所说的优化SQL语句基本是优化查询语句.接下来将学习JOIN的用法,JOIN包括:INNER JOIN(内连接).LEFT JOIN(左外 ...

  5. 通过手机音频口,实现与单片机通讯,做电子签名成功n

    手机端的Ukey便携产品, 可以管理证书.加密解密.电子签名 : 1.通讯稳定,生成签名成功率100% 2.证书固化,私钥安全 3.走手机音频接口,通用.跨平台 4.耗电少,自带电池可长期供电,且可充 ...

  6. Known BREAKING CHANGES from NH3.3.3.GA to 4.0.0

    Build 4.0.0.Alpha1 =============================   ** Known BREAKING CHANGES from NH3.3.3.GA to 4.0. ...

  7. 【spring】在spring cloud项目中使用@ControllerAdvice做自定义异常拦截,无效 解决原因

    之前在spring boot服务中使用@ControllerAdvice做自定义异常拦截,完全没有问题!!! GitHub源码地址: 但是现在在spring cloud中使用@ControllerAd ...

  8. 容器+AOP实现动态部署(四)

    上篇咱们介绍了容器和AOP的结合,结合后怎样将对象增强服务并没有过多的说明,这里将详细说明怎样将对象 进行增强 ,达到一个一对多和多对多的增强方式 先从简单的方式说起 /** *JDK代理类,实现动态 ...

  9. 数学图形(2.20)3D曲线

    这一节主要是发布我自己写的3D曲线, (1)立体flower线圈 vertices = a = 10.1 b = 3.1 s = (a + b) / b o = i = to (**PI) j = m ...

  10. go语言基础之普通参数列表

    1.普通参数列表 (备注:只有一个参数) 示例1: package main //必须有一个main包 import "fmt" //有参无返回值函数的定义,普通参数列表 //定义 ...