mbox series

[PATCHSET,RFC,0/6] Add io_uring support for futex wait/wake

Message ID 20230609183125.673140-1-axboe@kernel.dk (mailing list archive)
Headers show
Series Add io_uring support for futex wait/wake | expand

Message

Jens Axboe June 9, 2023, 6:31 p.m. UTC
Hi,

Sending this just to the io_uring list for now so we can iron out
details, questions, concerns, etc before going a bit broader to get
the futex parts reviewed. Those are pretty straight forward though,
and try not to get too entangled into futex internals.

Anyway, this adds support for IORING_OP_FUTEX_{WAIT,WAKE}. The
WAKE part is pretty trivial, as we can just call into the wake side
of things. For wait, obviously we cannot, as we don't want to block
on waiting on a futex. When futex currently does a wait, it queues up
the futex_q in question and then does a sync wait/schedule on that.
The futex handler futex_wake_mark() is responsible for waking the
task that is synchronousely sleeping on that futex_q. This default
handler is hardwired, and we simply add a wake handler in futex_q
for this intead and change the hardwired futex_q->task to be a
generic data piece for the handler.

With that, we can queue up a futex_q and get a callback when it
would have woken. With that, we can sanely implement async WAIT
support without blocking.

Notable omissions in this code so far:

- We don't support timeouts with futex wait. We could definitely
  add this support. Outside of some complications with racing with
  wake (and cancelation), it is certainly doable. The main question
  here is we need it? And if we do, can we get by with just using
  linked timeouts for this? That's the io_uring idiomatic way of
  achieving this goal. That said, I may just go ahead and add it
  if I can solve the races in a clean fashion. Because at that point,
  seems the right thing to do.

- No PI support. This can certainly get added later.

Code can also be found here:

git://git.kernel.dk/linux io_uring-futex

or on cgit:

https://git.kernel.dk/cgit/linux/log/?h=io_uring-futex

Very simple sample code below showing how to do a wait and wake,
Obviously not that exciting, just a brief demo.


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/futex.h>
#include <liburing.h>

#define	IORING_OP_FUTEX_WAIT (IORING_OP_SENDMSG_ZC + 1)
#define IORING_OP_FUTEX_WAKE (IORING_OP_FUTEX_WAIT + 1)

static void io_uring_prep_futex_wait(struct io_uring_sqe *sqe, void *futex,
				     int val)
{
	memset(sqe, 0, sizeof(*sqe));
	sqe->opcode = IORING_OP_FUTEX_WAIT;
	sqe->fd = FUTEX_WAIT;
	sqe->addr = (unsigned long) futex;
	sqe->len = val;
	sqe->file_index = FUTEX_BITSET_MATCH_ANY;
}

static void io_uring_prep_futex_wake(struct io_uring_sqe *sqe, void *futex,
				     int val)
{
	memset(sqe, 0, sizeof(*sqe));
	sqe->opcode = IORING_OP_FUTEX_WAKE;
	sqe->fd = FUTEX_WAIT;
	sqe->addr = (unsigned long) futex;
	sqe->len = val;
	sqe->file_index = FUTEX_BITSET_MATCH_ANY;
}

int main(int argc, char *argv[])
{
	struct io_uring_sqe *sqe;
	struct io_uring_cqe *cqe;
	struct io_uring ring;
	unsigned int *futex;
	int ret, i;

	futex = malloc(sizeof(*futex));
	*futex = 0;

	io_uring_queue_init(8, &ring, 0);

	sqe = io_uring_get_sqe(&ring);
	io_uring_prep_futex_wait(sqe, futex, 0);
	sqe->user_data = 1;
	
	io_uring_submit(&ring);

	*futex = 1;
	sqe = io_uring_get_sqe(&ring);
	io_uring_prep_futex_wake(sqe, futex, 1);
	sqe->user_data = 2;

	io_uring_submit(&ring);

	for (i = 0; i < 2; i++) {
		ret = io_uring_wait_cqe(&ring, &cqe);
		if (ret)
			return 1;

		io_uring_cqe_seen(&ring, cqe);
	}

	return 0;
}