linux AIO -- libaio 實現的異步 IO


POSIX AIO 是在用戶控件模擬異步 IO 的功能,不需要內核支持,而 linux AIO 則是 linux 內核原聲支持的異步 IO 調用,行為更加低級

 

關於 linux IO 模型及 AIO、POSIX AIO 的簡介,請參看:

POSIX AIO -- glibc 版本異步 IO 簡介

 

libaio 實現的異步 IO 主要包含以下接口:

libaio 實現的異步 IO
函數 功能 原型
io_setup 創建一個異步IO上下文(io_context_t是一個句柄) int io_setup(int maxevents, io_context_t *ctxp);
io_destroy 銷毀一個異步IO上下文(如果有正在進行的異步IO,取消並等待它們完成) int io_destroy(io_context_t ctx);
io_submit 提交異步IO請求 long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp);
io_cancel 取消一個異步IO請求 long io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result);
io_getevents 等待並獲取異步IO請求的事件(也就是異步請求的處理結果) long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

 

 

iocb 結構

 

struct iocb主要包含以下字段:

struct iocb
{
	/*
	 * 請求類型
	 * 如:IOCB_CMD_PREAD=讀、IOCB_CMD_PWRITE=寫、等
	 */
	__u16     aio_lio_opcode;
	/*
	 * 要被操作的fd
	 */
	__u32     aio_fildes;
	/*
	 * 讀寫操作對應的內存buffer
	 */
	__u64     aio_buf;
	/*
	 * 需要讀寫的字節長度
	 */
	__u64     aio_nbytes;
	/*
	 * 讀寫操作對應的文件偏移
	 */
	__s64     aio_offset;
	/*
	 * 請求可攜帶的私有數據
	 * 在io_getevents時能夠從io_event結果中取得)
	 */
	__u64     aio_data;
	/*
	 * 可選IOCB_FLAG_RESFD標記
	 * 表示異步請求處理完成時使用eventfd進行通知
	 */
	__u32     aio_flags;
	/*
	 * 有IOCB_FLAG_RESFD標記時,接收通知的eventfd
	 */
	__u32     aio_resfd;
}

  

 
io_event 結構
struct io_event
{
    /*
     * 對應iocb的aio_data的值
     */
    __u64     data;
    /*
     * 指向對應iocb的指針
     */
    __u64     obj;
    /*
     * 對應IO請求的結果
     * >=0: 相當於對應的同步調用的返回值;<0: -errno
     */
    __s64     res;
}
 
 
 

aio_context_t 即 AIO 上下文句柄,該結構體對應內核中的一個 struct kioctx 結構,用來給一組異步 IO 請求提供一個上下文環境,每個進程可以有多個 aio_context_t,io_setup 的第一個參數聲明了同時駐留在內核中的異步 IO 上下文數量

kioctx 結構主要包含以下字段:

struct kioctx
{
	/*
	 * 調用者進程對應的內存管理結構
	 * 代表了調用者的虛擬地址空間
	 */
	struct mm_struct*     mm;
	/*
	 * 上下文ID,也就是io_context_t句柄的值
	 * 等於ring_info.mmap_base
	 */
	unsigned long         user_id;
	/*
	 * 屬於同一地址空間的所有kioctx結構通過這個list串連起來
	 * 鏈表頭是mm->ioctx_list
	 */
	struct hlist_node     list;
	/*
	 * 等待隊列
	 * io_getevents系統調用可能需要等待
	 * 調用者就在該等待隊列上睡眠
	 */
	wait_queue_head_t     wait;
	/*
	 * 進行中的請求數目
	 */
	int                   reqs_active;
	/*
	 * 進行中的請求隊列
	 */
	struct list_head      active_reqs;
	/*
	 * 最大請求數
	 * 對應io_setup調用的int maxevents參數
	 */
	unsigned              max_reqs;
	/*
	 * 需要aio線程處理的請求列表
	 * 某些情況下,IO請求可能交給aio線程來提交
	 */
	struct list_head      run_list;
	/*
	 * 延遲任務隊列
	 * 當需要aio線程處理請求時,將wq掛入aio線程對應的請求隊列
	 */
	struct delayed_work   wq;
	/*
	 * 存放請求結果io_event結構的ring buffer
	 */
	struct aio_ring_info  ring_info;
}

  

其中,aio_ring_info 結構用於存放請求結果 io_event 結構的 ring buffer,主要包含以下字段:

 
struct aio_ring_info
{
     unsigned long   mmap_base;  // ring buffer 
     unsigned long   mmap_size;  // ring buffer 
     struct page**   ring_pages// ring buffer 的 page 
     long            nr_pages;   // 
     unsigned        nr;         // io_event 
     unsigned        tail;       // io_event 
}
 

