Yet Another Useless Unix Book

! zum Inhaltsverzeichnis
< zum vorangehenden Abschnitt:
> zum folgenden Abschnitt:


3 Grundlagen

3.1 Multitasking/Multiuser

Eine grundlegende Eigenschaft von Unix ist dessen Multitaskingfähigkeit. Unix lebt von der Möglichkeit, mehrere Prozesse quasi-gleichzeitig (im Zeitscheibenverfahren) ablaufen zu lassen. Dies geschieht nach dem Prinzip des präemptiven Multitaskings bei dem einzig das Betriebssystem für die Vergabe von Rechenzeit an einzelne Programme zuständig ist und nicht wie beim cooperativen Multitasking die Programme selber entscheiden ob und wieviel an Prozessorkapazität sie beanspruchen, bzw. an andere Programme abgeben (z.B. Windows 3.x). Praktisch jede Aufgabe, jeder Dienst und was sonst noch alles zu einem Betriebsystem gehört, wird jeweils durch einen eigenen Prozeß gelöst. Jeder Prozeß bekommt vom Unix-Kernel*3* einen Adressbereich*4* zugewiesen, auf den nur er Zugriff hat. Benötigt der Prozeß mehr Speicherplatz, so muß er ihn beim Kernel ``beantragen'' (malloc(3)). Zur Vergabe und Verwaltung der einzelnen Prozeß-Adressbereiche ist einzig der Kernel zuständig. Dies hat den Vorteil, daß kein Prozeß (z.B. ein Anwenderprogramm), ohne den Weg über den kontrollierenden Kernel zu nehmen, Zugriff auf den Speicherbereich eines anderen Prozesses hat. Gelöst wird dies durch die Verwendung verschiedener Speichersegmente: Das Program selber, das Executable, wird im Codesegment abgelegt, welches für den Prozeß selber nur lesbar, nicht aber schreibbar ist. Dadurch wird vermieden, daß sich Programme während ihrer Laufzeit modifizieren können, welches in Bezug auf Systemsicherheit sehr wichtig ist. Im Datasegment des Prozesses werden alle anfallenden Daten des Programms verwaltet und nur der Prozeß selber hat darauf Lese- und Schreibrechte. Beantragt der Prozeß allerdings beim Kernel einen sog. Shared-Memory Bereich, so kann aus diesem auch von anderen Prozessen gelesen werden. Im Stacksegment eines Prozesses werden Daten wie Aufrufparameter, Rückgabewerte, Register und ähnliches zwischengelagert. Auch dieser Speicherbereich ist nur Les- und Schreibbar für den Prozeß selber, andere besitzen keine Rechte darauf. Programme, die durch Speicheroperationen andere Programme oder gar das komplette Betriebsystem (den Kernel) zum Absturz bringen, können durch dieses Speicherschutz-Konzept nicht vorkommen. Dies gewährt natürlich ein hohes Maß an Ausfallsicherheit und begründet die teilweise immens hohen Laufzeiten (Uptimes) von Unix-Systemen; 500 Tage oder mehr sind keine Seltenheit.
Dadurch, daß immer mehrere Prozesse ``laufen'', also einen Bereich im Speicher belegen und vom Kernel verwaltet werden, müssen gleichzeitige Zugriffe auf ein und dasselbe Gerät (Bildschrim, Schnittstellen, Netzwerkkarte, Festplatte, Tastatur, Maus, usw.) vermieden werden. Auch das ist Aufgabe des Kernels. Indem kein anderer Prozeß außer ihm Zugriff auf die Hardware-Resourcen hat und jeder Hardware-Zugriff eines Prozesses, z.B. das Schreiben auf die Festplatte oder das Lesen einer CD, letzlich vom Kernel getätigt wird, kann dieser auch konkurrierende Zugriffe vermeiden. Zu diesem Zweck ist jedes Gerät, selbst die Grafikkarte eines Rechners oder der ``Speicher'', als Datei im Verzeichnis /dev (zum Thema Filesystem siehe 3.5) abgebildet. Benötigt ein Prozeß nun Zugriff auf ein Stück der Hardware, so liest oder schreibt er einfach in die stellvertretende Datei (z.B. /dev/lpt1 für eine parallele Schnittstelle oder /dev/cd0a für ein CD-ROM*5*). Die eigentliche Ein/Ausgabe-Operation wird dann vom Kernel durchgeführt. Dies bedingt natürlich, daß der Kernel über die Funktionsweise der Gerätschaften informiert ist. Das was unter DOS oder Windows als ``Treiber'' bekannt ist, findet man unter Unix direkt im Kernel, da ja nur hier alle hardwarenahen Operationen durchgeführt werden dürfen. Ein Nachteil dieser Funktionsweise ist, daß bei jedem neuangeschafften Gerät (z.B. neue Ethernetkarte) oder neugewünschten Funktion die der Kernel behandeln soll, dieser neu übersetzt werden muß. Da dies mitunter ziemlich zeitaufwendig sein kann, ist man dazu übergegangen einige Treiber-Funktionen in sogenannte loadable kernel modules auszulagern, welche dann bei Bedarf zur Laufzeit des Systems zum Kernel ``hinzugelinkt'' werden können. Um bei Änderungen von Hardware-Informationen, wie IRQ oder Base-Adress, einer PC-Steckkarte, nicht jedesmal einen neuen Kernel compilieren zu müssen, versuchen einige PC-Unixe diese zu ``proben'' (auf Verdacht alle Adressen durchgehen und schauen ob sich eine Karte meldet), z.B. Linux, oder einen Hardware-Konfigurations-Modus zwischen Laden und Ausführen des Kernels zu starten, wie z.B. FreeBSD, in dem man die gewünschten Einstellungen dann von Hand durchführen kann.

