Library / SDK
managarm/mlibc avatar
managarm/mlibc

mlibc is a C standard library for operating systems that lack one

Portable C standard library

1,148 stars226 forksCNOASSERTION

At a glance

What is it?
mlibc is a fully featured C standard library built for portability, with a clean syscall abstraction that lets a new operating system plug in a port, POSIX and Linux features gated behind options, and per OS ABI headers kept separate from the code. It runs Xorg, Mesa and WebKitGTK, and it is a large project with an explicit release policy.
Who is it for?
mlibc is worth adopting if you are writing or porting an operating system and need a C library that already provides pthreads, GNU extensions and enough of POSIX to run real software, because writing one from scratch is a project of years.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is an operating system with no libc

mlibc is described as a fully featured C standard library designed with portability in mind, and the problem it solves is more specific than the description suggests.

An operating system can be written without a C library. The kernel does not need one, and a hobby kernel or a research system may ship with nothing. But as soon as you want to run real software on it, you need libc, and a C library is a very large thing to write. It is the layer that every C program links against, and its surface is thousands of functions covering files, processes, threads, signals, memory, networking and locale.

The project supports a number of architectures, x86-64, AArch64, RISC-V, IA-32, m68k and LoongArch64, and provides a clean syscall abstraction layer for new operating system ports to plug into. That phrase, clean syscall abstraction layer, is the architectural centre of the project. mlibc is written in terms of an interface to the operating system, and a port is the implementation of that interface for one OS. Everything above that line is shared, so the work of supporting a new kernel is bounded.

The audience is kernel and OS developers, and the communities around them. The README shows the shape of that community: an official Discord server, an IRC channel on irc.libera.chat at #managarm-mlibc with a pointer to the other managarm channels, and an AUR package that provides mlibc-gcc for Arch users.

The primary language is C, and the licence in the repository is a custom one that GitHub cannot classify, so the LICENSE file is the one to read before you build on it.

Three directories that keep the layers apart

The design of the library is documented as a table of three directories, and the split is the clearest statement of the project's architecture.

options/ holds more or less OS independent headers and code, and it is divided into subdirectories that can be enabled or disabled by ports. This is where the portable substance of the library lives, and where the option gating happens.

sysdeps/ holds OS specific headers and code, divided into per port subdirectories, with exactly one of those subdirectories enabled in each build. That last clause is the important one, because it means the portable layer never has to test which OS it is on. There is one sysdeps tree per target, and the build picks it.

abis/ holds OS specific interface headers, described as the ABI headers, containing the constants and structs of the OS interface. The examples given are the numerical values of SEEK_SET or O_CREAT, and structs like struct stat. The rule stated for these files is strict: they are only allowed to contain constants, structs and unions, and no function declarations or logic. The division into per OS subdirectories is for organizational purposes only, and ports can still mix headers from different abis/ subdirectories.

That restriction is the interesting design decision. An ABI header is a description of the binary interface, the numbers and layouts that the kernel and the userland agree on. Keeping logic out of them means the file can be checked against the kernel's own definitions, and it means the compiler sees a clean contract.

Around these three directories the repository adds a meson.build with meson_options.txt, two configuration templates in internal-config.h.in and mlibc-config.h.in, a book directory for the user guide, a ci directory, a benchmarks directory, a dummy-libs directory for linking tests, a scripts directory, a tests directory and subprojects.

Feature parity with glibc and musl, with the option to opt out

The comparison the project makes with its peers is specific. Unlike other portable C standard libraries like newlib, mlibc aims for feature parity with glibc and musl, which in the README's terms means full pthread support and GNU extensions.

That is a meaningful difference. newlib is the comparison the README itself makes, as another portable C standard library. mlibc is trying to be the other thing, a library complete enough that a real desktop stack runs on it.

The capability claim is concrete rather than aspirational. mlibc is capable enough to run a range of software, including Xorg, several Wayland compositors, Mesa, and web browsers like WebKitGTK, though some features are still missing.

Those four are worth pausing on, because they are the hardest test cases. Xorg needs threads, file descriptors, sockets and X11 protocol handling. Mesa needs a GPU driver stack. WebKitGTK is a full browser engine with an enormous surface. Running those is a far stronger signal than passing unit tests, and it is the reason to believe the feature parity goal is genuine rather than aspirational.

