6 ms·
"The received wisdom suggests that Unix’s unusual combination of fork() and exec() for process creation was an inspired design. In this paper, we argue that for
by garethrowlands 4y ago
"The received wisdom suggests that Unix’s unusual combination of fork() and exec() for process creation was an
inspired design. In this paper, we argue that fork was a clever
hack for machines and programs of the 1970s that has long
outlived its usefulness and is now a liability. We catalog the
ways in which fork is a terrible abstraction for the modern programmer to use, describe how it compromises OS
implementations, and propose alternatives."
from A Fork in the Road, <https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf https://www.microsoft.com/en-us/research/uploads/prod/2019/0...>
- wongarsu 4y agofork() and exec() is a reflection of the unix philosophy: small tools for separate tasks, and as a result you have great composability. You can set up right etc for your new process by calling any number of system calls between fork() and exec(). Windows is a great example of the alternative (following "Windows philosophy"): There are about three different API calls for creating a new process, each with a heap of complicated optional arguments. The API becomes more complex, less composable, less extensible and less powerful. But also easier to reason about and easier for the kernel to provide, and arguably with fewer footguns.
- klodolph 4y agofork() is not a small tool. It is a MASSIVE, unwieldy tool that is difficult to use correctly. It happens to have a small interface.
- adastra22 4y ago...is it? In it is original implementation, fork() was pretty trivial. All it did was create a new process entry in the kernel table, with all the pages and capabilities and such copied from the original process. Then mark all pages as copy-on-write, and return to the caller. Maybe not trivial, but much less complicated than loading an executable file from disk. My understanding of Linux internals is maybe 20 years out of date, so I am legitimately curious what makes fork() so complicated these days.
- cryptonector 4y agofork() is not trivial now. Processes are huge now -- they have huge heaps among other things. Copying all that is expensive. In the 80s we tried COW, but that turns out to be very slow as well. What operating systems do now is immediately copy the resident set, then do COW for the rest of writable memory, but in large, multi-threaded processes, this is still too slow. Use vfork() or posix_spawn().
- adastra22 4y agoHrm. Googling "fork linux copy-on-write" seems to find a lot of stack overflow answers from 2014-2015 claiming Linux marks pages as copy-on-write when fork() is called. I didn't see anything more recent in the first page of results. I could see it being worthwhile to immediately copy a few pages, like the top of the stack, but copying the whole resident set seems excessive. Especially since some of that data might not even be written to.
- CHY872 4y agoSo the problem is what happens to the old and new processes after the fork. To CoW, you need to mark all the pages read only in _both_ old and new which means that every memory write in the caller will now pagefault since the OS now has to lazily copy on both sides. So with true copy on write the fixed costs may be low but the marginal cost per memory write may be high in both parent and child. In this case you can see why the resident set is copied, yes? It’s the smallest amount of memory that guarantees predictable performance subsequent to the call returning.
- cryptonector 4y agoIf the parent is threaded and the host has more than one CPU, then fork() == TLB shootdowns, which are slow. As well there's the cost of all those page faults that the two processes are likely to take to do the copying. And lastly there's all sorts of complexity involving multiple parent threads calling fork(), or the child calling fork() again (or vfork()) before calling exec. It's just much easier to copy the resident set and mark the address space as being CoW, because now you only have to worry about page faults for pages that are not in core anyways and so were going to fault anyways, and that means you don't have to worry about TLB shootdowns either (if a page is not in core, it's not referenced by any TLB either). You still have the multi-fork issues, but now you can use an atomic reference count on the address space.
- deleted 4y ago[deleted]
- _0w8t 4y agoWindows approach is not the only alternative. Simply provide API to create a process in a suspended state, then adjust its properties based on pid/handle and then start the process execution.
- ncmncm 4y agoA huge number of tricky thread problems go away if the child thread is blocked at startup, and allowed to run only after the parent allows it. To retrofit this, it is easiest to lock a mutex before spawning and have the child block on that. Then the parent unlocks it to let the child run.
- lanstin 4y agoI think shared libraries can spawn threads on their load/init phase that you don't know about. Then you are hosed but you only know about it due to sporadic weird problems, that if you restart on failure, e.g. a pre-forked worker scenario, you might never even really care about.
- Blikkentrekker 4y agoI always felt that the way to create new processes and new threads should initially start the same. A new process is simply a thread that after creation does a syscall to isolate itself from the other thread and receive a unique copy of all resources. This could also solve the issue with forking from multithreaded programs since we can ensure we own all shared resources when we isolate our thread, to effectively thus become a new process. So instead of fork we have clone/isolate. A new thread can also of course immediately suspend itself, allow other threads to work on it's data in some way, who then give it a signal to resume itself and then execute if need be.
- phendrenad2 4y agoHas Linux gained syscalls equivalent to those Windows API calls yet? Or is linux too different from the windows kernel to make that happen? (In that case, what does WINE do?)
- lanstin 4y agoOn linux, fork libc function(s) is(are) a wrapper to clone system call which is more flexible (they added some stuff to make Wine work better): https://lwn.net/Articles/826313/ https://lwn.net/Articles/826313/ Clone is also used to start threads, IIRC: https://stackoverflow.com/questions/4856255/the-difference-between-fork-vfork-exec-and-clone https://stackoverflow.com/questions/4856255/the-difference-b...
- sitkack 4y agoFork is not a reflection of unix philosophy! It is a beautiful hack tho.
- cryptonector 4y agoDiscussed in: https://hn.algolia.com/?q=https%3A%2F%2Fwww.microsoft.com%2Fen-us%2Fresearch%2Fuploads%2Fprod%2F2019%2F04%2Ffork-hotos19.pdf https://hn.algolia.com/?q=https%3A%2F%2Fwww.microsoft.com%2F... and also here: https://news.ycombinator.com/item?id=30502392 https://news.ycombinator.com/item?id=30502392
- indrora 4y agoTo present some contrast: Windows doesn't have fork(). It has a real, fully mature thread and process model. In Windows NT, every process consists of a handle that is a "Process", which in turn points to a structure containing a list of "Threads". A process is done when its main thread exits or all threads exit, whichever is defined by the main process. Fork/Exec is replaced with CreateProcess (or ShellExecute, your choice). For a very zen-like example of the fork/exec and pipe management that you'd do on a POSIX system done in Windows, the [MSDN Docs](https://docs.microsoft.com/en-us/windows/win32/procthread/creating-processes https://docs.microsoft.com/en-us/windows/win32/procthread/cr...) are quite informative.
- btilly 4y agoWhen I see what most people try to do with "a real, fully mature thread and process model", I wind up shaking my head about bad engineering processes.
- jiggawatts 4y agoBad... how? From my perspective watching the various techniques used by a multitude of operating systems over many decades, the Windows-style process+thread model seems to be winning out over the UNIX fork() model. For example, PostgreSQL seems to suffer greatly from the "forked process per connection" model, necessitating front-ends that do connection pooling. Database engine after database engine seems to go through this phase and then "upgrade" to either a single-process thread pool model, or start using async IO in some way. (Web servers also.) For reference, Microsoft SQL Server back in the 1990s could on Windows NT 4 could handle more connections than PostgreSQL in 2020...
- skissane 4y ago> Windows doesn't have fork() It does (or maybe did, not sure if it still works) have the literal equivalent of fork() - NtCreateProcess() with NULL SectionHandle argument creates a new process which is a clone of the caller. However, it never really worked. The Win32 API did not support forking, and so any process which forked itself and then tried to invoke any Win32 API calls would soon crash or fail mysteriously. In theory forking did work for pure NT API applications, but those are rather limited in their abilities.
- deleted 4y ago[deleted]