Mit der o.g. Fähigkeit des Multitaskings geht die Multiuserfähigkeit einher. Jeder Benutzer eines Unix-Systems muß diesem bekannt sein. Der Account (Zugangsberechtigung) wird durch den Loginnamen repräsentiert und besitzt ein Passwort. Durch die Möglichkeit von Unix, mehrere Prozesse gleichzeitig laufen zu lassen und die Unterscheidung zwischen verschiedenen Benutzern ist es möglich, daß mehrer Benutzer auch gleichzeitig mit dem System arbeiten und ihrerseits mehrere Prozesse laufen lassen können. Die Ein- und Ausgabe zu den Benutzer-Prozessen (z.B. dessen Shell, s. 3.6) kann z.B. auf der Console*6* (angeschlossener Monitor und Tastatur), seriellen Terminals (ASCII, vt100) oder Netzwerkverbindungen (Telnet, Rlogin) geschehen, aber auch aus einer Datei gelesen, bzw. in ein geschrieben werden, oder ganz unterdrückt werden. Das Betriebssystem muß gewährleisten, daß die Daten des Benutzers (seine Dateien, seine laufenden Prozesse) vor unberechtigten Zugriffen anderer Benutzer geschützt sind, bzw. nur soweit veröffentlicht werden, wie es der Benutzer möchte.

Zusammenfassend kann man daher drei wichtige Unterschiede eines Mehrbenutzerbetriebsystems gegenüber einem Einzelplatzsystems benennen:
*3* Der Kernel ist jenes Programm, welches beim Booten von Unix geladen wird. Seine Aufgabe ist es, alle Prozesse, den Speicher, angeschlossene Geräte und Ein/Ausgabe-Operationen, gleich welcher Art, zu verwalten.
*4* Der gesamte, zur Verfügung stehende Adressraum berechnet sich normalerweise aus physikalischem RAM + Swap-Bereich auf der Festplatte (oder im Netz).
*5* Die Dateinamen für Device-Files unterscheiden sich je nach Unix-Derivat.
*6* FreeBSD verwaltet mehrere virtuelle Consolen. Mit der Tastenkombination Alt + F1-F9 schaltet man zwischen den verschiedenen Consolen hin und her.

3.2 Benutzerverwaltung

Da jeder Benutzer, im Folgenden auch User genannt, dem System bekannt sein muß, werden in einer Datei mit dem Namen passwd(5) im Verzeichnis /etc alle benötigten Benutzer-Daten abgelegt. Am Beispiel eines Eintrages in dieser Datei möchte ich diese Daten erläutern. /etc/passwd ist Zeilenweise aufgebaut, d.h. für jeden Benutzer gibt es genau eine Zeile. Die einzelnen Datenfelder sind durch Doppelpunkte getrennt:
orpaulze:Hg98bga1Kq7cgE:2004:2000:Oliver Paulzen:/home/gast/orpaulze:/bin/tcsh

In neueren BSD-Arten werden noch drei weitere Felder (ein meist noch unbenutztes namens ``class'', eins zur Angabe wie lang das Passwort gültig sein wird und wann es geändert werden muß und eins zur Angabe wann der Account ungültig wird) verwendet. Diese befinden sich zwischen dem GID- und dem GECOS-Feld und sind vom Benutzer nicht einsehbar (in master.passwd).

Der erste Eintrag in der passwd-Datei bezeichnet den sogenannten Superuser, Accountname ``root''. Seine UID und GID ist 0 und er besitzt uneingeschränkte Lese- und Schreibrechte auf jede lokale Datei und das Recht Prozesse anderer Benutzer zu manipulieren (z.B. beenden; s. 3.6). Sein Passwort ist sozusagen der ``Schlüssel'' des Unix-Rechners, mit ihm kann alles am System modifiziert werden.

Unix kann mehrere Benutzer zu Benutzergruppen zusammenfassen und Zugriffsrechte für Mitglieder einer solchen Gruppe vergeben. Die Gruppen werden in der Datei /etc/group (group(5)) verwaltet. Der Aufbau ist ähnlich der der passwd, z.B.:
infosys:*:30:meguro,sabartes

Das erste Feld bezeichnet den Gruppennamen, fest verknüpft wieder mit dem dritten Feld, dem der GroupID. Dazwischen kann ein (verschlüsseltes) Passwort stehen, wird aber i.d.R. nicht verwendet und deshalb ein ``*'' als ``kein mögliches Passwort'' verwendet. Im letzten Feld werden, durch Komma getrennt, alle Mitglieder der Gruppe aufgezählt. Im Gegensatz zu System V Varianten von Unix, bei denen Benutzer immer nur einer Gruppe angehören und diese bei Bedarf und Berechtigung wechseln können, gehört ein BSD-User zu jedem Zeitpunkt jeder Gruppe an in die er eingetragen ist, mindestens aber seiner Login-Group (s. GID in der passwd). Mit dem Befehl id(1) kann man überprüfen in welchen Gruppen man Mitglied ist:
orpaulze@suninfo1:~> id
uid=2004(orpaulze) gid=2000(admin) groups=2000(admin),0(wheel),30(infosys)

In diesem Fall ist die UID 2004, aufgelöst als ``orpaulze'', die Login-Group ist 2000, aufgelöst als ``admin''. Desweiteren gehört der User noch der Gruppe 0 (``wheel''*11*) und der Infosys-Gruppe (GID=30) an.

In Netzwerken, in denen sich jeder Benutzer auf jedem Rechner einloggen können soll, empfiehlt es sich die passwd-Datei netzweit zu verteilen. Dies kann z.B. durch regelmässiges kopieren (z.B. mit dem rdist(1) Kommando) der Datei auf alle Rechner geschehen oder, wie im Fall des Rechnerpools an der BOS-Wirtschaft, durch den Einsatz eines Network-Information-Systems (NIS). Bei diesem wird die passwd- und ebenso die group-Datei (oder auch weitere) von einem zentralen NIS-Server verwaltet. Auf ihm läuft ein Prozeß (ypserv(8)), welcher auf Anfragen von Prozessen auf den NIS-Clients (ypbind(8)) hin, die gewünschten Dateien zur Verfügung stellt. Auf den Clients wird am Ende der passwd- bzw. group-Datei ein Eintrag mit ``+'' beginnend angefügt, welcher darauf hinweist, daß hier die Datei vom NIS-Server ``angedacht'' wird. Dem NIS-Client Prozeß ypbind muß dann nur noch mitgeteilt werden, welcher Rechner im Netz für ihn als Server zuständig ist und welcher NIS-Domain*12* er angehört.
*7* An der BOS-Wirtschaft ergeben sich die Usernamen (so weit möglich) aus dem ersten und dem letzten Buchstaben des Vornamens plus den ersten sechs Zeichen des Nachnamens.
*8* Meist nach dem DES-Algorithmus, eine Art ``Falltür''-Algorithmus. Alternativen sind z.B. MD5 oder Blowfish.
*9* Mit Hilfe von Crack-Programmen, Wörterbuch-Dateien und genügend Rechenleistung lassen sich einfach gewählte Passwörter entschlüsseln.
*10* Zur Verwaltung von Zugehörigkeiten einer Datei oder eines Prozesses wird einzig die UID bzw. GID verwendet. Ein Auflösung zum Namen geschieht über die passwd-Datei. z.B. wird bei NFS nur die UID/GID übertragen.
*11* Nur Mitgliedern dieser Gruppe ist es gestattet mit dem Befehl su(1) Root-Rechte zu erhalten.
*12* Zur Verwaltung verschiedener Netzbereiche in denen verschiedene Dateien gleichen Typs verteilt werden, trennt NIS zwischen den NIS-Domains.

