io_uring_...ed_buffers(7)
π io_uring_...ed_buffers(7) β bestie, let's talk registered buffers π
okay so hi!! π you're reading io_uring_...ed_buffers(7), which is the io_uring registered buffers overview page from the Linux Programmer's Manual. the title is a lil abbreviated in the header (iykyk) but the real name is io_uring_registered_buffers and it is literally about making your I/O not slow. let's get into it π
β¨ NAME β¨
io_uring_registered_buffers β the whole overview of io_uring registered buffers, no cap π
π¦ DESCRIPTION π¦
Registered buffers are a performance optimization feature of io_uring. they let applications pre-register a set of buffers with the kernel, and when buffers are registered, the kernel pins the memory and creates long-term mappings. that means the overhead of mapping and unmapping buffer memory for every single I/O operation justβ¦ goes away. iconic. β¨
π Why use registered buffers?
so like, for every I/O operation that transfers data between user space and the kernel, the kernel has to do a whole lil to-do list on the buffer memory:
- verify the memory is accessible to the process
- pin the pages in memory so they don't get swapped out
- set up kernel mappings to access the memory
each one is fast on its own, sure, but they add up when you're doing a bazillion tiny I/O operations. by registering buffers once upfront, you pay those costs only once, and all the I/O operations after that can use the pre-mapped buffers directly. periodt. π«Ά
Registered buffers are most beneficial for applications that:
- perform many small I/O operations
- reuse the same buffers repeatedly
- need the lowest possible per-I/O overhead
πΈ Registering buffers
you register buffers with io_uring_register_buffers(3) or io_uring_register_buffers_tags(3). the buffers get described using an array of struct iovec structures, like this:
struct iovec iovecs[2];
iovecs[0].iov_base = buf1;
iovecs[0].iov_len = 4096;
iovecs[1].iov_base = buf2;
iovecs[1].iov_len = 8192;
ret = io_uring_register_buffers(ring, iovecs, 2);
the buffers must be anonymous memory β allocated via malloc(3), mmap(2) with MAP_ANONYMOUS, or similar. file-backed memory is not supported. not the vibe, don't do it π
there's a limit of 1 GiB per individual buffer. huge pages are supported, and heads up: the entire huge page will be pinned even if only part of it is used.
the buffers get charged against the user's RLIMIT_MEMLOCK resource limit on kernels before 5.12. on kernel 5.12 and later with IORING_FEAT_NATIVE_WORKERS support, cgroup memory accounting is used instead and no memlock limit applies. slay.
unless you're running as root, if buffer registration fails with ENOMEM, the memlock limit may need to be increased. check the current limit with:
ulimit -l
and bump it for the current shell session with:
ulimit -l unlimited
for a permanent change, edit /etc/security/limits.conf or use setrlimit(2) programmatically with RLIMIT_MEMLOCK.
π Using registered buffers
to use a registered buffer in an I/O operation, use the fixed buffer variants of the prep functions:
io_uring_prep_read_fixed(3)instead ofio_uring_prep_read(3)io_uring_prep_write_fixed(3)instead ofio_uring_prep_write(3)io_uring_prep_readv_fixed(3)instead ofio_uring_prep_readv(3)io_uring_prep_writev_fixed(3)instead ofio_uring_prep_writev(3)
zero-copy send operations can also use registered buffers:
io_uring_prep_send_zc(3)withIORING_RECVSEND_FIXED_BUFio_uring_prep_sendmsg_zc(3)withIORING_RECVSEND_FIXED_BUF
these functions take a buf_index parameter that says which registered buffer to use β it's 0-indexed into the array you passed to io_uring_register_buffers(3).
the memory range used for the I/O operation must fall within the bounds of the registered buffer. but it is valid to use only a portion of a registered buffer for an operation. π«Ά
/* Use first 1024 bytes of registered buffer 0 */
io_uring_prep_read_fixed(sqe, fd, buf1, 1024, offset, 0);
/* Use registered buffer 1 */
io_uring_prep_write_fixed(sqe, fd, buf2, 2048, offset, 1);
π§ Sparse buffer registration
applications can register a sparse buffer table using io_uring_register_buffers_sparse(3). this creates a table with empty slots that can be filled in later using io_uring_register_buffers_update_tag(3). super useful when you don't know the full set of buffers at registration time. no cap.
/* Create sparse table with 10 slots */
ret = io_uring_register_buffers_sparse(ring, 10);
/* Later, fill in slot 3 */
struct iovec iov = { .iov_base = buf, .iov_len = 4096 };
ret = io_uring_register_buffers_update_tag(ring, 3, &iov, NULL, 1);
π·οΈ Buffer tagging
when you use io_uring_register_buffers_tags(3) or io_uring_register_buffers_update_tag(3), each buffer can be associated with a tag value. when a buffer is unregistered β either explicitly or by replacing it β and there are no more in-flight operations using that buffer, a completion queue entry is posted with user_data set to the tag value and all other fields zeroed.
this is the moment the application finds out it's safe to free or reuse the buffer memory. chef's kiss. π
π Updating registered buffers
registered buffers can be updated in place using io_uring_register_buffers_update_tag(3). this can:
- replace an existing buffer with a new one
- fill in a sparse slot
- remove a buffer by setting the iovec to zero length
updating buffers does not immediately free resources. the old buffer stays valid until all in-flight operations complete. be patient bestie. π«Ά
ποΈ Unregistering buffers
buffers get unregistered with io_uring_unregister_buffers(3). this releases all registered buffers. buffers are also automatically unregistered when the io_uring instance is destroyed.
applications don't need to explicitly unregister buffers before shutting down the ring. however, page unpinning may happen asynchronously, so pages may not be immediately available after ring destruction. π
π Cloning buffers
registered buffers can be cloned from one ring to another using io_uring_clone_buffers(3) or io_uring_clone_buffers_offset(3). this lets multiple rings share the same set of registered buffers without re-registering them. sharing is caring fr fr π
π NOTES π
- registered buffers provide the most benefit for small, frequent I/O operations where the per-operation overhead is significant.
- for large I/O operations, the buffer mapping overhead is small relative to the actual I/O time, so registered buffers may not provide much benefit.
- the maximum number of registered buffers is limited by available kernel memory and the
RLIMIT_MEMLOCKlimit (on older kernels). - registered buffers cannot be used with provided buffer rings (
IOSQE_BUFFER_SELECT). these are separate mechanisms for different use cases. don't mix them up π
π SEE ALSO π
io_uring(7), io_uring_registered_files(7), setrlimit(2), io_uring_register_buffers(3), io_uring_register_buffers_tags(3), io_uring_register_buffers_sparse(3), io_uring_register_buffers_update_tag(3), io_uring_unregister_buffers(3), io_uring_prep_read_fixed(3), io_uring_prep_write_fixed(3), io_uring_prep_send_zc(3), io_uring_prep_sendmsg_zc(3), io_uring_clone_buffers(3)
okay that's the whole page bestie, you ate that π this one came from Linux (the Linux Programmer's Manual, page io_uring_...ed_buffers(7)) and it's dated January 18, 2025. go register some buffers, you've got this πβ¨π
π¦ where this page comes from
- alpine 3.24: liburing-doc 2.14-r0 Β· LGPL-2.1-or-later OR MIT
- arch rolling: liburing 2.15-1 Β· GPL-2.0-only WITH Linux-syscall-note OR MIT AND LGPL-2.0-or-later AND MIT
- nixpkgs unstable: liburing 2.14
- ubuntu resolute: liburing-dev 2.14-1
- ubuntu stonking: liburing-dev 2.15-1
- π the page's license: LGPL-2.0-or-later
- Copyright (C) 2025 Jens Axboe <axboe@kernel.dk>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms