浅析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下的I/O复用与epoll详解
- 下一篇: Linux内核中影响tcp三次握手的一些