3.3 Dateirechte

Um festzulegen wer eine Datei lesen, schreiben oder ausführen darf, besitzen auch Dateien User- und Gruppenzugehörigkeiten. Wird eine angelegt, so geschieht dies unter der UserID und (normalerweise) Login-Group des Benutzers der dies tut. Darüber hinaus werden zu jeder Datei die Dateirechte mit im Filesystem abgelegt, sowie eine Art Typ-Bezeichnung. Die kompletten Zugriffsinformationen zu einer Datei erhält man mit dem Befehl ls -l*13* (ls(1) ist vergleichbar mit dem DIR-Befehl von DOS). Eine typische Ausgabe kann z.B. so aussehen:
orpaulze@pcinfo7:~> ls -l einkaufszettel 
-rw-r--r--  1 orpaulze admin         665 Mar 11 22:58 einkaufszettel

Der erste Bereich (-rw-r--r--) gibt die Zugriffsrechte an, das zweite Feld die Anzahl der Hardlinks welche diese Datei hat (zum Thema Links mehr in 3.5), gefolgt von der User- und Gruppenzugehörigkeit, der Größe der Datei in Bytes, dem Datum und der Uhrzeit des letzten Schreibzugriffes und dem Dateinamen. Das Feld der Zugriffsrechte setzt sich wie folgt zusammen:
*13* -l für ``long''. Bei einigen Systemen benötigt es noch ein ``-g'' zum Anzeigen der GroupID.
  • Das erste Zeichen (hier ``-'') gibt den Typ dieses Files an:
    • Eine normale Datei, also ein Programm oder Daten in jedlicher Form, werden eben mit jenem ``-'' dargestellt.
    • Verzeichnisse sind im Sinne des Unix-Dateisystems auch nur spezielle Dateien und werden in diesem Feld mit ``d'' gekennzeichnet.
    • Dateien im /dev-Verzeichnis, also die Hardware-Repräsentanten, die sogenannten Devices-Files werden mit einem ``b'' für blockorientierte oder einem ``c'' für zeichenorientierte Geräte wiedergegeben.
    • Ein symbolischer Link wird hier mit einem ``l'' gekennzeichnet.

  • Die darauffolgenden 9 Zeichen geben die eigentlichen Zugriffsrechte wieder und zwar in drei 3er Gruppen geteilt:

    • Die ersten 3 Zeichen bezeichnen die Zugriffsliste für den Inhaber (User, UID) der Datei
    • Die zweiten 3 Zeichen die Rechte für Mitglieder in der Gruppe die dieser Datei zugeordnet wurde.
    • Die letzten 3 Zeichen bezeichnen die Rechte für alle anderen Benutzer (Others), die nicht den ersten beiden zuzuordnen sind.

    Innerhalb eines 3er-Blocks gilt ein

    • ``r'' als das Recht zum Lesen der Datei oder im Falle eines Verzeichnisses, um die Dateien in einem Verzeichnis anzeigen zu können.
    • ``w'' als das Recht in diese Datei oder das Verzeichnis zu schreiben oder zu löschen.
    • ``x'' als Ausführungsrecht. Dieses wird z.B. von allen Programmen zum ausführen benötigt, aber auch für Verzeichnisse um diese mit cd(1) zu betreten.

Bei der Datei im oberen Beispiel handelt es sich also um ein ``normales'' File mit der Größe 665 Bytes. Besitzer dieser Datei ist ``orpaulze'' und ihre GID ist ``admin''. Der Besitzer darf diese Datei beschreiben, Mitglieder der Admin-Gruppe, aber auch alle anderen Benutzer dieses Systems, daraus lesen.
Im User- und Group-Feld für das Ausführungsrecht (``x'') kann als Sonderfall auch noch ein ``s'' oder ``S'', das sogenannte Set-User/Group-ID-Flag gesetzt sein. Ist ein ``s'' im User-Feld einer normalen Datei gesetzt, so bedeutet dies, daß im Fall einer Ausführung der Datei als Programm, dieses unter der UID des Datei-Besitzers abläuft. Folgender Fall ist denkbar:

orpaulze@pcinfo7:~> ls -l /usr/bin/passwd 
-r-sr-xr-x  2 root  bin  20480 Nov 16  1995 /usr/bin/passwd

Dies bedeutet, daß jeder Benutzer des Systems dieses Programm lesen*14* und starten kann, es aber sobald es läuft die effektive UID (EUID) ``root'', also ``0'' annimt*15* und nicht wie normalerweise unter den Rechten des Aufrufers läuft.
Ist das s-Flag im Group-Feld eines Verzeichnisses gesetzt, so werden Dateien unterhalb dieses Verzeichnisses unter der GroupID dieses Verzeichnisses abgelegt, falls man Mitglied dieser Gruppe ist. Ein populäres Beispiel wäre:
orpaulze@suninfo1:/usr/local/infosystems/www/home> ls -lg
total 2
drwxrwsr-x  3 infosys  infosys       512 Oct 12 20:28 bos
drwxrwsr-x 17 infosys  infosys       512 Mar 10 15:35 wirtschaft

Falls man Mitglied in der Gruppe ``infosys'' ist und Dateien in einem der beiden aufgeführten Verzeichnisse erzeugt (darf man ja dank dem ``w'' für die Gruppe), so werden diese unter der GID ``infosys'' abgelegt, was das Bearbeiten von Dateisystem-Bereichen (hier z.B. die WWW-Dateien) durch mehrere Benutzer u.U. vereinfachen kann.
Ein ``S'' zählt ähnlich einem ``s'', nur daß keine Ausführungsrechte für User, bzw. Group bestehen.

