A bootfolyamat

A Unix/Linux szerverek üzemeltetése wikiből
(Változatok közti eltérés)
a (Szolgáltatások leállítása á la init: alternatívák kiegészítése)
a (Szolgáltatások leállítása á la init: további alternatívák: s6, daemontools-encore, perp, launchd)
 
663. sor: 663. sor:
 
A "megoldás": pidfile. De:
 
A "megoldás": pidfile. De:
   
* A pidfile-t le lehet törölni, felül lehet írni.
+
* A pidfile és aközött a processz között, amelynek a PID-ját tartalmazza, nincs semmilyen kapcsolat; nincs olyan mechanizmus, ami a pidfile tartalmának naprakészségét versenyhelyzetmentesen szavatolná vagy szavatolni tudná.
  +
** A pidfile-t le lehet törölni, felül lehet írni.
 
* Nem minden program tud saját maga pidfile-t létrehozni.
 
* Nem minden program tud saját maga pidfile-t létrehozni.
 
** A <tt>start-stop-daemon</tt> meg tudja tenni helyettük, de komplex, több processzből álló alkalmazás esetén nem biztos, hogy a megfelelőnek a PID-ja kerül a fájlba;
 
** A <tt>start-stop-daemon</tt> meg tudja tenni helyettük, de komplex, több processzből álló alkalmazás esetén nem biztos, hogy a megfelelőnek a PID-ja kerül a fájlba;
670. sor: 670. sor:
 
* Összetett configfile esetén az initscript esetleg nem találja meg az érvényes pidfile-beállítást (pl. apache2).
 
* Összetett configfile esetén az initscript esetleg nem találja meg az érvényes pidfile-beállítást (pl. apache2).
 
** Vagyis: ha a pidfile-ból ki is olvasunk egy természetes számot, meg kell(ene) győződni arról, hogy az valóban a leállítandó szolgáltatáshoz tartozó processz PID-ja-e.
 
** Vagyis: ha a pidfile-ból ki is olvasunk egy természetes számot, meg kell(ene) győződni arról, hogy az valóban a leállítandó szolgáltatáshoz tartozó processz PID-ja-e.
** Itt viszont versenyhelyzetbe kerülhetünk, mert a szám kiolvasása, hitelesítése és a signal elküldése között az eredeti processz kiléphet, és másik kaphatja meg a PID-ját.
+
** Itt viszont versenyhelyzetbe kerülünk, mert a szám kiolvasása, hitelesítése és a signal elküldése között az eredeti processz kiléphet, és másik kaphatja meg a PID-ját.
 
** Még akkor is fennáll a versenyhelyzet, ha nem hitelesítjük a PID-t sehogy (ez amúgy rossz ötlet), mert a kiolvasás-signalküldés nem atomi művelet:
 
** Még akkor is fennáll a versenyhelyzet, ha nem hitelesítjük a PID-t sehogy (ez amúgy rossz ötlet), mert a kiolvasás-signalküldés nem atomi művelet:
 
*** a kiolvasás és a signalküldés között eltűnhet az eredeti processz és létrejöhet ugyanolyan PID-val másik;
 
*** a kiolvasás és a signalküldés között eltűnhet az eredeti processz és létrejöhet ugyanolyan PID-val másik;
869. sor: 869. sor:
 
Alternatívát kínál:
 
Alternatívát kínál:
   
