27.6 init
Nachdem der Kernel seinen Startup-Vorgang erfolgreich beendet hat, startet er den ersten Prozess /sbin/init. Diesem wird dabei immer die Prozess-ID 1 zugewiesen.
init wird auch als parent of all processes, also als Elternprozess aller Prozesse bezeichnet, was er mit Ausnahme seiner selbst auch ist. Der Grund dafür liegt darin, dass init alle weiteren Prozesse dadurch erstellt, dass er sich kopiert (forkt). Die erzeugten Prozesse sind somit »Kinder« (engl. children) von init. Diese Child-Prozesse können dann wiederum weitere Prozesse durch Forking erzeugen, so dass der Kernel nur den init-Prozess wirklich erstellen (und nicht kopieren) muss. [Fn. Letztlich ist die Prozesserzeugung und das Forking natürlich ein kernelinterner Prozess, doch ruft der jeweilige Prozess den entsprechenden Syscall fork() auf.]
Die Hauptaufgabe von init besteht allerdings darin, jene Prozesse zu starten, die das System initialisieren und sogenannte Runlevel-Skripte ausführen. Außerdem startet es jene Prozesse, die die Terminal-Zugriffe ermöglichen.
Bei Runlevel-Skripten handelt es sich um Skripte, die beim Einleiten und Verlassen eines bestimmten Runlevels ausgeführt werden. In der Regel sind diese in der Syntax der Bourne-Shell verfasst. Runlevel sind eine Entwicklung, die aus System V Release 4 stammt und nicht nur unter Linux und BSD, sondern auch unter Solaris zum Einsatz kommt.
Ein Runlevel ist ein Systemstatus, bei dem bestimmte Dienste laufen beziehungsweise nicht laufen. Entsprechend starten/stoppen Runlevel-Skripte jeweils eine Reihe von Diensten, die zum jeweiligen Runlevel gehören.
Ein Init-Skript startet beziehungsweise stoppt einen ganz bestimmten Dienst. Ein Runlevel-Skript wird also eine Reihe von Init-Skripten aufrufen, um entsprechend Dienste zu starten oder zu stoppen.
Es gibt zum Beispiel einen Runlevel, bei dem nur der Administrator Zugriff auf das System hat. Einen weiteren Runlevel gibt es für den Shutdown des Systems [Fn. Speziell in diesem Runlevel laufen natürlich keine Dienste. Dementsprechend werden alle Dienste beim Eintritt in diesen Runlevel gestoppt und das System damit »sanft« heruntergefahren.], einen für konsolenbasierte Multiuser-Zugriffe und einen für Multiuser-Zugriffe inklusive zusätzlichem grafischem Login – etwa per GDM. Wir werden uns diese Runlevel am Beispiel von Slackware-Linux im Folgenden genauer ansehen.
Unter BSD ist das Ganze allerdings anders angelegt. Dort gibt es keine klassischen Runlevel, sondern nur den Single-User- und den Multiuser-Mode. Möchte man einen bestimmten Dienst starten bzw. nicht starten, wird dies in der Konfiguration festgelegt. Um beispielsweise einen grafischen Login zu verwenden, wird – je nach Derivat – einfach nur die entsprechende Variable in der rc-Konfiguration gesetzt.
27.6.1 Linux und init
Zunächst werden wir uns mit dem Bootsystem von Linux und damit auch mit dem Runlevel-System beschäftigen. Standardmäßig gab es unter Unix sechs Runlevel. Diese wurden mittlerweile erweitert, wobei die Erweiterungen Runlevel 7 bis 9 praktisch keine Verwendung finden. Zusätzlich gibt es für jeden Level noch einen alternativen Namen, wie beispielsweise »S« für den Single-User-Mode.
Typische Runlevel
Typischerweise werden die klassischen Runlevel folgendermaßen benutzt: [Fn. Ausnahmen bilden einige von Distribution zu Distribution und unter den einzelnen Unix-Derivaten gewachsene Unterschiede.]
- 0 – halt
Das System wird angehalten und kann anschließend ausgeschaltet werden. Wird Powermanagement benutzt, schaltet sich der Rechner von selbst ab. - 1 – Single-User-Modus
Nur der Administrator hat Zugriff auf den Rechner. Als Benutzerschnittstelle dient dabei die Standardkonsole (/dev/console) mit dem Tool sulogin. init versucht dabei die Konsole gemäß den Einstellungen in /etc/ioctl.save zu konfigurieren. - Ist diese Datei nicht vorhanden, wird die Konsole als 9600-Baud-Schnittstelle initialisiert. Nachdem init den Single-User-Modus wieder verlassen hat, speichert es die Konsoleneinstellungen in /etc/ioctl.save ab, um sie beim nächsten Eintritt in den Single-User-Modus wieder laden zu können.
- 3 – Multiuser-Modus
In diesem Modus wird das System zur allgemeinen Benutzung freigegeben. Dies bedeutet, dass alle Dateisysteme eingehängt und Terminal-Schnittstellen initialisiert werden. - 4 – GUI
Dieser Runlevel verhält sich wie Runlevel 3, fügt aber zusätzlich den grafischen Login via KDM, GDM oder XDM hinzu. - 6 – reboot
Das System wird heruntergefahren und neu gestartet.
inittab
Damit init weiß, was es tun und in welchem Runlevel das System hochgefahren werden soll, gibt es die Datei /etc/inittab.
Der Aufbau der inittab gestaltet sich ähnlich wie der von /etc/passwd: In einer Datenzeile werden die einzelnen Attribute des Eintrags durch Doppelpunkte voneinander separiert:
- ID
Die erste Spalte definiert dabei eine ID, die aus 1 bis 4 Zeichen bestehen muss und zur Identifikation der Aktion dient. - Runlevel
Darauf folgt der Runlevel, der für diese Aktion aufgerufen werden soll. Diese Spalte kann auch leer bleiben. - Aktion
Die dritte Spalte legt die Aktion selbst fest. Dabei wird ein Schlüsselwort angegeben, um init zu vermitteln, was getan werden soll. Mögliche Werte sind dabei: - respawn
Der Prozess wird neu gestartet, wenn er terminiert. - wait
Der Prozess wird nur einmal gestartet, und init wartet auf seine Beendigung. - once
Der Prozess wird nur einmal gestartet, und zwar dann, wenn der entsprechende Runlevel eingeleitet wird. - boot
Der Prozess wird während des Bootstrap-Vorgangs ausgeführt. Das Runlevel-Feld wird hierbei nicht beachtet. - bootwait
Der Prozess wird während des Bootstrap-Vorgangs ausgeführt, und init wartet auf seine Beendigung. - off
Der Prozess wird nicht ausgeführt. - ondemand
Der Prozess wird nur beim Aufruf eines ondemand-Runlevels gestartet. Dabei handelt es sich bei Slackware um die Runlevel a, b und c. Bei einem ondemand-Runlevel wird der eigentliche Runlevel nicht verlassen. - initdefault
Dieses Schlüsselwort legt den Runlevel fest, der nach dem Startvorgang automatisch eingeleitet werden soll. - sysinit
Der Prozess wird beim Bootstrap-Vorgang, jedoch noch vor den boot(wait)- Prozessen, ausgeführt. Auch hierbei wird das Runlevel-Feld ignoriert. - powerwait
Der Prozess wird ausgeführt, wenn die Energieversorgung abbricht. Dies funktioniert in Verbindung mit einer unterbrechungsfreien Stromversorgung (USV). Es wird auf die Beendigung des Prozesses gewartet, bevor init mit seinen Tätigkeiten fortführt. - powerfail
Diese Aktion verhält sich wie powerwait mit dem Unterschied, dass init nicht die Beendigung des Prozesses abwartet. - powerokwait
Der Prozess wird unmittelbar ausgeführt, nachdem init weiß, dass die Stromversorgung wiederhergestellt ist. Auch dies funktioniert nur in Verbindung mit der oben erwähnten USV. - powerfailnow
Der Prozess wird ausgeführt, wenn die USV meldet, dass die USV-interne Stromversorgung fast erschöpft ist und kein neuer Strom-Input zur Verfügung steht. - ctrlaltdel
Der Prozess wird ausgeführt, wenn init das SIGINT-Signal empfängt. Dies passiert genau dann, wenn jemand an der Systemkonsole die berühmte Tastenkombination Strg + Alt + Entf gedrückt hat. - kbrequest
Der Prozess wird ausgeführt, wenn eine spezielle Tastenkombination gedrückt wurde. - Kommando
Das letzte Feld legt schließlich fest, welcher Prozess gestartet werden soll.
[»]Für die noch etwas tiefgründiger Interessierten: Wenn init einen Prozess starten soll, prüft es zunächst, ob das Skript /etc/initscript existiert. Ist dies so, wird es zum Start des Prozesses verwendet.
Im Folgenden ist die Datei inittab von Slackware-Linux zu sehen: Der Standard-Runlevel (initdefault) ist Runlevel 3, also der Multiuser-Modus ohne grafischen Login. Beim Systemstart wird das Shellskript rc.S ausgeführt. Wird beispielsweise die Tastenkombination Strg + Alt + Entf gedrückt, so wird ein System-Shutdown durch das shutdown-Programm eingeleitet.
Listing 27.17 Die Datei inittab am Beispiel von Slackware
# Default runlevel. (Do not set to 0 or 6)
id:3:initdefault:
# System initialization (runs when system boots).
si:S:sysinit:/etc/rc.d/rc.S
# Script to run when going single user (runlevel 1).
su:1S:wait:/etc/rc.d/rc.K
# Script to run when going multi user.
rc:2345:wait:/etc/rc.d/rc.M
# What to do at the "Three Finger Salute".
ca::ctrlaltdel:/sbin/shutdown -t5 -r now
# Runlevel 0 halts the system.
l0:0:wait:/etc/rc.d/rc.0
# Runlevel 6 reboots the system.
l6:6:wait:/etc/rc.d/rc.6
# What to do when power fails.
pf::powerfail:/sbin/genpowerfail start
# If power is back, cancel the running shutdown.
pg::powerokwait:/sbin/genpowerfail stop
Das Runlevel-Skript für den Wechsel in den Runlevel 0 ist offensichtlich die Datei /etc/rc.d/rc.0.
Jedes Mal, wenn sich ein Kindprozess von init beendet, protokolliert init dies in den sonst eigentlich hauptsächlich für Login-Informationen genutzten Dateien /var/run/utmp und /var/log/wtmp.
Umgebungsvariablen
Jeder der Prozesse, die init startet, bekommt einige Umgebungsvariablen mit auf den Weg. Diese möchten wir im Folgenden kurz erläutern.
PATH
Die Variable PATH kennen Sie bereits von der Shell. Sie gibt die Suchverzeichnisse für Binärdateien an. Unter Slackware-Linux ist diese Variable standardmäßig auf den Wert /usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin gesetzt.
INIT_VERSION
Die Variable INIT_VERSION enthält die Versionsnummer von init.
RUNLEVEL
Der aktuelle Runlevel des Systems steht in der Variable RUNLEVEL, der vorherige Runlevel in der Variable PREVLEVEL.
CONSOLE
Zu guter Letzt übergibt init noch die Variable CONSOLE, die das Device der Systemkonsole angibt; in der Regel ist dies /dev/console.
Runlevel wechseln und erfragen
telinit & init
Um von einem Runlevel zum anderen zu wechseln, verwendet man eigentlich das Tool telinit. Da man diesen Wechsel aber auch einfach mit init selbst bewerkstelligen kann, zeigen wir Ihnen beide Vorgehensweisen. Sie übergeben eigentlich nur den gewünschten Runlevel als Argument an eines der beiden Programme, und schon wird der Wechsel, sofern Sie Superuser sind, vollzogen.
Listing 27.18 Runlevel wechseln mit init
# init 4
telinit
Bei telinit kann zudem noch eine Zeitspanne zum Wechseln des Runlevels (in Sekunden) mit dem -t-Parameter übergeben werden.
Listing 27.19 Runlevel in zehn Sekunden wechseln mit telinit
# telinit -t 10 4
[»]Sowohl telinit als auch init kommunizieren über die Datei /dev/initctl mit dem init-Prozess. Leider existiert laut Manpage keine vernünftige Dokumentation über das Kommunikationsprotokoll. Wer trotzdem an Details hierzu interessiert ist, kann sich die Headerdatei initreq.h von init ansehen.
runlevel
Um den momentanen Runlevel und den, in dem sich das System zuvor befand, zu erfragen, verwenden Sie einfach das Tool runlevel. Dabei werden zwei Zahlen ausgegeben: Erstere gibt den Runlevel an, in dem sich das System vor dem letzten Runlevel-Wechsel befand, und letztere gibt den aktuellen Runlevel an.
Listing 27.20 Runlevel
# runlevel
3 4
[»]Wenn sich das System vorher in keinem anderen Runlevel befand, gibt runlevel anstelle der ersten Ziffer das Zeichen »N« aus.
Bootskripte
Die Boot- beziehungsweise Runlevel-Skripte des SVR4-Init-Systems befinden sich je nach Unix-Derivat und Linux-Distribution in verschiedenen Verzeichnissen. Übliche Speicherorte sind dabei die folgenden Verzeichnisse:
- /etc/rcN.d
Manchmal finden sich die Skripte des Runlevels N direkt in diesem Verzeichnis; dies ist beispielsweise unter Solaris der Fall. [Fn. Was nicht ganz stimmt, da es sich dabei nur um Softlinks handelt und die eigentlichen Runlevel-Verzeichnisse an einer anderen Stelle im System liegen.] Unter Linux findet man in diesen Verzeichnissen meistens Softlinks auf die eigentlichen Init-Skripte in init.d oder rc.d. Die eigentlichen Runlevel-Skripte starten in der Regel nun genau die Dienste, die in das entsprechende Runlevel-Verzeichnis verlinkt sind. - /etc/rc.d
Wird dieses Verzeichnis verwendet, so befinden sich ähnlich wie beim weiter unten beschriebenen BSD-Bootsystem alle Runlevel-Skripte in einem Verzeichnis. Das bedeutet, dass dort die Runlevel-Skripte sowie die Init-Skripte zum Starten, Neustarten und Beenden von Diensten gelagert werden. Diese werden zentral gespeichert und nur in die Runlevel-Verzeichnisse verlinkt, anstatt sie vollständig dorthin auszulagern. Schließlich kann ein Dienst in mehreren Runleveln benötigt werden, und die Pflege mehrerer identischer Init-Skripte für diesen Dienst wäre umständlich und redundant. - /etc/init.d
Es kann, wie beispielsweise bei Debian, auch vorkommen, dass man die Run- level-Skripte sowie die Init-Skripte in diesem Verzeichnis vorfindet.
Im Folgenden orientieren wir uns daran, dass die Runlevel-Skripte sich im Verzeichnis /etc/rc.d befinden, und verwenden die Slackware-Distribution, um die Skripte zu erläutern.
Dienste starten
Die Init-Skripte der Dienste haben meist Namen wie »rc.DIENST« oder einfach nur »DIENST« und sind ausführbare Shellskripte. Möchte man einen Dienst starten, übergibt man dem entsprechenden Skript den Parameter start. Zum Neustarten und Stoppen verwendet man die Parameter restart und stop.
Intern wird dies durch eine einfache case-Abfrage realisiert, wie Sie sie bereits aus dem Shellskript-Kapitel kennen.
Listing 27.21 Das Apache-Init-Skript
$ cd /etc/rc.d
$ cat rc.httpd
#!/bin/sh
#
# /etc/rc.d/rc.httpd
#
# Start/stop/restart the Apache web server.
#
# To make Apache start automatically at boot, make
# this file executable: chmod 755 /etc/rc.d/rc.httpd
#
case "$1" in
'start')
/usr/sbin/apachectl start ;;
'stop')
/usr/sbin/apachectl stop ;;
'restart')
/usr/sbin/apachectl restart ;;
*)
echo "usage $0 start|stop|restart" ;;
esac
$ pgrep httpd
252
260
261
262
263
264
$ ./rc.httpd stop
/usr/sbin/apachectl stop: httpd stop
$ pgrep httpd
$
Dienste administrieren
Wie aber legt man nach der Installation fest, welcher Dienst gestartet werden soll und welcher nicht? Das ist von System zu System leider äußerst unterschiedlich. Unter Linux wird man meistens den Link auf das jeweilige Init-Skript im entsprechenden /etc/rcN.d-Verzeichnis setzen beziehungsweise löschen.
Je nach Distribution kann es jedoch auch sein, dass man die entsprechende Zeile, in der ein Dienst gestartet wird, aus dem jeweiligen Runlevel-Skript auskommentiert. Da vor dem Starten eines Dienstes überprüft wird, ob das Init-Skript ausführbar ist, kann man diesem alternativ auch das Ausführrecht entziehen.
Listing 27.22 Init-Skript aus dem Runlevel-Skript heraus aufrufen
#if [ -x /etc/rc.d/rc.atalk ]; then
# /etc/rc.d/rc.atalk
#fi
27.6.2 BSD und init
Unter BSD-Systemen kommt das Runlevel-Prinzip von SVR4 nicht zum Einsatz. Hier gibt es nur die Unterscheidung zwischen Single-User-Mode und Multiuser-Mode. Im Folgenden werden wir uns am Beispiel eines OpenBSD-Systems weitere Details ansehen.
Nachdem der Kernel über boot [Fn. boot ist ein Programm mit interaktivem Prompt während des Bootvorgangs von OpenBSD, ähnlich dem ok-Prompt des OpenBoot-PROMs unter Solaris.] geladen wurde und init gestartet ist, führt Letzteres die Reboot-Sequenz aus. Auch wenn der Name anderes vermuten lässt, hat diese Sequenz nicht nur mit dem Herunterfahren des Systems, sondern eben auch mit jedem Startup zu tun.
Gibt es bei den Reboot-Skripten keine Probleme, so wechselt init in den Multiuser-Mode. Andernfalls wird der Single-User-Mode eingeleitet und eine Superuser-Shell gestartet. Wird diese Shell mit Strg + D wieder verlassen, so wird der Bootstrap-Vorgang fortgesetzt.
Auch hier ist init gefragt. Allerdings gibt es keine inittab-Datei, um das Vorgehen zu konfigurieren. Im Vergleich zu Linux werden bei BSD nur sehr wenige Skripte während des Bootens ausgeführt, in der Regel sogar einzig das rc-Skript (/etc/rc).
[»]Man unterscheidet dabei zwischen normalem Booten und dem sogenannten Fastboot. Bei Letzterem werden die Prüfvorgänge für die Dateisysteme übersprungen.
/etc/rc
Das Shellskript /etc/rc kann nahezu mit den Runlevel-Skripten von Linux gleichgesetzt werden. Seine Hauptaufgabe ist es, das System zu konfigurieren und Dienste zu starten. Die Konfiguration wird dabei über die Datei /etc/rc.conf abgewickelt. Da im Gegensatz zu Linux-Systemen mit etwa SVR4-Init nur ein Skript statt viele Skripte für die Ausführung von Programmen beim Systemstart bereitsteht, ergibt sich das Problem, dass bei einem fehlerhaften Skript ein Skript-Abbruch an einer Stelle $X$ im Skript auch alle Befehle hinter Stelle $X$ nicht mehr ausgeführt werden. Aus diesem Grund wurden für die diversen BSD-Derivate Aufteilungen des rc.d-Skripts in viele Unterskripte vorgenommen. Dadurch lassen sich einzelne Dienste wie bei Linux-Systemen gezielt neustarten. [Fn. Weitere Details zu diesem Thema finden sich prägnant in [Hem11].]
Wenn wir einen Blick in die rc.conf werfen, sehen wir diverse Shellvariablen, die in der Regel entweder auf »NO« gesetzt sind, wenn der Dienst nicht starten soll, oder auf »«, wenn der Dienst gestartet werden soll. Wird ein anderer Wert erwartet, um einen Dienst zu starten, zum Beispiel »-a«, so steht dies als Kommentar hinter der entsprechenden Variablen.
Listing 27.23 Auszug aus der rc.conf
routed_flags=NO # for normal use: "-q"
mrouted_flags=NO # for normal use: "", if activated
# be sure to enable
# multicast_router below.
bgpd_flags=NO # for normal use: ""
rarpd_flags=NO # for normal use: "-a"
bootparamd_flags=NO # for normal use: ""
rbootd_flags=NO # for normal use: ""
sshd_flags="" # for normal use: ""
named_flags=NO # for normal use: ""
rdate_flags=NO # for normal use: [RFC868-host]
# or [-n RFC2030-host]
timed_flags=NO # for normal use: ""
ntpd_flags=NO # for normal use: ""
isakmpd_flags=NO # for normal use: ""
mopd_flags=NO # for normal use: "-a"
apmd_flags=NO # for normal use: ""
dhcpd_flags=NO # for normal use: ""
rtadvd_flags=NO # for normal use: list of
# interfaces
Nachdem rc seine grundlegenden Konfigurationsaufgaben erledigt hat, wird die Datei rc.conf eingelesen. Aufgrund dieser Variablen kann rc anschließend die Dienste starten. Zuvor wird allerdings noch das Netzwerk-Setup via /etc/netstart durchgeführt.
Listing 27.24 Ausschnitt aus der Datei /etc/rc
# pick up option configuration
. /etc/rc.conf
...
...
# set hostname, turn on network
echo 'starting network'
. /etc/netstart
if [ "X${pf}" != X"NO" ]; then
if [ -f ${pf_rules} ]; then
pfctl -f ${pf_rules}
fi
fi
...
...
echo -n starting network daemons:
# $routed_flags are imported from /etc/rc.conf.
# If $routed_flags == NO, routed isn't run.
if [ "X${routed_flags}" != X"NO" ]; then
echo -n ' routed'; routed $routed_flags
fi
# $mrouted_flags is imported from /etc/rc.conf;
# If $mrouted_flags == NO, then mrouted isn't run.
if [ "X${mrouted_flags}" != X"NO" ]; then
echo -n ' mrouted'; mrouted $mrouted_flags
fi
if [ "X${bgpd_flags}" != X"NO" ]; then
echo -n ' bgpd'; /usr/sbin/bgpd $bgpd_flags
fi
...
...
securelevel
Ebenfalls in den Aufgabenbereich von rc gehört das Setzen des sogenannten Securelevels. Der Securelevel gibt an, auf welcher Sicherheitsstufe das System laufen soll.
Listing 27.25 Ein weiterer Auszug aus rc
[ -f /etc/rc.securelevel ] && . /etc/rc.securelevel
if [ X${securelevel} != X"" ]; then
echo -n 'setting kernel security level: '
sysctl kern.securelevel=${securelevel}
fi
Der Standard-Securelevel ist Level 1. Der Securelevel kann nach dem Start des Systems nur noch (vom Superuser) erhöht, jedoch nicht mehr verringert werden. Um ihn zu verringern, muss erst in /etc/securelevel der Wert des Securelevels heruntergesetzt werden und das System anschließend neu gestartet werden.
Unter OpenBSD gibt es vier Securelevel: Level –1 (»Permanently Insecure Mode«), Level 0 (»Insecure Mode«), Level 1 (»Secure Mode«) und Level 2 (»Highly Secure Mode«). Die genaue Unterscheidung der Runlevel ist in diesem Zusammenhang weniger wichtig, kann jedoch in der Manpage securelevel(7) nachgelesen werden.
[»]Interessant ist jedoch, dass OpenBSD per Default (nämlich im Securelevel 1) das Laden
von Kernel-Modulen untersagt. Dadurch sind viele Angriffe auf das System nicht mehr
möglich.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.