淺析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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM