📜 ⬆️ ⬇️

How Linux boots

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's better to study the actual code, and the system boot logic has changed a bit over the years - otherwise udev works, new synthetic fs have appeared like devtmpfs, /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 for the boot scripts ( /etc/inittab, /etc/rc*, /etc/init.d/*, ... ) and their configs ( /etc/sysconfig/*, /etc/cond.f/*, ... ). It is necessary to note the serious size and complexity of these scripts - it took a long time to figure them out. But in those days I sincerely believed that downloading is a difficult process, and that the size and complexity of the boot scripts are fully justified.

When I finally got RedHat (2001), I decided to build my own distribution based on LFS . For my distribution, I had to independently develop boot scripts, and then it turned out the truth: there is nothing difficult in the boot process!

Having worked 2.5 years on my distribution (PoWeR Linux), I migrated to Gentoo (I simply didn’t have enough time for quality support). Having studied the Gentoo boot scripts, I was horrified! Their size and complexity were even greater than that of 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 usual system - such a universal monster. So when switching to Gentoo, I decided to take the boot scripts from PoWeR Linux and not use standard Gentoos (ie, I only use portage from Gentoo). And since then, another 4 years these scripts work on my home workstation and a handful of remote servers.
')

Specifications


The size of the scripts (all together) - 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 can simply replace the /etc/init.d/alsasound, /etc/conf.d/alsasound, /etc/modules.d/alsa files. And in my case, the admin will need to edit /etc/runit/1 handles.
  2. There is no support for anything in the world. For example, I do not use RAID and LVM - so you will need to add commands to initialize them.
  3. You will need to support these scripts yourself. When I upgrade Gentoo, I usually look at the changes in the (unused) /etc/init.d/* scripts, and if something important changes, I update my scripts. But, in practice, the need for such changes occurs about once every two years.

Pros:
  1. All in one small file - there is no need to search for a heap of scripts and their configs where you need what you need; You can quickly and easily see all the basic system settings.
  2. There is support for everything that I and my friends needed for 11.5 years on home computers and servers.
  3. Ideal for learning about the Linux initialization process. You work with real basic Linux commands that are the same in all distributions, and not with scripts and configs specific to your distribution.
  4. Accelerate system loading. My home machine is loaded into 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 the 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 life easier for the admin:


An example of 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


For download, I use Runit instead of SysVinit. Runit does not support /etc/inittab , instead it uses a simple scheme:
  1. When loading the script runs /etc/runit/1 . His task is to completely initialize the system.
  2. Upon completion of the /etc/runit/1 script, the /etc/runit/2 script is launched, which should start all the necessary services (syslog, getty, ssh, apache, ...).
  3. When the user stops / overloads the system, the /etc/runit/3 script is launched which should prepare the system for shutdown (terminate all processes, unmount disks, etc.).

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

Starting / 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 run all the services (getty, syslog, mysql, etc.) I use the same runit (in fact, this is 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 to start the services, here only the system initialization / shutdown.

Sources


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 } emergency() { if wanna "open shell" 5; then bash --norc </dev/console &>/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 </etc/iptables #ip link set eth0 up #ip addr add 192.168.1.2/24 dev eth0 #ip route add default via 192.168.1.1 dev eth0 } RUNIT() { # Set default action (shutdown or not) if Ctrl+Alt+Del pressed, # but /etc/runit/ctrlaltdel don't setup /etc/runit/stopit. touch /etc/runit/stopit chmod 100 /etc/runit/stopit # Set default action on shutdown (halt or reboot) if: # - /etc/runit/1 crash or exit 100 # - /etc/runit/2 exit non 111 # - Ctrl+Alt+Del pressed, but /etc/runit/ctrlaltdel don't setup /etc/runit/reboot touch /etc/runit/reboot chmod 100 /etc/runit/reboot # Set runlevel to: # - single if kernel has param: S # - RUNLEVELNAME if kernel has param: runlevel=RUNLEVELNAME # - default if kernel has no params or unable to set requested runlevel grep -q '\(^\| \)S\( \|$\)' /proc/cmdline && runlevel='single' runsvchdir ${runlevel:-default} || runsvchdir default } SEND_MAIL() { echo -e "To: root\nSubject: reboot at $(date)" | sendmail -t } DMESG() { # Create an 'after-boot' dmesg log dmesg > /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 <LF> without <CR>). { 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 

Source: https://habr.com/ru/post/21205/


All Articles