天天看點

linux AIO -- libaio 實作的異步 IO異步 IO 上下文實作原理與 POSIX AIO 差別

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主要包含以下字段:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

struct

iocb

{

__u16     aio_lio_opcode;

__u32     aio_fildes;

__u64     aio_buf;

__u64     aio_nbytes;

__s64     aio_offset;

__u64     aio_data;

__u32     aio_flags;

__u32     aio_resfd;

}

  

io_event 結構

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

struct

io_event

{

__u64     data;

__u64     obj;

__s64     res;

}

異步 IO 上下文

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

struct

kioctx

{

struct

mm_struct*     mm;

unsigned

long

user_id;

struct

hlist_node     list;

wait_queue_head_t     wait;

int

reqs_active;

struct

list_head      active_reqs;

unsigned              max_reqs;

struct

list_head      run_list;

struct

delayed_work   wq;

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,主要包含以下字段:

1

2

3

4

5

6

7

8

9

10

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 空間,用于儲存處理的結果 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

struct

kiocb

{

struct

kioctx*      ki_ctx;          

struct

list_head    ki_run_list;     

struct

list_head    ki_list;         

struct

file*        ki_filp;         

void

__user*        ki_obj.user;     

__u64               ki_user_data;    

loff_t              ki_pos;          

unsigned

short

ki_opcode;       

size_t              ki_nbytes;       

char

__user *       ki_buf;          

size_t              ki_left;         

struct

eventfd_ctx* ki_eventfd;      

ssize_t (*ki_retry)(

struct

kiocb *); 

}

  

這以後,對應的異步讀寫請求就通過調用 file->f_op->aio_read 或 file->f_op->aio_write 被送出到了虛拟檔案系統,與普通的檔案讀寫請求非常類似,但是送出完後 IO 請求立即傳回,而不等待虛拟檔案系統完成相應操作

對于虛拟檔案系統傳回 EIOCBRETRY 需要重試的情況,核心會在目前 CPU 的 aio 線程中添加一個任務,讓 aio 完成該任務的重新送出

與 POSIX 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 的工作就需要使用者程序考慮了