Linux booting, init and rc scripts
 


Boot Process

The following are the 6 high level stages of a typical Linux boot process.

1. BIOS

  • BIOS stands for Basic Input/Output System
  • Performs some system integrity checks
  • Searches, loads, and executes the boot loader program.
  • It looks for boot loader in floppy, cd-rom, or hard drive. You can press a key (typically F12 of F2, but it depends on your system) during the BIOS startup to change the boot sequence.
  • Once the boot loader program is detected and loaded into the memory, BIOS gives the control to it.
  • So, in simple terms BIOS loads and executes the MBR boot loader.

2. MBR

  • MBR stands for Master Boot Record.
  • It is located in the 1st sector of the bootable disk. Typically /dev/hda, or /dev/sda
  • MBR is less than 512 bytes in size. This has three components 1) primary boot loader info in 1st 446 bytes 2) partition table info in next 64 bytes 3) mbr validation check in last 2 bytes.
  • It contains information about GRUB (or LILO in old systems).
  • So, in simple terms MBR loads and executes the GRUB boot loader.

3. GRUB

#boot=/dev/sda
default=0
timeout=5
splashimage=(hd0,0)/boot/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.18-194.el5PAE)
          root (hd0,0)
          kernel /boot/vmlinuz-2.6.18-194.el5PAE ro root=LABEL=/
          initrd /boot/initrd-2.6.18-194.el5PAE.img
  • GRUB stands for Grand Unified Bootloader.
  • If you have multiple kernel images installed on your system, you can choose which one to be executed.
  • GRUB displays a splash screen, waits for few seconds, if you don’t enter anything, it loads the default kernel image as specified in the grub configuration file.
  • GRUB has the knowledge of the filesystem (the older Linux loader LILO didn’t understand filesystem).
  • Grub configuration file is /boot/grub/grub.conf (/etc/grub.conf is a link to this). The following is sample grub.conf of CentOS.
  • As you notice from the above info, it contains kernel and initrd image.
  • So, in simple terms GRUB just loads and executes Kernel and initrd images.

4. Kernel

  • Mounts the root file system as specified in the “root=” in grub.conf
  • Kernel executes the /sbin/init program
  • Since init was the 1st program to be executed by Linux Kernel, it has the process id (PID) of 1. Do a ‘ps -ef | grep init’ and check the pid.
  • initrd stands for Initial RAM Disk.
  • initrd is used by kernel as temporary root file system until kernel is booted and the real root file system is mounted. It also contains necessary drivers compiled inside, which helps it to access the hard drive partitions, and other hardware.

5. Init

  • Looks at the /etc/inittab file to decide the Linux run level.
  • Following are the available run levels
    • 0 – halt
    • 1 – Single user mode
    • 2 – Multiuser, without NFS
    • 3 – Full multiuser mode
    • 4 – unused
    • 5 – X11
    • 6 – reboot
  • Init identifies the default initlevel from /etc/inittab and uses that to load all appropriate program.
  • Execute ‘grep initdefault /etc/inittab’ on your system to identify the default run level
  • If you want to get into trouble, you can set the default run level to 0 or 6. Since you know what 0 and 6 means, probably you might not do that.
  • Typically you would set the default run level to either 3 or 5.

6. Runlevel programs

  • When the Linux system is booting up, you might see various services getting started. For example, it might say “starting sendmail …. OK”. Those are the runlevel programs, executed from the run level directory as defined by your run level.
  • Depending on your default init level setting, the system will execute the programs from one of the following directories.
    • Run level 0 – /etc/rc.d/rc0.d/
    • Run level 1 – /etc/rc.d/rc1.d/
    • Run level 2 – /etc/rc.d/rc2.d/
    • Run level 3 – /etc/rc.d/rc3.d/
    • Run level 4 – /etc/rc.d/rc4.d/
    • Run level 5 – /etc/rc.d/rc5.d/
    • Run level 6 – /etc/rc.d/rc6.d/
  • Please note that there are also symbolic links available for these directory under /etc directly. So, /etc/rc0.d is linked to /etc/rc.d/rc0.d.
  • Under the /etc/rc.d/rc*.d/ directories, you would see programs that start with S and K.
  • Programs starts with S are used during startup. S for startup.
  • Programs starts with K are used during shutdown. K for kill.
  • There are numbers right next to S and K in the program names. Those are the sequence number in which the programs should be started or killed.
  • For example, S12syslog is to start the syslog deamon, which has the sequence number of 12. S80sendmail is to start the sendmail daemon, which has the sequence number of 80. So, syslog program will be started before sendmail.

 

