Why "a fork is often followed by an exec"? Can't you just create a new process in UNIX?
A slightly different perspective from most of the "fork-shaming" existing answers... ;)
Originally it was probably, as @davidbak mentioned, just so temptingly easy to do it this way. But having worked with
exec a lot (and also often with only
fork, for multiprocessing) there definitely are reasons why this way of working is still alive and kicking, and not delegated to the fog of history:
- It still is extremely simple from the view of a programmer in literally any programming language at all. It does not matter where I am coding - any language can trust the extremely simple implications of the
forksemantics and offer it as part of the language. Hence, every language has a relatively trivial (compared to in-process multithreading) method of offering at least multi-processing to its users. (N.B.: one exception to this is if you are using multithreading in your program - after a fork, only one thread is running; this can lead to obvious issues in all but the most trivial multithreading applications.)
- As a user (programmer), I can write my multiprocessing in a few lines of code, with no worry about mutexes, semaphores, illegally overwriting state of any of my program variables, and so on and so forth. At the same time, the "initial communication" between parent child is also trivially handled for me - the child does have full access to any variables or RAM the parent had, and can continue working with it. In practice this means, if I, say, have a need to perform some short I/O or networking process in parallel to my main program, I can do that with a few lines of code; all is in one place, easily visible. I can collect the child afterwards and be on my merry way. There are no "worker threads", I do not need to take care to use only thread-safe methods or data structures.
- Again, since the memory content starts identical but is in effect separate, there is zero risk of overwriting anything between parent/child processes. Yes, I do have to find other ways to to IPC then between parent and child, but those methods are not that hard either; often, languages offer a standard function like "open3" or something like that which automatically provides bi-directional pipe-based file handles for communication to avoid deadlocks and such.
- Specifically, when switching between programming languages, once one has understood the
forksemantics, one never needs to learn anything more about the new environment - it's always as simple as in any other language.
- It is nice to have
execanyways. It allows us to replace the current process image (i.e. the executable that's being executed) with something different. This makes it clean to, say, have some script or program that prepares some kind of environment and then execs something else, while disappearing from the scene, itself. Not only does it free resources (RAM, but also space in the process table, and so on), but makes it very clear to anybody involved or looking at it that the erstwhile parent is not going to play any role whatsoever anymore in the future. You often find this in well-written
bashscripts, which free up the resources of the
bashinterpreter when starting their "payload".
- It perfectly fits the Unix philosophy of having many small tools which can interact with each other, instead of fat black boxes that are either very limited, or need a big set of parameters or an API to really use.
- As shown above, it is powerful in some scenarios where only having a single function would be limiting; but it is also still easy enough to have
execfollowing each other. It's not like you have to do a lot of stuff inbetween (or any at all, really) unless you need it.
- As per the man pages, in some modern Unixes (namely, Linux), fork itself is only a wrapper around the more modern and more powerful clone call, which indeed is somewhat like a
fork+exec. Note that here we see complexity raise its ugly head already; Linux also has a clone3 function which supersedes
cloneand makes the interface a little easier or more convenient (using
structsinstead of so many flags).
fork() creates a new process, which is a copy of the parent process. So if you did only
fork(), you would have two identical processes running. Therefore in order to replace the forked process with another code, you need to perform
exec() which replaces the currently running process with the specified executable file.
Linux kernel is just organized that way. You don't have a single system call that creates new process and loads a new executable at the same time. You have to do it in two steps - first create new process, then load a new executable into this new process. (Although you may have a library function in your programming language that combines these two - for example there is
spawn() in many C variants).
exec() is not needed, if just creating another copy of the current process is all that you want. Many daemons for example do this.
This is because of historical reasons: At the beginning of time there was only
exec. Because it was easy to implement (according to DMR: only 27 lines of PDP-7 assembly code for
fork! - see e.g. A
fork() in the road (Baumann, Appavoo, Krieger, Roscoe, 2019) - a secondary source, though it references a primary source The Evolution of the Unix time-sharing system (Ritchie, 1979). Anyway, true ab initio direct process creation came much much much much later. (And is not in POSIX, possibly?)
The fact that a true direct process creation API came much much later affects Unix programming to this day. Because hundreds of books, manuals, tutorials, slide decks, and courses were written explaining
exec and they've been taught to students and programmers for decades as the way to do process creation/control in Unix and that extensive heritage persists in the way code is written to this day.
Oh, here is The Evolution of the Unix Time-sharing System (Ritchie, 1979). Scroll down to page 6 to see: "Process control in its modern form was designed and implemented within a couple of days. ..... In fact, the PDP-7's fork call required precisely 27 lines of assembly code."
Because an exec doesn't create a process, and linux doesn't have a single syscall for create process and load executable because that only works in the trivial case of creating a process with new executable with no preexisting resources. If you want to do anything more than the trivial case, the complexity rapidly ramps up, and it becomes easier to have separate 'create process' and 'start executable' steps with the ability to manipulate the process in between. See https://lwn.net/Articles/360556/ for a discussion on this.
Unix, going back to the earliest versions has solved this by using fork to create a duplicate of the parent process dedicated to setting up the environment, which then loads in the new executable, once it has completed. The child process then exists in a temporary state of having access to all the resources of the parent process, but of running in the child process. This approach has a couple of advantages:
- You can use the existing in-process manipulation APIs to set up the child process. This means you don't then need a whole family of API calls for manipulating the child process to set up resources.
- You can use exec on its own if the parent process will no longer need to exist after creating the new one.
- You can fork without using exec if you want a second process of the same executable.
No, you cannot create a new process in UNIX, you can only duplicate your current process (using
fork). If you want the new process to do something other than exactly what the current process is doing, you then replace it (using
You do not have to
fork before calling
exec. There's a common usage in scripts that start up a login session (
.xinitrc and the like) where you set up environment variables and start background tasks (such as
ssh-agent) and then run the session manager. There's nothing more for the startup script to do after launching the session manager so you
exec it to free up the resources assigned to running your startup script. The parent of the startup script is unaware of this replacement - the PID remains the same - so they continue to wait for this child to die before they perform their tidy-up actions.