add syscall unit tests
Add tests for io_uring_setup, io_uring_register and io_uring_enter. The test coverage is nowhere near complete and the reporting is not uniform. But, it's a start. Signed-off-by: Jeff Moyer <jmoyer@redhat.com> Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
CFLAGS ?= -g -O2 -Wall -D_GNU_SOURCE -L../src/
|
||||
|
||||
all_targets += io_uring-test io_uring-cp poll poll-cancel ring-leak fsync
|
||||
all_targets += io_uring-test io_uring-cp poll poll-cancel ring-leak fsync \
|
||||
io_uring_setup io_uring_register io_uring_enter
|
||||
|
||||
all: $(all_targets)
|
||||
|
||||
test_srcs := io_uring-test.c io_uring-cp.c poll.c poll-cancel.c ring-leak.c fsync.c
|
||||
test_srcs := io_uring-test.c io_uring-cp.c poll.c poll-cancel.c ring-leak.c \
|
||||
fsync.c io_uring_setup.c io_uring_register.c io_uring_enter.c
|
||||
|
||||
test_objs := $(patsubst %.c,%.ol,$(test_srcs))
|
||||
|
||||
@@ -20,5 +22,11 @@ ring-leak: ring-leak.c
|
||||
$(CC) $(CFLAGS) -o $@ ring-leak.c -luring
|
||||
fsync: fsync.c
|
||||
$(CC) $(CFLAGS) -o $@ fsync.c -luring
|
||||
io_uring_setup: io_uring_setup.c
|
||||
$(CC) $(CFLAGS) -o $@ io_uring_setup.c -luring
|
||||
io_uring_register: io_uring_register.c
|
||||
$(CC) $(CFLAGS) -o $@ io_uring_register.c -luring
|
||||
io_uring_enter: io_uring_enter.c
|
||||
$(CC) $(CFLAGS) -o $@ io_uring_enter.c -luring
|
||||
clean:
|
||||
rm -f $(all_targets) $(test_objs)
|
||||
|
||||
282
test/io_uring_enter.c
Normal file
282
test/io_uring_enter.c
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* io_uring_enter.c
|
||||
*
|
||||
* Description: Unit tests for the io_uring_enter system call.
|
||||
*
|
||||
* Copyright 2019, Red Hat, Inc.
|
||||
* Author: Jeff Moyer <jmoyer@redhat.com>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <poll.h>
|
||||
#include <assert.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/mman.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <limits.h>
|
||||
#include <sys/time.h>
|
||||
#include "../src/liburing.h"
|
||||
#include "../src/barrier.h"
|
||||
|
||||
#define IORING_MAX_ENTRIES 4096
|
||||
|
||||
int
|
||||
expect_failed_submit(struct io_uring *ring, int error)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = io_uring_submit(ring);
|
||||
if (ret == 1) {
|
||||
printf("expected failure, but io_uring_submit succeeded.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (errno != error) {
|
||||
printf("expected %d, got %d\n", error, errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
expect_fail(int fd, unsigned int to_submit, unsigned int min_complete,
|
||||
unsigned int flags, sigset_t *sig, int error)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = io_uring_enter(fd, to_submit, min_complete, flags, sig);
|
||||
if (ret != -1) {
|
||||
printf("expected %s, but call succeeded\n", strerror(error));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (errno != error) {
|
||||
printf("expected %d, got %d\n", error, errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
try_io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete,
|
||||
unsigned int flags, sigset_t *sig, int expect, int error)
|
||||
{
|
||||
int ret;
|
||||
|
||||
printf("io_uring_enter(%d, %u, %u, %u, %p)\n", fd, to_submit,
|
||||
min_complete, flags, sig);
|
||||
|
||||
if (expect == -1)
|
||||
return expect_fail(fd, to_submit, min_complete,
|
||||
flags, sig, error);
|
||||
|
||||
ret = io_uring_enter(fd, to_submit, min_complete, flags, sig);
|
||||
if (ret != expect) {
|
||||
printf("Expected %d, got %d\n", expect, errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* prep a read I/O. index is treated like a block number.
|
||||
*/
|
||||
int
|
||||
setup_file(off_t len)
|
||||
{
|
||||
int fd, ret;
|
||||
static char template[32] = "/tmp/io_uring_enter-test.XXXXXX";
|
||||
char buf[4096];
|
||||
|
||||
fd = mkstemp(template);
|
||||
if (fd < 0) {
|
||||
perror("mkstemp");
|
||||
exit(1);
|
||||
}
|
||||
ret = ftruncate(fd, len);
|
||||
if (ret < 0) {
|
||||
perror("ftruncate");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ret = read(fd, buf, 4096);
|
||||
if (ret != 4096) {
|
||||
printf("read returned %d, expected 4096\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
void
|
||||
io_prep_read(struct io_uring_sqe *sqe, int fd, off_t offset, size_t len)
|
||||
{
|
||||
struct iovec *iov;
|
||||
|
||||
iov = malloc(sizeof(*iov));
|
||||
assert(iov);
|
||||
|
||||
iov->iov_base = malloc(len);
|
||||
assert(iov->iov_base);
|
||||
iov->iov_len = len;
|
||||
|
||||
io_uring_prep_readv(sqe, fd, iov, 1, offset);
|
||||
io_uring_sqe_set_data(sqe, iov); // free on completion
|
||||
}
|
||||
|
||||
void
|
||||
reap_events(struct io_uring *ring, unsigned nr)
|
||||
{
|
||||
int ret;
|
||||
unsigned left = nr;
|
||||
struct io_uring_cqe *cqe;
|
||||
struct iovec *iov;
|
||||
struct timeval start, now, elapsed;
|
||||
|
||||
printf("Reaping %u I/Os\n", nr);
|
||||
gettimeofday(&start, NULL);
|
||||
while (left) {
|
||||
ret = io_uring_wait_completion(ring, &cqe);
|
||||
if (ret < 0) {
|
||||
printf("io_uring_wait_completion returned %d\n", ret);
|
||||
printf("expected success\n");
|
||||
exit(1);
|
||||
}
|
||||
if (cqe->res != 4096)
|
||||
printf("cqe->res: %d, expected 4096\n", cqe->res);
|
||||
iov = (struct iovec *)cqe->user_data;
|
||||
free(iov->iov_base);
|
||||
free(iov);
|
||||
left--;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
timersub(&now, &start, &elapsed);
|
||||
if (elapsed.tv_sec > 10) {
|
||||
printf("Timed out waiting for I/Os to complete.\n");
|
||||
printf("%u expected, %u completed\n", nr, left);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
submit_io(struct io_uring *ring, unsigned nr)
|
||||
{
|
||||
int fd, ret;
|
||||
off_t file_len;
|
||||
unsigned i;
|
||||
struct io_uring_sqe *sqe;
|
||||
|
||||
printf("Allocating %u sqes\n", nr);
|
||||
file_len = nr * 4096;
|
||||
fd = setup_file(file_len);
|
||||
for (i = 0; i < nr; i++) {
|
||||
/* allocate an sqe */
|
||||
sqe = io_uring_get_sqe(ring);
|
||||
/* fill it in */
|
||||
io_prep_read(sqe, fd, i * 4096, 4096);
|
||||
}
|
||||
|
||||
/* submit the I/Os */
|
||||
printf("Submitting %u I/Os\n", nr);
|
||||
ret = io_uring_submit(ring);
|
||||
if (ret < 0) {
|
||||
perror("io_uring_enter");
|
||||
exit(1);
|
||||
}
|
||||
printf("Done\n");
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
unsigned int status = 0;
|
||||
struct io_uring ring;
|
||||
struct io_uring_sq *sq = &ring.sq;
|
||||
unsigned ktail, mask, index;
|
||||
unsigned sq_entries;
|
||||
unsigned completed, dropped;
|
||||
|
||||
ret = io_uring_queue_init(IORING_MAX_ENTRIES, &ring, 0);
|
||||
if (ret < 0) {
|
||||
perror("io_uring_queue_init");
|
||||
exit(1);
|
||||
}
|
||||
mask = *sq->kring_mask;
|
||||
|
||||
/* invalid flags */
|
||||
status |= try_io_uring_enter(ring.ring_fd, 1, 0, ~0U, NULL, -1, EINVAL);
|
||||
|
||||
/* invalid fd, EBADF */
|
||||
status |= try_io_uring_enter(-1, 0, 0, 0, NULL, -1, EBADF);
|
||||
|
||||
/* valid, non-ring fd, EOPNOTSUPP */
|
||||
status |= try_io_uring_enter(0, 0, 0, 0, NULL, -1, EOPNOTSUPP);
|
||||
|
||||
/* to_submit: 0, flags: 0; should get back 0. */
|
||||
status |= try_io_uring_enter(ring.ring_fd, 1, 0, 0, NULL, 0, 0);
|
||||
|
||||
/* fill the sq ring */
|
||||
sq_entries = *ring.sq.kring_entries;
|
||||
submit_io(&ring, sq_entries);
|
||||
printf("Waiting for %u events\n", sq_entries);
|
||||
ret = io_uring_enter(ring.ring_fd, 0, sq_entries,
|
||||
IORING_ENTER_GETEVENTS, NULL);
|
||||
if (ret < 0) {
|
||||
perror("io_uring_enter");
|
||||
status = 1;
|
||||
} else {
|
||||
/*
|
||||
* This is a non-IOPOLL ring, which means that io_uring_enter
|
||||
* should not return until min_complete events are available
|
||||
* in the completion queue.
|
||||
*/
|
||||
completed = *ring.cq.ktail - *ring.cq.khead;
|
||||
if (completed != sq_entries) {
|
||||
printf("Submitted %u I/Os, but only got %u completions\n",
|
||||
sq_entries, completed);
|
||||
status = 1;
|
||||
}
|
||||
reap_events(&ring, sq_entries);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an invalid index to the submission queue. This should
|
||||
* result in the dropped counter increasing.
|
||||
*/
|
||||
printf("Submitting invalid sqe index.\n");
|
||||
index = *sq->kring_entries + 1; // invalid index
|
||||
dropped = *sq->kdropped;
|
||||
ktail = *sq->ktail;
|
||||
sq->array[ktail & mask] = index;
|
||||
++ktail;
|
||||
write_barrier();
|
||||
*sq->ktail = ktail;
|
||||
write_barrier();
|
||||
|
||||
ret = io_uring_enter(ring.ring_fd, 1, 0, 0, NULL);
|
||||
/* now check to see if our sqe was dropped */
|
||||
if (*sq->kdropped == dropped) {
|
||||
printf("dropped counter did not increase\n");
|
||||
status = 1;
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
printf("PASS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("FAIL\n");
|
||||
return -1;
|
||||
}
|
||||
525
test/io_uring_register.c
Normal file
525
test/io_uring_register.c
Normal file
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
* io_uring_register.c
|
||||
*
|
||||
* Description: Unit tests for the io_uring_register system call.
|
||||
*
|
||||
* Copyright 2019, Red Hat, Inc.
|
||||
* Author: Jeff Moyer <jmoyer@redhat.com>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <poll.h>
|
||||
#include <assert.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/mman.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <limits.h>
|
||||
#include "../src/liburing.h"
|
||||
|
||||
static int pagesize;
|
||||
static rlim_t mlock_limit;
|
||||
static int devnull;
|
||||
|
||||
int
|
||||
expect_fail(int fd, unsigned int opcode, void *arg,
|
||||
unsigned int nr_args, int error)
|
||||
{
|
||||
int ret;
|
||||
|
||||
printf("io_uring_register(%d, %u, %p, %u)\n",
|
||||
fd, opcode, arg, nr_args);
|
||||
ret = io_uring_register(fd, opcode, arg, nr_args);
|
||||
if (ret != -1) {
|
||||
int ret2 = 0;
|
||||
|
||||
printf("expected %s, but call succeeded\n", strerror(error));
|
||||
if (opcode == IORING_REGISTER_BUFFERS) {
|
||||
ret2 = io_uring_register(fd, IORING_UNREGISTER_BUFFERS,
|
||||
0, 0);
|
||||
} else if (opcode == IORING_REGISTER_FILES) {
|
||||
ret2 = io_uring_register(fd, IORING_UNREGISTER_FILES,
|
||||
0, 0);
|
||||
}
|
||||
if (ret2) {
|
||||
printf("internal error: failed to unregister\n");
|
||||
exit(1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (errno != error) {
|
||||
printf("expected %d, got %d\n", error, errno);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
new_io_uring(int entries, struct io_uring_params *p)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = io_uring_setup(entries, p);
|
||||
if (fd < 0) {
|
||||
perror("io_uring_setup");
|
||||
exit(1);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
#define MAXFDS (UINT_MAX * sizeof(int))
|
||||
|
||||
void *
|
||||
map_filebacked(size_t size)
|
||||
{
|
||||
int fd, ret;
|
||||
void *addr;
|
||||
char template[32] = "io_uring_register-test-XXXXXXXX";
|
||||
|
||||
fd = mkstemp(template);
|
||||
if (fd < 0) {
|
||||
perror("mkstemp");
|
||||
return NULL;
|
||||
}
|
||||
unlink(template);
|
||||
|
||||
ret = ftruncate(fd, size);
|
||||
if (ret < 0) {
|
||||
perror("ftruncate");
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
perror("mmap");
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return addr;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: this is now limited by SCM_MAX_FD (253). Keep the code for now,
|
||||
* but probably should augment it to test 253 and 254, specifically.
|
||||
*/
|
||||
int
|
||||
test_max_fds(int uring_fd)
|
||||
{
|
||||
int status = 1;
|
||||
int ret;
|
||||
void *fd_as; /* file descriptor address space */
|
||||
int fdtable_fd; /* fd for the file that will be mapped over and over */
|
||||
int io_fd; /* the valid fd for I/O -- /dev/null */
|
||||
int *fds; /* used to map the file into the address space */
|
||||
char template[32] = "io_uring_register-test-XXXXXXXX";
|
||||
unsigned long long i, nr_maps, nr_fds;
|
||||
|
||||
/*
|
||||
* First, mmap anonymous the full size. That will guarantee the
|
||||
* mapping will fit in the memory area selected by mmap. Then,
|
||||
* over-write that mapping using a file-backed mapping, 128MiB at
|
||||
* a time using MAP_FIXED.
|
||||
*/
|
||||
fd_as = mmap(NULL, UINT_MAX * sizeof(int), PROT_READ|PROT_WRITE,
|
||||
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
|
||||
if (fd_as == MAP_FAILED) {
|
||||
perror("mmap fd_as");
|
||||
exit(1);
|
||||
}
|
||||
printf("allocated %lu bytes of address space\n", UINT_MAX * sizeof(int));
|
||||
|
||||
fdtable_fd = mkstemp(template);
|
||||
if (fdtable_fd < 0) {
|
||||
perror("mkstemp");
|
||||
exit(1);
|
||||
}
|
||||
unlink(template);
|
||||
ret = ftruncate(fdtable_fd, 128*1024*1024);
|
||||
if (ret < 0) {
|
||||
perror("ftruncate");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
io_fd = open("/dev/null", O_RDWR);
|
||||
if (io_fd < 0) {
|
||||
perror("open /dev/null");
|
||||
exit(1);
|
||||
}
|
||||
fds = mmap(fd_as, 128*1024*1024, PROT_READ|PROT_WRITE,
|
||||
MAP_SHARED|MAP_FIXED, fdtable_fd, 0);
|
||||
if (fds == MAP_FAILED) {
|
||||
perror("mmap fdtable");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* fill the fd table */
|
||||
nr_fds = 128*1024*1024 / sizeof(int);
|
||||
for (i = 0; i < nr_fds; i++)
|
||||
fds[i] = io_fd;
|
||||
|
||||
/* map the file through the rest of the address space */
|
||||
nr_maps = (UINT_MAX * sizeof(int)) / (128*1024*1024);
|
||||
for (i = 0; i < nr_maps; i++) {
|
||||
fds = &fds[nr_fds]; /* advance fds by 128MiB */
|
||||
fds = mmap(fds, 128*1024*1024, PROT_READ|PROT_WRITE,
|
||||
MAP_SHARED|MAP_FIXED, fdtable_fd, 0);
|
||||
if (fds == MAP_FAILED) {
|
||||
printf("mmap failed at offset %lu\n", (char *)fd_as - (char *)fds);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now fd_as points to the file descriptor array. */
|
||||
/*
|
||||
* We may not be able to map all of these files. Let's back off
|
||||
* until success.
|
||||
*/
|
||||
nr_fds = UINT_MAX;
|
||||
while (nr_fds) {
|
||||
ret = io_uring_register(uring_fd, IORING_REGISTER_FILES,
|
||||
fd_as, nr_fds);
|
||||
if (ret != 0) {
|
||||
nr_fds /= 2;
|
||||
continue;
|
||||
}
|
||||
printf("io_uring_register(%d, IORING_REGISTER_FILES, %p, %llu)"
|
||||
"...succeeded\n", uring_fd, fd_as, nr_fds);
|
||||
status = 0;
|
||||
printf("io_uring_register(%d, IORING_UNREGISTER_FILES, 0, 0)...",
|
||||
uring_fd);
|
||||
ret = io_uring_register(uring_fd, IORING_UNREGISTER_FILES, 0, 0);
|
||||
if (ret < 0) {
|
||||
ret = errno;
|
||||
printf("failed\n");
|
||||
errno = ret;
|
||||
perror("io_uring_register UNREGISTER_FILES");
|
||||
exit(1);
|
||||
}
|
||||
printf("succeeded\n");
|
||||
break;
|
||||
}
|
||||
|
||||
close(io_fd);
|
||||
close(fdtable_fd);
|
||||
ret = munmap(fd_as, UINT_MAX * sizeof(int));
|
||||
if (ret != 0) {
|
||||
printf("munmap(%lu) failed\n", UINT_MAX * sizeof(int));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
test_memlock_exceeded(int fd)
|
||||
{
|
||||
int ret;
|
||||
void *buf;
|
||||
struct iovec iov;
|
||||
|
||||
iov.iov_len = mlock_limit * 2;
|
||||
buf = malloc(iov.iov_len);
|
||||
assert(buf);
|
||||
iov.iov_base = buf;
|
||||
|
||||
while (iov.iov_len) {
|
||||
ret = io_uring_register(fd, IORING_REGISTER_BUFFERS, &iov, 1);
|
||||
if (ret < 0) {
|
||||
if (errno == ENOMEM) {
|
||||
printf("io_uring_register of %lu bytes failed "
|
||||
"with ENOMEM (expected).\n", iov.iov_len);
|
||||
iov.iov_len /= 2;
|
||||
continue;
|
||||
}
|
||||
printf("expected success or EFAULT, got %d\n", errno);
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
||||
printf("successfully registered %lu bytes (%d).\n",
|
||||
iov.iov_len, ret);
|
||||
ret = io_uring_register(fd, IORING_UNREGISTER_BUFFERS, NULL, 0);
|
||||
if (ret != 0) {
|
||||
printf("error: unregister failed with %d\n", errno);
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!iov.iov_len)
|
||||
printf("Unable to register buffers. Check memlock rlimit.\n");
|
||||
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
test_iovec_nr(int fd)
|
||||
{
|
||||
int i, ret, status = 0;
|
||||
unsigned int nr = UIO_MAXIOV + 1;
|
||||
struct iovec *iovs;
|
||||
void *buf;
|
||||
|
||||
buf = malloc(pagesize);
|
||||
assert(buf);
|
||||
|
||||
iovs = malloc(nr * sizeof(struct iovec));
|
||||
assert(iovs);
|
||||
|
||||
for (i = 0; i < nr; i++) {
|
||||
iovs[i].iov_base = buf;
|
||||
iovs[i].iov_len = pagesize;
|
||||
}
|
||||
|
||||
status |= expect_fail(fd, IORING_REGISTER_BUFFERS, iovs, nr, EINVAL);
|
||||
|
||||
/* reduce to UIO_MAXIOV */
|
||||
nr--;
|
||||
printf("io_uring_register(%d, %u, %p, %u)\n",
|
||||
fd, IORING_REGISTER_BUFFERS, iovs, nr);
|
||||
ret = io_uring_register(fd, IORING_REGISTER_BUFFERS, iovs, nr);
|
||||
if (ret != 0) {
|
||||
printf("expected success, got %d\n", errno);
|
||||
status = 1;
|
||||
} else
|
||||
io_uring_register(fd, IORING_UNREGISTER_BUFFERS, 0, 0);
|
||||
|
||||
free(buf);
|
||||
free(iovs);
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* io_uring limit is 1G. iov_len limit is ~OUL, I think
|
||||
*/
|
||||
int
|
||||
test_iovec_size(int fd)
|
||||
{
|
||||
unsigned int status = 0;
|
||||
int ret;
|
||||
struct iovec iov;
|
||||
void *buf;
|
||||
|
||||
/* NULL pointer for base */
|
||||
iov.iov_base = 0;
|
||||
iov.iov_len = 4096;
|
||||
status |= expect_fail(fd, IORING_REGISTER_BUFFERS, &iov, 1, EFAULT);
|
||||
|
||||
/* valid base, 0 length */
|
||||
iov.iov_base = &buf;
|
||||
iov.iov_len = 0;
|
||||
status |= expect_fail(fd, IORING_REGISTER_BUFFERS, &iov, 1, EFAULT);
|
||||
|
||||
/* valid base, length exceeds size */
|
||||
/* this requires an unampped page directly after buf */
|
||||
buf = mmap(NULL, 2 * pagesize, PROT_READ|PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
assert(buf != MAP_FAILED);
|
||||
ret = munmap(buf + pagesize, pagesize);
|
||||
assert(ret == 0);
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = 2 * pagesize;
|
||||
status |= expect_fail(fd, IORING_REGISTER_BUFFERS, &iov, 1, EFAULT);
|
||||
munmap(buf, pagesize);
|
||||
|
||||
/* huge page */
|
||||
buf = mmap(NULL, 2*1024*1024, PROT_READ|PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_HUGETLB | MAP_HUGE_2MB | MAP_ANONYMOUS,
|
||||
-1, 0);
|
||||
if (buf == MAP_FAILED) {
|
||||
printf("Unable to map a huge page. Try increasing "
|
||||
"/proc/sys/vm/nr_hugepages by at least 1.\n");
|
||||
printf("Skipping the hugepage test\n");
|
||||
} else {
|
||||
/*
|
||||
* This should succeed, so long as RLIMIT_MEMLOCK is
|
||||
* not exceeded
|
||||
*/
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = 2*1024*1024;
|
||||
ret = io_uring_register(fd, IORING_REGISTER_BUFFERS, &iov, 1);
|
||||
if (ret < 0) {
|
||||
if (errno == ENOMEM)
|
||||
printf("Unable to test registering of a huge "
|
||||
"page. Try increasing the "
|
||||
"RLIMIT_MEMLOCK resource limit by at "
|
||||
"least 2MB.");
|
||||
else {
|
||||
printf("expected success, got %d\n", errno);
|
||||
status = 1;
|
||||
}
|
||||
} else {
|
||||
printf("Success!\n");
|
||||
ret = io_uring_register(fd, IORING_UNREGISTER_BUFFERS,
|
||||
0, 0);
|
||||
if (ret < 0) {
|
||||
perror("io_uring_unregister");
|
||||
status = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
ret = munmap(iov.iov_base, iov.iov_len);
|
||||
assert(ret == 0);
|
||||
|
||||
/* file-backed buffers -- not supported */
|
||||
buf = map_filebacked(2*1024*1024);
|
||||
if (!buf)
|
||||
status = 1;
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = 2*1024*1024;
|
||||
printf("reserve file-backed buffers\n");
|
||||
status |= expect_fail(fd, IORING_REGISTER_BUFFERS, &iov, 1, EOPNOTSUPP);
|
||||
munmap(buf, 2*1024*1024);
|
||||
|
||||
/* bump up against the soft limit and make sure we get EFAULT
|
||||
* or whatever we're supposed to get. NOTE: this requires
|
||||
* running the test as non-root. */
|
||||
if (getuid() != 0)
|
||||
status |= test_memlock_exceeded(fd);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void
|
||||
dump_sqe(struct io_uring_sqe *sqe)
|
||||
{
|
||||
printf("\topcode: %d\n", sqe->opcode);
|
||||
printf("\tflags: 0x%.8x\n", sqe->flags);
|
||||
printf("\tfd: %d\n", sqe->fd);
|
||||
if (sqe->opcode == IORING_OP_POLL_ADD)
|
||||
printf("\tpoll_events: 0x%.8x\n", sqe->poll_events);
|
||||
}
|
||||
|
||||
int
|
||||
ioring_poll(struct io_uring *ring, int fd, int fixed)
|
||||
{
|
||||
int ret;
|
||||
struct io_uring_sqe *sqe;
|
||||
struct io_uring_cqe *cqe;
|
||||
|
||||
sqe = io_uring_get_sqe(ring);
|
||||
memset(sqe, 0, sizeof(*sqe));
|
||||
sqe->opcode = IORING_OP_POLL_ADD;
|
||||
if (fixed)
|
||||
sqe->flags = IOSQE_FIXED_FILE;
|
||||
sqe->fd = fd;
|
||||
sqe->poll_events = POLLIN|POLLOUT;
|
||||
|
||||
printf("io_uring_submit:\n");
|
||||
dump_sqe(sqe);
|
||||
ret = io_uring_submit(ring);
|
||||
if (ret != 1) {
|
||||
printf("failed to submit poll sqe: %d.\n", errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ret = io_uring_wait_completion(ring, &cqe);
|
||||
if (ret < 0) {
|
||||
printf("io_uring_wait_completion failed with %d\n", ret);
|
||||
return 1;
|
||||
}
|
||||
if (cqe->res != POLLOUT) {
|
||||
printf("io_uring_wait_completion: expected 0x%.8x, got 0x%.8x\n",
|
||||
POLLOUT, cqe->res);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
test_poll_ringfd(void)
|
||||
{
|
||||
int status = 0;
|
||||
int ret;
|
||||
int fd;
|
||||
struct io_uring ring;
|
||||
|
||||
ret = io_uring_queue_init(1, &ring, 0);
|
||||
if (ret) {
|
||||
perror("io_uring_queue_init");
|
||||
return 1;
|
||||
}
|
||||
fd = ring.ring_fd;
|
||||
|
||||
/* try polling the ring fd */
|
||||
status = ioring_poll(&ring, fd, 0);
|
||||
|
||||
/*
|
||||
* now register the ring fd, and try the poll again. This should
|
||||
* fail, because the kernel does not allow registering of the
|
||||
* ring_fd.
|
||||
*/
|
||||
status |= expect_fail(fd, IORING_REGISTER_FILES, &fd, 1, EBADF);
|
||||
|
||||
/* tear down queue */
|
||||
io_uring_queue_exit(&ring);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int fd, ret;
|
||||
unsigned int status = 0;
|
||||
struct io_uring_params p;
|
||||
struct rlimit rlim;
|
||||
|
||||
/* setup globals */
|
||||
pagesize = getpagesize();
|
||||
ret = getrlimit(RLIMIT_MEMLOCK, &rlim);
|
||||
if (ret < 0) {
|
||||
perror("getrlimit");
|
||||
return 1;
|
||||
}
|
||||
mlock_limit = rlim.rlim_cur;
|
||||
printf("RELIMIT_MEMLOCK: %lu (%lu)\n", rlim.rlim_cur, rlim.rlim_max);
|
||||
devnull = open("/dev/null", O_RDWR);
|
||||
if (devnull < 0) {
|
||||
perror("open /dev/null");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* invalid fd */
|
||||
status |= expect_fail(-1, 0, NULL, 0, EBADF);
|
||||
/* valid fd that is not an io_uring fd */
|
||||
status |= expect_fail(devnull, 0, NULL, 0, EOPNOTSUPP);
|
||||
|
||||
/* invalid opcode */
|
||||
memset(&p, 0, sizeof(p));
|
||||
fd = new_io_uring(1, &p);
|
||||
ret = expect_fail(fd, ~0U, NULL, 0, EINVAL);
|
||||
if (ret) {
|
||||
/* if this succeeds, tear down the io_uring instance
|
||||
* and start clean for the next test. */
|
||||
close(fd);
|
||||
fd = new_io_uring(1, &p);
|
||||
}
|
||||
|
||||
/* IORING_REGISTER_BUFFERS */
|
||||
status |= test_iovec_size(fd);
|
||||
status |= test_iovec_nr(fd);
|
||||
/* IORING_REGISTER_FILES */
|
||||
status |= test_max_fds(fd);
|
||||
close(fd);
|
||||
/* uring poll on the uring fd */
|
||||
status |= test_poll_ringfd();
|
||||
|
||||
if (!status)
|
||||
printf("PASS\n");
|
||||
else
|
||||
printf("FAIL\n");
|
||||
|
||||
return status;
|
||||
}
|
||||
161
test/io_uring_setup.c
Normal file
161
test/io_uring_setup.c
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* io_uring_setup.c
|
||||
*
|
||||
* Description: Unit tests for the io_uring_setup system call.
|
||||
*
|
||||
* Copyright 2019, Red Hat, Inc.
|
||||
* Author: Jeff Moyer <jmoyer@redhat.com>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include "../src/liburing.h"
|
||||
|
||||
/*
|
||||
* Attempt the call with the given args. Return 0 when expect matches
|
||||
* the return value of the system call, 1 otherwise.
|
||||
*/
|
||||
char *
|
||||
flags_string(struct io_uring_params *p)
|
||||
{
|
||||
static char flagstr[64];
|
||||
int add_pipe = 0;
|
||||
|
||||
memset(flagstr, 0, sizeof(flagstr));
|
||||
|
||||
if (!p || p->flags == 0)
|
||||
return "none";
|
||||
|
||||
/*
|
||||
* If unsupported flags are present, just print the bitmask.
|
||||
*/
|
||||
if (p->flags & ~(IORING_SETUP_IOPOLL | IORING_SETUP_SQPOLL |
|
||||
IORING_SETUP_SQ_AFF)) {
|
||||
snprintf(flagstr, 64, "0x%.8x", p->flags);
|
||||
return flagstr;
|
||||
}
|
||||
|
||||
if (p->flags & IORING_SETUP_IOPOLL) {
|
||||
strncat(flagstr, "IORING_SETUP_IOPOLL", 64 - strlen(flagstr));
|
||||
add_pipe = 1;
|
||||
}
|
||||
if (p->flags & IORING_SETUP_SQPOLL) {
|
||||
if (add_pipe)
|
||||
strncat(flagstr, "|", 64 - strlen(flagstr));
|
||||
strncat(flagstr, "IORING_SETUP_SQPOLL", 64 - strlen(flagstr));
|
||||
}
|
||||
if (p->flags & IORING_SETUP_SQ_AFF) {
|
||||
if (add_pipe)
|
||||
strncat(flagstr, "|", 64 - strlen(flagstr));
|
||||
strncat(flagstr, "IORING_SETUP_SQ_AFF", 64 - strlen(flagstr));
|
||||
}
|
||||
|
||||
return flagstr;
|
||||
}
|
||||
|
||||
char *
|
||||
dump_resv(struct io_uring_params *p)
|
||||
{
|
||||
static char resvstr[4096];
|
||||
|
||||
if (!p)
|
||||
return "";
|
||||
|
||||
sprintf(resvstr, "0x%.8x 0x%.8x 0x%.8x 0x%.8x 0x%.8x", p->resv[0],
|
||||
p->resv[1], p->resv[2], p->resv[3], p->resv[4]);
|
||||
|
||||
return resvstr;
|
||||
}
|
||||
|
||||
/* bogus: setup returns a valid fd on success... expect can't predict the
|
||||
fd we'll get, so this really only takes 1 parameter: error */
|
||||
int
|
||||
try_io_uring_setup(unsigned entries, struct io_uring_params *p, int expect, int error)
|
||||
{
|
||||
int ret;
|
||||
|
||||
printf("io_uring_setup(%u, %p), flags: %s, resv: %s, sq_thread_cpu: %u\n",
|
||||
entries, p, flags_string(p), dump_resv(p),
|
||||
p ? p->sq_thread_cpu : 0);
|
||||
|
||||
ret = io_uring_setup(entries, p);
|
||||
if (ret != expect) {
|
||||
printf("expected %d, got %d\n", expect, ret);
|
||||
/* if we got a valid uring, close it */
|
||||
if (ret > 0)
|
||||
close(ret);
|
||||
return 1;
|
||||
}
|
||||
if (expect == -1 && error != errno) {
|
||||
printf("expected errno %d, got %d\n", error, errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int fd;
|
||||
unsigned int status = 0;
|
||||
struct io_uring_params p;
|
||||
|
||||
memset(&p, 0, sizeof(p));
|
||||
status |= try_io_uring_setup(0, &p, -1, EINVAL);
|
||||
status |= try_io_uring_setup(1, NULL, -1, EFAULT);
|
||||
|
||||
/* resv array is non-zero */
|
||||
memset(&p, 0, sizeof(p));
|
||||
p.resv[0] = p.resv[1] = p.resv[2] = p.resv[3] = p.resv[4] = 1;
|
||||
status |= try_io_uring_setup(1, &p, -1, EINVAL);
|
||||
|
||||
/* invalid flags */
|
||||
memset(&p, 0, sizeof(p));
|
||||
p.flags = ~0U;
|
||||
status |= try_io_uring_setup(1, &p, -1, EINVAL);
|
||||
|
||||
/* IORING_SETUP_SQ_AFF set but not IORING_SETUP_SQPOLL */
|
||||
memset(&p, 0, sizeof(p));
|
||||
p.flags = IORING_SETUP_SQ_AFF;
|
||||
status |= try_io_uring_setup(1, &p, -1, EINVAL);
|
||||
|
||||
/* attempt to bind to invalid cpu */
|
||||
memset(&p, 0, sizeof(p));
|
||||
p.flags = IORING_SETUP_SQPOLL | IORING_SETUP_SQ_AFF;
|
||||
p.sq_thread_cpu = get_nprocs_conf();
|
||||
status |= try_io_uring_setup(1, &p, -1, EINVAL);
|
||||
|
||||
/* I think we can limit a process to a set of cpus. I assume
|
||||
* we shouldn't be able to setup a kernel thread outside of that.
|
||||
* try to do that. (task->cpus_allowed) */
|
||||
|
||||
/* read/write on io_uring_fd */
|
||||
memset(&p, 0, sizeof(p));
|
||||
fd = io_uring_setup(1, &p);
|
||||
if (fd < 0) {
|
||||
printf("io_uring_setup failed with %d, expected success\n",
|
||||
errno);
|
||||
status = 1;
|
||||
} else {
|
||||
char buf[4096];
|
||||
int ret;
|
||||
ret = read(fd, buf, 4096);
|
||||
if (ret >= 0) {
|
||||
printf("read from io_uring fd succeeded. expected fail\n");
|
||||
status = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
printf("PASS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("FAIL\n");
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user