Technical Linux questions

John Chambers jc at trillian.mit.edu
Sat Feb 22 10:59:13 EST 2003


Nathan Meyers wrote:
| On Sat, Feb 22, 2003 at 04:49:42AM +0000, John Chambers wrote:
| > Which reminds me of  a  question  I've  never  really  seen
| > answered:   How  exactly  do kernel threads differ from the
| > result of a vfork() call?
|
| In intent, if nothing else. Threads are intended to allow for multiple
| threads of execution in the same process space; vfork is intended to
| spawn a child whose only purpose is to immediately exec() a different
| executable. One description I found somewhere - I'm not sure how accurate
| - is that the parent blocks until the new child exec()s a new executable.

On the projects where I used vfork(), I never saw this.  It
would have been a showstopper, of course, and reported as a
fatal bug.  I do recall warnings in  man  pages  about  the
dangers  of  the  child  process changing memory before the
exec; this implies (but doesn't prove) that the  parent  is
running. But it's moot now that vfork is widely implemented
as fork, so it can't be used if you want portability.

| There's a lot of context management needed to make threads work
| properly. Given its limited functionality, vfork() doesn't have to
| provide such management - that's more than a documentation difference :-).

While this is true for a lot of programs, it isn't true for
all.   And  in  any  case, system calls are rarely actually
needed for much of this. The work can be done in user space
(though  some  sync  primitives will require some judicious
use of machine language to get atomicity).

One example I saw but didn't implement myself: I worked for
a  while (at Prime, R.I.P.) on a Sys/V unix system that had
hundreds of processors.  There was a lot of  software  that
had been modified to take advantage of this.  This included
a version of make that would run subprocesses  in  parallel
when they didn't have common dependencies.  This was really
slick.  On a box with about 200 385  processors,  a  system
build took about 2 minutes.

I talked to some of the implementers on one  occasion,  and
found  out  that  modifying  make  this way wasn't all that
difficult. What they said they did was to take advantage of
vfork  to  get multi-tasking withing a single program.  The
top-level make built the tree of  dependencies,  which  was
then a nearly-static global structure. It then called vfork
to  fire  up  2nd-level  makes  on   subtrees   that   were
independent.   The  only local storage these needed was the
command buffer and a few pointers, which  could  easily  be
allocated  on  the  stack.  Each make could read the entire
dependency tree, but only modified its own subtree. A store
of  an  int  or a pointer was atomic, so no synchronization
was necessary.  The 2nd-level makes could also start  their
own  3rd-level  makes, and so on.  Each make had to wait on
its  own  children,  of  course,  which  again  made   sync
functions  other  than  wait()  unnecessary.  But note that
blocking the parent of a vfork() shoots down this approach,
since  the  parent needs to create multiple children before
waiting on all of them.

Granted,  some  multi-threaded  programs  need  interaction
between  threads,  which  implies  tools  to keep them from
shooting each other  in  the  foot.   But  there  are  also
applications  where none of this is needed.  Some tasks can
be divided cleanly into subtasks, in which  case  a  single
global  memory  area is the simple, elegant solution.  Such
tasks can be handled simply and efficiently by the original
vfork(), if it just does the minimal work of creating a new
process with its own stack and the parent's data area,  and
doesn't try to play any other tricky games.




More information about the Discuss mailing list