mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 18:21:39 +01:00
Move event callbacks registration into tapdisk-queue. This should also obsoletes the dummy pollfd pipe in the synchronous I/O case. Signed-off-by: Daniel Stodden <daniel.stodden@citrix.com>
672 lines
15 KiB
C
672 lines
15 KiB
C
/*
|
|
* Copyright (c) 2008, XenSource Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of XenSource Inc. nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
|
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <time.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <inttypes.h>
|
|
|
|
#include "io-optimize.h"
|
|
#include "tapdisk-log.h"
|
|
|
|
#if (!defined(TEST) && defined(DEBUG))
|
|
#define DBG(ctx, f, a...) tlog_write(TLOG_DBG, f, ##a)
|
|
#elif defined(TEST)
|
|
#define DBG(ctx, f, a...) printf(f, ##a)
|
|
#else
|
|
#define DBG(ctx, f, a...) ((void)0)
|
|
#endif
|
|
|
|
static void print_merged_iocbs(struct opioctx *ctx,
|
|
struct iocb **iocbs, int num_iocbs);
|
|
|
|
void
|
|
opio_free(struct opioctx *ctx)
|
|
{
|
|
free(ctx->opios);
|
|
ctx->opios = NULL;
|
|
|
|
free(ctx->free_opios);
|
|
ctx->free_opios = NULL;
|
|
|
|
free(ctx->iocb_queue);
|
|
ctx->iocb_queue = NULL;
|
|
|
|
free(ctx->event_queue);
|
|
ctx->event_queue = NULL;
|
|
}
|
|
|
|
int
|
|
opio_init(struct opioctx *ctx, int num_iocbs)
|
|
{
|
|
int i;
|
|
|
|
memset(ctx, 0, sizeof(struct opioctx));
|
|
|
|
ctx->num_opios = num_iocbs;
|
|
ctx->free_opio_cnt = num_iocbs;
|
|
ctx->opios = calloc(1, sizeof(struct opio) * num_iocbs);
|
|
ctx->free_opios = calloc(1, sizeof(struct opio *) * num_iocbs);
|
|
ctx->iocb_queue = calloc(1, sizeof(struct iocb *) * num_iocbs);
|
|
ctx->event_queue = calloc(1, sizeof(struct io_event) * num_iocbs);
|
|
|
|
if (!ctx->opios || !ctx->free_opios ||
|
|
!ctx->iocb_queue || !ctx->event_queue)
|
|
goto fail;
|
|
|
|
for (i = 0; i < num_iocbs; i++)
|
|
ctx->free_opios[i] = &ctx->opios[i];
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
opio_free(ctx);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static inline struct opio *
|
|
alloc_opio(struct opioctx *ctx)
|
|
{
|
|
if (ctx->free_opio_cnt <= 0)
|
|
return NULL;
|
|
return ctx->free_opios[--ctx->free_opio_cnt];
|
|
}
|
|
|
|
static inline void
|
|
free_opio(struct opioctx *ctx, struct opio *op)
|
|
{
|
|
memset(op, 0, sizeof(struct opio));
|
|
ctx->free_opios[ctx->free_opio_cnt++] = op;
|
|
}
|
|
|
|
static inline void
|
|
restore_iocb(struct opio *op)
|
|
{
|
|
struct iocb *io = op->iocb;
|
|
|
|
io->data = op->data;
|
|
io->u.c.buf = op->buf;
|
|
io->u.c.nbytes = op->nbytes;
|
|
}
|
|
|
|
static inline int
|
|
iocb_optimized(struct opioctx *ctx, struct iocb *io)
|
|
{
|
|
unsigned long iop = (unsigned long)io->data;
|
|
unsigned long start = (unsigned long)ctx->opios;
|
|
unsigned long end = start + (ctx->num_opios * sizeof(struct opio));
|
|
|
|
return (iop >= start && iop < end);
|
|
}
|
|
|
|
static inline int
|
|
contiguous_sectors(struct iocb *l, struct iocb *r)
|
|
{
|
|
return (l->u.c.offset + l->u.c.nbytes == r->u.c.offset);
|
|
}
|
|
|
|
static inline int
|
|
contiguous_buffers(struct iocb *l, struct iocb *r)
|
|
{
|
|
return (l->u.c.buf + l->u.c.nbytes == r->u.c.buf);
|
|
}
|
|
|
|
static inline int
|
|
contiguous_iocbs(struct iocb *l, struct iocb *r)
|
|
{
|
|
return ((l->aio_fildes == r->aio_fildes) &&
|
|
contiguous_sectors(l, r) &&
|
|
contiguous_buffers(l, r));
|
|
}
|
|
|
|
static inline void
|
|
init_opio_list(struct opio *op)
|
|
{
|
|
op->list.head = op->list.tail = op;
|
|
}
|
|
|
|
static struct opio *
|
|
opio_iocb_init(struct opioctx *ctx, struct iocb *io)
|
|
{
|
|
struct opio *op;
|
|
|
|
op = alloc_opio(ctx);
|
|
if (!op)
|
|
return NULL;
|
|
|
|
op->buf = io->u.c.buf;
|
|
op->nbytes = io->u.c.nbytes;
|
|
op->offset = io->u.c.offset;
|
|
op->data = io->data;
|
|
op->iocb = io;
|
|
io->data = op;
|
|
|
|
init_opio_list(op);
|
|
|
|
return op;
|
|
}
|
|
|
|
static inline struct opio *
|
|
opio_get(struct opioctx *ctx, struct iocb *io)
|
|
{
|
|
if (iocb_optimized(ctx, io))
|
|
return (struct opio *)io->data;
|
|
else
|
|
return opio_iocb_init(ctx, io);
|
|
}
|
|
|
|
static int
|
|
merge_tail(struct opioctx *ctx, struct iocb *head, struct iocb *io)
|
|
{
|
|
struct opio *ophead, *opio;
|
|
|
|
ophead = opio_get(ctx, head);
|
|
if (!ophead)
|
|
return -ENOMEM;
|
|
|
|
opio = opio_get(ctx, io);
|
|
if (!opio)
|
|
return -ENOMEM;
|
|
|
|
opio->head = ophead;
|
|
head->u.c.nbytes += io->u.c.nbytes;
|
|
ophead->list.tail = ophead->list.tail->next = opio;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
merge(struct opioctx *ctx, struct iocb *head, struct iocb *io)
|
|
{
|
|
if (head->aio_lio_opcode != io->aio_lio_opcode)
|
|
return -EINVAL;
|
|
|
|
if (!contiguous_iocbs(head, io))
|
|
return -EINVAL;
|
|
|
|
return merge_tail(ctx, head, io);
|
|
}
|
|
|
|
int
|
|
io_merge(struct opioctx *ctx, struct iocb **queue, int num)
|
|
{
|
|
int i, on_queue;
|
|
struct iocb *io, **q;
|
|
struct opio *ophead;
|
|
|
|
if (!num)
|
|
return 0;
|
|
|
|
on_queue = 0;
|
|
q = ctx->iocb_queue;
|
|
memcpy(q, queue, num * sizeof(struct iocb *));
|
|
|
|
for (i = 1; i < num; i++) {
|
|
io = q[i];
|
|
if (merge(ctx, queue[on_queue], io) != 0)
|
|
queue[++on_queue] = io;
|
|
}
|
|
|
|
#if (defined(TEST) || defined(DEBUG))
|
|
print_merged_iocbs(ctx, queue, on_queue + 1);
|
|
#endif
|
|
|
|
return ++on_queue;
|
|
}
|
|
|
|
static int
|
|
expand_iocb(struct opioctx *ctx, struct iocb **queue, struct iocb *io)
|
|
{
|
|
int idx;
|
|
struct opio *op, *next;
|
|
|
|
idx = 0;
|
|
op = (struct opio *)io->data;
|
|
while (op) {
|
|
next = op->next;
|
|
restore_iocb(op);
|
|
queue[idx++] = op->iocb;
|
|
free_opio(ctx, op);
|
|
op = next;
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
int
|
|
io_expand_iocbs(struct opioctx *ctx, struct iocb **queue, int idx, int num)
|
|
{
|
|
int i, on_queue;
|
|
struct iocb *io, **q;
|
|
|
|
if (!num)
|
|
return 0;
|
|
|
|
on_queue = 0;
|
|
q = ctx->iocb_queue;
|
|
memcpy(q, queue, num * sizeof(struct iocb *));
|
|
|
|
for (i = idx; i < num; i++) {
|
|
io = q[i];
|
|
if (!iocb_optimized(ctx, io))
|
|
queue[on_queue++] = io;
|
|
else
|
|
on_queue += expand_iocb(ctx, queue + on_queue, io);
|
|
}
|
|
|
|
return on_queue;
|
|
}
|
|
|
|
static int
|
|
expand_event(struct opioctx *ctx,
|
|
struct io_event *event, struct io_event *queue, int idx)
|
|
{
|
|
int err;
|
|
struct iocb *io;
|
|
struct io_event *ep;
|
|
struct opio *ophead, *op, *next;
|
|
|
|
io = event->obj;
|
|
ophead = (struct opio *)io->data;
|
|
op = ophead;
|
|
|
|
if (event->res == io->u.c.nbytes)
|
|
err = 0;
|
|
else if ((int)event->res < 0)
|
|
err = (int)event->res;
|
|
else
|
|
err = -EIO;
|
|
|
|
while (op) {
|
|
next = op->next;
|
|
ep = &queue[idx++];
|
|
ep->obj = op->iocb;
|
|
ep->res = (err ? err : op->nbytes);
|
|
restore_iocb(op);
|
|
free_opio(ctx, op);
|
|
op = next;
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
int
|
|
io_split(struct opioctx *ctx, struct io_event *events, int num)
|
|
{
|
|
int on_queue;
|
|
struct iocb *io;
|
|
struct io_event *ep, *q;
|
|
|
|
if (!num)
|
|
return 0;
|
|
|
|
on_queue = 0;
|
|
q = ctx->event_queue;
|
|
memcpy(q, events, num * sizeof(struct io_event));
|
|
|
|
for (ep = q; num-- > 0; ep++) {
|
|
io = ep->obj;
|
|
if (!iocb_optimized(ctx, io))
|
|
events[on_queue++] = *ep;
|
|
else
|
|
on_queue = expand_event(ctx, ep, events, on_queue);
|
|
}
|
|
|
|
return on_queue;
|
|
}
|
|
|
|
/******************************************************************************
|
|
debug print functions
|
|
******************************************************************************/
|
|
static inline void
|
|
__print_iocb(struct opioctx *ctx, struct iocb *io, char *prefix)
|
|
{
|
|
char *type;
|
|
|
|
type = (io->aio_lio_opcode == IO_CMD_PREAD ? "read" : "write");
|
|
|
|
DBG(ctx, "%soff: %08llx, nbytes: %04lx, buf: %p, type: %s, data: %08lx,"
|
|
" optimized: %d\n", prefix, io->u.c.offset, io->u.c.nbytes,
|
|
io->u.c.buf, type, (unsigned long)io->data,
|
|
iocb_optimized(ctx, io));
|
|
}
|
|
|
|
static char *null_prefix = "";
|
|
#define print_iocb(ctx, io) __print_iocb(ctx, io, null_prefix)
|
|
|
|
static void
|
|
print_iocbs(struct opioctx *ctx, struct iocb **iocbs, int num_iocbs)
|
|
{
|
|
int i;
|
|
char pref[10];
|
|
struct iocb *io;
|
|
|
|
DBG(ctx, "iocbs:\n");
|
|
for (i = 0; i < num_iocbs; i++) {
|
|
io = iocbs[i];
|
|
snprintf(pref, 10, "%d: ", i);
|
|
__print_iocb(ctx, io, pref);
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_optimized_iocbs(struct opioctx *ctx, struct opio *op, int *cnt)
|
|
{
|
|
char pref[10];
|
|
|
|
while (op) {
|
|
snprintf(pref, 10, " %d: ", (*cnt)++);
|
|
__print_iocb(ctx, op->iocb, pref);
|
|
op = op->next;
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_merged_iocbs(struct opioctx *ctx, struct iocb **iocbs, int num_iocbs)
|
|
{
|
|
int i, cnt;
|
|
char pref[10];
|
|
struct iocb *io;
|
|
struct opio *op;
|
|
|
|
DBG(ctx, "merged iocbs:\n");
|
|
for (i = 0, cnt = 0; i < num_iocbs; i++) {
|
|
io = iocbs[i];
|
|
snprintf(pref, 10, "%d: ", cnt++);
|
|
__print_iocb(ctx, io, pref);
|
|
|
|
if (iocb_optimized(ctx, io)) {
|
|
op = (struct opio *)io->data;
|
|
print_optimized_iocbs(ctx, op->next, &cnt);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_events(struct opioctx *ctx, struct io_event *events, int num_events)
|
|
{
|
|
int i;
|
|
struct iocb *io;
|
|
|
|
for (i = 0; i < num_events; i++) {
|
|
io = events[i].obj;
|
|
print_iocb(ctx, io);
|
|
}
|
|
}
|
|
/******************************************************************************
|
|
end debug print functions
|
|
******************************************************************************/
|
|
|
|
#if defined(TEST)
|
|
|
|
#define hmask 0x80000000UL
|
|
#define smask 0x40000000UL
|
|
#define make_data(idx, is_head, sparse) \
|
|
(void *)((idx) | ((is_head) ? hmask : 0) | ((sparse) ? smask : 0))
|
|
#define data_idx(data) (int)((unsigned long)(data) & (0x0fffffff))
|
|
#define data_is_head(data) (((unsigned long)(data) & hmask) ? 1 : 0)
|
|
#define data_is_sparse(data) (((unsigned long)(data) & smask) ? 1 : 0)
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "usage: io_optimize [-n num_runs] "
|
|
"[-i num_iocbs] [-s num_secs] [-r random_seed]\n");
|
|
exit(-1);
|
|
}
|
|
|
|
static int xalloc_cnt, xfree_cnt;
|
|
static inline char *
|
|
xalloc(int size)
|
|
{
|
|
char *buf = malloc(size);
|
|
if (!buf) {
|
|
fprintf(stderr, "xalloc failed\n");
|
|
exit(ENOMEM);
|
|
}
|
|
xalloc_cnt++;
|
|
return buf;
|
|
}
|
|
|
|
static inline void
|
|
xfree(void *buf)
|
|
{
|
|
free(buf);
|
|
xfree_cnt++;
|
|
}
|
|
|
|
static void
|
|
randomize_iocbs(struct iocb **iocbs, int num_iocbs, int num_secs)
|
|
{
|
|
int i, j;
|
|
|
|
i = 0;
|
|
while (i < num_iocbs) {
|
|
char *buf;
|
|
short type;
|
|
int segs, sparse_mem;
|
|
uint64_t offset, nbytes;
|
|
|
|
type = (random() % 10 < 5 ? IO_CMD_PREAD : IO_CMD_PWRITE);
|
|
offset = ((random() % num_secs) << 9);
|
|
|
|
if (random() % 10 < 4) {
|
|
segs = 1;
|
|
nbytes = (((random() % 7) + 1) << 9);
|
|
} else {
|
|
segs = (random() % 10) + 1;
|
|
nbytes = 4096;
|
|
}
|
|
|
|
if (i + segs > num_iocbs)
|
|
segs = (num_iocbs - i);
|
|
|
|
sparse_mem = (random() % 10 < 2 ? 1 : 0);
|
|
|
|
if (sparse_mem)
|
|
buf = xalloc(nbytes);
|
|
else
|
|
buf = xalloc(segs * nbytes);
|
|
|
|
for (j = 0; j < segs; j++) {
|
|
struct iocb *io = iocbs[i + j];
|
|
io->aio_lio_opcode = type;
|
|
io->u.c.nbytes = nbytes;
|
|
io->u.c.offset = offset;
|
|
io->u.c.buf = buf;
|
|
offset += nbytes;
|
|
|
|
io->data = make_data(i + j, (j == 0), sparse_mem);
|
|
|
|
if (j + 1 < segs && sparse_mem)
|
|
buf = xalloc(nbytes);
|
|
else
|
|
buf += nbytes;
|
|
}
|
|
|
|
i += segs;
|
|
}
|
|
}
|
|
|
|
static int
|
|
simulate_io(struct iocb **iocbs, struct io_event *events, int num_iocbs)
|
|
{
|
|
int i, done;
|
|
struct iocb *io;
|
|
struct io_event *ep;
|
|
|
|
if (num_iocbs > 1)
|
|
done = (random() % (num_iocbs - 1)) + 1;
|
|
else
|
|
done = num_iocbs;
|
|
|
|
for (i = 0; i < done; i++) {
|
|
io = iocbs[i];
|
|
ep = &events[i];
|
|
ep->obj = io;
|
|
ep->res = (random() % 10 < 8 ? io->u.c.nbytes : 0);
|
|
}
|
|
|
|
return done;
|
|
}
|
|
|
|
static inline void
|
|
process_events(struct opioctx *ctx,
|
|
struct iocb *iocb_list, struct io_event *events, int num)
|
|
{
|
|
int i;
|
|
struct iocb *io;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
io = events[i].obj;
|
|
print_iocb(ctx, io);
|
|
if (data_idx(io->data) != (io - iocb_list)) {
|
|
printf("corrupt data! data_idx = %d, io = %d\n",
|
|
data_idx(io->data), (io - iocb_list));
|
|
exit(-1);
|
|
}
|
|
if (data_is_head(io->data) || data_is_sparse(io->data))
|
|
xfree(io->u.c.buf);
|
|
memset(io, 0, sizeof(struct iocb));
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
init_optest(struct iocb *iocb_list,
|
|
struct iocb **iocbs, struct io_event *events, int num)
|
|
{
|
|
int i;
|
|
|
|
memset(iocb_list, 0, num * sizeof(struct iocb));
|
|
memset(events, 0, num * sizeof(struct io_event));
|
|
|
|
for (i = 0; i < num; i++)
|
|
iocbs[i] = &iocb_list[i];
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
uint64_t num_secs;
|
|
struct opioctx ctx;
|
|
struct io_event *events;
|
|
int i, c, num_runs, num_iocbs, seed;
|
|
struct iocb *iocb_list, **iocbs, **ioqueue;
|
|
|
|
num_runs = 1;
|
|
num_iocbs = 300;
|
|
seed = time(NULL);
|
|
num_secs = ((4ULL << 20) >> 9); /* 4GB disk */
|
|
|
|
while ((c = getopt(argc, argv, "n:i:s:r:h")) != -1) {
|
|
switch (c) {
|
|
case 'n':
|
|
num_runs = atoi(optarg);
|
|
break;
|
|
case 'i':
|
|
num_iocbs = atoi(optarg);
|
|
break;
|
|
case 's':
|
|
num_secs = strtoull(optarg, NULL, 10);
|
|
break;
|
|
case 'r':
|
|
seed = atoi(optarg);
|
|
break;
|
|
case 'h':
|
|
usage();
|
|
case '?':
|
|
fprintf(stderr, "Unrecognized option: -%c\n", optopt);
|
|
usage();
|
|
}
|
|
}
|
|
|
|
printf("Running %d tests with %d iocbs on %llu sectors, seed = %d\n",
|
|
num_runs, num_iocbs, num_secs, seed);
|
|
|
|
srand(seed);
|
|
|
|
iocb_list = malloc(num_iocbs * sizeof(struct iocb));
|
|
iocbs = malloc(num_iocbs * sizeof(struct iocb *));
|
|
events = malloc(num_iocbs * sizeof(struct io_event));
|
|
|
|
if (!iocb_list || !iocbs || !events || opio_init(&ctx, num_iocbs)) {
|
|
fprintf(stderr, "initialization failed\n");
|
|
exit(ENOMEM);
|
|
}
|
|
|
|
for (i = 0; i < num_runs; i++) {
|
|
int op_rem, op_done, num_split, num_events, num_done;
|
|
|
|
ioqueue = iocbs;
|
|
init_optest(iocb_list, ioqueue, events, num_iocbs);
|
|
randomize_iocbs(ioqueue, num_iocbs, num_secs);
|
|
print_iocbs(&ctx, ioqueue, num_iocbs);
|
|
|
|
op_done = 0;
|
|
num_done = 0;
|
|
op_rem = io_merge(&ctx, ioqueue, num_iocbs);
|
|
print_iocbs(&ctx, ioqueue, op_rem);
|
|
print_merged_iocbs(&ctx, ioqueue, op_rem);
|
|
|
|
while (num_done < num_iocbs) {
|
|
DBG(&ctx, "optimized remaining: %d\n", op_rem);
|
|
|
|
DBG(&ctx, "simulating\n");
|
|
num_events = simulate_io(ioqueue + op_done, events, op_rem);
|
|
print_events(&ctx, events, num_events);
|
|
|
|
DBG(&ctx, "splitting %d\n", num_events);
|
|
num_split = io_split(&ctx, events, num_events);
|
|
print_events(&ctx, events, num_split);
|
|
|
|
DBG(&ctx, "processing %d\n", num_split);
|
|
process_events(&ctx, iocb_list, events, num_split);
|
|
|
|
op_rem -= num_events;
|
|
op_done += num_events;
|
|
num_done += num_split;
|
|
}
|
|
|
|
DBG(&ctx, "run %d: processed: %d, xallocs: %d, xfrees: %d\n",
|
|
i, num_done, xalloc_cnt, xfree_cnt);
|
|
if (xalloc_cnt != xfree_cnt)
|
|
exit(-1);
|
|
xalloc_cnt = xfree_cnt = 0;
|
|
}
|
|
|
|
free(iocbs);
|
|
free(events);
|
|
free(iocb_list);
|
|
opio_free(&ctx);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|