Source-Changes-D archive

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

vfork() and posix_spawn() [was: Re: CVS commit: src/lib/libc/sys]



    Date:        Sat, 12 Jun 2021 23:13:54 +0200
    From:        Joerg Sonnenberger <joerg%bec.de@localhost>
    Message-ID:  <YMUjkhwh91De57eC%bec.de@localhost>

Sorry, missed this message when I was cherry picking messages to read in
a timely fashion.

  | On Wed, Jun 09, 2021 at 01:03:20AM +0700, Robert Elz wrote:
  | > after a vfork() the child can do anything

  | This is not true. After vfork(), it is only supposed to call async
  | signal safe functions.

What you said, and what I said are not contradictory.   Note I said "can
do" and you said "is only supposed to".

What is supposed to work and what actually does work (or can be made to
work) are two different things.

I would note though our man page doesn't make that requirement, or not
that I can see anyway, and vfork() is not in POSIX of course - and while
I don't have a copy to check, I'd suspect not in the C standards either),
so while that sounds like a reasonable requirement to impose for safer
operation, I'm not sure that anyone or anything actually does that.

  | That said, a flag for the double fork semantic might be useful.

If the "that said" relates to fork() or vfork() not being async signal safe,
so a double fork() (when the first is vfork anyway) would not be condoned,
that doesn't matter, there to be an async-signal-safe _Fork() call added to
the next version of POSIX, so even with the "only async signal safe"
requirement for vfork() a:

	if ((child = vfork()) == 0) {
		if (_Fork())	_exit(0);
		/* do child stuff here */
	} else {
		if (child != -1) waitpid(child, &status, 0);
		/* parent continues */
	}

sequence is possible (will be possible, we don't have _Fork() yet)
and fully conforming, apart from vfork() not being in the standard.
The child of the vfork() only does _Fork() and _exit(), both of which
are permitted.  The grandchild process is created by a full fork() so
can do anything at all.

For what it is worth, the definition of _Fork() is (to be):

	The _Fork() function shall be equivalent to fork(), except
	that fork handlers established by means of the pthread_atfork()
	function shall not be called and _Fork() shall be async-signal-safe.

Aside from the prototype line, which is just as you'd expect, that's it.

If (or when) we add it, we probably need to decide whether we need _VFork()
(or some similar name) as well.   Probably.

However, an extra posix_spawn attribute to handle double fork is almost
certainly not warranted, this kind of thing isn't all that common, and
adding the mechanism to the posix_spawn set of functions would really
just be bloat (there's also a proposal floating around to add the
ability to change a terminal's process group as a posix_spawn action --
that's required for shells to be able to use posix_spawn in general cases,
but that most likely won't go anywhere).

Remember what the standard says about posix_spawn() (in its rationale, this
is not strictly part of the standard)...

	The posix_spawn( ) function and its close relation posix_spawnp( )
	have been introduced to overcome the following perceived difficulties
	with fork( ): the fork( ) function is difficult or impossible to
	implement without swapping or dynamic address translation.
[...]
	This view of the role of posix_spawn( ) and posix_spawnp( ) influenced
	the design of their API. It does not attempt to provide the full
	functionality of fork( )/exec in which arbitrary user-specified
	operations of any sort are permitted between the creation of the
	child process and the execution of the new process image; any attempt
	to reach that level would need to provide a programming language as
	parameters.
[...]
	[ It lists some of the known problems with the posix_spawn interface ]
[...]
	The posix_spawn( ) and posix_spawnp( ) functions do not have all the
	power of fork( )/exec. This is to be expected. The fork( ) function
	is a wonderfully powerful operation. We do not expect to duplicate
	its functionality in a simple, fast function with no special hardware
	requirements. It is worth noting that posix_spawn( ) and
	posix_spawnp( ) are very similar to the process creation operations
	on many operating systems that are not UNIX systems.

It then goes on to list the requirements they had for posix_spawn()...

	The requirements for posix_spawn( ) and posix_spawnp( ) are:
	    � They must be implementable without an MMU or unusual hardware.
	    � They must be compatible with existing POSIX standards.

	Additional goals are:
	    � They should be efficiently implementable.
	    � They should be able to replace at least 50% of typical
	      executions of fork( ).
	    � A system with posix_spawn( ) and posix_spawnp( ) and without
	      fork( ) should be useful, at least for realtime applications.
	    � A system with fork( ) and the exec family should be able
	      to implement posix_spawn( ) and posix_spawnp( ) as library
	      routines.

Note that their target is just to replace half fork() calls (and just
executions, not appearances in the source - most fork() calls executed
come from a relatively small set of sources) and they explicitly state
that implementing posix_spawn() using fork()+exec() is acceptable - it
isn't necessarily intended to be cheaper to use.   What it is required to
be is implementable on low end, no virtual address space, systems.  That
was its motivation, not obsoleting vfork().

Please don't see posix_spawn() as being (or ever becoming) a panacea that
can replace all fork() (or even just vfork()) calls - even just in the
cases where an exec() follows soon after.   It works fine for most simple
cases, but there are lots of not-so-simple situations that it cannot handle,
and burdening the interface with the ability to deal with all of those
would reduce the "efficiently implementable" goal for little real benefit.

Another example is made obvious by the bug Martin committed a fix for
in the past few days ... the order of operations was incorrect in our
implementation.  But that this problem can exist shows that there
are ordering issues - a process that wants to open files using its
current privs, then reset those before (perhaps) opening more files,
and then doing the exec() part cannot do that with posix_spawn().
Again, this is rare enough that adding the complexity required to allow
it to work just isn't worth it.   [In this area also note that the
POSIX_SPAWN_RESETIDS flag causes (effectively) setuid(getuid()), there's
no similar operation to do setuid(geteuid()) ... again too rare to bother.]

If someone wanted something to propose that might be more useful, then
the ability for posix_spawn() to end with (effectively) an execve()
rather than execv() (ie: a call to pass in an environment vector to
pass to the new process, rather than having it inherit the parent's)
seems like a more useful facility to me.   That's something that is
currently listed as a known issue (more costly to achieve using posix_spawn()
than it is with fork()/exec()).   Of course, to propose something like
this (with any real hope of success) it needs to be implemented first,
ideally in several different systems.

kre




Home | Main Index | Thread Index | Old Index