淺析Linux Native AIO的實現

來源:互聯網
上載者:User

前段時間在自研的基於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

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.