aio_ring_info 結構中,nr_page * PAGE_SIZE = mmap_size

以上數據結構都是在內核地址空間上分配的,是內核專有的,用戶程序無法訪問和使用

但是 io_event 結構是內核在用戶地址空間上分配的 buffer,用戶可以修改,但是首地址、大小等信息都是由內核維護的,用戶程序通過 io_getevents 函數修改

 

io_setup 函數創建了一個 AIO 上下文,並通過值-結果參數 aio_context_t 類型指針返回其句柄

io_setup 調用后,內核會通過 mmap 在對應的用戶地址空間分配一段內存,由 aio_ring_info 結構中的 mmap_base、mmap_size 描述這個映射對應的位置和大小,由 ring_pages、nr_pages 描述實際分配的物理內存頁面信息,異步 IO 完成后,內核會將異步 IO 的結果寫入其中

 

在 mmap_base 指向的用戶地址空間上,會存放着一個 struct aio_ring 結構,用來管理 ring buffer,主要包含以下字段:

unsigned    id;     // 等於 aio_ring_info 中的 user_id
unsigned    nr;     // 等於 aio_ring_info 中的 nr
unsigned    head;   // io_events 數組隊首
unsigned    tail;   // io_events 數組游標
unsigned    magic;  // 用於確定數據結構有沒有異常篡改
unsigned    compat_features;
unsigned    incompat_features;
unsigned    header_length;  // aio_ring 結構大小
struct io_event *io_events; // io_event buffer 首地址
 

  

這個數據結構存在於用戶地址空間中,內核作為生產者,在 buffer 中放入數據,並修改 tail 字段,用戶程序作為消費者從 buffer 中取出數據,並修改 head 字段

 

每一個請求用戶都會創建一個 iocb 結構用於描述這個請求,而對應於用戶傳遞的每一個 iocb 結構,內核都會生成一個與之對應的 kiocb 結構,並只該結構中的 ring_info 中預留一個 io_events 空間,用於保存處理的結果 

 
struct kiocb
{
	struct kioctx*		ki_ctx;           /* 請求對應的kioctx(上下文結構)*/
	struct list_head	ki_run_list;      /* 需要aio線程處理的請求,通過該字段鏈入ki_ctx->run_list */
	struct list_head	ki_list;          /* 鏈入ki_ctx->active_reqs */
	struct file*		ki_filp;          /* 對應的文件指針*/
	void __user*		ki_obj.user;      /* 指向用戶態的iocb結構*/
	__u64				ki_user_data;     /* 等於iocb->aio_data */
	loff_t				ki_pos;           /* 等於iocb->aio_offset */
	unsigned short		ki_opcode;        /* 等於iocb->aio_lio_opcode */
	size_t				ki_nbytes;        /* 等於iocb->aio_nbytes */
	char __user *		ki_buf;           /* 等於iocb->aio_buf */
	size_t				ki_left;          /* 該請求剩余字節數(初值等於iocb->aio_nbytes)*/
	struct eventfd_ctx*	ki_eventfd;       /* 由iocb->aio_resfd對應的eventfd對象*/
	ssize_t (*ki_retry)(struct kiocb *);  /*由ki_opcode選擇的請求提交函數*/
}

  

這以后,對應的異步讀寫請求就通過調用 file->f_op->aio_read 或 file->f_op->aio_write 被提交到了虛擬文件系統,與普通的文件讀寫請求非常類似,但是提交完后 IO 請求立即返回,而不等待虛擬文件系統完成相應操作

對於虛擬文件系統返回 EIOCBRETRY 需要重試的情況,內核會在當前 CPU 的 aio 線程中添加一個任務,讓 aio 完成該任務的重新提交

 

 

 

從上圖中的流程就可以看出,linux 版本的 AIO 與 POSIX 版本的 AIO 最大的不同在於 linux 版本的 AIO 實際上利用了 CPU 和 IO 設備異步工作的特性,與同步 IO 相比,很大程度上節約了 CPU 資源的浪費

而 POSIX AIO 利用了線程與線程之間的異步工作特性,在用戶線程中實現 IO 的異步操作

 

POSIX AIO 支持非 direct-io,而且實現非常靈活,可配置性很高,可以利用內核提供的page cache來提高效率,而 linux 內核實現的 AIO 就只支持 direct-io,cache 的工作就需要用戶進程考慮了

 


免責聲明!

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



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