INIT

Init's job is "to get everything running the way it should be" once the kernel is fully running. Essentially it establishes and operates the entire user space. This includes checking and mounting file systems, starting up necessary user services, and ultimately switching to a user-environment when system startup is completed. It is similar to the Unix and BSD init processes, from which it derived, but in some cases has diverged or became customized. In a standard Linux system, Init is executed with a parameter, known as a runlevel, that takes a value from 1 to 6, and that determines which subsystems are to be made operational. Each runlevel has its own scripts which codify the various processes involved in setting up or leaving the given runlevel, and it is these scripts which are referenced as necessary in the boot process. Init scripts are typically held in directories with names such as "/etc/rc...". The top level configuration file for init is at /etc/inittab.

During system boot, it checks whether a default runlevel is specified in /etc/inittab, and requests the runlevel to enter via the system console if not. It then proceeds to run all the relevant boot scripts for the given runlevel, including loading modules, checking the integrity of the root file system (which was mounted read-only) and then remounting it for full read-write access, and sets up the network.

After it has spawned all of the processes specified, init goes dormant, and waits for one of three events to happen:- processes it started to end or die, a power failure signal, or a request via /sbin/telinit to further change the runlevel.

This applies to SysV-style init. Other init binaries, such as systemd or Upstart, may behave differently.

 

how init scripts are used

init scripts are essentially just shell scripts with options for performing basic actions on a service. They can be invoked manually, or automatically by the system. To invoke an init script manually, the syntax is:

/etc/init.d/service parameter

For example, to start the Apache2 service, execute:

# /etc/init.d/apache2 start
Starting httpsd2 (prefork)                                            done

There are several default parameters which can be used with an init script. They are explained in Table 1, which has been taken from the SUSE Linux Professional Administration Guide.

Table 1: The Standard init Script Parameters

start

Start service.

stop

Stop service.

restart

If the service is running, stop it then restart it. If it is not running, start it.

reload

Reload the configuration without stopping and restarting the service.

force-reload

Reload the configuration if the service supports this. Otherwise, do the same as if restart had been given.

status

Show the current status of service.

For a service to be included in a runlevel, it must be symbolically linked into the desired runlevel (found in /etc/rc.d/rc0.d/ through /etc/rc.d/rc6.d/). These links specify which services to start and stop as each runlevel is entered. The links are named in such a way that they control the order the scripts are started and stopped. First, the name starts with an S or K. An S indicates the script is to receive the 'start' parameter, and 'K' indicates it should receive the 'stop' parameter. Next, there is a two-digit number. This specifies the order a service should be started/stopped in. Lastly, the name of the service is listed.

For example, to start service /etc/init.d/foo in runlevel 5 the link may be /etc/init.d/rc5.d/S10foo -> /etc/init.d/foo. If foo is dependent on any other services, they should be numbered lower than 10. These links are automatically managed by the tool insserv and the YaST runlevel editor based on information contained in the init scripts.

In addition, a init script should also always have a symbolic link to /sbin/rc. . For example, foo should have the link /sbin/rcfoo -> /etc/init.d/foo.

An init script

Novell/SUSE Linux ships with an example init script, located at /etc/rc.d/skeleton. It has several critical sections to edit, the most important of which are highlighted here. For more information see the /etc/init.d/skeleton file--it is very well commented.

