This article originally appeared in c't magazine 9/09, p. 176
Loading the Linux kernel takes up just a fraction of the time spent waiting for the login prompt during booting. The system spends most of its time sitting waiting for
init
(which has its origins in Unix System V) to cycle through the various runlevels, during which it runs innumerable sequences of init scripts.In Ubuntu and Fedora, Upstart has long replaced the traditional SysV init.
The current boot sequence, with services starting consecutively in a fixed sequence, remains unaltered simply because no-one has sat down and adapted the init scripts for these various services to the capabilities of Upstart.
Upstart simply emulates the SysV init runlevels (which actually no longer exist on systems running Upstart) and continues to call the old init scripts.
For Ubuntu 9.10, the development team have finally started to convert some services to Upstart.
Both Upstart and SysV init are the first processes to be launched by the kernel (with ID 1) as soon as the latter has booted and any boot scripts from the initial ramdisk (initrd) have been run.
For SysV init, the lynchpin of system initialisation is the /etc/inittab file. This is where SysV init finds the default runlevel, the name of the first initialisation script and the commands for initialising each runlevel.
During runlevel initialisation, the linked init scripts in the relevant runlevel directory (e.g. /etc/rc5.d) are run sequentially.
For this to work, all services must run in the background as daemons and decouple themselves from the console, since the init script would otherwise hang until the service had terminated.
This decoupling means that for init to determine whether a service is still running, or has self-terminated, is time-consuming.
It generally achieves this by the daemon saving a file containing its process ID (PID) in /var/run, leaving it to the init script to determine whether a particular PID belongs to the daemon in question.
One of the final init scripts launches the GUI. Once SysV init has run all of its scripts, services listed in the /etc/inittab file, such as login consoles, are then launched and monitored.
Upstart, by contrast, is event-oriented and works using 'jobs', with each job file in the /etc/init directory being responsible for launching a service or for a specific component of system initialisation.
There is no fixed sequence; instead each job specifies the events to which it will react. When an event occurs, Upstart starts all jobs that have been waiting for this event, in parallel.
Upstart generates the first event,
startup
, automatically when it is called. Each job also generates a started Jobname
and stopped Jobname
event on starting and terminating respectively.Various jobs take an interest in the
startup
event in Ubuntu 9.10, including hostname
, which sets up the computer name.The associated job file is /etc/init/hostname.conf. The following example shows a very simplified version of this job:
start on startup
task
exec hostname -b -F /etc/hostname
The keyword
start on
specifies the event which will trigger this job. If the job is to be triggered by multiple events, these events must be logically linked.start on (runlevel [016]
and (stopped gdm
or stopped kdm
or stopped xdm))
Unlike in Ubuntu 9.04, there can no longer be multiple
start on
lines. If the job subsequently needs to be terminated, stop on
is used to define additional stop events which in turn cause the job to be stopped.If a job needs to be started or stopped manually, this can be achieved using
initctl start Jobname
and
initctl stop Jobname
.The name of the program which the job should run follows the keyword
exec
. One major difference between Upstart and SysV init is that services always run in the background in init scripts, since they would otherwise cause init to hang.Upstart, by contrast, expects the process following the
exec
statement to run in the foreground, since Upstart only considers the job to be running for as long as this process is running.If a process started using
exec
ends, Upstart considers the job to have ended and waits for another suitable event to occur (waiting).Upstart notes the status of each job listed in /etc/init. This information can be viewed using the
initctl list
and initctl status Jobname
commands.Events as the key
Upstart's event-based design differs fundamentally from that used by SysV init, in which init scripts are stubbornly called in the lexical sequence in the relevant runlevel directory. This allows Upstart to be much more flexible.
If, for example, there is no network connection present when the mail daemon (MTA) is launched, SysV init has to wait for the time-out before continuing to boot the system.
In Upstart, the MTA is only started once the network connection has been established. The
network up
event is set as the start event for the MTA job.This event is triggered by the service responsible for network set-up only after the network has been successfully configured – in the case of a laptop out on the road, for example, this means not at all.
Because jobs do not have a fixed start sequence, the system is able to boot faster with Upstart than with SysV init.
In addition, it is possible to process multiple jobs simultaneously, so that initialisation tasks can be executed in parallel. This also represents a potential time saving.
A major change in Ubuntu 9.10 is that Upstart has finally been brought creaking to life now many system settings and calls to daemons have been converted to it.
These include mounting drives and starting udev, cron and the GUI. However, Ubuntu is not yet quite at the point of being able to do away with the old init scripts altogether and there is still a wrapper job,
rc
, which emulates the SysV init runlevels and calls the init scripts linked in /etc/init.d and /etc/rc?.d.Shortly after the kernel has been started and the first pseudo-file system mounted, the /etc/init/rc-sysinit.conf job is started.
The start event for this job is
filesystem
and its role is to generate the runlevel S
event, thereby setting the equivalent of runlevel S. The /etc/init/rcS.conf job then starts; it processes the symbolic links in /etc/rcS.d, taking care of basic system configuration.The signal for runlevel 2, to which further Upstart jobs are coupled, is then generated and the init scripts linked in /etc/rc2.d are executed.
The /etc/inittab file – which on systems using SysV init deals with tasks such as starting the login console to complete initialisation – is replaced by a number of Upstart jobs, a illustrated by the following extract:
start on stopped rc RUNLEVEL=[2345]
stop on runlevel [!2345]
respawn
exec /sbin/getty -8 38400 tty1
Since the
rc
job, which emulates the runlevels, terminates after calling the init script for the relevant runlevel and is not running during the emulated runlevel, the start signal for the tty
jobs is the termination of the rc
job, rather than for example, its start.In addition, the login console needs to be deactivated when shutting down the system (runlevels 0 and 6) and in single user mode.
Getty itself runs in the foreground and, since it is called using the keyword
exec
, is monitored by Upstart. The additional keyword respawn
tells Upstart to keep restarting the process if it terminates. This causes a new login prompt to be displayed after logging off.To prevent the system from being overloaded by a process constantly restarting, the frequency with which Upstart tries to start a service over a particular period can be limited. Fedora 10 uses this feature in the prefdm job to launch the GUI.
start on stopped rc5
stop on runlevel [!5]
respawn
respawn limit 10 120
exec /etc/X11/prefdm --nodaemon
The limit for restarting is set to 10 attempts within 120 seconds, for which both the
respawn
and respawn limit
commands are required. respawn limit
on its own does not cause the service to restart.No backchat
When it comes to terminating a job, Upstart concerns itself only with the process started in the foreground using
exec
.It sends the terminate signal (SIGTERM) and expects the process to quit. Upstart does not tolerate dissent – if the process fails to terminate, it is summarily terminated a few seconds later using the kill signal (SIGKILL).
Once a stop event has been triggered, a service can neither block it, delay it or roll it back
The keywords
pre-stop
and post-stop
can be used to specify commands which Upstart should execute before and after terminating the service. This is useful for any clean-up work, for example:post-stop exec rm -f /var/run/tserv.pid
There are also
pre-start
and post-start
keywords which can be used to specify commands to be executed immediately before and after starting a service, such as creating required directories or adjusting certain system settings, after starting the service.Since
exec
expects the service to be started in the foreground, Upstart can't wait for the service to be terminated before executing post-start
.Upstart therefore executes
post-start
in parallel with starting the service. The diagram on the right illustrates the sequence of events when starting and ending an Upstart job.In the above examples, all our commands have been called using
exec
. It is, however, also possible to use a block of commands book-ended by the keywords script
and end script
.pre-start script
if [ ! -e /var/run/tserv ]; then
mkdir -p /var/run/tserv
fi
end script
Where, instead of using
exec
, a service application is started using a block of commands. It is worth bearing in mind that the commands following the command for calling the service will only be executed if the service terminates immediately after being called.If the service is subsequently terminated by means of a terminate signal from Upstart, these commands will not be executed.
Migrating init scripts
Its event-based design makes Upstart particularly useful for services which need to react to outside influences, such as VDR, which turns the computer into a digital video recorder.
On a desktop, it can reasonably be assumed that a DVB receiver card, once fitted into the machine, will always be present.
By contrast, on a laptop which is to be used as a DVB-T TV with a USB receiver only intermittently plugged in, this is not necessarily the case.
In this case, we only want VDR to run when the DVB-T receiver is connected – to achieve this we have to switch to using Upstart as our init script.
The VDR package's own init script needs to be deactivated to prevent it from inopportunely sticking its oar in.
In Ubuntu, this can be achieved temporarily until the VDR package is next updated by using update-rc.d:
update-rc.d -f vdr remove
For Upstart to be aware that a DVB receiver has been connected, a udev rules file (see link) needs to be added under /etc/udev/rules.d: to trigger an Upstart event
SUBSYSTEM=="dvb", SUBSYSTEMS=="usb", ACTION=="add", \
KERNEL=="dvb*.dvr0", RUN+="/sbin/initctl \
--quiet emit --no-wait -e UDEV_KERNEL=$kernel \
-e UDEV_DEVPATH=$devpath dvb-device-add"
This udev rule applies only to USB DVB devices which create a DVB output device with the kernel designation dvbX.dvr0.
If the kernel reports the presence of such a device, udev uses
initctl emit
to generate the dvb-device-add
Upstart event. The --quiet
parameter tells initctl to skip the usual status messages.All the other parameters relate to the initctl command
emit
. Initctl normally waits until the triggered event has been processed in full.For a service, this means that the initctl call does not return until the service has stopped. This is avoided by using the
--no-wait
parameter, which terminates initctl as soon as the Upstart event has been triggered.The
-e
parameter permits environment variables to be passed to the Upstart job, in this case UDEV_KERNEL
containing the device's kernel name and UDEV_DEVPATH
containing the path to the device tree under Sysfs.The dvb Upstart job (see listing at the end of this article) takes care of the dvb-device-add event. Its job is to create a file, for each DVB device in the /var/run/dvb directory, which stores the Sysfs path to the device tree.
This ensures that it is subsequently possible to trace which DVB device is associated with which USB device.
Finally, the job triggers the
vdr-start
Upstart signal.The vdr Upstart job for calling VDR is trivial. It is triggered by the
vdr-start
and vdr-stop
events. VDR also needs to run in runlevels 2 to 5 only.stop on (vdr-stop
or runlevel [!2345])
exec /usr/sbin/vdr-upstart
It is the vdr-upstart script which does the spadework when it comes to calling VDR. It checks whether VDR is activated in the /etc/defaults/vdr file, loads various configuration files and calls the runvdr start script in the foreground.
Vdr-upstart is based on the init script from the VDR package.
By adding the udev rule and the two Upstart jobs, we have ensured that VDR starts only if at least one DVB device is connected.
If more than one is connected, it doesn't matter. The dvb job always terminates after creating the file containing the sysfs path in /var/run/dvb and triggering the
vdr-start
Upstart event and is processed anew for each additional device.VDR, by contrast, runs in the foreground. The job is therefore listed by
initctl list
with the status 'running'.Upstart consequently ignores the
vdr-start
start signal, preventing multiple instances of VDR from being started by multiple DVB receivers.Phantom devices
Stopping VDR turns out to be a lot more complicated that starting it. VDR needs to be terminated only when the final DVB receiver is removed.
As long as VDR is running, however, the program holds the DVB devices under /dev/dvb open, for which reason the kernel does not remove them even when the USB receiver has long been packed away – they are phantom devices.
As long as VDR is still running therefore, no udev event corresponding to a DVB device being removed occurs.
On top of this, since kernel 2.6.29, the device's Sysfs tree is only cleared out when the last device is closed.
With VDR still running, a little educated guesswork is therefore required in order to realise that the DVB receiver has already been put away.
The solution is to monitor all events affecting the removed USB devices via udev.
SUBSYSTEMS=="usb", ACTION=="remove", \
RUN+="/sbin/initctl --quiet emit --no-wait \
-e UDEV_DEVPATH=$devpath device-remove"
The dvb Upstart job also reacts to the
device-remove
event and checks the device path of the just-removed USB device against the device paths of the USB DVB receivers stored in the /var/run/dvb directory.If it finds a match, the job assumes that the DVB receiver in question has been removed and deletes the associated file in /var/run/dvb.
Only once the final DVB receiver has been removed does the dvb job trigger the
vdr-stop
Upstart event, in response VDR is stopped and udev clears out the DVB device entries downstream from /dev/dvb.Our example illustrates the flexibility which can be achieved using Upstart, but also how complicated adapting the old init scripts to the Upstart concept is.
It's likely to be a while yet before the last SysV init script is migrated to Upstart.
Udev-Job dvb.conf
env RUNDIR=/var/run/dvb
start on (dvb-device-add
or device-remove)
emits vdr-start vdr-stop
script
case "$UPSTART_EVENT" in
dvb-device-add)
mkdir -p $RUNDIR
echo ${UDEV_DEVPATH%/dvb/${UDEV_KERNEL}} >\
$RUNDIR/$UDEV_KERNEL
/sbin/initctl --quiet emit --no-wait vdr-start
;;
device-remove)
if [ -d $RUNDIR ]; then
for d in $RUNDIR/*; do
if [ -f $d ]; then
read basedev < $d
if [ -z "$basedev" -o "${UDEV_DEVPATH#${basedev}}" != \
"${UDEV_DEVPATH}" ]; then
rm -f $d
fi
fi
done
rmdir --ignore-fail-on-non-empty $RUNDIR
if [ ! -d $RUNDIR ]; then
/sbin/initctl --quiet emit --no-wait vdr-stop
fi
fi
;;
esac
done
end script
No comments:
Post a Comment