sh: Use vfork in a few common cases.
authorPeter Avalos <pavalos@dragonflybsd.org>
Sun, 5 Feb 2012 20:21:25 +0000 (12:21 -0800)
committerPeter Avalos <pavalos@dragonflybsd.org>
Sun, 5 Feb 2012 21:04:05 +0000 (13:04 -0800)
This uses vfork() for simple commands and command substitutions containing a
single simple command, invoking an external program under certain conditions
(no redirections or variable assignments, non-interactive shell, no job
control). These restrictions limit the amount of code executed in a vforked
child.

There is a large speedup (for example 35%) in microbenchmarks. The
difference in buildkernel is smaller (for example 0.5%) but still
statistically significant. See
http://lists.freebsd.org/pipermail/freebsd-hackers/2012-January/037581.html
for some numbers.

The use of vfork() can be disabled by setting a variable named
SH_DISABLE_VFORK.

Obtained-from:  FreeBSD 230998
(cherry picked from commit 3fb5edd5046fcd04f359351f47e70ad37816977a)

bin/sh/eval.c
bin/sh/jobs.c
bin/sh/jobs.h
bin/sh/var.c
bin/sh/var.h

index b1d250b..9e429ff 100644 (file)
@@ -34,7 +34,7 @@
  * SUCH DAMAGE.
  *
  * @(#)eval.c  8.9 (Berkeley) 6/8/95
- * $FreeBSD: src/bin/sh/eval.c,v 1.119 2012/01/22 14:00:33 jilles Exp $
+ * $FreeBSD: src/bin/sh/eval.c,v 1.120 2012/02/04 23:12:14 jilles Exp $
  */
 
 #include <sys/time.h>
@@ -924,6 +924,15 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
                        if (pipe(pip) < 0)
                                error("Pipe call failed: %s", strerror(errno));
                }
+               if (cmdentry.cmdtype == CMDNORMAL &&
+                   cmd->ncmd.redirect == NULL &&
+                   varlist.list == NULL &&
+                   (mode == FORK_FG || mode == FORK_NOJOB) &&
+                   !disvforkset() && !iflag && !mflag) {
+                       vforkexecshell(jp, argv, environment(), path,
+                           cmdentry.u.index, flags & EV_BACKCMD ? pip : NULL);
+                       goto parent;
+               }
                if (forkshell(jp, cmd, mode) != 0)
                        goto parent;    /* at end of routine */
                if (flags & EV_BACKCMD) {
index 4ee7b8a..33dd86a 100644 (file)
@@ -34,7 +34,7 @@
  * SUCH DAMAGE.
  *
  * @(#)jobs.c  8.5 (Berkeley) 5/4/95
- * $FreeBSD: src/bin/sh/jobs.c,v 1.95 2011/06/13 21:03:27 jilles Exp $
+ * $FreeBSD: src/bin/sh/jobs.c,v 1.97 2012/02/04 23:12:14 jilles Exp $
  */
 
 #include <sys/ioctl.h>
@@ -56,6 +56,7 @@
 #undef CEOF                    /* syntax.h redefines this */
 #endif
 #include "redir.h"
+#include "exec.h"
 #include "show.h"
 #include "main.h"
 #include "parser.h"
@@ -883,6 +884,54 @@ forkshell(struct job *jp, union node *n, int mode)
 }
 
 
+pid_t
+vforkexecshell(struct job *jp, char **argv, char **envp, const char *path, int idx, int pip[2])
+{
+       pid_t pid;
+       struct jmploc jmploc;
+       struct jmploc *savehandler;
+
+       TRACE(("vforkexecshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n,
+           mode));
+       INTOFF;
+       flushall();
+       savehandler = handler;
+       pid = vfork();
+       if (pid == -1) {
+               TRACE(("Vfork failed, errno=%d\n", errno));
+               INTON;
+               error("Cannot fork: %s", strerror(errno));
+       }
+       if (pid == 0) {
+               TRACE(("Child shell %d\n", (int)getpid()));
+               if (setjmp(jmploc.loc))
+                       _exit(exception == EXEXEC ? exerrno : 2);
+               if (pip != NULL) {
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               dup2(pip[1], 1);
+                               close(pip[1]);
+                       }
+               }
+               handler = &jmploc;
+               shellexec(argv, envp, path, idx);
+       }
+       handler = savehandler;
+       if (jp) {
+               struct procstat *ps = &jp->ps[jp->nprocs++];
+               ps->pid = pid;
+               ps->status = -1;
+               ps->cmd = nullstr;
+               jp->foreground = 1;
+#if JOBS
+               setcurjob(jp);
+#endif
+       }
+       INTON;
+       TRACE(("In parent shell:  child = %d\n", (int)pid));
+       return pid;
+}
+
 
 /*
  * Wait for job to finish.
index bb6b629..72274f3 100644 (file)
@@ -34,7 +34,7 @@
  * SUCH DAMAGE.
  *
  *     @(#)jobs.h      8.2 (Berkeley) 5/4/95
- * $FreeBSD: src/bin/sh/jobs.h,v 1.21 2011/06/13 21:03:27 jilles Exp $
+ * $FreeBSD: src/bin/sh/jobs.h,v 1.22 2012/02/04 23:12:14 jilles Exp $
  */
 
 /* Mode argument to forkshell.  Don't change FORK_FG or FORK_BG. */
