|
|
Log in / Subscribe / Register

Moving beyond fork() + exec()

By Jonathan Corbet
June 5, 2026
Since the earliest days of Unix, two of the core process-oriented system calls have been fork(), which creates a child process as a copy of the parent, and exec(), which runs a new program in the place of the current one. In Linux kernels, those system calls are better known as clone() and execve(), but the core functionality remains the same. While there is elegance to this process-creation model, there are shortcomings as well. A recent proposal from Li Chen to add "spawn templates" to the kernel will not be accepted in its current form, but it may point the way toward a new process-creation primitive in the future.

fork() is a relatively expensive system call; it must copy the entire process state (including memory) for the child process. Many optimizations have been made over the years, but a fork is still a fundamentally costly operation. To make things worse, a fork() call is often immediately followed by an exec(), which will discard all of that memory that was so carefully copied for the child. Attempts (such as vfork()) have been made over the years to optimize for this case, but the pattern still is more expensive than it could be.

Spawn templates

Chen's patch set takes an interesting approach to optimize the fork() and exec() pattern. It is focused on applications that repeatedly launch processes running the same executable; imagine, for example, a program that must run Git repeatedly to obtain information about the contents of a repository. In such cases, the program could establish a template to accelerate those invocations, spreading the setup cost across multiple operations. This template would be created with the spawn_template_create() system call:

    struct spawn_template_create_args {
	__aligned_u64 flags;
	__s32 execfd;
	__u32 exec_flags;
	__aligned_u64 filename;
	/* Some fields elided */
    };

    int spawn_template_create(struct spawn_template_create_args *args, size_t args_size);

This call will return a file descriptor representing a template for the executable file, which can be specified as either a file descriptor (execfd) or an absolute path (filename), but not both. To create the template, the kernel will open the indicated file and cache a bunch of information that will allow a process to run that file more quickly in the future.

The application in question may run a given executable many times, but each invocation is different in a number of ways. The details of a specific invocation must be placed into an instance of this structure:

    struct spawn_template_spawn_args {
	__aligned_u64 flags;
	__aligned_u64 pidfd;
	__aligned_u64 argv;
	__aligned_u64 envp;
	__aligned_u64 actions;
	__aligned_u64 actions_len;
	__aligned_u64 reserved[4];
    };

The argv field is a pointer to the argument list to be passed to the program, while envp points to its environment. Changes to file descriptors and signal handling, instead, are passed through actions, which is a pointer to an array of:

    struct spawn_template_action {
	__u32 type;
	__u32 flags;
	__s32 fd;
	__s32 newfd;
	__aligned_u64 arg;
    };

If, for example, file descriptor four should be closed in the child, the associated spawn_template_action structure would have type set to SPAWN_TEMPLATE_ACTION_CLOSE and fd set to four. Other actions exist for duplicating file descriptors, opening files, changing the working directory, and changing signal handling.

Once the spawn_template_spawn_args structure has been filled in, the new process can be run with:

    int spawn_template_spawn(int template_fd,
    			     struct spawn_template_spawn_args *args, int args_size);

Internally, this system call follows something close to the normal fork()/exec() path. Chen is careful to point out that all of the normal checks applied when executing a new file remain in place. But the cached information in the template makes the whole process faster than it was before. How much faster? Benchmark results provided in the cover letter show an improvement of about 2%, which may not seem like a lot, but it may make a difference for applications that fit the expected pattern.

Toward posix_spawn()

The most detailed review of this work was posted by Mateusz Guzik, who said: "This problem is dear to my heart and I have been pondering it on and off for some time now. The entire fork + exec idiom is terrible and needs to be retired". He pointed out that the focus of the patch set was a bit strange in that it left the fork() part of the problem untouched. That is where most of the cost lies, he said, so optimization efforts should seek to remove it from the picture. Rather than copying the current process, "creating a pristine process is the way to go".

Christian Brauner was favorable toward the goal, saying: "The idea of having a builder api for exec isn't all that crazy". His suggestion, though, was that a new API should be built on top of the existing pidfd abstraction. Without getting into any degree of detail, he said that the right approach would be to create an option to pidfd_open() to create an empty process. A series of calls to a new pidfd_config() system call would then configure this new process as desired, setting up its environment, image to execute, and more. pidfd_config() would thus be analogous to fsconfig().

An important objective for a new interface, Brauner said, would be the ability to support an implementation of posix_spawn() in user space. posix_spawn() is well suited as a replacement for the fork()/exec() pattern; developers would likely welcome a native implementation that isn't (unlike the current implementation) hiding fork() and exec() under the covers. Chen agreed that the API as broadly sketched out by Brauner seemed better, and said that future work would be in that direction. So there will be no spawn templates in the Linux kernel but, if Chen's future work comes to fruition, Linux may finally gain a proper posix_spawn() implementation instead.

Index entries for this article
KernelSystem calls/clone()
KernelSystem calls/execve()


to post comments

posix_spawn() does avoid the copy-on-write, at least sometimes