* [[daemontools]] (részben)
+
* [[daemontools]], [http://untroubled.org/daemontools-encore/ daemontools-encore] (részben)
 
* [[A runit működése|runit]] (teljesen)
 
* [[A runit működése|runit]] (teljesen)
  +
* [http://www.skarnet.org/software/s6/index.html s6] (teljesen; lehetséges házi feladat)
 
* [[initng]] (lehetséges házi feladat)
 
* [[initng]] (lehetséges házi feladat)
 
* [[systemd]] (lehetséges házi feladat)
 
* [[systemd]] (lehetséges házi feladat)
876. sor: 877. sor:
 
* Az Ubuntu új üdvöskéje, az [[upstart]] (lehetséges házi feladat)
 
* Az Ubuntu új üdvöskéje, az [[upstart]] (lehetséges házi feladat)
 
* [[insserv]] (lehetséges házi feladat)
 
* [[insserv]] (lehetséges házi feladat)
  +
* [http://b0llix.net/perp/ perp] (lehetséges házi feladat)
  +
* [http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man8/launchd.8.html launchd] (ilyen van a Mac OS X-ben)
 
* egyéb (lehetséges házi feladat :)
 
* egyéb (lehetséges házi feladat :)
   

A lap jelenlegi, 2011. november 7., 16:02-kori változata

Lássuk, hogyan bootol egy olyan Unix, ami nem BSD (a BSD-szerűségek egy kicsit másképp csinálják; ennek kidolgozása egy lehetséges házi feladat). Kivesézzük, mi a baj az ún. SystemV inittel, és megismerünk néhány alternatívát.

Tartalomjegyzék

[szerkesztés] 1 A bootloader

A számítógép bekapcsolás után inicializálja magát (ez architektúránként mást és mást jelent), majd "valahonnan" betölt egy programot, amit elindít. Honnan?

  • konzol :)
  • lyukkártya, lyukszalag :)
  • diszk
  • cd
  • usb
  • hálózat (PXE, házi feladat)

Ez a program a bootloader (vagy egy idő után az lesz). A bootloader feladata betölteni és elindítani az operációs rendszert. Ezt a témát itt most nem lihegjük túl: mindenki látott már LILOt és grubot is.

Az initrd betöltése is a bootloader feladata.

[szerkesztés] 2 A kernel

A kernel inicializálja a hardvert és elindítja az init folyamatot, ami 1-es PID-del fut és ennek köszönhetően számos signalt nem kap meg (a HUP és a PWR signalt igen). Ez initrd használata esetén nem pont így van - init helyett általában egy script indul el, ami betölti a rootfs felmountolásához szükséges modulokat, majd be is mountolja a rootfst, pivot_root-tal odavált, majd egy exec chroot /sbin/init jellegű paranccsal meghívja a tényleges initet.

[szerkesztés] 3 Az init

Az init(8) először is beolvassa a /etc/inittabot. Nézzünk erre egy példát:

# /etc/inittab: init(8) configuration.
# $Id: inittab,v 1.8 1998/05/10 10:37:50 miquels Exp $

# The default runlevel.
id:2:initdefault:

# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS

# What to do in single-user mode.
~~:S:wait:/sbin/sulogin

# /etc/init.d executes the S and K scripts upon change
# of runlevel.
#
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.

l0:0:wait:/etc/init.d/rc 0
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5
l6:6:wait:/etc/init.d/rc 6
# Normally not reached, but fallthrough in case of emergency.
z6:6:respawn:/sbin/sulogin

# What to do when CTRL-ALT-DEL is pressed.
ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now

# Action on special keypress (ALT-UpArrow).
kb::kbrequest:/bin/echo "Keyboard Request--edit /etc/inittab to let this work."

# What to do when the power fails/returns.
pf::powerwait:/etc/init.d/powerfail start
pn::powerfailnow:/etc/init.d/powerfail now
po::powerokwait:/etc/init.d/powerfail stop

# /sbin/getty invocations for the runlevels.
#
# The "id" field MUST be the same as the last
# characters of the device (after "tty").
#
# Format:
#  <id>:<runlevels>:<action>:<process>
1:2345:respawn:/sbin/mingetty tty1
2:23:respawn:/sbin/mingetty tty2
3:23:respawn:/sbin/mingetty tty3
4:23:respawn:/sbin/mingetty tty4
5:23:respawn:/sbin/mingetty tty5
6:23:respawn:/sbin/mingetty tty6

# Example how to put a getty on a serial line (for a terminal)
#
#T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
T1:23:respawn:/sbin/getty -L ttyS1 9600 vt320

# Example how to put a getty on a modem line.
#
#T3:23:respawn:/sbin/mgetty -x0 -s 57600 ttyS3

Az init számos kulcsszót ismer, ezek közül néhány:

  • initdefault: ez az alapértelmezett runlevel, amibe bootkor váltani kell
  • sysinit: ezzel kezdődik a bootfolyamat
  • boot: a sysinit után kell lefuttatni
  • bootwait: mint a boot, de meg is kell várni, amíg végez
  • wait: az adott runlevelbe váltáskor le kell futtatni és meg kell várni, amíg kilép
  • once: az adott runlevelbe váltáskor le kell futtatni
  • respawn: az adott programot újra kell indítani, amikor kilép
  • ondemand: elvileg lehetnének ondemand runlevelek (a, b és c), amikbe "váltáskor" az adott program elindul, de a rendszer amúgy abban a runlevelben marad, amelyikben volt. Használja ezt bárki is?
  • power*: UPS-kezeléssel függ össze, nemigen szokás használni (van helyette pl. nut, ami megér egy házi feladatot)
  • ctrlaltdel: ezt kell lefuttatni, ha a user a konzolnál megnyomta a ctrl+alt+delt (más Unixon hogy van ez?)
  • kbrequest: egy billentyűkombinációhoz hozzárendelhetjük ezt az eseményt; ha a megfelelő gombokat megnyomják, lefut a megadott program. Lehet vele pl. billentyűzetkiosztást váltani. :)

[szerkesztés] 3.1 A runlevel fogalma

A runlevel ("futási szint"?) elvileg véges sok szolgáltatáskombináció közül az egyik.

  • A 0-ás runlevel a rendszerleállítást "szokta" jelenteni
  • Az 1-es a single user módot
  • A 6-os az újraindítást
  • A többit elvileg szabadon definiálhatjuk
    • De minek?
    • Nem jellemző, hogy egy számítógép többféle komplex szolgáltatáscsomag közül hol az egyiket, hol a másikat nyújtaná

A runlevelekhez (a SystemV init esetén) tartozik egy-egy könyvtár, amiben symlinkek vannak az egyes szolgáltatásokat elindító ill. leállító scriptekre.

Runlevelváltáskor először a leállítóscriptek futnak "stop", majd az indítóscriptek "start" paraméterrel.

Ami K-val kezdődik, az leállítóscript, ami S-sel, az indítóscript. Az első betű utáni két szám a sorrendet adja meg.

Pl:

