35 points · 58 comments · 16 years ago · billpg
news.ycombinator.com/item?id=863871Back in my youth when I was first taught the fork call, I got how how it worked, two copies of the process return from the function call and they both continue in parallel. The only thing that was bothering me was the long list of caveats my text book discussed.
It told me that a copy of the process was made, except for file handles, and that the memory wasn't really copied until one of the two processes tried to modify something. It seemed all terribly complicated but I figured there was a good reason for it that I didn't yet understand, grasshopper.
Time passed and I started working in embedded systems and later coding for Windows. I never used fork beyond those juvenilia programs I made. These OSs started new processes by passing an executable filename to the OS and telling it to start a new program. That new process started with a clean slate, no memory, empty stack, no open file handles except stdin/out/err. Simple.
Now, I've just been reminded of the fork call in Unix, and I'm prompted to ask; Why was it ever there? Who wants the ability to do a fork when simpler ways of starting a new process exist.
Nearly all the uses of fork I've seen are usually followed by an exec call. So the OS goes to all that trouble setting up a duplicate process only for all that hard work to be eliminated by running exec.
Even when concurrency within a program is needed, the thread model seems far more useful with a lot less complications.
So please, I need to know, why fork?
jacquesm
jbert
It's also still a good model when you want to run N copies of the same code concurrently, since the processes are isolated from each other, making it easier to reason about correctness. There are some wrinkles (primarily due to inherited global state such as filehandles), but they're reasonably well understood (by Unix+C programmers).
If you need significant shared state between concurrent paths of execution in the same code, then threads are probably easier.
vinutheraj
"In Plan 9, fork is not a system call, but a special version of the true system call, rfork (resource fork) which has an argument consisting of a bit vector that defines how the various resources belonging to the parent will be transferred to the child. Rather than having processes and threads as two distinct things in the system, then, Plan 9 provides a general process-creation primitive that permits the creation of processes of all weights." - Rob Pike.
You can read more about it here - http://groups.google.com/group/comp.os.research/browse_threa...
jgrahamc
It's true that Windows tends not to use this paradigm, but it is common in the Unix world specifically because of the simple ability to share file handles between a parent and child process. And also the parent and child processes share most everything else (for example, they have the same environment settings).
DougWebb
- The initial process reads in configuration files, sets up an environment, and opens a listening socket for the service. It then forks several times to create service processes. From this point, the initial process' only job is start new service processes when/if they exit or when load increases, and to shutdown the whole service when told to.
- The service processes run in a loop waiting for requests to come in on the socket, which they all share. The service can handle as many concurrent requests as you've got service processes, and they all operate independently. Thanks to copy-on-write, they all access the same configuration information stored in the initial process' memory. When a request comes in, the service process accepts it (which creates a new socket) and does some initial sanity checking to make sure it's a valid request, and then forks to create a handler process to actually process the request. It then goes back to listening for requests.
- The handler process is the workhorse. It gets the connection socket from its parent, and it's still got access to all of the config info. It's an independent process, so it's free to do whatever it needs to, without risk of impacting the continued operation of the service. Once it's done handling the request it can simply exit, freeing up whatever resources it consumed while handling the request.
In this pattern, the initial process and service processes have very simple jobs and very little code, which makes them easier to make bug-free and robust. Having lots of independent processes instead of threads adds robustness, because a crashing process can't take down the other processes in the service (unless it takes the whole machine down, of course.) This is rarely a problem in the initial or service processes, but the handler processes are exposed to the world and are much more likely to encounter unanticipated input, so they're the hardest to make robust. With the pattern, they don't need to be as robust, because they're allowed to exit unexpectedly without harming the service.
barrkel
Processes normally inherit lots of context from their parent: the user identity, the window station (Win32-speak), security capabilities, I/O handles / console, environment variables, current directory, etc. The most logical way to inherit everything is to make a logical copy, which is very cheap owing to memory architecture.
Because of this things that would normally need two APIs, one synchronous and one asynchronous, can be programmed easily. If you need the synchronous version, call it directly; otherwise, fork and call it, and wait on the pid (at a later point) if necessary in the parent.
And I rather vehemently disagree with you saying that the threading model has less complications than the process model. I believe there's almost universal agreement that the problem with threading is mutable shared state, and the process model avoids it.
rtm
pid = fork();
if(pid == 0){
close(1);
dup(pipefds[1]);
exec(...);
}Erwin
Another useful application of the implicit descriptor sharing is http://en.wikipedia.org/wiki/Privilege_separation
yan
That's just it, creating a process that's an exact copy is the path of least resistance. Due to the way the VM system works in most modern hardware, it's much cheaper to create an exact copy of a virtual address space (you're just copying TLB entries) than it is to create a brand new one.
gcv
No real need to monitor (except to try to catch the bug that caused the crash in the first place), and no need to manually restart anything. It all just keeps going.
Basically, a server process using fork has a lot of resilience built in. In contrast, a crash in a threaded process will kill all the threads at once, and all users feel the pain.
vii
Actually, fork(2) has now evolved into clone(2) on Linux, so you can choose in quite a fine grained way what the threads/processes will share.
The separation of the functionality of spawn between fork and exec is surprisingly handy (even though people occasionally still come up with vfork(2)).
kniwor
$ cmd_a | cmd_b | cmd_c
The simplest way for the shell to accomplish this request is to fork itself multiple times. Doing so without fork would be difficult. I figure since multitasking and pipes are old as eternity in the linux world, fork must have been an early necessity and this use case might have something to do with their prominence but then again I am just guessing.tybris
i.e. the things Unix systems are good at, but Windows systems are not.
clord
But seriously: fork(2) is natural and mathematical. There is no IO involved. That is to say, when you call it, you don't have to activate some spinning mechanical thing and wait several million or billion cycles while it clatters and bumbles along, filling the higher caches with code and data.
fork is blazing fast; effectively an O(1) operation. It's just about as light-weight as process creation can get.
fork is useful. It allows one to manage complicated families of processes, complete with pre-fork and post-fork activity. Threads can't match it here. The only thing I can think of that surpasses the multiprocessing capability of a forking process is modern async IO. And then you have to implement all the management stuff by hand.
With all due respect, someone who claims to have embedded experience shouldn't have to ask hacker news about the benefits of fork, unless your embedded experience is all on Windows Mobile and its ilk, where CreateProcess rules the day.
DarkShikari
paulmcl
hapless
Fork model:
Step 1: Write a program to accept a single connection to a single TCP socket, then handle the request.
Step 2: Judiciously place a fork() call at the time of the new connection coming into the socket.
Step 3: Add an "if" statement to wait for another request if you happen to be the parent process after the fork.
You're done!
You just wrote a program capable of handling thousands of concurrent requests, with none of the concurrency nightmares that keep sensible men up at night. Going from the simplest case to the finished version was a two-line code change.
klodolph
First I create the pipe, then call fork. In the child, I chdir to /var/fred, open /var/log/greg, run fdup2 on the pipe and on the handle to /var/log/greg, setuid to user1, and then finally call exec.
Show me an API that can do that without fork.
All the popen / spawn / system functions are not system calls but rather library functions which operate by calling fork.
toddh
Simple embedded system don't have processes or threads. The are just loops. More complex embedded systems are real-time oriented and will use threads as the locus of control because the whole memory space is shared amongst the threads. No need for processes at all.
coliveira
Windows philosophy, on the other hand, is to have monolithic programs that solve everything by themselves. They infrequently need to start new processes, so fork is not viewed as important.
ErrantX
In the end I gave it up as a lost job; whilst the general idea of fork() is appealing we found much "better" ways for fine grained process control.
bengtan
Someone with a better memory may correct me.
I'm not sure if this holds true for Windows though.
bediger
fork() allows the ability to do anything before exec(), setting up lighter-weight process creation, and whatever flexibility the programmer desires.
I'd turn it around: why do the designer's of spawn() or CreateProcess() think they've got the foresight to cover all of the bases for programmers? Why don't those systems do fork()/stuff/exec() to simplify?
axod
Maybe you should be using higher level calls, but back when I was writing linux assembly programs fork was awesome.
There's not really that many caveats to using it at all.
I actually think it is one of the most elegant system calls in unix.
Think of all the alternative clunky ways that OS's before unix had to use to start a process at a given depth into the process. Lots of flags to make sure that you started off where you left in the 'parent', to recreate all or most of the state required for the child process. Fork passes all that state 'for free'. And copy-on-write makes it fast.
It's a bit like biology. Split the cell, then let them both specialize a bit towards what they have to become. The moment of splitting is almost 100% symmetrical, the only difference being who is the 'parent' and who is the 'child' process.
Other ways of starting new processes feel clunky in comparision, you have to specify a binary to run, you have to know all kinds of details about parameters to pass and so on.
Fork essentially abstracts the creation of a sub-process to the absolute minimum.
Fork is atomic, it's got 0 parameters and it returns only one integer (or it fails for a simple reason, no more process slots).