The gating is what makes the goal practical. Individual operating systems can opt in or out of certain features as desired, and the example given is that POSIX APIs like pthread are gated behind the POSIX option while Linux APIs like epoll are gated behind the Linux option. So a port does not have to choose between a full POSIX surface and nothing, it chooses a profile, and the parts it has not implemented simply do not exist for that port.

Porting: pin a release, because master moves underneath you

The porting guidance is the part of the README that most directly addresses a new user, and it comes with a warning.

Ports to new operating systems are welcome, and the instruction is to follow the mlibc User Guide, which lives in the book directory of the repository and at docs.managarm.org/mlibc-book. The guide is the actual documentation, and it is long enough to have its own book.

The warning concerns the source of truth. The recommendation is that new ports do not build from master, because the maintainers occasionally make internal changes that cause out of tree sysdeps to break. The alternative offered is to pin a specific release or commit, or to upstream your changes to the repository so that the project can build them on its CI, and any breakages will then be fixed in tree by the maintainers.

That is a well chosen piece of advice, and it applies to more than mlibc. The code in sysdeps/ is a moving interface, and a port that tracks master is a port that will break on a commit you did not choose. A port that upstream its sysdeps becomes part of the build matrix, which is slower to land but does not rot.

The repository layout supports both routes. There is a RELEASE_PROCEDURE.md for the release side, ABI_BREAKS.md which is a log of the changes that break the binary interface, and a ci directory for the continuous integration that an upstreamed port would join.

So the decision for a new port is whether you want to be a downstream consumer of a moving library, or a contributor to a stable one. The README tells you to prefer the second unless you have a reason not to.

Meson options that let you build a library without a linker

The build configuration is meson, and the custom options are the interesting part because they let you take pieces of the library rather than all of it.

All of the options are booleans that default to false, and the list in the README is explicit. headers_only installs only the headers and does not build libc.so or ld.so. no_headers does the opposite, building the libraries without installing the headers, which is the shape you want for a runtime package on a system that already has headers. build_tests builds the test suite. x_option enables the x component of mlibc functionality, and the documentation says this may be used to disable POSIX and glibc extensions, with the full list of possible values in meson_options.txt.

Two options are more specific. linux_kernel_headers directs mlibc to installed Linux headers, which are required if the Linux option is enabled, and the note in the options file points at the kernel documentation for obtaining them. debug_allocator replaces the normal allocator with a debug allocator, with the implementation in the generic allocator source. And use_freestnd_hdrs uses freestnd-c and freestnd-cxx headers instead of looking for compiler headers, which is described as useful if you are not using a compiler with the correct target triple.

Then there is the meson built-in that does the most damage. The type of library to be built, static, shared or both, is controlled by meson's default_library option, and passing -Ddefault_library=static effectively disables the dynamic linker. That is how you get a statically linked userspace with no ld.so at all, which is a real option for small or embedded-like systems.

For development there is also support for building with -Db_sanitize=undefined to use UBSan inside mlibc, with the caveat stated in the README that this does not enable UBSan for external applications linking against libc.so, but that it is useful during development to detect internal bugs, for example when adding new sysdeps.

The formatting side is clang-format and clang-tidy, both with configuration at the repository root, and a pre-commit setup where you install pre-commit and then run pre-commit install to get the git hook, or pre-commit run -a to format everything manually.

The test suite runs on a Linux host, not on your new kernel

The test instructions are worth reading closely, because they explain how a C library gets tested when the operating system it targets is the thing under construction.

The mlibc test suite can be run under a Linux host. To do this you first need a set of Linux kernel headers, and there is a convenience script to download and install them into a local linux-headers directory:

bash
./scripts/get-linux-headers.sh

You also need pyexpect and a set of locales installed, and the list is specific: de_DE, de_DE.utf8, ru_RU.utf8, en_US.utf8 and C.utf8. The presence of German and Russian locales is a hint about what the suite covers, since locale handling is where a portable library most often differs from the one a developer is used to.