Posted Jun 5, 2026 14:29 UTC (Fri) by smcv (subscriber, #53363) [Link] (2 responses)

> a native implementation that isn't (unlike the current implementation) hiding fork() and exec() under the covers

posix_spawn() does at least *sometimes* avoid the copying problems of fork/exec - if there's no dedicated syscall for it, perhaps it uses vfork() and exec()? I know that recent GLib tries to use posix_spawn() as a backend for g_spawn_async() and similar functions if it can, and that was done to avoid real user-facing issues with fork/exec on low-memory systems, apparently successfully.

(Unfortunately, the things you can do in posix_spawn() are rather limited, so libraries that want to run subprocesses in a somewhat predictable execution environment, while not 100% in control of their own host process's execution environment, can't always use it - for example you can posix_spawn_file_actions_addclose() to close individual fds, but you can't do the equivalent of close_range(). So GLib still needs to use fork/exec in many cases, depending on the options passed to GLib's own APIs by the library user.)

posix_spawn() does avoid the copy-on-write, at least sometimes

Posted Jun 5, 2026 16:49 UTC (Fri) by bluca (subscriber, #118303) [Link]

Yeah it uses CLONE_VM and CLONE_VFORK, so that the parent is frozen until the child is exec'ed, and that way the memory is fully shared with no COW, saving a ton of memory

posix_spawn() does avoid the copy-on-write, at least sometimes

Posted Jun 9, 2026 1:37 UTC (Tue) by sionescu (subscriber, #59410) [Link]

> for example you can posix_spawn_file_actions_addclose() to close individual fds, but you can't do the equivalent of close_range()

I wrote a clone of posix_spawn with some extra extensions, like specifying which FDs to keep, and close all the remaining ones: https://github.com/sionescu/libfixposix/blob/master/src/i...

io_uring

Posted Jun 5, 2026 14:45 UTC (Fri) by josh (subscriber, #17465) [Link] (14 responses)

I've said this before (and have given a talk on it): I think the right mechanism for "actions" is an io_uring. Create a new empty process, run a ring in it to do things like receive/install file descriptors, end the ring with one or more attempts at exec, if you hit the end of the ring without an exec then the process gets SIGKILLed.

I do think it makes sense to combine that with the "spawn template" mechanism somehow, insofar as one might want to load an ELF once and then repeatedly execute it (e.g. make invoking gcc).

io_uring

Posted Jun 5, 2026 15:06 UTC (Fri) by krisman (subscriber, #102057) [Link]

io_uring

Posted Jun 5, 2026 15:32 UTC (Fri) by bluca (subscriber, #118303) [Link] (6 responses)

One major difference is that there would be no seccomp support, which for something that allows spawning processes would be a pretty major shortcoming. I'm not up to date on the generic LSM story aside from that, but at some point it I think it basically boiled down to "block iouring" or "allow iouring" and that was it, maybe things have moved on that front though

io_uring

Posted Jun 5, 2026 15:41 UTC (Fri) by krisman (subscriber, #102057) [Link] (5 responses)

Not true anymore. We now have per-operation bpf-based filtering in io_uring.

io_uring

Posted Jun 5, 2026 15:55 UTC (Fri) by bluca (subscriber, #118303) [Link] (2 responses)

So still no seccomp, and requires its own bespoke filtering? As a userspace developer making heavy use of pidfd_spawn I'd much rather have a normal syscall based approach, like fsconfig(). Much nicer, and integrates much better with the existing sandboxing ecosystem. fsconfig/opentree/movemount/etc are a really nice family of APIs, well designed and pleasant to use.

io_uring

Posted Jun 5, 2026 16:09 UTC (Fri) by josh (subscriber, #17465) [Link] (1 responses)

No matter what filtering mechanism you use, operations in io_uring do not map 1:1 to syscalls. I think the BPF-based filtering in io_uring is a reasonable mapping of filtering to the concepts of io_uring. Using seccomp would still require substantially adapting seccomp; existing filters would not Just Work.

io_uring

Posted Jun 7, 2026 7:13 UTC (Sun) by daandemeyer (subscriber, #163201) [Link]

Uring has its own filtering mechanism these days. Not quite seccomp but close. But I doubt any userspace has plumbed it through already. Will take years likely before uring is removed from existing seccomp profiles.

io_uring

Posted Jun 7, 2026 3:30 UTC (Sun) by DemiMarie (subscriber, #164188) [Link]

Can that be enforced by a sandboxing tool and automatically applied to all rings created by a child process?

Until Google enables io_uring for third-party apps on Android I will stay skeptical.

io_uring filtering

Posted Jun 7, 2026 3:30 UTC (Sun) by DemiMarie (subscriber, #164188) [Link]

Can the filtering be enforced by a sandboxing tool and automatically applied to all rings created by a child process? Does it require eBPF or only cBPF?

Until Google enables io_uring for third-party apps on Android I will stay skeptical

io_uring

Posted Jun 5, 2026 17:49 UTC (Fri) by calvin (subscriber, #168398) [Link] (3 responses)

Why not go further? Be able to spawn a process with an empty set of FDs, address space, etc, then load an executable yourself. This would also allow for moving more exec() loader responsibilities into userspace.

io_uring

Posted Jun 5, 2026 19:56 UTC (Fri) by josh (subscriber, #17465) [Link]

That would work in many cases, but you still need to handle exec in the kernel for suid or similar.

That said, that does not need to be the high performance case; it would be fine to have a userspace mechanism for everything and have it just not work when escalating privileges.

io_uring

Posted Jun 6, 2026 4:53 UTC (Sat) by IAmLiterallyABee (subscriber, #144892) [Link]

That's basically how Fuchsia works

io_uring

Posted Jun 8, 2026 19:20 UTC (Mon) by kreijack (guest, #43513) [Link]

> Why not go further? Be able to spawn a process with an empty set of FDs, address space, etc, then load an executable yourself. This would also allow for moving more exec() loader responsibilities into userspace.

How it could be useful: with an empty list of FD, there is no way to communicate; without any memory sharing, all the parameter (like the executable path, arguments) has to be "hard coded" in the code...

Unfortunately, the true is that the parent and the child have to have something in common (at least some file descriptors to communicate). But other things can be shared, like shared memory , privileges....

I am curious to see some numbers about the cost of fork+exec vs the zygote patter, just to understand if it is a common problem, or it is only a problem of some edge case.

io_uring

Posted Jun 5, 2026 19:36 UTC (Fri) by khim (subscriber, #9252) [Link] (1 responses)

You want to extend io_uring is some “special way”… Why? What would it buy you? Do you really spawn so many processes that normal syscalls are not enough?

Just execute the code that would perform you damn template (and yes, this very much includes io_uring if you really need to) using existing mechanisms.

No need to change anything in kernel at all, everything can be done from the userspace.

io_uring

Posted Jun 5, 2026 19:58 UTC (Fri) by josh (subscriber, #17465) [Link]

I am suggesting adding a system call to load an executable for subsequent repeated reuse, and then separately providing bindings for that system call to io_uring. It would be available either way, it would just be faster via io_uring.

Cost vs benefit ?

Posted Jun 5, 2026 18:01 UTC (Fri) by alkbyby (subscriber, #61687) [Link] (86 responses)

>> but the pattern still is more expensive than it could be.

I am curious if there is much evidence on that kind of statement. Sure, having vfork (or clone with CLONE_VFORK) plus a sequence of "spawn action" syscalls (such as closing fd-s, getting signal actions/masks etc) are not without overheads. E.g. crossing userspace/kernelspace boundary and copying some process structures. But we're talking possibly in the ballpark of (perhaps tens of) microseconds. Compared to "natural" exec overhead with process startup with dynamic linker, relocation and so on being likely in the ballpark order of magnitude larger.

I do get people's frustration with exec. Clearly posix_spawn is the right user-space API. But for that API we already have everything that we need in kernel space. And user-space is at least mostly, right as well.

There used to be a time when e.g. glibc did "regular" fork with all the dangers of pthread_atfork business and/or OOMing, but that has long been fixed.

So then what is the point of more complexity/security risk and so on?

Cost vs benefit ?

Posted Jun 5, 2026 18:36 UTC (Fri) by alkbyby (subscriber, #61687) [Link] (85 responses)

Well, most likely the biggest issue with vfork+exec's overhead is that it is linear on number of file descriptors and VMAs of the parent's process. For larger processes this can add up to quite a bit. But having numbers-based discussion would be still better to have.

Cost vs benefit ?

Posted Jun 5, 2026 19:14 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link] (84 responses)

[v]fork()+exec is terrible when you have a multithreaded app. You either end up COW-ing tons of pages just to be discarded a few milliseconds later, or you're stalling everything.

Cost vs benefit ?

Posted Jun 5, 2026 20:25 UTC (Fri) by alkbyby (subscriber, #61687) [Link] (83 responses)

vfork doesn't trigger any COWs and should scale okay in multi threaded processes (modulo VMA copy overheads; each thread is 1 or 2 vmas; but those should not be huge)

Cost vs benefit ?

Posted Jun 5, 2026 20:44 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link] (82 responses)

vfork "freezes" the process instead, which might be even worse in case you have hundreds of threads that are now waiting for vfork() to finish.

Either way, it's bad.

Cost vs benefit ?

Posted Jun 5, 2026 22:03 UTC (Fri) by alkbyby (subscriber, #61687) [Link] (81 responses)

No. vfork only halts calling thread. Only for the duration of posix_spawn. Which is likely helpful w.r.t. reducing cache line bouncings and contention.

Also I made mistake above. VMAs are not copied by vfork/CLONE_VFORK. So looks like only "unscalable" part of vfork is duplicating file descriptors. Of which most are being closed either explicitly or via O_CLOEXEC, in a most typical uses. Some processes have millions of those. But in most cases it is not expensive.

Again, I'd like to point out that people here (including myself) are speculating about costs. If costs are driving decision, then concrete numbers should be obtained.

I have a sense: a) fork has real issues b) people tend to (incorrectly) attribute much of it's issues to vfork c) the entire vfork+small set of signal-safe actions+exec is very very foot-gun heavy d) so it is tempting to kernel folk to propose something nicer

But IMHO as long as libc handles the foot-gun aspect by delivering high quality posix_spawn implementation, what is the difference?

Cost vs benefit ?

Posted Jun 6, 2026 19:22 UTC (Sat) by quotemstr (subscriber, #45331) [Link] (80 responses)

> But IMHO as long as libc handles the foot-gun aspect by delivering high quality posix_spawn implementation, what is the difference?

You're correct. You've identified one of many areas in which a bit of simple shared glue in user mode saves our having to add worse glue to the kernel.

The possibility of such wins is one reason I'm so vigorously opposed to efforts by the Go people, the Zig people, and others to bypass the "bloated" libc and do "pure" system calls. Bypassing libc is a foolish false economy. It forces coordination points from cheap user mode code to expensive kernel code, because the Zigs of the world refuse to adopt any cooperative protocol that hardware privilege separation forces them to use.

Go, for example, is happy enough using user32/kernel32/ntdll on Windows; it's happy enough using libc on macOS. But on Linux, it's somehow imperative for them to make direct system calls, therefore defeating attempts to form a cooperative commons that doesn't involve a CPU privilege transition. Why? libc system call wrappers aren't slow. They aren't bloated. Bypassing them doesn't grant a program superpowers. AFAICT, the impulse of some language runtime authors to bypass libc is based on vibes. Marginal individual benefits, large community costs.

Vanity, thy name is static linking.

Cost vs benefit ?

Posted Jun 6, 2026 21:01 UTC (Sat) by Cyberax (✭ supporter ✭, #52523) [Link]

Go/Zig people can probably just replicate what GLIBC is doing? It also doesn't really cost a lot to _load_ glibc into the process and use it only for posix_spawn, while avoiding it for anything else.

Cost vs benefit ?

Posted Jun 7, 2026 0:43 UTC (Sun) by koflerdavid (subscriber, #176408) [Link]

Go also got a lot of pushback from OpenBSD for attempting to directly call syscalls. By now OpenBSD has outright banned that practice, requiring everybody to use libc instead.

https://marc.info/?l=openbsd-tech&m=170647326029909&...

Cost vs benefit ?

Posted Jun 7, 2026 0:45 UTC (Sun) by intelfx (subscriber, #130118) [Link] (76 responses)

Well said.

And you nailed it: it's all being done in the name of static linking. Because wrapping everything in containers is apparently not enough, and now we want that everything is wrapped in containers that consist of a single file.

Cost vs benefit ?

Posted Jun 7, 2026 1:57 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link] (69 responses)

The problem is that glibc's symbol versioning is terrible. If you compile an app on a later version of glibc, you can't deploy it on an earlier version. And there's no easy way to fix this.

user32.dll and macOS libc, in contrast, are extremely stable. I can compile an app on Windows 11, and it will likely work on Windows XP, as long as it doesn't use any missing functions.

I understand that symbol versioning theoretically prevents subtle bugs with slightly different function behaviors between versions. But we're talking about freaking libc!

Cost vs benefit ?

Posted Jun 7, 2026 8:27 UTC (Sun) by erincandescent (guest, #141058) [Link] (56 responses)

The problem here is that on Linux we link against /lib/libc.so.6, and not e.g. /usr/lib/sdk/rhel-8.0/lib/libc.so.6.stub

of course working out what such "sdks" should exist and what such a stub file should look like... is a non-trivial question

Cost vs benefit ?

Posted Jun 7, 2026 9:58 UTC (Sun) by malmedal (subscriber, #56172) [Link] (52 responses)

I'm convinced that glibc is a major reason behind both Go's otherwise rather surprising success and the year of the Linux desktop never arriving.

Even in ostensibly well-run computer departments there are almost always some dirty corners with an out of support RHEL box doing something important, can't be upgraded, can't be turned off, don't have any other like it, and then you get a request to just do this specific thing and don't touch anything else. With Go you can sit on a random Mac/Windows/Linux box, write what you need, and just copy it over.

Cost vs benefit ?

Posted Jun 7, 2026 10:14 UTC (Sun) by bluca (subscriber, #118303) [Link] (51 responses)

That just exposes a limitation of the build and deployment system in use. It's really not that hard to target the specific environment one wants to deploy for and automatically build repositories. One doesn't even have to reinvent such a system, as they already exist and are plentiful, like SUSE's OBS.

Cost vs benefit ?

Posted Jun 7, 2026 11:41 UTC (Sun) by malmedal (subscriber, #56172) [Link]

It's not hard, no. At least *I* don't find it hard. Other people seem to be mystified by build systems however, so in previous jobs more than my fair share of these tasks have been pushed over to me.

Nobody has ever asked me to run go build and scp for them...

Cost vs benefit ?

Posted Jun 7, 2026 22:50 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link] (49 responses)

For me it looked like this: I need to compile BlahApp to work on Ubuntu 18.04

So what do I do? I run the Ubuntu 18.04 in a VM/container, and try to compile my app there. Except that 18.04 doesn't have a new enough CMake, so I need to download and build CMake. I also then need to build libsomething, because it's not recent enough. Oh, and of course its pkgconfig is broken, so I need to just hardcode the paths in CFLAGS/LDFLAGS.

...6 hours later...

Ok, now I have a binary. And you want me to automate ALL these steps?!?

The story with Go: `go build ....`, copy the binary over. If the kernel has the required syscalls, it'll work.

Cost vs benefit ?

Posted Jun 7, 2026 23:59 UTC (Sun) by bluca (subscriber, #118303) [Link] (46 responses)

> The story with Go: `go build ....`, copy the binary over. If the kernel has the required syscalls, it'll work.

The story then goes: you vendored 1092302 dependencies, of which one third roll their own crypto. You have no idea of course, because you got no clue, you just run "go build" as you found on StackOverflow and you have no idea what any of that does, but it works, so who cares right? 4 months later half of those have 10.0 severity CVEs that enable RCEs. You still have no clue, as you just have a single binary blob and no idea what's in there. But hey, it's so easy to "scp" around!

Cost vs benefit ?

Posted Jun 8, 2026 2:24 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link] (45 responses)

And do you think a binary for an old Ubuntu 18 will somehow get updates from the Ubuntu 24 repo? Or that I'm going to re-do all the building shenanigans once one of the libraries gets Mythosed?

> 4 months later half of those have 10.0 severity CVEs that enable RCEs. You still have no clue, as you just have a single binary blob and no idea what's in there. But hey, it's so easy to "scp" around!

On the contrary. I can throw in a Github's dependabot to watch over my binary and scream at me if there are CVEs in my Go library's dependencies. For free.

The solutions to monitor a hodgepodge of randomly wired C libraries exist, but are more complex.

Cost vs benefit ?

Posted Jun 8, 2026 10:07 UTC (Mon) by bluca (subscriber, #118303) [Link] (44 responses)

> And do you think a binary for an old Ubuntu 18 will somehow get updates from the Ubuntu 24 repo? Or that I'm going to re-do all the building shenanigans once one of the libraries gets Mythosed?

Of course you won't, which is the problem: you have no idea what you are doing, and yet you ended up in charge of the entire stack, which is a job you obviously cannot do, instead of letting the professionals (Canonical) handle it for you, as it is proper and sensible and demonstrated to work

> For free.

And then it breaks because you misconfigured it and never noticed, and never fires anyway because you are just "scp" copying around random binaries built on your laptop at random points in time and lost track of them

> The solutions to monitor a hodgepodge of randomly wired C libraries exist, but are more complex.

"apt upgrade" - wow, such complexity!

Cost vs benefit ?

Posted Jun 8, 2026 10:16 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link] (2 responses)

> Of course you won't, which is the problem: you have no idea what you are doing, and yet you ended up in charge of the entire stack, which is a job you obviously cannot do, instead of letting the professionals (Canonical) handle it for you, as it is proper and sensible and demonstrated to work

Ah yes, "don't bother your pretty little head when adults are speaking". This attitude led to classic desktop Linux only _now_ gaining some momentum. And only because distros finally embraced packaging and stable ABIs (and Win32 is the most stable Linux desktop ABI so far).

> And then it breaks because you misconfigured it and never noticed, and never fires anyway because you are just "scp" copying around random binaries built on your laptop at random points in time and lost track of them

Well, and what is YOUR proposal for backporting to older OSes?

Perhaps the good old Soviet Union method? As in, "Today the Great Soviet People have no demand for butter".

> "apt upgrade" - wow, such complexity!

Try to build the most recent LTFS on older RHEL or Debian. Go on, I'll wait.

What? You're still trying to compile libicu and fighting with its message generator?

Cost vs benefit ?

Posted Jun 8, 2026 11:18 UTC (Mon) by bluca (subscriber, #118303) [Link] (1 responses)

> This attitude led to classic desktop Linux only _now_ gaining some momentum.

There is no momentum on Linux desktop, there never was, there never will. It has nothing to do about your whingings, and instead all to do with 99.99999% of desktop hardware available for sale not shipping with it, because vendors simply don't care - and why should they, it's a low-margin business after all, and as for-profit entities in a capitalist society they need to prioritize making more money than they spend or they go out of business, over ideology

> Well, and what is YOUR proposal for backporting to older OSes?

Have you tried not writing shitty software? It's _really_ not that hard to find even mediocre software that just works on any platforms it targets. First you decide what your targets are, and then you write software accordingly. It's really trivial to do, one just has to know what they are doing.

Here's a handy example: https://build.opensuse.org/project/show/network:vpn:openc...
<shock and horror> single source tree that supports targeting centos 8/9/10, debian 11/12/13/14, fedora 43/44/45, rhel 8, sle 15/16, ubuntu 20.04/22.04/24.04/26.04, leap 15/16, thumbleweed. Across 4 or 5 architectures. As if by magic. No golang crap in sight.

> What? You're still trying to compile libicu and fighting with its message generator?

No, I'm not compiling any library, because unlike you I know what I am doing, and I let the OS vendor worry about providing libraries. Not my problem, as it shouldn't be.

Please stop - again

Posted Jun 8, 2026 13:13 UTC (Mon) by corbet (editor, #1) [Link]

Luca, this is the second time in just a few days that I've had to ask you to stop with the personal attacks. Seriously, just stop.

Cost vs benefit ?

Posted Jun 8, 2026 11:15 UTC (Mon) by malmedal (subscriber, #56172) [Link] (40 responses)

> instead of letting the professionals (Canonical) handle it for you, as it is proper and sensible and demonstrated to work
> [...]
> "apt upgrade" - wow, such complexity!

The professionals at Canonical have actually found that apt upgrade is too difficult so they invented snaps which basically is static linking except more heavyweight.

apt works well enough as long as you only use the software that Canonical compiiles for you, but as soon as you want to use something from a third party vendor it starts being complex. It often works for a while with one vendor that gives you a nice ppa, but if you need two or three it gets really annoying to debug the conflicts.

Cost vs benefit ?

Posted Jun 8, 2026 11:22 UTC (Mon) by bluca (subscriber, #118303) [Link] (39 responses)

> The professionals at Canonical have actually found that apt upgrade is too difficult so they invented snaps which basically is static linking except more heavyweight.

That's an answer to flatpak and sandboxing, nothing to do with static linking

> apt works well enough as long as you only use the software that Canonical compiiles for you, but as soon as you want to use something from a third party vendor it starts being complex. It often works for a while with one vendor that gives you a nice ppa, but if you need two or three it gets really annoying to debug the conflicts.

Have you tried not using crappy vendors?

Cost vs benefit ?

Posted Jun 8, 2026 12:27 UTC (Mon) by malmedal (subscriber, #56172) [Link] (38 responses)

> That's an answer to flatpak and sandboxing, nothing to do with static linking

It's an answer to dynamic linking not being a good idea with more than one supplier.

> Have you tried not using crappy vendors?

Vendors are not selected for their skills in packaging software, the criteria is whether the software is useful or not.

Cost vs benefit ?

Posted Jun 8, 2026 12:44 UTC (Mon) by bluca (subscriber, #118303) [Link] (37 responses)

> It's an answer to dynamic linking not being a good idea with more than one supplier.

It still uses dynamic linking, so no

> Vendors are not selected for their skills in packaging software, the criteria is whether the software is useful or not.

If they can't even get the basics right then it is not useful by definition

Cost vs benefit ?

Posted Jun 8, 2026 13:14 UTC (Mon) by malmedal (subscriber, #56172) [Link] (36 responses)

> It still uses dynamic linking, so no

Please read what I wrote, "not a good idea with more then one supplier". If you have e.g. an appimage you have one supplier for all the dlls so it's perfectly fine.

> If they can't even get the basics right then it is not useful by definition

It's useful if the software does something the buyer wants done.

Cost vs benefit ?

Posted Jun 8, 2026 13:33 UTC (Mon) by bluca (subscriber, #118303) [Link] (35 responses)

> Please read what I wrote, "not a good idea with more then one supplier". If you have e.g. an appimage you have one supplier for all the dlls so it's perfectly fine.

The supplier of libraries is the same in both cases - the OS vendor, IE Canonical

> It's useful if the software does something the buyer wants done.

If it doesn't work then it doesn't get anything done by definition

Cost vs benefit ?

Posted Jun 8, 2026 14:44 UTC (Mon) by malmedal (subscriber, #56172) [Link] (34 responses)

> The supplier of libraries is the same in both cases - the OS vendor, IE Canonical

No, in an AppImage it can be anybody, usually whoever was the OS vendor where the packge was made.

> If it doesn't work then it doesn't get anything done by definition

You seem to not bother reading the posts you respond to, as I explained previously, it is often that two apps from different vendors will have confliciting requirements. Each will work just fine by itself. Sometimes they will both work at first and then a later update will introduce a conflict.

Cost vs benefit ?

Posted Jun 8, 2026 14:48 UTC (Mon) by bluca (subscriber, #118303) [Link] (33 responses)

> No, in an AppImage it can be anybody, usually whoever was the OS vendor where the packge was made.

Well sure, but appimage is an entirely different thing from snaps...?

> You seem to not bother reading the posts you respond to, as I explained previously, it is often that two apps from different vendors will have confliciting requirements. Each will work just fine by itself. Sometimes they will both work at first and then a later update will introduce a conflict.

Sure, software sometimes breaks, still not sure what this has to do with shared libraries?

Cost vs benefit ?

Posted Jun 8, 2026 16:00 UTC (Mon) by malmedal (subscriber, #56172) [Link] (32 responses)

> but appimage is an entirely different thing from snaps...?

Thought it would be easier to explain that way since I find snapcraft.io is a bit messy, but lets use snaps then.

Look at snapcraft.io and click on C/C++ and see this listed advantage of snaps: "Snaps install and run the same across Linux. They bundle the exact versions of your app’s dependencies."

This particular advantage is the same as what static linking gives you.

Snaps are in many ways better than static linking since static linking only ensures that the libraries are correct, while snaps can also make sure that other resource-files, icons, images, etc. are also present. There is in no rule that a snap uses shared libraries from Canonical, they can be from anywhere, there was talk of saving memory and use only one copy if two snaps want the exact same library, but I am not sure if that has been implemented.

You can list further advantages of snaps such as automatic updates, but shared libraries out of sync is the item that I have seen stop users being able to use the software at all, so for that reason I focus on just that point as the big issue that needs fixing for Linux to take over the desktop.

> Sure, software sometimes breaks, still not sure what this has to do with shared libraries?

What usually happens is that software from vendor A and vendor B depends on different versions of the same library. In principle this can also happen with any kind of file, but vendors are usually diligent about having their name somewhere in that path, so I've only ever seen it happen with shared libraries.

Cost vs benefit ?

Posted Jun 8, 2026 16:15 UTC (Mon) by bluca (subscriber, #118303) [Link] (31 responses)

> This particular advantage is the same as what static linking gives you.

It is not. It's just a worse version of flatpak, which achieves the same result by having shared runtimes with shared libraries that applications use.

> What usually happens is that software from vendor A and vendor B depends on different versions of the same library. In principle this can also happen with any kind of file, but vendors are usually diligent about having their name somewhere in that path, so I've only ever seen it happen with shared libraries.

Those are bugs. All projects must depend only on what the target vendor provides: et voila, no conflicts.

Cost vs benefit ?

Posted Jun 8, 2026 17:18 UTC (Mon) by malmedal (subscriber, #56172) [Link] (30 responses)

> Those are bugs.

Yes, nobody is disputing that. They are bugs that keep happening on Linux frequently enough that I think it is a major reason for why the year of the Linux desktop hasn't arrived yet. (Not the only reason to be clear)

Cost vs benefit ?

Posted Jun 8, 2026 17:27 UTC (Mon) by bluca (subscriber, #118303) [Link] (26 responses)

That seems wildily unlikely. The "year of the linux desktop" will never arrive because there's no reason for consumer device vendors to spend any serious money to ship such devices. This has nothing to do with shared libraries, they won't even know what they are, and all to do with razor thin margins and a sector which is experiencing serious difficulties as it is, without embarking in hopeless projects with no clear returns. Such is life under capitalism.

Cost vs benefit ?

Posted Jun 8, 2026 18:36 UTC (Mon) by malmedal (subscriber, #56172) [Link] (25 responses)

Linux is cheaper than the competition, in a capitalistic society the cheapest option will most often win, so you would expect Linux to win everywhere. In practice it has won everywhere *except* the *desktop*. So what is special about the desktop compared to the rest of computing?

The reason I think shared libraries is part of the problem is from observing users. Even colleagues whose job it was to actually run Linux would struggle with any shared library issues.

Cost vs benefit ?

Posted Jun 8, 2026 18:48 UTC (Mon) by bluca (subscriber, #118303) [Link] (24 responses)

> Linux is cheaper than the competition, in a capitalistic society the cheapest option will most often win, so you would expect Linux to win everywhere. In practice it has won everywhere *except* the *desktop*. So what is special about the desktop compared to the rest of computing?

No, because the cost of the license is irrelevant and a pittance. What matters is what sales&support agreements large hardware vendors get with large software vendors, what kickback their sales folks get, how much they get wined&dined, etc etc.

> The reason I think shared libraries is part of the problem is from observing users. Even colleagues whose job it was to actually run Linux would struggle with any shared library issues.

The average user doesn't even know what a library is, let alone having any issue with it. They just click on the desktop dashboard to start the browser or mail client or document editor. If they are a bit more advanced, they may also know how to open steam to install and play a few games.
IE, you are falling for https://xkcd.com/2501/

Stop here

Posted Jun 8, 2026 19:03 UTC (Mon) by jzb (editor, #7867) [Link]

It appears that we have strayed far from the original topic; perhaps this debate could be moved elsewhere or at least not conducted here.

Cost vs benefit ?

Posted Jun 9, 2026 8:49 UTC (Tue) by malmedal (subscriber, #56172) [Link] (22 responses)

> you are falling for https://xkcd.com/2501/

I'm not, on windows a normal user can copy a program from one machine to another. On Linux, no.

Cost vs benefit ?

Posted Jun 9, 2026 10:32 UTC (Tue) by bluca (subscriber, #118303) [Link] (21 responses)

Really? So you can copy a UWP from Windows 11 to Windows XP and it will work?

Cost vs benefit ?

Posted Jun 9, 2026 16:50 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link] (18 responses)

You can copy a Win32 binary, and as long as it doesn't depend on anything new, it'll work.

Nobody is asking for impossible. But glibc has been actively preventing the _basic_ interoperability.

Cost vs benefit ?

Posted Jun 9, 2026 17:14 UTC (Tue) by bluca (subscriber, #118303) [Link] (17 responses)

> You can copy a Win32 binary, and as long as it doesn't depend on anything new, it'll work.

...so exactly as you do on Linux with glibc, then. You target the lowest common denominator among your targets, and then you can ship on all of them, if for some reason you insist on a single build because the build system is use is not adequate.

Cost vs benefit ?

Posted Jun 9, 2026 17:42 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link] (16 responses)

No. You seem to be remarkably impervious to actual information that people are telling you.

Windows or macOS make it _very_ easy to target earlier OSes. I can open VisualStudio, specify Windows 10 as a target, and build code for it. Even though I'm running on Windows 11. Same for macOS, I can set up macOS BigSur from 2020 as a target, even though I'm running on Tahoe from 2026.

And this behavior has ALWAYS been the case. I used to compile software for Win95 from my workstation on WinXP.

There is NOTHING like this for classic desktop Linux. I can not just install Ubuntu 26.04 and create a binary that would work on Ubuntu 24.04. Even though this binary does not use anything more recent than epoll(), which was added more than 20 years ago. And the main reason for that is the utterly braindead glibc symbol versioning.

That's why Go became so popular. I can make a binary, scp it and then use it immediately.

Cost vs benefit ?

Posted Jun 9, 2026 18:24 UTC (Tue) by bluca (subscriber, #118303) [Link] (15 responses)

> I can not just install Ubuntu 26.04 and create a binary that would work on Ubuntu 24.04.

Of course you can. I regularly build on my Debian 13 x86_64 binaries targeting a Yocto Dunfell aarch64 environment, no sweat.
It's exactly the same as Windows or OSX. The toolchains and dev environments are different - obviously - but the processes are essentially equivalent.

Cost vs benefit ?

Posted Jun 9, 2026 18:34 UTC (Tue) by quotemstr (subscriber, #45331) [Link] (5 responses)

Have you built for other OSes? Down level targeting of glibc is a whole universe of pain that's just absent on other systems. That you need something as enormous as Yocto (built for generating embedded device immutable images, not distribution packages) to get a semblance of downlevel compatibility is a further indictment of the glibc model.

Cost vs benefit ?

Posted Jun 9, 2026 18:55 UTC (Tue) by bluca (subscriber, #118303) [Link] (4 responses)

No, it isn't an indictment of anything, as it's not any different from the OP's "_very_ easy" way to target OSX or Windows: you need a toolchain for the target. That's what XCode or VS give you, wrapped in a nice GUI. Nobody stops anybody from providing that same GUI for Linux targets, but it is absolutely not glibc's bloody job to give you one.

On Linux in fact things are _better_ because you have multiple ways of targeting other systems, while on OSX/Windows you either use the proprietary tools or can get into the sea.

On Linux you can:

- get a toolchain for the target (eg, Yocto with its SDK, debootstrap/dnf bootstrap/whatever else to get a target tree, etc) and use that to compile
- build packages for all your targets natively on a distro build system (eg on SUSE's OBS like the example I already gave upthread which from a single source tree builds for a dozen distros/versions/arches https://build.opensuse.org/project/show/network:vpn:openc... )
- use your favourite container engine, VSCode has extensions to wrap this up in a pretty GUI
- use Flatpak if you are building a desktop app

The demand that building against your _host_'s system should work on _any_ system is what is patently absurd. You don't demand that of OSX or Windows, and yet you demand it of Linux, and then badmouth glibc (why not gcc? why not openssl? etc) when it obviously doesn't work. Why? It makes no sense.

But the FUNNIEST thing is that, you actually CAN build against your host system and target an old one, without any of the above. You need to code it properly of course, and it's not as simple as the above methods, but if you use dlsym to load functions at runtime and handle it gracefully when they are not found, it does work. It is possible, and not that hard either, we do that in systemd now after Daan implemented it some weeks ago.

Cost vs benefit of extended discussion

Posted Jun 9, 2026 18:59 UTC (Tue) by corbet (editor, #1) [Link] (1 responses)

I vaguely recall that, once upon a time, this was an article about fork() and exec(). This discussion has clearly strayed far from that topic. Perhaps more to the point, though, it seems to be becoming increasingly circular. Perhaps it's time for all of the parties involved to let it go?

Cost vs benefit of extended discussion

Posted Jun 9, 2026 19:00 UTC (Tue) by bluca (subscriber, #118303) [Link]

> I vaguely recall that, once upon a time, this was an article about fork() and exec().

Yes, but then it was forked.

...sorry

Cost vs benefit ?

Posted Jun 9, 2026 19:15 UTC (Tue) by quotemstr (subscriber, #45331) [Link] (1 responses)

> The demand that building against your _host_'s system should work on _any_ system is what is patently absurd

No Way to Prevent This, Says Only OS Where This Regularly Happens

> when it obviously doesn't work

Compiling for a downlevel OS works just fine on other systems, including Android with Bionic. And you don't need to connect to some SUSE cloud service to do it.

The problem with downlevel compatibility for GNU/Linux isn't that it's physically impossible, but that in practice it's so fraught (especially since binaries often seem to work until they don't) that it leads people to eschew static linking entirely.

If it were so easy, why would people spend so much time making partial solutions like the glibc polyfill thing (apparently recently abandoned) or the older glibc downlevel header project (also abandoned)?

All glibc has to do is provide a version target preprocessor macro. It would cost them little and benefit the ecosystem massively.

> use dlsym to load functions at runtime and handle it gracefully when they are not found,

dlvsym, since presumably you want to target a specific contract. If you don't, you might call a new (or old) version of a symbol under ABI skew and summon the UB nasal demons.

Under the glibc model, one does not simply call dlsym. dlsym finds the default version of the symbol, which glibc points at the latest. That your "not that hard" advice in fact creates programs with nightmare ABI time bombs embedded in them is kind of emblematic of how the barriers to broader Linux adoption are social, not technical.

The unsafely of dlsym is another charge against the glibc way of managing versions.

Cost vs benefit ?

Posted Jun 9, 2026 19:18 UTC (Tue) by daroc (editor, #160859) [Link]

I will charitably assume you hadn't seen Jon's comment asking for this thread to wind up, but it has in fact wandered far off topic, and we should leave it where it lies.

Cost vs benefit ?

Posted Jun 9, 2026 18:46 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link] (8 responses)

And how are you doing it? Here's an actual issue that I have: I want to build LTFS to run on Synology. It has build scripts for Debian and it doesn't have to depend on anything external.

> It's exactly the same as Windows or OSX.

It's really really not. The only sane way to do it is to have another glibc in some sort of a container, from a classic chroot to a VM.

You can play around with LD scripts or manually specifying symbols, but this is practical only for no-dependencies plain C binaries. Even just depending on libstc++ makes this infeasible.

Cost vs benefit ?

Posted Jun 9, 2026 18:59 UTC (Tue) by bluca (subscriber, #118303) [Link] (7 responses)

> And how are you doing it?

https://lwn.net/Articles/1077242/

If your vendor doesn't give you what you need, go harass them instead of the glibc maintainers

> It's really really not. The only sane way to do it is to have another glibc in some sort of a container, from a classic chroot to a VM.

...which is the same bloody thing that the "_very_ easy" things you mentioned on Windows/OSX do - they give you a toolchain for your target. You accept it for those, and even call it "_very_ easy", and yet you refuse to accept the same on Linux, which is patently absurd

Cost vs benefit ?

Posted Jun 9, 2026 19:21 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link] (6 responses)

> If your vendor doesn't give you what you need, go harass them instead of the glibc maintainers

So I should harass Debian? Synology uses old Debian as a base.

> ...which is the same bloody thing that the "_very_ easy" things you mentioned on Windows/OSX do - they give you a toolchain for your target. You accept it for those, and even call it "_very_ easy", and yet you refuse to accept the same on Linux, which is patently absurd

Ok. How do I build on Debian 13 for Debian 12?

Your solution is "run everything in a container and bundle all dependencies". Which for some reason you also seem to be against?

Cost vs benefit ?

Posted Jun 9, 2026 19:41 UTC (Tue) by bluca (subscriber, #118303) [Link] (5 responses)

> So I should harass Debian? Synology uses old Debian as a base.

No, Synology, since that's the vendor

> Ok. How do I build on Debian 13 for Debian 12?

"debootstrap bookworm" and then use the sysroot to build against. No "dependency bundling" needed.

Cost vs benefit ?

Posted Jun 9, 2026 20:02 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link] (4 responses)

> "debootstrap bookworm" and then use the sysroot to build against. No "dependency bundling" needed.

Yes. And this is literally "run everything in a container". With a full OS installed, a compiler, and a set of include headers.

Microsoft toolchain does NOT require it. MSVS does not have an old copy of Windows hiding inside it. You simply set up a C preprocessor definition that hides symbols from the compiler. The system API .h files have guards like "#if WINVER > WIN7" so that compilation fails if the target version is too old for that symbol.

That's it. It's THAT simple.

Cost vs benefit ?

Posted Jun 9, 2026 20:22 UTC (Tue) by bluca (subscriber, #118303) [Link] (3 responses)

> Yes. And this is literally "run everything in a container".

No. That's the build toolchain, your equivalent of VS or XCode. You _do not_ ship that. It's the build environment. You do not run anything in it.

Cost vs benefit ?

Posted Jun 9, 2026 21:57 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link] (2 responses)

> No. That's the build toolchain, your equivalent of VS or XCode. You _do not_ ship that. It's the build environment. You do not run anything in it.

Sigh. It's not. It's a full containerized system, with full binary copies of glibc, binutils and other libraries. It even requires root privileges to install, fakeroot is not enough. But no matter, suppose that somebody stripped all the binaries out of it and gave it to you as a tar file.

GCC indeed supports systemroot, so you can use it for very simple apps.

But anything more complicated breaks, in practice. For example, pkg-config often can't mix libraries inside and outside the systemroot properly. I recommended you to try and build LTFS ( https://github.com/LinearTapeFileSystem/ltfs ) for an earlier Debian. It's a real project, built with classic autotools. It doesn't use anything special.

And if you want to retain sanity, building it inside a chroot/container is the easiest option. And yes, glibc is absolutely the root of evil here. It does not support easy version selection, so you need to have a full copy of it for the target OS. Same goes for the C++ stdlib.

Cost vs benefit ?

Posted Jun 9, 2026 22:03 UTC (Tue) by bluca (subscriber, #118303) [Link]

> Sigh. It's not.

Yes, it is the same. It doesn't matter that it works differently, it's irrelevant, of course it's different, as it's a different system. XCode and VS are not identical either. What matters is that there are multiple simple and common ways to build for different targets, exactly like on other OSes.

Cost vs benefit ?

Posted Jun 10, 2026 9:14 UTC (Wed) by taladar (subscriber, #68407) [Link]

But the same is true for any other library, you literally need a version of every library you want to use for the target system. So why not just use a copy of the target system inside a container/chroot/... for that purpose instead of some curated set of libraries packaged in a slightly different way a second time?

Cost vs benefit ?

Posted Jun 9, 2026 18:00 UTC (Tue) by Wol (subscriber, #4433) [Link] (1 responses)

Depending on when XP went EOL, then yes if you build a program on Win11 then it would install successfully on XP. (More likely Win7, XP has been EOL for over 10 years.)

But the rule is if you build on the latest Windows, and install on the oldest "live" Windows, it should work (and if you install on EOL windows, it will probably work - "probably" getting less likely the longer it's been EOL).

Cheers,
Wol

Cost vs benefit ?

Posted Jun 9, 2026 18:17 UTC (Tue) by bluca (subscriber, #118303) [Link]

Nope. Not an UWP program. Because the point was, these things work everywhere, _if_ you code and build for the right target.

Cost vs benefit ?

Posted Jun 9, 2026 12:56 UTC (Tue) by mbunkus (subscriber, #87248) [Link] (2 responses)

Yesterday Ubuntu released a security update for nginx, the well-known & widely used web server & reverse proxy, as part of their "security" update channel. All machines that have automatic security updates enabled (usually the "unattended upgrades" mechanism) dutifully installed the update over the last 24h.

Today we arrived at $DAYJOB to a lot of our customers having opened support tickets as their reverse proxies didn't work properly anymore. Judging by the bug report on Launchpad[1] server fleets all over the world face the same issue after upgrading.

Turns out they broke the ABI with their backport of the security fix into the packaged versions. Now that wouldn't be bad if they had also re-built all packages using said ABI & including versions of those packages in the same security update, but they didn't realize they needed to (= didn't realize they broke the ABI) in the first place, so they didn't do that. Hence tons of servers trying to run a mixture of now incompatible nginx ABIs. As of 1 hour ago Ubuntu has acknowledged this, bumped the criticality to "critical", and will address the issue by first reverting the change, releasing another security update (a "regression" update), and then figuring out how to implement the fix without breaking the ABI.

I'm not making a judgement here about static vs. dynamic linking as both prioritize different properties and concerns which simply cannot be fulfilled all at the same time. Would this particular issue have happened with static linking? Obviously not. Would have static linking helped in any way, shape or form with any of the user usual security updates that distros push out daily and which do NOT require re-building the whole world? Also obviously no.

[1] https://bugs.launchpad.net/ubuntu/+source/nginx/+bug/2155992

Cost vs benefit ?

Posted Jun 9, 2026 13:12 UTC (Tue) by bluca (subscriber, #118303) [Link]

Yeah the lack of ABI checks in security updates is problematic. We do have the tools for it, but the glue is just not in place. The vast, vast, vast majority of the time it all works fine, but it very occasionally goes wrong like this, and having abi-compliance-checker or equivalent as part of the autopkgtest pipeline would be beneficial to avoid these occurrences.

Cost vs benefit ?

Posted Jun 10, 2026 9:23 UTC (Wed) by taladar (subscriber, #68407) [Link]

I would guess most of those ABI users were plugins, a pattern fundamentally impossible with static linking anyway as long as you want to select which plugins are included after compile time.

Cost vs benefit ?

Posted Jun 15, 2026 5:48 UTC (Mon) by aleXXX (subscriber, #2742) [Link] (1 responses)

You can download and unpack the binary distribution of CMake, it should work more or less everywhere.

Cost vs benefit ?

Posted Jun 15, 2026 11:15 UTC (Mon) by malmedal (subscriber, #56172) [Link]

The point is not that it's difficult, it's not.

The point is that it is tedious and time-consuming and most people can't be bothered, while they can be bothered to type "go build".

(Though I should mention that it's become a lot less tedious recently, LLMs can now do this with a single prompt)

Cost vs benefit ?

Posted Jun 7, 2026 22:45 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

This is no different from Windows. Apps link to the same user32.dll in the system32 folder.

Microsoft made sure to NOT change the behavior of published API functions, except for serious bugfixes. Instead, they create new functions as needed with new names. glibc developers decided to keep the function names and transparently change the behavior, depending on the glibc version.

This is the crux of the issue. Back when I was doing Windows-to-Linux ports around 2008, I had to use a virtual machine with an old RedHat to make sure I could compile a binary that would work everywhere.

Cost vs benefit ?

Posted Jun 8, 2026 7:56 UTC (Mon) by kleptog (subscriber, #1183) [Link] (1 responses)

> The problem here is that on Linux we link against /lib/libc.so.6, and not e.g. /usr/lib/sdk/rhel-8.0/lib/libc.so.6.stub

> of course working out what such "sdks" should exist and what such a stub file should look like... is a non-trivial question

Evidently there is demand. If it were easy someone would have done it, right?

Cost vs benefit ?

Posted Jun 8, 2026 9:14 UTC (Mon) by malmedal (subscriber, #56172) [Link]

> If it were easy someone would have done it, right?

It's easy, then glibc does a random change and you have to do the easy thing again and again and yet again. Better to use a language like go or rust where the designers actually understand the problem.

Useful tools include patchelf, chrpath and statifier. You can often, but not always do it with just giving /lib/ld-linux.so some command-line flags and environment variables.

Cost vs benefit ?

Posted Jun 7, 2026 10:34 UTC (Sun) by quotemstr (subscriber, #45331) [Link] (2 responses)

Yes, symbol versioning is terrible and unnecessary. (Function has a new contract? Give it a new name. That's how most systems work and it works fine.)

But the problem is deeper: glibc just refuses to provide a TARGET_VERSION facility of some kind. All other major operating systems let you use new headers to target old systems. Even with symbol versions, glibc could too. They just don't want to. They can't sit there any tell everyone version targeting is infeasible when the rest of the planet does it.

glibc's decades of obstinacy about version targeting has distorted the Linux ecosystem, wasted inestimable developer-years, and, ultimately, has destroyed our ability to coordinate intraprocess resources in userspace.

Thanks, libc-alpha.

Cost vs benefit ?

Posted Jun 7, 2026 22:59 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link] (1 responses)

And the worst of it, this was done for no real gain in performance or compatibility. glibc still breaks backwards compatibility from time to time, and glibc's allocator is famously middling.

Cost vs benefit ?

Posted Jun 8, 2026 1:25 UTC (Mon) by 0xilly (subscriber, #172315) [Link]

I often wonder if the kernel could create a libkernel and expose that via the VDSO and one could avoid glibc if they wanted outside of direct syscalls or musl etc…

Cost vs benefit ?

Posted Jun 8, 2026 11:46 UTC (Mon) by ballombe (subscriber, #9523) [Link] (3 responses)

> The problem is that glibc's symbol versioning is terrible. If you compile an app on a later version of glibc, you can't deploy it on an earlier version. And there's no easy way to fix this.

Sure there is... Install your newer libc in /bil64, and edit the binaries so that the .interp section says /bil64/ld-linux-x86-64.so.2 instead /lib64/ld-linux-x86-64.so.2

It is not rocket science.

Cost vs benefit ?

Posted Jun 8, 2026 18:20 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link] (2 responses)

> Sure there is... Install your newer libc in /bil64, and edit the binaries so that the .interp section says /bil64/ld-linux-x86-64.so.2 instead /lib64/ld-linux-x86-64.so.2

Try that. It'll explode if you have NSS/PAM modules or any other kind of dynamically loaded code. And yes, I _did_ try that.

Cost vs benefit ?

Posted Jun 8, 2026 18:35 UTC (Mon) by farnz (subscriber, #17727) [Link] (1 responses)

Have you tried the .symver assembly directive to link against older symbol versions with a newer glibc? It should, in theory, work (albeit I haven't tested it), but it'll be painful to use because you need every object file you build to have a consistent set of imported symbols to ensure that you can dynamically link against an older glibc.

If that works, then there's room for glibc to supply a "compat" header for each older version that just sets the symbol versions to link against for every glibc signal, and for your build system to use the -include preprocessor option to force-include the right compat header in your code.

Cost vs benefit ?

Posted Jun 8, 2026 19:15 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link]

Symvers or linker scripts indeed work. But they are hard to integrate when you have third-party libraries with their own build systems. I used to binary-patch the executables to downgrade the versions.

It really needs to be something like a "target level" support from glibc, like in Windows/macOS, that every library respects.

Cost vs benefit ?

Posted Jun 8, 2026 12:10 UTC (Mon) by bluca (subscriber, #118303) [Link] (4 responses)

> If you compile an app on a later version of glibc, you can't deploy it on an earlier version.

Yeah that's horrible! And also if I compile an app for OSX 12, then I can't run it on Windows 11. Also if I compile it for DOS, then I can't run it on HP Unix. What a shit show, am I right?

Cost vs benefit ?

Posted Jun 8, 2026 18:31 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link] (3 responses)

Are you saying that two versions of Linux Desktop are as different as Windows and macOS?

...

Actually, you might be right.

Cost vs benefit ?

Posted Jun 8, 2026 18:34 UTC (Mon) by bluca (subscriber, #118303) [Link] (2 responses)

Yes, of course, and that's exactly how it should be, because there's no such thing as "Linux Desktop"

Cost vs benefit ?

Posted Jun 8, 2026 23:28 UTC (Mon) by mjg59 (subscriber, #23239) [Link] (1 responses)

It feels a little bit odd for someone working on a project explicitly attempting to define a consistent set of plumbing shared between distributions to not be concerned about offering a consistent layer above that.

Cost vs benefit ?

Posted Jun 9, 2026 10:28 UTC (Tue) by bluca (subscriber, #118303) [Link]

Vendoring the universe and static linking is not a sensible answer to that problem. It's a cop out with severe drawbacks that get ignored because they are "tomorrow's problems"

Cost vs benefit ?

Posted Jun 7, 2026 15:43 UTC (Sun) by alkbyby (subscriber, #61687) [Link] (5 responses)

Well. Quite a lot of discussion. Turns out I was not 100% correct too.

So, I don't know about Zig, but the Go runtime is among the most well-engineered. I'd generally trust them to handle any footguns around process spawning. I took a quick look at their code, and for some reason they use CLONE_VFORK only if not using CLONE_NEWUSER. Which is by far most of the time, but it is still odd that they do fork. At least in the Go world, it is not common to encounter pthread_atfork issues. So, rare use of fork is just a cost thing most of the time. But yes, occasionally they'll face trouble using it.

With that said, libc isn't always 100% right, either. Looks like I need to elaborate (and I welcome people pointing out any errors in the reasoning below).

So, first, let me note that I silently conflated classic vfork and CLONE_VFORK. Let's remind ourselves that a classic vfork returns to _the same stack frame_ in both the parent and the child. So we're permanently one compiler optimization away from complete disaster. One can easily imagine a tiny asm "trampoline" function that makes classic vforking safe. Just have it invoke the user-supplied function in the child. But this is not how people commonly do vfork. On the other hand, CLONE_VFORK forces you to give a dedicated stack to the child (which can be allocated on the parent's stack, since spawning doesn't need much stack). Making it nice and clean.

But not all libc-s use CLONE_VFORK. glibc started doing so around 2016. Before that, defaulting to fork. With the "use-vfork" flag available and defaulting to off. Notably, bionic still does the exact same behavior (fork by default, vfork (classic one!) if asked).

Now, what is the problem with fork. Apart from being much more expensive (and more so with larger and more heavily threaded processes, as pointed out above), it is effectively incompatible with multi-threading. To remind people. Fork snapshots the parent process's memory. So a bunch of locks will occasionally be in the "locked" state, and some global state protected by those locks will be messed up. Among those messed-up things, you're not allowed to invoke malloc/free (but see below). Of course, if all you do in the child is invoking few syscalls and exec-ing, it doesn't matter. That is what posix_spawn is built to do. All the "actions" are prepared in advance (which involves dynamic memory allocation) and only a carefully curated set of essentially bare syscalls are invoked in the child. The only global state ever touched is errno (which is already implemented to be async-signal-safe).

So, in theory posix_spawn-ing via fork versus CLONE_VFORK becomes just a matter of efficiency. But in practice, we have pthread_atfork complications.

pthread_atfork allows custom code to be invoked before and after fork (in both the parent and child). A somewhat typical use is to take lock(s) before fork, so that whatever global state is in the snapshot is safe and consistent, and then release the lock(s) after fork. The issue is making sure the ordering of taking the locks is safe. Which, in the most general case, is ~impossible to get right. A particularly notable use case for that is jemalloc. Being reasonably advanced malloc, they have a number of locks. And they have a very elaborate code scattered across every single module that carefully takes those locks in the right order. This, and the fact that jemalloc sets up its atfork handler early enough, makes malloc/free usable in practice in child processes after forking. I maintain gperftools, and we also do the locking/unlocking dance and early registration. But we don't have absolutely all the locks covered, and doing so would be quite challenging. Our "sibling" project, abseil tcmalloc, doesn't do any pthread_atforks. And doing so is explicitly forbidden by Google's policies.

To elaborate on the locking order troubles of pthread_atfork. Imagine there are modules foo and bar. Foo occasionally invokes bar while holding some lock. In that case, whatever lock(s) bar has cannot be taken before foo's lock. Violating that order triggers an obvious deadlock. pthread_atfork runs the handlers in the reverse order of registration. So we "simply" need to have bar's atfork handler registered before foo's. But with realistic programs having many thousands of modules, arranging this specific order of registration is basically impossible. That is the reason Google forbids atfork.

In general, it is reasonably well documented that mixing multithreading and fork is unsupported. You're supposed to either use threads (like e.g. mysql) or to use fork without threads (e.g. postgresql). There are some notable exceptions that have to be super-careful. E.g. redis. Both ruby and python do threads and expose fork. At least python does loud warning. Not their worst design mistake, but clearly a mistake.

There are other uses of atfork. For example, if your process mmaps some hardware MMIOs (e.g. for GPU access or NVMe or NIC), you really cannot let hardware conflate parent and child accesses. So something has to be done. Way back nvidia drivers actually had a bit of a problem there. I had bug reports against gperftools of processes stuck somewhere inside the system() call. Note that, in this case, there was no intention to actually use nvidia stuff from both processes. Turns out whatever nvidia drivers did invoked malloc, and it was before we carefully ensured early registration of gperftool's atfork handler. So our atfork handler ended up running before theirs, and everything deadlocked when their handler tried to malloc or free something. Their current drivers only reset some flag in their atfork handler, and avoid any trouble. It may cost them, presumably, in the slight overhead of having to check that flag in ~all their calls that touch shared state. So pthread_atfork cannot be entirely avoided, but at least sometimes it can be done in a way that is guaranteed safe.

So the issue with fork in posix_spawn (or, e.g., system()) is that your multi-threaded process is exposed to the fundamentally messy aspects of fork completely unintentionally.

Cost vs benefit ?

Posted Jun 8, 2026 10:19 UTC (Mon) by smcv (subscriber, #53363) [Link] (4 responses)

I think the real problem case here is library code. Each program can have one consistent policy, either "I'm single-threaded" or "I'm careful to only invoke async-signal-safe functions after fork()", and live with that limitation; but library code (including the Nvidia drivers and malloc implementations mentioned here, but also application-layer libraries like GLib) can't know which of those policies the host application is going to have, so it has to make the pessimistic assumption that it's going to be loaded by a multi-threaded application and therefore can't do anything non-trivial after fork().

That's a very strict constraint, and rules out using most library functions (anything not explicitly documented as async-signal-safe needs to be assumed to be unsafe), so few libraries actually achieve it.

Cost vs benefit ?

Posted Jun 8, 2026 19:01 UTC (Mon) by NYKevin (subscriber, #129325) [Link]

This is a special case of a more general rule. For any process-wide state, and any safety criterion pertaining to shared state (such as thread-safety or reentrancy), there are three possibilities:

1. The state is immutable, so the safety criterion is inapplicable. This also includes cases where the state is technically mutable, but library code must not actually mutate it (e.g. setenv(3)).
2. The state is mutable, but the safety criterion is enforced transparently (e.g. malloc(3) does its own locking so you don't have to).
3. With respect to this state, the safety criterion is violated in practice in all large applications. This does not mean that they actually have UB. Instead, it means that you have to code around the lack of this safety criterion, and cannot somehow enforce it or cause library code to respect it. There is no "bring your own locking" solution or the like.

vfork is a lot like a signal handler, so the relevant safety criterion is (mostly the same as) async-signal-safety. Hardly anything is inherently async-signal-safe, nor do we go around making large swaths of global state immutable just in case somebody wants to interact with them from a signal handler, so you can't do much in a vfork.

Cost vs benefit ?

Posted Jun 9, 2026 17:18 UTC (Tue) by alkbyby (subscriber, #61687) [Link] (2 responses)

Yes, libraries in the POSIX world have some extra limitations due to global state (e.g., in the most general case, cannot use signals unless explicitly documented). But I don't think spawning child processes is such a limitation. As long as some library does clean posix_spawn, it'll work fine. (Or even if it does fork+exec, then there's still a decent chance for it to work, because bugs around pthread_atfork are uncommon)

There are real complications with waiting for child completion, which can make subprocesses potentially unusable from the library context. I am not sure about an exhaustive list, but setting SIGCHLD to SIG_IGN will "eat" process termination status. Or the main process could get confused by either SIGCHLD or wait() reporting and consuming "unexpected" child completion. I might be wrong, but I don't think Linux has extensions to avoid those issues. Still, on my system, I recently started seeing those glycin-rs sub-processes for doing image decoding in a sandbox. But this is likely implemented by gtk "stack," which already wraps subprocesses' business in the glib main loop thingy, so it can handle those wait{,id,pid} issues in a centralized fashion.

So perhaps if spawning children needs to be extended on Linux, it should be around consuming child terminations, not spawning.

Things are generally getting better with time. Way back in the day, I think around 2002 or so, it used to be the case that simply linking -lpthread without even using any threads, caused my program to run 2x slower. Because glibc's malloc didn't have per-thread caching and had to do locking around malloc/free calls. And trivial locks were much more expensive back then. Effects like that were one of the reasons why gperftools had weak symbol dependency on pthread functions (we need at least pthread_key_create for per-thread caches cleanup). If pthread is linked in, we use it for correctness, otherwise we are not the reason libpthread needed to be loaded.

Since then, pthread has been completely integrated into libc.so, and the "penalty" of using threads is nearly gone. So, I think, it is more or less accepted for libraries to spawn threads at will. Or use, e.g., OpenMP, which uses threads under the hood. I am aware of one potential exception. In gcc's implementation of std::shared_ptr, there is code that checks the __libc_single_threaded flag every time the shared pointer's refcount is updated. Inlining this massive bloat everywhere. E.g.: https://godbolt.org/z/vbWKW9G4d. There might be some single-threaded programs where non-atomic refcounting is important for performance, so then simply having a library that had some thread(s) spawned under the hood will slow those down.

But IMHO, threading is common enough that it is more or less settled that threads are usable in libraries. And with time, we'll get more and more POSIX "stuff" usable from libraries, I think.

Having "meta" platforms like Go or JDK is another way to make everything work nicely in practice. In some way, wine implementing Win32 stuff is such a "meta" platform too, but Win32 is _full_ legacy and broken crap.

Cost vs benefit ?

Posted Jun 9, 2026 17:22 UTC (Tue) by bluca (subscriber, #118303) [Link] (1 responses)

> So perhaps if spawning children needs to be extended on Linux, it should be around consuming child terminations, not spawning.

This should be sorted now by using pidfds to track status and the PIDFD_GET_INFO ioctl, which should always return the exit status in recent kernels, IIRC

Cost vs benefit ?

Posted Jun 9, 2026 21:35 UTC (Tue) by alkbyby (subscriber, #61687) [Link]

>> So perhaps if spawning children needs to be extended on Linux, it should be around consuming child terminations, not spawning.

> This should be sorted now by using pidfds to track status and the PIDFD_GET_INFO ioctl, which should always return the exit status in recent kernels, IIRC

This is nice. I wasn't aware. Actually there is much simpler alternative I forgot about. By using 0 for exit signal field of clone, child's terminations get effectively hidden from ~most uses of wait/waitpid/etc. Doesn't need pidfd and doesn't need modern Linux kernel.

libc is not a cooperative commons

Posted Jun 13, 2026 6:39 UTC (Sat) by anton (subscriber, #25547) [Link]

it's somehow imperative for them to make direct system calls, therefore defeating attempts to form a cooperative commons that doesn't involve a CPU privilege transition. Why?
libc does not provide a cooperative commons. From the beginning the C people have designed their interfaces to the system calls to deal with the shortcomings of C, in particular by introducing errno.

But ok, you might still consider this a commons of a kind. But then the C people use macros liberally, in particular for implementing errno, thus only C programs can access these features without contortions. So no, the libc interface to the system calls is no cooperative commons. There is C, in a privileged position, the language that libc implementations are designed for, and for which compatibility is guaranteed. And there are the other languages, in a position that have to accomodate every whim of a libc maintainer if they choose to use libc.

In Linux system calls provide a cleaner, more stable, and, for other languages, easier to use interface, so why should anyone who implements a language other than C use the libc interface?

Fork() in the road paper

Posted Jun 5, 2026 18:54 UTC (Fri) by joib (subscriber, #8541) [Link] (4 responses)

A famous (?) paper describing problems with the fork+exec approach to process creation: https://www.microsoft.com/en-us/research/wp-content/uploa...

It suggests an alternative approach of creating an "empty" process and then various syscalls to be extended with variants taking a pid argument (or, if implemented today on Linux, presumably pidfd) that could be used for setting up the new process before launching it.

Fork() in the road paper

Posted Jun 5, 2026 21:33 UTC (Fri) by gutschke (subscriber, #27910) [Link] (3 responses)

The last time this conversation came up about two years ago, it was suggested that we can already do all of this with existing system calls. By creative combination of system calls, we can spawn an "empty process" that then configures itself as needed.

I was skeptical but curious, and https://github.com/gutschke/safeexec/blob/main/safeexec.c is the result of my investigation.

It's a bit ugly as the system calls weren't really designed with this goal in mind, but it's good enough to start experimenting. At the very least, we could validate the basic concept of starting from an empty process. It's much easier to experiment with different alternatives to traditional fork()/exec(), if we can do so from userspace instead of having to propose kernel additions.

Once there is consensus on what we would like to do and what real-life applications actually require, we can identify specific issues that need to be added to the kernel.

As is, we just run in circles. Every few years, there is a new proposal. It gains some traction. And then it peters out as it isn't really a full replacement for what applications already have, or it runs into some major roadblock with other kernel subsystems.

I would love if somebody identified two or three major applications that suffer from measurable issues using today's fork()/exec() or using the various spawn() implementations, and then hacked in my code to see if the benchmark numbers that they care about change. Incidentally, if I correctly understand the new proposal for templates, I believe that could also be simulated with my POC userspace implementation.

N.B.: I don't claim that my hack'ish code should ever be used for production. But I believe we need a lot more real-life examples of applications testing different process launching strategies to see if new API proposals make a difference or would largely go unused.

Fork() in the road paper

Posted Jun 5, 2026 22:57 UTC (Fri) by malmedal (subscriber, #56172) [Link] (2 responses)

If you want hacky. Why not just CLONE_VM without CLONE_VFORK?

It's somewhat annoying because you have to allocate a stack in the parent process and you can't reuse that area until you are sure the child has called exec but not insurmountable.

Fork() in the road paper

Posted Jun 5, 2026 23:11 UTC (Fri) by gutschke (subscriber, #27910) [Link] (1 responses)

It's been ages since I last experimented with CLONE_VM, so I probably don't remember all the details. But I think it had really awkward calling conventions that made it pretty much impossible to use from C code.

If my cover ever was productized in some form, adding yet another assembly wrapper is obviously doable. But I intentionally tried to keep things as high level and portable as it's possible with this sort of low level code.

And yes, it's ugly, and very far from portable without at least some effort. That's the nature of these APIs

Fork() in the road paper

Posted Jun 5, 2026 23:35 UTC (Fri) by malmedal (subscriber, #56172) [Link]

I was curious as to what it would look like, so I coded it up. It's not too bad, unless I'm missing something.

The stack for each process can be reused as soon at it has called exec. Not sure how to best detect that. Options include things like FD_CLOEXEC or when /proc/<pid>/exe of the child has changed.

#include <fcntl.h>
#include <linux/sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>

int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
/* pid_t *parent_tid, void *tls, pid_t *child_tid */);

int target(void *arg) {
char fname[256];
int i = *(int *)arg;
snprintf(fname, sizeof(fname), "file%d.txt", i);
int fd = open(fname, O_CREAT | O_RDWR, 0777);
dup2(fd, 1);
dup2(fd, 2);
execl("/usr/bin/bash", "bash", "-c", "sleep 100 ; date", NULL);
return -1;
}

double dtime() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (double)tv.tv_sec + tv.tv_usec / 1000000.0L;
}

int main(int argc, char **argv, char **envp) {
const int STACK_SIZE = 65536;
const int STACKS = 200;
int pids[STACKS];
int args[STACKS];
char *stack = malloc(STACK_SIZE * STACKS);
const long SIZE = 20 * 1024L * 1024 * 1024;
char *buffer = malloc(SIZE);
memset(buffer, 1, SIZE);
double start = dtime();
for (int i = 0; i < STACKS; i++) {
args[i] = i + 1;
pids[i] = clone(target, stack + STACK_SIZE * (i % STACKS + 1), CLONE_VM,
&args[i]);
}
fprintf(stderr, "Avg %d %f\n", 0, (dtime() - start) / 200.0);
for (int i = 0; i < STACKS; i++) {
int status;
int ret = waitpid(pids[i], &status, 0);
if (status != 0 || ret < 0) {
printf("status %d %d %d %d\n", i, pids[i], status, ret);
}
}
printf("done\n");
}

fork() + exec()

Posted Jun 5, 2026 18:55 UTC (Fri) by clugstj (subscriber, #4020) [Link] (4 responses)

If you are repeatedly creating large processes, you are already doing it wrong. The fix is in user space, not the kernel.

fork() + exec()

Posted Jun 5, 2026 21:54 UTC (Fri) by roc (subscriber, #30627) [Link] (3 responses)

I came here to say this!! If, as a userspace developer, you really care about performance there are many tools available to you today to mitigate the cost of fork+exec, some of which reach much higher levels of performance than creating new process ever can.

Obviously the most efficient thing you can do is not use another process. Get the functionality into a library and call it in your current process.

If you do need a separate process for some reason (e.g., resource management or sandboxing), create a persistent subprocess and reuse it many times.

If for some reason you need lots of separate subprocesses, use the zygote pattern: fork+exec one zygote process, then every time you need a new subprocess, fork the zygote.

There are edge cases where these don't apply, but adding complex new kernel interfaces that give tiny wins on edge cases does not seem like a good idea.

fork() + exec()

Posted Jun 5, 2026 22:07 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link] (1 responses)

A lot of software might be _avoiding_ the subprocesses exactly because they are so horrible. In my case, we actually used a "process runner" server in one project to avoid forking in a large Java app. We needed it to do text extraction from Microsoft documents and for image parsing/resizing.

fork() + exec()

Posted Jun 9, 2026 0:09 UTC (Tue) by csamuel (✭ supporter ✭, #2624) [Link]

There's another example from the HPC community - the batch system Slurm has a slurmctld which handles the queue of active and pending jobs and can get very large when you've a big queue (10s of GBs of RAM). It also used to fork/exec to do things like run certain scripts (when configured) at various times of the job lifecycle and (which we would see more commonly) send emails when a job started/completed/failed (depending on which options the user selected at job submission time). When you can be starting and ending many jobs per second that became... suboptimal.

So SchedMD (who maintain Slurm) decided to implement a separate daemon (slurmscriptd) which was forked from slurmctld at startup and then it would be sent messages for things to do on its behalf. That relieved massively the overhead of continual fork/exec a huge process for this.

All the best,
Chris

fork() + exec()

Posted Jun 8, 2026 21:23 UTC (Mon) by NYKevin (subscriber, #129325) [Link]

> If for some reason you need lots of separate subprocesses, use the zygote pattern: fork+exec one zygote process, then every time you need a new subprocess, fork the zygote.
>
> There are edge cases where these don't apply, but adding complex new kernel interfaces that give tiny wins on edge cases does not seem like a good idea.

Most of those edge cases are probably covered by some obscure config option in systemd (which is already designed to execute arbitrary programs in arbitrary environments). But nobody wants to faff with systemd config files, hence everyone ends up reinventing the wheel instead.

I think the real win would be getting systemd to provide a nice clean posix_spawn-flavored programmatic interface so that you don't have to deal with unit files. Or if such an interface already exists, evangelizing it and getting people to use it.

But won't someone think of the children?

Posted Jun 5, 2026 23:49 UTC (Fri) by ejr (subscriber, #51652) [Link]

So many puns intended, and this is kinda-sorta horribly bitten tongue in horribly bitten cheek.

For how many of us was our first OS class a lesson in "denial of service" by not checking fork()'s return value? Back in the day of shared resources, that was a very, very quick and socially enforced lesson. Later on, I was a sysadmin in academia, and we knew to be ready for it.

Maybe we need "bad idea" emulation environments within which students (*cough* agents) must learn? Removing vfork+exec seems fantastic. But how do we train the N+4th generation on *why* that choice is fantastic? No programming language itself helps or else we'd all be using Pascal / Modula-{2,3,...} / Ada.

Now that I'm a bit outside education, I really worry. We already had the problem of people knowing only Matlab, and then(?) only Python... There was one student who did not understand that a textual representation of an integer was different than the different encodings. I admit that I still have great difficulty in explaining the difference without courses in architecture, programming, and applications. If you *start* with the difference, sure, it's just there. If you've gone five+ years of college-level education without ever experiencing that difference, well, I've failed to put myself in their place sufficiently to explain it well.

Sorry. Rambling.

Otherwise, I'd assume that init would remain responsible for reaping zombie children. Perhaps we also could modify the terminology?

The entire fork + exec idiom is terrible and needs to be retired

Posted Jun 8, 2026 10:12 UTC (Mon) by rweikusat2 (subscriber, #117920) [Link] (1 responses)

Both fork and exec have a ton of use cases of their own, that is, without doing the other operation. That's just something which is bound to never occur to people who want a single system call to "spawn a new process" (wonder where they got the idea from ... not really) and thus simply reject the UNIX model as "complication we neither want nor understand."

The entire fork + exec idiom is terrible and needs to be retired

Posted Jun 9, 2026 23:34 UTC (Tue) by NYKevin (subscriber, #129325) [Link]

> Both fork and exec have a ton of use cases of their own, that is, without doing the other operation. That's just something which is bound to never occur to people who want a single system call to "spawn a new process" (wonder where they got the idea from ... not really) and thus simply reject the UNIX model as "complication we neither want nor understand."

That's not the point. Nobody is seriously proposing outright removal of fork or exec. You will still be able to use them in isolation for those use cases.

This is a discussion because a lot of use cases *do* look a lot like fork+exec, or fork+(some minor setup)+exec. That turns out to be wasteful because it COWs the entire address space, only to fully overwrite it a few (userspace) instructions later. So we need a separate operation, one that doesn't COW. That leaves us with a few options:

* vfork exists, is standards compliant, but is a serious footgun for userspace programmers. The standard is also pretty lax about how vfork is required to behave, so you can't rely on the pausing behavior in portable code.
* clone with CLONE_VM | CLONE_VFORK is nonstandard but slightly less dangerous than vfork (according to https://lwn.net/Articles/1076803/). Note that vfork(2) says it's equivalent to clone with appropriate flags; I'm not enough of an expert to figure out who's right here.
* posix_spawn exists, is standards compliant, but is currently implemented in userspace on Linux (in terms of clone).

Besides the risk of UB if done incorrectly, the other problem with vfork-like semantics is that we pause the parent thread until the child is finished starting up. This is wasteful compared to a hypothetical solution where the kernel immediately creates a brand-new address space. Of course, that raises the question of how the resulting process is meant to execute when its address space starts out pristine. To my mind, the "obvious" solution is that libc would pass the path to some helper binary, and the kernel would cause that helper binary to load into the new address space (perhaps with a libc-provided argv). The helper would then perform the remaining setup operations before exec'ing the "real" target binary (with the "real" argv if necessary). This avoids the problem of making the kernel grow functionality that is better done in userspace.


Copyright © 2026, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds