NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: kern/49017: vfork does not suspend all threads



The following reply was made to PR kern/49017; it has been noted by GNATS.

From: Robert Elz <kre%munnari.OZ.AU@localhost>
To: gnats-bugs%NetBSD.org@localhost
Cc: 
Subject: Re: kern/49017: vfork does not suspend all threads
Date: Fri, 07 Apr 2017 10:07:46 +0700

     Date:        Thu,  6 Apr 2017 15:05:01 +0000 (UTC)
     From:        Nico Williams <Nico.Williams%twosigma.com@localhost>
     Message-ID:  <20170406150501.356437A2B8%mollari.NetBSD.org@localhost>
 
   |  I'll take it much further: it is fork() that is EVIL, and vfork()
   |  that is GOOD.
   |  
   |  Here's my rationale for such an extraordinary statement:
   |  https://gist.github.com/nicowilliams/a8a07b0fc75df05f684c23c18d7db234
 
 All that shows is that fork() is (or can be) expensive, which is hardly
 news, nothing at all about evilness, in fact, the closest I can see that
 it comes are these sentences ...
 
     Since back then programs and processes were small that inelegance was
     easy to overlook. But now processes tend to be huge, and that makes
     copying even just a parent's resident set, and page table fiddling for
     the rest, extremely expensive.
 
 That's kind of like saying that Ferrari's are evil, because they cost
 too much if all you do is drive them grocery shopping once a week...
 
 Being expensive (even too expensive to routinely use) is not evil, just
 not the right (or perhaps even, just not the best) choice in many cases.
 
 If anything is "evil" from your text (IMO) it would be "But now processes
 tend to be huge" - that is the problem, not fork().
 
 But fork() permits
 
 	if (fork() > 0)
 		_exit(0);
 
 which most of the other methods (I know nothing of clone(), so that one
 possibly excepted) do not - expensive perhaps (in some cases) but useful
 nevertheless.
 
   |  Briefly: fork()'s copying and/or COW are terrible and would never have been
   |  necessary had Dennis Ritchie et. al. thought of vfork()'s semantics.
 
 While I suspect that fork() is really Ken's, not Dennis's (irrelevant here)
 I kind of doubt that.  First because neither of them is/was in any way
 deficient in their thinking (simply ignoring a possibility like that is
 not something I would expect) and second, because fork(), expensive or not,
 is simply far more general than vfork().
 
   |  Besides, fork() has a ton of safety issues (which I mostly
   |  did not address in that gist, 
 
 Nor anywhere else I have seen - I'm sure it is possible to write code
 badly enough that fork() would cause problems, (and it is certainly
 possible to make a mess using buffered I/O) but almost all of that is
 trivially overcome.
 
   |  Now, vfork() is... clumsy because of the stack sharing silliness, but it
   |  predates threads, so its authors probably did not realize that taking a
   |  callback function and argument to run in a new stack would have been a
   |  superior design
 
 When vfork() was designed, the total (guaranteed) address space was just
 64KB (text, data, stack, all combined).   Duplicating stacks (adding an
 extra stack - and if you want to be able to return in the child, it actually
 means copying the existing stack, while adjusting any self-referencing pointers
 that occur there)  would have been laughed away as absurd.
 
 In another message Nico.Williams%twosigma.com@localhost (kind of) quotes me:
    |  Robert Elz <kre%munnari.OZ.AU@localhost> wrote:
    |  > [ description of vfork_into_fork() elided ]
 
 and then says...
 
    | That's a neat idea, but I don't think it's needed.  I can't think of why I
    | would ever need it or any time that I could have used it.
 
 Maybe you never would have, but I know of one immediate use - that is /bin/sh
 
 Our sh uses vfork() whenever it can (for the obvious reason) but sometimes,
 while evaluating the code to be executed in the sub-shell, it discovers that
 it simply cannot do that after a vfork() and really needs a whole new process.
 
 What happens now is that the child sets a magic "do me again using fork()"
 flag (in the parent's address space, which it shares of course) and then
 exits.  The parent observes the flag, fork()'s, and the child starts all over
 again.   If the child could have simply converted its vfork() state into a
 fork() state that wacky dance would not be needed.
 
 Now, of course, the shell could avoid this by examining the tree of commands
 to be executed before the fork()/vfork() (it does that for the very common
 cases that will certainly require fork() rather than vfork()) but that would
 mean duplicating the whole process, initially just to discover which kind of
 fork() is required, and then again to actually do the work - for every
 sub-shell invocation (more or less every command executed that isn't a
 function) and all this for a relatively rare circumstance.
 
    | What I really want is
    |        pid_t avfork(int (*)(void *), void *);
    | which is like vfork() but allocates a new stack, calls the given callback
    | in it just like pthread_create() would, and does not stop any threads in
    | the parent, not even the one that called it.
 
 I have no objection to that, go ahead, write the code for it, and submit
 it, it sounds useful enough to consider at least.
 
 But...
 
   | Note that avfork() would have much the same constraints for the child as
   | vfork() does, except, naturally, that the avfork() child could return while
   | the vfork() child cannot.
 
 Return to what?   You're having it execute a callback, are you saying that
 that function can return?   Return to where exactly?   And what does that
 mean?   What would be the difference between
 
 	child = avfork(func, &sp);
 and
 	if ((child = avfork(&sp)) == 0) func();
 ??
 
 If there's none, why the need for the callback?  If avfork() cannot
 actually return in the child, so the second is not possible, then neither
 can func() right?
 
 kre
 


Home | Main Index | Thread Index | Old Index