aboutsummaryrefslogtreecommitdiffstats
path: root/linux-aio.c
diff options
context:
space:
mode:
authorChristoph Hellwig <hch@lst.de>2009-08-20 16:58:35 +0200
committerAnthony Liguori <aliguori@us.ibm.com>2009-08-27 20:30:22 -0500
commit5c6c3a6c54b23caa84fb4e046e85a461612279bb (patch)
tree7c23c2c86ca96f0a1b3dcbb86c70d5989e156710 /linux-aio.c
parent9ef91a677110ec200d7b2904fc4bcae5a77329ad (diff)
raw-posix: add Linux native AIO support
Now that do have a nicer interface to work against we can add Linux native AIO support. It's an extremly thing layer just setting up an iocb for the io_submit system call in the submission path, and registering an eventfd with the qemu poll handler to do complete the iocbs directly from there. This started out based on Anthony's earlier AIO patch, but after estimated 42,000 rewrites and just as many build system changes there's not much left of it. To enable native kernel aio use the aio=native sub-command on the drive command line. I have also added an option to qemu-io to test the aio support without needing a guest. Signed-off-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Diffstat (limited to 'linux-aio.c')
-rw-r--r--linux-aio.c204
1 files changed, 204 insertions, 0 deletions
diff --git a/linux-aio.c b/linux-aio.c
new file mode 100644
index 000000000..f53a08cb0
--- /dev/null
+++ b/linux-aio.c
@@ -0,0 +1,204 @@
+/*
+ * Linux native AIO support.
+ *
+ * Copyright (C) 2009 IBM, Corp.
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qemu-common.h"
+#include "qemu-aio.h"
+#include "block_int.h"
+#include "block/raw-posix-aio.h"
+
+#include <sys/eventfd.h>
+#include <libaio.h>
+
+/*
+ * Queue size (per-device).
+ *
+ * XXX: eventually we need to communicate this to the guest and/or make it
+ * tunable by the guest. If we get more outstanding requests at a time
+ * than this we will get EAGAIN from io_submit which is communicated to
+ * the guest as an I/O error.
+ */
+#define MAX_EVENTS 128
+
+struct qemu_laiocb {
+ BlockDriverAIOCB common;
+ struct qemu_laio_state *ctx;
+ struct iocb iocb;
+ ssize_t ret;
+ size_t nbytes;
+};
+
+struct qemu_laio_state {
+ io_context_t ctx;
+ int efd;
+ int count;
+};
+
+static inline ssize_t io_event_ret(struct io_event *ev)
+{
+ return (ssize_t)(((uint64_t)ev->res2 << 32) | ev->res);
+}
+
+static void qemu_laio_completion_cb(void *opaque)
+{
+ struct qemu_laio_state *s = opaque;
+
+ while (1) {
+ struct io_event events[MAX_EVENTS];
+ uint64_t val;
+ ssize_t ret;
+ struct timespec ts = { 0 };
+ int nevents, i;
+
+ do {
+ ret = read(s->efd, &val, sizeof(val));
+ } while (ret == 1 && errno == EINTR);
+
+ if (ret == -1 && errno == EAGAIN)
+ break;
+
+ if (ret != 8)
+ break;
+
+ do {
+ nevents = io_getevents(s->ctx, val, MAX_EVENTS, events, &ts);
+ } while (nevents == -EINTR);
+
+ for (i = 0; i < nevents; i++) {
+ struct iocb *iocb = events[i].obj;
+ struct qemu_laiocb *laiocb =
+ container_of(iocb, struct qemu_laiocb, iocb);
+
+ s->count--;
+
+ ret = laiocb->ret = io_event_ret(&events[i]);
+ if (ret != -ECANCELED) {
+ if (ret == laiocb->nbytes)
+ ret = 0;
+ else if (ret >= 0)
+ ret = -EINVAL;
+
+ laiocb->common.cb(laiocb->common.opaque, ret);
+ }
+
+ qemu_aio_release(laiocb);
+ }
+ }
+}
+
+static int qemu_laio_flush_cb(void *opaque)
+{
+ struct qemu_laio_state *s = opaque;
+
+ return (s->count > 0) ? 1 : 0;
+}
+
+static void laio_cancel(BlockDriverAIOCB *blockacb)
+{
+ struct qemu_laiocb *laiocb = (struct qemu_laiocb *)blockacb;
+ struct io_event event;
+ int ret;
+
+ if (laiocb->ret != -EINPROGRESS)
+ return;
+
+ /*
+ * Note that as of Linux 2.6.31 neither the block device code nor any
+ * filesystem implements cancellation of AIO request.
+ * Thus the polling loop below is the normal code path.
+ */
+ ret = io_cancel(laiocb->ctx->ctx, &laiocb->iocb, &event);
+ if (ret == 0) {
+ laiocb->ret = -ECANCELED;
+ return;
+ }
+
+ /*
+ * We have to wait for the iocb to finish.
+ *
+ * The only way to get the iocb status update is by polling the io context.
+ * We might be able to do this slightly more optimal by removing the
+ * O_NONBLOCK flag.
+ */
+ while (laiocb->ret == -EINPROGRESS)
+ qemu_laio_completion_cb(laiocb->ctx);
+}
+
+static AIOPool laio_pool = {
+ .aiocb_size = sizeof(struct qemu_laiocb),
+ .cancel = laio_cancel,
+};
+
+BlockDriverAIOCB *laio_submit(BlockDriverState *bs, void *aio_ctx, int fd,
+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
+ BlockDriverCompletionFunc *cb, void *opaque, int type)
+{
+ struct qemu_laio_state *s = aio_ctx;
+ struct qemu_laiocb *laiocb;
+ struct iocb *iocbs;
+ off_t offset = sector_num * 512;
+
+ laiocb = qemu_aio_get(&laio_pool, bs, cb, opaque);
+ if (!laiocb)
+ return NULL;
+ laiocb->nbytes = nb_sectors * 512;
+ laiocb->ctx = s;
+ laiocb->ret = -EINPROGRESS;
+
+ iocbs = &laiocb->iocb;
+
+ switch (type) {
+ case QEMU_AIO_WRITE:
+ io_prep_pwritev(iocbs, fd, qiov->iov, qiov->niov, offset);
+ break;
+ case QEMU_AIO_READ:
+ io_prep_preadv(iocbs, fd, qiov->iov, qiov->niov, offset);
+ break;
+ default:
+ fprintf(stderr, "%s: invalid AIO request type 0x%x.\n",
+ __func__, type);
+ goto out_free_aiocb;
+ }
+ io_set_eventfd(&laiocb->iocb, s->efd);
+ s->count++;
+
+ if (io_submit(s->ctx, 1, &iocbs) < 0)
+ goto out_dec_count;
+ return &laiocb->common;
+
+out_free_aiocb:
+ qemu_aio_release(laiocb);
+out_dec_count:
+ s->count--;
+ return NULL;
+}
+
+void *laio_init(void)
+{
+ struct qemu_laio_state *s;
+
+ s = qemu_mallocz(sizeof(*s));
+ s->efd = eventfd(0, 0);
+ if (s->efd == -1)
+ goto out_free_state;
+ fcntl(s->efd, F_SETFL, O_NONBLOCK);
+
+ if (io_setup(MAX_EVENTS, &s->ctx) != 0)
+ goto out_close_efd;
+
+ qemu_aio_set_fd_handler(s->efd, qemu_laio_completion_cb,
+ NULL, qemu_laio_flush_cb, s);
+
+ return s;
+
+out_close_efd:
+ close(s->efd);
+out_free_state:
+ qemu_free(s);
+ return NULL;
+}