8

In the man pages I've been reading, it seems popen, system, etc. tend to call fork(). In turn, fork() copies the process's entire memory state. This seems really heavy, especially when in many situations a child from a call to fork() uses little if any of the memory allocated for the parent.

So, my question is, can I get fork() like behavior without duplicating the whole memory state of the parent process? Or is there something I am missing, such that fork() is not as heavy as it appears (like, maybe calls tend to be optimized to avoid unnecessary memory duplication)?

Kyle
  • 878
  • 6
  • 14
  • You know it already, don't you? There is copy-on-write. – Jo So Oct 20 '15 at 21:18
  • You should look what is COW (copy-on-write) http://stackoverflow.com/questions/13813636/how-does-copy-on-write-in-fork-handle-multiple-fork – ouah Oct 20 '15 at 21:18
  • Possible duplicate of [Is it possible to fork a process without inherit virtual memory space of parent process?](http://stackoverflow.com/questions/31604324/is-it-possible-to-fork-a-process-without-inherit-virtual-memory-space-of-parent) – skrrgwasme Oct 20 '15 at 21:18

3 Answers3

6

fork(2) is, as all syscalls, a primitive operation (but some C libraries use clone(2) for it), from the point of view of user-space application. It is mostly a single machine instruction SYSCALL or SYSENTER to switch from user-mode to kernel-mode, then the (recent version of) Linux kernel is doing quite significant processing.

It is in practice quite efficient (e.g. less than a millisecond, and sometimes even less than a tenth of it) because the kernel is extensively using lazy copy-on-write techniques to share pages between parent & child processes. The actual copying would happen later, on page faults, when overwriting a shared page.

And forking has a huge advantage, since the starting of some other program is delegated to execve(2): it is conceptually simple: the only difference between the parent & child processes is the result of fork

BTW on POSIX systems such as Linux, fork(2) or the suitable clone(2) equivalent is the only way to create a process (there are some few weird exceptions that you should generally ignore: the kernel is making some processes like /sbin/init etc...), since vfork(2) is obsolete.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • That is perfect, thanks! I guess it does work more efficiently than I thought. – Kyle Oct 20 '15 at 21:39
  • Note this is true for e.g. current *Linux* as well as a lot of other modern kernels, but talking about *the kernel* is a bit misleading. There was a reason back then when `vfork()` was introduced (and even mentioned in POSIX), although it was a mess, of course... –  Oct 20 '15 at 21:48
  • 1
    @FelixPalmen - Bite your tongue! `vfork()` is nasty. ;) See here: http://ewontfix.com/7/ – Andrew Henle Oct 20 '15 at 22:06
  • @AndrewHenle I didn't say otherwise. Just saying *"the kernel"* isn't an exact descriptions and there are (or better: were) systems where `fork()` overhead *was* a concern. –  Oct 20 '15 at 22:07
  • @AndrewHenle still interesting link! Didn't know about `posix_spawn()` until today :) –  Oct 20 '15 at 22:15
  • 2
    On my system, fork takes 56 microseconds for child and 37 microseconds for parent – Craig Estey Oct 20 '15 at 22:17
  • @FelixPalmen OT, but interestingly enough, `vfork()` seems to be an issue in this current gdb-on-Solaris UL question: http://unix.stackexchange.com/questions/237489/gdb-hangs-forever-on-solaris – Andrew Henle Oct 20 '15 at 22:19
  • Fork is not conceptually simple. It's a super weird tlway to start a new process. What happens to all the RAII resources? Do I end up freeing everything on the(duplicate) heap multiple times, and worse, deleting temporary files twice, etc? There have been many articles written about why `fork` is a terrible interface. – Timmmm Nov 11 '21 at 22:16
  • Here's a good article on it: https://www.microsoft.com/en-us/research/publication/a-fork-in-the-road/ – Timmmm Nov 11 '21 at 22:18
2

The problem is that to run the main function of a standardly linked executable, you need to call execve, and exec replaces the whole process image and so you need a new address space, which is what fork is for.

You can get around this by having your calee expose its main functionality in a shared library (but then it must not be called main), and then you can load the function with the main functionality without having to fork (provided there are no symbol conflicts).

That would be a more efficient alternative to system (basically with the efficiency of a function call). Now popen involves pipes and to use pipes you need to have the pipe ends in different schedulable units. Threads, which use the same address space, can be used here as a lighter alternative to separate processes.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • 1
    As a side note, I think it would be pretty sweet to have a system that allows connecting C/C++ modules as in pipelines but without the forking overhead. Essentially, you need to ensure no symbol conflicts, and a filedescriptor abstraction so that purely in-process pipelines don't need to contact the kernel. I've actually been working on putting something like that together. – Petr Skocik Oct 20 '15 at 21:45
2

As you alluded to fork() is a bit of a mad syscall that has kind of stuck around for historical reasons. There's a great article about its flaws here, and also this post goes into some details and potential workarounds.

Although on Linux fork() is optimised to use copy-on-write for the memory, it's still not "free" because:

  1. It still has to do some memory-related admin (new page tables, etc.)
  2. If you're using RAII (e.g. in C++ or possibly Rust) then all the objects that are copied will be cleaned up twice. That might even lead to logic errors (e.g. deleting temporary files twice).
  3. It's likely that the parent process will keep running, probably modifying lots of its memory, and then it will have to be copied.

The alternatives appear to be:

  • vfork()
  • clone()
  • posix_spawn()

vfork() was created for the common use case of doing fork() and then execve() to run a program. execve() replaces all of the memory of the current process with a new set, so there's no point copying the parent process's memory if your just about to obliterate it.

So vfork() doesn't do that. Instead it runs in the same memory space as the parent process and pauses it until it gets to execve(). The Linux man page for vfork() says that doing just about anything except vfork() then execve() is undefined behaviour.

posix_spawn() is basically a nice wrapper around vfork() and then execve().

clone() is similar to fork() but allows you to exactly specify what is copied (file descriptors, memory, etc.). It has a load of options, including one (CLONE_VM) which lets the child process run in the same address space as the parent, which is pretty wild! I guess that is the lightest weight way to make a new process because it doesn't involve any copying of memory at all!

But in practice I think in most situations you should either:

  • Use threads, or
  • Use posix_spawn().

(Note, I am just researching this now; I'm not an expert so I might have got some things wrong.)

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • You somewhat need to consider the source that is advocating the paper for removal. Microsoft. Certainly not free from any particular interests in the matter. – David C. Rankin Nov 12 '21 at 18:18
  • True, however I have read the paper and it appears to be a fair technical evaluation. They are far from the only people saying that `fork()` is flawed. – Timmmm Nov 13 '21 at 20:40
  • Yes, agreed, but what struck me as a flaw in the current argument is the authors were unable to point to a replacement that itself was without flaws. The argument was basically 1) "copy parent environment for initialization - bad", 2) "some other alternative - good but we can't point to any alternative that has consensus and that initialization of the child doesn't have other drawbacks." So I was left with the impression that something could improve on `fork()` and avoid some of the lock-ins it forces, but there is no one-shoe-fits-all replacement that is currently there. – David C. Rankin Nov 14 '21 at 01:03
  • I agree actually. In an ideal world you'd be able to start a new process by specifying a function and it's arguments. The new process would use the same binary as the current one (so you don't have to mess around with paths and unreliable `argv[0]`), not copy the parent's heap/stack (so it is fast with no logic errors) and only copy the memory of its arguments (so you don't have to pass everything by command line arguments). That last bit would be tricky for various reasons though. I doubt anyone is working on anything like that though because `fork()` kind of works and people blindly love it. – Timmmm Nov 14 '21 at 09:55