Um die User- und Gruppenzugehörigkeit einer Datei nachträglich zu bestimmen, kann man diese mit dem Befehl chown(8) ändern.
``chown meguro:infosys test.gif''*16* ändert (entsprechende Rechte vorrausgesetzt) die Zugehörigkeit der Datei ``test.gif'' zum User ``meguro'' und der Gruppe ``infosys''. Um nur die Gruppenzugehörigkeit zu ändern, genügt der Befehl chgrp(1) <Gruppe> <Datei>. Die Zugriffsrechte lassen sich nachträglich mit dem Befehl chmod(1) <Mode> <Datei> modifizieren. Als Mode kann man die Zugriffsrechte im Zahlencode angeben. Diese sind in einer vierstelligen Zahl codiert:
  • Eine ``2'' in der ersten Ziffer setzt das SUID-Flag, eine ``4'' das SGID-Flag.
  • Die weiteren drei Ziffern sind analog der ls-Ausgabe für User-, Group- und Others zu setzen, wobei
    • eine ``1'' für ausführbar
    • eine ``2'' für schreibbar
    • eine ``4'' für lesbar
    steht.
Möchte man z.B. eine Datei für sich selbst und Mitglieder der gleichen Gruppe ausführ- und lesbar machen, für sich selbst schreibbar lassen und allen anderen nur Leserechte auf diese Datei geben, so setzt man chmod 0754 <Datei> was in der ls-Ausgabe ``-rwxr-xr--'' endet.
Eine weitere Möglichkeit Zugriffsrechte mit dem Befehl chmod zu setzen, besteht darin die bestehenden Rechte einzeln abzuändern. Dies geschieht mit chmod <Wer><+/-><Was> <Datei>:
  • <Wer> kann aus einem oder einer Kombination von ``u'' für User, ``g'' für Group, ``o'' für Others und ``a'' für alle bestehen.
  • ``+'' oder ``-'' sagen aus, ob das Recht hinzukommen oder weggenommen werden soll.
  • <Was> kann wiederrum aus einem oder einer Kombination aus ``r'', ``w'', ``x'' und ``s'' bestehen.
So entzieht chmod go-rwx <Datei> z.B. allen Nicht-Eigentümern der Datei alle Rechte.

Um die Zugriffsrechte einer neuangelegten Datei zu beeinflussen, gibt es eine Art ``default*17*'' für Zugriffsrechte, die mit der umask(2) Funktion der Shell gesetzt werden. Mit dieser Funktion wird eine Maske gesetzt, welche Rechte ausblendet. Ausgehend von ``0777'' des o.g. Zahlenmodells (also Schreib- und Leserechte für jedermann; Bei Erzeugung von Executables, z.B. durch einen Compiler, auch noch Ausführungsrechte), werden mit umask <Maske> Rechte ausgeblendet. z.B. setzt umask 22*18* die Default-Rechte auf ``-rw-r--r--'' oder umask 0007 auf ``-rw-rw----''.
*14* Leserechte benötigt man u.a. zum Kopieren.
*15* Nur so ist das Ändern von /etc/passwd, die nur für Root schreibbar ist, möglich.
*16* Oft kann man statt des ``:'' auch ein ``.'' verwenden.
*17* Grundeinstellung
*18* Führende Nullen werden ignoriert.

3.4 Prozeßrechte

Obengenannte Rechte für Dateien, gelten bedingt genauso für laufende Prozesse. Wird ein Prozeß gestartet, so läuft dieser prinzipiell mit der UID des Aufrufers. Im Falle des s-Bits (s. 3.3) trennt sich die Identität des Prozesses in die UID des Aufrufers, also dessen gesamten Umfeld, wie Umgebungsvariablen o.ä., und der effektiven UID (EUID) auf. Die EUID bestimmt die Rechte die dieser Prozess bekommt, nämlich jene des Besitzer der Datei. Ein Prozeß kann dann wiederrum lesend oder schreibend auf Dateien zugreifen, so wie dies der jeweilige User tun dürfte. Prozeßsignale, also Signale die ein Benutzer an einen laufenden Prozeß senden kann (z.B. um den Prozeß zu beenden oder ein gewisses Verhalten zu erzwingen), können an einen Prozeß gesendet werden, wenn der Prozeß dem Benutzer gehört. Prozesse, die von einem (Parent-) Prozeß abstammen, sog. Childs die durch den fork(2) Vorgang entstehen, können auf gleiche Weise beeinflusst werden.

Mit der ``-u'' Option des ps(1) Kommandos läßt sich die UID eines Prozesses anzeigen, z.B.:
orpaulze@pcinfo7:~> ps -u
USER       PID %CPU %MEM   VSZ  RSS  TT  STAT STARTED       TIME COMMAND
orpaulze   565  5.0  3.2   448  192  p1  R+   12:53AM    0:00.06 ps -u
orpaulze   341  0.1  9.6   656  600  p1  Ss    8:15PM    0:01.68 -tcsh (tcsh)

Hier gehören beide Prozesse (PID 565 und 341) dem User ``orpaulze''.
Wieviel Prozesse unter der UserID eines Benutzers laufen dürfen, kann in den meisten Unix-Systemen vom Systemverwalter begrenzt werden. Mehr zum Thema Prozesse und deren Verwaltung im Kapitel 3.6

3.5 Filesystem

