[SV 44555] Use vfork() instead of fork() where available.

Testing has shown that vfork() is actually significantly
more efficient on systems where it's supported, even for
copy-on-write implementations.  If make is big enough,
duplicating the page tables is significant overhead.

* configure.ac: Check for fork/vfork.
* makeint.h: Include vfork.h and set up #define for it.
* os.h, posixos.c (get_bad_stdin): For children who can't use
the normal stdin file descriptor, get a broken one.
* job.c (start_job_command): Avoid so many ifdefs and simplify
the invocation of child_execute_job()
(child_execute_job): move the fork operation here so it can
return early for the parent process.  Switch to use vfork().
* function.c (func_shell_base): Use new child_execute_job() and
simplify ifdefs.
* job.h, main.c, remote-cstms.c, vmsjobs.c, w32os.c: Update
declarations and calls.
This commit is contained in:
Paul Smith 2016-03-13 01:12:07 -05:00
parent 14b2d7effb
commit fd1dd7c398
11 changed files with 258 additions and 265 deletions

View file

@ -144,6 +144,8 @@ AC_CHECK_FUNCS([strdup strndup mkstemp mktemp fdopen fileno \
AC_CHECK_DECLS([bsd_signal], [], [], [[#define _GNU_SOURCE 1
#include <signal.h>]])
AC_FUNC_FORK
AC_FUNC_SETVBUF_REVERSED
# Rumor has it that strcasecmp lives in -lresolv on some odd systems.

View file

@ -1751,6 +1751,7 @@ func_shell_base (char *o, char **argv, int trim_newlines)
perror_with_name (error_prefix, "pipe");
return o;
}
#elif defined(WINDOWS32)
windows32_openpipe (pipedes, errfd, &pid, command_argv, envp);
/* Restore the value of just_print_flag. */
@ -1763,7 +1764,7 @@ func_shell_base (char *o, char **argv, int trim_newlines)
perror_with_name (error_prefix, "pipe");
return o;
}
else
#else
if (pipe (pipedes) < 0)
{
@ -1771,118 +1772,113 @@ func_shell_base (char *o, char **argv, int trim_newlines)
return o;
}
# ifdef __EMX__
/* close some handles that are unnecessary for the child process */
/* Close handles that are unnecessary for the child process. */
CLOSE_ON_EXEC(pipedes[1]);
CLOSE_ON_EXEC(pipedes[0]);
/* Never use fork()/exec() here! Use spawn() instead in exec_command() */
pid = child_execute_job (FD_STDIN, pipedes[1], errfd, command_argv, envp);
if (pid < 0)
perror_with_name (error_prefix, "spawn");
# else /* ! __EMX__ */
pid = fork ();
if (pid < 0)
perror_with_name (error_prefix, "fork");
else if (pid == 0)
{
# ifdef SET_STACK_SIZE
/* Reset limits, if necessary. */
if (stack_limit.rlim_cur)
setrlimit (RLIMIT_STACK, &stack_limit);
# endif
child_execute_job (FD_STDIN, pipedes[1], errfd, command_argv, envp);
}
else
# endif
#endif
{
/* We are the parent. */
char *buffer;
unsigned int maxlen, i;
int cc;
/* Record the PID for reap_children. */
shell_function_pid = pid;
{
struct output out;
out.syncout = 1;
out.out = pipedes[1];
out.err = errfd;
pid = child_execute_job (&out, 1, command_argv, envp);
}
if (pid < 0)
{
perror_with_name (error_prefix, "fork");
return o;
}
#endif
{
char *buffer;
unsigned int maxlen, i;
int cc;
/* Record the PID for reap_children. */
shell_function_pid = pid;
#ifndef __MSDOS__
shell_function_completed = 0;
shell_function_completed = 0;
/* Free the storage only the child needed. */
free (command_argv[0]);
free (command_argv);
/* Free the storage only the child needed. */
free (command_argv[0]);
free (command_argv);
/* Close the write side of the pipe. We test for -1, since
pipedes[1] is -1 on MS-Windows, and some versions of MS
libraries barf when 'close' is called with -1. */
if (pipedes[1] >= 0)
close (pipedes[1]);
/* Close the write side of the pipe. We test for -1, since
pipedes[1] is -1 on MS-Windows, and some versions of MS
libraries barf when 'close' is called with -1. */
if (pipedes[1] >= 0)
close (pipedes[1]);
#endif
/* Set up and read from the pipe. */
/* Set up and read from the pipe. */
maxlen = 200;
buffer = xmalloc (maxlen + 1);
maxlen = 200;
buffer = xmalloc (maxlen + 1);
/* Read from the pipe until it gets EOF. */
for (i = 0; ; i += cc)
{
if (i == maxlen)
{
maxlen += 512;
buffer = xrealloc (buffer, maxlen + 1);
}
/* Read from the pipe until it gets EOF. */
for (i = 0; ; i += cc)
{
if (i == maxlen)
{
maxlen += 512;
buffer = xrealloc (buffer, maxlen + 1);
}
EINTRLOOP (cc, read (pipedes[0], &buffer[i], maxlen - i));
if (cc <= 0)
break;
}
buffer[i] = '\0';
EINTRLOOP (cc, read (pipedes[0], &buffer[i], maxlen - i));
if (cc <= 0)
break;
}
buffer[i] = '\0';
/* Close the read side of the pipe. */
/* Close the read side of the pipe. */
#ifdef __MSDOS__
if (fpipe)
{
int st = pclose (fpipe);
shell_completed (st, 0);
}
if (fpipe)
{
int st = pclose (fpipe);
shell_completed (st, 0);
}
#else
(void) close (pipedes[0]);
(void) close (pipedes[0]);
#endif
/* Loop until child_handler or reap_children() sets
shell_function_completed to the status of our child shell. */
while (shell_function_completed == 0)
reap_children (1, 0);
/* Loop until child_handler or reap_children() sets
shell_function_completed to the status of our child shell. */
while (shell_function_completed == 0)
reap_children (1, 0);
if (batch_filename)
{
DB (DB_VERBOSE, (_("Cleaning up temporary batch file %s\n"),
batch_filename));
remove (batch_filename);
free (batch_filename);
}
shell_function_pid = 0;
if (batch_filename)
{
DB (DB_VERBOSE, (_("Cleaning up temporary batch file %s\n"),
batch_filename));
remove (batch_filename);
free (batch_filename);
}
shell_function_pid = 0;
/* shell_completed() will set shell_function_completed to 1 when the
child dies normally, or to -1 if it dies with status 127, which is
most likely an exec fail. */
/* shell_completed() will set shell_function_completed to 1 when the
child dies normally, or to -1 if it dies with status 127, which is
most likely an exec fail. */
if (shell_function_completed == -1)
{
/* This likely means that the execvp failed, so we should just
write the error message in the pipe from the child. */
fputs (buffer, stderr);
fflush (stderr);
}
else
{
/* The child finished normally. Replace all newlines in its output
with spaces, and put that in the variable output buffer. */
fold_newlines (buffer, &i, trim_newlines);
o = variable_buffer_output (o, buffer, i);
}
if (shell_function_completed == -1)
{
/* This likely means that the execvp failed, so we should just
write the error message in the pipe from the child. */
fputs (buffer, stderr);
fflush (stderr);
}
else
{
/* The child finished normally. Replace all newlines in its output
with spaces, and put that in the variable output buffer. */
fold_newlines (buffer, &i, trim_newlines);
o = variable_buffer_output (o, buffer, i);
}
free (buffer);
}
free (buffer);
}
return o;
}

230
job.c
View file

@ -1074,17 +1074,12 @@ unblock_sigs (void)
static void
start_job_command (struct child *child)
{
#if !defined(_AMIGA) && !defined(WINDOWS32)
static int bad_stdin = -1;
#endif
int flags;
char *p;
#ifdef VMS
char *argv;
#else
char **argv;
int outfd = FD_STDOUT;
int errfd = FD_STDERR;
#endif
/* If we have a completely empty commandset, stop now. */
@ -1328,32 +1323,6 @@ start_job_command (struct child *child)
fflush (stdout);
fflush (stderr);
#ifndef VMS
#if !defined(WINDOWS32) && !defined(_AMIGA) && !defined(__MSDOS__)
/* Set up a bad standard input that reads from a broken pipe. */
if (bad_stdin == -1)
{
/* Make a file descriptor that is the read end of a broken pipe.
This will be used for some children's standard inputs. */
int pd[2];
if (pipe (pd) == 0)
{
/* Close the write side. */
(void) close (pd[1]);
/* Save the read side. */
bad_stdin = pd[0];
/* Set the descriptor to close on exec, so it does not litter any
child's descriptor table. When it is dup2'd onto descriptor 0,
that descriptor will not close on exec. */
CLOSE_ON_EXEC (bad_stdin);
}
}
#endif /* !WINDOWS32 && !_AMIGA && !__MSDOS__ */
/* Decide whether to give this child the 'good' standard input
(one that points to the terminal or whatever), or the 'bad' one
that points to the read side of a broken pipe. */
@ -1362,8 +1331,6 @@ start_job_command (struct child *child)
if (child->good_stdin)
good_stdin_used = 1;
#endif /* !VMS */
child->deleted = 0;
#ifndef _AMIGA
@ -1380,7 +1347,7 @@ start_job_command (struct child *child)
{
int is_remote, id, used_stdin;
if (start_remote_job (argv, child->environment,
child->good_stdin ? 0 : bad_stdin,
child->good_stdin ? 0 : get_bad_stdin (),
&is_remote, &id, &used_stdin))
/* Don't give up; remote execution may fail for various reasons. If
so, simply run the job locally. */
@ -1409,7 +1376,7 @@ start_job_command (struct child *child)
child->remote = 0;
#ifdef VMS
if (!child_execute_job (argv, child))
if (!child_execute_job (child, argv))
{
/* Fork failed! */
perror_with_name ("fork", "");
@ -1420,68 +1387,20 @@ start_job_command (struct child *child)
parent_environ = environ;
#ifndef NO_OUTPUT_SYNC
/* Divert child output if output_sync in use. */
if (child->output.syncout)
{
if (child->output.out >= 0)
outfd = child->output.out;
if (child->output.err >= 0)
errfd = child->output.err;
}
#endif
# ifdef __EMX__
/* If we aren't running a recursive command and we have a jobserver
pipe, close it before exec'ing. */
if (!(flags & COMMANDS_RECURSE) && jobserver_enabled ())
jobserver_pre_child ();
jobserver_pre_child (flags & COMMANDS_RECURSE);
/* Never use fork()/exec() here! Use spawn() instead in exec_command() */
child->pid = child_execute_job (child->good_stdin ? FD_STDIN : bad_stdin,
outfd, errfd,
argv, child->environment);
if (child->pid < 0)
{
/* spawn failed! */
unblock_sigs ();
perror_with_name ("spawn", "");
goto error;
}
child->pid = child_execute_job (&child->output, child->good_stdin, argv, child->environment);
/* undo CLOSE_ON_EXEC() after the child process has been started */
if (!(flags & COMMANDS_RECURSE) && jobserver_enabled ())
jobserver_post_child ();
#else /* !__EMX__ */
child->pid = fork ();
environ = parent_environ; /* Restore value child may have clobbered. */
if (child->pid == 0)
{
/* We are the child side. */
unblock_sigs ();
jobserver_post_child (flags & COMMANDS_RECURSE);
/* If we AREN'T running a recursive command and we have a jobserver,
clear it before exec'ing. */
if (!(flags & COMMANDS_RECURSE) && jobserver_enabled ())
jobserver_clear ();
#ifdef SET_STACK_SIZE
/* Reset limits, if necessary. */
if (stack_limit.rlim_cur)
setrlimit (RLIMIT_STACK, &stack_limit);
#endif
child_execute_job (child->good_stdin ? FD_STDIN : bad_stdin,
outfd, errfd, argv, child->environment);
}
else if (child->pid < 0)
if (child->pid < 0)
{
/* Fork failed! */
unblock_sigs ();
perror_with_name ("fork", "");
goto error;
}
# endif /* !__EMX__ */
#endif /* !VMS */
}
@ -1557,6 +1476,8 @@ start_job_command (struct child *child)
{
HANDLE hPID;
char* arg0;
int outfd = FD_STDOUT;
int errfd = FD_STDERR;
/* make UNC paths safe for CreateProcess -- backslash format */
arg0 = argv[0];
@ -2098,82 +2019,95 @@ start_waiting_jobs (void)
/* EMX: Start a child process. This function returns the new pid. */
# if defined __EMX__
int
child_execute_job (int stdin_fd, int stdout_fd, int stderr_fd,
char **argv, char **envp)
child_execute_job (struct output *out, int good_stdin, char **argv, char **envp)
{
int pid;
int save_stdin = -1;
int save_stdout = -1;
int save_stderr = -1;
int fdin = good_stdin ? FD_STDIN : get_bad_stdin ();
int fdout = FD_STDOUT;
int fderr = FD_STDERR;
int save_fdin = -1;
int save_fdout = -1;
int save_fderr = -1;
#ifndef NO_OUTPUT_SYNC
/* Divert child output if output_sync in use. */
if (out && out->syncout)
{
if (out->out >= 0)
fdout = out->out;
if (out->err >= 0)
fderr = out->err;
}
#endif
/* For each FD which needs to be redirected first make a dup of the standard
FD to save and mark it close on exec so our child won't see it. Then
dup2() the standard FD to the redirect FD, and also mark the redirect FD
as close on exec. */
if (stdin_fd != FD_STDIN)
if (fdin != FD_STDIN)
{
save_stdin = dup (FD_STDIN);
if (save_stdin < 0)
save_fdin = dup (FD_STDIN);
if (save_fdin < 0)
O (fatal, NILF, _("no more file handles: could not duplicate stdin\n"));
CLOSE_ON_EXEC (save_stdin);
CLOSE_ON_EXEC (save_fdin);
dup2 (stdin_fd, FD_STDIN);
CLOSE_ON_EXEC (stdin_fd);
dup2 (fdin, FD_STDIN);
CLOSE_ON_EXEC (fdin);
}
if (stdout_fd != FD_STDOUT)
if (fdout != FD_STDOUT)
{
save_stdout = dup (FD_STDOUT);
if (save_stdout < 0)
save_fdout = dup (FD_STDOUT);
if (save_fdout < 0)
O (fatal, NILF,
_("no more file handles: could not duplicate stdout\n"));
CLOSE_ON_EXEC (save_stdout);
CLOSE_ON_EXEC (save_fdout);
dup2 (stdout_fd, FD_STDOUT);
CLOSE_ON_EXEC (stdout_fd);
dup2 (fdout, FD_STDOUT);
CLOSE_ON_EXEC (fdout);
}
if (stderr_fd != FD_STDERR)
if (fderr != FD_STDERR)
{
if (stderr_fd != stdout_fd)
if (fderr != fdout)
{
save_stderr = dup (FD_STDERR);
if (save_stderr < 0)
save_fderr = dup (FD_STDERR);
if (save_fderr < 0)
O (fatal, NILF,
_("no more file handles: could not duplicate stderr\n"));
CLOSE_ON_EXEC (save_stderr);
CLOSE_ON_EXEC (save_fderr);
}
dup2 (stderr_fd, FD_STDERR);
CLOSE_ON_EXEC (stderr_fd);
dup2 (fderr, FD_STDERR);
CLOSE_ON_EXEC (fderr);
}
/* Run the command. */
pid = exec_command (argv, envp);
/* Restore stdout/stdin/stderr of the parent and close temporary FDs. */
if (save_stdin >= 0)
if (save_fdin >= 0)
{
if (dup2 (save_stdin, FD_STDIN) != FD_STDIN)
if (dup2 (save_fdin, FD_STDIN) != FD_STDIN)
O (fatal, NILF, _("Could not restore stdin\n"));
else
close (save_stdin);
close (save_fdin);
}
if (save_stdout >= 0)
if (save_fdout >= 0)
{
if (dup2 (save_stdout, FD_STDOUT) != FD_STDOUT)
if (dup2 (save_fdout, FD_STDOUT) != FD_STDOUT)
O (fatal, NILF, _("Could not restore stdout\n"));
else
close (save_stdout);
close (save_fdout);
}
if (save_stderr >= 0)
if (save_fderr >= 0)
{
if (dup2 (save_stderr, FD_STDERR) != FD_STDERR)
if (dup2 (save_fderr, FD_STDERR) != FD_STDERR)
O (fatal, NILF, _("Could not restore stderr\n"));
else
close (save_stderr);
close (save_fderr);
}
return pid;
@ -2181,32 +2115,50 @@ child_execute_job (int stdin_fd, int stdout_fd, int stderr_fd,
#elif !defined (_AMIGA) && !defined (__MSDOS__) && !defined (VMS)
/* UNIX:
Replace the current process with one executing the command in ARGV.
STDIN_FD/STDOUT_FD/STDERR_FD are used as the process's stdin/stdout/stderr;
ENVP is the environment of the new program. This function does not return. */
void
child_execute_job (int stdin_fd, int stdout_fd, int stderr_fd,
char **argv, char **envp)
/* POSIX:
Create a child process executing the command in ARGV.
ENVP is the environment of the new program. Returns the PID or -1. */
int
child_execute_job (struct output *out, int good_stdin, char **argv, char **envp)
{
int r;
int pid;
int fdin = good_stdin ? FD_STDIN : get_bad_stdin ();
int fdout = FD_STDOUT;
int fderr = FD_STDERR;
/* For any redirected FD, dup2() it to the standard FD then close it. */
if (stdin_fd != FD_STDIN)
#ifndef NO_OUTPUT_SYNC
/* Divert child output if output_sync in use. */
if (out && out->syncout)
{
EINTRLOOP (r, dup2 (stdin_fd, FD_STDIN));
close (stdin_fd);
if (out->out >= 0)
fdout = out->out;
if (out->err >= 0)
fderr = out->err;
}
#endif
if (stdout_fd != FD_STDOUT)
EINTRLOOP (r, dup2 (stdout_fd, FD_STDOUT));
if (stderr_fd != FD_STDERR)
EINTRLOOP (r, dup2 (stderr_fd, FD_STDERR));
pid = vfork();
if (pid != 0)
return pid;
if (stdout_fd != FD_STDOUT)
close (stdout_fd);
if (stderr_fd != FD_STDERR && stderr_fd != stdout_fd)
close (stderr_fd);
/* We are the child. */
unblock_sigs ();
#ifdef SET_STACK_SIZE
/* Reset limits, if necessary. */
if (stack_limit.rlim_cur)
setrlimit (RLIMIT_STACK, &stack_limit);
#endif
/* For any redirected FD, dup2() it to the standard FD.
They are all marked close-on-exec already. */
if (fdin != FD_STDIN)
EINTRLOOP (r, dup2 (fdin, FD_STDIN));
if (fdout != FD_STDOUT)
EINTRLOOP (r, dup2 (fdout, FD_STDOUT));
if (fderr != FD_STDERR)
EINTRLOOP (r, dup2 (fderr, FD_STDERR));
/* Run the command. */
exec_command (argv, envp);

14
job.h
View file

@ -24,7 +24,7 @@ this program. If not, see <http://www.gnu.org/licenses/>. */
/* How to set close-on-exec for a file descriptor. */
#if !defined F_SETFD
#if !defined(F_SETFD) || !defined(F_GETFD)
# ifdef WINDOWS32
# define CLOSE_ON_EXEC(_d) process_noinherit(_d)
# else
@ -124,20 +124,16 @@ void start_waiting_jobs (void);
char **construct_command_argv (char *line, char **restp, struct file *file,
int cmd_flags, char** batch_file);
#ifdef VMS
int child_execute_job (char *argv, struct child *child);
int child_execute_job (struct child *child, char *argv);
#else
# define FD_STDIN (fileno (stdin))
# define FD_STDOUT (fileno (stdout))
# define FD_STDERR (fileno (stderr))
# if defined(__EMX__)
int child_execute_job (int stdin_fd, int stdout_fd, int stderr_fd,
char **argv, char **envp);
# else
void child_execute_job (int stdin_fd, int stdout_fd, int stderr_fd,
char **argv, char **envp) __attribute__ ((noreturn));
# endif
int child_execute_job (struct output *out, int good_stdin, char **argv, char **envp);
#endif
#ifdef _AMIGA
void exec_command (char **argv) __attribute__ ((noreturn));
#elif defined(__EMX__)

3
main.c
View file

@ -2417,8 +2417,7 @@ main (int argc, char **argv, char **envp)
termination. */
int pid;
int r;
pid = child_execute_job (FD_STDIN, FD_STDOUT, FD_STDERR,
nargv, environ);
pid = child_execute_job (NULL, 1, nargv, environ);
/* is this loop really necessary? */
do {

View file

@ -138,6 +138,13 @@ extern int errno;
# define SA_RESTART 0
#endif
#ifdef HAVE_VFORK_H
# include <vfork.h>
#endif
#if !HAVE_WORKING_VFORK
# define vfork fork
#endif
#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif

11
os.h
View file

@ -69,9 +69,16 @@ int jobserver_acquire (int timeout);
#define jobserver_release(_fatal) (void)(0)
#define jobserver_acquire_all() (0)
#define jobserver_signal() (void)(0)
#define jobserver_pre_child() (void)(0)
#define jobserver_post_child() (void)(0)
#define jobserver_pre_child(_r) (void)(0)
#define jobserver_post_child(_r) (void)(0)
#define jobserver_pre_acquire() (void)(0)
#define jobserver_acquire(_tmout) (0)
#endif
/* Create a "bad" file descriptor for stdin when parallel jobs are run. */
#if !defined(VMD) && !defined(WINDOWS32) && !defined(_AMIGA) && !defined(__MSDOS__)
int get_bad_stdin ();
#else
# define get_bad_stdin() (-1)
#endif

View file

@ -174,27 +174,31 @@ jobserver_acquire_all ()
}
}
/* This is really only invoked on OS/2.
On POSIX we just call jobserver_clear() in the child process. */
void jobserver_pre_child ()
/* Prepare the jobserver to start a child process. */
void jobserver_pre_child (int recursive)
{
CLOSE_ON_EXEC (job_fds[0]);
CLOSE_ON_EXEC (job_fds[1]);
/* If it's not a recursive make, avoid polutting the jobserver pipes. */
if (!recursive && job_fds[0] >= 0)
{
CLOSE_ON_EXEC (job_fds[0]);
CLOSE_ON_EXEC (job_fds[1]);
}
}
void jobserver_post_child ()
void jobserver_post_child (int recursive)
{
#if defined(F_GETFD) && defined(F_SETFD)
for (int i = 0; i < 2; ++i)
{
int flags;
EINTRLOOP (flags, fcntl (job_fds[i], F_GETFD));
if (flags >= 0)
{
int r;
EINTRLOOP (r, fcntl (job_fds[i], F_SETFD, flags & ~FD_CLOEXEC));
}
}
if (!recursive && job_fds[0] >= 0)
for (int i = 0; i < 2; ++i)
{
int flags;
EINTRLOOP (flags, fcntl (job_fds[i], F_GETFD));
if (flags >= 0)
{
int r;
EINTRLOOP (r, fcntl (job_fds[i], F_SETFD, flags & ~FD_CLOEXEC));
}
}
#endif
}
@ -388,3 +392,33 @@ jobserver_acquire (int timeout)
#endif
#endif /* MAKE_JOBSERVER */
/* Create a "bad" file descriptor for stdin when parallel jobs are run. */
int
get_bad_stdin ()
{
static int bad_stdin = -1;
/* Set up a bad standard input that reads from a broken pipe. */
if (bad_stdin == -1)
{
/* Make a file descriptor that is the read end of a broken pipe.
This will be used for some children's standard inputs. */
int pd[2];
if (pipe (pd) == 0)
{
/* Close the write side. */
(void) close (pd[1]);
/* Save the read side. */
bad_stdin = pd[0];
/* Set the descriptor to close on exec, so it does not litter any
child's descriptor table. When it is dup2'd onto descriptor 0,
that descriptor will not close on exec. */
CLOSE_ON_EXEC (bad_stdin);
}
}
return bad_stdin;
}

View file

@ -222,7 +222,7 @@ start_remote_job (char **argv, char **envp, int stdin_fd,
fflush (stderr);
}
pid = fork ();
pid = vfork ();
if (pid < 0)
{
/* The fork failed! */

View file

@ -803,7 +803,7 @@ build_vms_cmd (char **cmd_tokens,
}
int
child_execute_job (char *argv, struct child *child)
child_execute_job (struct child *child, char *argv)
{
int i;

View file

@ -148,11 +148,11 @@ jobserver_signal ()
{
}
void jobserver_pre_child ()
void jobserver_pre_child (int recursive)
{
}
void jobserver_post_child ()
void jobserver_post_child (int recursive)
{
}