25.6 init
Nachdem der Kernel seinen Startup-Vorgang erfolgreich beendet hat, startet er den ersten Prozess /sbin/init.
Dieser Prozess bekommt 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 natürlich mehr oder weniger direkt auch ist. Der Grund dafür liegt darin, dass init alle weiteren Prozesse dadurch erstellt, dass er sich kopiert (»forkt«) und die erzeugten Prozesse nun »Kinder« (engl. »children«) von init sind. Diese Child-Prozesse können dann wiederum weitere Prozesse durch Forking erzeugen, sodass der Kernel nur den init-Prozess wirklich erstellen (und nicht kopieren) muss.
Die Hauptaufgabe von init besteht allerdings darin, jene Prozesse zu starten, die das System initialisieren und sogenannte Runlevel-Skripts ausführen, sowie darin die Prozesse zu starten, die die Terminalzugriffe ermöglichen.
Bei »Runlevel-Skripten« handelt es sich um Skripts, die beim Einleiten bzw. Verlassen eines bestimmten Runlevels ausgeführt werden. In der Regel sind diese Skripts in der Syntax der Bourne-Shell verfasst. Runlevel sind eine Entwicklung, die aus System V Release 4 stammt und die nicht nur unter Linux und BSD, sondern auch unter Solaris zum Einsatz kommt.
Ein Runlevel ist dabei ein Systemstatus, bei dem bestimmte Dienste laufen beziehungsweise nicht laufen. Entsprechend starten/stoppen Runlevel-Skripts 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-Skripts 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, weiterhin gibt es einen für den Shutdown des Systems <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ätzliches grafisches Login – etwa via gdm. Wir werden uns diese Runlevel am Beispiel von Slackware-Linux im Folgenden genauer ansehen.
Unter BSD ist das Ganze allerdings anders ausgelegt. Dort gibt es keine klassischen Runlevel, sondern nur Singleuser- und 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.
25.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 6 Runlevel. Diese wurden mittlerweile erweitert, wobei die Erweiterungen der 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 Singleuser-Mode.
Typische Runlevel
Typischerweise werden die klassischen Runlevel, abgesehen von einigen von Distribution zu Distribution und unter den einzelnen Unix-Derivaten gewachsenen Unterschieden, folgendermaßen benutzt:
- 0 – halt
- Das System wird angehalten und kann anschließend ausgeschaltet werden. Wird Powermanagement benutzt, schaltet sich der Rechner von selbst ab.
- 1 – Singleuser-Modus
- Das bedeutet, dass nur der Administrator Zugriff auf den Rechner hat. 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 Singleuser-Modus wieder verlassen hat, speichert es die Konsoleneinstellungen in /etc/ioctl.save ab, um sie beim nächsten Eintritt in den Singleuser-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 werden und Terminalschnittstellen 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 soll und in welchem Runlevel das System hochgefahren werden soll, gibt es die Datei /etc/inittab.
Der Aufbau der inittab gestaltet sich ähnlich wie der der 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 dessen 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 sogenannten 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 dem so, wird es zum Start des Prozesses verwendet.
Im Folgenden ist die 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, wird ein System-Shutdown durch das shutdown-Programm eingeleitet.
# 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
Listing 25.15 inittab am Beispiel von Slackware
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 Umgebungsvariablen 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 diesen Wert gesetzt:
/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin
INIT_- VERSION
Die Variable INIT_VERSION beinhaltet die Versionsnummer von init.
RUNLEVEL
Der momentane Runlevel des Systems steht in RUNLEVEL, der vorherige Runlevel in 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 dies aber auch einfach mit init selbst bewerkstelligen kann, zeigen wir Ihnen beide Vorgehensweisen. Man übergibt eigentlich nur den gewünschten Runlevel als Argument an eines der beiden Programme, und schon wird der Wechsel, sofern man Superuser ist, vollzogen.
# init 4
Listing 25.16 Runlevel wechseln mit init
telinit
Bei telinit kann zudem noch eine Zeitspanne zum Wechseln des Runlevels (in Sekunden) mit dem -t-Parameter übergeben werden.
# telinit -t 10 4
Listing 25.17 Runlevel in 10 Sekunden wechseln mit telinit
Sowohl telinit als auch init kommunizieren über /dev/initctl mit dem init-Prozess. Leider ist laut Manpage keine vernünftige Dokumentation über das Kommunikationsprotokoll vorhanden. Wer trotzdem an Details hierzu interessiert ist, kann sich allerdings die Headerdatei initreq.h von init ansehen.
runlevel
Möchte man nun den momentanen Runlevel und den, in dem sich das System zuvor befand, erfragen, verwendet man 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.
# runlevel 3 4
Listing 25.18 Runlevel
Wenn sich das System vorher in keinem anderen Runlevel befand, gibt runlevel anstelle einer ersten Ziffer das Zeichen »N« aus.
Bootskripts
Die Boot- beziehungsweise Runlevel-Skripts des SVR4-Init-Systems befinden sich je nach Unix-Derivat und Linux-Distribution in verschiedenen Verzeichnissen. Üblich sind dabei folgende Orte:
- /etc/rcN.d
- Manchmal finden sich die Skripts des Runlevels N direkt in diesem Verzeichnis, wie dies beispielsweise unter Solaris der Fall ist. <Was nicht ganz stimmt, da es sich dabei nur um Hardlinks 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-Skripts im init.d- oder rc.d-Verzeichnis. Die eigentlichen Runlevel-Skripts starten in der Regel nun genau die Dienste, die in das entsprechende Runlevel-Verzeichnis verlinkt sind.
- /etc/rc.d
- Wird dieses Verzeichnis verwendet, befinden sich ähnlich dem später noch beschrieben BSD-Bootsystem alle Runlevel-Skripts in einem Verzeichnis. Das bedeutet, dass dort die Runlevel-Skripts selbst sowie die einzelnen Init-Skripts zum Starten, Neustarten und Beenden von Diensten gelagert werden. Diese werden zentral gelagert 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-Skripts für diesen Dienst wäre umständlich und redundant.
- /etc/init.d
- Es kann, wie beispielsweise bei Debian, auch vorkommen, dass man die Runlevel-Skripts sowie die Init-Skripts in diesem Verzeichnis vorfindet.
Im Folgenden orientieren wir uns daran, dass die Runlevel-Skripte sich im Verzeichnis /etc/rc.d befinden, und verwenden die derzeit aktuelle Version 10.1 der Distribution Slackware, um die Runlevel-Skripts zu erläutern.
Dienste starten
Die Init-Skripts der Dienste haben meist Namen wie »rc.DIENST« oder einfach nur »DIENST« und sind ausführbare Shellskripts. Möchte man diese starten, übergibt man dem Skript den Parameter »start«. Zum Neustarten und Stoppen des Dienstes 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.
# 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 #
Listing 25.19 Das Apache-Init-Skript
Dienste administrieren
Doch wie 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 als Alternativmöglichkeit auch das Ausführrecht entziehen.
#if [ -x /etc/rc.d/rc.atalk ]; then # /etc/rc.d/rc.atalk #fi
Listing 25.20 Init-Skript aus dem Runlevel-Skript heraus aufrufen
25.6.2 BSD und init
Unter BSD-Systemen kommt das Runlevel-Prinzip von SVR4 nicht zum Einsatz. Hier gibt es nur die Unterscheidung zwischen Singleuser-Mode und Multiuser-Mode. Im Folgenden werden wir uns am Beispiel eines OpenBSD-Systems weitere Details ansehen.
Nachdem der Kernel über boot <»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 es 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, wechselt init in den Multiuser-Mode. Andernfalls wird der Singleuser-Mode eingeleitet und eine Superuser-Shell gestartet. Wird diese mit Strg+D wieder verlassen, 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 Skripts während des Bootens ausgeführt, in der Regel sogar nur das rc-Skript /etc/rc allein.
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. Die Hauptaufgabe dieses Skriptes ist es, das System zu konfigurieren und Dienste zu starten. Die Konfiguration wird dabei über die Datei /etc/rc.conf abgewickelt.
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, etwa »-a«, so steht dies als Kommentar hinter der entsprechenden Variable.
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
Listing 25.21 Auszug aus der rc.conf
Nachdem rc seine grundlegenden Konfigurationsaufgaben erledigt hat, wird eben die 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.
# 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
...
...
Listing 25.22 Ausschnitt aus /etc/rc
securelevel
Ebenfalls in dem Aufgabenbereich von rc gehört das Setzen des sogenannten Securelevels. Der Securelevel gibt an, auf welcher Sicherheitsstufe das System laufen soll.
[ -f /etc/rc.securelevel ] && . /etc/rc.securelevel if [ X${securelevel} != X"" ]; then echo -n 'setting kernel security level: ' sysctl kern.securelevel=${securelevel} fi
Listing 25.23 Ein weiterer Auszug aus rc
Der Standard-Securelevel ist Level 1. Der Securelevel kann nach dem Start des Systems nur noch erhöht werden (vom Superuser), jedoch nicht mehr verringert werden. Um den Securelevel 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: –1 (»Permanently insecure mode«), 0 (»Insecure mode«), 1 (»Secure mode«) und 2 (»Highly secure mode«). Die genaue Unterscheidung der Runlevel ist hier eher 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.