Subject: Re: Real vfork() (was: third results)
To: None <tech-kern@NetBSD.ORG>
From: Greg A. Woods <woods@most.weird.com>
List: tech-kern
Date: 04/09/1998 02:22:35
[ On Tue, April 7, 1998 at 09:54:42 (-0000), jiho@postal.c-zone.net wrote: ]
> Subject: Re: Real vfork() (was: third results)
>
> To start, as long as a call and its design behavior are defined and documented,
> I fail to see the "bad programming practice" if people exploit the behavior in
> ways perhaps not envisioned for the design.
The unix process model normally ensures that processes cannot muck about
in each other's address space (shared memory wasn't commonly implemented
in unix at the time vfork() was invented). vfork() allows the child to
muck about in the parent's storage, and even to mess with file pointers
and such. Even with the most intimate parent/child relationship, where
it's effectively the "same" program doing these things to itself, I'd
say that at best it's a poor excuse, and at worst it is indeed "bad
programming practice".
Further, using (i.e. depending on) these "abnormal" semantics was
defined as a bug in the same manual page that described them (though I
don't know exactly when that paragraph was added). My interpretation of
the manual page has always been that the "abnormal" behaviour of vfork()
was described in detail only to prevent surprises, not to condone their
usage. vfork() is/was only a necessary evil to pacify programmers
trying to squeeze more performance out of a system with limited hardware
resources.
BTW, the original C Shell was the most infamous abuser of the vfork()
semantics, which made it one of the most cursed at of all programs when
programmers tried to port BSD tools into a non-BSD environment. Of
course csh wasn't the only difficult to port program that "abused"
vfork(). This fact alone has caused many people to claim vfork() as
patently evil since it introduced a major portability gap between the
major variants of Unix at the time.
Remember too that programmers trying to take advantage of vfork() when
it was first introduced found that they couldn't just "s/fork/vfork/"
and assume everything would run. Now as we re-implement it again we
find the same problems can creep back into programs that may have once
worked fine with a "real" vfork() and we're forced to maintain a
compatability entry for old binaries using a vfork() that wasn't "real".
FYI, there was even a recent version of GNU emacs that broke on SunOS-4
because of the "real" vfork() but didn't break with NetBSD-1.2's "fake"
vfork().
So, we've seen clearly that unexpected things can happen in both
directions with the vfork() semantics. This isn't just a sacred cow,
but rather something that has caused real problems. In my book
something that can easily lead to unexpected behaviour is clearly "bad
practice" and should be avoided, even at the expense of a wee bit of
performance. Of course I'm also the kind of guy who buys only ECC
memory and I'd never dream of compiling something with the -DNDEBUG (I
*want* the assert()s turned on *all* the time!).
> It's the party who arbitrarily
> decides to alter the defined behavior who is pursuing "bad programming practice"
> there, unless of course there's a "good reason" -- and none was articulated
> in this case. (Or none that I've seen.)
I'm not sure exactly what you mean here. Are you saying there was no
reason given for the half-baked vfork() that was in 4.4BSD?
> > My personal opinion is that vfork() is still an abomination and no
> > modern OS should support it -- copy-on-write is close enough that we
> > should not need to rely on stupid tricks for minor performance gain,
> > esp. when this teeter-totter dependence on semantics happens. The
> > 4.4BSD release was correct to obliterate it.
>
> But they didn't offer anything better.
I beg to differ. They did offer something nearly as good in terms of
performance, and infinitely better in terms of elegance.
(i.e. copy-on-write to preserve performance, and a "unified" fork()
interface to relieve the application programmer from the burden of
having to figure out when the performance gain could be made use of.)
> If you mean that the original vfork() was a "stupid trick", I think I'd say
> that do an exec, fork() would be even more so.
I'm not sure I understand what your getting at here....
> I also think there's more than just a minor performance gain at issue here.
> The copy-on-write behavior of the 4.4BSD vfork() causes all kinds of havoc in
> the vm system. That havoc doesn't go away with an execve(), and duplicate
> pages pile up. I haven't thoroughly evaluated just how big a problem yet, but
> there's no question a shared vmspace vfork() would/should alleviate a problem
> for the vm (whichever vm you use).
I wouldn't call it "all kinds of havoc". Because the parent can keep on
running in a normal fork(), various VM data structures do indeed have to
be copied.
Remember that the original purpose of vfork() was to avoid having to
copy whole processes, sometimes on the swap device too. The cost of
copying a few page table entries and such is minor compared to copying
an entire processes' address space, and extremely minor compared to
doing swap I/O.
> Elsewhere in the same book, the same authors are complaining about the
> copy-on-write mechanisms in the Mach vm, and suggesting THAT behavior might be
> eliminated from future releases of the OS!
I think that was more specific on the VM implementation, and not on the
general idea of copy-on-write, though my own bias (obtained long ago
from Bach's "The Design of the UNIX Operating System") may be showing
here.
Bach says: "A dangerous situation could arise if a programmer uses
vfork() incorrectly, so the onus for calling vfork() lies with the
programmer. The difference between the System V approach and the BSD
approach is philosophical: Should the kernel hide idiosyncrasies of its
implementation from users, or should it allow sophisticated users the
opportunity to take advantage of the implementation to do a logical
function more efficiently?"
I think it's even simpler than that though -- copy-on-write gives every
program that calls fork() an advantage, even if it never calls exec(),
which in actual measurements I did long ago on System V showed that
overall system throughput was indeed better than it was with just
vfork() in those programs that could make use of it. I realize that the
combination of the two which we now have in NetBSD is even better still
in terms of overall system performance, but I don't think it's worth the
"risk" of having programmers either accidentally fall asunder of
vfork() or worse to abuse it on purpose.
--
Greg A. Woods
+1 416 443-1734 VE3TCP <gwoods@acm.org> <robohack!woods>
Planix, Inc. <woods@planix.com>; Secrets of the Weird <woods@weird.com>