/etc/rc2.d# ls -la
total 7
drwxr-xr-x  2 root root 1024 Sep  6 00:05 .
drwxr-xr-x 69 root root 5120 Oct  4 15:35 ..
-rw-r--r--  1 root root  556 Jul 25 19:52 README
lrwxrwxrwx  1 root root   16 Aug  1 20:12 K01valami -> ../init.d/valami
lrwxrwxrwx  1 root root   18 Aug  1 20:12 S10sysklogd -> ../init.d/sysklogd
lrwxrwxrwx  1 root root   15 Aug  1 20:12 S11klogd -> ../init.d/klogd
lrwxrwxrwx  1 root root   17 Aug  1 20:12 S16openvpn -> ../init.d/openvpn
lrwxrwxrwx  1 root root   17 Aug  1 20:12 S20hddtemp -> ../init.d/hddtemp
lrwxrwxrwx  1 root root   17 Aug  1 20:12 S20makedev -> ../init.d/makedev
lrwxrwxrwx  1 root root   23 Aug 29 18:49 S20openbsd-inetd -> ../init.d/openbsd-inetd
lrwxrwxrwx  1 root root   15 Sep  6 00:05 S20pound -> ../init.d/pound
lrwxrwxrwx  1 root root   15 Aug  1 20:12 S20qmail -> ../init.d/qmail
lrwxrwxrwx  1 root root   15 Aug  1 20:12 S20rsync -> ../init.d/rsync
lrwxrwxrwx  1 root root   23 Aug  1 20:12 S20smartmontools -> ../init.d/smartmontools
lrwxrwxrwx  1 root root   13 Aug  1 20:12 S20ssh -> ../init.d/ssh
lrwxrwxrwx  1 root root   14 Aug 29 17:36 S20xend -> ../init.d/xend
lrwxrwxrwx  1 root root   20 Aug 29 17:36 S21xendomains -> ../init.d/xendomains
lrwxrwxrwx  1 root root   13 Aug 30 11:35 S23ntp -> ../init.d/ntp
lrwxrwxrwx  1 root root   15 Aug 30 18:21 S25mdadm -> ../init.d/mdadm
lrwxrwxrwx  1 root root   17 Aug  1 20:12 S50systune -> ../init.d/systune
lrwxrwxrwx  1 root root   14 Aug  1 20:12 S89cron -> ../init.d/cron
lrwxrwxrwx  1 root root   20 Aug  1 20:12 S98munin-node -> ../init.d/munin-node
lrwxrwxrwx  1 root root   22 Aug  1 20:12 S99firewall -> /etc/firewall/firewall
lrwxrwxrwx  1 root root   19 Aug  1 20:12 S99rmnologin -> ../init.d/rmnologin
lrwxrwxrwx  1 root root   23 Aug  1 20:12 S99stop-bootlogd -> ../init.d/stop-bootlogd
lrwxrwxrwx  1 root root   12 Aug 30 18:39 S99ud -> ../init.d/ud

[szerkesztés] 3.2 Az init tevékenysége

Először elindítja a sysinit jelzéssel ellátott programot; Debianon ez a /etc/init.d/rcS. Nézzük, mit csinál:

#! /bin/sh
#
# rcS
#
# Call all S??* scripts in /etc/rcS.d/ in numerical/alphabetical order
#

exec /etc/init.d/rc S

OK, akkor nézzük a /etc/init.d/rc-t (figyelem, valóságos szörnyszülött):

#! /bin/sh
#
# rc
#
# Starts/stops services on runlevel changes.
#
# Optimization: A start script is not run when the service was already
# configured to run in the previous runlevel.  A stop script is not run
# when the the service was already configured not to run in the previous
# runlevel.
#
# Authors:
#       Miquel van Smoorenburg <miquels@cistron.nl>
#       Bruce Perens <Bruce@Pixar.com>

PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH

# Un-comment the following for debugging.
# debug=echo

# Specify method used to enable concurrent init.d scripts.
# Valid options are 'none', 'shell' and 'startpar'
CONCURRENCY=none

Ez aránylag új: korábban nem lehetett párhuzamosítani az initscriptek futtatását. Most sem feltétlenül jó ötlet.


# Make sure the name survive changing the argument list
scriptname="$0"

umask 022

on_exit() {
    echo "error: '$scriptname' exited outside the expected code flow."
}
trap on_exit EXIT # Enable emergency handler

Ez azért kell, mert így legalább látjuk, ha valahol lukra fut.


# Ignore CTRL-C only in this shell, so we can interrupt subprocesses.
trap ":" INT QUIT TSTP

# Set onlcr to avoid staircase effect.
stty onlcr 0>&1

Megkérjük a terminált, hogy minden soremelés után írjon ki egy kocsivisszát is.


# Decide if usplash progress bar should be activated or not.  Override
# in /etc/default/rcS if required.
if type usplash_write >/dev/null 2>&1; then
    SPLASH=true
else
    SPLASH=false
fi

Nagyon fontos: grafikus splashscreen támogatása, színes-szagos bitkolbásszal...


# Now find out what the current and what the previous runlevel are.

runlevel=$RUNLEVEL
# Get first argument. Set new runlevel to this argument.
[ "$1" != "" ] && runlevel=$1
if [ "$runlevel" = "" ]
then
        echo "Usage: $scriptname <runlevel>" >&2
        exit 1
fi
previous=$PREVLEVEL
[ "$previous" = "" ] && previous=N

export runlevel previous

if [ S = "$runlevel" ]
then
        #
        # See if system needs to be setup. This is ONLY meant to
        # be used for the initial setup after a fresh installation!
        #
        if [ -x /sbin/unconfigured.sh ]
        then
                /sbin/unconfigured.sh
        fi
fi

. /etc/default/rcS
export VERBOSE

Itt a /etc/default/rcS file-ból konfigurációs változókat olvasunk be. Ezek közül az egyik a VERBOSE, amit a gyermekfolyamatainkkal is meg akarunk osztani.


#
# Stub to do progress bar ticks (currently just for usplash) on startup
#
startup_progress() {
    $@
    if [ "$SPLASH" = true ] ; then
        step=$(($step + $step_change))
        progress=$(($step * $progress_size / $num_steps + $first_step))
        usplash_write "PROGRESS $progress" || true
    fi
}

#
# Start script or program.
#

Most fogjuk definiálni a startup() függvényt aszerint, hogy mi az értéke a CONCURRENCY változónak:

