Quantcast
Channel: Библиотека знаний
Viewing all articles
Browse latest Browse all 1318

Как загружается Linux - [Как загружается Linux]

$
0
0

Как загружается Linux

Когда я осваивал Linux, мне было очень интересно что происходит при загрузке системы. Попытка разобраться в процессе загрузки привела меня в исходники загрузочных скриптов (/etc/inittab, /etc/rc*, /etc/init.d/*, ...) и их конфигов (/etc/sysconfig/*, /etc/cond.f/*, ...). Надо отметить серьёзные размеры и сложность этих скриптов - чтобы в них разобраться потребовалось немало времени. Но я в те времена искренне верил, что загрузка это сложный процесс, и что размеры и сложность загрузочных скриптов вполне оправданы. Когда меня окончательно достал RedHat, я решил собрать свой дистрибутив на базе LFS. Для своего дистрибутива пришлось самостоятельно разрабатывать загрузочные скрипты, и тут-то выяснилась правда: ничего сложного в процессе загрузки нет! Проработав 2.5 года на своём дистрибутиве (PoWeR Linux) я мигрировал на Gentoo (на качественную поддержку своего просто не хватало времени). Изучив загрузочные скрипты Gentoo я пришёл в ужас! Их размеры и сложность были ещё больше, чем у старого RedHat. После детального изучения стала ясна причина: один и тот же комплект загрузочных скриптов использовался и для LiveCD и для обычной системы - такой себе универсальный монстрик.
Так что при переходе на Gentoo я решил взять загрузочные скрипты из PoWeR Linux а стандартные Gentoo-шные не использовать (т.е. у меня от Gentoo используется только portage). И с тех пор ещё 4 года эти скрипты работают у меня на домашней рабочей станции и кучке удалённых серверов.

Характеристики

Размер скриптов (всех вместе) - 316 строк, 7.5KB:

$ wc 1 3 lib.sh 
 199  703 5152 1
  89  272 1671 3
  28  118  770 lib.sh
 316 1093 7593 итого

Минусы:

Всё в одном файле - при обновлении приложений практически невозможно автоматически обновить код инициализации этого приложения. Например, когда обновляется ALSA, то пакет может просто заменить файлы /etc/init.d/alsasound, /etc/conf.d/alsasound, /etc/modules.d/alsa. А в моём случае админу нужно будет ручками править /etc/runit/1. Нет поддержки всего на свете. Например, я не использую RAID и LVM - так что команды для их инициализации вам нужно будет добавлять самостоятельно. Вам нужно будет самостоятельно поддерживать эти скрипты. Я обычно при обновлении Gentoo поглядываю на изменения в (неиспользуемых мной) /etc/init.d/* скриптах и если меняется что-то важное - обновляю свои скрипты. Но, на практике, необходимость в таких изменениях возникает примерно раз в два года.

Плюсы:

Всё в одном небольшом файле - нет необходимости разыскивать по куче скриптов и их конфигов где настраивается то, что вам понадобилось; можно быстро и легко увидеть все базовые настройки системы.

Есть поддержка всего, что мне и моим друзьям за 6.5 лет было нужно на домашних компах и серверах.

Идеальны для изучения процесса инициализации Linux. Вы работаете с реальными базовыми командами Linux, которые одинаковы во всех дистрибутивах, а не со скриптами и конфигами специфичными для вашего дистрибутива.

Ускоряют загрузку системы. У меня домашняя машина грузится в single user mode (6 консолек с getty, syslog, klog, gpm) за 16.5 секунд. Я экспериментировал с параллельной загрузкой в стиле 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

Для загрузки я вместо SysV init использую 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  none /proc
    mount -n -t sysfs none /sys
    mount -n -t ramfs none /dev
    mknod -m 660 /dev/console c 5 1
    mknod -m 660 /dev/null c 1 3
    ln -snf /proc/self/fd /dev/fd   # needed for startlog in /etc/runit/lib.sh
}

UDEV() {
    udevstart

    ### Standard add-on
    ln -snf /proc/self/fd/0 /dev/stdin
    ln -snf /proc/self/fd/1 /dev/stdout
    ln -snf /proc/self/fd/2 /dev/stderr
    ln -snf /proc/kcore /dev/core
    mkdir /dev/pts
    mkdir /dev/shm
    mount -n -t devpts -o gid=5,mode=0620 none /dev/pts

    ### My custom add-on
    ln -s vcs /dev/vcs0
    ln -s dvd2 /dev/dvd
    ln -s cdrom2 /dev/cdrom
    mkdir /dev/lirc
    mknod /dev/lirc/0 c 61 0
    mknod /dev/vmmon c 10 165
    mknod /dev/sdb  b 8  16
    mknod /dev/sdb1 b 8  17
}

MODULES() {
    update-modules

    ### Network
    modprobe -q skge
    modprobe -q 8139too media=56 # 1=10half 17=10full 56=100full
    modprobe -q sky2

    ### Lirc
    setserial /dev/ttyS1 uart none
    modprobe -q lirc_serial io=0x2f8 irq=3 # io=0x3f8 irq=4

    ### VMware
    modprobe -q vmmon
    modprobe -q vmnet
}

SYSCTL() {
    sysctl -p /etc/sysctl.conf
}

MTAB() {
    # Adding already-mounted fs to /etc/mtab
    >/etc/mtab
    mount -f /
    awk '$2 != "/" {print}' /proc/mounts >> /etc/mtab
    for i in $(cut -d ' ' -f 2 </etc/mtab); do mount -f -o remount "$i"; done
}

MOUNTALL() {
    swapon -a
    mount -at nocoda,nonfs,noproc,noncpfs,nosmbfs,noshm
}

CLEANTMP() {
    # Cleaning up temporary files and locks, prepare utmp & wtmp
    rm -rf /var/lib/net-scripts/state/*
    rm -rf /var/run/console.lock /var/run/console/* # reset pam_console
    find /var/lock -type f -print0 | xargs -0 rm -f --
    find /var/run ! -type d ! -name utmp ! -name innd.pid \
        ! -name random-seed -exec rm -f -- {} \;
    > /var/lock/.keep
    rm -f /tmp/.X*-lock /tmp/esrv* /tmp/kio* /tmp/jpsock.* /tmp/.fam* /tmp/iceauth.* /tmp/xauth.*
    rm -rf /tmp/.esd* /tmp/orbit-* /tmp/ssh-* /tmp/ksocket-* /tmp/.*-unix
    rm -rf /tmp/.{ICE,X11}-unix
    mkdir -p /tmp/.{ICE,X11}-unix
    chmod 1777 /tmp/.{ICE,X11}-unix
}

RANDOMSEED() {
    # Restoring random-seed.
    [ -f /var/run/random-seed ] && cat /var/run/random-seed >>/dev/urandom
    rm -f /var/run/random-seed
    ( umask 077 ; dd if=/dev/urandom of=/var/run/random-seed count=1 2>/dev/null )
}

HWCLOCK() {
    if [ ! -f /etc/adjtime ]; then echo "0.0 0 0.0" > /etc/adjtime ; fi
    hwclock --adjust --localtime
    hwclock --hctosys --localtime
}

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.
    loadkeys koi2   # -q windowkeys
}

SOUND() {
    alsactl -f /etc/asound.state restore
}

HOST_NAME() {
    hostname home
    echo "HOSTNAME='$(hostname)'" >/etc/env.d/01hostname
}

ENVUPDATE() {
    env-update.sh -u
}

NETWORK() {
    ifconfig lo 127.0.0.1 up
    route add -net 127.0.0.0 netmask 255.0.0.0 gw 127.0.0.1 dev lo
    iptables-restore </etc/iptables
    ifconfig eth2 192.168.2.1 broadcast 192.168.2.255 netmask 255.255.255.0
    ifconfig eth2:0 192.168.2.254
    ifconfig eth1 192.168.1.2 broadcast 192.168.1.255 netmask 255.255.255.0
    ifconfig eth0 192.168.10.2 broadcast 192.168.10.255 netmask 255.255.255.0
    #route add default gw 192.168.1.1 dev eth1
}

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
    touch /var/log/dmesg
    chmod 640 /var/log/dmesg
    dmesg > /var/log/dmesg
}


PATH=/sbin:/usr/sbin:/bin:/usr/bin
trap ':' INT QUIT TSTP
. /etc/runit/lib.sh

try CONSOLE
try INIT
startlog /var/log/boot
try UDEV
try MODULES
try SYSCTL
try MTAB
try MOUNTALL
try CLEANTMP
try RANDOMSEED
try HWCLOCK
try SENSORS
try LOADKEYS
try SOUND
try HOST_NAME
try ENVUPDATE
try NETWORK
try RUNIT
#try SEND_MAIL
try DMESG
stoplog

# 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
}

HWCLOCK() {
    hwclock --systohc --localtime
}

SERVICES() {
    sv force-stop /var/service/* &>/dev/null || :
}

SOUND() {
    alsactl -f /etc/asound.state store
}

RANDOMSEED() {
    umask 077
    dd if=/dev/urandom of=/var/run/random-seed count=1 2>/dev/null
}

NETWORK() {
    for i in $(ifconfig | grep '^[^ ]' | cut -d ' ' -f 1 | tac); do
        ifconfig "$i" down
    done
}

WTMP() {
    /sbin/halt -w
}

KILL() {
    # Goodbye to everybody...
    killall5 -9
}

UMOUNTPSEUDO() {
    # Unmounting memory filesystems:
    umount -a -t tmpfs
    swapoff -a
    # 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
    umount -d -r -f -a -O bind 
}

UMOUNTHDD() {
    # Unmounting all filesystems:
    for d in $(egrep -v '^[^/]|^/dev/root' /proc/mounts | cut -d ' ' -f 2 | tac); do
        eval "umount -d -r -f $'$d'"
    done
}

FINI() {
    sync; sync
    mount -n -o remount,ro / || { sleep 1; mount -n -o remount,ro /; }
}


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
try RANDOMSEED
try NETWORK
try WTMP
try KILL
try UMOUNTPSEUDO
try UMOUNTHDD
stoplog
try FINI

Viewing all articles
Browse latest Browse all 1318