![]() |
Vorlesung "UNIX"von Prof. Jürgen Plate |
Ein neuer Prozeß kann nur von einem bereits laufenden Prozeß erzeugt werden. Dadurch werden, ähnlich wie beim Dateibaum, die einzelnen Prozesse im Betriebssystemkern in einer baumartigen, hierarchischen Struktur verwaltet. Jeder Kind-Prozeß ist genau einem Eltern-Prozeß untergeordnet. Ein Eltern-Prozeß kann jedoch beliebig viele Kind-Prozesse besitzen. Die Wurzel der Prozeßstruktur wird durch den Systemstart geschaffen und als init-Prozeß (PID 1) bezeichnet.
In der Regel wartet der Eltern-Prozeß auf die Beendigung seiner Kind-Prozesse. Diese Art der Prozeßsynchronisation wird als synchrone Ausführung bezeichnet, der Kind-Prozeß wird als Vordergrundprozeß ausgeführt. Bezogen auf einen Benutzer ist die Shell (Login-Shell) der Eltern-Prozeß. Alle Kommandos, die der Benutzer startet, sind Kind-Prozesse. Während diese abgearbeitet werden ruht der Eltern-Prozeß. Als asynchroner Prozeß oder Hintergrundprozeß werden solche Prozesse bezeichnet, bei denen der Eltern-Prozeß nicht auf das Ende seines Kind-Prozesses wartet, sondern parallel (quasiparallel auf einer Ein-Prozessor-Maschine) asynchron weiterläuft. Auf der Shell-Ebene kann jeder Prozeß durch Anfügen von '&' (kaufm. UND) in der Kommandozeile als Hintergrundprozeß gestartet werden.
Über einen Scheduling-Algorithmus zur Berechnung der Priorität erhält jeder einzelne Prozeß einen bestimmten Teil der Rechenzeit zugewiesen. D.h. der Prozeß mit der zur Zeit höchsten Priorität erhalt die CPU, wird nach einen Zeitintervall suspendiert und, falls noch nicht beendet, zu einem späteren Zeitpunkt wieder reaktiviert. Die aktuelle Priorität eines Prozesses setzt sich aus dem Produkt des CPU-Faktors und der Grundpriorität zusammen.
Die Prozeßverwaltung und Prioritätssteuerung ist recht komplex. In Stichpunkten:
fork() erzeugt einen Kindprozeß, der ein vollständiges Abbild des Elternprozesses ist und der beim gleichen Stand des Befehlszählers fortgesetzt wird. Eltern- und Kindprozeß wird jedoch die Möglichkeit geboten, festzustellen, ob es sich um Eltern- oder Kindprozeß handelt: Der Kindprozeß bekommt als Rückgabewert 0, der Elternprozeß die PID des Kindprozesses. Durch bedingte Verzweigung nach dem Schema (".. if Elternprozeß then ... else ...") können beide Prozesse dann unterschiedlich weiterarbeiten.
wait() ermöglicht dem Elternprozeß das Warten auf die Beendigung des/der Kindprozess(e). Der Elternprozeß wird verdrängt und erst durch das Ende eines Kindprozesses wieder "aufgeweckt". Zur Unterscheidung mehrerer Kindprozesse liefert die Funktion wait() die PID des "gestorbenen" Kindprozesses zurück.
Gibt es keinen Kindprozeß, ist das Ergebnis -1. Beheben des Waisenkind/Zombie-Problems:
Beim Wait-Aufruf wird zuerst die Prozeßtabelle nach terminierten Kindprozessen durchsucht und diese dann gelöscht.
Beim Terminieren des Elternprozesses werden dessen Kindprozesse zu Kindprozessen des Systemprozesses Init.
Bei exec() wird der ursprüngliche Prozeß durch einen neuen Prozeß ersetzt (eine Rückkehr zum aufrufenden Prozeß ist daher logischerweise nicht möglich). exec() ist der komplizierteste Aufruf, da der komplette Prozeßadreßraum ersetzt werden muß. Dieser Aufruf ist auch als Kommando verfügbar (exec [Programmname]). Der Ablauf im Schema:
Schließlich gibt es noch eine Systemfunktion, welche die zeitweise Blockierung eines Prozesses erzwingt: Mit sleep() kann ein Prozeß für eine definierte Zeit "eingeschläfert" werden. Nach Ablauf der vorgegebenen Zeit, wird der Prozeß wieder auf "bereit" gesetzt und kann weiterlaufen. Auch sleep() ist als Kommando verfügbar (sleep [Zeit in Sekunden]).
Alle Prozesse, die auf dem Rechner laufen sind Kindprozesse von init (wobei "Kind" hier auch für alle weiteren Nachkömmlinge steht). Im Single-User-Modus werden von init nur noch einige Prozesse gestartet, im Multi-User-Modus sind wesentlich mehr Prozesse zu aktivieren (z. B. der Drucker-Spooler, die Abrechnung, etc.). Alle zu startenden Prozesse sind in der Datei /etc/inittab aufgeführt. init holt sich die Informationen über zu startende Prozesse aus der Datei /etc/inittab. Für uns ist eigentlich nur das Programm getty interessant, den dieses Programm sorgt für den Kontakt mit den Terminals. Was geschieht nun weiter?
Beim Login (und beim Wechsel des Benutzerkennzeichens während einer Terminalsitzung) wird auf diese Dateien zugegriffen - sie sind übrigens für alle Benutzer lesbar. Schreiben darf jedoch nur der Superuser und das Programm passwd. Da passwd von jedem Benutzer aufgerufen werden kann, ergibt sich hier eigentlich ein Widerspruch. Gelöst wird das Problem durch das UID-Bit von passwd: während das Programm läuft, nimmt der Prozeß die Identität seines Eigentümers an - und der darf schreiben. Anmerkung: Ab der UNIX-Version System V, Version 3 steht das Paßwort nicht mehr in /etc/passwd, sondern in einer eigenen Datei /etc/shadow. Da /etc/passwd für alle lesbar sein muß (z. B. für die Anzeige des Benutzernamens im ls-Kommando), kann man mit entsprechenden Programmen versuchen, die Paßwörter zu 'knacken'. Durch die Verlagerung der Paßwortinfo in /etc/shadow, die nur von Superuser-Prozessen gelesen werden kann, wird diese Sicherheitslücke geschlossen.
Wichtig: Das Paßwort ist in der Datei /etc/passwd verschlüsselt gespeichert. Die Verschlüsselung erfolgt beim Ändern des Passworts mit dem Programm passwd oder bei der Eingabe im Login-Programm. Verglichen werden immer nur die verschlüsselten Passwörter. Auch der Superuser kann das Paßwort nicht entschlüsseln. Wenn Sie Ihr Paßwort vergessen haben, kann er nur ihr altes Paßwort löschen, damit Sie dann ein neues eintragen können. Um das "knacken" von Login-Name und Paßwort zu erschweren, fragt das Login-Programm auch dann das Paßwort ab, wenn schon der Benutzername falsch war. Außerdem wird beim Eingeben des Passworts nichts auf dem Bildschirm angezeigt.
Login-Name:Paßwort:UID:GID:Kommentar:Home-Directory:Programm
Beispiel für einen Eintrag in /etc/passwd:
karl:,..:235:100:Karl Müller:/usr/karl:/bin/sh
Die Zeitdauer wird in Wochen gezählt; der Punkt "." bedeutet 0 Wochen, der Schrägstrich "/" 1 Woche, dann folgen Ziffern und Buchstaben:
Sonderfälle:
Gruppenname:Paßwort:GID:Benutzernamen
Beispiel für einen Eintrag in /etc/group:
dozenten::200:kristl,plate,rother,ries,thomas
Name:Paßwort:letzte Änderung:Min:Max:Vorwarnzeit:Inaktiv:Verfall:Kennzeichen
/etc/default/passwd:
Standardwerte für das Paßwort
und seine Gültigkeitsdauer. Einige wichtige Werte sind:
Um alle Dateien (nicht Directories) eines Benutzers zu löschen, kann das Kommando find (siehe später) verwendet werden (für UID wird die User-ID des Benutzers eingesetzt):
find / -user UID -type f -exec rm -f {} ";"
Probleme können durch Dateien des zu löschenden Benutzers verursacht werden, auf die andere Benutzer ein Link gesetzt haben. Man kann diese Dateien z. B. den betroffenen Benutzern zuordnen.
0 | Power-Down: Ausschalten des Rechners |
1 | Administrativer Level: z. B. Einrichten neu eingebauter Platten oder andere Hardware-Initialisierungen. Oft auch "s" oder "S" (Singleuser = Einzelbenutzer-Modus). |
2 | Multiuser-Modus ohne Netzwerkanbindung |
3 | 3 Multiuser-Modus mit Netzwerkanbindung (Normal-Level) |
4 | Frei für benutzerdefinierten Modus |
5 | Firmware-Modus: z. B. Diagnose und Wartung; oft nur mit spezieller Floppy zu starten |
6 | Shutdown und Reboot: Wechsel zu Level 0 und dann sofortiges Hochlaufen |
Die Zuordnung der Level kann auch von der oben angeführten abweichen. Der Wechsel des Levels wird durch spezielle Kommandos erreicht, z. B. shutdown, telinit, (re)boot oder halt. So ist beispielsweise auch ein Bootstrap von einem bestimmten Datenträger (Floppy, CD-ROM, ...) möglich.
Beispiel: Reboot des Systems nach 2 Minuten (Solaris): shutdown -g120 -i6 -y Shutdown sendet eine Nachricht an alle eingeloggten Benutzer, bevor der eigentliche Prozess beginnt. Egal, ob der Reboot-Vorgang durch shutdown oder durch Einschalten des Rechners ausgelöst wurde, sind die Systemaktivitäten im Prinzip immer gleich:
Diese doch relativ komplexen Aktionen werden wieder über spezielle Shell-Scripts gesteuert. Bei BSD-Unix war der Aufbau dieser Scripts relativ einfach. Die Datei /etc/rc enthält alle beim Systemstart auszuführenden Kommandos. Innerhalb von rc werden eventuell weitere rc-Dateien aufgerufen, z. B.:
/etc/rc.local | Start lokaler Software |
/etc/rc.net | Start der Netzwerksoftware |
/etc/rc.single | Start im Single-User-Modus |
Später wurde das System dahingehend erweitert, daß es für jeden Runlevel eine eigene rc-Datei gab (rc0, rc1, rc2, usw.). Ab System V ist das System der rc-Dateien vereinheitlicht worden. Für jeden Runlevel exisitiert eine rc-Datei (rc0, rc1, rc2, etc.) und ein Verzeichnis unter /etc, wobei der Name der Verzeichnisse einheitlich /etc/rcü.d ist (ü steht für den Runlevel, es gibt also rc0.d, rcs.d, rc2.d, usw.). Im Verzeichnis /etc/init.d (manchmal auch /sbin/init.d) sind alle Programme (oder Shell-Scripts) gespeichert, die beim System-Boot aufgerufen werden könnten. In den Verzeichnissen rc.d sind nun nur noch Links auf diese Programme enthalten. Alle Links folgen ebenfalls einer festen Namenskonvention:
Die so entstandenen rc-Scripts werden in lexikalischer Reihenfolge aufgerufen und zwar zuerst die K-Dateien, dann die S-Dateien. Die Zahl im Namen legt also die Reihenfolge innerhalb der K- oder S-Gruppe fest. Die K-Dateien dienen zum Löschen (Kill) von Prozessen, die S-Dateien zum Starten von Prozessen. Angenommen, es existieren folgende Dateien in /etc/rc.d:
Dann ist die Aufruf-Reihenfolge:
K30tcp K40nfs S20sysetup S30tcp S40nfs S75cron S85lp
Dabei sind K-und S-Dateien mit ansonsten gleichem Namen lediglich Hinweise darauf, dasselbe Programm aufzurufen. So wird z. B. bei den Dateien K30tcp und S30tcp das Programm oder Script /etc/init.d/tcp einmal mit dem Parameter "stop" und einmal mit dem Parameter "start" aufgerufen. Man kann also durch Anlegen von Links das Hochfahren des Systems sehr gezielt steuern. Das entsprechende rc-Script wird dann auch sehr einfach, es läßt sich folgendermaßen skizzieren:
#!/bin/sh # Wenn Directory /etc/rc2.d vorhanden ist if [ -d /etc/rc2.d] ; then # K-Files bearbeiten for f in /etc/rc2.d/K* ; do if [ -s $f ]; then /bin/sh $f stop fi done # S-Files bearbeiten for f in /etc/rc2.d/S*; do if [ -s $f ] ; then /bin/sh $f start fi done fi
Ein von der rc-Datei aufgerufenens Script in /etc/init.d könnte dann z. B. so aussehen:
#!/bin/sh case $1 in 'start') # aufgerufen als "Kxxcron" # Lockfilelöschen rm -f /var/spool/cron/FIFO if [ -x /etc/cron ] ; then /etc/cron fi ;; 'stop') # aufgerufen als "Sxxcron" pid=`/bin/ps -e | grep 'cron$' | sed -e 's/^ *//' -e 's/ .*//'` if [ "$pid" != "" ] ; then /bin/kill -9 $pid fi ;; esac
Da das Protokoll allerdings recht aufwendig war - es teilt sich der Klarheit wegen in mehrere streng getrennte Schichten - waren die anfänglichen Implementierungen relativ langsam. Wesentlich langsamer jedenfalls als solche Windowsysteme, die direkt auf das Bitmap-Display zugreifen. Es folgten dann immer mehr Programme, die auf dem X Window System aufbauen.
Das X Window System ist jedoch keine einheitliche Benutzeroberfläche, die ein bestimmtes "Look-and-Feel" bietet. X könnte aussehen wie der Macintosh Finder oder wie Microsoft Windows. Dieses deshalb, weil das X-Protokoll sehr einfach ist. Man kann lediglich grafische Elemente (Linien, Kreise, etc.) und Buchstaben auf dem Bildschirm anzeigen. Das Protokoll enthält keine komplexeren Grafikelemente wie Buttons oder Menus. Deshalb gibt es auch keine Aussagen über Aussehen von Anwendungsprogrammen (Style Guide), so daß sich mehrere Standards gebildet haben.
Möchte man zum Beispiel einen Knopf (Button) mit einer Aufschrift, so muß dieser aus Linien und Text selbst zusammengesetzt werden. Dies kann dem Programmierer aber auch durch ein Toolkit abgenommen werden. Diese Toolkits bestimmen dann hauptsächlich das Look-and-Feel.
Das Pointing-Device muß nur zum Zeigen auf Punkte fähig sein. Es könnte zum Beispiel auch ein Touch-Screen sein. Oder eine Maus mit nur einer Taste. Soll ein Programm konform zur X-Spezifikation sein, müssen alle Funktionen mit nur einer Maustaste ausführbar sein. Dies kann man z. B. erreichen, indem man beim Drücken der Maustaste auch noch gleichzeitig gedrückte Tasten (Control, Meta, etc.) abfragt.
Softwareseitig gibt es folgende Prozesse:
X Server |
Der X-Server ist das Programm, das alle Bildschirmausgaben übernimmt und
alle Eingaben von der Tastatur und der Maus verarbeitet. Daher ist ein Teil des
X-Servers sehr an die Hardware des Rechners gebunden (Farb- oder
Schwarzweiß-Bildschirm, Art der Tastatur, Anzahl der Maustasten,
Bildschirmgröße ...). Ein Programm, das etwas auf dem Bildschirm
ausgeben will, schickt einen diesbezüglichen Auftrag an den X-Server, der
daraufhin eine Linie zeichnet, einen Text ausgibt oder tut, was immer das
Programm von ihm verlangt. In der anderen Richtung gibt der X-Server Meldungen
an die X-Clients, wann immer der Benutzer eine Eingabe getätigt hat, sei es
das Bewegen der Maus, das Drücken einer Maustaste oder eine Eingabe
über die Tastatur. Die Programme können dann entscheiden, was sie mit
dieser Eingabe anfangen und wie (oder ob überhaupt) sie darauf reagieren.
Vorteil dieser Konfiguration ist, daß nur der X-Server über die
Möglichkeiten der vorhandenen Hardware informiert sein muß. Die Clients
können diese Information vom Server erfragen, wenn sie sie brauchen,
müssen sich ansonsten aber nicht darum kümmern.
Zur Verdeutlichung noch ein Hinweis: bei den meisten anderen Client-Server Systemen (beispielsweise Datenbanksystem, Mailsystem usw.) befindet sich der Client näher am Benutzer als der Server. Bei X ist das naturgemäß umgekehrt, da der Server Tastatur und Bildschirm verwaltet und den Clients zur Verfügung stellt.
| ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
X Clients | Jedes Programm, das auf einem X-Bildschirm ein Fenster darstellen will, ist ein X-Client. Der X-Client bittet den Server, gewisse Aufgaben (eben das Zeichnen des Fensters) für ihn zu übernehmen. Sie müssen dazu Aufträge im X Protokoll an den Server schicken. | ||||||||||
Window-Manager |
Ein Client hat den anderen gegenüber einen gewissen Sonderstatus: der
Window-Manager, hier heißt er ctwm. Er stellt dem Benutzer Mittel
zur Verfügung, mit deren Hilfe dieser das Aussehen seiner
Benutzeroberfläche bestimmen kann. Insbesondere kann der Window-Manager die
Größe und die Position der Fenster anderer Clients beeinflussen. Die
Clients können dem Window-Manager Hinweise geben, wo und in welcher
Größe sie ihre Fenster plaziert haben wollen. Der Window-Manager
muß diese Hinweise zwar nicht berücksichtigen, die meisten gängigen
Systeme tun dies aber. Durch die Funktion des Window-Managers ergibt sich,
daß es zumindest problematisch ist, zwei Window-Manager für einen
Bildschirm zu starten. Daher prüfen Window-Manager beim Start in der Regel,
ob bereits ein anderer Window-Manager für den Bildschirm existiert und
brechen in diesem Fall die Arbeit ab.
Die Rahmen und Titelbalken, die die einzelnen Fenster verzieren, sind nicht Teil des jeweiligen Clients, sondern werden vom Windowmanager um die Fenster herumgezeichnet, damit durch ihre Betätigung Funktionen des Windowmanagers ausgelöst werden können. Der Windowmanager bestimmt somit auch "look-and-feel" der Benutzeroberfläche. Beachten Sie aber, daß der Window-Manager aber auch nur ein Clientprozeß ist. Es gibt alle möglichen Window-Manager:
|
X ist netzwerkfähig. Clients können ihre Grafikausgabe auch auf Server machen, die auf anderen Rechnern im Netz laufen. Als Netzwerkprotokolle können dabei verschiedene Protokolle eingesetzt werden. Bei Unix ist dies meist TCP/IP.
Auf dem Zielrechner muß dem Server mitgeteilt werden, daß er Requests vom Senderechner zulassen darf. Das geschieht mit dem Kommando:
xhost +senderechnerAuf dem Senderechner muß man dem Client mitteilen, daß die Ausgabe nicht auf dem eigenen Display erscheinen soll, sondern beim Zielrechner. Dazu setzt man entweder die Environment-Variable
export DISPLAY=zielrechner:0oder man schreibt beim Aufruf des Programms
clientprogramm -display zielrechner:0Die Zahl hinter dem Rechnernamen gibt die Nummer des Displays an. Sie kann auch eine Nachkommastelle haben, z.B. 0.1. Normalerweise haben aber die Rechner nur ein Display mit der Nummer 0.
Der Mechanismus funktioniert folgendermaßen: von Systemprozessen wird eine Prozedur aufgerufen, welche die Session steuert. Wenn sich die Prozedur beendet, übernehmen wieder die Systemprozesse die Steuerung.
process xdm is while (true) do xlogin; if (Benutzer hat eigene Session-Prozedur) then fuehre Benutzer-Session-Prozedur aus else if (Benutzer hat zusätzliche Session-Prozedur) then fuehre zusätzliche Session-Prozedur im Hintergrund aus fi; starte xterm im Hintergrund; starte xclock im Hintergrund; starte twm; fi done endprocessBei xdm heißt die Benutzer-Session-Prozedur .xsession und die zusätzliche Session-Prozedur .xsession+. Man sollte darauf achten, daß diese Dateien ausführbar sind. Außerdem wird die Session beendet, wenn sich die Prozedur beendet. In der Prozedur sollten also alle Kommandos im Hintergrund gestartet werden, außer dem letzten, das während der gesamten Session laufen muß. Dies ist meist der Window-Manager. Wird dieser dann mit Exit beendet, wird auch die Session mit allen anderen Clients beendet.
Resourcen sind hierarchisch aufgebaut. Jedes Programm greift auf eine Klasse von Resourcen zu (xterm zum Beispiel auf Xterm). Darunter können sich Komponenten des Programms befinden und am Ende der Hierarchie stehen Eigenschaften. Teile der Hierarchie können auch durch Wildcards ('*') ersetzt werden. Beispiel:
xterm*font: 9x15setzt in allen Komponenten von xterm den gewünschten Font auf 9x15. Resourcen werden vom System vorbesetzt. Man kann sie durch eigene ersetzen (.xresources) oder erweitern (.xresources+).
Ein anderes Problem liegt am Konzept des TCP/IP-Protokolls. Da nicht alle Rechnertypen, die am Internet hängen, das Konzept einer Benutzer-ID haben, ist diese auch bei TCP/IP nicht vorgesehen. Auf den X-Server kann also nicht nur der augenblickliche Benutzer der Konsole zugreifen, sondern alle Benutzer des Rechners. Und das in beliebiger Form. Ein anderer Benutzer kann zum Beispiel den gesamten Bildschirminhalt überschreiben.
Bei jedem Anmelden an einem Rechner generiert das Programm xdm, das die Anmeldemaske zur Verfügung stellt, einen Schlüssel und legt ihn in der Datei .Xauthority im HOME-Verzeichnis des Benutzers ab. Jedes X-Programm, das der Benutzer dann startet, sucht in dieser Datei nach dem Schlüssel und gibt ihn dem Server beim Aufbau der Verbindung an. Nur wenn dieser Schlüssel mit dem übereinstimmt, der beim Login generiert wurde, wird die Verbindung tatsächlich aufgebaut, ansonsten wird der Aufbauversuch vom Server zurückgewiesen. Auf diese Art wird verhindert, daß jeder Benutzer seine Fenster auf den Bildschirm eines anderen Benutzers legen und diesen dadurch bei seiner Arbeit behindern kann. Voraussetzung für die Sicherheit des Systems ist natürlich, daß die Rechte für die Datei .Xauthority richtig gesetzt sind. Nur der Eigentümer der Datei darf dafür das Lese- und Schreibrecht haben, alle anderen Benutzer nicht einmal das Leserecht, da sie sonst den Schlüssel aus der Datei herauslesen könnten.
X ermöglicht unter anderem jedem Programm, das eine Verbindung zum X-Server aufbauen kann, das Mitlesen von Tastatureingaben, die auf dem Rechner vorgenommen werden. Darunter können natürlich auch Paßworteingaben sein (zum Beispiel bei einem rlogin). Daher darf der Zugriff auf den Server auf keinen Fall unkontrolliert freigegeben werden.
xterm [Optionen]
oder
xterm [Optionen] & (als Hintergrundprozeß)
Optionen:
breite x höhe +xkoord +ykoord
'breite' und 'höhe' geben die Fenstergröße (in Buchstaben) an, 'xkoord' und 'ykoord' die Position (in Pixeln). Ein xterm-Fenster hat gemäß Voreinstellung 24 Zeilen mit 80 Zeichen Breite. Je nach verwendeten Zeichen + oder - ergibt sich die Ecke des Bildschirms und des Fensters, auf die sich 'xkoord' und 'ykoord' jeweils beziehen. Es gibt vier Möglichkeiten:
xterm -geometry 80x25+200+400
xterm -geometry 80x25+130+360
erzeugt ein Terminalfenster mit 80 Spalten und 25 Zeilen, dessen obere linke Ecke an der X-Koordinate 130 und der Y-Koordinate 360 liegt.
Rechnername [Benutzer]
Was kann man nun überhaupt mit Ressourcen festlegen? Mit Programmname.geometry wird festgelegt, an welcher Stelle ein Fenster des angegebenen Programms bei dessen Start auf dem Bildschirm erscheinen soll. Die Einträge mit den Endungen background und foreground legen für die entsprechenden Programme Vordergrund- und Hintergrundfarbe fest. iconic bestimmt, ob ein bestimmtes Programm als Icon (true) oder als Fenster (false) gestartet wird.
![]() |
![]() |