This example will build an init script for the sample service bar, which has a binary located at /usr/bin/bar. It uses the configuration file /etc/bar.cfg, and is a network-dependent service. This script will only show the most basic functions of an init script. For more advanced information, check the /etc/init.d/skeleton file, or look through other scripts contained in /etc/init.d/.

  1. Define the interpreter. You need to specify which interpreter will be used to execute this script. The first line of an init script should aways be:

    #! /bin/sh
  2. Author and other useful information. It is always good to place contact information or anything else which may be useful at the beginning of a script.

    # Copyright (c) 2005 John Doe
    # All rights reserved.
    #
    # Author: John Doe, 2005
    #
    # /etc/init.d/bar
    #   and its symbolic link
    # /usr/sbin/rcbar
  3. Populate "INIT INFO" The entries in the "INIT INFO" section are comments which are read by insserv and the YaST runlevel editor. They specify what this service should be known as to other init scripts, what other services are required for this one to run, and what runlevels to be active in. The following entry specifies that other init scripts can refer to this service as $bar. It should be started after the network is brought up, and should be active in runlevels 3 and 5. It should be stopped in any other.

    ### BEGIN INIT INFO
    # Provides:          bar
    # Required-Start:    $network
    # Required-Stop:
    # Default-Start:     3 5
    # Default-Stop:      0 1 2 6
    # Short-Description: bar daemon, providing a useful network service
    # Description:       The bar daemon is a sample network
    #	service.  We want it to be active in runlevels 3
    #	and 5, as these are the runlevels with the network
    #	available.
    ### END INIT INFO
  4. Check for the binary and configuration files. Load the configuration variables. It is probably a good idea to have the script check for the binary. Configuration files, if any, should be checked, and the variables from them should be loaded into environment variables.

    # Check for missing binaries
    BAR_BIN=/usr/bin/bar
    test -x $BAR_BIN || { echo "$BAR_BIN not installed";
            if [ "$1" = "stop" ]; then exit 0;
            else exit 5; fi; }
    
    # Check for existence of needed config file and read it
    BAR_CONFIG=/etc/bar.cfg
    test -r $BAR_CONFIG || { echo "$BAR_CONFIG not existing";
            if [ "$1" = "stop" ]; then exit 0;
            else exit 6; fi; }
    
    # Read config
    . $BAR_CONFIG
  5. Load and reset the /etc/rc_status script for this service. This script is used to oversee and report the status of the given service.

    # Load the rc.status script for this service.
    . /etc/rc.status
    
    # Reset status of this service
    rc_reset
  6. Define the control parameters for the service. This is were the start, stop, and other control parameters for bar are defined. In general, the command /usr/bin/bar should be started and stopped using startproc and killproc respectively. The status of a process can be checked with checkproc.

    • startproc: startproc starts a process, specified by a full path to the executable. It can take many parameters such as the priority the process should be executed with, where to redirect STDIN and STDOUT (i.e. where the log file is), and which user to execute the process as. startproc also writes a file out to /var/run/.pid which contains the process number assigned to the service. See the startproc (8) manpage for more information.

    • killproc: killproc kills a process which is given by a full path to the executable. By default, any process which is an instance of this executable is killed. killproc takes parameters such as which process to not kill if there is more than one running (given by specifying one of the .pid files created by startproc), and what signal to send the process (by default a SIGTERM is sent, then a SIGKILL if the process does not die within 5 seconds).
    • checkproc: checkproc works like killproc and startproc, returning the current status of a given process, which is specified by the full path to the executable from which the process was spawned.

    the command rc_status reads the return values of startproc, killproc, and checkproc and prints that status out to the user. For example, a green 'done' if the service started successfully, or a red 'failed' if it did not.

    Using startproc, killproc, checkproc, and rc_status, the rest of the section is very straightforward:

      case "$1" in
        start)
            echo -n "Starting bar "
            ## Start daemon with startproc(8). If this fails
            ## the return value is set appropriately by startproc.
            startproc $BAR_BIN
    
            # Remember status and be verbose
            rc_status -v
            ;;
        stop)
            echo -n "Shutting down bar "
            ## Stop daemon with killproc(8) and if this fails
            ## killproc sets the return value according to LSB.
    
            killproc -TERM $BAR_BIN
    
            # Remember status and be verbose
            rc_status -v
            ;;
        restart)
            ## Stop the service and regardless of whether it was
            ## running or not, start it again.
            $0 stop
            $0 start
    
            # Remember status and be quiet
            rc_status
            ;;
        reload)
            # If it supports signaling:
            echo -n "Reload service bar "
            killproc -HUP $BAR_BIN
            #touch /var/run/BAR.pid
            rc_status -v
    
            ## Otherwise if it does not support reload:
            #rc_failed 3
            #rc_status -v
            ;;
        status)
            echo -n "Checking for service bar "
            ## Check status with checkproc(8), if process is running
            ## checkproc will return with exit status 0.
    
            # Return value is slightly different for the status command:
            # 0 - service up and running
            # 1 - service dead, but /var/run/  pid  file exists
            # 2 - service dead, but /var/lock/ lock file exists
            # 3 - service not running (unused)
            # 4 - service status unknown :-(
            # 5--199 reserved (5--99 LSB, 100--149 distro, 150--199 appl.)
    
            # NOTE: checkproc returns LSB compliant status values.
            checkproc $BAR_BIN
            # NOTE: rc_status knows that we called this init script with
            # "status" option and adapts its messages accordingly.
            rc_status -v
            ;;
        *)
            ## If no parameters are given, print which are avaiable.
            echo "Usage: $0 {start|stop|status|restart|reload}"
            exit 1
            ;;
    esac
  7. End the /etc/rc.status script.

    rc_exit
    Remember to make the new script executable. For example:
    chmod 744 /etc/init.d/bar
    Also, remember to make a symbolic link to /etc/init/bar in /sbin:
    ln -s /etc/init.d/bar /sbin/rcbar