Das Filesystem-Konzept von Unix unterscheidet sich prinzipiell von dem der Microsoft Produkte DOS und Windows3.x/95/NT. Es macht, aus der Sicht des Benutzers, keine Unterscheidung zwischen den physikalischen Laufwerken anhand von Laufwerksbuchstaben oder Ähnlichem. Vielmehr ``verbaut'' es alle zur Verfügung stehenden Massenspeicher-Devices, wie Festplatten-Partitionen, CD-ROM's , Disketten, Netzwerkverzeichnisse, usw., im Dateisystem-Baum des Systems. Für den Benutzer ist es deshalb auf den ersten Blick nicht ersichtlich, auf welchem Stück der Hardware er sich beim Betreten eines Verzeichnisses mit cd(1) befindet, bzw. woher eine Datei wirklich stammt. Welche Devices nun welchen Teil im Dateisystem repräsentieren, kann der Systemverwalter jederzeit festlegen, der entsprechende Befehl zum ``anmontieren'' eines Massenspeichers an ein Verzeichnis ist mount(8). Ihm übergibt man das entsprechende Device-File (z.B. /dev/wd0a), das Verzeichnis unter dem das Device ``hängen'' soll, den Filesystem-Typ und eventuelle Optionen, die z.B. anzeigen das das Device nur read-only gemountet werden soll, also Schreibzugriffe prinzipiell nicht möglich sind. Gängige Filesystem-Typen für FreeBSD sind z.B.:
  • ufs Das lokale Unix-Filesystem für Festplatten oder Disketten. Auf ihm lassen sich Dateien mit einer Dateinamenlänge von bis zu 254 Zeichen speichern und Rechte, wie in 3.3 beschrieben, vergeben.
  • msdos Eine Möglichkeit auch MS-DOS Filesysteme (``FAT'') unter Unix*19* verwenden zu können. Dieses Filesystem unterliegt in der Dateinamen-Vergabe natürlich der 8.3-Einschränkung von DOS und besitzt keine Art von Benutzerreche, bzw. Datei-Eigentümer.
  • iso9660 Das ISO-kompatible Filesystem für die meisten CD-ROMs. Dateinamen dürfen hier bis zu 254 Zeichen lang sein. Eine Vergabe von Rechten oder Datei-Eigentümern gibt es nicht.
  • nfs Das Network-Filesystem für den Zugriff auf Verzeichnisse anderer Rechner im Netzwerk. Prinzipiell kann jeder Unix-Rechner Verzeichnisse auf diese Weise für andere zur Verfügung stellen, Clients existieren aber auch für fast alle gängigen Betriebssysteme. Dateinamenlängen und Zugriffsrechte sind vom verwendeten Filesystem auf dem NFS-Server abhängig.
  • procfs Dies ist ein Filesystem, welches ähnlich wie NFS nicht auf lokal vorhandene Hardware zugreift. Es repräsentiert die momentan auf dem Unix-System laufenden Prozesse. Jeder Prozess erhält ein Verzeichnis mit der PID als Namen, in dem verschiedene Daten über den Status dieses Prozesses gespeichert werden. Programmme zur Prozeß-Überwachung können aus diesen Dateien lesen und so die Prozeß-Informationen anzeigen.
  • swap Auf Devices diesen Typs werden Inhalte von Speicherbereich ausgelagert, die im RAM keinen Platz mehr haben und momentan nicht von Prozessen benutzt werden. Im Gegensatz zu den ``Auslagerungsdateien'' von Windows wird unter Unix prinzipiell nur in komplette Festplatten-Partitionen ge-``swappt'' und nicht in eine Datei im Dateisystem. Mehrere Swap-Partitions sind möglich.
Darüber hinaus gibt es noch eine Vielzahl weiterer Filesystem-Typen, die man für spezielle Anwendungen, aufgrund ihrer Robustheit, besonderer Funktionen oder Geschwindigkeit, einsetzen kann.

Welche Devices beim Booten des Systems wohin gemountet werden, wird in der Datei /etc/fstab (fstab(5)) festgelegt. Eine Typische fstab kann z.B. so aussehen:
/dev/wd0a                       /               ufs     rw 1 1
/dev/wd0s1                      /dos            msdos   ro 0 0
/dev/wd0s2e                     /usr            ufs     rw 1 1
proc                            /proc           procfs  rw 0 0
suninfo1:/home                  /home           nfs     rw 0 0
suninfo1:/usr/spool.mail        /var/mail       nfs     rw 0 0
suninfo1:/soft/FreeBSD          /usr/local      nfs     rw 0 0

In diesem Beispiel werden 3 lokale Festplatten-Partitionen beim Booten gemountet. Zwei davon mit dem Unix-Filesystem (ufs) für Lese- und Schreibzugriffe (rw), eine mit einem MSDOS-Filesystem für Nur-Lese-Zugriff (ro). Desweiteren wird das Prozeß-Filesystem unter /proc gemountet und drei Netzwerk-Verzeichnisse eines anderen Rechners (suninfo1) per NFS eingehängt. Das fünfte Feld gibt an, ob das Filesystem mit dump(8) gesichert werden soll. Im letzten Feld läßt sich eine Reihenfolge für den Filesystem-Check*20* beim Booten festlegen.
Benutzer können jederzeit mit dem Kommando mount (ohne Parameter aufgerufen) feststellen welche Devices oder Netz-Verzeichnisse im System verwendet werden, aber auch die Ausgabe von df(1) zeigt dies, zusammen mit Informationen über den belegten Platz auf dem Filesystem, an.
orpaulze@pcinfo7:~> df
Filesystem               1K-blocks     Used    Avail Capacity  Mounted on
/dev/wd0a                    19966    14458     3910    79%    /
/dev/wd0s1                   30676    14016    16660    46%    /dos
/dev/wd0s2e                  89102    71702    10270    87%    /usr
procfs                           4        4        0   100%    /proc
suninfo1:/home              673043   461387   144352    76%    /home
suninfo1:/usr/spool.mail    411022   156740   213180    42%    /var/mail
suninfo1:/soft/FreeBSD     1476207   761703   640694    54%    /usr/local

Das die Prozentangaben für die Kapazität manchmal auf den ersten Blick nicht mit dem belegten Platz in Einklang stehen, rührt daher, daß beim Anlegen eines Filesystems (mit newfs(8)) vom Systemverwalter ein gewisser Prozentsatz an Speicherplatz reserviert werden kann, auf den nur ``root'', bzw. Prozesse mit der (E)UID 0, schreiben darf. Dies verhindert das Massenspeicher durch Benutzer soweit belegt werden, daß der normale System-Betrieb aufgrund von Speicherplatzmangel nicht mehr gewährleistet ist.

