io_uring_...ered_files(7)
π io_uring_registered_files β bestie's guide to registered files π
okay so this is the Linux Programmer's Manual page io_uring_registered_files, and we're gonna talk about registered files, aka fixed files. it's a whole vibe, i promise. sit down, grab a lil snack π
β¨ NAME β¨
io_uring_registered_files β the whole overview of io_uring registered files, no cap
π¦ DESCRIPTION π¦
so registered files (also known as fixed files, they have two names, main character energy fr) are a performance optimization thing in io_uring. they let applications pre-register a set of file descriptors with the kernel ahead of time. once files are registered, the kernel takes a reference to each file, which means it skips all the overhead of looking up file descriptors and grabbing references for every single I/O operation. it's giving "i already know her, no need to introduce us again" π
πΈ Why use registered files?
for every I/O operation that uses a file descriptor, the kernel has to:
- look up the file descriptor in the process's file descriptor table
- take a reference to the file so it stays valid while the operation is happening
- release that reference when the operation is done
now for apps doing a lot of I/O operations β especially threaded apps where the file table is shared, which makes reference counting more expensive β these lil overheads start to stack up. it's giving death by a thousand cuts π
but bestie, if you register the files once, the reference is held for the entire lifetime of the registration, and operations can just use the file directly. no per-operation lookups, no per-operation reference counting. iconic behavior.
registered files are most beneficial for applications that:
- perform many I/O operations on the same set of files
- are multi-threaded (where file table operations are more expensive, fr fr)
- need the lowest possible per-I/O overhead
π Registering files
files get registered using io_uring_register_files(3) or io_uring_register_files_tags(3). you describe the files with an array of file descriptors:
int fds[3];
fds[0] = open("file1", O_RDONLY);
fds[1] = open("file2", O_RDONLY);
fds[2] = open("file3", O_WRONLY | O_CREAT, 0644);
ret = io_uring_register_files(ring, fds, 3);
once they're registered, the original file descriptors can be closed if you want. the kernel holds its own references to the underlying files. so like, you're free bestie, go live your life π
π Using registered files
to use a registered file in an I/O operation, set the IOSQE_FIXED_FILE flag in the SQE's flags field, and then use the index into the registered file array β not the original file descriptor β in the fd field:
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
io_uring_prep_read(sqe, 0, buf, len, offset); /* index 0, not fd */
io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE);
the index is 0-based into the array you passed to io_uring_register_files(3). so first slot is 0, not 1, don't get caught slipping π
π§ Sparse file registration
the file array can be sparse, which means some slots can just be empty. empty slots are indicated by setting the file descriptor to -1. applications can create a fully sparse table with io_uring_register_files_sparse(3) and then fill in the slots later:
/* Create sparse table with 100 slots */
ret = io_uring_register_files_sparse(ring, 100);
/* Later, fill in slot 5 */
int fd = open("file", O_RDONLY);
ret = io_uring_register_files_update(ring, 5, &fd, 1);
build the skeleton first, decorate later. it's giving interior design π
π Updating registered files
registered files can be updated with io_uring_register_files_update(3) or io_uring_register_files_update_tag(3). this can:
- replace an existing file with a new one
- fill in an empty slot
- remove a file by setting the descriptor to
-1
if you wanna skip updating certain slots while updating others, use the special value IORING_REGISTER_FILES_SKIP.
int fds[3];
fds[0] = new_fd; /* replace slot 0 */
fds[1] = IORING_REGISTER_FILES_SKIP; /* leave slot 1 unchanged */
fds[2] = -1; /* remove slot 2 */
ret = io_uring_register_files_update(ring, 0, fds, 3);
updates do not require the ring to be idle on kernels 5.13 and later. on older kernels, updates would wait for in-flight operations to complete. so like, glow up if you're on 5.13+, otherwise you wait your turn bestie π
β File tagging
when you use io_uring_register_files_tags(3) or io_uring_register_files_update_tag(3), each file can be associated with a tag value. when a file gets unregistered β either explicitly or by replacement β and there are no more in-flight operations using that file, a completion queue entry is posted with user_data set to the tag value and all other fields zeroed.
this notification mechanism lets applications know when it's safe to do cleanup actions associated with the file. it's basically the kernel texting you "she's gone, you're clear" π«Ά
π Direct file descriptors
some io_uring operations can allocate file descriptors directly into the registered file table, skipping the regular file descriptor table entirely. you do this by setting the file_index field in the SQE (using io_uring_sqe_set_target_fixed_file(3)) to the slot you want, or by using IORING_FILE_INDEX_ALLOC to have io_uring allocate the next available slot for you.
operations that support direct descriptors include:
IORING_OP_OPENAT/IORING_OP_OPENAT2IORING_OP_ACCEPTIORING_OP_SOCKETIORING_OP_PIPE
when you use IORING_FILE_INDEX_ALLOC, the application should use io_uring_register_file_alloc_range(3) to specify which range of the file table should be used for allocations.
/* Reserve slots 50-99 for dynamic allocation */
io_uring_register_file_alloc_range(ring, 50, 50);
/* Accept with direct descriptor allocation */
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
io_uring_prep_accept_direct(sqe, listen_fd, NULL, NULL, 0,
IORING_FILE_INDEX_ALLOC);
the allocated slot index comes back in the CQE res field on success. slay β¨
π¦ Closing direct descriptors
direct descriptors β that's files that exist only in the registered file table β can be closed using IORING_OP_CLOSE with the IOSQE_FIXED_FILE flag set, or by updating the slot to -1 using io_uring_register_files_update(3). two ways, both valid, pick your fighter π
πΈ Unregistering files
files get unregistered with io_uring_unregister_files(3). this releases all registered files. files are also automatically unregistered when the io_uring instance is destroyed.
and here's the chill part: applications do not need to explicitly unregister files before shutting down the ring. the kernel handles it, bestie. no cleanup anxiety π«Ά
π NOTES π
- registered files provide the most benefit for applications performing many operations on the same files, especially multi-threaded applications.
- direct descriptors (files that only exist in the registered table) are not visible to operations outside io_uring, such as
read(2)orwrite(2). - the
IOSQE_FIXED_FILEflag must be set when using a registered file index; without it, thefdfield is interpreted as a regular file descriptor. so don't forget it or you'll be reading from the wrong girl entirely π - it is an error to use
IOSQE_FIXED_FILEwith an index that does not correspond to a registered file.
π₯ SEE ALSO π₯
io_uring(7), io_uring_registered_buffers(7), io_uring_register_files(3), io_uring_register_files_tags(3), io_uring_register_files_sparse(3), io_uring_register_files_update(3), io_uring_register_files_update_tag(3), io_uring_unregister_files(3), io_uring_register_file_alloc_range(3)
and that's the whole page bestie!! this one's from the Linux crew, dated January 18, 2025 β so like, she's fresh, she's current, she ate and left no crumbs ππ mwah, go register some files fr fr π¦β¨
π¦ 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