Libaio Simple Example

18 Oct 2011

libaio api

AIO system call entry points are located in fs/aio.c; file in the kernel’s

source code. Types and constants exported to the user space reside in /usr/include/linux/aio_abi.hheader file.

There are only 5 AIO system calls:

int io_setup(unsigned nr_events, aio_context_t *ctxp);
int io_destroy(aio_context_t ctx);
int io_submit(aio_context_t ctx, long nr, struct iocb *cbp[]);
int io_cancel(aio_context_t ctx, struct iocb *, struct io_event *result);
int io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

The Linux AIO model is used as follows:

  • Open an I/O context to submit and reap I/O requests from.
  • Create one or more request objects and set them up to represent the desired operation
  • Submit these requests to the I/O context, which will send them down to the device driver to process on the device
  • Reap completions from the I/O context in the form of event completion objects,
  • Return to step 2 as needed.

I/O Context

The first step to use libaio is create and init io_context by using io_setup

io_context_t ctx;
memset(&ctx, 0, sizeof(ctx));
if(io_setup(10, &ctx)!=0){//init
  printf("io_setup errorn");
  return -1;
}

The first augments of io_setup is the maximum length of the I/O query.

Create I/O request

The I/O request is represented by iocb structure.

struct iocb {
    PADDEDptr(void *data, __pad1);  /* Return in the io completion event */
    PADDED(unsigned key, __pad2);   /* For use in identifying io requests */

    short       aio_lio_opcode;
    short       aio_reqprio;
    int     aio_fildes;

    union {
        struct io_iocb_common       c;
        struct io_iocb_vector       v;
        struct io_iocb_poll     poll;
        struct io_iocb_sockaddr saddr;
    } u;
};

It is defined in libaio.h. You can manually set up the variables. However, it is recommended to use the helper functions:

static inline void io_set_callback(struct iocb *iocb, io_callback_t cb)
{
    iocb->data = (void *)cb;
}

static inline void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset)
{
    memset(iocb, 0, sizeof(*iocb));
    iocb->aio_fildes = fd;
    iocb->aio_lio_opcode = IO_CMD_PREAD;
    iocb->aio_reqprio = 0;
    iocb->u.c.buf = buf;
    iocb->u.c.nbytes = count;
    iocb->u.c.offset = offset;
}

static inline void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset)
{
    memset(iocb, 0, sizeof(*iocb));
    iocb->aio_fildes = fd;
    iocb->aio_lio_opcode = IO_CMD_PWRITE;
    iocb->aio_reqprio = 0;
    iocb->u.c.buf = buf;
    iocb->u.c.nbytes = count;
    iocb->u.c.offset = offset;
}

Submit the request

The submittion is easy:

if(io_submit(ctx, 1, &iocb_request)!=1){
  io_destroy(ctx);
  printf("io_submit errorn");    
  return -1;
}

You can also submit an array of iocbs:

io_submit(ctx, sizeof(iocb_array)/sizeof(struct iocb), iocb_array);

Wait the io events with timeout

struct io_event e;
while(1){
  timeout.tv_sec=0;
  timeout.tv_nsec=500000000;//0.5s
  if(io_getevents(ctx, 0, 1, &e, &timeout)==1){
    break;
  }   
  printf("haven't donen");
  sleep(1);
}   

The io_event is a structure, you can get the submitted iocb from it:

struct io_event {
    PADDEDptr(void *data, __pad1);
    PADDEDptr(struct iocb *obj,  __pad2);
    PADDEDul(res,  __pad3);
    PADDEDul(res2, __pad4);
};

Release I/O Context

If you do not use the I/O context any more, remember to destroy it:

io_destroy(ctx);

Reference