How does Linux boot

  • Tutorial
Update: The article and scripts were updated in March 2013 (5 years have passed, the old scripts are not much different from the current ones, but still it is better to study the current code, and the system boot logic has changed a bit over the years - otherwise udev works, new synthetic fs appeared like devtmpfs, /var/runmoved to /run, etc.).

When I mastered Linux, I was very interested in what happens when the system boots. An attempt to understand the boot process led me to the source code of the boot scripts ( /etc/inittab, /etc/rc*, /etc/init.d/*, ...) and their configs ( /etc/sysconfig/*, /etc/cond.f/*, ...). It is worth noting the serious size and complexity of these scripts - it took a lot of time to figure them out. But in those days, I sincerely believed that downloading is a complex process, and that the size and complexity of the boot scripts are justified.

When RedHat finally got me (2001), I decided to build my LFS- based distribution . For my distribution I had to develop boot scripts on my own, and then the truth became clear: there is nothing complicated in the boot process!

After working for 2.5 years on my distribution (PoWeR Linux), I migrated to Gentoo (I simply did not have enough time to provide quality support for my). After learning the Gentoo boot scripts, I was horrified! Their size and complexity were even greater than the old RedHat. After a detailed study, the reason became clear: the same set of boot scripts was used for both the LiveCD and the regular system - such a universal monster. So when I switched to Gentoo, I decided to take boot scripts from PoWeR Linux and not use the standard Gentoo ones (that is, I only use portage from Gentoo). And since then, for 4 more years, these scripts have been working on my home workstation and a bunch of remote servers.

Specifications


The size of the scripts (all together) is 308 lines, 8KB:
$ wc 1 3 lib.sh 
 201  769 5855 1
  78  272 1726 3
  29  118  771 lib.sh
 308 1159 8352 итого

Minuses:
  1. All in one file - when updating applications it is almost impossible to automatically update the initialization code of this application. For example, when ALSA is updated, the package may simply replace the files /etc/init.d/alsasound, /etc/conf.d/alsasound, /etc/modules.d/alsa. And in my case, the admin will need to edit the pens /etc/runit/1.
  2. There is no support for everything in the world. For example, I do not use RAID and LVM - so you will need to add commands to initialize them yourself.
  3. You will need to independently support these scripts. When updating Gentoo, I usually look at changes in (unused) /etc/init.d/*scripts and if something important changes, I update my scripts. But, in practice, the need for such changes arises approximately every two years.

Pros:
  1. All in one small file - there is no need to look for a bunch of scripts and their configs where you configure what you need; You can quickly and easily see all the basic settings of the system.
  2. There is support for everything that my friends and I needed for 11.5 years on home computers and servers.
  3. Ideal for exploring the Linux initialization process. You work with real basic Linux commands, which are the same in all distributions, and not with scripts and configs specific to your distribution.
  4. Speed ​​up system loading. My home machine loads in single user mode (6 consoles with getty, syslog, klog, acpid, dnscache, tinydns, gpm) in 11 seconds . I experimented with parallel loading in the initng style - the effect is rather negative due to the complexity of the scripts and the generation of unnecessary processes. Initng is good for speeding up loading of traditional, bloated scripts that perform many unnecessary actions, and in my case there is simply nothing to speed up. :)

Despite the small size, these scripts not only reliably and quickly load the system, but also support several features that make the admin's life easier:
  • Executable commands are grouped into blocks by type. In the process of loading, the names of the blocks are displayed, with information on whether there were errors when executing block commands.
  • If there were errors during the execution of the command block, detailed information is displayed on the commands of this block and the errors that occurred, after which the system waits 5 seconds for any button to be pressed to start bash and manually fix the errors. After exiting bash, it is suggested either to continue the download or reboot. If you do not click anything, the download will continue.
  • The logs of everything that is displayed when loading and disabling the screen (names of command blocks and information on errors that occur) are stored in files /var/log/boot, /var/log/shutdown. Thanks to this, you can see how the download / shutdown went on on remote servers.


Example messages displayed at boot


 + UDEV
 + MODULES
 + SYSCTL
 + MTAB
 - MOUNTALL

++ swapon -a
++ false
EXIT CODE: 1
++ mount -at nocoda, nonfs, noproc, noncpfs, nosmbfs, noshm
... press any key in 5 seconds to open shell ...
 + CLEANTMP + RANDOMSEED
 + HWCLOCK
 + SENSORS
 +
 LOADKEYS
 + SOUND
 + HOST_NAME
 + ENVUPDATE
 + NETWORK
 + RUNIT
 + DMESG



Runit


Instead of SysVinit, I use Runit to boot . Runit does not support /etc/inittab, instead, it uses a simple scheme:
  1. When loading the script is launched /etc/runit/1. His task is to fully initialize the system.
  2. At the end of the script, a script is /etc/runit/1launched /etc/runit/2that should run all the necessary services (syslog, getty, ssh, apache, ...).
  3. When the user stops / reboots the system, a script is launched /etc/runit/3that should prepare the system for shutdown (complete all processes, unmount disks, etc.).

If desired, you can configure SysVinit to work in the same style:

Running / etc / runit / {1,2,3} from SysVinit: / etc / inittab


id: 3: initdefault:
rc :: bootwait: / etc / runit / 1
l0: 0: wait: / bin / sh -c '/ etc / runit / 3; exec / sbin / halt '
l3: 3: once: / etc / runit / 2
l6: 6: wait: / bin / sh -c' / etc / runit / 3; exec / sbin / reboot '
ca: 12345: ctrlaltdel: / sbin / shutdown -r now


Services


To start all services (getty, syslog, mysql, etc.) I use the same runit (in fact, it's just a slightly improved version of daemontools ). But this is a separate big topic, so I’ll just clarify that in this article there are no scripts for launching services, here only the initialization / shutdown of the system.

Source code


Helper Functions: /etc/runit/lib.sh


#!/bin/bash
startlog() { exec 3>&1 4>&2 1> >(tee $1) 2>&1; }
stoplog() { exec 1>&3- 2>&4-; }
wanna() {
    echo -e "\a... press any key in $2 seconds to $1 ..."
    read -t $2 -n 1 -s /dev/console
        if [[ "$0" == "/etc/runit/1" ]] && wanna "reboot now" 3; then
            exit 100
        fi
    fi
}
trace() { trap 'ERR=$?' ERR; set -Ex; $1 2>&1; set +Ex; trap ERR; } 2>&-
try() {
    local output=$( trace $1 )
    if [[ "$output" =~ "ERR=" ]]; then
        echo -e "\e[1m\e[31m - \e[37m$1\e[0m"
        echo "$output" | sed $'s/.*ERR=\(.*\)/\a\033[36mEXIT CODE: \\1\033[0m/g'
        emergency
    else
        echo -e "\e[1m\e[32m + \e[37m$1\e[0m"
    fi
}


Startup: / etc / runit / 1


#!/bin/bash
CONSOLE() {
    dmesg -n 1
}
INIT() {
    mount -n -t proc -o "noexec,nosuid,nodev" none /proc
    mount -n -t sysfs -o "noexec,nosuid,nodev" none /sys
    mount -n -t tmpfs -o "mode=0755,nosuid,nodev" none /run
    mkdir /run/lock
    chmod 0775 /run/lock
    chown root:uucp /run/lock
    if grep -qs devtmpfs /proc/mounts; then
	mount -n -t devtmpfs -o "remount,exec,nosuid,mode=0755,size=10M" none /dev
    elif grep -qs devtmpfs /proc/filesystems; then
	mount -n -t devtmpfs -o "exec,nosuid,mode=0755,size=10M" none /dev
    else
	mount -n -t tmpfs none /dev
	busybox mdev -s
    fi
    # needed to run startlog (in /etc/runit/lib.sh) before UDEV
    ln -snf /proc/self/fd /dev/fd
    # extra mountpoints in /dev
    mkdir -p /dev/pts
    mount -n -t devpts -o "noexec,nosuid,gid=5,mode=0620" none /dev/pts
    mkdir -p /dev/shm
    mount -n -t tmpfs -o "noexec,nosuid,nodev" none /dev/shm
}
UDEV() {
    echo "" >/proc/sys/kernel/hotplug
    udevd --daemon
    udevadm trigger --type=subsystems --action=add
    udevadm trigger --type=devices --action=add
    udevadm settle --timeout=30
}
HWCLOCK() {
    hwclock --hctosys --localtime && touch /run/init.hwclock
}
MODULES() {
    true    # bash doesn't allow empty functions
#    modprobe -q nvidia NVreg_DeviceFileMode=432 NVreg_DeviceFileUID=0 NVreg_DeviceFileGID=27 NVreg_ModifyDeviceFiles=1
#    modprobe -q -a vmmon vmci vsock vmblock vmnet
#    modprobe -q -a vboxdrv vboxnetflt vboxnetadp
}
FSCK() {
    fsck -A -p -C0 -T -t noafs,nocifs,nocoda,nodavfs,nofuse,nofuse.sshfs,nogfs,noglusterfs,nolustre,noncpfs,nonfs,nonfs4,noocfs2,noshfs,nosmbfs,noopts=_netdev
}
REMOUNT() {
    mount -n -o remount,rw /
    grep -v ^rootfs /proc/mounts > /etc/mtab
    for i in $(cut -d ' ' -f 2 /etc/mtab | grep -vx /); do
    	mount -o remount "$i"
    done
}
LOCALMOUNT() {
    mount -at noproc,noafs,nocifs,nocoda,nodavfs,nofuse,nofuse.sshfs,nogfs,noglusterfs,nolustre,noncpfs,nonfs,nonfs4,noocfs2,noshfs,nosmbfs -O no_netdev
    swapon -a
}
SYSCTL() {
    sysctl -p /etc/sysctl.conf
}
MIGRATERUN() {
    rm -rf /var/lock
    ln -s /run/lock /var/lock
    rm -rf /var/run
    ln -s /run /var/run
}
UTMPWTMP() {
    > /var/run/utmp
    chgrp utmp /var/run/utmp
    chmod 0664 /var/run/utmp
    [ -e /var/log/wtmp ] || cp -a /var/run/utmp /var/log/wtmp
}
CLEANTMP() {
    rm -f  /tmp/.X*-lock /tmp/esrv* /tmp/kio* /tmp/jpsock.* /tmp/.fam*
    rm -rf /tmp/.esd* /tmp/orbit-* /tmp/ssh-* /tmp/ksocket-* /tmp/.*-unix
    mkdir -p /tmp/.{ICE,X11}-unix
    chmod 1777 /tmp/.{ICE,X11}-unix
}
RANDOMSEED() {
    mkdir -p /var/lib/misc
    [ -f /var/lib/misc/random-seed ] && cat /var/lib/misc/random-seed >/dev/urandom
    rm -f /var/lib/misc/random-seed
    local psz=$(( $(sysctl -n kernel.random.poolsize 2>/dev/null || echo 4096) / 4096 ))
    (umask 077; dd if=/dev/urandom of=/var/lib/misc/random-seed count=$psz 2>/dev/null)
}
SENSORS() {
    sensors -s
}
LOADKEYS() {
    # Commands for TTY initialization like 'setfont' and 'echo -ne "\033(K"'
    # shouldn't be executed in /etc/runit/1 because:
    # - which TTYs should be initialized may depend on current runlevel
    # - if TTY state become broken (for ex. after 'cat /dev/urandom'),
    #   then after logout and login TTY state should be reinitialized
    # these commands should be executed before each getty invocation instead.
    kbd_mode -u
    loadkeys koi2   # -q windowkeys
#    loadkeys -q -u ru4
    dumpkeys -c koi8-r | loadkeys --unicode
}
SOUND() {
    alsactl -f /etc/asound.state restore && touch /run/init.alsa
}
HOST_NAME() {
    # Here you should set only "host" part of your fqdn.
    # Add this line to /etc/hosts to configure FQDN:
    #   YOUR.IP.ADDR.ESS    YOUR_HOSTNAME.DOMAIN.TLD    YOUR_HOSTNAME
    hostname YOUR_HOSTNAME
}
NETWORK() {
    ip link set lo up
    iptables-restore  /var/log/dmesg
    chmod 640 /var/log/dmesg
}
PATH=/sbin:/usr/sbin:/bin:/usr/bin
trap ':' INT QUIT TSTP
. /etc/runit/lib.sh
try CONSOLE
try INIT
startlog /run/boot.log
try UDEV
try HWCLOCK
#try MODULES    # Enable & configure this if you have modules support in kernel
    FSCK
try REMOUNT
try LOCALMOUNT
try SYSCTL
try MIGRATERUN
try UTMPWTMP
try CLEANTMP
try RANDOMSEED
#try SENSORS    # Enable this if you have configured lm_sensors
#try LOADKEYS   # Enable & configure this for non-english keyboard layout
#try SOUND      # Enable this if you have sound card (also in /etc/runit/3!)
try HOST_NAME   # Do not forget to configure this
try NETWORK     # Do not forget to configure this
try RUNIT
#try SEND_MAIL  # Enable this if you wanna receive notification email on reboot
try DMESG
stoplog
mv /run/boot.log /var/log/boot
# Select next stage (exit 0 for stage 2, exit 100 for stage 3):
exit 0


Shutdown: / etc / runit / 3


#!/bin/bash
CONSOLE() {
    chvt 1
    # Required in case getty was last process in this console and it leave
    # console in broken state (\n work as  without ).
    { stty sane ; echo ; } >/dev/console
}
TERM() {
    # Give a chance for all processes for clean exit.
    # This also will kill all 'runsvdir' and signal all 'runsv' to exit.
    killall5 -15 || [ $? -eq 2 ]
}
HWCLOCK() {
    test -f /run/init.hwclock && hwclock --systohc --localtime --noadjfile
}
SERVICES() {
    sv force-stop /var/service/* &>/dev/null || :
}
SOUND() {
    test -f /run/init.alsa && alsactl -f /etc/asound.state store
}
RANDOMSEED() {
    local psz=$(( $(sysctl -n kernel.random.poolsize 2>/dev/null || echo 4096) / 4096 ))
    (umask 077; dd if=/dev/urandom of=/var/lib/misc/random-seed count=$psz 2>/dev/null)
}
NETWORK() {
    ip link set group default down
}
WTMP() {
    halt -w
}
KILL() {
    # Goodbye to everybody...
    killall5 -9 || [ $? -eq 2 ]
}
UMOUNT() {
    sync; sync
    # Unmounting loopback devices first:
    for d in $(grep '^/dev/loop' /proc/mounts | cut -d ' ' -f 2 | tac); do
        eval "umount -d -r -f $'$d'"
    done
    # Unmounting all real filesystems except root:
    for d in $(egrep -v '^\S+ (/|/dev|/dev/.*|/proc|/proc/.*|/run|/sys|/sys/.*) ' /proc/mounts | cut -d ' ' -f 2 | tac); do
        eval "umount -r -f $'$d'"
    done
    # Switching off swap
    umount -a -t tmpfs 2>/dev/null || :
    swapoff -a
}
PATH=/sbin:/usr/sbin:/bin:/usr/bin
trap ':' INT QUIT TSTP
. /etc/runit/lib.sh
try CONSOLE
startlog /var/log/shutdown
try TERM
try HWCLOCK
try SERVICES
#try SOUND      # Enable this if you have sound card
try RANDOMSEED
try NETWORK
try WTMP
try KILL
try UMOUNT
stoplog

Also popular now: