Reprinted with permission of the Linux Journnal
Init is the driving force that keeps our Linux box alive, and it is the one that can put it to death. This article is meant to summarize why Init is so powerful and how you can instruct it to behave differently from its default behaviour (yes, Init is powerful, but the superuser rules over Init).
In Unix parlance, the word ``init'' doesn't identify a specific program, but rather a class of programs. The name ``init'' is generically used to call the first process that is executed at system boot -- actually, the only process that is executed at system boot. When the kernel is done with setting up the computer's hardware, it invokes init and gives up controlling the computer. From now on the kernel only processes system calls, without taking any decisional role in system operation. After the kernel is done mounting the root filesystem, everything is controlled by init.
Currently, there are several choices as far as init is concerned: you can use the now-classic program that comes in the SysVinit package by Miquel van Smoorenburg, or simpleinit by Peter Orbaek (found in the source package of util-linux), or a simple shell script (like the one shown in this article, which has a lot of functionality less than any C-language implementation). If you set up embedded systems you can even just run the target application like it was init. Insane people who dislike multitasking could even port command.com to Linux and run it as the init process, although you won't ever be able to restrict yourself to 640k when running a Linux kernel.
No matter what is the program you choose, it needs to be accessed with a pathname of /sbin/init, /etc/init or /bin/init, because these pathnames are compiled in the kernel. If neither of them can be executed than the system is severely broken, and the kernel will spawn a root shell to allow interactive recovery (i.e., /bin/sh is used as an init process).
To achieve maximum flexibility, kernel developers offered a way to
select a different pathname for the init process. The kernel accepts a
command line option of init=
exactly for that purpose. Kernel
options can be passed interactively at boot time, or you can use the
append=
directive in /etc/lilo.conf. Silo, Milo, Loadlin and
other loaders allow specifying kernel options as well.
As you may imagine, the easiest way to get root access to a Linux box
is by typing init=/bin/sh
to the Lilo prompt. Note that this
is not a security hole per se, because the real security hole
here is physical access to the console. If you are concerned about the
init=
option, Lilo can prevent interaction using its own
password protection.
Ok, so init is a generic naming, and almost anything can be used as init. The question now is what is a real init supposed to do. Being the first (and only) process spawned by the kernel, the task of init consists in spawning every other process in the system. This usually includes the various daemons used in system operation as well as any login session on the text console. Init is also expected to restart some of its child processes as soon as they exit. This typically applies to the login sessions running on the text consoles: as soon as you logout the system should run another ``getty'' to allow starting another session. Init should also collect dead processes and dispose of them. In the Unix abstraction of processes, a process can't be removed from the system table unless its death is reported to its parent (or another ancestor in case its parent doesn't exist anymore). Whenever a process dies, by calling exit or otherwise, it remains around in the state of a zombie process until someone collects it. Init, being the ancestor of any other process, is expected to collect the exit status of any orphaned zombie process -- note that every well-written program should reap its own children, zombies only exist when some program is misbehaving. If init wouldn't collect zombies, lazy programmers could easily consume system resources and hang the system by filling the process table. The last task of Init is handling system shutdown. The init program must stop any process and unmount all the filesystems when the superuser tells that shutdown time has arrived. The shutdown executable, actually, doens't do anything but tell init that anything is over. As we have seen, the task of init is not too hard to implement, and a shell script could well perform most of the required tasks. Note that every decent shell collects its dead children, so this is not a problem with shell scripts. What real init implementations add to the simple shell script approach is a greater control over system activity, and thus a huge benefit in overall flexibility. This article will now proceed by showing different implementations of the init concept, in ascending order of complexity.
As suggested above, the shell can be used as an init program.
Using a bare shell, in the init=/bin/sh
way, only opens a
root shell in a completely unconfigured system. This section shows how
a shell script can perform all of the tasks you need to have a minimal
running system. This kind of tiny init can be used in embedded system
or similar reduced environments, where you must squeeze out every
single byte out of the system. Note that the most radical approach to
embedded systems is directly running the target application as the
init process; this results in a closed system (no way for the
administrator to interact should problems arise), but it sometimes
suites the setup. The typical example of non-init-driven Linux system
is the installation environment of most modern distributions, where
/sbin/init is a symbolic link to the installation program.
#!/bin/sh # avoid typing full pathnames export PATH=/usr/bin:/bin:/sbin:/usr/sbin # remount root read-write, and mount all mount -n -o remount,rw / mount -a swapon -a # system log syslogd klogd # start your lan modprobe eth0 2> /dev/null ifconfig eth0 192.168.0.1 route add 192.168.0.0 eth0 route add default gw 192.168.0.254 # start lan services inetd sendmail -bd -q30m # Anything else: crond, named, ... # And run one getty with a sane path export PATH=/usr/bin:/bin /sbin/mingetty tty1 Listing 1To make a long story short, Listing 1 shows a script that can perform acceptably as init. The script is very short and incomplete; in particular, note that it only runs one getty, which isn't restarted when it terminates. Be careful if you try to use this script, as each Linux distribution chooses its own flavour of getty. Try
grep
getty /etc/inittab
to know what you have and how to call it.
The script shown has another misfeature: it doesn't deal with system
shutdown. Adding shutdown support, however, is pretty easy; just bring
everything down after the interactive shell terminates. Adding the
text shown in Listing2 at the end of Listing1 does the trick.
# killa anything you started killall inetd killall sendmail killall klogd killall syslogd # kill anything else kill -TERM -1 sleep 2 kill -KILL -1 # release the disks swapoff -a umount -a mount -n -o remount,ro / echo "The system is halted" exit Listing 2Whenever you boot with a plain
init=/bin/sh
, you should at
least remount the root filesystem before you'll be able to do
anything; you should also remember to umount -a
before pressing
ctrl-alt-del, because the shell doesn't intercept the three-finger
salute.
The util-linux package includes a C version of an init program. It's quite featured and can work well for most personal systems, although it doesn't offer the huge amount of configurability offered by the SysVinit package, which is the default on modern distributions. The role of simpleinit (which should be called init to work properly, as suggested above) is very similar to the shell script just shown, with the added capability of managing single-user mode and iterative invocation of console sessions. It also correctly processes shutdown requests. Simpleinit is interesting to look at, and well documented too, so you might just enjoy reading the documentation; I suggest using the source distribution of util-linux to get up to date information. The implementation of simpleinit is actually simple, like its name suggests. The program executes a shell script (/etc/rc) and parses a configuration file to know what processes need to be respawned. The configuration file is called /etc/inittab, like the one used by the full-featured init; note however that its format is different. If you plan to install simpleinit in your system (which most likely already includes SysVinit) you must proceed with great care, and be prepared to reboot with a kernel argument of ``init=/bin/sh'' to recover from instable situations.
Most Linux distributions come with the version of init written by
Miquel van Smoorenburg, this version is similar the approach taken by
System-V (five) Unix.
The main idea here is that the user of a computer system can wish to
operate his box in one of several different ways (not just single-user
and multi-user). Although this feature is not usually exploited, it's
not so crazy as you might imagine. When the computer is shared by two
or more people in the family, different setups can be needed; a
network server and a standalone playstation can happily coexist in the
same computer as different runlevels. And although I'm the only user
of my laptop, I sometimes want a network server (through PLIP) and
sometimes a netless environment, to save resources when I'm working on
the train.
Each operating mode is called ``runlevel'', and you can choose the
runlevel to use either at boot or at runtime. The main configuration
file for init is called /etc/inittab, which defines what to do at
boot, when entering a runlevel or when switching from one runlevel to
another. It also tells how handle the three-finger salute and how to
deal with power fails, although you'll need a power-daemon and an UPS
to benefit from this feature.
The inittab file is organized by lines, where each line is made up of
several colon-separated fields:
``id:
runlevel:
action:
command''
The inittab(5)
man page is well written and comprehensive
like a man page should be, but I feel worth repeating here one of its
examples: a stripped-down /etc/inittab that implements the
same features and misfeatures of the shell script shown above:
id:1:initdefault: rc::bootwait:/etc/rc 1:1:respawn:/sbin/getty 9600 tty1
This simple inittab tells init that the default runlevel is ``1'',
that at system boot it must execute /etc/rc waiting for its
completion, and that when in runlevel 1 it must respawn forever the
command ``/sbin/getty 9600 tty1
''. As you may suspect, you're
not expected to test this out, because it doesn't handle the shutdown
procedure.
Before proceeding further, however, I must fill a pair of gaps
I left behinf. Let'd reply to the questions you keep asking:
Naturally, the typical /etc/inittab file is much more featured than the three-liner shown above. Although ``bootwait'' and ``respawn'' are the most important actions, in order to deal with several issues related to system management several other actions exist, but I won't detail them here. Note that SysVinit can deal with ctrl-alt-del whereas the versions of init shown earlier didn't catch the three-finger salute (i.e., the machine would reboot if you press the key sequence). Who is interested in how this is done can check sys_reboot in /usr/src/linux/kernel/sys.c (if you look in the code you'll note the use of a magic number of 672274793: can you imagine why Linus chose this very number? I think I know what it is, but you'll enjoy finding it by yourself). So, let's see how a fairly complete /etc/inittab can take care of everything that's needed to handle the needs of a system's lifetime, including different runlevels. Although the magic of the game is always on show in /etc/inittab, you can choose between several different approaches to system configuration, the simplest being the three-liner shown above. In my opinion, two approaches are worth discussing in some detail: I'll call them ``the Slackware way'' and ``the Debian way'' from two renown Linux distributions that chose to follow them.
Although it's quite some time I don't install Slackware, the documentation included in SysVinit-2.74 tells that it still works the same, less featured but much faster than the Debian way described later. My personal 486 box runs a Slackware-like /etc/inittab just for the speed benefit. The core of an /etc/inittab as used by a Slackare system is shown in Listing 3.
# Default runlevel. id:5:initdefault: # System initialization (runs when system boots). si:S:sysinit:/etc/rc.d/rc.S # Script to run when going single user (runlevel 1). su:1S:wait:/etc/rc.d/rc.K # Script to run when going multi user. rc:2345:wait:/etc/rc.d/rc.M # What to do at the "Three Finger Salute". ca::ctrlaltdel:/sbin/shutdown -t5 -rf now # Runlevel 0 halts the system. l0:0:wait:/etc/rc.d/rc.0 # Runlevel 6 reboots the system. l6:6:wait:/etc/rc.d/rc.6 # Runlevel 1,2,3,5 have text login c1:1235:respawn:/sbin/agetty 38400 tty1 linux # Runlevel 4 is X only x1:4:wait:/etc/rc.d/rc.4 # But run a getty on /dev/tty4 just in case... c4:4:respawn:/sbin/agetty 38400 tty1 linux Listing 3You should not rightahead that the runlevels 0, 1 and 6 have a predefined meaning. This is hardwired into the init command (or better, into the shutdown command, part of the same package). Whenever you want to halt or reboot the system, init is told to switch to runlevel 0 or 6, thus executing /etc/rc.d/rc.0 or /etc/rc.d/rc.6. This works flawlessly because whenever init switches to a different runlevel it stops respawning any task that is not defined for the new runlevel; actually, it even kills the running copy of the task (in this case, the active /sbin/agetty). Configuring this setup is pretty simple, as the role of the different files is pretty clear:
# The default runlevel. id:2:initdefault: # This is run first si::sysinit:/etc/init.d/boot # What to do in single-user mode. ~~:S:wait:/sbin/sulogin # Enter each runlevel 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 # getty 1:2345:respawn:/sbin/getty 38400 tty1 Listing 3The Red Hat setup featuring exactly he same structure for system initialization but uses different pathnames; you'll be able to map one structure over the other. Let's list the role of the different files:
/etc/rc$runlevel.d
invoking any script that appears in the
directory.
A stripped down version of ``rc'' would look like the following:
#!/bin/sh level=$1 cd /etc/rc.d/rc$level.d for i in K*; do $i stop done for i in S*; do $i start done
What does it mean? It means that /etc/rc2.d (for example) includes
files called K*
and S*
; the former identify services
that must be stopped, and the latter identify services that must be
started.
Ok, but I didn't tell whence do K* and S* come from. This is the smart
part of it all: every software package that needs to be run for some
runlevel adds itself to all the /etc/rc?.d directories, either as a
``start'' entry or as a ``kill'' (stop) entry. To avoid code
duplication, the package installs a script in /etc/init.d and several
symbolic links from the various /etc/rc?.d.
To show a real-life example, lets's see what is included in two
``rc'' directories of debian:
rc1.d: K11cron K20sendmail K12kerneld K25netstd_nfs K15netstd_init K30netstd_misc K18netbase K89atd K20gpm K90sysklogd K20lpd S20single K20ppp rc2.d: S10sysklogd S20sendmail S12kerneld S25netstd_nfs S15netstd_init S30netstd_misc S18netbase S89atd S20gpm S89cron S20lpd S99rmnologin S20ppp
This shows how entering runlevel 1 (single-user) kills all the
services and start a ``single'' script; entering runlevel 2 (the
default level) starts all the services. The number that appears near
the K or the S is used to order the birth of death of the various
services, as the shell expands wildcards appearing in /etc/init.d/rc
in ascii order. Inovking an ls -l
command confirms that all
of these files are symlinks, like the following:
rc2.d/S10sysklogd -> ../init.d/sysklogd rc1.d/K90sysklogd -> ../init.d/sysklogd
To summarize, adding a new software package in this environment means adding a file in /etc/init.d and the proper symbolic link from each of the /etc/rc?.d directories. To make different runlevels behave differently (2, 3, 4 and 5 are configured in the same way by default), just remove or add symlinks in the proper /etc/rc?.d directories. If this scares you as too difficult, not all is lost. If you use Red Hat (or Slackware), you can think of /etc/rc.d/rc.local like it was autoexec.bat -- if you are old enough to remember about the pre-Linux age. If you run Debian, you could create /etc/rc2.d/S95local and use it as your own rc.local; note however that Debian is very clean about system setup and I would have better not cast in print such a heresy. You know, powerful and trivial seldom match; you have been warned.
As I write this article, Debian 2.0 is being released to the public,
and I suspect it will be of wide use when you read it.
Although the structure of system initialization is the same, it's
interesting to note that the developers managed to make it faster.
Instead of running the files in /etc/rc2.d, the script /etc/init.d/rc
can now run them in the same process, without spawning another shell;
whether to execute them or source them is controlled by the filename:
executables whoe name ends in .sh
are sourced, the other ones
are executed. The trick is shown in the following few lines, and the
speed benefit is non-negligible:
case ``$i'' in *.sh) # Source shell script for speed. ( trap - INT QUIT TSTP set start; . $i ) ;; *) # No sh extension, so fork subprocess. $i start ;; esac
Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved.