# liburing: the userspace helper library for Linux io_uring

> liburing wraps the raw io_uring syscall interface in a small C API, but most of the repository is a kernel regression suite. Here is what it installs, what it hides, and where it stops helping.

**axboe/liburing** — Library providing helpers for the Linux kernel io_uring support

- Repository: https://github.com/axboe/liburing
- Stars: 3,782 · Forks: 538
- Language: C
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/axboe-liburing

## What liburing actually takes off your plate

io_uring is a kernel interface. Submitting and reaping I/O means mapping ring buffers, filling submission queue entries with the right opcode and flags, advancing the tail, and reading completion queue entries back. liburing exists so application code does not have to do that by hand. The README describes it as providing "helpers to setup and teardown io_uring instances, and also a simplified interface for applications that don't need (or want) to deal with the full kernel side implementation." That is the whole pitch, and it is narrower than it sounds.

The library does not hide the kernel model. You still think in submission queue entries and completion queue entries, still pick opcodes, still manage buffer lifetimes. What liburing removes is the plumbing around them: the ring setup call, the mmap of the queue memory, the memory barriers, and the syscall wrappers. If your mental model of io_uring is wrong, liburing will not correct it. It is a convenience layer for people who already know what they want to submit.

Who is it for? C and C++ developers building I/O-heavy Linux services: proxies, storage engines, network servers, file copy tools. The examples directory is a fair summary of the intended audience. It contains io_uring-cp.c, a copy tool, echo-server.c and proxy.c for networking, kdigest.c for hashing, and send-zerocopy.c and zcrx.c for zero-copy send paths. These are systems programmers, not application developers looking for a portable async I/O API.

## Static inline headers, FFI libraries and the build layout

The design choice that catches people out is where the API lives. According to the README, "liburing's main public interface lives in liburing.h as 'static inline' functions." In other words, much of what you call is compiled into your translation unit from the header rather than resolved from a shared object at link time.

That works for C and C++. It does not work for languages that cannot consume static inline functions, which is why the build produces four libraries by default: two shared (liburing.so and liburing-ffi.so) and two static (liburing.a and liburing-ffi.a). The README is explicit that "languages and applications that can't use 'static inline' functions in liburing.h should use the FFI variants," and that liburing-ffi "contains definitions for every 'static inline' function." If you are writing a binding for another language, that is the library you link, not the plain one.

Kernel coupling is looser than the name suggests. The README states that "liburing itself is not tied to any specific kernel release, and hence it's possible to use the newest liburing release even on older kernels (and vice versa)," with the obvious caveat that newer features need newer kernels. So upgrading the library does not force a kernel upgrade, but calling a helper for a feature your kernel lacks will fail at runtime rather than at build time. Nothing in the README describes a feature-negotiation API that would let you check first.

## Installing liburing from source

There is no package manager instruction in the README. It documents a source build only. The configure step is optional and takes compiler overrides; the README gives gcc and g++ as the example values.

```bash
./configure --cc=gcc --cxx=g++
```

Then build the library and its pkg-config file. The README lists the pkg-config generation as a separate step from the main build, which matters if you plan to link with pkg-config rather than by hand.

```bash
make -j$(nproc)
make liburing.pc
```

Installation puts headers, the shared and static libraries, and the manpages in place. It needs root.

```bash
sudo make install
```

After that, a C program includes liburing.h and links with -luring. The repository ships examples you can read for the call sequence, but the README does not give a compile command for them, so the exact flags are something you work out from the examples Makefile. Out-of-source builds are supported: the README shows mkdir build, cd build, then ../configure and make. That is the cleaner option if you want more than one configuration, for instance a normal build and a debug build side by side.

## The memlock limit is the first thing that breaks

The README devotes a section to ulimit settings, and it is the most concrete operational warning in the document. io_uring accounts its memory under the RLIMIT_MEMLOCK option, which "can be quite low on some setups (64K)." The default is usually enough for small rings, but the README notes that "bigger rings or things like registered buffers deplete it quickly." Root is not subject to the restriction; regular users are.

That asymmetry is the real problem. A service that works when you run it as root during development can fail for the unprivileged user it runs as in production, and the failure appears when you enlarge a ring or register buffers, not at startup. The README points at /etc/security/limits.conf for user-specific settings and at /etc/systemd/user.conf and /etc/systemd/system.conf for systemd setups, and says it will not go into detail on bumping the limit on various systems. It also notes the constraint is looser on newer kernels: "This affects 5.11 and earlier, new kernels are less dependent on RLIMIT_MEMLOCK as it is only used for registering buffers." If you are on an older kernel, treat the limit as a deployment requirement you have to verify, not a detail.