case "$CONCURRENCY" in
  none)
        startup() {
                action=$1
                shift
                scripts="$@"
                sh=sh
                # Debian Policy §9.3.1 requires .sh scripts in runlevel S to be sourced
                # However, some important packages currently contain .sh scripts
                # that do "exit" at some point, thus killing this process.  Bad!
                #[ S = "$runlevel" ] && sh=.
                for script in $scripts ; do
                        case "$script" in
                          *.sh)
                                if [ "." = "$sh" ] ; then
                                        set "$action"

Ez itt azt csinálta, hogy az "$action"-t írta a $1-be; ezután majd source-olja a meghívandó scriptet.

                                        RC_SAVE_PATH="$PATH"
                                        startup_progress $debug . "$script"
                                        PATH="$RC_SAVE_PATH"

A PATH-os trükközés azért kell, hogy a meghívott script - ami ugyanebben a shellben fut - ne tudja elbarmolni a PATH változót.

                                else
                                        startup_progress $debug $sh "$script" $action
                                fi
                                ;;
                          *)
                                startup_progress $debug "$script" $action
                                ;;
                        esac
                done
        }
        ;;
  shell)
        startup() {
                action=$1
                shift
                scripts="$@"
                sh=sh
                # Debian Policy §9.3.1 requires .sh scripts in runlevel S to be sourced
                # However, some important packages currently contain .sh scripts
                # that do "exit" at some point, thus killing this process.  Bad!
                #[ S = "$runlevel" ] && sh=.
                backgrounded=0
                for script in $scripts ; do
                        case "$script" in
                          *.sh)
                                if [ "." = "$sh" ] ; then
                                        set "$action"
                                        RC_SAVE_PATH="$PATH"
                                        startup_progress $debug . "$script"
                                        PATH="$RC_SAVE_PATH"
                                else
                                        startup_progress $debug $sh "$script" $action
                                fi
                                ;;
                          *)
                                startup_progress $debug "$script" $action &
                                backgrounded=1
                                ;;
                        esac
                done
                [ 1 = "$backgrounded" ] && wait

Itt a nem .sh végű initscripteket párhuzamosan elindítja a háttérben, majd megvárja, amíg mind kilép.

        }
        ;;
  startpar)
        startup() {
                action=$1
                shift
                scripts="$@"
                sh=sh
                # Debian Policy §9.3.1 requires .sh scripts in runlevel S to be sourced
                # However, some important packages currently contain .sh scripts
                # that do "exit" at some point, thus killing this process.  Bad!
                #[ S = "$runlevel" ] && sh=.
                # Make sure .sh scripts are sourced in runlevel S
                if [ "." = "$sh" ] ; then
                        newscripts=
                        for script in $scripts ; do
                                case "$script" in
                                  *.sh)
                                        set "$action"
                                        RC_SAVE_PATH="$PATH"
                                        startup_progress $debug . "$script"
                                        PATH="$RC_SAVE_PATH"
                                        ;;
                                  *)
                                        newscripts="$newscripts $script"
                                        ;;
                                esac
                        done
                        scripts="$newscripts"

scripts itt már csak azokat a scripteket tartalmazza, amiket nem tudunk source-olni.

                fi

                # startpar is not able to handle time jumps.  So the
                # hwclock.sh scripts should not be executed from
                # within startpar.  The .sh hack above make this
                # problem irrelevant. [pere 2005-09-10]
                [ -n "$scripts" ] && startup_progress $debug startpar -a $action $scripts

A startpar párhuzamosan lefuttatja a megadott scripteket, és ügyesen összefésüli a kimenetüket. Azt érezzük, hogy ez itt desktop-gányolás - kit érdekel, milyen gyorsan bootol be a szerver? Úgyse rebootoljuk minden évben... :)

        }
        ;;
esac

# Is there an rc directory for this new runlevel?
if [ -d /etc/rc$runlevel.d ]

Megnézzük, van-e rc1.d, rc2.d stb. jellegű könyvtár ahhoz a runlevelhez, amibe váltanunk kell.

then
        # Find out where in the progress bar the initramfs got to.
        PROGRESS_STATE=0
        if [ -f /dev/.initramfs/progress_state ]; then
            . /dev/.initramfs/progress_state
        fi

        # Split the remaining portion of the progress bar into thirds
        progress_size=$(((100 - $PROGRESS_STATE) / 3))

Ezek megint fontos bitkolbászos teendők.

        case "$runlevel" in
                0|6)
                        ACTION=stop
                        # Count down from 0 to -100 and use the entire bar
                        first_step=0
                        progress_size=100
                        step_change=-1
                        ;;
                S)
                        ACTION=start
                        # Begin where the initramfs left off and use 2/3
                        # of the remaining space
                        first_step=$PROGRESS_STATE
                        progress_size=$(($progress_size * 2))
                        step_change=1
                        ;;
                *)
                        ACTION=start
                        # Begin where rcS left off and use the final 1/3 of
                        # the space (by leaving progress_size unchanged)
                        first_step=$(($progress_size * 2 + $PROGRESS_STATE))
                        step_change=1
                        ;;
        esac

        if [ "$SPLASH" = true ] ; then
            # Count the number of scripts we need to run (for usplash
            # progress bar)
            num_steps=0
            for s in /etc/rc$runlevel.d/[SK]*; do
                case "${s##/etc/rc$runlevel.d/S??}" in
                 gdm|xdm|kdm|reboot|halt)
                    break
                    ;;
                esac
                num_steps=$(($num_steps + 1))
            done
            step=0
        fi

        # First, run the KILL scripts.
        if [ "$previous" != N ]
        then
                # Run all scripts with the same level in parallel
                CURLEVEL=""
                for s in /etc/rc$runlevel.d/K*

