Correct me if I am wrong.
Will do :)
As fork()
is a POSIX system call, its behavior is well defined:
A process shall be created with a single thread. If a multi-threaded process calls fork(), the new process shall contain a replica of the calling thread and its entire address space, possibly including the states of mutexes and other resources. Consequently, to avoid errors, the child process may only execute async-signal-safe operations until such time as one of the exec functions is called.
https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html
A forked child is an exact duplicate of its parent, yet only the thread that called fork()
in the parent, still exists in the child and is the new main thread of that child until you call exec()
.
The POSIX description "shall be created with a single thread" is misleading as in fact most implementation will really create an exact duplicate of the parent process, so all other threads and their memory are duplicated as well, which means the threads are in fact there, they just cannot run anymore as the system never assigns
any CPU time to them; they are in fact missing in the kernel thread scheduler table.
An easier mental image is the following:
When the parent calls fork, the entire process is frozen for a moment, atomically duplicated, and then the parent is unfrozen as a whole, yet in the child only the one thread that called fork is unfrozen, everything else stays frozen.
That's why it isn't safe to perform certain system calls in between fork()
and exec()
as also pointed out by the POSIX standard. Ideally you shouldn't do much more than maybe closing or duplicating file descriptors, setting or restoring signal handlers and then calling exec()
.