The creation and management of user-space processes in Linux have many principles in common with UNIX but also include several unique optimizations specific to Linux. Here, review the life cycle of Linux processes and explore the kernel internals for user process creation, memory management, scheduling, and death.
I’ve never understood why unix/linux process creation is in fact a fork, ie a copy of the original process.
It strikes me as really inefficient, especially process duplication is rarely intended. If the parent and child processes are very different in terms of text (code) and data then this fork mechanism means that the parent and child processes contain code and data that is irrelevant. Sure they can have different execution paths but its still inefficient to allocate and copy this stuff.
Exec() sounds different but in fact replaces the original process.
Am I missing something? And if not why is it this way?
Is thereba way to spawn a child process without copying the parent’s image?
It used to be inefficient, but now it’s not. The fork() just creates a copy-on-write copy of the original process.
If you think about it, it really is two operations: creating a new process, and executing a new program. They don’t often go together. There are many times when it is useful to fork and not exec. And occasionally you want to exec without forking. By having the operations separate, you can do interesting things.
(Apologies for this slightly tangential post.)
Yes. Forking makes a lot of sense, given its copy on write nature. I believe that the “thread mania” of the 2000’s is finally abating, in part because multicore is actually bringing the issue to the fore, to the extent that regular programmers (the foot-soldiers) are faced with actually having to deal with their complexity.
Mapped onto what might be a more familiar metaphor, threads, with their “everything shared by default” approach are like a firewall policy in which you allow everything by default and then block the stuff you think might be evil. You’ve got to think about, list, and address *everything* that might go wrong throughout the entire memory space. With processes, you typically do message passing. Meaning that nothing is shared by default, and what is shared is passed through a clear interface. (The “actor model” is the view I take of process based concurrency.) In that way, it is more like a firewall policy in which you block everything by default, and then allow the things which are necessary.
Another way to look at threads is as a step back in time, to when we didn’t have memory protection. As a programmer of threads, you simply don’t have any of that. It’s up to you to cover all your bases and make sure that one thread doesn’t step on another.
Processes give you back memory protection and make very clear what is being shared. Of course, nothing in computer science is free, and everything is a compromise of some sort. Depending upon what you are doing, the message passing (and create/destroy overhead) of processes and the actor model may have significant performance consequences. And this is where an interesting solution steps in: fibers.
A fiber is even lighter than a thread. It’s basically like a thread. All memory is shared by default. But fibers within a process are guaranteed not to be concurrent. A fiber within a process must explicitly yield before another fiber in the same process can run. Fibers within an individual process practice cooperative multitasking. They cannot be preempted by another fiber, and have complete control over when other code that shares their memory space can run. This avoids (most) all of the ugly and complex syncrohization that you have to deal with in threads. Interestingly, it appears to be another step back in time… to the days of cooperative multitasking.
But the combination of processes and the actor model, and optionally, fibers within them, gives the programmer a way to reap all the benefits of memory protection, take advantage of multicore, and avoid and potential, significant, penalties of using processes, and also sidestep most of the pain of using threads. The two strategies, combined, end up looking more like 2 steps back, 10 steps forward.
What does this have to do with the article? Well, in a world of process-based concurrency combined with fibers, the very fast and efficient implementation of fork() in unix in general, and Linux in particular, should give us a leg up on OSes which do not have such an efficient implementation of fork(). So my tangential post has arrived home, in the end. Sort of… 😉
Edited 2008-12-25 21:21 UTC
In a world of multicore heterogeneous platforms (say, CELL, big iron) you usually need to share everything between the different kind of processors within a single program, not protect. “Multicore fibers” are called threads
My experience is not in that area. But I see from your profile that you apparently do. I would be interested if you would care to elaborate upon that.