[
  {
    "path": "README.md",
    "content": "*Note, Linux AIO is now subsumed by the io_uring API ([tutorial](https://blogs.oracle.com/linux/an-introduction-to-the-io_uring-asynchronous-io-framework), [LWN coverage](https://lwn.net/Articles/810414/)). The below explanation is mostly useful for old kernels.*\n\n## Introduction\nThe Asynchronous Input/Output (AIO) interface allows many I/O requests to be submitted in parallel without the overhead of a thread per request. The purpose of this document is to explain how to use the Linux AIO interface, namely the function family `io_setup`, `io_submit`, `io_getevents`, `io_destroy`. Currently, the AIO interface is best for `O_DIRECT` access to a raw block device like a disk, flash drive or storage array.\n\n## What is AIO?\nInput and output functions involve a device, like a disk or flash drive, which works much slower than the CPU. Consequently, the CPU can be doing other things while waiting for an operation on the device to complete. There are multiple ways to handle this:\n\n- In the **synchronous I/O model**, the application issues a request from a thread. The thread blocks until the operation is complete. The operating system creates the illusion that issuing the request to the device and receiving the result was just like any other operation that would proceed just on the CPU, but in reality, it may switch in other threads or processes to make use of the CPU resources and to allow other device requests to be issued to the device in parallel, originating from the same CPU.\n- In the **asynchronous I/O (AIO) model**, the application can submit one or many requests from a thread. Submitting a request does not cause the thread to block, and instead the thread can proceed to do other computations and submit further requests to the device while the original request is in flight. The application is expected to process completions and organize logical computations itself without depending on threads to organize the use of data.\n\nAsynchronous I/O can be considered “lower level” than synchronous I/O because it does not make use of a system-provided concept of threads to organize its computation. However, it is often more efficient to use AIO than synchronous I/O due the nondeterministic overhead of threads.\n\n## The Linux AIO model\nThe Linux AIO model is used as follows:\n\n1. Open an I/O context to submit and reap I/O requests from.\n1. Create one or more request objects and set them up to represent the desired operation\n1. Submit these requests to the I/O context, which will send them down to the device driver to process on the device\n1. Reap completions from the I/O context in the form of event completion objects,\n1. Return to step 2 as needed.\n\n## I/O context\n`io_context_t` is a pointer-sized opaque datatype that represents an “AIO context”. It can be safely passed around by value. Requests in the form of a `struct iocb` are submitted to an `io_context_t` and completions are read from the `io_context_t`. Internally, this structure contains a queue of completed requests. The length of the queue forms an upper bound on the number of concurrent requests which may be submitted to the `io_context_t`.\n\nTo create a new `io_context_t`, use the function\n\n```c\nint io_setup(int maxevents, io_context_t *ctxp);\n```\n\nHere, `ctxp` is the output and `maxevents` is the input. The function creates an `io_context_t` with an internal queue of length `maxevents`. To deallocate an `io_context_t`, use\n\n```c\nint io_destroy(io_context_t ctx);\n```\n\nThere is a system-wide maximum number of allocated `io_context_t` objects, set at 65536.\n\nAn `io_context_t` object can be shared between threads, both for submission and completion. No guarantees are provided about ordering of submission and completion with respect to interaction from multiple threads. There may be performance implications from sharing `io_context_t` objects between threads.\n\n## Submitting requests\n`struct iocb` represents a single request for a read or write operation. The following struct shows a simplification on the struct definition; a full definition is found in `<libaio.h>` within the libaio source code.\n\n```c\nstruct iocb {\n    void *data;\n    short aio_lio_opcode;\n    int aio_fildes;\n\n    union {\n        struct {\n            void *buf;\n            unsigned long nbytes;\n            long long offset;\n        } c;\n    } u;\n};\n```\n\nThe meaning of the fields is as follows: data is a pointer to a user-defined object used to represent the operation\n\n- `aio_lio_opcode` is a flag indicate whether the operation is a read (`IO_CMD_PREAD`) or a write (`IO_CMD_PWRITE`) or one of the other supported operations\n- `aio_fildes` is the fd of the file that the iocb reads or writes\n- `buf` is the pointer to memory that is read or written\n- `nbytes` is the length of the request\n- `offset` is the initial offset of the read or write within the file\n\nThe convenience functions `io_prep_pread` and `io_prep_pwrite` can be used to initialize a `struct iocb`.\nNew operations are sent to the device with `io_submit`.\n\n```c\nint io_submit(io_context_t ctx, long nr, struct iocb *ios[]);\n```\n\n`io_submit` allows an array of pointers to `struct iocb`s to be submitted all at once. In this function call, `nr` is the length of the `ios` array. If multiple operations are sent in one array, then no ordering guarantees are given between the `iocb`s. Submitting in larger batches sometimes results in a performance improvement due to a reduction in CPU usage. A performance improvement also sometimes results from keeping many I/Os ‘in flight’ simultaneously.\n\nIf the submission includes too many iocbs such that the internal queue of the `io_context_t` would overfill on completion, then `io_submit` will return a non-zero number and set `errno` to `EAGAIN`.\n\nWhen used under the right conditions, `io_submit` should not block. However, when used in certain ways, it may block, undermining the purpose of asynchronous I/O. If this is a problem for your application, be sure to use the `O_DIRECT` flag when opening a file, and operate on a raw block device. Work is ongoing to fix the problem.\n\n## Processing results\nCompletions read from an `io_context_t` are of the type `struct io_event`, which contains the following relevant fields.\n\n```c\nstruct io_event {\n    void *data;\n    struct iocb *obj;\n    long long res;\n};\n```\n\nHere, `data` is the same data pointer that was passed in with the `struct iocb`, and `obj` is the original `struct iocb`. `res` is the return value of the read or write.\n\nCompletions are reaped with `io_getevents`.\n\n```c\nint io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);\n```\n\nThis function has a good number of parameters, so an explanation is in order:\n\n- `ctx_id` is the `io_context_t` that is being reaped from.\n- `min_nr` is the minimum number of `io_events` to return. `io_gevents` will block until there are `min_nr` completions to report, if this is not already the case when the function call is made.\n- `nr` is the maximum number of completions to return. It is expected to be the length of the `events` array.\n- `events` is an array of `io_events` into which the information about completions is written.\n- `timeout` is the maximum time that a call to `io_getevents` may block until it will return. If `NULL` is passed, then `io_getevents` will block until `min_nr` completions are available.\n\nThe return value represents how many completions were reported, i.e. how much of events was written. The return value will be between 0 and `nr`. The return value may be lower than `min_nr` if the timeout expires; if the timeout is `NULL`, then the return value will be between `min_nr` and `nr`.\n\nThe parameters give a broad range of flexibility in how AIO can be used.\n\n- `min_nr = 0` (or, equivalently, `timeout = 0`). This option forms a non-blocking polling technique: it will always return immediately, regardless of whether any completions are available. It makes sense to use `min_nr = 0` when calling `io_getevents` as part of a main run-loop of an application, on each iteration.\n- `min_nr = 1`. This option blocks until a single completion is available. This parameter is the minimum value which will produce a blocking call, and therefore may be the best value for low latency operations for some users. When an application notices that an `eventfd` corresponding to an iocb is triggered (see the next section about `epoll`), then the application can call `io_getevents` on the corresponding `io_context_t` with a guarantee that no blocking will occur.\n- `min_nr > 1`. This option waits for multiple completions to return, unless the timeout expires. Waiting for multiple completions may improve throughput due to reduced CPU usage, both due to fewer `io_getevents` calls and because if there is more space in the completion queue due to the removed completions, then a later `io_submit` call may have a larger granularity, as well as a reduced number of context switches back to the calling thread when the event is available. This option runs the risk of increasing the latency of operations, especially when the operation rate is lower.\n\nEven if `min_nr = 0` or `1`, it is useful to make nr a bit bigger for performance reasons: more than one event may be already complete, and it could be processed without multiple calls to `io_getevents`. The only cost of a larger nr value library is that the user must allocate a larger array of events and be prepared to accept them.\n\n## Use with epoll\nAny `iocb` can be set to notify an `eventfd` on completion using the libaio function `io_set_eventfd`. The `eventfd` can be put in an `epoll` object. When the `eventfd` is triggered, then the `io_getevents` function can be called on the corresponding `io_context_t`.\n\nThere is no way to use this API to trigger an eventfd only when multiple operations are complete--the eventfd will always be triggered on the first operation. Consequently, as described in the previous section, it will often make sense to use `min_nr = 1` when using `io_getevents` after an `epoll_wait` call that indicates an `eventfd` involved in AIO.\n\n## Performance considerations\n- **Blocking during `io_submit` on ext4, on buffered operations, network access, pipes, etc.** Some operations are not well-represented by the AIO interface. With completely unsupported operations like buffered reads, operations on a socket or pipes, the entire operation will be performed during the io_submit syscall, with the completion available immediately for access with io_getevents. AIO access to a file on a filesystem like ext4 is partially supported: if a metadata read is required to look up the data block (ie if the metadata is not already in memory), then the io_submit call will block on the metadata read. Certain types of file-enlarging writes are completely unsupported and block for the entire duration of the operation.\n- **CPU overhead.** When performing small operations on a high-performance device and targeting a very high operation rate from single CPU, a CPU bottleneck may result. This can be resolved by submitting and reaping AIO from multiple threads.\n- **Lock contention when many CPUs or requests share an io_context_t.** There are several circumstances when the kernel datastructure corresponding to an io_context_t may be accessed from multiple CPUs. For example, multiple threads may submit and get events from the same io_context_t. Some devices may use a single interrupt line for all completions. This can cause the lock to be bounced around between cores or the lock to be heavily contended, resulting in higher CPU usage and potentially lower throughput. One solution is to shard into multiple io_context_t objects, for example by thread and a hash of the address.\n- **Ensuring sufficient parallelism.** Some devices require many concurrent operations to reach peak performance. This means making sure that there are several operations ‘in flight’ simultaneously. On some high-performance storage devices, when operations are small, tens or hundreds must be submitted in parallel in order to achieve maximum throughput. For disk drives, performance may improve with greater parallelism if the elevator scheduler can make better decisions with more operations simultaneously in flight, but the effect is expected to be small in many situations.\n\n## Alternatives to Linux AIO\n- **Thread pool of synchronous I/O threads.** This can work for many use cases, and it may be easier to program with. Unlike with AIO, all functions can be parallelized via a thread pool. Some users find that a thread pool does not work well due to the overhead of threads in terms of CPU and memory bandwidth usage from context switching. This comes up as an especially big problem with small random reads on high-performance storage devices.\n- **POSIX AIO.** Another asynchronous I/O interface is POSIX AIO. It is implemented as part of glibc. However, the glibc implementation uses a thread pool internally. For cases where this is acceptable, it might be better to use your own thread pool instead. Joel Becker implemented [a version](https://oss.oracle.com/projects/libaio-oracle/files/) of POSIX AIO based on the Linux AIO mechanism described above. IBM DeveloperWorks has [a good introduction](http://www.ibm.com/developerworks/linux/library/l-async/index.html) to POSIX AIO.\n- **epoll.** Linux has limited support for using epoll as a mechanism for asynchronous I/O. For reads to a file opened in buffered mode (that is, without O_DIRECT), if the file is opened as O_NONBLOCK, then a read will return EAGAIN until the relevant part is in memory. Writes to a buffered file are usually immediate, as they are written out with another writeback thread. However, these mechanisms don’t give the level of control over I/O that direct I/O gives.\n\n## Sample code\nBelow is some example code which uses Linux AIO. I wrote it at Google, so it uses the [Google glog logging library](https://github.com/google/glog) and the [Google gflags command-line flags library](http://gflags.github.io/gflags/), as well as a loose interpretation of [Google’s C++ coding conventions](https://google.github.io/styleguide/cppguide.html). When compiling it with gcc, pass `-laio` to dynamically link with libaio. (It isn’t included in glibc, so it must be explicitly included.)\n\n```c\n// Code written by Daniel Ehrenberg, released into the public domain\n\n#include <fcntl.h>\n#include <gflags/gflags.h>\n#include <glog/logging.h>\n#include <libaio.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n\nDEFINE_string(path, \"/tmp/testfile\", \"Path to the file to manipulate\");\nDEFINE_int32(file_size, 1000, \"Length of file in 4k blocks\");\nDEFINE_int32(concurrent_requests, 100, \"Number of concurrent requests\");\nDEFINE_int32(min_nr, 1, \"min_nr\");\nDEFINE_int32(max_nr, 1, \"max_nr\");\n\n// The size of operation that will occur on the device\nstatic const int kPageSize = 4096;\n\nclass AIORequest {\n public:\n  int* buffer_;\n\n  virtual void Complete(int res) = 0;\n\n  AIORequest() {\n    int ret = posix_memalign(reinterpret_cast<void**>(&buffer_),\n                             kPageSize, kPageSize);\n    CHECK_EQ(ret, 0);\n  }\n\n  virtual ~AIORequest() {\n    free(buffer_);\n  }\n};\n\nclass Adder {\n public:\n  virtual void Add(int amount) = 0;\n\n  virtual ~Adder() { };\n};\n\nclass AIOReadRequest : public AIORequest {\n private:\n  Adder* adder_;\n\n public:\n  AIOReadRequest(Adder* adder) : AIORequest(), adder_(adder) { }\n\n  virtual void Complete(int res) {\n    CHECK_EQ(res, kPageSize) << \"Read incomplete or error \" << res;\n    int value = buffer_[0];\n    LOG(INFO) << \"Read of \" << value << \" completed\";\n    adder_->Add(value);\n  }\n};\n\nclass AIOWriteRequest : public AIORequest {\n private:\n  int value_;\n\n public:\n  AIOWriteRequest(int value) : AIORequest(), value_(value) {\n    buffer_[0] = value;\n  }\n\n  virtual void Complete(int res) {\n    CHECK_EQ(res, kPageSize) << \"Write incomplete or error \" << res;\n    LOG(INFO) << \"Write of \" << value_ << \" completed\";\n  }\n};\n\nclass AIOAdder : public Adder {\n public:\n  int fd_;\n  io_context_t ioctx_;\n  int counter_;\n  int reap_counter_;\n  int sum_;\n  int length_;\n\n  AIOAdder(int length)\n      : ioctx_(0), counter_(0), reap_counter_(0), sum_(0), length_(length) { }\n\n  void Init() {\n    LOG(INFO) << \"Opening file\";\n    fd_ = open(FLAGS_path.c_str(), O_RDWR | O_DIRECT | O_CREAT, 0644);\n    PCHECK(fd_ >= 0) << \"Error opening file\";\n    LOG(INFO) << \"Allocating enough space for the sum\";\n    PCHECK(fallocate(fd_, 0, 0, kPageSize * length_) >= 0) << \"Error in fallocate\";\n    LOG(INFO) << \"Setting up the io context\";\n    PCHECK(io_setup(100, &ioctx_) >= 0) << \"Error in io_setup\";\n  }\n\n  virtual void Add(int amount) {\n    sum_ += amount;\n    LOG(INFO) << \"Adding \" << amount << \" for a total of \" << sum_;\n  }\n\n  void SubmitWrite() {\n    LOG(INFO) << \"Submitting a write to \" << counter_;\n    struct iocb iocb;\n    struct iocb* iocbs = &iocb;\n    AIORequest *req = new AIOWriteRequest(counter_);\n    io_prep_pwrite(&iocb, fd_, req->buffer_, kPageSize, counter_ * kPageSize);\n    iocb.data = req;\n    int res = io_submit(ioctx_, 1, &iocbs);\n    CHECK_EQ(res, 1);\n  }\n\n  void WriteFile() {\n    reap_counter_ = 0;\n    for (counter_ = 0; counter_ < length_; counter_++) {\n      SubmitWrite();\n      Reap();\n    }\n    ReapRemaining();\n  }\n\n  void SubmitRead() {\n    LOG(INFO) << \"Submitting a read from \" << counter_;\n    struct iocb iocb;\n    struct iocb* iocbs = &iocb;\n    AIORequest *req = new AIOReadRequest(this);\n    io_prep_pread(&iocb, fd_, req->buffer_, kPageSize, counter_ * kPageSize);\n    iocb.data = req;\n    int res = io_submit(ioctx_, 1, &iocbs);\n    CHECK_EQ(res, 1);\n  }\n\n  void ReadFile() {\n    reap_counter_ = 0;\n    for (counter_ = 0; counter_ < length_; counter_++) {\n        SubmitRead();\n        Reap();\n    }\n    ReapRemaining();\n  }\n\n  int DoReap(int min_nr) {\n    LOG(INFO) << \"Reaping between \" << min_nr << \" and \"\n              << FLAGS_max_nr << \" io_events\";\n    struct io_event* events = new io_event[FLAGS_max_nr];\n    struct timespec timeout;\n    timeout.tv_sec = 0;\n    timeout.tv_nsec = 100000000;\n    int num_events;\n    LOG(INFO) << \"Calling io_getevents\";\n    num_events = io_getevents(ioctx_, min_nr, FLAGS_max_nr, events,\n                              &timeout);\n    LOG(INFO) << \"Calling completion function on results\";\n    for (int i = 0; i < num_events; i++) {\n      struct io_event event = events[i];\n      AIORequest* req = static_cast<AIORequest*>(event.data);\n      req->Complete(event.res);\n      delete req;\n    }\n    delete events;\n    \nLOG(INFO) << \"Reaped \" << num_events << \" io_events\";\n    reap_counter_ += num_events;\n    return num_events;\n  }\n\n  void Reap() {\n    if (counter_ >= FLAGS_min_nr) {\n      DoReap(FLAGS_min_nr);\n    }\n  }\n\n  void ReapRemaining() {\n    while (reap_counter_ < length_) {\n      DoReap(1);\n    }\n  }\n\n  ~AIOAdder() {\n    LOG(INFO) << \"Closing AIO context and file\";\n    io_destroy(ioctx_);\n    close(fd_);\n  }\n\n  int Sum() {\n    LOG(INFO) << \"Writing consecutive integers to file\";\n    WriteFile();\n    LOG(INFO) << \"Reading consecutive integers from file\";\n    ReadFile();\n    return sum_;\n  }\n};\n\nint main(int argc, char* argv[]) {\n  google::ParseCommandLineFlags(&argc, &argv, true);\n  AIOAdder adder(FLAGS_file_size);\n  adder.Init();\n  int sum = adder.Sum();\n  int expected = (FLAGS_file_size * (FLAGS_file_size - 1)) / 2;\n  LOG(INFO) << \"AIO is complete\";\n  CHECK_EQ(sum, expected) << \"Expected \" << expected << \" Got \" << sum;\n  printf(\"Successfully calculated that the sum of integers from 0\"\n         \" to %d is %d\\n\", FLAGS_file_size - 1, sum);\n  return 0;\n}\n```\n"
  }
]