System initialization is yet another particularity of Unix systems. As explained in Chapter 2, the kernel's last initialization action is to start the init program. This program is in charge of finalizing system startup by spawning various applications and starting some key software components. In most Linux systems, init mimics System V init and is configured much the same way. In embedded Linux systems, the flexibility of System V init is overkill since such systems are rarely run as multiuser systems.
There is no actual requirement for you to have a standard init program, such as System V init, on your root filesystem. The kernel itself doesn't really care. All it needs is an application it can start once it's done initializing the system. For instance, you can add an init=PATH_TO_YOUR_INIT boot parameter to tell the kernel to use your init, which could be your main application. There are, however, drawbacks to this approach, since your application will be the one and only application the kernel ever starts. Your application would then be responsible for starting other applications on the system. Furthermore, if your application unexpectedly dies, its exit will cause a kernel panic followed by a system reboot; as would an unexpected exit of System V init. Though this may be the desired behavior in some cases, in most cases, the system is most likely rendered useless. For these reasons, it is usually much safer and useful to actually have a real init on your root filesystem.
In the following subsections, I will cover the standard init package found in most Linux distributions, the BusyBox init, and Minit, a miniature init provided by the author of embutils and diet libc.
As with other issues in Unix, init is a broad subject. There are quite a few documents that discuss Linux init at length. Chapter 5 of Running Linux describes the mainstream workstation and server init setups. Alessandro Rubini wrote a very interesting piece about init that goes into the nuances of the various initialization schemes. His article is available at http://www.linux.it/kerneldocs/init/.
The standard init package found in most Linux distributions is written by Miquel van Soorenburg and is available at ftp://ftp.cistron.nl/pub/people/miquels/sysvinit/. By using this package, you get the same flexibility to configure your target's startup as you would in configuring the startup of a workstation or a server. However, the extra functionality and flexibility requires additional space. Also, it requires that you keep track of the development of yet another software package. The package includes the following commands: halt, init, killall5, last, mesg, runlevel, shutdown, sulogin, utmpdump, and wall.
The package can be cross-compiled easily. First, download the package and uncompress it into your ${PRJROOT}/sysapps directory. For my control module, I used sysvinit Version 2.84. Then, move into the package's source directory and build it:
$ cd ${PRJROOT}/sysapps/sysvinit-2.84/src $ make CC=powerpc-linux-gcc
Replace the value of CC to match the cross-compiler for your target. With the package now built, we can install it on the target's root filesystem:
$ make BIN_OWNER="$(id -un)" BIN_GROUP="$(id -gn)" \ > ROOT=${PRJROOT}/rootfs install
This command will install all the binaries in the target's root filesystem but will fail afterward, since the Makefile tries to install the manpages on the root filesystem as well. You can modify the Makefile to avoid this, but you can also ignore the failure.
The command just shown set the BIN_OWNER and BIN_GROUP variables to be that of your own current user. By default, the Makefile attempts to install the various components and set their ownership to the root user. Since you aren't logged in as root, the Makefile would fail. The ownership of the binaries matters little on the target, since it isn't a multiuser system. If it were, however, you need to log in as root and then run the install command. Be very careful, in any case, to appropriately set the value of ROOT to point to your target's root filesystem. Otherwise, you may end up overwriting your workstation's init with a target binary. Alternatively, to avoid having to log in as root, you could still run the install command using your normal user privileges and then use the chown command as root to change the privileges on each file installed. This, however, involves going through the Makefile to find each file installed and its destination.
With init installed on your target's root filesystem, you will need to add the appropriate /etc/inittab file and fill the /etc/rc.d directory with the appropriate files. In essence, /etc/inittab will define the runlevels for your system, and the files in /etc/rc.d will define which services run on each runlevel. Table 6-5 lists init's seven runlevels and their typical use in a workstation and server distribution.
Runlevel |
Description |
---|---|
0 |
System is halted |
1 |
Only one user on system, no need for login |
2 |
Multiuser mode without NFS, command-line login |
3 |
Full multiuser mode, command-line login |
4 |
Unused |
5 |
X11, graphical user interface login |
6 |
Reboot the system |
Each runlevel corresponds to a certain set of applications. When entering runlevel 5 on a workstation, for example, init starts X11 and the user is prompted to enter his username and password using a graphical login. When switching between runlevels, the services started in the previous runlevel are shut down and the services of the new runlevel are started. In this scheme, runlevels 0 and 6 have a special meaning. Particularly, they are used for stopping the system safely. This may involve, for example, unmounting all the filesystems except the root filesystem and remounting the root filesystem read-only so that no filesystem corruption occurs.
On most workstations, the default runlevel at system startup is 5. For an embedded system, it can be set to 1, if no access control is necessary. The system's runlevel can be changed after system startup either using init or telinit, which is a symbolic link to init. In both cases, the newly issued init command communicates with the original init through the /dev/initctl fifo. To this end, we need to create a corresponding entry in our target's root filesystem:
$ mknod -m 600 ${PRJROOT}/rootfs/dev/initctl p
For more information on the format of /etc/inittab and the files found in /etc/rc.d, refer to the resources provided above.
Among the commands it supports by default, BusyBox provides init-like capabilities. As with the original mainstream init, BusyBox can handle the system's startup. BusyBox init is particularly well adapted to embedded systems, because it provides most of the init functionality an embedded system typically needs without dragging the weight of the extra features found in System V init. Also, because BusyBox is a single package, there is no need to keep track of an additional software package when developing or maintaining your system. There are cases, however, where BusyBox init may not be sufficient for your system. BusyBox init, for example, does not provide runlevel support.
Since I already described how to obtain, configure, and build BusyBox, I will limit this discussion to the setup of the init configuration files.
Because /sbin/init is a symbolic link to /bin/busybox, BusyBox is the first application to run on the target system. BusyBox identifies that the command being invoked is init and immediately jumps to the init routine.
The init routine of BusyBox carries out the following main tasks in order:
Sets up signal handlers for init.
Initializes the console(s).
Parses the inittab file, /etc/inittab.
Runs the system initialization script. /etc/init.d/rcS is the default for BusyBox.
Runs all the inittab commands that block (action type: wait).
Runs all the inittab commands that run only once (action type: once).
Once it has done this, the init routine loops forever carrying out the following tasks:
Runs all the inittab commands that have to be respawned (action type: respawn).
Runs all the inittab commands that have to be asked for first (action type: askfirst).
During console initialization, BusyBox determines whether the system was configured to run the console on a serial port (by passing console=ttyS0 as a kernel boot parameter, for instance). If so, BusyBox versions prior to 0.60.4 used to disable all virtual terminals. Since 0.60.4, however, BusyBox continues through its initialization without disabling virtual terminals. If in fact there are no virtual terminals, its attempts to start shells on some virtual terminals later will fail anyway, so there is no need to disable virtual terminals outright.
After having initialized the console, BusyBox checks for the existence of an /etc/inittab file. If no such file exists, BusyBox uses a default inittab configuration. Mainly, it sets up default actions for system reboot, system halt, and init restart. Also, it sets up actions to start shells on the first four virtual consoles, /dev/tty1 through /dev/tty4. BusyBox will complain if you haven't created these device entries.
If an /etc/inittab file is found, it is parsed and the commands it contains are recorded inside internal structures to be carried out at the appropriate time. The format of the inittab file as recognized by BusyBox is well explained in the documentation included in the BusyBox package. The documentation provided in the BusyBox package includes an elaborate example inittab file.
Each line in the inittab file follows this format:
id:runlevel:action:process
Although this format resembles that of traditional System V init, take note that the meaning of id is different in BusyBox init. Mainly, the id is used to specify the controlling tty for the process to be started. You can safely leave this entry empty if the process to be started isn't an interactive shell. Interactive shells, such as BusyBox's sh, should always have a controlling tty. BusyBox's sh will actually complain if it has no controlling tty. BusyBox completely ignores the runlevel field, so you can leave it blank. The process field specifies the path of the program to run, along with its command-line options. The action field is one of eight recognized actions to be applied to process as described in Table 6-6.
Action |
Effect |
---|---|
sysinit |
Provide init with the path to the initialization script. |
respawn |
Restart the process every time it terminates. |
askfirst |
Similar to respawn, but is mainly useful for reducing the number of terminal applications running on the system. It prompts init to display "Please press Enter to activate this console." at the console and wait for the user to press Enter before restarting the process. |
wait |
Tell init that it has to wait for the process to complete before continuing. |
once |
Run process only once without waiting for them. |
ctrlaltdel |
Run process when the Ctrl-Alt-Delete key combination is pressed. |
shutdown |
Run process when the system is shutting down. |
restart |
Run process when init restarts. Usually, the process to be run here is init itself. |
The following is a simple inittab file for my control module:
::sysinit:/etc/init.d/rcS ::respawn:/sbin/getty 115200 ttyS0 ::respawn:/control-module/bin/init ::restart:/sbin/init ::shutdown:/bin/umount -a -r
This inittab file does the following:
Sets /etc/init.d/rcS as the system initialization file.
Starts a login session on the serial port at 115200 bps.
Starts the control module's custom software initialization script.
Sets /sbin/init as the program to execute if init restarts.
Tells init to run the umount command to unmount all filesystems it can at system shutdown and set the others as read-only to preserve the filesystems.
The id is left blank in this case, because it doesn't matter to the normal operation of the commands. runlevel is also left blank, since it's completely ignored by BusyBox.
As shown earlier, however, none of these actions will take place until init runs the system initialization script. This script can be quite elaborate and can actually call other scripts. Use this script to set all the basic settings and initialize the various components of the system that need special handling. Particularly, this is a good place to:
Remount the root filesystem in read-write mode.
Mount additional filesystems.
Initialize and start networking interfaces.
Start system daemons.
Here is the initialization script for my control module:
#!/bin/sh # Remount the root filesystem in read-write (requires /etc/fstab) mount -n -o remount,rw / # Mount /proc filesystem mount /proc # Start the network interface /sbin/ifconfig eth0 192.168.172.10
The above initialization script depends on the existence of an /etc/fstab file in the target's root filesystem. I will not discuss the content and use of this file as it is already discussed in depth in Running Linux. Nevertheless, here's the /etc/fstab file I use for my control module during development:
# /etc/fstab # device directory type options # /dev/nfs / nfs defaults none /proc proc defaults
In this case, I mount the target's root filesystem on NFS to simplify development. We will discuss filesystem types in Chapter 8 and NFS mounting in Chapter 9.
Minit is part of the miniaturized tools developed by Felix von Leitner, such as diet libc and embutils. Minit is available from http://www.fefe.de/minit/.[8] As with the other tools distributed by Felix, Minit requires a properly configured diet libc.
[8] As with the other tools available from fefe.de, the last slash ("/") is important.
Minit's initialization procedure is a complete departure from the traditional System V init. Instead of using a /etc/inittab, for instance, Minit relies on the existence of a properly built /etc/minit directory. Firdtjof Busse wrote a description of how Minit operates at http://www.fbunet.de/minit.shtml. Firdtjof also provides pointers to example /etc/minit directories.
Unfortunately, as of Version 0.8, Minit is not yet as mature as the other tools provided by Felix. Its Makefile, for instance, is unable to deal with installing the various components in a root filesystem other than the host's own. For the time being, Minit is not appropriate for an embedded system, but it may be worth considering sometime in the near future.