Can't be done. The libc is legacy, it can't be changed without breaking everything. It's also mandatory on every operating system other than Linux.
A change in paradigm is necessary. Freestanding C, not hosted C. This completely gets rid of the libc and is a surprisingly clean language. Linux only, because it's the only kernel with a stable binary interface. Every other OS forces a C runtime.
I once worked on a liblinux project that embodied this... Stopped because Linux itself has a nolibc thing in the kernel tree and I didn't want to compete with it. Now I'm working on the Rust version.
> what is necessary for users of the C language to actually do stuff
Surprisingly little. I wrote an entire lisp interpreter in freestanding C with Linux system calls. It managed to survive for a rather long time without any memory allocation at all.
The system layer is refreshingly tiny. It consists of a memory allocator and extremely basic functions like memmove and strlen. I successfully got rid of total nonsense like thread local errno, locales, implicit buffering, cached global state, possibly more. All that stuff is gone! Exactly one global survived: the stack canary generated by GCC and clang. Every other symbol in the ELF is controlled by me.
Wasn't able to get rid of the NUL terminator. Linux itself needs it. To get rid of that little billion dollar mistake requires an entirely new kernel with zero UNIX/POSIX influence. I had to make my peace with that one. All my buffers maintain an extra NUL byte at the end.
Windows never had this problem. If you really want it, you can actually skip CRT completely on Windows. Only thing you need is the initialization code that's statically linked anyway: https://nullprogram.com/blog/2023/02/15/ . You can still make Win32 calls with it. CRT is there as a convenience.
And since Linux distros are compiling everything from source anyway, this makes Linux a prime candidate for complete libc and complete ABI replacement. Thanks to stable kernel ABI, we can also use the same kernel even with Docker etc! Musl did it. Why can a completely modularized libc not?
Will it break a lot of programs in the userspace? YES! Hyrum's Law exists.
Will it be rejected by a significant part of user base? YES! They rejected systemd as well.
Will it take one or more decades to have something working? HELL YES! This is Linux we're talking about. Everybody has seen how Wayland played out. However, maybe if Valve has enough motivation to fix Linux binary problem, we maybe can get finally a binary-compatible distro in a decade or half a decade.
Great choice for small programs, but what if I want hardware accelerated 3d?
With Linux system calls alone it should be possible to set up kernel mode setting without depending on any toolkit at all. This should be enough to get a framebuffer for software rendering.
For hardware acceleration though, one must give this graphics context to an OpenGL ES implementation. That's where it gets ugly. There is no way to divorce that from the libc short of literally rewriting it.
Maybe Vulkan will enable it? I can't say for sure at my current knowledge level.
https://vulkan.lunarg.com/doc/view/latest/mac/LoaderInterfac...
Windows explicitly does not want you to link the system libc. You are expected to bring your own, and doing so means your process has multiple libc's loaded into its address space.
And if you choose to build a binary that doesn't need a libc, you won't be bringing one.
This is categorically false; UCRT[1] is a thing. The 'U' stands for universal. Unlike Linux, Windows allows developers to choose their ABI boundary and also ship that boundary if they desire, or use the 'system' one and ask older platforms to install redistributables or Windows update packages. There's the old and new C runtimes in MSVCRT.DLL and UCRTBASE.DLL, the C++ runtime in VCRUNTIME140.DLL, Win32 in KERNEL32.DLL, USER32.DLL and more, and then the stable-ish kernel interfaces in NTDLL.DLL, in order of 'closeness to the kernel'.
And also, 'libc' is a UNIXism; on Windows the term is CRT, for 'C runtime'.
[1]: https://learn.microsoft.com/en-gb/cpp/porting/upgrade-your-c...
Linux has a stable system call ABI. I can trash the entire user space and boot Linux with init=/my/program if I want.
It's not that Linux doesn't allow developers to choose, it's that Linux doesn't actually control the userspace ABIs. Userspace is whatever we want it to be, and people settled on "GNU/Linux".
This is a noble but practically worthless ideal. Hardly anything is written against the kernel ABI except glibc itself. End-user application developers don't write against the kernel interface. Platform library and application developers like KDE and GNOME don't really write against the kernel interface either. Nowadays even driver developers don't entirely write against the kernel interface, instead also linking in glibc and lib{std}c++ for things like shader compilers (which is nowadays usually just a fork of Clang). Like it or not, glibc is the singular entry point for desktop Linux, which is the point of essentially every comment under this post.
That 'desktop Linux ≡ glibc' is something that I daresay the kernel developers also have to take responsibility for, rather than shrugging their shoulders and pawning it off to GNU. Linux's unstable user-mode is the entire reason for Docker's existence. It is bodge upon bodge and has led to much gnashing of teeth. The GNU/Linux community (I don't care for Stallman's meme rant here) have to grow a backbone and understand that an OS is much more than just a kernel, it is a platform, and currently the Linux platform is a disparate set of communities with highly divergent goals.
That's why I've always said the best Linux is Android, because Android is a platform.
I'm working hard to change that.
Here's an entire lisp interpreter written in freestanding C using nothing but Linux system calls:
https://github.com/lone-lang/lone
It has a system-call primitive too, which means it can do anything.
Here's a TCP echo server written in lone lisp:
https://github.com/lone-lang/lone/blob/master/examples/tcp-e...
It works, and you don't need libc.
> Like it or not, glibc is the singular entry point for desktop Linux
Well I don't like it, and I've made it my mission to fix it.
Maybe lisp isn't the answer, Rust is. I've revived my liblinux crate too.
https://github.com/matheusmoreira/liblinux
I'm going to add system calls to this thing every day until I have every single one. Then I'll use them to make all of my applications.
Already have my first project planned: a custom Rust network stack for my QEMU virtual machines so I can firewall them off in userspace without root access.
> the Linux platform
The linux platform is these system calls. Everything else is userspace. This is exactly what makes it special.
This is exactly the attitude I meant by 'the kernel developers also have to take responsibility for, rather than shrugging their shoulders and pawning it off to GNU' and 'an OS is much more than just a kernel, it is a platform'.
By 'platform', I mean windowing toolkits, such as Wayland. Audio libraries like ALSA and PipeWire. Desktop idioms and libraries like DBus, and the entire freedesktop.org effort including Mesa and more. Init systems like systemd. Bootloaders like GRUB/systemd-boot. Networking, including systemd-networkd, iwd, and NetworkManager. Power and device management including PowerDevil, Bluez.
If all of this is considered 'user space' then my point is strongly vindicated. On most competitor OSs these comprise the 'platform'. You won't ever encounter a Windows or OS X developer arguing that USER32.DLL or Foundation is 'userspace' even if it is technically true and code from those libraries may only ever run in ring 3. From the average user's point of view, the only 'userspace' are end-user applications that they have installed, like Chrome, video games, etc. From that perspective, even purely user-mode applications that ship with the distro ought to considered 'platform'. This includes such trivia like calculator and camera applications.
We need to talk about the practical realities of a platform, not a theoretical, unattainable, and as I mentioned, useless ideal. These practical realities mean shipping a unified set of useful 'batteries included' user-mode libraries and applications. If one wants to develop the platform itself, then sure, the kernel has a nice stable interface, but on most Linux distributions the kernel is built from source anyway, so even that nicety is moot.
You mean Linux distributions.
> From that perspective, even purely user-mode applications that ship with the distro ought to considered 'platform'.
Every Linux distribution is its own platform.
You decried Stallman's meme rant yet you just rewrote the entire rant in your own terms. Linux is the kernel, GNU's just the stuff on top. You can get rid of it if you want and make your own. Android did exactly that.
Also, I daily drive Alpine Linux (using musl) and I'm quite happy to assure you that glibc really is only one option, not at all mandatory for desktop Linux.
I never disparaged it; I said having it doesn't make it any more useful that OSs that don't, because the practical reality is that the kernel interface is narrow and highly technical, and a stable kernel binary interface has little to do with a user's day-to-day experience with an OS.
I daresay Android would still work pretty well even sans the kernel ABI stability, because the kernel is usually built from source, and if it isn't, then every Android source tree has kernel prebuilts for the kernel version shipping. I also never said nor implied glibc should be uniquely privileged; I think you have misrepresented my comment.
Some advertisement for VC-LTL: https://github.com/Chuyu-Team/VC-LTL5 and https://github.com/Chuyu-Team/YY-Thunks
VC-LTL Implements a good subset of modern CRT functions using the msvcrt.dll, which makes modern MSVC compiler to emit binaries that theoretically can run in Windows NT 4.1 or Windows 98 (until CFG broke them, but you can turn it off). Still can teach an old dog new tricks.
YY-Thunk is a little more funky, it tries to implement new Win32 userspace API for old system by emulating the behavior and expected values using userspace API in the old apartment, so you can try to implement a function in Windows 10 with functions in Windows Vista, and hopefully it will work out.
> At some point, the decision was made to just give up and declare it an operating system DLL, to be used only by operating system components.
https://devblogs.microsoft.com/oldnewthing/20140411-00/?p=12...
When you link with UCRT your specific program just gets a "view" of standard C functions. You can load DLLs that were linked with MSVCRT from a UCRT program. It just works
amd64: https://devblogs.microsoft.com/oldnewthing/20260319-00/?p=11...
arm64: https://devblogs.microsoft.com/oldnewthing/20260320-00/?p=11...
Usually Windows developers will use Win32 directly, ZeroMemory() instead of memset(), and so on.
That only massively compounds the problem.
> And if you choose to build a binary that doesn't need a libc, you won't be bringing one.
NT system calls are not stable. You still need to link against ntdll.dll at the very least, like a forced Linux vDSO.
The Windows ecosystem, that manages to deliver built binaries easily & widely, regardless of whether the author has a 1 year old OS or a 15 year old OS, suggests that it's not as big a problem as you believe.
Or.. any other programming language. A lot of systems programming is done in Rust nowadays.
I'm sure those OSes make efforts to make said runtime binary compatible between executables.
That's when you run into the Darth Vader of binary interfaces.
> I have altered the ABI. Pray I do not alter it further. -- De Raadt
GPU drivers are a perfect example - GPUs are hellishly complex, and their interfaces are super high-level - OpenGL/Vulkan is not something that should be implemented by the driver.
I think a general practice that is followed with GPU drivers is to move as much of the complex and non-privileged stuff out of the kernel, and only keep modesetting/power management/memory and control stuff in.
That reduces massively the amount of code that needs to run in Ring 0, but in exchange, the interface between the user and kernel blobs is non-stable. Programs talk to the userspace blob and there's no guarantee of compatibility between different kernel/userspace driver versions.
This is something even Linus accepts, with the only contention that the userspace blob needs to be open source as well. They're not maintaining something in tree, that they have no way of testing without proprietary external deps.