enabling an init script

Novell/SUSE Linux provides several tools for managing init scripts. The most simple is innserv. For example, to have /etc/init.d/bar loaded with the default runlevels specified in the "INIT INFO" section, execute:

# innserv /etc/init.d/bar

To have it removed, type

# innserv -r /etc/init.d/bar

Another tool is the runlevel editor in YaST. It can be launched from the command-line with 'yast runlevel', or can be found under System. In simple mode, enabling a service places it in the default runlevels. In expert mode, the runlevels to be used can be customized.

 


RC scripts

A Linux service is an application (or set of applications) that runs in the background waiting to be used, or carrying out essential tasks. I've already mentioned a couple of typical ones (Apache and MySQL). You will generally be unaware of services until you need them.

How can you tell what services are running, and more importantly, how can you set up your own?

Let's start by looking at how the system is set up, and in particular at the directory /etc/rc.d. Here you will find either a set of files named rc.0, rc.1, rc.2, rc.3, rc.4, rc.5, and rc.6, or a set of directories named rc0.d, rc1.d, rc2.d, rc3.d, rc4.d, rc5.d, and rc6.d. You will also find a file named /etc/inittab. The system uses these files (and/or directories) to control the services to be started.

If you look in the file /etc/inittab you will see something like:

id:4:initdefault:l
0:0:wait:/etc/rc.d/rc.0l
6:6:wait:/etc/rc.d/rc.6x
1:4:wait:/etc/rc.d/rc.4

The boot process uses these parameters to identify the default runlevel and the files that will be used by that runlevel. In this example, runlevel 4 is the default and the scripts that define runlevel 4 can be found in /etc/rc.d/rc.4.

And what is a runlevel? You might assume that this refers to different levels that the system goes through during a boot up. Instead, think of the runlevel as the point at which the system is entered. Runlevel 1 is the most basic configuration (simple single user access using an text interface), while runlevel 5 is the most advanced (multi-user, networking, and a GUI front end). Runlevels 0 and 6 are used for halting and rebooting the system.

There are, however, differences between Linux distributions. For instance, Fedora uses runlevel 5 for X-based logins, whereas Slackware uses runlevel 4 to do the same job. Therefore, you should check your documentation before making any changes. This table shows a generic list of configurations (and some examples of different distros) taken from Linux - The Complete Reference (R.Peterson, Osbourne/McGraw-Hill).

Run Level

Generic

Fedora Core

Slackware

Debian

