Io uring: Difference between revisions
| (19 intermediate revisions by the same user not shown) | |||
| Line 11: | Line 11: | ||
==Rings== | ==Rings== | ||
Central to every uring are two ringbuffers holding CQEs (Completion Queue Entries) and SQE (Submission Queue Entries) descriptors (as best I can tell, this terminology was borrowed from the [https://nvmexpress.org/specifications/ NVMe specification]; I've also seen it with Mellanox NICs). Note that SQEs are allocated externally to the SQ descriptor ring (the same is not true for CQEs). SQEs roughly correspond to a single system call: they are tagged with an operation type, and filled in with the values that would traditionally be supplied as arguments to the appropriate function. Userspace is provided references to SQEs on the SQE ring, which it fills in and submits. Submission operates up through a specified SQE, and thus all SQEs before it in the ring must also be ready to go (this is likely the main reason why the SQ holds descriptors to an external ring of SQEs: you can acquire SQEs, and then submit them out of order). The kernel places results in the CQE ring. These rings are shared between kernel- and userspace. The rings must be distinct unless the kernel specifies the <tt>IORING_FEAT_SINGLE_MMAP</tt> feature (see below). | Central to every uring are two ringbuffers holding CQEs (Completion Queue Entries) and SQE (Submission Queue Entries) descriptors (as best I can tell, this terminology was borrowed from the [https://nvmexpress.org/specifications/ NVMe specification]; I've also seen it with Mellanox NICs). Note that SQEs are allocated externally to the SQ descriptor ring (the same is not true for CQEs). SQEs roughly correspond to a single system call: they are tagged with an operation type, and filled in with the values that would traditionally be supplied as arguments to the appropriate function. Userspace is provided references to SQEs on the SQE ring, which it fills in and submits. Submission operates up through a specified SQE, and thus all SQEs before it in the ring must also be ready to go (this is likely the main reason why the SQ holds descriptors to an external ring of SQEs: you can acquire SQEs, and then submit them out of order, but see <tt>IORING_SETUP_NO_SQARRAY</tt>). The kernel places results in the CQE ring. These rings are shared between kernel- and userspace. The rings must be distinct unless the kernel specifies the <tt>IORING_FEAT_SINGLE_MMAP</tt> feature (see below). | ||
uring does not generally make use of <tt>errno</tt>. Synchronous functions return the negative error code as their result. Completion queue entries have the negated error code placed in their <tt>res</tt> fields. | uring does not generally make use of <tt>errno</tt>. Synchronous functions return the negative error code as their result. Completion queue entries have the negated error code placed in their <tt>res</tt> fields. | ||
| Line 57: | Line 57: | ||
|- | |- | ||
| <tt>IORING_ENTER_REGISTERED_RING</tt> || <tt>ring_fd</tt> is an offset into the registered ring pool rather than a normal file descriptor. | | <tt>IORING_ENTER_REGISTERED_RING</tt> || <tt>ring_fd</tt> is an offset into the registered ring pool rather than a normal file descriptor. | ||
|- | |||
| <tt>IORING_ENTER_ABS_TIMER</tt> || (Since Linux 6.12) The timeout argument in the <tt>io_uring_getevents_arg</tt> is an absolute time, using the registered clock. | |||
|- | |- | ||
|} | |} | ||
| Line 219: | Line 221: | ||
|- | |- | ||
| IORING_SETUP_REGISTERED_FD_ONLY || 6.6 || Registers the ring fd for use with <tt>IORING_REGISTER_USE_REGISTERED_RING</tt>. Returns a registered fd index. | | IORING_SETUP_REGISTERED_FD_ONLY || 6.6 || Registers the ring fd for use with <tt>IORING_REGISTER_USE_REGISTERED_RING</tt>. Returns a registered fd index. | ||
|- | |||
| IORING_SETUP_NO_SQARRAY || 6.6 || Do not use an indirection array for the submission queue, instead submitting them strictly in order. | |||
|- | |- | ||
|} | |} | ||
| Line 260: | Line 264: | ||
|- | |- | ||
| <tt>IORING_FEAT_REG_REG_RING</tt> || 6.3 || Ring descriptors can be registered with <tt>IORING_REGISTER_USE_REGISTERED_RING</tt>. | | <tt>IORING_FEAT_REG_REG_RING</tt> || 6.3 || Ring descriptors can be registered with <tt>IORING_REGISTER_USE_REGISTERED_RING</tt>. | ||
|- | |||
| <tt>IORING_FEAT_RECVSEND_BUNDLE</tt> || 6.10 || <tt>send</tt> and <tt>recv</tt> operations can be bundled. | |||
|- | |||
| <tt>IORING_FEAT_MIN_TIMEOUT</tt> || 6.12 || A minimum batch wait timeout is supported. | |||
|- | |- | ||
|} | |} | ||
| Line 359: | Line 367: | ||
|- | |- | ||
| <tt>IORING_UNREGISTER_PERSONALITY</tt> || 5.6 || | | <tt>IORING_UNREGISTER_PERSONALITY</tt> || 5.6 || | ||
|- | |||
|} | |||
====NAPI settings==== | |||
{| class="wikitable" border="1" | |||
! Flag !! Kernel !! Description | |||
|- | |||
| <tt>IORING_REGISTER_NAPI</tt> || || | |||
|- | |||
| <tt>IORING_UNREGISTER_PERSONALITY</tt> || || | |||
|- | |- | ||
|} | |} | ||
| Line 410: | Line 427: | ||
|- | |- | ||
| <tt>IORING_RECV_MULTISHOT</tt> || | | <tt>IORING_RECV_MULTISHOT</tt> || | ||
|- | |||
| <tt>IORING_SEND_MULTISHOT</tt> || Introduced in 6.8. | |||
|- | |- | ||
| <tt>IORING_ACCEPT_MULTISHOT</tt> || | | <tt>IORING_ACCEPT_MULTISHOT</tt> || | ||
| Line 556: | Line 575: | ||
// IORING_OP_CONNECT (5.5) | // IORING_OP_CONNECT (5.5) | ||
void io_uring_prep_connect(struct io_uring_sqe *sqe, int sockfd, const struct sockaddr *addr, socklen_t addrlen); | void io_uring_prep_connect(struct io_uring_sqe *sqe, int sockfd, const struct sockaddr *addr, socklen_t addrlen); | ||
</syntaxhighlight> | |||
====Operations on file descriptors==== | |||
<syntaxhighlight lang="c"> | |||
// IORING_OP_URING_CMD | |||
void io_uring_prep_cmd_sock(struct io_uring_sqe *sqe, int cmd_op, int fd, int level, | |||
int optname, void *optval, int optlen); | |||
// IORING_OP_GETXATTR | |||
void io_uring_prep_getxattr(struct io_uring_sqe *sqe, const char *name, char *value, | |||
const char *path, unsigned int len); | |||
// IORING_OP_SETXATTR | |||
void io_uring_prep_setxattr(struct io_uring_sqe *sqe, const char *name, const char *value, | |||
const char *path, int flags, unsigned int len); | |||
// IORING_OP_FGETXATTR | |||
void io_uring_prep_fgetxattr(struct io_uring_sqe *sqe, int fd, const char *name, | |||
char *value, unsigned int len); | |||
// IORING_OP_FSETXATTR | |||
void io_uring_prep_fsetxattr(struct io_uring_sqe *sqe, int fd, const char *name, | |||
const char *value, int flags, unsigned int len); | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 596: | Line 634: | ||
All transring messages are prepared using the target uring's file descriptor, and thus aren't particularly compatible with the higher-level liburing API (of course, you can just yank <tt>ring_fd</tt> out of <tt>struct iouring</tt>). That's unfortunate, because this functionality is very useful in a multithreaded environment. Note that the ring to which the SQE is submitted can itself be the target of the message! | All transring messages are prepared using the target uring's file descriptor, and thus aren't particularly compatible with the higher-level liburing API (of course, you can just yank <tt>ring_fd</tt> out of <tt>struct iouring</tt>). That's unfortunate, because this functionality is very useful in a multithreaded environment. Note that the ring to which the SQE is submitted can itself be the target of the message! | ||
<syntaxhighlight lang="c"> | <syntaxhighlight lang="c"> | ||
// IORING_OP_MSG_RING | |||
void io_uring_prep_msg_ring(struct io_uring_sqe *sqe, int fd, unsigned int len, __u64 data, unsigned int flags); | void io_uring_prep_msg_ring(struct io_uring_sqe *sqe, int fd, unsigned int len, __u64 data, unsigned int flags); | ||
void io_uring_prep_msg_ring_cqe_flags(struct io_uring_sqe *sqe, int fd, unsigned int len, __u64 data, unsigned int flags, unsigned int cqe_flags); | void io_uring_prep_msg_ring_cqe_flags(struct io_uring_sqe *sqe, int fd, unsigned int len, __u64 data, unsigned int flags, unsigned int cqe_flags); | ||
void io_uring_prep_msg_ring_fd(struct io_uring_sqe *sqe, int fd, int source_fd, int target_fd, __u64 data, unsigned int flags); | void io_uring_prep_msg_ring_fd(struct io_uring_sqe *sqe, int fd, int source_fd, int target_fd, __u64 data, unsigned int flags); | ||
void io_uring_prep_msg_ring_fd_alloc(struct io_uring_sqe *sqe, int fd, int source_fd, __u64 data, unsigned int flags); | void io_uring_prep_msg_ring_fd_alloc(struct io_uring_sqe *sqe, int fd, int source_fd, __u64 data, unsigned int flags); | ||
</syntaxhighlight> | |||
====Futexes==== | |||
<syntaxhighlight lang="c"> | |||
// IORING_OP_FUTEX_WAKE | |||
void io_uring_prep_futex_wake(struct io_uring_sqe *sqe, uint32_t *futex, uint64_t val, | |||
uint64_t mask, uint32_t futex_flags, unsigned int flags); | |||
// IORING_OP_FUTEX_WAIT | |||
void io_uring_prep_futex_wait(struct io_uring_sqe *sqe, uint32_t *futex, uint64_t val, | |||
uint64_t mask, uint32_t futex_flags, unsigned int flags); | |||
// IORING_OP_FUTEX_WAITV | |||
void io_uring_prep_futex_waitv(struct io_uring_sqe *sqe, struct futex_waitv *futex, | |||
uint32_t nr_futex, unsigned int flags); | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 688: | Line 740: | ||
|- | |- | ||
| <tt>IORING_CQE_F_NOTIF</tt> || Notification CQE for zero-copy sends | | <tt>IORING_CQE_F_NOTIF</tt> || Notification CQE for zero-copy sends | ||
|- | |||
| <tt>IORING_CQE_F_BUF_MORE</tt> || There will be more CQEs to the specified registered buffer | |||
|- | |- | ||
|} | |} | ||
| Line 776: | Line 830: | ||
Why is there no <tt>vmsplice(2)</tt> action? How about <tt>fork(2)</tt>/<tt>pthread_create(3)</tt> (or more generally <tt>clone(2)</tt>)? We could have coroutines with I/O. | Why is there no <tt>vmsplice(2)</tt> action? How about <tt>fork(2)</tt>/<tt>pthread_create(3)</tt> (or more generally <tt>clone(2)</tt>)? We could have coroutines with I/O. | ||
It's kind of annoying that chains can't extend over submissions. If I've got a lot of data I need delivered in order, it seems I'm limited to a single chain in-flight at a time, or else I risk out-of-order delivery due to one chain aborting, followed by items from a subsequent chain succeeding. <tt>recv_multishot</tt> can help with this. | It's kind of annoying that chains can't extend over submissions. If I've got a lot of data I need delivered in order, it seems I'm limited to a single chain in-flight at a time, or else I risk out-of-order delivery due to one chain aborting, followed by items from a subsequent chain succeeding. <tt>recv_multishot</tt> can help with this. | ||
| Line 803: | Line 855: | ||
** "[https://lwn.net/Articles/863071/ Discriptorless files for io_uring]" 2021-07-19 | ** "[https://lwn.net/Articles/863071/ Discriptorless files for io_uring]" 2021-07-19 | ||
** "[https://lwn.net/Articles/879724/ Zero-copy network transmission with io_uring]" 2021-12-30 | ** "[https://lwn.net/Articles/879724/ Zero-copy network transmission with io_uring]" 2021-12-30 | ||
** "[https://lwn.net/Articles/1002371/ Process creation in io_uring]" 2024-12-20 | |||
[[CATEGORY: Networking]] | [[CATEGORY: Networking]] | ||