This init
program was inspired by the one from UNIX System V, and so dates back to the mid 1980s. The version most often found in Linux distributions was written initially by Miquel van Smoorenburg. Until recently, it was considered the way to boot Linux, obviously including embedded systems, and BusyBox init
is a trimmed down version of System V init
.
Compared to BusyBox init
, System V init
has two advantages. Firstly, the boot scripts are written in a well-known, modular format, making it easy to add new packages at build time or runtime. Secondly, it has the concept of runlevels, which allow a collection of programs to be started or stopped in one go, by switching from one runlevel to another.
There are 8 runlevels numbered from 0 to 6, plus S:
Levels 1 to 5 can be used as you please. On desktop Linux distributions, they are conventionally assigned as follows:
The init
program starts the default runlevel
given by the initdefault
line in /etc/inittab
. You can change the runlevel at runtime using the command telinit [runlevel]
which sends a message to init
. You can find the current runlevel, and the previous one, by using the runlevel
command. Here is an example:
# runlevel N 5 # telinit 3 INIT: Switching to runlevel: 3 # runlevel 5 3
On the first line, the output from runlevel
is N 5
, meaning that there is no previous runlevel because the runlevel
has not changed since booting, and the current runlevel
is 5
. After changing the runlevel
, the output is 5 3
showing that there has been a transition from 5
to 3
. The halt
and reboot
commands switch to runlevels of 0
and 6
respectively. You can override the default runlevel
by giving a different one on the kernel command line as a single digit from 0
to 6
, or S
for single user mode. For example, to force the runlevel
to be for a single user, you would append S
to the kernel command line and it would look something like this:
console=ttyAMA0 root=/dev/mmcblk1p2 S
Each runlevel has a number of scripts that stop things, called kill
scripts, and another group that starts things, the start
scripts. When entering a new runlevel
, init
first runs the kill
scripts and then the start
scripts. Running daemons which have neither a start
script nor a kill
script in the new runlevel
are sent a SIGTERM
signal. In other words, the default action on switching runlevel
is to terminate the daemons unless told to do otherwise.
In truth, runlevels are not used that much in embedded Linux: most devices simply boot to the default runlevel
and stay there. I have a feeling that it is partly because most people are not aware of them.
System V init
is an option in Buildroot and the Yocto Project. In both cases, the init scripts have been stripped of any bash specifics, so they work with the BusyBox ash shell. However, Buildroot cheats by replacing the BusyBox init
program with SystemV init
and adding inittab
that mimics the behavior of BusyBox. Buildroot does not implement runlevels except that switching to levels 0 or 6 halts or reboots the system.
Next, let's look at some of the details. The following examples are taken from the fido version of the Yocto Project. Other distributions may implement the init
scripts a little differently.
The init
program begins by reading /etc/inttab
, which contains entries that define what happens at each runlevel
. The format is an extended version of the BusyBox inittab
that I described in the preceding section, which is not surprising because BusyBox borrowed it from System V in the first place!
The format of each line in inittab
is as follows:
id:runlevels:action:process
The fields are shown here:
id
: A unique identifier of up to four characters.runlevels
: The runlevels for which this entry should be executed. (This was left blank in the BusyBox inittab
)action
: One of the keywords given as follows.process
: The command to run.The actions are the same as for BusyBox init
: sysinit
, respawn
, once
, wait
, restart
, ctrlaltdel
, and shutdown
. However, System V init
does not have askfirst
, which is specific to BusyBox.
As an example, this is the complete inttab
supplied by the Yocto Project target core-image-minimal:
# /etc/inittab: init(8) configuration. # $Id: inittab,v 1.91 2002/01/25 13:35:21 miquels Exp $ # The default runlevel. id:5:initdefault: # Boot-time system configuration/initialization script. # This is run first except when booting in emergency (-b) mode. si::sysinit:/etc/init.d/rcS # What to do in single-user mode. ~~:S:wait:/sbin/sulogin # /etc/init.d executes the S and K scripts upon change # of runlevel. # # Runlevel 0 is halt. # Runlevel 1 is single-user. # Runlevels 2-5 are multi-user. # Runlevel 6 is reboot. l0:0:wait:/etc/init.d/rc 0 l1:1:wait:/etc/init.d/rc 1 l2:2:wait:/etc/init.d/rc 2 l3:3:wait:/etc/init.d/rc 3 l4:4:wait:/etc/init.d/rc 4 l5:5:wait:/etc/init.d/rc 5 l6:6:wait:/etc/init.d/rc 6 # Normally not reached, but fallthrough in case of emergency. z6:6:respawn:/sbin/sulogin AMA0:12345:respawn:/sbin/getty 115200 ttyAMA0 # /sbin/getty invocations for the runlevels. # # The "id" field MUST be the same as the last # characters of the device (after "tty"). # # Format: # <id>:<runlevels>:<action>:<process> # 1:2345:respawn:/sbin/getty 38400 tty1
The fist entry, id:5:initdefault
, sets the default runlevel
to 5
. The next entry, si::sysinit:/etc/init.d/rcS
, runs the script rcS
at boot up. There will be more about this later. A little further on, there is a group of six entries beginning with l0:0:wait:/etc/init.d/rc 0
. They run the script /etc/init.d/rc
each time there is a change in the runlevel: this script is responsible for processing the start
and kill
scripts. There is an entry for runlevel S
which runs the single-user login program.
Towards the end of inittab
, there are two entries that run a getty
daemon to generate a login prompt on the devices /dev/ttyAMA0
and /dev/tty1
when entering runlevels 1 through to 5, thereby allowing you to log on and get an interactive shell:
AMA0:12345:respawn:/sbin/getty 115200 ttyAMA0 1:2345:respawn:/sbin/getty 38400 tty1
The device ttyAMA0
is the serial console on the ARM Versatile board we are emulating with QEMU, it will be different for other development boards. Tty1 is a virtual console which is often mapped to a graphical screen if you have built your kernel with CONFIG_FRAMEBUFFER_CONSOLE or VGA_CONSOLE
. Desktop Linux usually spawns six getty
processes on virtual terminals 1 to 6, which you can select with the key combination Ctrl + Alt + F1 through Ctrl + Alt + F6, with virtual terminal 7 reserved for the graphical screen. Virtual terminals are seldom used on embedded devices.
The script /etc/init.d/rcS
that is run by the sysinit
entry does little more than enter runlevel S
:
#!/bin/sh [...] exec /etc/init.d/rc S
Hence, the first run level entered is S
, followed by the default runlevel
of 5
. Note that runlevel
S
is not recorded and is never displayed as a prior runlevel by the runlevel
command.
Each component that needs to respond to a runlevel
change has a script in /etc/init.d
to perform that change. The script should expect two parameters: start
and stop
. I will give an example of this later.
The runlevel
handling script, /etc/init.d/rc
, takes the runlevel
it is switching to as a parameter. For each runlevel
, there is a directory named rc<runlevel>.d
:
# ls -d /etc/rc* /etc/rc0.d /etc/rc2.d /etc/rc4.d /etc/rc6.d /etc/rc1.d /etc/rc3.d /etc/rc5.d /etc/rcS.d
There you will find a set of scripts beginning with a capital S
followed by two digits and you may also find scripts beginning with a capital K
. These are start
and kill
scripts: Buildroot uses the same idea, borrowed from here:
# ls /etc/rc5.d S01networking S20hwclock.sh S99rmnologin.sh S99stop-bootlogd S15mountnfs.sh S20syslog
These are in fact symbolic links back to the appropriate script in init.d
. The rc
script runs all the scripts beginning with a K
first, adding the stop
parameter , and then runs those beginning with an S
adding the start
parameter . Once again, the two digit code is there to impart the order in which the scripts should run.
Imagine that you have a program named simpleserver
which is written as a traditional Unix daemon, in other words, it forks and runs in the background. You will need an init.d
script like this:
#! /bin/sh case "$1" in start) echo "Starting simpelserver" start-stop-daemon -S -n simpleserver -a /usr/bin/simpleserver ;; stop) echo "Stopping simpleserver" start-stop-daemon -K -n simpleserver ;; *) echo "Usage: $0 {start|stop}" exit 1 esac exit 0
Start-stop-daemon
is a helper function that makes it easier to manipulate background processes such as this. It originally came from the Debian installer package, dpkg
, but most embedded systems use the one from BusyBox. It starts the daemon with the -S
parameter, making sure that there is never more than one instance running at any one time and it finds the daemon by name with -K
and sends a signal, SIGTERM
, by default. Place this script in /etc/init.d/simpleserver
and make it executable.
Then, add symlinks
from each of the run levels that you want to run this program from, in this case, only the default runlevel
, 5
:
# cd /etc/init.d/rc5.d # ln -s ../init.d/simpleserver S99simpleserver
The number 99
means that this will be one of the last programs to be started. Bear in mind that there may be other links beginning S99
, in which case the rc
script will just run them in lexical order.
It is rare in embedded devices to have to worry too much about shutdown operations, but if there is something that needs to be done, add kill symlinks
to levels 0 and 6:
# cd /etc/init.d/rc0.d # ln -s ../init.d/simpleserver K01simpleserver # cd /etc/init.d/rc6.d # ln -s ../init.d/simpleserver K01simpleserver
You can interact with the scripts in /etc/init.d
by calling them directly with, for example, the syslog
script which controls the syslogd
and klogd
daemons:
# /etc/init.d/syslog --help Usage: syslog { start | stop | restart } # /etc/init.d/syslog stop Stopping syslogd/klogd: stopped syslogd (pid 198) stopped klogd (pid 201) done # /etc/init.d/syslog start Starting syslogd/klogd: done
All scripts implement start
and stop
and should implement help
. Some implement status
as well, which will tell you whether the service is running or not. Mainstream distributions that still use System V init
have a command named service to start and stop services and hide the details of calling the scripts directly.