0 Halt Halt Halt Halt
1 Single-user mode Single-user mode Single-user mode Single-user mode
2 Basic multi-user mode (without networking) User definable (Unused) User definable - configured the same as runlevel 3 Multi-user mode
3 Full (text based) multi-user mode Multi-user mode Multi-user mode - default Slackware runlevel
4 Not used Not used X11 with KDM/GDM/XDM (session managers) Multi-user mode
5 Full (GUI based) multi-user mode Full multi-user mode (with an X-based login screen) - default runlevel User definable - configured the same as runlevel 3 Multi-user mode
6 Reboot Reboot Reboot Reboot

As you can see there are slight (but important) differences between Linux distributions. One thing is common between them -- if you want to change the default level, you must edit /etc/initab. You will need to be root or use sudo to edit this file, naturally.

Why would you want to change the runlevel? Normally you will only use full GUI or text multi-user mode -- runlevels 4 or 5. You'd only want runlevels 1 or 2 if you have some system problems and you want the most basic access. Runlevels 0 and 6 should never be used as a default (for obvious reasons -- you don't want the system to shutdown or reboot as soon as you turn it on). You can, of course, change mode whilst the system is running. Type init followed by the required runlevel e.g.:

init 6

This will reboot the system.

The boot process, or to be more accurate the init command, will decide the runlevel to select (in the example above it's 4) and from that will decide the rc.d script files to be run. In this case either the file /etc/rc.d/rc.4 or any files in the directory /etc/rc.d/rc4.d. Let's look at an example rc.d script file. Here's the default rc.4 file for Slackware 10.2:

# Try to use GNOME's gdm session manager:
if [ -x /usr/bin/gdm ]; 
then  exec /usr/bin/gdm -nodaemonfi
# Not there?  OK, try to use KDE's KDM session manager:
if [ -x /opt/kde/bin/kdm ];
 then  exec /opt/kde/bin/kdm -nodaemonfi
# If all you have is XDM, I guess it will have to do:
if [ -x /usr/X11R6/bin/xdm ];
 then  exec /usr/X11R6/bin/xdm -nodaemonfi

As you would expect, since runlevel 4 is the Slackware X11 mode, the commands are all concerned with the setting up of the graphical interface.

In the other distros (such as Fedora and Debian) you'll find that the scripts to be run are actually symbolic links to files in the directory /etc/init.d -- the central repository for all startup scripts. So all you have to do is to write your startup script, place it in /etc/init.d, and then create a symbolic link to it from the appropriate runlevel directory (or runlevel file, if that's what your system uses).

For example, runlevel 2 is the default runlevel for Debian in non-GUI mode. If you're running Apache 2 on Debian, you'd find an init script for Apache 2 under /etc/init.d called apache2. A symlink, S91apache2, points to /etc/init.d/apache2 from /etc/rc2.d -- this tells init to start Apache 2 in runlevel 2, but only after other services with lower S numbers.

When the system is shut down, there is another symlink in the /etc/rc0.d and /etc/rc6.d directories (halt and reboot, respectively) that starts with a K instead of an S, which tells init to shut down the process.

If this all still sounds a bit too complicated, you can instead simply make use of the /etc/rc.d/rc.local file. This script file is run once, before all other scripts have run but before the logon prompt appears. By default it looks something like:

#!/bin/bash## /etc/rc.local - run once at boot time
# Put any local setup commands in here:

You can append your instructions onto the end of the file by defining another script to be run:

/root/bin/start_bb

Or you can modify rc.local by adding the commands themselves:

modprobe -r uhcimodprobe usb-uhcieciadsl-startiptable -Fiptables -A 
INPUT -i ppp0 -p tcp --syb -j DROPnetdate time.nist.gov

Here a USB modem is initialized, a connection set up to a broadband network, some basic security is set up, and then the local time is synchronized with a time server. You can also start Apache or MySQL:

apachectl startecho "/usr/bin/mysqld_safe &" | su mysql

Note that some distros, such as Debian, do not use rc.local for startup scripts. See the Debian FAQ if you'd like to add startup scripts for Debian or Debian-derived distros.

One final thought -- in addition to startup scripts (for rc.local), try to remember to write close-down scripts to be added to rc.0 and rc.6. This ensures that your services are shut down neatly and not left in strange states when the system halts.