Selbst bei einem rein lokalen Unix-System empfiehlt es sich den Dateisystem-Baum auf verschiedene Filesysteme aufzuteilen. Meist wählt man hier eine Aufteilung in mindestens ``/'' (die Root-Partition, immer die erste im System) und ``/usr'', oft auch noch ``/var''. Im ersten Bereich liegen die wichtigsten Programme zum Betrieb des Systems, zumindest soweit das es bootet und andere Filesysteme mounten, checken oder anlegen kann, sowie der Kernel selber. In ``/usr'' befinden sich alle weiteren, oft auch nachträglich installierten Programme, sowie Manual-Pages, Programm-Bibliotheken und ähnliches. Unter ``/var'' sollten sich ausschließlich schnell-verändernde Daten wie Logfiles oder Statusinformationen befinden. Vorteil dieser Trennung im Filesystem ist, daß wenn eine Partition defekt ist, nicht gleich alles betroffen ist, sondern nur jener Teil auf eben dieser Partition. Zumindest Bootet das System (solang nich ``/'' defekt ist) und man kann dann mit den Utilities auf der Root-Partition weitere Maßnahmen ergreifen. Wenn die Root-Partition defekt sein sollte, reicht es in den meisten Fällen diese neu zu erstellen*21* und hoffentlich vorher gesicherte Konfigurations-Dateien aus /etc wieder einzuspielen. Auch ein gewisser Geschwindigkeitsgewinn, vorallem bei Aufteilung auf verschiedene Festplatten oder der Verwendung von speziellen Filesystem-Typen für spezielle Anwendungen (z.B. News-Server oder /tmp), ist zu beobachten.

Soll ein Rechner für einen oder mehrer andere einen Teil des Filesystems zugänglich machen, so muß dies in der Datei /etc/exports (exports(5)) eingetragen werden. Als mögliche ``Nutzer'' der in dieser Datei angegebenen Verzeichnisse können Rechner über Hostname oder IP-Adresse, Rechnergruppen (über NIS-netgroups) oder (in neueren BSD-Varianten) IP-Subnetze eingetragen werden. Dort kann auch festgelegt werden, ob die NFS-Clients in diesen Bereich schreiben dürfen und ob dortige User mit der UID 0 (also root) gleichwertig wie dem root auf dem NFS-Server behandelt werden sollen*22*. Zur Identifikation aller anderen User werden einzig die UID und die GID verwendet, d.h. gehört eine Datei auf einem Rechner einer gewissen UID, so wird diese Zugehörigkeit auch auf allen anderen Rechnern, die dieses NFS-Verzeichnis verwenden, erstellt, gleich o.b. diese UID/GID auch dem ``Namen nach'' gleichen Benutzer gehöert.
Eine exports-Datei kann z.B. so aussehen:
/proj/archiv            -ro,access=pcinfo:pclehr:eddie.technik.bos-muenchen.de
/var/spool/mail         -access=pcinfo:pclehr
/usr/spool.mail         -access=pcinfo:pclehr
/home                   -access=pcinfo:pclehr:kiste.muffin.org
/soft/local             -ro,access=pcinfo
/mnt                    -access=pcinfo,root=pcinfo7
/cdrom                  -ro,access=pcinfo


*19* Meist nur auf PC-Unixarten zu finden
*20* Damit keine ``unsauberen'' Filesysteme, z.B. nach einem unsachgemässen Reboot oder einem Absturz des Systems, verwendet werden, wird während des Bootens eine Überprüfung und, soweit möglich, Reperatur des (lokalen) Filesystems mit fsck(8) vorgenommen.
*21* z.B. mit der Fixit-Floppy von FreeBSD.
*22* Normalerweise wird UID 0 auf NFS-Clients zu ``nobody'' und GID 0 zu ``nogroup'' gemappt, da man nicht davon ausgehen kann, daß ein root auf dem NFS-Server der gleiche ist wie auf dem NFS-Server. Mappt man diese UID/GID allerdings auch auf dem Server als 0, so hat ein root auf einem NFS-Client die gleichen Rechte wie der Server-root in diesem Bereich des Dateisystems.

3.6 Shell

Die Shell ist der Prozess welcher die Benutzereingaben entgegennimmt und die vom Benutzer gewünschten Befehle ausführt, ähnlich dem ``COMMAND.COM'' unter DOS. Sie wird üblicherweise direkt nach dem Einloggen*23* gestartet. Die typischen Shells beinhalten in der Regel eigene Porgrammiersprachen die Schleifenkonstrukte und Funktionen beherrschen. Man unterscheidet im groben drei Klassen von Shells, die Bourne-Shells, die C-Shells und alle übrigen. Die Gruppe der Bourne-Shells umfasst die Bourne-Shell (sh), die Korn-Shell (ksh), die GNU Bourne-Again Shell (bash) und die Z-Shell (zsh). Diese Gruppe versteht (mehr oder weniger) die gleiche Syntax an Befehlen deshalb ist es besonders leicht zwischen ihnen hin und her zu wechseln. Die zweite bekannte Gruppe der Shells umfasst die TC-Shell (tcsh) und die C-Shell (csh). Sie unterscheiden sich von den Bourne-shells durch eine andere, mehr an die Programmiersprache C angelehnte, Syntax, welche sie inkompatibel zu den Bourne-Shells macht. Zuletzt gibt es auch noch jede Menge Shells, die nicht in die o.g. Gruppen passen, z.b. die Plan9-Shell (rc), die Key-Shell (keysh) und viele andere, die aber alle meistens für irgend einen speziellen Zweck und nicht als allgemeine Shell zu empfehlen sind. Um die Wahl der richtigen Shell zu erleichtern hier ein kleiner Überblick:
  • sh Die sh ist sozusagen die Ur-Shell, man wird sie auf jedem Unix-System als /bin/sh finden. Sie ist als User-Shell nicht zu empfehlen, da sie kaum Editiermöglichkeiten der Eingabezeile bietet. Allerdings wird sie aufgrund ihrer hohen Verfügbarkeit gerne für Shell-skripten verwendet.
  • csh Die csh ist die ``erste'' benutzerfreundlichere Shell gewesen, und deshalb auch auf vielen Systemen unter /bin/csh zu finden, allerdings hat sie viele BUG's*24* und ist wie alle C-Shells nicht kompatibel zur sh. Darum kann ich sie nicht empfehlen. *25*
  • tcsh Die tcsh ist die Weiterentwicklung der csh, und deshalb zu dieser kompatibel. Sie ist schon in der Grundeinstellung eine der benutzerfreundlichsten Shells, und wird deshalb auch von den mit Abstand meisten Benutern bevorzugt. Allerdings ist die mangelnde Bourne-Shell Kompatibilität ein Manko wenn man ab und zu auch Shell-Skripten schreiben will, denn wer lernt schon gerne doppelt so viel ?
  • ksh Wieder eine Shell aus der Bourne-Shell-Reihe, die auch recht benutzerfreundlich ist und einem die Wahl zwischen einem Emacs- und einem vi-kompatiblen Editiermodus bietet. Sie ist eine der wenigen Shells, die mehrzeilige Kommandos editieren und in der History verarbeiten kann.
  • bash Die Neuimplementation der Bourne-Shell von GNU. Sie ist auch in der Grund-Einstellung recht benutzerfreundlich, bietet aber nicht so viele Konfigurationsmöglichkeiten. Sie ist mit der tcsh eine der beliebtesten Shells.
  • zsh Die zsh bietet alles was die vorhergehenden Shells auch bieten (insbesondere ein vernünftiges multi-line editing (ähnlich der ksh) und eine programmierebare Completion (ähnlich der tcsh)) allerdings ist es ein höherer Aufwand die Features zu lernen und zu konfigurieren, so daß sie den eigenen Wünschen ensprechen.

