bin/160403: concurrently running rc-scripts during boot
Kilian Klimek
kklimek at uos.de
Fri Sep 2 18:50:08 UTC 2011
>Number: 160403
>Category: bin
>Synopsis: concurrently running rc-scripts during boot
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: change-request
>Submitter-Id: current-users
>Arrival-Date: Fri Sep 02 18:50:07 UTC 2011
>Closed-Date:
>Last-Modified:
>Originator: Kilian Klimek
>Release:
>Organization:
>Environment:
>Description:
Attached in a patch against current that modifies rcorder to allow it to run rc-scripts concurrently during boot. The purpose is, of course, to speedup the boot process. The speedup can be quite substantial. I measured a speedup of up to 30% (some numbers to support this can be found at https://github.com/kil/rcorder in the README).
A few notes about the patch:
- The modifications in /etc/rc.d/ are required to make sure cleartmp doesn't run at the same time as the scripts modified (they create temporary files in /tmp).
- To ensure compatibility, the modifications to rcorder don't affect the default invocation of it in /etc/rc. The ordering of the rc-scripts that is generated is identical to the ordering that is generated now.
>How-To-Repeat:
>Fix:
Patch attached with submission follows:
Index: etc/rc
===================================================================
--- etc/rc (revision 225227)
+++ etc/rc (working copy)
@@ -82,17 +82,21 @@
# Do a first pass to get everything up to $early_late_divider so that
# we can do a second pass that includes $local_startup directories
#
-files=`rcorder ${skip} /etc/rc.d/* 2>/dev/null`
+if checkyesno rc_concurrent; then
+ rcorder -r ${skip} -a ${_boot} -l ${early_late_divider} /etc/rc.d/*
+else
+ files=`rcorder ${skip} /etc/rc.d/* 2>/dev/null`
-_rc_elem_done=' '
-for _rc_elem in ${files}; do
- run_rc_script ${_rc_elem} ${_boot}
- _rc_elem_done="${_rc_elem_done}${_rc_elem} "
+ _rc_elem_done=' '
+ for _rc_elem in ${files}; do
+ run_rc_script ${_rc_elem} ${_boot}
+ _rc_elem_done="${_rc_elem_done}${_rc_elem} "
- case "$_rc_elem" in
- */${early_late_divider}) break ;;
- esac
-done
+ case "$_rc_elem" in
+ */${early_late_divider}) break ;;
+ esac
+ done
+fi
unset files local_rc
@@ -104,15 +108,20 @@
*) find_local_scripts_new ;;
esac
-files=`rcorder ${skip} /etc/rc.d/* ${local_rc} 2>/dev/null`
-for _rc_elem in ${files}; do
- case "$_rc_elem_done" in
- *" $_rc_elem "*) continue ;;
- esac
+if checkyesno rc_concurrent; then
+ rcorder -r ${skip} -a ${_boot} -f ${early_late_divider} /etc/rc.d/* \
+ ${local_rc}
+ echo "rc_concurrent finished"
+else
+ files=`rcorder ${skip} /etc/rc.d/* ${local_rc} 2>/dev/null`
+ for _rc_elem in ${files}; do
+ case "$_rc_elem_done" in
+ *" $_rc_elem "*) continue ;;
+ esac
- run_rc_script ${_rc_elem} ${_boot}
-done
-
+ run_rc_script ${_rc_elem} ${_boot}
+ done
+fi
echo ''
date
exit 0
Index: etc/Makefile
===================================================================
--- etc/Makefile (revision 225227)
+++ etc/Makefile (working copy)
@@ -102,7 +102,7 @@
.endif
# -rwxr-xr-x root:wheel, for the new cron root:wheel
-BIN2= netstart pccard_ether rc.suspend rc.resume
+BIN2= netstart pccard_ether rc.suspend rc.resume rc.trampoline
MTREE= BSD.include.dist BSD.root.dist BSD.usr.dist BSD.var.dist
.if ${MK_SENDMAIL} != "no"
Index: etc/rc.d/abi
===================================================================
--- etc/rc.d/abi (revision 225227)
+++ etc/rc.d/abi (working copy)
@@ -6,6 +6,7 @@
# PROVIDE: abi
# REQUIRE: archdep
# KEYWORD: nojail
+# BEFORE: cleartmp
. /etc/rc.subr
Index: etc/rc.d/jail
===================================================================
--- etc/rc.d/jail (revision 225227)
+++ etc/rc.d/jail (working copy)
@@ -4,7 +4,7 @@
#
# PROVIDE: jail
-# REQUIRE: LOGIN cleanvar
+# REQUIRE: LOGIN cleanvar cleartmp
# BEFORE: securelevel
# KEYWORD: nojail shutdown
Index: etc/rc.d/motd
===================================================================
--- etc/rc.d/motd (revision 225227)
+++ etc/rc.d/motd (working copy)
@@ -5,7 +5,7 @@
# PROVIDE: motd
# REQUIRE: mountcritremote
-# BEFORE: LOGIN
+# BEFORE: LOGIN cleartmp
. /etc/rc.subr
Index: etc/defaults/rc.conf
===================================================================
--- etc/defaults/rc.conf (revision 225227)
+++ etc/defaults/rc.conf (working copy)
@@ -25,6 +25,7 @@
rc_info="NO" # Enables display of informational messages at boot.
rc_startmsgs="YES" # Show "Starting foo:" messages at boot
rcshutdown_timeout="30" # Seconds to wait before terminating rc.shutdown
+rc_concurrent="NO" # start rc scripts concurrently.
early_late_divider="FILESYSTEMS" # Script that separates early/late
# stages of the boot process. Make sure you know
# the ramifications if you change this.
Index: etc/rc.trampoline
===================================================================
--- etc/rc.trampoline (revision 0)
+++ etc/rc.trampoline (revision 0)
@@ -0,0 +1,11 @@
+#!/bin/sh
+. /etc/rc.subr
+load_rc_config 'XXX'
+
+if test -n "$_RCORDER_RUN_DEBUG"; then
+ echo '_RCORDER_RUN_DEBUG' $1 $2
+ sleep 0.02
+ exit 0
+fi
+
+run_rc_script $1 $2
Property changes on: etc/rc.trampoline
___________________________________________________________________
Added: svn:executable
+ *
Index: sbin/rcorder/rcorder.c
===================================================================
--- sbin/rcorder/rcorder.c (revision 225227)
+++ sbin/rcorder/rcorder.c (working copy)
@@ -3,6 +3,7 @@
#endif
/*
+ * Copyright (c) 2011 Kilian Klimek
* Copyright (c) 1998, 1999 Matthew R. Green
* All rights reserved.
* Copyright (c) 1998
@@ -46,6 +47,12 @@
#include <string.h>
#include <unistd.h>
#include <util.h>
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <libgen.h>
#include "ealloc.h"
#include "sprite.h"
@@ -76,6 +83,14 @@
int exit_code;
int file_count;
char **file_list;
+int kq;
+int childs = 0;
+char d_script_arg[] = "faststart";
+char d_trampoline[] = "/etc/rc.trampoline";
+char *trampoline = d_trampoline;
+char *script_arg = d_script_arg;
+char *rc_first = NULL;
+char *rc_last = NULL;
typedef int bool;
#define TRUE 1
@@ -83,6 +98,9 @@
typedef bool flag;
#define SET TRUE
#define RESET FALSE
+#define RUNNING 2
+#define FIRST 3
+#define LAST 4
Hash_Table provide_hash_s, *provide_hash;
@@ -90,6 +108,7 @@
typedef struct filenode filenode;
typedef struct f_provnode f_provnode;
typedef struct f_reqnode f_reqnode;
+typedef struct f_neednode f_neednode;
typedef struct strnodelist strnodelist;
struct provnode {
@@ -109,6 +128,11 @@
f_reqnode *next;
};
+struct f_neednode {
+ filenode *entry;
+ f_neednode *next;
+};
+
struct strnodelist {
filenode *node;
strnodelist *next;
@@ -122,6 +146,7 @@
f_reqnode *req_list;
f_provnode *prov_list;
strnodelist *keyword_list;
+ f_neednode *need_list;
};
filenode fn_head_s, *fn_head;
@@ -151,17 +176,31 @@
void initialize(void);
void generate_ordering(void);
int main(int, char *[]);
+static pid_t spawn(filenode *);
+static int wait_child(void);
+static void run_scripts(void);
+static void filenode_unlink(filenode *);
+static int can_run(filenode *);
+static void check_start(filenode *);
+static void generate_needs(void);
int
main(int argc, char *argv[])
{
int ch;
+ int run = 0;
+ struct stat st;
- while ((ch = getopt(argc, argv, "dk:s:")) != -1)
+ while ((ch = getopt(argc, argv, "a:df:k:l:rs:T:")) != -1)
switch (ch) {
+ case 'a':
+ script_arg = optarg;
+ break;
case 'd':
#ifdef DEBUG
debug = 1;
+ /* inherited by the trampoline script */
+ setenv("_RCORDER_RUN_DEBUG", "yes", 1);
#else
warnx("debugging not compiled in, -d ignored");
#endif
@@ -169,9 +208,21 @@
case 'k':
strnode_add(&keep_list, optarg, 0);
break;
+ case 'r':
+ run = 1;
+ break;
case 's':
strnode_add(&skip_list, optarg, 0);
break;
+ case 'T':
+ trampoline = optarg;
+ break;
+ case 'f':
+ rc_first = optarg;
+ break;
+ case 'l':
+ rc_last = optarg;
+ break;
default:
/* XXX should crunch it? */
break;
@@ -187,9 +238,27 @@
DPRINTF((stderr, "initialize\n"));
crunch_all_files();
DPRINTF((stderr, "crunch_all_files\n"));
- generate_ordering();
- DPRINTF((stderr, "generate_ordering\n"));
+ if (run) {
+ /* do some sanity checking on the trampoline script */
+ if (stat(trampoline, &st) == -1)
+ err(1, "failed to stat %s", trampoline);
+ if (!S_ISREG(st.st_mode))
+ errx(1, "not a regular file: %s", trampoline);
+
+ if ((st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0)
+ errx(1, "not executable: %s", trampoline);
+
+ if ((kq = kqueue()) == -1)
+ err(1, "kqueue failed");
+
+ run_scripts();
+ DPRINTF((stderr, "run_scripts\n"));
+ } else {
+ generate_ordering();
+ DPRINTF((stderr, "generate_ordering\n"));
+ }
+
exit(exit_code);
}
@@ -240,7 +309,16 @@
temp->req_list = NULL;
temp->prov_list = NULL;
temp->keyword_list = NULL;
- temp->in_progress = RESET;
+ temp->need_list = NULL;
+
+ if (rc_first != NULL && strncmp(rc_first, basename(filename), strlen(rc_first)) == 0) {
+ temp->in_progress = FIRST;
+ } else if (rc_last != NULL && strncmp(rc_last, basename(filename), strlen(rc_last)) == 0) {
+ temp->in_progress = LAST;
+ } else {
+ temp->in_progress = RESET;
+ }
+
/*
* link the filenode into the list of filenodes.
* note that the double linking means we can delete a
@@ -720,9 +798,6 @@
} else
was_set = 0;
- /* mark fnode */
- fnode->in_progress = SET;
-
/*
* for each requirement of fnode -> r
* satisfy_req(r, filename)
@@ -739,6 +814,10 @@
}
fnode->req_list = NULL;
+ /* mark fnode */
+ if (fnode->in_progress != FIRST && fnode->in_progress != LAST)
+ fnode->in_progress = SET;
+
/*
* for each provision of fnode -> p
* remove fnode from provision list for p in hash table
@@ -763,8 +842,14 @@
DPRINTF((stderr, "next do: "));
/* if we were already in progress, don't print again */
- if (was_set == 0 && skip_ok(fnode) && keep_ok(fnode))
- printf("%s\n", fnode->filename);
+ if (was_set == 0 && skip_ok(fnode) && keep_ok(fnode)) {
+ if (rc_first == NULL)
+ printf("%s\n", fnode->filename);
+ if (fnode->in_progress == FIRST)
+ rc_first = NULL;
+ else if (fnode->in_progress == LAST)
+ exit(0);
+ }
if (fnode->next != NULL) {
fnode->next->last = fnode->last;
@@ -805,3 +890,299 @@
do_file(fn_head->next);
}
}
+
+/*
+ * Check if fn_this can be started by checking its requirements and status.
+ */
+static int
+can_run(filenode *fn_this) {
+ provnode *p;
+ Hash_Entry *entry;
+ f_reqnode *r;
+ int all_set;
+
+ if (fn_this->in_progress == RUNNING
+ || fn_this->in_progress == LAST
+ || fn_this->in_progress == SET)
+ return (0);
+
+ all_set = 1;
+
+ if (fn_this->req_list != NULL) {
+ r = fn_this->req_list;
+
+ /* check if all requirements are satisfied */
+ while (r != NULL) {
+ entry = r->entry;
+ p = Hash_GetValue(entry);
+
+ if (p != NULL && p->head == SET)
+ p = p->next;
+
+ if (p != NULL) {
+ all_set = 0;
+ break;
+ }
+
+ r = r->next;
+ }
+ }
+ return (all_set);
+}
+
+/*
+ * Generate the need_list for all nodes. This has to happen after all
+ * dependencies have been resolved.
+ */
+static void
+generate_needs(void)
+{
+ provnode *p;
+ Hash_Entry *entry;
+ f_reqnode *r;
+ filenode *fn_this;
+ f_neednode *n;
+
+ for(fn_this = fn_head->next; fn_this != NULL; fn_this = fn_this->next) {
+ if (fn_this->req_list != NULL) {
+ r = fn_this->req_list;
+
+ while (r != NULL) {
+ entry = r->entry;
+ p = Hash_GetValue(entry);
+
+ if (p != NULL && p->head == SET)
+ p = p->next;
+
+ while (p != NULL) {
+ if(p->fnode == NULL) {
+ p = p->next;
+ continue;
+ }
+
+ n = emalloc(sizeof(f_neednode));
+ n->next = NULL;
+ n->entry = fn_this;
+ n->next = p->fnode->need_list;
+ p->fnode->need_list = n;
+ p = p->next;
+ }
+ r = r->next;
+ }
+ }
+ }
+}
+
+/*
+ * fill the need lists and start everything that has no requirements.
+ */
+static void
+run_scripts(void)
+{
+ filenode *fn_this,
+ *t = NULL;
+
+ generate_needs();
+
+ DPRINTF((stderr, "init...\n"));
+ fn_this = fn_head->next;
+ while (fn_this != NULL) {
+ if (fn_this->in_progress == FIRST) {
+ t = fn_this;
+ } else {
+ if (can_run(fn_this))
+ spawn(fn_this);
+ }
+ fn_this = fn_this->next;
+ }
+
+ /*
+ * If rc_first was set, we have to skip the dependecies before
+ * rc_first. We can't unset rc_first in the loop above because
+ * that would allow scripts, that should not started, to run.
+ */
+ if (t) {
+ rc_first = NULL;
+ t->in_progress = RESET;
+ spawn(t);
+ fn_this = fn_head->next;
+ while (fn_this != NULL) {
+ if (can_run(fn_this))
+ spawn(fn_this);
+ fn_this = fn_this->next;
+ }
+ }
+
+ DPRINTF((stderr, "wait ...\n"));
+ while (childs > 0)
+ wait_child();
+ exit(0);
+}
+
+
+/*
+ * Start a rc script for a filenode.
+ */
+static pid_t
+spawn(filenode *fn)
+{
+ struct kevent event;
+ pid_t p;
+ char *args[] = {trampoline, fn->filename, script_arg, NULL};
+
+ if (fn->in_progress == SET || fn->in_progress == RUNNING)
+ return (0);
+
+ if (fn->in_progress == FIRST)
+ return (0);
+
+ if (fn->in_progress == LAST)
+ return (0);
+
+ if (rc_first != NULL) {
+ filenode_unlink(fn);
+ check_start(fn);
+ return (1);
+ }
+
+ if (!(skip_ok(fn) && keep_ok(fn))) {
+ filenode_unlink(fn);
+ check_start(fn);
+ return (1);
+ }
+
+ DPRINTF((stderr, "spawn: %s\n", fn->filename));
+ childs++;
+ p = fork();
+
+ if (p == -1) {
+ if (errno == EAGAIN)
+ return (0);
+ err(1, "fork");
+ }
+
+ /* parent */
+ if (p > 0) {
+ EV_SET(&event, p, EVFILT_PROC,
+ EV_ADD | EV_ENABLE | EV_ONESHOT,
+ NOTE_EXIT, 0, fn);
+
+ if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) {
+ if (errno == EINTR)
+ return (0);
+ err(1, "kevent");
+ }
+
+ fn->in_progress = RUNNING;
+ return (p);
+ }
+
+ /* child */
+ execv(args[0], args);
+ exit(1);
+}
+
+/*
+ * Wait for at least one child process to exit. We block for a maximum
+ * of 20 seconds. After that, collect what is available.
+ */
+static int
+wait_child(void)
+{
+ struct kevent event;
+ filenode *f;
+ int ret;
+ struct timespec ts;
+
+ ts.tv_sec = 20;
+ ts.tv_nsec = 0;
+
+ while (1) {
+ ret = kevent(kq, NULL, 0, &event, 1, &ts);
+
+ if (ret == 0)
+ break;
+
+ ts.tv_sec = 0;
+
+ if (ret == -1) {
+ if (errno == EINTR)
+ break;
+ err(1, "kevent");
+ }
+
+ /*
+ * ignore waitpid errors and exit status; nothing we can do.
+ * just collect childs.
+ */
+ waitpid(event.ident, NULL, WNOHANG);
+ childs--;
+
+ f = (filenode *) event.udata;
+
+ if (event.fflags & NOTE_EXIT) {
+ DPRINTF((stderr, "exit: %s (%d)\n", f->filename, event.ident));
+ filenode_unlink(f);
+ check_start(f);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * For f check which nodes that require it can be started. and start them.
+ */
+static void
+check_start(filenode *f)
+{
+ filenode *fn;
+ f_neednode *n;
+
+ if (f->need_list == NULL)
+ return;
+
+ n = f->need_list;
+ while (n != NULL) {
+ fn = n->entry;
+ if(can_run(fn))
+ spawn(fn);
+ n = n->next;
+ }
+}
+
+/*
+ * Remove filenode from list.
+ */
+static void
+filenode_unlink(filenode *f)
+{
+ f_provnode *p,
+ *p_tmp;
+ provnode *pnode;
+
+ f->in_progress = SET;
+ if (f->next != NULL)
+ f->next->last = f->last;
+ if (f->last != NULL)
+ f->last->next = f->next;
+ f->req_list = NULL;
+
+ /*
+ * for each provision of fnode -> p
+ * remove fnode from provision list for p in hash table
+ */
+ p = f->prov_list;
+ while (p != NULL) {
+ p_tmp = p;
+ pnode = p->pnode;
+ if (pnode->next != NULL)
+ pnode->next->last = pnode->last;
+ if (pnode->last != NULL)
+ pnode->last->next = pnode->next;
+ free(pnode);
+ p = p->next;
+ free(p_tmp);
+ }
+ f->prov_list = NULL;
+}
Index: sbin/rcorder/rcorder.8
===================================================================
--- sbin/rcorder/rcorder.8 (revision 225227)
+++ sbin/rcorder/rcorder.8 (working copy)
@@ -39,8 +39,13 @@
.Nd print a dependency ordering of interdependent files
.Sh SYNOPSIS
.Nm
+.Op Fl a Ar action
+.Op Fl f Ar first
.Op Fl k Ar keep
+.Op Fl l Ar last
+.Op Fl r
.Op Fl s Ar skip
+.Op Fl T Ar trampoline_script
.Ar
.Sh DESCRIPTION
The
@@ -95,18 +100,44 @@
.Pp
The options are as follows:
.Bl -tag -width indent
+.It Fl a Ar action
+Argument passed to rc scripts by the trampoline script.
+.It Fl f Ar first
+Act as if the requirement
+.Ar first,
+and requirements leading up to it, have already been satisfied (see
+.Sx CAVEATS
+sections).
.It Fl k
Add the specified keyword to the
.Dq "keep list" .
If any
.Fl k
option is given, only those files containing the matching keyword are listed.
+.It Fl l Ar last
+Stop when the requirement
+.Ar last
+has been satisfied (see
+.Sx CAVEATS
+sections).
+.It Fl r
+Instead of printing the ordered list of rc scripts, execute them concurrently
+as
+.Nm
+sees fit.
.It Fl s
Add the specified keyword to the
.Dq "skip list" .
If any
.Fl s
option is given, files containing the matching keyword are not listed.
+.It Fl T Ar trampoline_script
+When running with the
+.Fl r
+flag, use the specified argument as the
+.Ar trampoline_script.
+It is called with the rc script to start as the first argument and the action
+(e.g. faststart) to take as the second argument.
.El
.Pp
An example block follows:
@@ -155,6 +186,48 @@
A set of files has a circular dependency which was detected while
processing the stated file.
.El
+.Sh CAVEATS
+When running with the
+.Fl r
+flag, the arguments passed to
+.Fl f
+or
+.Fl l
+must be one of the check-points (or "placeholders") mentioned in
+.Xr rc 8 .
+.Pp
+The ordering generated when running with or without the
+.Fl r
+flag is different. Without the
+.Fl r
+flag, and with the
+.Fl l
+flag,
+.Nm
+produces an ordering that only guarantees that the check-point will
+be satisfied. With the
+.Fl r
+flag,
+.Nm
+will guarantee that all and only the requirements leading up to the
+check-point will be satisfied.
+.Pp
+Likewise, with the
+.Fl f
+flag and the
+.Fl r
+flag set,
+.Nm
+will assume all and only the requirements before the check-point are
+satisfied. Without the
+.Fl r
+flag, the ordering will complement the ordering generated of the same
+.Nm
+run with the
+.Fl f
+replaced with
+.Fl l
+flag.
.Sh SEE ALSO
.Xr rc 8
.Sh HISTORY
Index: share/man/man5/rc.conf.5
===================================================================
--- share/man/man5/rc.conf.5 (revision 225227)
+++ share/man/man5/rc.conf.5 (working copy)
@@ -114,6 +114,11 @@
show
.Dq Starting foo:
when faststart is used (e.g., at boot time).
+.It Va rc_concurrent
+.Pq Vt bool
+If set to
+.Dq Li YES ,
+start rc scripts concurrently.
.It Va early_late_divider
.Pq Vt str
The name of the script that should be used as the
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list