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,
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 (
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.
The size of the scripts (all together) is 308 lines, 8KB:
Minuses:
Pros:
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:
Instead of SysVinit, I use Runit to boot . Runit does not support
If desired, you can configure SysVinit to work in the same style:
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.
/var/run
moved 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:
- 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
. - 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.
- 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:
- 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.
- There is support for everything that my friends and I needed for 11.5 years on home computers and servers.
- 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.
- 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:- When loading the script is launched
/etc/runit/1
. His task is to fully initialize the system. - At the end of the script, a script is
/etc/runit/1
launched/etc/runit/2
that should run all the necessary services (syslog, getty, ssh, apache, ...). - When the user stops / reboots the system, a script is launched
/etc/runit/3
that 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