There is a second warning, aimed at a different group. The README says the bulk of liburing is regression and unit tests for both liburing and the kernel io_uring support, and that "this suite isn't expected to pass on older kernels, and may even crash or hang older kernels!" Running the test suite on an old machine is therefore not a harmless check. It is a way to take the machine down.

## liburing versus epoll, and versus calling io_uring directly

The comparison people reach for is epoll, and the difference is not one of API style. epoll tells you when a file descriptor is ready; you then issue a read or write yourself, and that call blocks or returns EAGAIN. io_uring submits the operation and reports completion later, so the readiness notification and the data transfer are separate steps in epoll and a single submission in io_uring. liburing is the C wrapper over that second model. If your workload is a small number of long-lived sockets with light traffic, epoll is simpler and has no ring to size, no memlock accounting, and no kernel-version floor. The io_uring model pays off when you have many concurrent operations and want to avoid a syscall per operation.

The other alternative is not a different library but no library: issuing the io_uring syscalls directly. That is what liburing is a shortcut for. The README frames the library as being for applications that do not want to deal with the full kernel side implementation, which implies the direct route remains available and is what you would take if you needed control the helper layer does not expose. The cost is that you reimplement ring setup, teardown and the header-level helpers yourself, and you own that code across kernel changes. For most teams the helper layer is worth it; for a project that already has a tuned ring implementation, adding liburing is a second abstraction over the same syscalls.

## Licence, releases and what upgrading costs

The licence situation is not a single identifier. The repository description lists MIT, and the README says "All software contained within this repo is dual licensed LGPL and MIT, see COPYING and LICENSE." One header is different: it comes from the kernel and is "dual licensed GPL with a Linux-syscall-note exception and MIT, see COPYING.GPL." The top-level files match that: COPYING, COPYING.GPL and LICENSE are all present. If your legal review needs one answer, the honest answer is that the repository is dual licensed with a kernel-derived exception, and the specific files you copy determine which terms apply. That is a question for your own counsel, not something the README resolves.

Release cadence is visible in the tags: liburing 2.13 on 2025-12-16, 2.14 on 2026-02-07, and 2.15 on 2026-06-29. The last push to the default branch was on 2026-09-11. The repository is not archived. Three releases in roughly seven months, with commits continuing after the most recent tag, is a pace that suggests fixes land between releases rather than only at tag time, which is a reason to track the branch if you hit a bug.

Upgrade cost is bounded by the interface, not the library. Because the public API is static inline in a header, rebuilding against a newer liburing recompiles those helpers into your binary. A behaviour change in a helper is therefore a source-level change for you, not a shared-library swap. Pin a version, read CHANGELOG between tags, and rebuild deliberately rather than letting a distribution package move underneath you.

## Conclusion

Adopt liburing if you are writing C or C++ against io_uring and want the ring setup, submission queue and completion queue handling out of your source. Do not adopt it as a portability layer: it targets Linux only, and the README says the regression suite is not expected to pass on older kernels and may crash or hang them. Before committing, verify two things on your own machines. First, the RLIMIT_MEMLOCK value your service runs under, because the README notes io_uring accounts its memory there and that registered buffers deplete it quickly on 5.11 and earlier. Second, whether your language binding links liburing-ffi rather than liburing, since the README states the main interface in liburing.h is static inline functions and that the FFI library defines every one of them. If neither applies, the plain shared or static liburing build is enough.

## FAQ

### What is io_uring used for?

It is the Linux kernel interface for asynchronous I/O. liburing provides helpers to set up and tear down io_uring instances and a simplified interface for applications that do not want to deal with the full kernel side implementation.

### How do I install liburing?

The README documents a source build: run ./configure, then make -j$(nproc), then make liburing.pc, then sudo make install. Out-of-source builds are also supported by creating a build directory and running ../configure from inside it.

### What is the difference between liburing and io_uring?

io_uring is the kernel facility; liburing is the userspace library that wraps it. The README describes liburing as providing helpers to setup and teardown io_uring instances plus a simplified interface, and notes the library is not tied to any specific kernel release.

### How does liburing compare with epoll?

epoll reports readiness and leaves the read or write to you; io_uring submits the operation and reports completion, and liburing is the C wrapper over that model. The README does not benchmark the two against each other, so the choice depends on your workload rather than on a published number.

## Sources

- [axboe/liburing on GitHub](https://github.com/axboe/liburing)
- [Issues](https://github.com/axboe/liburing/issues)
- [License: MIT](https://github.com/axboe/liburing/blob/master/LICENSE)
- [README](https://github.com/axboe/liburing/blob/master/README.md)
- [Releases](https://github.com/axboe/liburing/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/axboe-liburing