*23* siehe auch 3.2
*24* Bug's sind Fehler in einem Programm
*25* http://late5.e-technik.uni-erlangen.de/Software-Doku/csh-harmful.html

Nun etwas über die Benutzung einer typischen Bourne-Shell:

  • kommando

    alls ``kommando'' ein Alias oder eine Shell-Funktion ist, wird dieses intern ausgeführt. Ansonsten wird kommando im Pfad ($PATH)*26* gesucht und ausgeführt, (Ausführbare Programme werden unter UNIX mit einem x-bit*27* gekennzeichnet und nicht mit einer Endung (.com,.exe)). Die Trennung zwischen einem Befehl und dem nächsten erfolgt entweder durch den Beginn einer neuen Zeile oder durch ein ;.
    Beispiel: ls -l ; who


    *26* Im Gegensatz zu DOS wird . nicht implitzit durchsucht
    *27* siehe 3.3

  • Normalerweise liest ein Befehl von der Tastatur und gibt die Daten auf dem Bildschirm aus. Dies kann man natürlich auch ändern:

  • kommando < datei1 >datei2 2>datei3

    Liest die Eingabe für den Prozeß ``kommando'' aus datei1 und schreibt das Ergebnis in datei2. Etwaige Fehlermeldungen landen in datei3*28*
    Beispiel:
    grep Milch <einkaufszettel; uptime > 8.April; make 2>errors


    *28* In Wirklichkeit wird lediglich Filedescriptor Nr. 2 in die Datei umgelenkt, per Konvention sollte ein Programm Fehlermeldungen aber dort ausgeben, was dann zum gewünschten Effekt führt. Man kann Filedescriptoren aber auch auf andere Filedescriptoren umlenken z.B.: traceroute pcinfo7>/dev/null 2>1 wird die Fehlerausgabe zusammen mit der Standardausgabe ins nichts umgelenkt.
  • kommando<<Marke

    Liest die Eingabe für ``kommando'' aus der selben Quelle aus der die Befehle gelesen werden, bis zur Endemarkierung Marke:

    mail sec@42.org <<ende
    Hi,
    dies ist meine testmail
    ende
    
    

  • Man kann natürlich auch die Ausgabe eines Prozesses direkt als Eingabe für den nächsten Prozeß nehmen. Dies ist eine sogenante ``Pipeline'':

  • kommando1 | kommando2 | kommando3

    Verwendet die Ausgabe von kommando1 direkt als Eingabe für kommando2 u.s.w.
    Beispiel: ls -l | sort -n +4 | more

  • `kommando`

    Die sogenannten ``Backticks'' ersetzen den Platz in der Befehlszeile durch die Ausgabe des Befehls.
    Beispiel: finger `whoami`

  • Filename globbing

    Die Shell verwendet gewisse Zeichen um Filenamen zu ``erzeugen''*29*
    * steht für beliebig viele (auch garkeine) Zeichen, nicht jedoch für einen Punkt am Anfang eines Filenamens.
    ? steht für genau ein beliebiges Zeichen, wiederum jedoch nicht für einen Punkt, wenn es am Anfang eines Filenamens steht.
    [abc] steht für genau eins der Zeichen zwischen den eckigen Klammern.
    [a-q] für genau ein Zeichen aus dem Bereich.
    [!...] für genau ein Zeichen das nicht aus ... ist
    Beispiel: ls einkauf* oder lp [1-3].April


    *29* Im Gegensatz zu DOS, wo jede Anwendung selbst dafür verantwortlich ist, diese Zeichen auszuwerten.
  • Variablen

    Die Shell verwaltet sogenannte ``Variablen'' die beliebige Werte (Zahlen, Strings) annehmen können. Man unterscheidet 2 Arten von Variablen, normale und sog. Environment-Variablen. Erstere sind nur der Shell selbst bekannt, wobei letzere auch an von der Shell gestartete Prozesse weitergegeben werden. Eine normale Variable wird zu einer Environment-Variablen in dem sie mit dem export-Befehl als solche markiert wird.
    Beispiel: DISPLAY=pcinfo7:0;export DISPLAY;xterm

    Die Shell selber expandiert Variablen zu ihren werten, wenn man ihnen ein $ voranstellt: eins=zwei; echo eins: $eins
    Man kann allerdings eine Variable gezielt an genau ein Programm übergeben, indem man die Definition durch ein Leerzeichen getrennt vor den Programmaufruf stellt
    Beispiel: NNTPSERVER=news.lrz-muenchen.de tin

    Die Shell stellt uns einige vordefinierte Variablen zu Verfügung:

    
    $SHELL Pfad zur Loginshell                                        
    $HOME  Home-Directory                                             
    $USER  Username                                                   
    $?     Exit-Code des zuletzt ausgeführten Prozesses               
    $$     Prozeß-Id der Shell                                        
    $!     Prozeß-Id des zuletzt im Hintergrund ausgeführten Prozesses
    
    

  • Quoting

    Es gibt verschiedene Möglichkeiten die Expansion von Variablen und das Filename-globbing zu verhindern, dies nennt man Quoting. Wenn ein Text von ``single quotes'' ' begrenzt wird, findet darin keinerlei Umwandlung durch die Shell statt, selbst Zeilenumbrüche können auf diese Weise eingegeben werden: echo 'eink*' '$eins'
    Verwendet man zur Begrenzung ``double quotes'' ", so wird das Filename-globbing unterbunden, alles andere (wie Variablenexpansion, Auswerten von Backticks ...) wird weiterhin durchgeführt: echo "*" "`id -u`"
    Der Backslash \ verhindert lediglich die Sonderbehandlung des nachfolgenden Zeichens: echo \'eink*\'

  • Job control

    Natürlich kann man auch von einer Shell aus mehrere Prozsse starten, und sogar verwalten. Um einen Befehl im ``Hintergrund'' auszuführen schliesst man ihn einfach mit einem & statt mit einem ; ab: xterm &
    Um einen bereits normal im Vordergrund laufenden Prozeß in den Hintergrund zu befördern muß man ihn zuerst ``suspenden'' (anhalten) um wieder zur Shell zurückzukehren. Dies erreicht man in der Regel mit ^Z*30* in der Shell in der Prozeß gestartet wurde. Man kann allerdings auch aus einer anderen Shell heraus dem Prozeß ein STOP-Signal schicken: kill -STOP <pid>
    Die Shell selber bietet einem dann die Möglichkeit mit jobs die laufenden Prozesse einzusehen, und sie dann mit fg oder bg in der Vorder- bzw. Hintergrund zu verschieben. Zu diesem Zweck nummeriert die Shell die Prozesse durch, diese Zahlen können an stelle der PID's fuer fg,bg und kill verwendet werden, wenn man ihnen ein Hintergrund laufender Proze"s etwas von der Tastatur lesen\footnote{Manchmal auch schon bei der Ausgabe auf den Bildschirm, dieses Verhalten l"asst sich jedoch wiederum mit {\tt stty tostop} einstellen} will wird er jedoch angehalten (um Konflikte zu vermeiden) was die Shell mit einem \verb<pid> Stopped (input)_ anmerkt und wartet bis man ihn wieder in den Vordergrund holt.


    *30* Dieses Kontroll-Zeichen kann man mit dem Befehl stty(1) beliebig umstellen
  • Funktionen

    Man kann beliebige Shell-stückchen als Funktion deklarieren, indem man das Stück in {} fasst und ein funktionsname() voranstellt. Man kann innerhalb der Funktion auf die Aufrufparameter mittels $1 $2 ... oder mit $@ und $* zugreifen: (die beiden unterscheiden sich nur durch ihre Expansion innerhalb von double quotes: "$@" -> "$1" "$2" und "$*" -> "$1 $2" )

    hello(){
      echo Hello $*, this is a nice new World.
    }
    
    hello you there
    
    
  • Kontrollstrukturen

    Nun fehlen uns noch die bedingten Befehle:

    befehl1 && befehl2 : befehl2 wird nur ausgeführt, wenn befehl1 wahr zurückgeliefert*31* hat.
    befehl1 || befehl2 : befehl2 wird nur ausgeführt, wenn befehl1 false zurückgeliefert hat.

    if bedbefehl ; then
      wbefehl
    else
      fbefehl
    fi
    
    
    Wenn der bedbefehl wahr zurückliefert, wird wbefehl ausgeführt, ansonsten fbefehl. Der else-Fall kann natürlich auch komplett weggelassen werden. Für die Verwendung von if ist die Kenntniss des ``test(1)'' - Befehls wichtig:
    test ausdruck liefert true zurück wenn ``ausdruck'' wahr ist, und false sonst. Die wichtigsten Elemente für ``ausdruck'' sind:
    
    -d file    wahr wenn file ein Directory ist
    -w file    wahr wenn file schreibbar ist   
    -e file    wahr wenn file existiert        
    n1 -lt n2  wahr wenn n1<=n2                
    n1 -eq n2  wahr wenn n1=n2 (Zahlen)        
    s1 = s2    wahr wenn sq=s1 (Strings)       
    ! ausdruck wahr wenn ausdruck falsch ist   
    a1 -a a2   wahr wenn a1 und a2 wahr sind   
    a1 -o a2   wahr wenn a1 oder a2 wahr ist   
    
    

    *31* wahr entspricht einem Rückgabewert ($?) von 0, false allen anderen.
    test(1) kann auch als [ aufgerufen werden, wenn man der Argumentliste eine abschließende ] hinzufügt.

    case a in
    match) befehl ;;
    ...
    esac
    
    
    wenn der Inhalt von Variable a auf einen der ``match*32*'' passt, dann wird der zugehörige Befehl ausgeführt.
    *32* verwendet die selben Sonderzeichen wie beim Filename globbing
  • Schleifen

    Um die Shell zu einer richtigen Programmiersprache zu, machen fehlt natürlich noch etwas. Richtig -- Schleifen:

    for variable in liste ; do
      befehl
    done
    
    
    ``liste'' sind durch Spaces*33* getrennte Werte. Die Schleife wird für jeden Wert aus liste einmal durchlaufen
    while befehl; do
      befehl2 
    done
    
    
    solange die Ausführung von ``befehl'' nicht fehlschlägt, wird befehl2 ausgeführt.

    *33* In Wirklichkeit alle in $IFS aufgezählten Zeichen, normalerweise Space, Tab und Newline. Als Benutzer kann ich das aber jederzeit ändern, ist aber normalerweise nur in Shell-skripten sinnvoll

  • Beispiel:

    crsr(){
      BKSP=`tput kb`
      BKSP="$BKSP$BKSP"
      while true ; do
        echo -n " |$BKSP" ; sleep 1
        echo -n " /$BKSP" ; sleep 1
        echo -n " -$BKSP" ; sleep 1
        echo -n " \\$BKSP"  ; sleep 1
      done
    }
    
    checkfiles() {
      for file in * ; do
        if [ -d $file ] ; then
          cd $file && checkfiles && cd ..
        fi
        case file in
          *.bak) echo $file sollte gel"oscht werden;;
        esac
      done
    }
    
    crsr &
    prozess=$!
    checkfiles
    kill $prozess
    echo Fertig.
    
    



  • This document was converted from LaTeX using Karl Ewald's latex2html.