浅析Linux Native AIO的实现
前段时间在自研的基于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的实现的更多相关文章
- Linux Native Aio 异步AIO的研究
Linux Native Aio 异步AIO的研究 http://rango.swoole.com/archives/282 首先声明一下epoll+nonblock从宏观角度可以叫做全异步,但从微观 ...
- 浅析 Linux 初始化 init 系统
近年来,Linux 系统的 init 进程经历了两次重大的演进,传统的 sysvinit 已经逐渐淡出历史舞台,新的 UpStart 和 systemd 各有特点,越来越多的 Linux 发行版采纳了 ...
- 浅析Linux下进程间通信:共享内存
浅析Linux下进程间通信:共享内存 共享内存允许两个或多个进程共享一给定的存储区.因为数据不需要在客户进程和服务器进程之间复制,所以它是最快的一种IPC.使用共享内存要注意的是,多个进程之间对一给定 ...
- 浅析 Linux 初始化 init 系统,第 1 部分: sysvinit 第 2 部分: UpStart 第 3 部分: Systemd
浅析 Linux 初始化 init 系统,第 1 部分: sysvinit 第 2 部分: UpStart 第 3 部分: Systemd http://www.ibm.com/developerw ...
- 浅析Linux操作系统工作的基础
环境:lubuntu 13.04 kernel 3.9.7 作者:SA12226265 katao 简介: 本文根据 Linux™ 系统工作基础的分析,对存储程序计算机.堆栈(函数调用堆栈)机制和 ...
- 浅析Linux服务器集群系统技术
浅析Linux服务器集群系统技术 目录 前言 常用的服务器集群 集群系统的优势 LVS集群的通用体系结构 为什么使用层次的体系结构 为什么是共享存储 可伸缩Web服务 前言 总结两篇技术文章,努力学习 ...
- 浅析linux内核中timer定时器的生成和sofirq软中断调用流程(转自http://blog.chinaunix.net/uid-20564848-id-73480.html)
浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...
- 浅析linux内核中timer定时器的生成和sofirq软中断调用流程【转】
转自:http://blog.chinaunix.net/uid-20564848-id-73480.html 浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_time ...
- 浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程【转】
本文转载自:http://www.cnblogs.com/qingchen1984/p/7007631.html 本篇文章主要介绍了"浅析 Linux 中的时间编程和实现原理一—— Linu ...
随机推荐
- 高斯消元法求解异或方程组: cojs.tk 539.//BZOJ 1770 牛棚的灯
高斯消元求解异或方程组: 比较不错的一篇文章:http://blog.sina.com.cn/s/blog_51cea4040100g7hl.html cojs.tk 539. 牛棚的灯 ★★☆ ...
- [转]安卓虚拟机启动后报错: 类似 SDK Manager] Error: Error parsing .devices.xml 解决方案
昨天用android sdk manager 更新了android sdk, 我是在myeclipse上面安装adt来开发android的现在每次打开myeclipse都报错, 而且我每次打开虚拟机的 ...
- jsonp和jsonpcallback的使用
1. jsonp.jsonpCallback jsonp跨域时可以自定义的两个参数 2. jsonp: 回掉函数名的参数名,默认callback,服务端通过它来获取到回掉函数名 3. jsonpCa ...
- Java性能优化的9大工具
在这篇文章中,我会带着大家一起看一下9个可以帮助我们优化Java性能的工具.有一些我们已经在IDR Solutions中使用了,而另外一些有可能在个人项目中使用. NetBeans Profiler ...
- python笔记17-字典如何按value排序
前言 面试题:如何统计数组中出现次数最多的数据,按出现次数由大到小排序 这个排序看似简单,涉及到的基础知识点还是很多的,真正写起来并不容易 备注:本篇是以python3.6讲解的,python2会多一 ...
- java jdk查看源代码
事实上假设你安装了JDK的话,你就已经拥有了java api的源代码. 安装JDK文件夹下的src.zip文件就是java api的源代码. 比方:C:\Program Files\Java\jdk1 ...
- soa文章摘抄
from: http://blog.vsharing.com/fengjicheng/MC19136/ 浅析深究什么是SOA? (入选推荐日志,加10币)浅析深究什么是SOA? 金蝶中间件有限公司总经 ...
- [Android Studio] Android Studio使用教程(二)
以下是本次Google I/O大会发布的IDE Android Studio使用教程第二篇: 在Android Studio使用教程(一)中简要介绍了Android Studio的基本使用,包括安装. ...
- 如何在jenkins上通过mvn方式运行sonar
1.首先在jenkins所在机器的的maven配置文件(settings.xml)里做如下配置: <profile> <id>sonar</id> <acti ...
- @使用javap反编译Java字节码文件
在Sun公司提供的JDK中,就已经内置了Java字节码文件反编译工具javap.exe(位于JDK安装目录的bin文件夹下). 我们可以在dos窗口中使用javap来反汇编指定的Java字节码文件.在 ...