@@ -96,6 +96,7 @@ void showjobs(int, int);
 pid_t getjobpgrp(char *);
 struct job *makejob(union node *, int);
 pid_t forkshell(struct job *, union node *, int);
+pid_t vforkexecshell(struct job *, char **, char **, const char *, int, int []);
 int waitforjob(struct job *, int *);
 int stoppedjobs(void);
 int backgndpidset(void);
index a3a5a9b..7d54848 100644 (file)
@@ -34,7 +34,7 @@
  * SUCH DAMAGE.
  *
  * @(#)var.c   8.3 (Berkeley) 5/4/95
- * $FreeBSD: src/bin/sh/var.c,v 1.63 2011/06/17 10:21:24 jilles Exp $
+ * $FreeBSD: src/bin/sh/var.c,v 1.64 2012/02/04 23:12:14 jilles Exp $
  */
 
 #include <unistd.h>
@@ -93,6 +93,7 @@ struct var vps2;
 struct var vps4;
 struct var vvers;
 static struct var voptind;
+struct var vdisvfork;
 
 int forcelocal;
 
@@ -124,6 +125,8 @@ static const struct varinit varinit[] = {
 #endif
        { &voptind,     0,                              "OPTIND=1",
          getoptsreset },
+       { &vdisvfork,   VUNSET,                         "SH_DISABLE_VFORK=",
+         NULL },
        { NULL, 0,                              NULL,
          NULL }
 };
index 44890e3..fbbba59 100644 (file)
@@ -34,7 +34,7 @@
  * SUCH DAMAGE.
  *
  *     @(#)var.h       8.2 (Berkeley) 5/4/95
- * $FreeBSD: src/bin/sh/var.h,v 1.24 2011/06/13 21:03:27 jilles Exp $
+ * $FreeBSD: src/bin/sh/var.h,v 1.25 2012/02/04 23:12:14 jilles Exp $
  */
 
 /*
@@ -83,6 +83,7 @@ extern struct var vppid;
 extern struct var vps1;
 extern struct var vps2;
 extern struct var vps4;
+extern struct var vdisvfork;
 #ifndef NO_HISTORY
 extern struct var vhistsize;
 extern struct var vterm;
@@ -113,6 +114,7 @@ extern int initial_localeisutf8;
 #endif
 
 #define mpathset()     ((vmpath.flags & VUNSET) == 0)
+#define disvforkset()  ((vdisvfork.flags & VUNSET) == 0)
 
 void initvar(void);
 void setvar(const char *, const char *, int);