Then, from the project root:

bash
meson setup -Dbuild_tests=true -Dlinux_kernel_headers=./linux-headers build

That creates a build directory, and then you change into it and run the tests with output shown:

bash
meson test -v

So the architecture of the testing is: the sysdeps for Linux are used as the host implementation, and the portable layer is exercised against them. A port under development is then a different sysdeps tree plugged into the same portable layer, and the same test suite applies. That is a much better testing story than building a whole reference OS, and it is why the option to run under a Linux host exists at all.

Version 7, an ABI break log, and a custom licence

The release history shows a project with a slow, deliberate cadence and a strong major version discipline. v6.3.0 on 2025-10-09, v6.3.1 on 2025-10-12, and v7.0.0 on 2026-08-02, with the repository pushed to on 2026-10-18.

That gap between 6.3.1 and 7.0.0 is nearly ten months, which fits a library where a major version means an interface change rather than a feature drop. The repository makes that explicit with ABI_BREAKS.md, a file whose name tells you the question to ask about any upgrade: what changed in the binary interface? For a C library, that is the question that matters, because the whole point of a libc is the contract between a compiler and a kernel.

The licence deserves a note. The repository metadata reports it as a custom licence that GitHub cannot classify, and the LICENSE file is the source of the terms. Since mlibc is designed to be shipped as part of an operating system, and operating systems are distributed as complete systems, a licence review here is more than a formality, and it is worth reading the file before you decide to build a port on it.

The project also has a RELEASE_PROCEDURE.md, so the release process is written down rather than tribal, and a book directory holding the user guide, which is where a porter should start.

The summary judgement is that mlibc is a serious engineering project with a clear architecture, a documented porting path and an explicit compatibility story. Adopting it means committing to a long term relationship with the interface in sysdeps/, and the README's own advice, pin a release or upstream your changes, is the right way to begin.

Editorial conclusion

mlibc is worth adopting if you are writing or porting an operating system and need a C library that already provides pthreads, GNU extensions and enough of POSIX to run real software, because writing one from scratch is a project of years. It is the wrong choice for a Linux application, where glibc or musl already exist and are better integrated, and it is the wrong choice if you need a stable ABI guarantee across versions, since the project ships an ABI_BREAKS.md and a major version line. Before you start a port, read the mlibc User Guide, pin a specific release rather than tracking master, and note that the current release is v7.0.0 from 2026-08-02 with the repository pushed to on 2026-10-18.

Frequently asked questions

What is mlibc and what is it for?

It is a fully featured C standard library designed with portability in mind, providing a clean syscall abstraction layer for new operating system ports. It supports x86-64, AArch64, RISC-V, IA-32, m68k and LoongArch64, and is capable enough to run Xorg, several Wayland compositors, Mesa and WebKitGTK.

How is mlibc different from newlib?

The README states that unlike other portable C standard libraries like newlib, mlibc aims for feature parity with glibc and musl, that is full pthread support and GNU extensions. It also states that some features are still missing.

What are the options, sysdeps and abis directories in mlibc?

options/ holds the OS independent headers and code, divided into subdirectories that ports enable or disable, sysdeps/ holds the OS specific code with one per port subdirectory enabled in each build, and abis/ holds OS specific interface headers holding only constants, structs and unions, with no function declarations or logic.

How do I run the mlibc test suite?

The suite runs under a Linux host. Install the kernel headers with ./scripts/get-linux-headers.sh, install pyexpect and the locales de_DE, de_DE.utf8, ru_RU.utf8, en_US.utf8 and C.utf8, then run meson setup -Dbuild_tests=true -Dlinux_kernel_headers=./linux-headers build from the project root, change into the build directory and run meson test -v.

How do I port mlibc to a new operating system?

The README points to the mlibc User Guide, and warns that new ports should not build from master because internal changes can break out of tree sysdeps. The recommendation is to pin a specific release or commit, or to upstream the changes to the repository so the project can build them on its CI and fix breakages in tree.

Official sources

  1. Issues
  2. managarm/mlibc on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/managarm-mlibc.svg)](https://hysenlabs.com/projects/managarm-mlibc)

Was this useful?