Végigmegyünk az összes, az adott runlevelhez tartozó K-val kezdődő nevű scripten.

                do
                        level=$(echo $s | sed 's/.*\/K\([0-9][0-9]\).*/\1/')

Csak a K betű után álló két számot nézzük.

                        if [ "$level" = "$CURLEVEL" ]
                        then
                                continue
                        fi

Ha ez a sorszám már volt, akkor ugrunk a következő scriptre.

                        CURLEVEL=$level
                        SCRIPTS=""
                        for i in /etc/rc$runlevel.d/K$level*

Itt pedig "egyszerre" le fogjuk futtatni az összes olyat, ami az aktuális sorszámot viseli.

                        do
                                # Check if the script is there.
                                [ ! -f $i ] && continue

                                #
                                # Find stop script in previous runlevel but
                                # no start script there.
                                #
                                suffix=${i#/etc/rc$runlevel.d/K[0-9][0-9]}
                                previous_stop=/etc/rc$previous.d/K[0-9][0-9]$suffix
                                previous_start=/etc/rc$previous.d/S[0-9][0-9]$suffix
                                #
                                # If there is a stop script in the previous level
                                # and _no_ start script there, we don't
                                # have to re-stop the service.
                                #
                                [ -f $previous_stop ] && [ ! -f $previous_start ] && continue

Hurrá! Ennyi szüttyögéssel kioptimalizáltunk egy scriptfuttatást! Halleluja!

                                # Stop the service.
                                SCRIPTS="$SCRIPTS $i"
                        done
                        startup stop $SCRIPTS

Ez ugyebár az a startup() függvény, amit fent okosan egy case utasításon belül deklaráltunk, valószínűleg azért, hogy ha valamelyik source-olt script játszik a CONCURRENCY változó értékével, akkor ne változzon meg a startup() függvény viselkedése. Persze a script új startup() függvényt is definiálhat...

                done
        fi

        # Now run the START scripts for this runlevel.
        # Run all scripts with the same level in parallel

Ez most ugyanaz lesz, mint az előbb, csak az S-sel kezdődő nevű scriptekre.

        CURLEVEL=""
        step=0
        for s in /etc/rc$runlevel.d/S*
        do
                level=$(echo $s | sed 's/.*\/S\([0-9][0-9]\).*/\1/')
                if [ "$level" = "$CURLEVEL" ]
                then
                        continue
                fi
                CURLEVEL=$level
                SCRIPTS=""
                for i in /etc/rc$runlevel.d/S$level*
                do
                        [ ! -f $i ] && continue

                        if [ "$previous" != N ]
                        then
                                #
                                # Find start script in previous runlevel and
                                # stop script in this runlevel.
                                #
                                suffix=${i#/etc/rc$runlevel.d/S[0-9][0-9]}
                                stop=/etc/rc$runlevel.d/K[0-9][0-9]$suffix
                                previous_start=/etc/rc$previous.d/S[0-9][0-9]$suffix
                                #
                                # If there is a start script in the previous level
                                # and _no_ stop script in this level, we don't
                                # have to re-start the service.
                                #
                                [ -f $previous_start ] && [ ! -f $stop ] && continue
                        fi
                        SCRIPTS="$SCRIPTS $i"
                done
                startup $ACTION $SCRIPTS
        done
fi

if [ S = "$runlevel" ]
then
        #
        # For compatibility, run the files in /etc/rc.boot too.
        #
        [ -d /etc/rc.boot ] && run-parts /etc/rc.boot

        #
        # Finish setup if needed. The comment above about
        # /sbin/unconfigured.sh applies here as well!
        #
        if [ -x /sbin/setup.sh ]
        then
                /sbin/setup.sh
        fi
fi

trap - EXIT # Disable emergency handler

exit 0

Az első runlevel, amibe váltunk, az S. Nézzük, mi történik ebben a runlevelben egy átlagos szerveren:

/etc/rcS.d# ls -la
total 7
drwxr-xr-x  2 root root 1024 Sep  4 16:35 .
drwxr-xr-x 69 root root 5120 Oct  4 15:35 ..
-rw-r--r--  1 root root  785 Jul 25 19:51 README
lrwxrwxrwx  1 root root   24 Aug  1 20:12 S02mountkernfs.sh -> ../init.d/mountkernfs.sh
lrwxrwxrwx  1 root root   18 Aug  1 20:12 S04bootlogd -> ../init.d/bootlogd
lrwxrwxrwx  1 root root   26 Aug  1 20:12 S04mountdevsubfs.sh -> ../init.d/mountdevsubfs.sh
lrwxrwxrwx  1 root root   16 Aug 30 17:35 S07hdparm -> ../init.d/hdparm
lrwxrwxrwx  1 root root   22 Aug  1 20:12 S10checkroot.sh -> ../init.d/checkroot.sh
lrwxrwxrwx  1 root root   14 Aug 31 19:23 S10udev -> ../init.d/udev
lrwxrwxrwx  1 root root   25 Aug 29 18:19 S18hwclockfirst.sh -> ../init.d/hwclockfirst.sh
lrwxrwxrwx  1 root root   24 Aug  1 20:12 S18ifupdown-clean -> ../init.d/ifupdown-clean
lrwxrwxrwx  1 root root   27 Aug  1 20:12 S20module-init-tools -> ../init.d/module-init-tools
lrwxrwxrwx  1 root root   18 Aug  1 20:12 S20modutils -> ../init.d/modutils
lrwxrwxrwx  1 root root   20 Aug 29 18:19 S22hwclock.sh -> ../init.d/hwclock.sh
lrwxrwxrwx  1 root root   26 Aug  1 20:12 S25libdevmapper1.02 -> ../init.d/libdevmapper1.02
lrwxrwxrwx  1 root root   20 Aug 30 18:21 S25mdadm-raid -> ../init.d/mdadm-raid
lrwxrwxrwx  1 root root   13 Aug  1 20:12 S26lvm -> ../init.d/lvm
lrwxrwxrwx  1 root root   20 Aug  1 20:12 S30checkfs.sh -> ../init.d/checkfs.sh
lrwxrwxrwx  1 root root   19 Aug  1 20:12 S30procps.sh -> ../init.d/procps.sh
lrwxrwxrwx  1 root root   21 Aug  1 20:12 S35mountall.sh -> ../init.d/mountall.sh
lrwxrwxrwx  1 root root   31 Aug  1 20:12 S36mountall-bootclean.sh -> ../init.d/mountall-bootclean.sh
lrwxrwxrwx  1 root root   17 Aug  1 20:12 S36mtab.sh -> ../init.d/mtab.sh
lrwxrwxrwx  1 root root   18 Aug  1 20:12 S39ifupdown -> ../init.d/ifupdown
lrwxrwxrwx  1 root root   21 Aug  1 20:12 S40hostname.sh -> ../init.d/hostname.sh
lrwxrwxrwx  1 root root   20 Aug  1 20:12 S40networking -> ../init.d/networking
lrwxrwxrwx  1 root root   21 Aug  1 20:12 S45mountnfs.sh -> ../init.d/mountnfs.sh
lrwxrwxrwx  1 root root   31 Aug  1 20:12 S46mountnfs-bootclean.sh -> ../init.d/mountnfs-bootclean.sh
lrwxrwxrwx  1 root root   20 Aug 29 18:23 S47lm-sensors -> ../init.d/lm-sensors
lrwxrwxrwx  1 root root   17 Aug  1 20:12 S51ntpdate -> ../init.d/ntpdate
lrwxrwxrwx  1 root root   21 Aug  1 20:12 S55bootmisc.sh -> ../init.d/bootmisc.sh
lrwxrwxrwx  1 root root   17 Aug  1 20:12 S55urandom -> ../init.d/urandom
lrwxrwxrwx  1 root root   17 Aug  1 20:12 S70nviboot -> ../init.d/nviboot
lrwxrwxrwx  1 root root   24 Aug  1 20:12 S70screen-cleanup -> ../init.d/screen-cleanup
lrwxrwxrwx  1 root root   14 Aug 29 18:22 S75sudo -> ../init.d/sudo
lrwxrwxrwx  1 root root   30 Aug  1 20:12 S99stop-bootlogd-single -> ../init.d/stop-bootlogd-single

Miután ezek lefutottak, mehetünk a 2-es runlevelbe (listát l. fent).

Ha a runlevelváltás sikerült, elindulnak a mingetty-k és beléphetünk a konzolon.

[szerkesztés] 3.3 Az init használata

  • Van egy -b opciója, aminek a hatására a sysinitet sem hajtja végre, hanem egyből single user módba megy
  • init u hatására az init végrehajt egy exec('/sbin/init')-et - így lehet frissíteni
  • init q vagy HUP signal hatására újraolvassa az inittabot
  • init runlevel hatására az adott runlevelbe vált

[szerkesztés] 4 Bajok az inittel

  • Aránylag bonyolult maga az init program; ellenkezik a Unix-filozófiával, hogy több mindent csinál.
    • Elég lenne, ha le tudná futtatni, ami kell a rendszerinduláshoz
    • A respawnt is csinálhatná külső program
    • UPS-kezelést pláne
  • Minden shellscriptes trükközés ellenére lassú
  • Nem indítja újra a szolgáltatásokat, ha kilépnek (kivéve, ha az inittabból futtatjuk őket)
  • De a respawnoló szolgáltatásokat egyáltalán nem lehet szelektíven leállítani - csak runlevelváltással
    • Viszont ahhoz kevés a használható runlevel, hogy minden reális szolgáltatáskombinációhoz külön legyen egy
  • Az initscript örökli az őt indító shell környezetét
    • Vagyis parancssorból indítva a szolgáltatást nem biztos, hogy ugyanazt csinálja majd, mint bootkor
  • Nem triviális leállítani egy szolgáltatást, vagy signalt küldeni neki

[szerkesztés] 4.1 Szolgáltatások leállítása á la init

A szolgáltatás leállításához általában valamilyen signalt (pl. TERM) kell küldeni a szolgáltatáshoz tartozó processznek.

Csakhogy: mi a PID-ja ennek a processznek?

A "megoldás": pidfile. De:

  • A pidfile és aközött a processz között, amelynek a PID-ját tartalmazza, nincs semmilyen kapcsolat; nincs olyan mechanizmus, ami a pidfile tartalmának naprakészségét versenyhelyzetmentesen szavatolná vagy szavatolni tudná.
    • A pidfile-t le lehet törölni, felül lehet írni.
  • Nem minden program tud saját maga pidfile-t létrehozni.
    • A start-stop-daemon meg tudja tenni helyettük, de komplex, több processzből álló alkalmazás esetén nem biztos, hogy a megfelelőnek a PID-ja kerül a fájlba;
    • és az is lehet, hogy később válik érvénytelenné a PID, amikor mondjuk az alkalmazás forkol egyet.
  • Ha a pidfile helye konfigolható, indítás óta megváltozhatott a konfig, és talán már nem aktuális a pidfile elérési útja.
  • Összetett configfile esetén az initscript esetleg nem találja meg az érvényes pidfile-beállítást (pl. apache2).
    • Vagyis: ha a pidfile-ból ki is olvasunk egy természetes számot, meg kell(ene) győződni arról, hogy az valóban a leállítandó szolgáltatáshoz tartozó processz PID-ja-e.
    • Itt viszont versenyhelyzetbe kerülünk, mert a szám kiolvasása, hitelesítése és a signal elküldése között az eredeti processz kiléphet, és másik kaphatja meg a PID-ját.
    • Még akkor is fennáll a versenyhelyzet, ha nem hitelesítjük a PID-t sehogy (ez amúgy rossz ötlet), mert a kiolvasás-signalküldés nem atomi művelet:
      • a kiolvasás és a signalküldés között eltűnhet az eredeti processz és létrejöhet ugyanolyan PID-val másik;
      • vagy csak eltűnhet az, aminek a signalt küldeni akartuk, de az is elég baj.

Így aztán ilyen megoldások születnek (apache2, Ubuntu, 2006. körül):

ENV="env -i LANG=C PATH=/usr/local/bin:/usr/bin:/bin"
APACHE2="$ENV /usr/sbin/apache2"
APACHE2CTL="$ENV /usr/sbin/apache2ctl"
  • Környezettisztítás, de hasztalan:
  • Pl. mi lesz az ulimitekkel?
  • LD_*? (Az env -i az esetek döntő részében szerencsére megszabadul ezektől a változóktól, de az ördög nem alszik...)
  • umask?
  • scheduler?
  • fájldeszkriptorok?
  • ...
apache_stop() {
  PID=""
  PIDFILE=""
  AP_CONF=/etc/apache2/apache2.conf

  # apache2 allows more than PidFile entry in the config but only the
  # last found in the config is used; we attempt to follow includes
  # here, but only first-level includes are supported, not nested ones

  for i in $AP_CONF `awk '$1 ~ /^\s*[Ii]nclude$/ && $2 ~ /^\// {print $2}' $AP_CONF`; do
    PIDFILE=`grep -i ^PidFile $i | tail -n 1 | awk '{print $2}'`
    if [ -e "$PIDFILE" ]; then
      PID=`cat $PIDFILE`
    fi
  done
  • Vagyis, ha többszintű include-rendszer volt, már itt elvéreztünk.
  • Az az awk-script az include-ok közül csak azokat találja meg, amelyek abszolút elérési úttal vannak megadva.
    • A relatív útvonalak a ServerRoot-hoz képest értendők, azt pedig az utolsó ServerRoot direktíva adja meg...
  • Az apache támogatja alkönyvtárak include-olását is: ha az include kulcsszó paramétere egy könyvtárnév, az apache a benne levő összes fájlt konfigurációs fájlnak tekinti és beolvassa, ráadásul rekurzívan. Ezt az esetet a fenti, a faékével vetekedő összetettségű awk-script meg sem kísérli kezelni.
    • Rekurzív shell-függvénnyel megoldható lenne az apache-konfiguráció feldolgozása, akárhányszintű include-rendszer, többször változó ServerRoot-beállítás és alkönyvtárak include-olása esetén is, de biztos szeretnénk egy ilyen függvényt látni az initscriptünkben? (A megírása egyébként jó shell-gyakorlat.)
  errors=`$APACHE2 -t 2>&1`

Vajon hibátlan-e a config? Kérdezzük meg magát az apache-t, elvégre ő tudja ezt a legjobban.

  • Az apache hibás configgal nem indul.
  • Vagyis ha a config hibás, de az apache fut, akkor a configot indítás után módosítottuk, vagy mindenesetre nem azzal a configgal indítottuk el, amelyiket az initscript nézeget.
  • A megtalált PidFile direktíva akár kerülhetett oda az indítás után is.
  • Ha indítás óta módosítottuk a configot, de nem hibás, a script sehonnan se tudja meg.
  if [ $? = 0 ]; then
    # if the config is ok then we just stop normally

    if [ -n "$PID" ]; then
      $APACHE2CTL stop

Ez vajon mit csinál? Tömören:

open("/var/run/apache2.pid", O_RDONLY)  = 4
read(4, "9155\n", 13)                   = 5
read(4, "", 8)                          = 0
close(4)                                = 0
kill(9155, SIG_0)                       = 0
kill(9155, SIGTERM)                     = 0

Vagyis majdnem vakon bízik a pidfile-ban, és itt is van versenyhelyzet. Azt azért el kell ismerni, hogy az apache2ctl legalább elolvassa az apache2 teljes konfigurációját és a megfelelő pidfájlból olvassa ki a kilövendő PIDet, az initscripttel ellentétben.

Vissza a scriptre:

      CNT=0
      while [ 1 ]; do
        CNT=$(expr $CNT + 1)
        [ ! -d /proc/$PID ] && break
  • Ha kilépett a pidfájlban szereplő PID, mi is kilépünk
  • De: lehet hogy kilépett, és közben már indult azzal a PID-val más
  • Ekkor esetleg sosem lépünk ki, vagy mégis?
        if [ $CNT -gt 60 ]; then
          if [ "$VERBOSE" != "no" ]; then
            echo " ... failed!"
            echo "Apache2 failed to honor the stop command, please investigate the situation by hand."
          fi
          return 1
        fi
        sleep 1
      done
  • Ha elbukjuk a fenti versenyhelyzetet, azért még kiírunk egy félrevezető hibaüzenetet, de legalább hibát jelezve visszatérünk az apache_stop() függvényből.

Nézzük, mi történik, ha üres maradt a PID változó (ez akkor fordul elő, ha üres a pidfájl, amit beolvasunk, vagy nem is létezik):

    else
      if [ "$VERBOSE" != "no" ]; then
        echo -n " ... no pidfile found! not running?"
      fi
    fi
  • Nem volt pidfile, jaj-jaj!
  • Ki is lépünk, nem csinálunk semmit;
  • pedig lehet, hogy pl. éppen a pidfájl helyét módosítottuk a configban -- nem érvényes az a következtetés, hogy az apache nem fut.

Lássuk, mi a helyzet, ha hibás volt a config. A script vad találgatásba kezd a futó apache2 PID-ját illetően:

  else
    [ "$VERBOSE" != "no" ] && echo "$errors"

    # if we are here something is broken and we need to try
    # to exit as nice and clean as possible

    # if pidof is null for some reasons the script exits automagically
    # classified as good/unknown feature

    PIDS=`pidof apache2` || true

    REALPID=0
    # if there is a pid we need to verify that belongs to apache2
    # for real
    for i in $PIDS; do
      if [ "$i" = "$PID" ]; then
        # in this case the pid stored in the
        # pidfile matches one of the pidof apache
        # so a simple kill will make it
        REALPID=1
      fi
    done

    if [ $REALPID = 1 ]; then
      # in this case everything is nice and dandy
      # and we kill apache2
      kill $PID

Mi történt?

  • Kerestünk apache2 nevű processzeket.
    • Amúgy a pidof(8) is írja, hogy teljes path-t kéne használni ilyenkor.
    • Lehet, hogy több apache2 példányunk is van, ekkor itt az összeset megtaláljuk.
  • Ha a megtalált PID-k közül valamelyik szerepelt a pidfile-ban, kilőjük (vagy legalábbis küldünk neki egy TERM signalt).
  • De: hibás volt a config!
    • Hibás configgal az apache el se indul, ugye.
    • Tehát a futó apache nem azzal a configgal indult.
    • Tehát ki tudja, mit olvastunk ki a pidfile-ból.
    • Arról nem is beszélve, hogy mikor; a pidfile olvasása óta már egy csomó idő eltelt.
      • Processzek léphettek ki és születhettek.
      • A pidfile tartalma is megváltozhatott, amióta utoljára belenéztünk - pl. közben egy másik shellből indítva lefutott egy apache2 restart.
      • Ráadásul a pidof és a kill között is eltelik valamennyi idő
      • A múltban élünk...

Vagyis: igazából fogalmunk sincs, miféle processznek küldjük a TERM signalt. Az történt, hogy találtunk egy szerencsétlen, apache2 nevű folyamatot, amely rossz helyen volt a rossz időben – a PID-ja véletlenül megegyezett egy olyan PID-val, amit egy szintaktikailag hibás apache2-konfiguráció tökéletlen elemzése révén nyert ismeretlen eredetű pidfájlból olvastunk ki bizonytalan idővel korábban. Gyorsan le is lőttük: túl sokat tudott. Különben sem hiszünk a véletlenekben. Vesznie kellett, ez volt a sorsa. Ez volt megírva a csillagokban. Hatalmas karmikus teher nehezedett rá.

Na és mi van, ha a pidof nem talál áldozatjelöltet?

    else
      # this is the worst situation... just kill all of them
      #for i in $PIDS; do
      #       kill $i
      #done
      # Except, we can't do that, because it's very, very bad
      if [ "$PIDS" ] && [ "$VERBOSE" != "no" ]; then
        echo " ... failed!"
        echo "You may still have some apache2 processes running.  There are"
        echo "processes named 'apache2' which do not match your pid file,"
        echo "and in the name of safety, we've left them alone.  Please review"
        echo "the situation by hand."
      fi
      return 1
    fi
  fi
}
  • Itt azért győzött a józanság szava... egyelőre :) nem irtunk ki minden olyan processzt, amelynek PID-jával valamennyi idővel ezelőtt egy apache2 nevű folyamat rendelkezett.

A gond: a daemonizálódás.

  • Ha az apache2 szépen az előtérben maradna, a szülője
    • tudná a PID-ját és
    • tudna neki signalt küldeni.

Alternatívát kínál:

[szerkesztés] 5 Ajánlott irodalom

[szerkesztés] 6 Potenciális ZH-kérdések

  • Soroljon fel legalább három különböző kulcsszót a /etc/inittabból; mire valók, mikor szokás őket használni? (Pl: "A kbrequest opcióval megadott program akkor fog lefutni, amikor a konzolon lenyomjuk azt a billentyűkombinációt, amihez a 'kbrequest' kódját rendeltük; alapértelmezés szerint nincs ilyen, és a kbrequestnek alapértelmezett felhasználása sincs. Lehet pl. billentyűzetkiosztás-váltásra, vagy a cd unmountolására és kidobására használni.")
  • Mi a runlevel?
  • Ismertesse az rc.d-mechanizmus működését! Mikor érdemes használni? Mik az előnyei (és mihez képest)?
  • Hogyan történik a runlevelek közötti váltás System V init használata esetén? (Nem az a kérdés, hogyan kell kiváltani, hanem hogy milyen folyamat játszódik le runlevel-váltás közben.)
  • Milyen problémák vannak a System V inittel?
  • Miért nehéz a System V init használata esetén egy daemonizált szolgáltatás megbízható, robusztus leállítása? Milyen módszerrel próbálják enyhíteni a problémát, és mi a baj ezzel a módszerrel? Hogyan foglalná össze, mi a probléma gyökere? Hivatkozhat az előadáson megismert konkrét példákra.
  • Magyarázza el saját szavaival, mit jelent a „versenyhelyzet” a szerverüzemeltetés kontextusában! Milyen esetekben kell különösen odafigyelni a versenyhelyzetek elkerülésére, és miért? Mondjon konkrét (akár fiktív) példát is!
Személyes eszközök