Update: Статья и скрипты были обновлены в марте 2013 (прошло 5 лет, старые скрипты не сильно отличаются от текущих, но всё-таки лучше изучать актуальный код, а логика загрузки системы за эти годы немного изменилась — иначе работает udev, появились новые синтетические fs вроде devtmpfs,
Когда я осваивал Linux, мне было очень интересно что происходит при загрузке системы. Попытка разобраться в процессе загрузки привела меня в исходники загрузочных скриптов (
Когда меня окончательно достал RedHat (2001 год), я решил собрать свой дистрибутив на базе LFS. Для своего дистрибутива пришлось самостоятельно разрабатывать загрузочные скрипты, и тут-то выяснилась правда: ничего сложного в процессе загрузки нет!
Проработав 2.5 года на своём дистрибутиве (PoWeR Linux) я мигрировал на Gentoo (на качественную поддержку своего просто не хватало времени). Изучив загрузочные скрипты Gentoo я пришёл в ужас! Их размеры и сложность были ещё больше, чем у старого RedHat. После детального изучения стала ясна причина: один и тот же комплект загрузочных скриптов использовался и для LiveCD и для обычной системы — такой себе универсальный монстрик. Так что при переходе на Gentoo я решил взять загрузочные скрипты из PoWeR Linux а стандартные Gentoo-шные не использовать (т.е. у меня от Gentoo используется только portage). И с тех пор ещё 4 года эти скрипты работают у меня на домашней рабочей станции и кучке удалённых серверов.
Размер скриптов (всех вместе) — 308 строк, 8KB:
Минусы:
Плюсы:
Не смотря на малый размер эти скрипты не только надёжно и быстро грузят систему, но и поддерживают несколько фич облегчающих жизнь админу:
Для загрузки я вместо SysVinit использую Runit. Runit не поддерживает
При желании можно настроить SysVinit для работы в том же стиле:
Для запуска всех сервисов (getty, syslog, mysql, etc.) я использую тот же runit (по сути это просто немного улучшенный вариант daemontools). Но это отдельная большая тема, так что просто уточню что в этой статье скриптов для запуска сервисов нет, здесь только инициализация/отключение системы.
/var/run
переехал в /run
, etc.).Когда я осваивал Linux, мне было очень интересно что происходит при загрузке системы. Попытка разобраться в процессе загрузки привела меня в исходники загрузочных скриптов (
/etc/inittab, /etc/rc*, /etc/init.d/*, ...
) и их конфигов (/etc/sysconfig/*, /etc/cond.f/*, ...
). Надо отметить серьёзные размеры и сложность этих скриптов — чтобы в них разобраться потребовалось немало времени. Но я в те времена искренне верил, что загрузка это сложный процесс, и что размеры и сложность загрузочных скриптов вполне оправданы.Когда меня окончательно достал RedHat (2001 год), я решил собрать свой дистрибутив на базе LFS. Для своего дистрибутива пришлось самостоятельно разрабатывать загрузочные скрипты, и тут-то выяснилась правда: ничего сложного в процессе загрузки нет!
Проработав 2.5 года на своём дистрибутиве (PoWeR Linux) я мигрировал на Gentoo (на качественную поддержку своего просто не хватало времени). Изучив загрузочные скрипты Gentoo я пришёл в ужас! Их размеры и сложность были ещё больше, чем у старого RedHat. После детального изучения стала ясна причина: один и тот же комплект загрузочных скриптов использовался и для LiveCD и для обычной системы — такой себе универсальный монстрик. Так что при переходе на Gentoo я решил взять загрузочные скрипты из PoWeR Linux а стандартные Gentoo-шные не использовать (т.е. у меня от Gentoo используется только portage). И с тех пор ещё 4 года эти скрипты работают у меня на домашней рабочей станции и кучке удалённых серверов.
Характеристики
Размер скриптов (всех вместе) — 308 строк, 8KB:
$ wc 1 3 lib.sh
201 769 5855 1
78 272 1726 3
29 118 771 lib.sh
308 1159 8352 итого
Минусы:
- Всё в одном файле — при обновлении приложений практически невозможно автоматически обновить код инициализации этого приложения. Например, когда обновляется ALSA, то пакет может просто заменить файлы
/etc/init.d/alsasound, /etc/conf.d/alsasound, /etc/modules.d/alsa
. А в моём случае админу нужно будет ручками править/etc/runit/1
. - Нет поддержки всего на свете. Например, я не использую RAID и LVM — так что команды для их инициализации вам нужно будет добавлять самостоятельно.
- Вам нужно будет самостоятельно поддерживать эти скрипты. Я обычно при обновлении Gentoo поглядываю на изменения в (неиспользуемых мной)
/etc/init.d/*
скриптах и если меняется что-то важное — обновляю свои скрипты. Но, на практике, необходимость в таких изменениях возникает примерно раз в два года.
Плюсы:
- Всё в одном небольшом файле — нет необходимости разыскивать по куче скриптов и их конфигов где настраивается то, что вам понадобилось; можно быстро и легко увидеть все базовые настройки системы.
- Есть поддержка всего, что мне и моим друзьям за 11.5 лет было нужно на домашних компах и серверах.
- Идеальны для изучения процесса инициализации Linux. Вы работаете с реальными базовыми командами Linux, которые одинаковы во всех дистрибутивах, а не со скриптами и конфигами специфичными для вашего дистрибутива.
- Ускоряют загрузку системы. У меня домашняя машина грузится в single user mode (6 консолек с getty, syslog, klog, acpid, dnscache, tinydns, gpm) за 11 секунд. Я экспериментировал с параллельной загрузкой в стиле initng — эффект скорее отрицательный за счёт усложнения скриптов и порождения лишних процессов. Initng хорош для ускорения загрузки традиционных, раздутых скриптов, выполняющих множество ненужных действий, а в моём случае ускорять просто нечего. :)
Не смотря на малый размер эти скрипты не только надёжно и быстро грузят систему, но и поддерживают несколько фич облегчающих жизнь админу:
- Выполняемые команды сгруппированы в блоки по типам. В процессе загрузки выводятся названия блоков, с информацией были ли ошибки при выполнении команд блока.
- Если при выполнении блока команд были ошибки — выводится детальная информация по командам этого блока и возникшим ошибкам, после чего система ждёт 5 секунд нажатия любой кнопки для запуска bash и ручного исправления ошибок. После выхода из bash предлагается либо продолжить загрузку либо перегрузиться. Если ничего не нажимать, загрузка продолжится.
- Логи всего, что выводится при загрузке и отключении на экран (названия блоков команд и информация по возникающим ошибкам) сохраняются в файлах
/var/log/boot, /var/log/shutdown
. Благодаря этому можно на удалённых серверах посмотреть, как проходила загрузка/отключение.
Пример сообщений выводимых при загрузке
+ 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
Для загрузки я вместо SysVinit использую Runit. Runit не поддерживает
/etc/inittab
, вместо этого в нём используется простая схема:- При загрузке запускается скрипт
/etc/runit/1
. Его задача полностью проинициализировать систему. - По завершению скрипта
/etc/runit/1
запускается скрипт/etc/runit/2
, который должен запустить все необходимые сервисы (syslog, getty, ssh, apache, ...). - Когда пользователь останавливает/перегружает систему запускается скрипт
/etc/runit/3
который должен подготовить систему к отключению (завершить все процессы, отмонтировать диски, etc.).
При желании можно настроить SysVinit для работы в том же стиле:
Запуск /etc/runit/{1,2,3} из 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
Сервисы
Для запуска всех сервисов (getty, syslog, mysql, etc.) я использую тот же runit (по сути это просто немного улучшенный вариант daemontools). Но это отдельная большая тема, так что просто уточню что в этой статье скриптов для запуска сервисов нет, здесь только инициализация/отключение системы.
Исходники
Вспомогательные функции: /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