Subject: A report on implementing runlevels in NetBSD
To: None <tech-userlevel@netbsd.org>
From: Giles Lean <giles@nemeton.com.au>
List: tech-userlevel
Date: 12/04/1999 11:23:15
Hi all,
Some time back in 1996 I added runlevels to NetBSD's init. After
doing so I decided it wasn't a useful thing to have done. With some
prompting from Luke Mewburn I have dug out my old notes and email and
am re-presenting my results in the hope that my investigation may be
useful in the current efforts to extend /etc/rc functionality.
To put the summary up front and get my personal biases somewhat out of
the way:
1. I recommend NetBSD adopt 'start' and 'stop' scripts, since they are
very useful for manually starting and stopping services and
integrate readily with the package system.
2. I am ambivalent about whether these scripts should be called
directly from /etc/rc or from rc.sh and rcorder.
I like the simplicity of /etc/rc. I like the flexibility of easy
programatic addition of new scripts that rc.sh and rcorder should
provide. I am dubious about providing two methods, since this
seems more a political compromise than a technical advantage.
3. Runlevels in SysV are badly designed, incompatibly implemented and
provide too few runlevels for very flexible usage.
4. Anyone looking at this topic I would encourage to evaluate the work
Simon Gerraty has done, which used to be available at
ftp.quick.com.au, but I can't seem to get there today.
Simon's rc scripts have been used on a number of platforms,
including on systems that provide runlevels.
Now, on with the show.
SysV init offers two facilities that BSD init doesn't:
1. runlevels
2. respawning of daemons
To take the second point first, this is trivially done via:
#! /bin/sh
while :
do
"$@"
done
Fancier setups could be done with configurable timeouts, respawn
limits, up/down states, logging, whatever. I can readily imagine a
'start' script settting this going, and a 'stop' script shutting it
down. There is some prior art for this sort of thing outside of SysV
init.
This functionality can be put into init, but it doesn't have to be.
The second point, runlevels, are the more contentious. I endeavour
not to be religious about the SysV/BSD/Linux thing. (I'm paid to
support HP-UX. I can't afford to be religious; it's the oldest
BSD/SysV hybrid there is, and has diverged from both as well.)
When I added runlevels to NetBSD's init I found:
(a) I had to add a kernel variable to store the runlevel
SysV uses utmp, but in single user mode we don't have a filesystem
mounted read/write so that's not appropriate for us. Adding a new
kernel variable isn't a real hassle (we already have securelevel,
for example) but it does mean that the kernel knows about
runlevels at least minimally. Numerous people felt that this was
"unclean". Maybe so.
Linux I presume does do runlevels (since they have a SysVinit
source package) but I don't know how it works.
(b) there are gross discrepancies in the current implementations
At the time I did this work (1996) I investigated Solaris 2.5 and
HP-UX 10.x. I had some memories of SCO as well, but they're just
about gone now. (Both SCO and my memories. :-)
(i) the AT&T documentation is used by both Sun and HP, and a
naive reading suggests that runlevels are in fact levels,
with a defined ordering
(ii) an alternative school of thought says that the runlevels
should be independent states, with no implied ordering
[more discussion later]
(iii) nobody agrees about _which_ runlevels should be available,
and several are taken and are not available
0 shutdown
1/S single user mode
2 former default multiuser (HP-UX 9.x, SCO?)
3 current HP-UX default runlevel
4
5 ?? reserved by Solaris for something (or is this 4?)
6 reboot (some OSes only, definitely SCO)
This suggests runlevels are a really scare resource: you get
the default, a couple of others, and then you're into OS
dependent territory again.
(iv) there is no standard numbering scheme used by the vendors
for startup and shutdown scripts, and no place for application
vendors to register to get numbers, so vendor names for
startup and shutdown scripts are probably OS specific
(c) some of the existing design is asinine
When shutting down to a runlevel, it is defined that the kill
scripts and then the startup scripts will be run at that level.
Suppose I have defined:
runlevel 2: networking up, no logins
runlevel 3: networking up, network services (httpd) up, no logins
runlevel 4: networking up, network service up, logins allowed
When I transition from runlevel 4 to runlevel 3 the K* kill
scripts for runlevel 4 are not run, the kill scripts in runlevel 3
are run, then the S* start scripts in runlevel 3 are run.
On the face of it this merely means that the kill scripts need to
be one level lower; services that are started in runlevel 3 need
to be shutdown in runlevel 2. Tolerable, but confusing to many
administrators.
A bigger issue is that all start scripts have to be able to notice
that they're being re-run, and not do damage. In my example above
the http daemon will already be running when the S* scripts for
level.
Minor nit (just thought of this one, brand new today :-) is that
inetd has no knowledge of runlevels. Altering /etc/inetd.conf
under inetd and signaling it on a change of runlevels is clunky.
(Sure, we can do atomic file updates to survive across crashes,
but this is getting uglier ...)
Now to return to (b)(ii). HP implemented runlevels. When switching
from runlevel 4 to 2, the kill scripts are run for levels 3 and 2,
then the start scripts for level 2 are run.
Solaris is closer to run _states_ and in a transition from 4 to 2 will
only run the kill scripts in level 2, then the start scripts in level
2. This turns out to be really awkward: I can't simply put the kill
scripts for a particular level one level down, since they won't always
be run. Either I have to enforce an administrative policy that says
"never jump runlevels" or I have to put *all* the kill scripts for
every service in every lower level.
I do not recall whether Solaris runs all the S* scripts when jumping
runlevels upwards or not. HP-UX will.
I also did not like the amount of code I had had to change in init to
make all this work, and scrapped the project. The useful results
seemed to be:
- a minor bug in our init was found and fixed
- I had a good understanding of how useless SysV runlevels are in the
real world
Regards,
Giles