File: reg-wait.c

package info (click to toggle)
liburing 2.14-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,448 kB
  • sloc: ansic: 59,512; sh: 816; makefile: 603; cpp: 32
file content (194 lines) | stat: -rw-r--r-- 4,714 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/* SPDX-License-Identifier: MIT */
/*
 * Sample program that shows how to use registered waits.
 *
 * (C) 2024 Jens Axboe <axboe@kernel.dk>
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/time.h>
#include <liburing.h>

#include "helpers.h"

static unsigned long long mtime_since(const struct timeval *s,
				      const struct timeval *e)
{
	long long sec, usec;

	sec = e->tv_sec - s->tv_sec;
	usec = (e->tv_usec - s->tv_usec);
	if (sec > 0 && usec < 0) {
		sec--;
		usec += 1000000;
	}

	sec *= 1000;
	usec /= 1000;
	return sec + usec;
}

static unsigned long long mtime_since_now(struct timeval *tv)
{
	struct timeval end;

	gettimeofday(&end, NULL);
	return mtime_since(tv, &end);
}

static int register_memory(struct io_uring *ring, void *ptr, size_t size)
{
	struct io_uring_region_desc rd = {};
	struct io_uring_mem_region_reg mr = {};

	rd.user_addr = uring_ptr_to_u64(ptr);
	rd.size = size;
	rd.flags = IORING_MEM_REGION_TYPE_USER;
	mr.region_uptr = uring_ptr_to_u64(&rd);
	mr.flags = IORING_MEM_REGION_REG_WAIT_ARG;

	return io_uring_register_region(ring, &mr);
}

int main(int argc, char *argv[])
{
	struct io_uring_reg_wait *reg;
	struct io_uring_sqe *sqe;
	struct io_uring_cqe *cqe[2];
	struct io_uring ring;
	char b1[8], b2[8];
	unsigned long msec;
	struct timeval tv;
	int ret, fds[2];
	int page_size;

	if (argc > 1) {
		fprintf(stdout, "%s: takes no arguments\n", argv[0]);
		return 0;
	}

	page_size = sysconf(_SC_PAGESIZE);
	if (page_size < 0) {
		fprintf(stderr, "sysconf(_SC_PAGESIZE) failed\n");
		return 1;
	}

	if (pipe(fds) < 0) {
		perror("pipe");
		return 1;
	}

	ret = io_uring_queue_init(8, &ring, IORING_SETUP_R_DISABLED);
	if (ret) {
		fprintf(stderr, "Queue init: %d\n", ret);
		return 1;
	}

	/*
	 * Setup a region we'll use to pass wait arguments. It should be
	 * page aligned, we're using only first two wait entries here and
	 * the rest of the memory can be reused for other purposes.
	 */
	reg = t_aligned_alloc(page_size, page_size);
	if (!reg) {
		fprintf(stderr, "allocation failed\n");
		return 1;
	}

	ret = register_memory(&ring, reg, page_size);
	if (ret) {
		if (ret == -EINVAL) {
			fprintf(stderr, "Kernel doesn't support registered waits\n");
			return 1;
		}
		fprintf(stderr, "Registered wait: %d\n", ret);
		return 1;
	}

	ret = io_uring_enable_rings(&ring);
	if (ret) {
		fprintf(stderr, "io_uring_enable_rings failure %i\n", ret);
		return 1;
	}

	/*
	 * Setup two distinct wait regions. Index 0 will be a 1 second wait,
	 * and region 2 is a short wait using min_wait_usec as well. Neither
	 * of these use a signal mask, but sigmask/sigmask_sz can be set as
	 * well for that.
	 */
	reg[0].ts.tv_sec = 1;
	reg[0].ts.tv_nsec = 0;
	reg[0].flags = IORING_REG_WAIT_TS;

	reg[1].ts.tv_sec = 0;
	reg[1].ts.tv_nsec = 100000000LL;
	reg[1].min_wait_usec = 10000;
	reg[1].flags = IORING_REG_WAIT_TS;

	/*
	 * No pending completions. Wait with region 0, which should time
	 * out after 1 second.
	 */
	gettimeofday(&tv, NULL);
	ret = io_uring_submit_and_wait_reg(&ring, cqe, 1, 0);
	if (ret == -EINVAL) {
		fprintf(stderr, "Kernel doesn't support registered waits\n");
		return 1;
	} else if (ret != -ETIME) {
		fprintf(stderr, "Wait should've timed out... %d\n", ret);
		return 1;
	}
	msec = mtime_since_now(&tv);
	if (msec < 900 || msec > 1100) {
		fprintf(stderr, "Wait took an unexpected amount of time: %lu\n",
			msec);
		return 1;
	}

	/*
	 * Now prepare two pipe reads. We'll trigger one completion quickly,
	 * but the other one will never happen. Use min_wait_usec timeout
	 * to abort after 10 msec of time, where the overall timeout is
	 * otherwise 100 msec. Since we're waiting on two events, the min
	 * timeout ends up aborting us.
	 */
	sqe = io_uring_get_sqe(&ring);
	io_uring_prep_read(sqe, fds[0], b1, sizeof(b1), 0);
	sqe = io_uring_get_sqe(&ring);
	io_uring_prep_read(sqe, fds[0], b2, sizeof(b2), 0);

	/* trigger one read */
	ret = write(fds[1], "Hello", 5);
	if (ret < 0) {
		perror("write");
		return 1;
	}

	/*
	 * This should will wait for 2 entries, where 1 is already available.
	 * Since we're using min_wait_usec == 10 msec here with an overall
	 * wait of 100 msec, we expect the wait to abort after 10 msec since
	 * one or more events are available.
	 */
	gettimeofday(&tv, NULL);
	ret = io_uring_submit_and_wait_reg(&ring, cqe, 2, 1);
	msec = mtime_since_now(&tv);
	if (ret != 2) {
		fprintf(stderr, "Should have submitted 2: %d\n", ret);
		return 1;
	}
	if (msec < 8 || msec > 12)
		fprintf(stderr, "min_wait_usec should take ~10 msec: %lu\n", msec);

	/*
	 * Cleanup after ourselves
	 */
	io_uring_queue_exit(&ring);
	free(reg);
	return 0;
}