25.01.2013 Aufrufe

UNIX-Systemprogrammierung

UNIX-Systemprogrammierung

UNIX-Systemprogrammierung

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

1<br />

<strong>UNIX</strong>-<strong>Systemprogrammierung</strong><br />

<strong>UNIX</strong>-<strong>Systemprogrammierung</strong><br />

Inhalt<br />

© 1996, Axel T. Schreiner, Fachbereich Mathematik-Informatik, Universität Osnabrück<br />

Eine Einführung in die Benutzung von <strong>UNIX</strong> (Dateien, Prozesse, Kommunikation) auf<br />

der Ebene von Programmiersprachen wie C und Objective C.<br />

Dieser Band enthält Kopien der OH-Folien, die in der Vorlesung verwendet wurden.<br />

Diese Information steht außerdem im W orld-Wide Web online zur Verfügung; sie ist in<br />

sich und mit einer Kopie der relevanten Manualseiten für Linux über Hypertext-Links<br />

verbunden. Die Beispielprogramme werden maschinell in diesen Text eingefügt.<br />

Gründliche Kenntnis von ANSI-C und Grundkenntnisse im Bereich objekt-orientierte<br />

Programmierung werden vorausgesetzt. Der Band stellt kein komplettes Manuskript<br />

der Vorlesung dar. Zum Selbststudium müßten zusätzlich Bücher über das <strong>UNIX</strong>-<br />

System und auch über C-Programmierung konsultiert werden.<br />

Einführung<br />

Kopierprogramme<br />

Systemaufrufe testen<br />

Dateinamen<br />

Implementierung der Datei-Klassen<br />

Prozesse<br />

Prozeßkommunikation


2<br />

Kalender<br />

April 16 Übersicht, Server, Programmieren mit Systemaufrufen, W as ist ein<br />

Systemaufruf?<br />

18 Programmieren mit Klassen, Fehlerbehandlung<br />

23 Fehlerbehandlung, Main-Framework<br />

25 Architektur der makefiles, Main-Framework<br />

30 Main-Framework, Einfache Transfer-Operationen<br />

Mai 2 Einfache Transfer-Operationen, Fd, ReadFd, WriteFd, PrintFd<br />

7 Einfache Transfer-Operationen, Dateien kopieren, Dateien im<br />

Adreßraum<br />

9 krank<br />

14 Dateien kopieren, Implementierung: Stat, Fd, ReadFd, WriteFd,<br />

PrintFd, Path<br />

16 Himmelfahrt<br />

21 MappedReadFd, MappedWriteFd, Systemaufrufe testen<br />

23 ls<br />

28 Pfingsten<br />

30 Pfingsten<br />

Juni 4 Prozesse: Begriff, ps, proc-Dateisystem, Prozeßzustand<br />

6 Prozeßmanipulationen: psj, Prozeßgruppen, Sessions, Terminalgruppen<br />

11 Prozeßmanipulationen: Job-Control, Dämonen, Waisen. Signale<br />

13 Posix-Probleme. orphan. Signale: Signal-Operationen, Signal-Nummern<br />

18 Signale: Signal-Nummern, sig, dsh<br />

20 Demo-Shell: Ziel, Architektur der Hauptschleife, fork, exec, wait<br />

25<br />

27<br />

Juli 2<br />

4<br />

9 wird verlegt: 30.4. und 7.5. um 9:15 Uhr<br />

11 wird verlegt: 14.5. und 21.5. um 9:15 Uhr


3<br />

Termine<br />

Vorlesung Dienstag, 10:15 31/449a Schreiner<br />

Donnerstag, 16:15 31/449a<br />

Übungen Montag, 12:15 31/449a Bischof<br />

Sprechstunde Donnerstag ab 15:00 n.V. 31/321 Schreiner (969-2480)<br />

31/318b Bischof (969-2534)<br />

Literatur<br />

Diese Folien befinden sich auf unserem WWW -Server<br />

http://www.informatik.uni-osnabrueck.de/Vorlesung/USP und gedruckt in der<br />

Lehrsammlung. Sie sollten mit einem W eb-Browser betrachtet werden. Bitte nicht per<br />

Laserdrucker drucken. Hier können das jeweils aktuelle Skript (Mai/8) im HTML-<br />

Format und die aktuellen Quellen als komprimierte TAR-Datei abgeholt werden.<br />

Es gibt heute sehr viele Bücher über <strong>UNIX</strong> und <strong>Systemprogrammierung</strong>. Die<br />

folgenden Bücher sind nützlich. Soweit vorhanden, befinden sie sich in der<br />

Lehrsammlung.<br />

Kernighan/Pike 3-446-14273-8 Der <strong>UNIX</strong>-Werkzeugkasten<br />

Kerninghan/Ritchie 3-446-15497-3 Programmieren in C<br />

NeXT Software 0-201-63251-9 NeXTSTEP Object-Oriented Programming and the<br />

Objective C Language<br />

Rochkind 0-13-011800-1 Advanced <strong>UNIX</strong> Programming<br />

Stevens 0-201-56317-7 Advanced Programming in the <strong>UNIX</strong> Environment<br />

Stevens 0-13-949876-1 <strong>UNIX</strong> Network Programming<br />

Einige meiner Beispiele habe ich in Artikeln in unix/mail behandelt.<br />

Einige Manual-Seiten für Linux befinden sich auch auf dem Server. Eine<br />

private Kopie der Datei bookmarks.html eignet sich als Bookmarks für OmniWeb zum<br />

schnellen Zugriff auf die Systemaufrufe. Bitte nicht per Laserdrucker drucken.


Einführung<br />

Einführung<br />

Dieser Abschnitt zeigt primitive Beispiele zur Programmierung mit Systemaufrufen und<br />

mit Klassen und führt grundsätzlich verwendete Klassen ein.<br />

Fd verkapselt einen File-Deskriptor, der die Verbindung zwischen einem Prozeß und<br />

einer Datei repräsentiert. Diese Klasse wird erst im nächsten Abschnitt diskutiert.<br />

Exception und Handler verkapseln die Behandlung von Fehlern in verschachtelten<br />

Funktionsaufrufen.<br />

Main ist ein Framework für ein Programm, das eine Kommandozeile bearbeitet.<br />

Themen<br />

Systemaufrufe<br />

Programmieren mit Systemaufrufen<br />

Was ist ein Systemaufruf?<br />

Programmieren mit Klassen<br />

Fehlerbehandlung<br />

Ein Framework für die Kommandozeile<br />

exit, _exit<br />

write<br />

Wichtige Bibliotheksfunktionen<br />

perror<br />

setjmp, longjmp<br />

strerror<br />

1-1


Programmieren mit Systemaufrufen<br />

Programmieren mit Systemaufrufen — hello/hello.c<br />

libc.h<br />

hello gibt die Argumente aus, mit denen das Kommando aufgerufen wird.<br />

hello illustriert ein typisches C-Programm, den Zugriff auf die Kommandozeile, die<br />

Systemaufrufe write() und _exit() und die Bibliotheksfunktion perror() zur<br />

Berichterstattung über Fehler bei Systemaufrufen.<br />

#include "libc.h"<br />

int main (int argc, char * argv []) {<br />

const char * progname = argv[0];<br />

}<br />

while (* ++ argv) {<br />

size_t len = strlen(argv[0]);<br />

if (write(1, argv[0], len) != len<br />

|| write(1, argv[1] ? " " : "\n", 1) != 1)<br />

perror(progname), _exit(1);<br />

}<br />

return 0;<br />

C-Funktionen sollte man vor Aufruf deklarieren.<br />

unistd.h ist eine POSIX-Definitionsdatei für die Systemaufrufe. POSIX verträgt sich<br />

(derzeit) nicht mit den präcompilierten Definitionsdateien für NeXT Objectvie C.<br />

libc.h enthält (bei NeXTSTEP) Deklarationen der (meisten) Systemaufrufe. Wir<br />

portieren, indem wir libc.h bei anderen Plattformen aus vorhandenen Definitionsdateien<br />

zusammenstellen.<br />

Kommandozeile<br />

Die Ausführung eines C-Programms beginnt durch (impliziten) Aufruf von main(). Als<br />

Argumente werden die ‘‘Wörter’’ der Kommandozeile übergeben.<br />

$ hello hello, world<br />

argv[]<br />

null<br />

h<br />

h<br />

w<br />

e<br />

e<br />

o<br />

l<br />

l<br />

r<br />

l<br />

l<br />

l<br />

o<br />

o<br />

d<br />

\0<br />

,<br />

\0<br />

main() erhält als int argc die Anzahl der Wörter auf der Kommandozeile und als char<br />

\0<br />

1-2


* argv[] die Wörter selbst. argc ist mindestens 1, denn der Kommandoname ist ein<br />

Wort, und argv[argc] ist immer ein Nullzeiger.<br />

File-Deskriptoren und Standard-Verbindungen<br />

File-Deskriptoren sind kleine, nicht-negative, ganze Zahlen, die Dateiverbindungen für<br />

Systemaufrufe repräsentieren.<br />

Ein Prozeß hat normalerweise a priori drei Dateiverbindungen:<br />

0 ist die Standard-Eingabe.<br />

1 ist die Standard-Ausgabe.<br />

2 ist die Diagnose-Ausgabe.<br />

Ausgabe — write<br />

size_t write (int fd, const char * buf, size_t count);<br />

write() gibt bis zu count Bytes zur Dateiverbindung fd aus, beginnend mit dem Byte<br />

bei buf, und liefert die Anzahl der ausgegebenen Bytes oder -1. Normalerweise ist das<br />

Resultat count.<br />

Fehlermeldungen — perror<br />

void perror (const char * prefix);<br />

perror() beschreibt den letzten Fehler, der von einem Systemaufruf verursacht wurde,<br />

auf der Diagnose-Ausgabe. Davor wird prefix ausgegeben.<br />

extern int errno;<br />

char * strerror (int errno);<br />

errno enthält eine Fehlernummer, die von Systemaufrufen nur gesetzt aber nicht<br />

gelöscht wird. strerror() liefert die zugehörige Fehlermeldung, die perror() implizit<br />

ausgibt.<br />

Prozeßende — _exit<br />

void _exit (int status);<br />

void exit (int status);<br />

int atexit (void (* function) (void));<br />

_exit() beendet den Prozeß, ohne die Aufräumungsaufarbeiten, die exit() zusätzlich<br />

vornimmt. status ist der Exit-Code des Prozesses, der dann an den erzeugenden<br />

Prozeß geliefert wird.<br />

atexit() hinterlegt Funktionen, die in umgekehrter Reihenfolge im Zuge von exit()<br />

ausgeführt werden, und liefert 0 bei Erfolg oder -1 bei Fehler.<br />

In main() ist return äquivalent zu exit(); der Resultatwert wird zum Exit-Code.<br />

1-3


Was ist ein Systemaufruf?<br />

Was ist ein Systemaufruf?<br />

Systemaufrufe werden wie Bibliotheksfunktionen aufgerufen — in einem C-Programm<br />

kann man keinen Unterschied erkennen.<br />

Die Leistung eines Systemaufrufs wird jedoch im Kern des Betriebssystems, außerhalb<br />

des Adreßraums des aufrufenden Prozesses erbracht:<br />

0<br />

1<br />

.<br />

.<br />

.<br />

Memory<br />

Map<br />

end<br />

0<br />

1<br />

.<br />

.<br />

.<br />

end<br />

traps:<br />

0 _exit<br />

1 read<br />

2 write<br />

write:<br />

...<br />

main()<br />

printf()<br />

write()<br />

trap 2<br />

Kern<br />

Hauptspeicher<br />

Programm<br />

Bibliotheken<br />

Im Adreßraum des aufrufenden Prozesses befindet sich im Code der Funktion zum<br />

Systemaufruf ein Maschinenbefehl zum Überwechseln in den Adreßraum des Kerns,<br />

wobei eine Tabelle am Ziel für begrenzte Einsprungmöglichkeiten sorgt.<br />

Der Kern hat wesentlich mehr Privilegien als der Benutzerprozeß — Hardware-Zugriff<br />

auf Geräte, Definition der Memory Map, Kontrolle der Unterbrechbarkeit etc.<br />

Aus der Sicht des Kerns sind Prozesse durch Tabelleneinträge beschrieben; der Kern<br />

entscheidet, welchen Prozeß er zur Ausführung bringt.<br />

1-4


File-Deskriptoren<br />

Prinzipiell können bei Systemaufrufen zwischen Prozeß und Kern beliebige Daten<br />

ausgetauscht werden. In der Praxis beschränkt man sich im Design auf einfache<br />

Datentypen, die leicht zwischen den Adreßräumen transportiert werden können: int<br />

oder Zeigerwerte in Registern sowie Puffer , die kopiert werden müssen.<br />

Systemaufrufe haben fast immer int als Resultattyp, wobei -1 einen Fehler und Null<br />

und manchmal positive Werte Erfolg anzeigen.<br />

Dateiverbindungen werden durch File-Deskriptoren repräsentiert, die im Kern per<br />

Prozeß als Indizes in eine Tabelle verwendet werden, die die Verbindungen beschreibt.<br />

Dadurch kann der Kern Dateiverbindungen selbst dann korrekt terminieren, wenn der<br />

Prozeß seinen eigenen Adreßraum total zerstört.<br />

1-5


Programmieren mit Klassen<br />

Programmieren mit Klassen — hello/echo.m<br />

echo gibt die Argumente aus, mit denen das Kommando aufgerufen wird.<br />

echo illustriert das Framework Main zur Bearbeitung der Kommandozeile und<br />

Methoden der Klasse PrintFd zur Verkapselung von formatierter Ausgabe.<br />

#include "Fd.h"<br />

#include "Main.h"<br />

@interface Echo: Object<br />

{<br />

id fout; // standard output<br />

}<br />

@end<br />

@implementation Echo<br />

- arg:(const char *)arg { // process one argument<br />

if (! fout)<br />

fout = [mainFrame fout];<br />

else<br />

[fout putc:’ ’];<br />

[fout puts:arg];<br />

return self;<br />

}<br />

- done { // following all arguments<br />

if (fout)<br />

[fout putc:’\n’];<br />

return self;<br />

}<br />

@end<br />

int main (int argc, char * argv []) {<br />

[[[Main new] setClient:[Echo new]] run:argv];<br />

return 0;<br />

}<br />

Objekt, Nachricht, Methode und Klasse<br />

Ein Objekt verwaltet einen gewissen Zustand. Ein Programm besteht darin, daß<br />

Objekte erzeugt werden, die dann miteinander kommunizieren und Aufgaben lösen.<br />

main() hat bestenfalls die Aufgabe, Nachrichten an die zuständigen Objekte zu<br />

vermitteln – die eigentlichen Aktivitäten wählt es selbst nicht aus.<br />

Ein Objekt modelliert man natürlich mit einer Struktur , in der der Zustand gespeichert<br />

wird. Eine Nachricht ist dann ein Funktionsaufruf, der sich auf ein Objekt bezieht und<br />

dessen Zustand ändert. Die Funktion selbst nennt man eine Methode .<br />

Eine Klasse besteht aus Objekten, die die gleichen Nachrichten empfangen können<br />

und damit gleichartige Zustände (Strukturen) und Methoden (Funktionen) besitzen.<br />

Dynamische Bindung<br />

Die gleiche Nachricht kann durchaus für völlig verschiedene Objekte sinnvoll sein:<br />

1-6


initialize, draw, free usw.<br />

Der gleiche Funktionsname kann also auf verschiedene Klassen von Objekten<br />

angewendet werden — was die Funktion exakt tut, kann von der Klasse des Objekts<br />

abhängen. Wenn beim Übersetzen die genaue Klasse des Objekts noch nicht bekannt<br />

ist, wird die Funktion endgültig erst zur Laufzeit ausgewählt — man spricht von<br />

dynamischer Bindung.<br />

Unabhängig von der Klasse müssen Argumente und Resultattyp allerdings schon beim<br />

Übersetzen bekannt sein.<br />

Information Hiding<br />

Um Chaos zu vermeiden, sollte der Zustand eines Objekts möglichst gut verkapselt<br />

sein. Nur Methoden dürfen den Zustand ändern.<br />

Für eine Klasse definiert man daher eine Struktur und Funktionen, wobei nur die<br />

Funktionen Zugriff auf die Strukturkomponenten besitzen.<br />

Objective C<br />

Objective C ist ANSI C mit wenigen Erweiterungen zur objekt-orientierten<br />

Programmierung. Die Sprache wurde von Cox definiert. Es gibt Implementierungen von<br />

Cox’ Firma Stepstone, von NeXT und von GNU. Entscheidend ist, daß man nicht nur<br />

einen Compiler, sondern auch die Klasse Object benötigt — nur die Systeme von NeXT<br />

und GNU sind deshalb einigermaßen kompatibel miteinander.<br />

Im wesentlichen gibt es zwei Erweiterungen: man kann Klassen (Struktur und<br />

Funktionen) vereinbaren und Nachrichten an Objekte versenden (Funktionen für<br />

Objekte aufrufen).<br />

Nachrichten<br />

Ein Objekt ist eigentlich ein Zeiger, der aber normalerweise mit dem Typ id vereinbart<br />

wird. Eine Nachricht ist ein Funktionsaufruf mit einer speziellen Syntax und mit einem<br />

Objekt als erstem Argument — das Objekt ist der Empfänger der Nachricht, und die<br />

Funktion nennt man Methode. Innerhalb der Methode heißt der Empfänger self.<br />

#include Definitionsdatei, einmal eingefügt<br />

id anObject; vereinbaren<br />

anObject = [Object new]; erzeugen<br />

if ([anObject isMemberOf:[Object class]]) prüfen<br />

[anObject free]; zerstören<br />

Die Syntax einer Nachricht ist an SmallTalk angelehnt:<br />

[empfänger name] ohne Argumente<br />

[empfänger name:argument key:argument ...] mit Argumenten<br />

[empfänger name:argument, argument ...] mit variablen Argumenten<br />

Der Name der Methode ist die Folge name:key:.. und muß im System eindeutig sein.<br />

Eine Methode kann für verschiedene Klassen definiert sein.<br />

1-7


Klassen und Klassenmethoden<br />

Es gibt eine Reihe von vordefinierten Klassen, die eine Hierarchie bilden, die mit der<br />

Klasse Object beginnt. Für jede Klasse gibt es ein Klassenobjekt, von dem man mit<br />

Hilfe der Methode new ein neues Objekt erhält.<br />

Es gibt ein Objekt nil und eine Klasse Nil; beides sind Null-Zeiger. nil akzeptiert jede<br />

Nachricht und liefert immer Null als Resultat.<br />

new ist eine Klassenmethode, denn damit wird eine Nachricht an ein Klassenobjekt<br />

geschickt. Es ist nur eine Abkürzung für:<br />

id anObject = [[Object alloc] init];<br />

[anObject free];<br />

alloc ist eine Klassenmethode, die ein Objekt erzeugt und mit 0 initialisiert; init ist<br />

eine Methode, die das resultierende Objekt initialisiert. Eine Variante von init muß<br />

unbedingt aufgerufen werden.<br />

free ist eine Methode, die den Speicherbereich ihres Empfängers freigibt. free liefert<br />

normalerweise nil als Resultat.<br />

class ist eine Methode und eine Klassenmethode, die jeweils einen V erweis auf das<br />

Klassenobjekt liefert:<br />

if ([anObject class] == [Object class])<br />

puts("super");<br />

Der Name einer Klasse wird nur als Empfänger direkt angegeben – sonst muß man<br />

class verwenden, um vom Klassennamen zum Klassenobjekt zu kommen.<br />

Klassenmethoden und Methoden können die gleichen Namen besitzen.<br />

1-8


Fehlerbehandlung<br />

Fehlerbehandlung<br />

Prinzip<br />

Fehler passieren meistens in verschachtelten Funktionsaufrufen und sollen dann auf<br />

einer äußerem Ebene abgefangen werden. Manche Sprachen bieten folgende<br />

Kontrollstruktur, die verschachtelt werden kann:<br />

try {<br />

} catch {<br />

}<br />

falls hier irgendwo ein Fehler passiert...<br />

[Exception throw:..]<br />

Fehlerbehandlung<br />

Globaler Transfer — setjmp<br />

Zu einem ANSI-C System gehören die Funktionen setjmp() und longjmp(), die<br />

folgende Konstruktion ermöglichen:<br />

jmp_buf on_error;<br />

if (setjmp(on_error)) {<br />

} else {<br />

}<br />

Fehlerbehandlung<br />

falls hier irgendwo ein Fehler passiert...<br />

longjmp(on_error, ...);<br />

int setjmp(jmp_buf jb);<br />

setjmp() definiert jb als Ziel eines späteren longjmp() und liefert zunächst Null.<br />

void longjmp(jmp_buf jb, int value);<br />

longjmp() sorgt dafür, daß der letzte Aufruf von setjmp(), der jb gesetzt hat, nochmals<br />

ein Resultat liefert — entweder value, falls von Null verschieden, oder 1. Der<br />

Funktionsaufruf, zu dessen setjmp() zurückgesprungen wird, muß noch aktiv sein.<br />

Das Problem besteht darin, setjmp() und longjmp() so systematisch zu verwenden,<br />

daß auch Fehlerbehandlung verschachtelt werden kann — man braucht einen Stack<br />

von jmp_buf-Werten.<br />

1-9


Ein triviales Beispiel — try/try.m<br />

try gibt einen Text aus.<br />

try illustriert die Klassen Handler und Exception, mit denen man sich systematisch bei<br />

Fehlern aus verschachtelten Funktionsaufrufen befreien kann.<br />

#include <br />

#include "Exception.h"<br />

int main () {<br />

id h = [Handler new];<br />

}<br />

if (catch(h))<br />

puts([[h exception] info]);<br />

else<br />

[Exception throw:"something funny"];<br />

[h free];<br />

return 0;<br />

Mit den Klassen kann man Folgendes machen:<br />

id h = [Handler new];<br />

if (catch(h)) {<br />

} else {<br />

}<br />

Fehlerbehandlung<br />

[[h exception] info];<br />

falls hier irgendwo ein Fehler passiert...<br />

[h free];<br />

[Exception throw: ...];<br />

Handler-Objekte müssen exakt verschachtelt erzeugt und freigegeben werden.<br />

catch() beruht auf setjmp() und bereitet einen Handler zum Fehlerempfang vor, wobei<br />

das Resultat 0 geliefert wird. catch() ist ein Makro (warum?!).<br />

Eine Fehlerbehandlung wird mit throw: ausgelöst, wobei ein Text im Stil von printf()<br />

erzeugt wird, der später mit info abgefragt werden kann. Unterklassen von Exception<br />

mit mehr Information sind möglich.<br />

Für den neuesten, vorbereiteten Handler liefert catch() dann 1. exception liefert dann<br />

das durch throw: erzeugte Exception-Objekt.<br />

Der Handler verwaltet jeweils das Exception-Objekt. Es wird implizit freigegeben,<br />

wenn der Handler freigegeben wird.<br />

1-10


Ein komplizierteres Beispiel — try/errno.m<br />

errno gibt die Werte 1 bis 3 aus und bricht dann wegen einer Exception ab.<br />

errno illustriert verschachtelte Handler und eine Unterklasse von Exception.<br />

#include <br />

#include <br />

#include "Exception.h"<br />

@interface Errno: Exception<br />

{<br />

int errno; // info as a number<br />

}<br />

- (int)errno;<br />

@end<br />

@implementation Errno<br />

- init:(int)e info:(const char *)fmt :(va_list)ap {<br />

[super init:e info:fmt :ap];<br />

errno=atoi(info); // a bit of a kludge...<br />

return self;<br />

}<br />

- (int)errno {<br />

return errno;<br />

}<br />

@end<br />

void main () {<br />

id a = [Handler new], b = [Handler new];<br />

}<br />

switch (catch(a), [[a exception] errno]) {<br />

case 0: switch (catch(b), [[b exception] errno]) {<br />

case 0: [Errno throw:"1"]; assert(0);<br />

case 1: puts("caused 1"); // 1<br />

[Errno throw:"2"]; assert(0);<br />

case 2: puts("caused 2"); // 2<br />

[b free]; [Errno throw:"3"];<br />

}<br />

assert(0);<br />

case 3: puts("caused 3"); // 3<br />

[a free]; [Errno throw:"4"];<br />

}<br />

assert(0);<br />

1-11


Interface — Exception.h<br />

#ifndef Exception_h<br />

#define Exception_h<br />

#include <br />

#include <br />

#include <br />

// handler = [Handler new];<br />

// if (catch(handler)) { [handler exception] }<br />

// else { [Exception throw:...] }<br />

// [handler free];<br />

@interface Handler: Object // stack of Exception handlers<br />

{ // new/free properly nested<br />

jmp_buf label; // for setjmp/longjmp<br />

BOOL armed; // set once label is set<br />

id exception; // current problem if any<br />

}<br />

- (void *)_setjmp; // only for catch()<br />

- disarm; // opens a window outward<br />

- exception;<br />

- (void)rethrow; // current exception -> outer handler<br />

@end<br />

#define catch(handler) setjmp([handler _setjmp])<br />

@interface Exception: Object // sent from throw to catch’s handler<br />

{ // recycled by handler<br />

char * info; // information about a problem<br />

}<br />

+ new:(const char *)fmt, ...; // [[self alloc] init...]<br />

+ new:(const char *)fmt :(va_list)ap;<br />

+ new:(int)e info:(const char *)fmt, ...;<br />

+ new:(int)e info:(const char *)fmt :(va_list)ap;<br />

+ throw; // [[self new...] throw]<br />

+ throw:(const char *)fmt, ...;<br />

+ throw:(const char *)fmt :(va_list)ap;<br />

+ throw:(int)e info:(const char *)fmt, ...;<br />

+ throw:(int)e info:(const char *)fmt :(va_list)ap;<br />

- init; // init:errno info:0<br />

- init:(const char *)fmt, ...; // init:0 info:fmt :...<br />

- init:(const char *)fmt :(va_list)ap; // init:0 info:fmt :ap<br />

- init:(int)e info:(const char *)fmt, ...; // info = [fmt...][:strerror(e)]<br />

- init:(int)e info:(const char *)fmt :(va_list)ap; // designated init’er<br />

- (const char *)info;<br />

- throw; // send to top catch()<br />

@end<br />

#endif<br />

Die Notwendigkeit von armed, disarm und rethrow erkennt man erst während der<br />

Implementierung. Mit new kann man eine Exception erzeugen, die erst später mit throw<br />

ausgelöst wird.<br />

Da catch() ein Makro sein muß, benötigt man _setjmp. Falls etwa ein jmp_buf nicht als<br />

void* übergeben werden könnte, schaltet man noch eine Struktur dazwischen.<br />

Für Funktionen mit variablen Argumentlisten sollte man immer auch die Versionen<br />

implementieren, die Zeiger akzeptieren.<br />

1-12


Implementierung — Handler<br />

@implementation Handler<br />

+ initialize {<br />

if (self == [Handler class])<br />

stack = [List new];<br />

return self;<br />

}<br />

- init { // push new Handler<br />

[stack addObject:[super init]];<br />

return self;<br />

}<br />

- free { // pop up to current Handler<br />

id last; // but do not free intervening ones<br />

do<br />

if (! (last = [stack removeLastObject]))<br />

[self error:"exception handler stack underflow\n"];<br />

while (last != self);<br />

[exception free]; // free our exception, if any<br />

return [super free];<br />

}<br />

- (void *)_setjmp {<br />

armed = YES; // protects uninit’ed jmp_buf<br />

return label;<br />

}<br />

- disarm {<br />

armed = NO;<br />

return self;<br />

}<br />

- exception {<br />

return exception;<br />

}<br />

- (void)rethrow {<br />

id current = exception;<br />

exception = nil, [self free];<br />

[Handler _throw:current];<br />

}<br />

@end<br />

Handler verwendet einen globalen Stack aller seiner (aktiven) Objekte, der in<br />

initialize einmal angelegt wird.<br />

init notiert neue Handler auf dem Stack, free entfernt auch die offenbar vergessenen.<br />

Die letzte Exception löscht free.<br />

_setjmp verwaltet die Bedingung armed, da man einem jmp_buf nicht ansieht, ob er<br />

gesetzt wurde.<br />

Eine versteckte Klassenmethode _throw: dient dazu, ein Exception-Objekt an den<br />

innersten, aktivierten Handler zu liefern. Eine ältere Exception wird dabei freigegeben:<br />

1-13


#include <br />

#include <br />

#include <br />

#include "Exception.h"<br />

#ifndef MAXINFO<br />

#define MAXINFO 1024 // max length of Exception info<br />

#endif<br />

static id stack; // List of nested handlers<br />

@interface Handler (private)<br />

+ (void)_throw:exception; // longjmp or crash...<br />

@end<br />

@implementation Handler (private)<br />

+ (void)_throw:_exception {<br />

unsigned n = [stack count];<br />

while (n -- > 0) { // try stack...<br />

Handler * handler = [stack objectAt:n];<br />

1-14<br />

if (handler->armed) {<br />

[handler->exception free], handler->exception = _exception;<br />

longjmp(handler->label, 1);<br />

}<br />

} // oops...<br />

[[_exception class] error:"%s: uncaught exception\n", [_exception info]];<br />

}<br />

@end


Implementierung — Exception<br />

new und throw führen im W esentlichen zur zentralen init-Methode und dann zu throw.<br />

Hier sind Beispiele:<br />

+ new:(int)e info:(const char *)fmt :(va_list)ap {<br />

return [[self alloc] init:e info:fmt :ap];<br />

}<br />

+ throw:(int)e info:(const char *)fmt, ... {<br />

va_list ap;<br />

}<br />

va_start(ap, fmt);<br />

return [[self new:e info:fmt :ap] throw];<br />

- init:(int)e info:(const char *)fmt :(va_list)ap {<br />

char buf [MAXINFO] = "";<br />

[super init];<br />

if (fmt && fmt[0]) {<br />

vsprintf(buf, fmt, ap);<br />

if (e)<br />

strcat(buf, ": ");<br />

}<br />

if (e)<br />

strcat(buf, strerror(e));<br />

if (! (info = malloc(strlen(buf)+1)))<br />

[self error:"no more memory\n"];<br />

strcpy(info, buf);<br />

return self;<br />

}<br />

- free {<br />

free(info);<br />

return [super free];<br />

}<br />

- (const char *)info {<br />

return info ? info : "";<br />

}<br />

- throw {<br />

[Handler _throw:self];<br />

return self; // dummy<br />

}<br />

@end<br />

Exception verwaltet einen dynamischen String. throw schickt die Exception an den<br />

innersten, vorbereiteten Handler.<br />

1-15


Ein Framework für die Kommandozeile<br />

Ein Framework für die Kommandozeile<br />

Ein Framework ist eine Klasse, von der ein einziges Objekt existiert, das die<br />

Hauptschleife eines Programms abwickelt. Ein Framework soll die Routineaufgaben<br />

einer Klasse von Programmen abwickeln. Dazu können durchaus weitere Objekte<br />

anderer Klassen verwendet werden.<br />

In <strong>UNIX</strong> hat sich ein gewisser Standard für die Gestaltung von Kommandozeilen<br />

eingebürgert:<br />

Optionen gehen Argumenten voraus und beginnen mit einem Minuszeichen,<br />

dem Flaggen folgen.<br />

Flaggen sind einzelne Buchstaben, die beliebig in Optionen zusammengefaßt<br />

werden können..<br />

Zu einer Flagge kann ein Wert angegeben sein, der der Flagge als Rest der<br />

Option oder als nächstes Argument folgt.<br />

Ein einzelnes Minuszeichen beendet die Optionen und gilt als Argument —<br />

normalerweise als Verweis auf die Standard-Eingabe.<br />

Eine Option aus zwei Minuszeichen beendet die Optionen, gilt aber nicht als<br />

Argument.<br />

1-16


Das Framework Main<br />

Da sich sehr viele Kommandos an diesen Standard halten, bietet sich an, dessen<br />

Implementierung in einer Klasse Main zu verkapseln: Main erhält die Argumente von<br />

einem Klienten-Objekt und schickt Nachrichten über decodierte Flaggen und<br />

Argumente zurück an den Klienten, der deshalb das Protokoll CommandLine<br />

implementieren muß:<br />

CommandLine<br />

setClient:<br />

run:<br />

flag:<br />

nextarg<br />

noarg arg:<br />

arg nextarg<br />

length<br />

fd:<br />

done<br />

Initialisierung<br />

Flaggen<br />

Argumente<br />

als Fd<br />

Abschluß<br />

mainFrame<br />

Damit nicht alle Methoden aus diesem Protokoll explizit implementiert werden<br />

müssen, gibt es eine Klasse MainClient, die CommandLine implementiert, und von der<br />

ein Klient abgeleitet werden kann, der nur einige Methoden ersetzt.<br />

MainClient<br />

Klient<br />

MainClient<br />

Main<br />

Damit der Klient weder von MainClient abstammen, noch CommandLine voll<br />

implementieren muß, stammt Main selbst auch von MainClient ab und schickt die<br />

Nachrichten an sich selbst, die der Klient nicht implementiert.<br />

MainClient erlaubt keine Flaggen und interpretiert Argumente als Dateinamen, für die<br />

File-Deskriptoren mit Lesezugriff erzeugt und mit fd: bearbeitet werden. Gibt es keine<br />

Argumente, wird fd: mit der Standard-Eingabe aufgerufen. Dies ist für manche<br />

Filterprogramme recht praktisch. fd: ist ohne Funktion.<br />

1-17


Ein triviales Beispiel — main/cmd.m<br />

cmd erlaubt die Flagge -f mit und -v ohne Optionswert. Argumente müssen lesbare<br />

Dateien sein.<br />

cmd illustriert eine Verwendung des Frameworks Main.<br />

#include "Exception.h"<br />

#include "Fd.h"<br />

#include "Main.h"<br />

@interface Cmd: MainClient<br />

@end<br />

@implementation Cmd<br />

- flag:(char)ch {<br />

switch (ch) {<br />

case ’f’: [[mainFrame fout] puts:"-f\n"]; break;<br />

case ’v’: [[mainFrame fout] printf:"-v %s\n", [mainFrame nextarg]];<br />

break;<br />

default: [super flag:ch];<br />

}<br />

return self;<br />

}<br />

@end<br />

int main (int argc, char * argv []) {<br />

id handler = [Handler new];<br />

}<br />

if (catch(handler)) {<br />

id e = [handler exception];<br />

[mainFrame error:"usage: %s file...", [mainFrame progname]];<br />

[mainFrame fatal:"%s: %s", [[e class] name], [e info]];<br />

}<br />

[[[Main new] setClient:[Cmd new]] run:argv];<br />

return 0;<br />

Cmd ist der Klient, der nur die ihm bekannten Flaggen selbst abschöpft.<br />

Main verursacht einige Fehler wie unkekannte Flaggen, unzugängliche Dateien etc.<br />

Diese werden hier in einem Handler abgefangen, der auch eine Gebrauchsanweisung<br />

ausgibt.<br />

1-18


Interface — Main.h<br />

#ifndef Main_h<br />

#define Main_h<br />

#include <br />

#include <br />

@protocol CommandLine<br />

- flag:(char)ch; // -f<br />

- noarg; // no arguments<br />

- arg:(const char *)arg; // each argument<br />

- fd:fd; // argument as fd<br />

- done; // after all arguments<br />

@end<br />

@interface MainClient: Object <br />

@end<br />

extern id mainFrame; // the single object, returned by +new<br />

@interface Main: MainClient<br />

{<br />

id flag, noarg, arg, fd, done; // message receivers<br />

char ** argv; // argument list<br />

const char * progname; // argv[0]<br />

int pos; // next pos in *argv<br />

id fin, fout, ferr; // stdin, stdout, stderr<br />

}<br />

- initMain:(const char *)progname; // sets progname, if any<br />

- setClient:client; // sets receivers<br />

- run:(char **)argv; // main loop, returns [done done]<br />

- (const char *)progname; // argv[0]<br />

- (const char *)arg; // current *argv<br />

- (const char *)nextarg; // option’s (next) value, next *argv<br />

- (int)length; // # arguments, with current<br />

- fin; // stdin<br />

- fout; // stdout<br />

- ferr; // stderr<br />

- error; // error message for errno<br />

- error:(const char *)fmt, ...; // ... with formatted text<br />

- error:(const char *)fmt :(va_list)ap;<br />

- (void)fatal; // terminate for errno<br />

- (void)fatal:(const char *)fmt, ...; // ... with formatted text<br />

- (void)fatal:(const char *)fmt :(va_list)ap;<br />

@end<br />

#endif<br />

progname, arg und nextarg greifen auf die Kommandozeile zu, length liefert die Zahl<br />

der verbleibenden Argumente.<br />

fin, fout und ferr liefern Fd-Objekte für die Standard-Verbindungen.<br />

error: und fatal: standardisieren die Behandlung häufiger Probleme. Wahrscheinlich<br />

braucht man auch eine Version, die direkt eine Exception akzeptiert.<br />

1-19


Implementierung — MainClient<br />

#include <br />

#include <br />

#include "Exception.h"<br />

#include "Fd.h"<br />

#include "Main.h"<br />

id mainFrame;<br />

@implementation MainClient<br />

- flag:(char)ch { // don’t allow flags<br />

return [Exception throw:"flag -%c not permitted", ch];<br />

}<br />

- noarg { // offer stdin<br />

[mainFrame fd:[mainFrame fin]];<br />

return self;<br />

}<br />

- arg:(const char *)arg { // offer arg or stdin for -<br />

id f;<br />

if (strcmp(arg, "-") == 0)<br />

return [self noarg];<br />

f = [ReadFd open:arg];<br />

[mainFrame fd:f];<br />

[f free];<br />

return self;<br />

}<br />

- fd:fd { // dummy to handle Fd<br />

return self;<br />

}<br />

- done { // dummy epilogue<br />

return self;<br />

}<br />

@end<br />

In MainClient werden Methoden hinterlegt, die dann zum Zug kommen, wenn sie der<br />

Klient nicht selbst implementiert, oder wenn er sie zur Fehlerbehandlung nutzt.<br />

Auch Fd verwendet natürlich Exception zur Fehlerbehandlung, deshalb müssen die<br />

Nachrichten nicht geprüft werden.<br />

1-20


Implementierung — Main<br />

@implementation Main<br />

+ new {<br />

return mainFrame ? mainFrame : [[self alloc] init];<br />

}<br />

+ alloc { // don’t let another one be created<br />

if (mainFrame)<br />

[self error:"cannot create another object\n"];<br />

return mainFrame = [super alloc];<br />

}<br />

- initMain:(const char *)_progname {<br />

if (self != mainFrame) // don’t let another one be init’ed<br />

[self error:"cannot create another object\n"];<br />

[super init];<br />

if (_progname && _progname[0])<br />

progname = _progname;<br />

return self;<br />

}<br />

- init {<br />

return [self initMain:0];<br />

}<br />

- free { // don’t let it be freed<br />

return nil;<br />

}<br />

- setClient:client {<br />

#define receiver(msg) \<br />

[client respondsTo: @selector(msg)] ? client : self<br />

flag = receiver(flag:);<br />

noarg = receiver(noarg);<br />

arg = receiver(arg:);<br />

fd = receiver(fd:);<br />

done = receiver(done);<br />

#undef receiver<br />

return self;<br />

}<br />

- fd:_fd { // send to intended receiver<br />

return fd == self ? self : [fd fd:_fd];<br />

}<br />

- done { // make sure, fout is complete<br />

[fout close];<br />

return self;<br />

}<br />

Es gibt nur ein einziges Main-Objekt, das überall als mainFrame oder mit new erreicht und<br />

niemals freigegeben werden kann.<br />

setClient: legt fest, wer die Nachrichten der CommandLine erhält.<br />

fd: wird ersetzt, damit File-Deskriptoren von Main (alias MainClient) an den eigentlich<br />

erwünschten Empfänger weitergeleitet werden.<br />

done schließt fout, denn bei einer Netzverbindung könnte da noch ein Fehler auftreten.<br />

Dies hätte vielleicht auch in MainClient geschehen sollen.<br />

1-21


- run:(char **)_argv {<br />

if (! (argv = _argv) || ! argv[0])<br />

[Exception throw:"invalid argument vector"];<br />

if (! progname || progname == argv[0]) // set/skip progname<br />

progname = * argv ++;<br />

}<br />

while (argv[0] && argv[0][0] == ’-’) { // option loop<br />

switch (argv[0][1]) {<br />

case ’\0’: // - is a filename<br />

break; // ... and ends options<br />

case ’-’:<br />

if (argv[0][2] == ’\0’) { // --<br />

++ argv; // ... is ignored<br />

break; // ... and ends options<br />

}<br />

default: // -abc -fvalue<br />

for (pos = 2; argv[0][pos-1]; ++ pos)<br />

[flag flag:argv[0][pos-1]];<br />

++ argv; // next argument<br />

continue;<br />

}<br />

break;<br />

}<br />

if (argv[0]) // argument loop<br />

do<br />

pos = strlen(argv[0]), // so that [arg] advances<br />

[arg arg:argv[0]];<br />

while (* ++ argv);<br />

else // no arguments<br />

[noarg noarg];<br />

return [done done];<br />

run: verwaltet die Hauptschleife mit den Invarianten argv (aktuelles Argument) und pos<br />

(Position des nächsten Zeichens in argv[0]).<br />

Der Code ist einfacher als in der konventionellen Lösung, erlaubt aber klarere<br />

Rückfragen.<br />

progname kann entweder hier oder schon in initMain: gesetzt werden — damit kann<br />

auch ein Vektor von Strings als Kommandozeile verarbeitet werden.<br />

1-22


- (const char *)progname {<br />

if (! progname) {<br />

static char buf [10];<br />

sprintf(buf, "[%d]", (int)getpid());<br />

progname = buf;<br />

}<br />

return progname;<br />

}<br />

- (const char *)arg {<br />

if (! argv || ! argv[0])<br />

[Exception throw:"missing argument"];<br />

return argv[0];<br />

}<br />

- (const char *)nextarg {<br />

const char * item = 0;<br />

if (argv && argv[0])<br />

item = argv[0][pos] ? &argv[0][pos] : * ++ argv;<br />

if (item)<br />

pos = strlen(argv[0]); // so that [arg] or [run:] advance<br />

else<br />

[Exception throw:"missing argument"];<br />

return item;<br />

}<br />

- (int)length {<br />

int result = 0;<br />

if (argv)<br />

while (argv[result])<br />

++ result;<br />

return result;<br />

}<br />

- fin {<br />

return fin ? fin : (fin = [[ReadFd alloc] initFd:0]);<br />

}<br />

- fout {<br />

return fout ? fout : (fout = [[PrintFd alloc] initFd:1]);<br />

}<br />

- ferr {<br />

return ferr ? ferr : (ferr = [[PrintFd alloc] initFd:2]);<br />

}<br />

progname, arg und length betrachten die Kommandozeile.<br />

nextarg greift in den Ablauf der Hauptschleife durch Änderung der Invarianten ein.<br />

fin etc. liefern Fd-Objekte, die nur bei Bedarf einmal erzeugt werden.<br />

1-23


- error {<br />

return argv[0] && argv[0][0]<br />

? [self error:"%s: %s: %s",<br />

[self progname], argv[0], strerror(errno)]<br />

: [self error:"%s: %s", [self progname], strerror(errno)];<br />

}<br />

- error:(const char *)fmt, ... {<br />

va_list ap;<br />

va_start(ap, fmt);<br />

return [self error:fmt :ap];<br />

}<br />

- error:(const char *)fmt :(va_list)ap {<br />

if (fmt)<br />

[[self ferr] printf:fmt :ap], [[self ferr] putc:’\n’];<br />

else<br />

[self error:"%s: unknown error", [self progname]];<br />

return self;<br />

}<br />

- (void)fatal {<br />

[self error];<br />

return [self fatal:""];<br />

}<br />

- (void)fatal:(const char *)fmt, ... {<br />

va_list ap;<br />

va_start(ap, fmt);<br />

return [self fatal:fmt :ap];<br />

}<br />

- (void)fatal:(const char *)fmt :(va_list)ap {<br />

[self error:fmt :ap];<br />

exit(1);<br />

}<br />

@end<br />

Mit derartigen Methoden kann man die Fehlerberichte zentralisieren und<br />

standardisieren. Werden die Methoden in einer Unterklasse ersetzt, gewinnt man zum<br />

Beispiel den Effekt von atexit() bei Abbruch.<br />

Zum Gebrauch in Pipelines sollten die Berichte immer den Programmnamen enthalten.<br />

1-24


1-25


Kopierprogramme<br />

Kopierprogramme<br />

Dieser Abschnitt beschäftigt sich anhand von cat und cp mit Klassen, die<br />

Systemaufrufe zum Zugriff auf Dateien verkapseln.<br />

Stat enthält Informationen über ein Objekt im Dateisystem (Inode), aus denen zum<br />

Beispiel die eindeutige Identität, Zugriffsschutz, Größe und Art bestimmt werden<br />

können. Die Information ist per Dateiname oder File-Deskriptor erreichbar, deshalb gibt<br />

es zwei Unterklassen. Fehler werden als IOException gemeldet.<br />

Fd verkapselt einen File-Deskriptor, der die Verbindung zwischen einem Prozeß und<br />

einer Inode repräsentiert. Unterklassen erlauben nur die Operationen, die für den File-<br />

Deskriptor möglich sind.<br />

Path verkapselt einen Dateinamen und die Operationen, die damit vorgenommen<br />

werden können.<br />

Themen<br />

Systemaufrufe<br />

Einfache Transfer-Operationen<br />

Klassen für Dateien<br />

Dateien im Adreßraum<br />

Dateien kopieren<br />

chmod<br />

close<br />

creat<br />

fcntl<br />

fstat<br />

lseek<br />

lstat, stat<br />

map_fd<br />

mmap, munmap<br />

open<br />

read<br />

unlink<br />

write<br />

2-1


Einfache Transfer-Operationen<br />

Einfache Transfer-Operationen — cat<br />

cat interpretiert seine Argumente als Dateinamen und kopiert die Inhalte zur Standard-<br />

Ausgabe. Ohne Argumente oder für jedes Argument - wird von der Standard-Eingabe<br />

kopiert.<br />

cat illustriert Dateizugriff mit den Fd-Klassen und die Transfer-Operationen.<br />

cat wird in vier Schritten verfeinert:<br />

cat0 ist eine triviale Lösung.<br />

cat1 versucht, eine Datei nicht in sich selbst zu kopieren.<br />

cat2 läßt trotzdem Kopieren von und zu einem Gerät zu.<br />

cat3 ist wesentlich effizienter , da eine Datei im Speicher abgebildet wird.<br />

Hauptprogramm für cat<br />

Das Hauptprogramm erzeugt ein Object als Klient von Main und erklärt Details einer<br />

etwaigen Exception.<br />

#include "Stat.h" // IOException<br />

#include "Main.h"<br />

int main (int argc, char * argv []) {<br />

id handler = [Handler new];<br />

}<br />

if (catch(handler)) {<br />

id e = [handler exception];<br />

if (! [e isKindOf:[IOException class]])<br />

[mainFrame error:"usage: %s file...", [mainFrame progname]];<br />

[mainFrame fatal:"%s: %s", [[e class] name], [e info]];<br />

}<br />

[[[Main new] setClient:[Object new]] run:argv];<br />

return 0;<br />

2-2


cat0<br />

Main liefert fertige ReadFd-Objekte an die zu Object als Kategorie hinzugefügte<br />

Methode fd:<br />

#include "Fd.h"<br />

#include "Main.h"<br />

@implementation Object (cat)<br />

- fd:fd {<br />

return [fd cat:[mainFrame fout]];<br />

}<br />

@end<br />

Die trivialste Lösung stützt sich auf eine Methode cat: von ReadFd, die ihrerseits<br />

transfer: verwendet:<br />

- transfer:fout {<br />

int n;<br />

char buf [BUFSIZ];<br />

}<br />

while ((n = [self read:buf len:sizeof buf]))<br />

if ([fout write:buf len:n] != n)<br />

[IOException throw:errno info:"write \"%s\"", [fout name]];<br />

return self;<br />

Die Ausgabe muß zu einem WriteFd-Objekt erfolgen. Kopiert wird mit read:len: und<br />

write:len: — bei Fehlern gibt es eine IOException und write:len: sollte immer die<br />

richtige Länge ausgeben können.<br />

Die IOException sollte zu einer anderen Fehlermeldung führen als die vom Framework<br />

erzeugten Exception-Objekte.<br />

2-3


cat1<br />

cat1 versucht, eine Kopie zu vermeiden, falls Eingabe und Ausgabe zur gleichen Datei<br />

führen.<br />

#include "Fd.h"<br />

#include "Main.h"<br />

@implementation Object (cat)<br />

- fd:fd {<br />

id fout = [mainFrame fout];<br />

if ([[fd fstat] isEqual:[fout fstat]])<br />

[Exception throw:"\"%s\" equals \"%s\"", [fd name], [fout name]];<br />

return [fd cat:fout];<br />

}<br />

@end<br />

isEqual: ist eine Methode von Object, die Objekt-Äquivalenz implementieren soll. Für<br />

File-Deskriptoren und Dateinamen muß das Problem auf die Gleichheit der dahinter<br />

befindlichen Inodes zurückgeführt werden. Dazu dient die Klasse Stat:<br />

- (BOOL)isEqual:obj {<br />

if (obj == self)<br />

return YES;<br />

if ([obj isKindOf:[Stat class]])<br />

{ Stat * sp = obj;<br />

}<br />

if (s.st_dev == sp->s.st_dev && s.st_ino == sp->s.st_ino)<br />

return YES;<br />

}<br />

return NO;<br />

Nur die Kombination aus Inode-Nummer st_ino und Gerätenummer st_dev ist<br />

eindeutig — dies gilt auch für Dateien im NFS.<br />

Bevor die Stat-Information für einen File-Deskriptor oder Dateinamen verwendet<br />

werden kann, muß sie durch Methoden wie fstat erzeugt werden.<br />

2-4


cat2<br />

cat2 vermeidet das Problem, daß bei Geräten wie /dev/tty Eingabe und Ausgabe<br />

durchaus gleichzeitig erfolgen können, indem der Test nur bei Dateien vorgenommen<br />

wird:<br />

#include "Fd.h"<br />

#include "Main.h"<br />

@implementation Object (cat)<br />

- fd:fd {<br />

id fout = [mainFrame fout];<br />

if ([[fd fstat] isFile] && [fd isEqual:[fout fstat]])<br />

[Exception throw:"\"%s\" equals \"%s\"", [fd name], [fout name]];<br />

return [fd cat:fout];<br />

}<br />

@end<br />

Ob eine Inode eine Datei beschreibt, kann wieder in Stat geklärt werden:<br />

- (BOOL)isFile {<br />

return (s.st_mode & S_IFMT) == S_IFREG;<br />

}<br />

Auch für isFile muß zuerst die Stat-Information zum Beispiel mit fstat aktualisiert<br />

werden.<br />

2-5


cat3<br />

<strong>UNIX</strong> kann Programme zwischen Hauptspeicher und Platte verlagern, um den Speicher<br />

besser zu nutzen (Swapping). In der Regel kann der konstante Programmtext aus der<br />

Programmdatei gelesen werden und nur die Daten müssen voll verschoben werden.<br />

Hauptspeicher<br />

0<br />

1<br />

.<br />

.<br />

.<br />

end<br />

Text<br />

Daten<br />

Datei<br />

Platte<br />

Programm<br />

Swap-Bereich<br />

Datei<br />

Daten<br />

Die Zeichnung deutet an, daß diese ohnehin vorhandenen Mechanismen auch dazu<br />

verwendet werden können, eine Datei im Adreßraum eines Prozesses abzubilden. Für<br />

Lesezugriff leistet dies die Klasse MappedReadFd.<br />

cat3 ist wesentlich effizienter für Dateien, die in den Hauptspeicher abgebildet werden<br />

können.<br />

#include "MappedFd.h"<br />

#include "Main.h"<br />

@implementation Object (cat)<br />

- fd:fd {<br />

id fout = [mainFrame fout];<br />

if ([[fd fstat] isFile] && [fd isEqual:[fout fstat]])<br />

[Exception throw:"\"%s\" equals \"%s\"", [fd name], [fout name]];<br />

return [fd cat:fout];<br />

}<br />

- arg:(const char *)arg {<br />

id f;<br />

if (strcmp(arg, "-") == 0)<br />

return [mainFrame noarg];<br />

f = [MappedReadFd open:arg];<br />

if ([f isMemberOf:[MappedReadFd class]])<br />

[[mainFrame ferr] printf:"%s mapped\n", arg];<br />

[self fd:f];<br />

[f free];<br />

return self;<br />

}<br />

@end<br />

Eine Abbildung wird nur für Dateien vorgenommen, die nicht als Standard-Eingabe<br />

2-6


verwendet werden. Die Standard-Eingabe kann mehrfach abgefragt werden; das ist nur<br />

sinnvoll, wenn sie dazwischen wächst, was sich aber in der Abbildung nicht unbedingt<br />

auswirkt.<br />

Für Dateien liefert open: einen MappedReadFd, für andere Inodes wird stillschweigend<br />

ein ReadFd geliefert.<br />

- transfer:fout {<br />

const void * buf;<br />

size_t n, len;<br />

}<br />

for (n = 0; (buf = [self at:n len:&len]); n += len)<br />

if ([fout write:buf len:len] != len)<br />

[IOException throw:errno info:"write \"%s\" truncated",<br />

[self name]];<br />

return self;<br />

MappedReadFd ersetzt transfer: und führt statt read:len: eine Abbildung mit at:len:<br />

durch.<br />

2-7


Klassen für Dateien<br />

Klassen für Dateien<br />

Stat<br />

In diesem Abschnitt wird die Funktionalität der Klassen vorgestellt, die<br />

Implementierungen folgen später .<br />

Stat Stat.h Stat.m Information über ein Objekt im Dateisystem<br />

Path Path.h Path.m Dateiname<br />

Fd Fd.h Fd.m File-Deskriptor, Dateiverbindung<br />

ReadFd Lesezugriff<br />

MappedReadFd Abbildung zum Lesen<br />

WriteFd Schreibzugriff<br />

MappedWriteFd Abbildung zum Schreiben (und Lesen)<br />

PrintFd PrintFd.m formatierte Ausgabe<br />

Ein Stat-Objekt verkapselt die Information, die vom Betriebssystem über ein Objekt im<br />

Dateisystem zu erfahren ist.<br />

#ifndef Stat_h<br />

#define Stat_h<br />

#include // off_t<br />

#include <br />

#include "Exception.h"<br />

@interface Stat: Object // base class for Fd and Path<br />

{<br />

struct stat s; // optional, describes attributes<br />

}<br />

- (BOOL)isEqual:obj; // TRUE if same inode on same device<br />

- (BOOL)isFile; // true for regular file<br />

- (BOOL)isDir; // true for directory<br />

- (short)mode; // (current) mode<br />

- (long)size; // (current) size<br />

@end<br />

@interface IOException: Exception<br />

@end<br />

#endif<br />

Beim Umgang mit Stat-Objekten kann es zu einer IOException kommen.<br />

Bevor die Information gültig ist, muß sie von einer Unterklasse aus aktualisiert werden.<br />

2-8


Path<br />

Ein Path-Objekt macht die Stat-Information mit Hilfe eines Pfads zugänglich und<br />

erlaubt Manipulationen von Dateien per Namen.<br />

#ifndef Path_h<br />

#define Path_h<br />

#include "Stat.h"<br />

@interface Path: Stat<br />

{<br />

char * path; // dynamic<br />

}<br />

- initPath:(const char *)path;<br />

- (const char *)name; // path<br />

- stat; // update attributes<br />

- lstat; // update attributes<br />

- chmod:(short)mode; // change mode<br />

- unlink; // remove<br />

@end<br />

#endif<br />

stat und lstat aktualisieren die Stat-Information, wobei lstat einen symbolischen<br />

Link als solchen zeigt und stat dem Link folgt und das Zielobjekt untersucht.<br />

2-9


Fd<br />

Ein Fd-Objekt verkapselt den File-Deskriptor, der dem Betriebssystem gegenüber als<br />

Dateiverbindung gilt. Falls bekannt, wird der zur Erzeugung verwendete Dateiname für<br />

Fehlermeldungen aufbewahrt.<br />

#ifndef Fd_h<br />

#define Fd_h<br />

#include <br />

#include "Stat.h"<br />

@interface Fd: Stat // "abstract" base class<br />

{<br />

int fd; // -1 or open file descriptor<br />

char * path; // dynamic, if known<br />

}<br />

- close; // free implies close<br />

- fstat; // update attributes<br />

- (long)lseek:(long)pos from:(int)where;<br />

- (const char *)name; // path or fd<br />

@end<br />

Als gemeinsame Basisklasse enthält Fd die Methoden, die allen File-Deskriptoren<br />

gemeinsam sind.<br />

Diese Klasse soll abstrakt sein, deshalb ist die Initialisierungsmethode in einer privaten<br />

Definitionsdatei FdP.h versteckt:<br />

#ifndef FdP_h<br />

#define FdP_h<br />

#include "Fd.h"<br />

@interface Fd (private)<br />

+ open:(const char *)_path flags:(int)flags mode:(int)mode;<br />

// initFd:open(...)<br />

- initFd:(int)_fd io:(int)want; // designated init’er<br />

@end // ..checks flags for want<br />

@interface ReadFd (private)<br />

- transfer:fout; // common code of cat: and cp:<br />

@end<br />

#endif<br />

open:flags:mode: soll ein Objekt einer Unterklasse von Fd erzeugen, initFd:io: prüft,<br />

ob der gewünschte Zugriff auch möglich ist.<br />

Die Unterklassen sollen initFd: implementieren, damit auch eine Zahl in ein Fd-Objekt<br />

verkapselt werden kann:<br />

@protocol Fd // implemented only in subclasses<br />

- initFd:(int)fd; // check and wrap number as Fd<br />

@end<br />

2-10


ReadFd und WriteFd<br />

Dateiverbindungen können nur für Lesezugriff, nur für Schreibzugriff oder für beides<br />

zugelassen sein, folglich gibt es Protokolle für die Transfermethoden und Unterklassen<br />

ReadFd und WriteFd, die nur die möglichen Operationen als Methoden besitzen.<br />

@protocol Read // implemented by readable Fd<br />

- (int)read:(void *)buf len:(int)len;<br />

@end<br />

@interface ReadFd: Fd <br />

+ open:(const char *)path;<br />

- cat:fout; // [fout cat:self] || append contents to fout<br />

- cp:fout; // [fout cp:self] || replace contents of fout<br />

@end<br />

open: greift auf eine existente Datei zu. read:len: versucht, einen Puffer zu füllen und<br />

liefert die Anzahl transferierter Bytes, die auch Null sein kann.<br />

cat: hängt den eigenen Inhalt an eine andere Dateiverbindung an, cp: soll den anderen<br />

Inhalt ersetzen. Beide Methoden geben dem Partner Vorrang, die Operation selbst<br />

durchzuführen; damit kann für eine abgebildete Datei effizienter verfahren werden.<br />

@protocol Write // implemented by writable Fd<br />

- (int)write:(const void *)buf len:(int)len;<br />

@end<br />

@interface WriteFd: Fd <br />

+ create:(const char *)path mode:(int)mode;<br />

@end<br />

create:mode: erzeugt eine neue Datei mit Zugriffsschutz oder schneidet eine existente<br />

Datei auf Länge Null zurück. write:len: versucht, einen Puffer zu schreiben und liefert<br />

die Anzahl transferierter Bytes, die je nach Dateiverbindung auch weniger als<br />

gewünscht sein kann.<br />

2-11


PrintFd<br />

PrintFd ist eine Unterklasse von WriteFd, die einfache Methoden zur formatierten<br />

Textausgabe besitzt.<br />

@interface PrintFd: WriteFd // formatted printing above WriteFd<br />

{<br />

id wfd; // WriteFd as transport<br />

FILE * fp; // pushed on top<br />

}<br />

- initPrintFd:wfd; // designated init’er<br />

- flush; // empty buffer<br />

- (int)putc:(char)ch; // all return -1 or #bytes output<br />

- (int)puts:(const char *)string;<br />

- (int)printf:(const char *)fmt, ...; // might return 0 on NeXT ??<br />

- (int)printf:(const char *)fmt :(va_list)ap;<br />

@end<br />

Ein PrintFd-Objekt kann entweder mit initFd: initialisiert oder mit initPrintFd: auf<br />

einen vorhandenen WriteFd aufgesetzt werden, über den dann die Kontrolle<br />

übernommen wird.<br />

Die Ausgabe-Methoden sind auf die stdio-Funktionen abgebildet, wobei die Standard-<br />

Ausgabe zeilenweise und die Diagnose-Ausgabe nicht gepuffert wird. Falls nötig, muß<br />

flush verwendet werden, was bei close implizit erfolgt.<br />

Bei free wird auch der WriteFd, falls verwendet, freigegeben.<br />

2-12


Dateien im Adreßraum<br />

Dateien im Adreßraum<br />

Manche Systeme können einen Teil des Prozeß-Adreßraums mit einer Datei<br />

verknüpfen, entweder nur für Lesezugriff, oder auch für Schreibzugriff, wobei ein<br />

Schreibzugriff auf den Speicher unmittelbar zu einer Schreiboperation in der Datei führt.<br />

Hauptspeicher<br />

0<br />

1<br />

.<br />

.<br />

.<br />

end<br />

Text<br />

Daten<br />

Datei<br />

Platte<br />

Programm<br />

Swap-Bereich<br />

Datei<br />

Daten<br />

Das Verfahren ist höchst effizient, weil unnütze Kopien im Kern des Betriebssystems<br />

vermieden werden.<br />

Prozeß<br />

System<br />

Ausgabe<br />

Ausgabe<br />

Eingabe<br />

Eingabe<br />

Platte<br />

Eingabe<br />

Ausgabe<br />

Prozeß<br />

System<br />

Ausgabe<br />

Eingabe<br />

Allerdings können derartig abgebildete Dateien nur durch Ändern der Abbildung<br />

wachsen.<br />

Platte<br />

Eingabe<br />

Ausgabe<br />

2-13


MappedReadFd<br />

MappedReadFd bildet eine Datei für Lesezugriff ab.<br />

#ifndef MappedFd_h<br />

#define MappedFd_h<br />

#include "Fd.h"<br />

@interface MappedReadFd: ReadFd<br />

{<br />

const void * content; // mapped area<br />

size_t offset, size; // begin, length<br />

}<br />

- initFd:(int)fd; // may return ReadFd instead<br />

- (const void *)at:(size_t)offset len:(size_t *)lenp;<br />

@end<br />

Es bleibt offen, ob man eine Datei ganz abbilden kann. at:lenp: versucht, möglichst<br />

viel von einer Anfangsposition in der Datei ab zur Verfügung zu stellen und liefert auch<br />

die benutzbare Länge. Falls das Dateiende erreicht wurde, ist das Resultat NULL.<br />

MappedWriteFd<br />

MappedWriteFd versucht, eine Datei für Schreibzugriff abzubilden. Das ist nicht auf jeder<br />

<strong>UNIX</strong>-Plattform (effizient) möglich.<br />

@interface MappedWriteFd: WriteFd<br />

{<br />

void * content; // mapped area<br />

size_t offset, size; // begin, length<br />

}<br />

- initFd:(int)fd; // may return WriteFd instead<br />

- (void *)at:(size_t)offset size:(size_t)size;<br />

@end<br />

#endif<br />

Es bleibt offen, ob eine Datei implizit oder explizit geschrieben wird. at:len: versucht,<br />

von einer Anfangsposition ab einen vorgeschriebenen Bereich zum Schreiben<br />

bereitzustellen, dabei muß am Schluß ein Null-Byte eingefügt werden.<br />

2-14


Dateien kopieren<br />

Dateien kopieren — cp<br />

cp interpretiert seine Argumente als Dateinamen und kopiert entweder die erste Datei<br />

in die zweite, oder alle Dateien in den Katalog, den das letzte Argument bezeichnet.<br />

cp illustriert, wie man Dateien erzeugt und ihren Zugriffsschutz anpaßt.<br />

cp wird in sechs Schritten verfeinert:<br />

cp0 ist eine triviale Lösung.<br />

cp1 versucht, die Eingabedatei abzubilden.<br />

cp2 versucht, beide Dateien abzubilden.<br />

cp3 vermeidet, daß die Eingabedatei als Ausgabedatei verwendet wird.<br />

cp4 übernimmt für Dateien den Zugriffsschutz und löscht die Ausgabedatei bei<br />

Fehlern.<br />

cp5 verwendet ein Cp-Objekt, mit dem auch in einen Katalog kopiert werden<br />

kann.<br />

Hauptprogramm für cp<br />

Das Hauptprogramm erzeugt ein Cp-Objekt als Klient von Main und erklärt Details einer<br />

etwaigen Exception.<br />

#include "Cp.h"<br />

#include "Fd.h"<br />

#include "Main.h"<br />

int main (int argc, char * argv []) {<br />

id handler = [Handler new];<br />

}<br />

if (catch(handler)) {<br />

id e = [handler exception];<br />

if (! [e isKindOf:[IOException class]])<br />

[mainFrame error:"usage: %s from ... to", [mainFrame progname]];<br />

[mainFrame fatal:"%s: %s", [[e class] name], [e info]];<br />

}<br />

[[Main alloc] initMain:argv[0]]; // progname is set<br />

if (argc < 3)<br />

[Exception throw:"bad argument count"];<br />

[mainFrame setClient:[[Cp alloc] initPath:argv[argc-1]]];<br />

argv[-- argc] = 0;<br />

[mainFrame run:argv];<br />

return 0;<br />

Cp soll mit arg: nur die Eingabedateien verarbeiten, deshalb wird das letzte Argument<br />

vorher als Ziel an Cp übergeben. Damit Fehlermeldungen mit dem Programmnamen<br />

markiert werden können, erhält Main ihn vorab.<br />

2-15


Ein trivialer Klient — Cp0<br />

Cp merkt sich den Zielpfad und (später) ob es sich um einen Katalog handelt.<br />

#ifndef Cp_h<br />

#define Cp_h<br />

#include <br />

@interface Cp: Object<br />

{<br />

const char * path;<br />

BOOL isDir;<br />

}<br />

- initPath:(const char *)path;<br />

@end<br />

#endif<br />

Damit Cp unabhängig vom Kopiervorgang verfeinert werden kann, gibt es dafür eine<br />

Funktion copy():<br />

#ifndef copy_h<br />

#define copy_h<br />

void copy (const char * from, const char * to);<br />

#endif<br />

Eine triviale Implementierung von Cp erlaubt nur zwei Argumente:<br />

#include "Cp.h"<br />

#include "Exception.h"<br />

#include "Main.h"<br />

#include "copy.h"<br />

@implementation Cp<br />

- initPath:(const char *)_path {<br />

[super init];<br />

path = _path;<br />

return self;<br />

}<br />

- noarg {<br />

[mainFrame fatal:"botched argument count"];<br />

return self; // dummy<br />

}<br />

- arg:(const char *)arg {<br />

if ([mainFrame length] != 1) // target is consumed...<br />

[Exception throw:"bad argument count"];<br />

copy(arg, path);<br />

return self;<br />

}<br />

@end<br />

noarg kann eigentlich nicht aufgerufen werden...<br />

2-16


cp0<br />

cp1<br />

Die trivialste Lösung stützt sich auf eine Methode cp: von ReadFd, die genau wie cat:<br />

auf transfer: beruht.<br />

#include "Fd.h"<br />

#include "copy.h"<br />

void copy (const char * from, const char * to) {<br />

id fin = [ReadFd open:from];<br />

id fout = [WriteFd create:to mode:0644];<br />

}<br />

[fin cp:fout];<br />

[fout free];<br />

[fin free];<br />

cp1 versucht, die Eingabedatei abzubilden, um dann effizienter zu kopieren.<br />

#include "Main.h"<br />

#include "MappedFd.h"<br />

#include "copy.h"<br />

void copy (const char * from, const char * to) {<br />

id fin, fout;<br />

fin = [MappedReadFd open:from];<br />

if ([fin isMemberOf:[MappedReadFd class]])<br />

[[mainFrame ferr] printf:"%s mapped\n", from];<br />

}<br />

fout = [WriteFd create:to mode:0644];<br />

[fin cp:fout];<br />

[fout free];<br />

[fin free];<br />

2-17


cp2<br />

cp2 versucht, beide Dateien abzubilden.<br />

#include "Main.h"<br />

#include "MappedFd.h"<br />

#include "copy.h"<br />

void copy (const char * from, const char * to) {<br />

id fin, fout = nil;<br />

fin = [MappedReadFd open:from];<br />

if ([fin isMemberOf:[MappedReadFd class]])<br />

[[mainFrame ferr] printf:"%s mapped\n", from];<br />

fout = [MappedWriteFd create:to mode:0644];<br />

if ([fout isMemberOf:[MappedWriteFd class]])<br />

[[mainFrame ferr] printf:"%s mapped\n", to];<br />

}<br />

[fin cp:fout];<br />

[fout free];<br />

[fin free];<br />

Wenn dies gelingt, kann MappedWriteFd mit einer effizienteren Version von cp:<br />

wesentlich effizienter kopieren:<br />

- cp:fin {<br />

const void * ibuf;<br />

size_t n, len;<br />

}<br />

if (! [fin isKindOf:[MappedReadFd class]])<br />

return nil;<br />

for (n = 0; (ibuf = [fin at:n len:&len]); n += len)<br />

memcpy([self at:n size:len], ibuf, len);<br />

return self;<br />

2-18


cp3<br />

cp3 vermeidet, daß durch Erzeugen der Ausgabedatei die Eingabedatei vorzeitig<br />

zerstört wird.<br />

#include "Main.h"<br />

#include "MappedFd.h"<br />

#include "Path.h"<br />

#include "copy.h"<br />

void copy (const char * from, const char * to) {<br />

id fin, fout = nil;<br />

id path, handler;<br />

fin = [[MappedReadFd open:from] fstat];<br />

if ([fin isMemberOf:[MappedReadFd class]])<br />

[[mainFrame ferr] printf:"%s mapped\n", from];<br />

handler = [Handler new];<br />

if (! catch(handler))<br />

path = [[Path alloc] initPath:to], [path stat];<br />

else // path does not exist<br />

[path free], path = nil;<br />

[handler free];<br />

if ([fin isFile] && [fin isEqual:path]) {<br />

id e = [Exception new:"\"%s\" equals \"%s\"",<br />

[fin name], [path name]];<br />

[fin free], [path free], [e throw];<br />

}<br />

[path free];<br />

fout = [MappedWriteFd create:to mode:0644];<br />

if ([fout isMemberOf:[MappedWriteFd class]])<br />

[[mainFrame ferr] printf:"%s mapped\n", to];<br />

}<br />

[fin cp:fout];<br />

[fout free];<br />

[fin free];<br />

Das Problem unterscheidet sich von cat1 dadurch, daß die Ausgabedatei als Pfad<br />

untersucht werden muß. W enn sie nicht existiert, ist das besonders erfreulich.<br />

2-19


cp4<br />

cp4 gibt einer (neuen?) Ausgabedatei den Zugriffsschutz der Eingabedatei. Bei<br />

Mißerfolg werden die Spuren vertilgt und die internen Objekte freigegeben.<br />

#include "Main.h"<br />

#include "MappedFd.h"<br />

#include "Path.h"<br />

#include "copy.h"<br />

void copy (const char * from, const char * to) {<br />

id fin, fout = nil;<br />

id path, handler;<br />

fin = [[MappedReadFd open:from] fstat];<br />

if ([fin isMemberOf:[MappedReadFd class]])<br />

[[mainFrame ferr] printf:"%s mapped\n", from];<br />

if ([fin isDir]) {<br />

id e = [Exception new:"\"%s\" is directory ", [fin name]];<br />

[fin free], [e throw];<br />

}<br />

path = [[Path alloc] initPath:to];<br />

handler = [Handler new];<br />

if (! catch(handler)) {<br />

[path stat];<br />

[handler disarm];<br />

if ([fin isFile] && [fin isEqual:path]) {<br />

id e = [Exception new:"\"%s\" equals \"%s\"",<br />

[fin name], [path name]];<br />

[handler free], [path free], [fin free], [e throw];<br />

}<br />

}<br />

fout = [MappedWriteFd create:to mode:0644];<br />

if ([fout isMemberOf:[MappedWriteFd class]])<br />

[[mainFrame ferr] printf:"%s mapped\n", to];<br />

}<br />

if (! catch(handler)) {<br />

[fin cp:fout];<br />

if ([fin isFile] && [[fout fstat] isFile])<br />

[path chmod:[fin mode]];<br />

} else { // may be entered more than once<br />

id x;<br />

if (path)<br />

x = path, path = nil, [x unlink], [x free];<br />

if (fout)<br />

x = fout, fout = nil, [x free];<br />

if (fin)<br />

x = fin, fin = nil, [x free];<br />

[handler rethrow];<br />

}<br />

[handler free], [path free], [fout free], [fin free];<br />

Das Problem ist diffizil, weil man Folgefehler sorgfältig intern abfangen muß.<br />

2-20


cp5<br />

Für cp5 wird Cp anders implementiert, damit man viele Dateien in einen Katalog<br />

kopieren kann.<br />

#include "Cp.h"<br />

#include "Fd.h"<br />

#include "Main.h"<br />

#include "Path.h"<br />

#include "copy.h"<br />

@implementation Cp<br />

- initPath:(const char *)_path {<br />

id handler, dir;<br />

[super init];<br />

path = _path;<br />

handler = [Handler new];<br />

if (! catch(handler)) {<br />

dir = [[Path alloc] initPath:path];<br />

isDir = [[dir stat] isDir];<br />

}<br />

[dir free], [handler free];<br />

return self;<br />

}<br />

- noarg {<br />

[mainFrame fatal:"botched argument count"];<br />

return self; // dummy<br />

}<br />

- arg:(const char *)arg {<br />

const char * fnm;<br />

if (isDir) { // attach basename to path<br />

const char * basename = arg;<br />

if ((fnm = strrchr(arg, ’/’))) // bug: path// gets botched<br />

basename = fnm+1;<br />

fnm = alloca(strlen(path)+1 + strlen(basename)+1);<br />

sprintf((char *)fnm, "%s/%s", path, basename);<br />

} else if ([mainFrame length] != 1) // is it: cp old newname ??<br />

[Exception throw:"bad argument count"];<br />

else // use path as name<br />

fnm = path;<br />

copy(arg, fnm);<br />

return self;<br />

}<br />

@end<br />

2-21


2-22


Systemaufrufe testen<br />

Systemaufrufe testen<br />

fd ist ein einfaches Programm, mit dem man verschiedene Systemaufrufe zum Datei-<br />

Management testen kann.<br />

usage: fd command...<br />

close -c fd<br />

dup -d fd<br />

fstat -f fd<br />

lseek -l fd pos from<br />

open -o path r|w[tca] mode<br />

read -r fd len<br />

stat -s path<br />

unlink -u path<br />

write -w fd text<br />

fd demonstriert, wie man mit der gleichen prinzipiellen Architektur sowohl Argumente<br />

von der Kommandozeile als auch aus Dateien bearbeiten kann.<br />

Mit fd kann man eine Reihe unangenehmer Fragen im Bereich dieser Systemaufrufe<br />

experimentell untersuchen. Die Resultate sind nicht bei allen Plattformen gleich.<br />

fd ist ein typisches Programm zur Klärung derartiger Probleme: man muß die<br />

Systemaufrufe möglichst einzeln und direkt ausführen können, man muß ihre Resultate<br />

ganz genau sehen, und man muß die richtigen Beispiele durchprobieren.<br />

3-1


Beispiele<br />

$ echo 0123456789 > 0123456789<br />

$ { fd -l0 5 0 & wait; read x; echo $x; } < 0123456789 2> /dev/null<br />

Auf NeXT liefert dieses Beispiel — wie beabsichtigt — die Zeichenkette 56789.<br />

Auf Linux erhält man 0123456789.<br />

Des Rätsels Lösung ist, daß bash auf Linux dem Hintergrundprozeß /dev/null als<br />

Standard-Eingabe liefert, wohingegen sh auf NeXT korrekterweise Zugriff auf die Datei<br />

beläßt.<br />

Andererseits sieht man auch Folgendes:<br />

$ fd -f0 < 0123456789<br />

fstat 0 -> 0: dev 0x2, ino 31236, mode 0100644, nlink 1, uid 200, gid 0, rdev<br />

0x0, size 11<br />

$ fd -f0 0: dev 0x2, ino 31236, mode 0100644, nlink 1, uid 200, gid 0, rdev<br />

0x0, size 11<br />

$ ls -l `tty`<br />

2298 crw--w---- 1 axel tty 4, 192 May 18 08:03 /dev/ttyp0<br />

$ fd -f0<br />

fstat 0 -> 0: dev 0x302, ino 2298, mode 020620, nlink 1, uid 200, gid 5, rdev<br />

0x4c0, size 0<br />

$ fd -f0 &<br />

fstat 0 -> 0: dev 0x302, ino 2298, mode 020620, nlink 1, uid 200, gid 5, rdev<br />

0x4c0, size 0<br />

Hier wird offensichtlich sowohl eine Datei als auch das Terminal an den<br />

Hintergrundprozeß übergeben — das liegt daran, daß im interaktiven Bereich Job-<br />

Control dafür sorgt, daß der Zugriff zum Terminal korrekt erfolgt...<br />

Enthält die ausführbare Datei skript folgendes:<br />

#!/bin/sh<br />

fd -f0 &<br />

dann sieht man:<br />

$ skript<br />

fstat 0 -> 0: dev 0x302, ino 2129, mode 020666, nlink 1, uid 0, gid 3, rdev<br />

0x103, size 0<br />

$ . skript<br />

fstat 0 -> 0: dev 0x302, ino 2298, mode 020620, nlink 1, uid 200, gid 5, rdev<br />

0x4c0, size 0<br />

Im ersten Fall wird ‘‘korrekt’’ /dev/null an den Hintergrundprozeß angeschlossen, im<br />

zweiten Fall bleibt es wegen Job-Control beim Terminal /dev/ttyp0.<br />

Weitere, interessante Beispiele sind Untersuchungen zu nahezu gleichzeitigem<br />

Lesezugriff und neuem Erzeugen einer Datei sowie Verwenden und Löschen einer<br />

Datei.<br />

3-2


Implementierung<br />

Implementierung — fd/fd.m<br />

Im Hauptprogramm wird das übliche Framework eingerichtet. Außerdem werden Fd-<br />

Objekte für stdio und ein List-Objekt zur Verwaltung der Fd-Objekte erzeugt:<br />

#include <br />

#include <br />

#include "FdP.h"<br />

#include "Main.h"<br />

#include "Path.h"<br />

#define USAGE "usage: %s command...\n" \<br />

"close -c fd\n" \<br />

"dup -d fd\n" \<br />

"fstat -f fd\n" \<br />

"lseek -l fd pos from\n" \<br />

"open -o path r|w[tca] mode\n" \<br />

"read -r fd len\n" \<br />

"stat -s path\n" \<br />

"unlink -u path\n" \<br />

"write -w fd text"<br />

#define DELIM " \t\n"<br />

static id fds; // List managing all Fd objects<br />

static id fin, fout, ferr; // stdin, stdout, stderr<br />

typedef const char * a; // pass all arguments as strings<br />

...<br />

int main (int argc, char * argv []) {<br />

id handler = [Handler new];<br />

}<br />

if (catch(handler)) {<br />

id e = [handler exception];<br />

if (! [e isKindOf:[IOException class]])<br />

[mainFrame error:USAGE, [mainFrame progname]];<br />

[mainFrame fatal:"%s: %s", [[e class] name], [e info]];<br />

}<br />

[[Main new] setClient:[Object new]];<br />

fds = [List new];<br />

fin = [fds put:[mainFrame fin] at:0];<br />

fout = [fds put:[mainFrame fout] at:1];<br />

ferr = [fds put:[mainFrame ferr] at:2];<br />

[mainFrame run:argv];<br />

return 0;<br />

3-3


Ein erweitertes List-Objekt verwaltet Fd-Objekte und erlaubt Systemaufrufe:<br />

@interface List (fds) // vector of Fd objects, runs commands<br />

- at:(int)fd; // returns List[fd] (must be Fd)<br />

- put:fd at:(int)n; // returns List[n] = fd (nil or fd)<br />

- close:(a)fd; // Fd system calls<br />

- dup:(a)fd;<br />

- fstat:(a)fd;<br />

- lseek:(a)fd :(a)pos :(a)from;<br />

- open:(a)path :(a)flags :(a)mode;<br />

- read:(a)fd :(a)len;<br />

- stat:(a)path;<br />

- unlink:(a)path;<br />

- write:(a)fd :(a)text;<br />

@end<br />

Das List-Objekt dient als Vektor; leere Positionen werden mit V erweisen auf List<br />

selbst gefüllt. at: liefert ein Fd-Objekt nach Nummer:<br />

@implementation List (fds)<br />

- at:(int)fd {<br />

id result = [self objectAt:fd];<br />

}<br />

if (! result || ! [result isKindOf:[Fd class]])<br />

[IOException throw:"%d: no such fd", fd];<br />

return result;<br />

put:at: fügt ein Fd-Objekt ein und füllt etwaige Zwischenräume:<br />

- put:fd at:(int)n {<br />

int i;<br />

}<br />

for (i = [self count]; i


ead:: liest ein und kopiert zur Standard-Ausgabe, die von der Shell her unterdrückt<br />

werden könnte.<br />

- read:(a)_fd :(a)_len {<br />

int fd = atoi(_fd), len = atoi(_len);<br />

id ofd;<br />

}<br />

[ferr printf:"read %d %d -> ", fd, len];<br />

if ([ofd = [self at:fd] respondsTo:@selector(read:len:)]) {<br />

void * area = len ? alloca(len) : alloca(1);<br />

len = [ofd read:area len:len];<br />

[ferr printf:"%d \"%.*s\"\n", len, len, area];<br />

[fout write:area len:len];<br />

} else<br />

[IOException throw:"%d: cannot read fd", fd];<br />

return self;<br />

write:: schreibt einen Text.<br />

- write:(a)_fd :(a)text {<br />

int fd = atoi(_fd);<br />

id ofd;<br />

[ferr printf:"write %d \"%s\" -> ", fd, text];<br />

if ([ofd = [self at:fd] respondsTo:@selector(write:len:)])<br />

[ferr printf:"%d\n", [ofd write:text len:strlen(text)]];<br />

else<br />

[IOException throw:"%d: cannot write fd", fd];<br />

return self;<br />

}<br />

@end<br />

lseek::: positioniert.<br />

- lseek:(a)_fd :(a)_pos :(a)_from {<br />

int fd = atoi(_fd), pos = atoi(_pos), from = atoi(_from);<br />

}<br />

[ferr printf:"lseek %d %d %d -> ", fd, pos, from];<br />

[ferr printf:"%d\n", [[self at:fd] lseek:pos from:from]];<br />

return self;<br />

unlink: entfernt eine Pfadkomponente.<br />

- unlink:(a)path {<br />

id opath = [[Path alloc] initPath:path];<br />

}<br />

[ferr printf:"unlink %s -> ", path];<br />

[opath unlink];<br />

[ferr puts:"0\n"];<br />

[opath free];<br />

return self;<br />

fstat: und stat: holen und zeigen die Attribute.<br />

3-5


- fstat:(a)_fd {<br />

int fd = atoi(_fd);<br />

id ofd;<br />

}<br />

[ferr printf:"fstat %d -> ", fd];<br />

ofd = [[self at:fd] fstat];<br />

[ferr puts:"0: "];<br />

[ofd displayOn:ferr], [ferr putc:’\n’];<br />

return self;<br />

- stat:(a)path {<br />

id opath = [[Path alloc] initPath:path];<br />

}<br />

[ferr printf:"stat %s -> ", path];<br />

[opath stat];<br />

[ferr puts:"0: "];<br />

[opath displayOn:ferr], [ferr putc:’\n’];<br />

[opath free];<br />

return self;<br />

Dazu wird eine Erweiterung der Klasse Stat verwendet:<br />

@interface Stat (displayOn) // need to display stat fields<br />

- displayOn:fd;<br />

@end<br />

@implementation Stat (displayOn)<br />

- displayOn:fd {<br />

[fd printf:"dev 0x%x, ", s.st_dev];<br />

[fd printf:"ino %d, ", s.st_ino];<br />

[fd printf:"mode 0%o, ", s.st_mode];<br />

[fd printf:"nlink %d, ", s.st_nlink];<br />

[fd printf:"uid %d, ", s.st_uid];<br />

[fd printf:"gid %d, ", s.st_gid];<br />

[fd printf:"rdev 0x%x, ", s.st_rdev];<br />

[fd printf:"size %d", s.st_size];<br />

return self;<br />

}<br />

@end<br />

3-6


Etwas aufwendiger sind Systemaufrufe, bei denen Fd-Objekte erzeugt oder gelöscht<br />

werden.<br />

- dup:(a)_fd {<br />

int fd = atoi(_fd), n;<br />

id ofd;<br />

}<br />

[ferr printf:"dup %d -> ", fd];<br />

ofd = [self at:fd];<br />

[ferr printf:"%d\n", n = dup(fd)];<br />

if (n >= 0)<br />

[self put:[[[ofd class] alloc] initFd:n] at:n];<br />

return self;<br />

dup: erzeugt ein neues Fd-Objekt und trägt es in die List ein.<br />

- open:(a)path :(a)_flags :(a)_mode {<br />

char * dummy;<br />

int flags = 0, mode = strtol(_mode, &dummy, 0);<br />

id class = nil, fd;<br />

}<br />

if (strchr(_flags, ’r’)) flags |= O_RDONLY, class = [ReadFd class];<br />

else if (strchr(_flags, ’w’)) flags |= O_WRONLY, class = [WriteFd class];<br />

if (strchr(_flags, ’a’)) flags |= O_APPEND;<br />

if (strchr(_flags, ’c’)) flags |= O_CREAT;<br />

if (strchr(_flags, ’t’)) flags |= O_TRUNC;<br />

[ferr printf:"open %s %s 0%o -> ", path, _flags, mode];<br />

fd = [class open:path flags:flags mode:mode];<br />

[ferr printf:"%d\n", [fd fd]];<br />

if (fd)<br />

[self put:fd at:[fd fd]];<br />

return self;<br />

open::: geht zum verborgenen Initializer, um beliebige Flaggen verwenden zu können.<br />

Hier ist eine zusätzliche Methode in Fd nötig:<br />

@interface Fd (fd) // need to get to fd number<br />

- (int)fd;<br />

@end<br />

@implementation Fd (fd)<br />

- (int)fd {<br />

return fd;<br />

}<br />

@end<br />

close: könnte prinzipiell ein Fd-Objekt aus der List entfernen, aber es wird nur<br />

unverwendbar hinterlassen, bis es bei einem erneuten open oder dup überschrieben<br />

wird.<br />

- close:(a)_fd {<br />

int fd = atoi(_fd);<br />

}<br />

[ferr printf:"close %d -> ", fd];<br />

[[self at:fd] close];<br />

[ferr puts:"0\n"];<br />

return self;<br />

3-7


Jetzt können die Systemaufrufe sehr leicht von der Kommandozeile aus aufgerufen<br />

werden. Hier zahlt sich die Verwendung des Frameworks aus:<br />

@implementation Object (svcs) // Main client<br />

- flag:(char)ch { // run from command line<br />

a fd, pos, path, flags;<br />

switch (ch) {<br />

case ’c’: return [fds close:[mainFrame nextarg]];<br />

case ’d’: return [fds dup:[mainFrame nextarg]];<br />

case ’f’: return [fds fstat:[mainFrame nextarg]];<br />

case ’l’: fd = [mainFrame nextarg];<br />

pos = [mainFrame nextarg];<br />

return [fds lseek:fd :pos :[mainFrame nextarg]];<br />

case ’o’: path = [mainFrame nextarg];<br />

flags = [mainFrame nextarg];<br />

return [fds open:path :flags :[mainFrame nextarg]];<br />

case ’r’: fd = [mainFrame nextarg];<br />

return [fds read:fd :[mainFrame nextarg]];<br />

case ’s’: return [fds stat:[mainFrame nextarg]];<br />

case ’u’: return [fds unlink:[mainFrame nextarg]];<br />

case ’w’: fd = [mainFrame nextarg];<br />

return [fds write:fd :[mainFrame nextarg]];<br />

default: return [mainFrame flag:ch];<br />

}<br />

return self;<br />

}<br />

- noarg { // ok to have no arguments<br />

return self;<br />

}<br />

Wenn man auch Kommandos in einer Datei angeben will, muß man arg:<br />

implementieren:<br />

- arg:(const char *)arg { // run argument file<br />

FILE * fp;<br />

char buf [BUFSIZ];<br />

id handler;<br />

if (strcmp(arg, "-") == 0)<br />

fp = stdin, clearerr(stdin);<br />

else if (! (fp = fopen(arg, "r")))<br />

[IOException throw:errno info:arg];<br />

handler = [Handler new]; // in this case, continue after error<br />

if (catch(handler)) {<br />

id e = [handler exception];<br />

}<br />

[mainFrame error:"%s: %s", [[e class] name], [e info]];<br />

Ein eigener Handler sorgt dafür, daß in diesem Fall Fehler einigermaßen<br />

stillschweigend vergeben werden. Die Zeilen zerpflückt man mit stdio und strtok():<br />

3-8


while (fgets(buf, sizeof buf, fp)) {<br />

a cmd;<br />

if ((cmd = strtok(buf, DELIM))) {<br />

a fd, pos, from, path, flags, mode, len, text;<br />

}<br />

}<br />

[handler free];<br />

if (fp != stdin)<br />

fclose(fp);<br />

return self;<br />

}<br />

@end<br />

cmd += cmd[0] == ’-’; // optional -<br />

switch (cmd[0]) {<br />

case ’#’: continue;<br />

case ’c’: if (! (fd = strtok(0, DELIM))) break;<br />

[fds close:fd]; continue;<br />

case ’d’: if (! (fd = strtok(0, DELIM))) break;<br />

[fds dup:fd]; continue;<br />

case ’f’: if (! (fd = strtok(0, DELIM))) break;<br />

[fds fstat:fd]; continue;<br />

case ’l’: if (! (fd = strtok(0, DELIM))<br />

|| ! (pos = strtok(0, DELIM))<br />

|| ! (from = strtok(0, DELIM))) break;<br />

[fds lseek:fd :pos :from]; continue;<br />

case ’o’: if (! (path = strtok(0, DELIM))<br />

|| ! (flags = strtok(0, DELIM))<br />

|| ! (mode = strtok(0, DELIM))) break;<br />

[fds open:path :flags :mode]; continue;<br />

case ’r’: if (! (fd = strtok(0, DELIM))<br />

|| ! (len = strtok(0, DELIM))) break;<br />

[fds read:fd :len]; continue;<br />

case ’s’: if (! (path = strtok(0, DELIM))) break;<br />

[fds stat:path]; continue;<br />

case ’u’: if (! (path = strtok(0, DELIM))) break;<br />

[fds unlink:path]; continue;<br />

case ’w’: if (! (fd = strtok(0, DELIM))<br />

|| ! (text = strtok(0, DELIM))) break;<br />

[fds write:fd :text]; continue;<br />

}<br />

[ferr printf:USAGE "\n", [mainFrame progname]];<br />

Dank der Systemaufrufe im List-Objekt kann ein wesentlicher Teil des Codes<br />

wiederverwendet werden, obgleich eine völlig andere Strategie zur Fehlerbehandlung<br />

verfolgt wird.<br />

3-9


3-10


Dateinamen<br />

Dieser Abschnitt beschäftigt sich mit Systemaufrufen zum Zugriff auf Dateien als Ganzes und mit Klassen zum Umgang mit diesen<br />

Systemaufrufen.<br />

Filename verkapselt einen Dateinamen.<br />

Themen<br />

• Dateimanipulationen<br />

Systemaufrufe<br />

• access<br />

• chdir<br />

• chmod<br />

• chown<br />

• chroot<br />

• close<br />

• creat<br />

• dup, dup2<br />

• fcntl<br />

• link<br />

• lock<br />

• lseek<br />

• mkdir<br />

• mknod<br />

• mmap, munmap<br />

• mount, umount<br />

• open<br />

• pipe<br />

• read<br />

• readdir<br />

• readlink<br />

• rename<br />

• rmdir<br />

• stat, lstat, fstat<br />

• statfs<br />

• truncate<br />

• unlink<br />

• write<br />

Wichtige Bibliotheksfunktionen<br />

• perror<br />

• strerror<br />

1996/5/20<br />

1996/5/20


Transfer-Operationen -- cat<br />

cat interpretiert seine Argumente als Dateinamen und kopiert die Inhalte zur Standard-Ausgabe. Ohne Argumente oder für jedes<br />

Argument - wird von der Standard-Eingabe kopiert.<br />

cat illustriert Dateizugriff mit der Klasse Fd und die Transfer-Operationen..<br />

#include "Fd.h"<br />

#include "Main.h"<br />

@implementation Object (cat)<br />

- fd:fd {<br />

return [fd cat:[mainFrame fout]];<br />

}<br />

@end<br />

1996/5/20<br />

1996/5/20


Implementierung der Datei-Klassen<br />

Implementierung der Datei-Klassen<br />

Dieser Abschnitt beschäftigt sich mit der Implementierung der Klassen zum Umgang<br />

mit Dateien. Die Funktionalität wurde im Abschnitt Klassen für Dateien vorgestellt.<br />

Klassen<br />

Systemaufrufe<br />

Stat — Information über ein Objekt im Dateisystem<br />

IOException — Fehler beim Umgang mit Dateien<br />

Fd — File-Deskriptor, Dateiverbindung<br />

ReadFd — Lesezugriff<br />

WriteFd — Schreibzugriff<br />

PrintFd — formatierte Ausgabe<br />

MappedReadFd — Abbildung zum Lesen<br />

MappedWriteFd — Abbildung zum Schreiben (und Lesen)<br />

Path — Dateiname<br />

chmod<br />

close<br />

creat<br />

dup, dup2<br />

fcntl<br />

fstat<br />

lseek<br />

lstat, stat<br />

map_fd<br />

mmap, munmap<br />

open<br />

read<br />

unlink<br />

write<br />

vm_deallocate<br />

5-1


Wichtige Bibliotheksfunktionen<br />

fclose<br />

fdopen<br />

memcpy<br />

setvbuf<br />

5-2


Stat — Implementierung<br />

Stat — Implementierung<br />

Ein Stat-Objekt verkapselt die Information, die vom Betriebssystem über ein Objekt im<br />

Dateisystem zu erfahren ist.<br />

Diese Information wird in einer struct stat abgelegt und in den Unterklassen von Stat<br />

mit Systemaufrufen wie stat() angefordert.<br />

- (BOOL)isEqual:obj {<br />

if (obj == self)<br />

return YES;<br />

if ([obj isKindOf:[Stat class]])<br />

{ Stat * sp = obj;<br />

}<br />

if (s.st_dev == sp->s.st_dev && s.st_ino == sp->s.st_ino)<br />

return YES;<br />

}<br />

return NO;<br />

isEqual: ist eine Methode von Object, die Objekt-Äquivalenz implementieren soll. Für<br />

File-Deskriptoren und Dateinamen muß das Problem auf die Gleichheit der dahinter<br />

befindlichen Inodes zurückgeführt werden. Nur die Kombination aus Inode-Nummer st<br />

_ino und Gerätenummer st_dev ist eindeutig — dies gilt auch für Dateien im NFS.<br />

- (short)mode {<br />

return s.st_mode & ~S_IFMT;<br />

}<br />

st_mode enthält den Zugriffsschutz und die Art der Inode. Damit kann man unter<br />

anderem bestimmen, ob es sich um eine Datei oder einen Katalog handelt:<br />

- (BOOL)isFile {<br />

return (s.st_mode & S_IFMT) == S_IFREG;<br />

}<br />

- (BOOL)isDir {<br />

return (s.st_mode & S_IFMT) == S_IFDIR;<br />

}<br />

st_size enthält die Größe einer Datei oder eines Katalogs. Bei Geräten ist die<br />

Bedeutung nicht standardisiert.<br />

- (long)size {<br />

return s.st_size;<br />

}<br />

5-3


IOException — Implementierung<br />

Bei Dateizugriff kann es viele Fehler geben. Damit sie von anderen Fehlern<br />

unterschieden werden können, gibt es die Klasse IOException, die zusammen mit Stat<br />

implementiert wird.<br />

@implementation IOException<br />

@end<br />

5-4


Fd — Implementierung<br />

Fd — Implementierung<br />

Ein Fd-Objekt verkapselt den File-Deskriptor, der dem Betriebssystem gegenüber als<br />

Dateiverbindung gilt.<br />

Falls bekannt, wird der zur Erzeugung verwendete Dateiname für Fehlermeldungen<br />

aufbewahrt, deshalb muß Fd die üblichen Methoden zur Verwaltung eines<br />

dynamischen Strings enthalten.<br />

Dateiverbindung erzeugen — open oder creat<br />

int open (const char * path, int flags, ...);<br />

int creat (const char * path, int mode);<br />

open() richtet eine Dateiverbindung zu path ein, je nach flags zum Lesen mit<br />

O_RDONLY, Schreiben mit O_WRONLY oder zu beidem mit O_RDWR. Die Datei kann dabei mit<br />

O_CREATauch erzeugt oder auf Länge Null abgeschnitten werden mit O_TRUNC. Zum<br />

Erzeugen ist ein drittes Argument mode nötig.<br />

creat() erzeugt eine Datei path mit Zugriffsschutz mode, oder schneidet eine existente<br />

Datei auf Länge Null ab und liefert Schreibzugriff.<br />

Dies ist in einer privaten Methode in Fd verkapselt, die leere Dateinamen abwehrt und<br />

den Dateinamen für Fehlermeldungen speichert:<br />

+ open:(const char *)_path flags:(int)flags mode:(int)mode {<br />

int f;<br />

Fd * result;<br />

}<br />

if (! _path || ! _path[0])<br />

[IOException throw:"null filename"];<br />

if ((f = open(_path, flags, mode)) == -1)<br />

[IOException throw:errno info:"open \"%s\"", _path];<br />

if ((result = [[self alloc] initFd:f]) // may be nil<br />

&& (result->path = malloc(strlen(_path)+1)))<br />

strcpy(result->path, _path);<br />

return result;<br />

- (const char *)name {<br />

if (! path)<br />

{ char buf [20];<br />

}<br />

sprintf(buf, "fd %d", fd);<br />

if ((path = malloc(strlen(buf)+1)))<br />

strcpy(path, buf);<br />

}<br />

return path ? path : "?";<br />

5-5


Eigenschaften einer Dateiverbindung prüfen oder setzen — fcntl<br />

int fcntl (const char * path, int flag, ...);<br />

Je nach flag kontrolliert fcntl() verschiedene Aspekte einer Dateiverbindung. Mit<br />

F_GETFL kann man sehen, für welche Art von Transfer die Verbindung eröffnet wurde.<br />

Da Fd-Objekte auch für Zahlen erzeugt werden können, prüft initFd:io:, ob sich der<br />

File-Deskriptor für den gewünschten Zugriff eignet:<br />

- initFd:(int)_fd io:(int)want {<br />

[super init];<br />

fd = _fd;<br />

if (fd >= 0)<br />

{ int io = fcntl(fd, F_GETFL, 0) & 3;<br />

}<br />

if (io != O_RDWR && io != want) {<br />

id e = [IOException new:"\"%s\": fd mode mismatch", [self name]];<br />

[self free], [e throw];<br />

}<br />

} else if (want != -1) { // can set fd = -1 with want == -1<br />

id e = [IOException new:"\"%s\": invalid fd", [self name]];<br />

[self free], [e throw];<br />

}<br />

return self;<br />

Fd soll eine abstrakte Klasse sein. init verbietet, daß es uninitialisierte Fd-Objekte gibt.<br />

free muß auf jeden Fall den File-Deskriptor und den Dateinamen freigeben.<br />

- init {<br />

return [self subclassResponsibility:_cmd];<br />

}<br />

- free {<br />

[self close];<br />

free(path), path = 0;<br />

return [super free];<br />

}<br />

Dateiverbindung freigeben — close<br />

int close (int fd);<br />

close() gibt eine Dateiverbindung frei.<br />

- close {<br />

if (fd >= 0) {<br />

int result = close(fd);<br />

}<br />

fd = -1;<br />

if (result == -1)<br />

[IOException throw:errno info:"close \"%s\"", [self name]];<br />

}<br />

return self;<br />

5-6


Attribute einer Dateiverbindung — fstat<br />

int fstat (int fd, struct stat * sp);<br />

fstat() legt die Attribute für fd in *sp ab.<br />

- fstat {<br />

if (fstat(fd, &s) == -1)<br />

[IOException throw:errno info:"fstat \"%s\"", [self name]];<br />

return self;<br />

}<br />

Positionieren — lseek<br />

long lseek (int fd, long pos, int where);<br />

lseek() positioniert, falls möglich, die Dateiverbindung fd relativ zu where, so daß der<br />

nächste Lese- oder Schreibzugriff ab pos erfolgt, und liefert die neue absolute Position.<br />

Bei where bedeutet 0 den Dateianfang, 1 die aktuelle Position und 2 das Dateiende.<br />

Löcher (durch Nullzeiger effizient repräsentierte große Null-Flächen in einer Datei)<br />

entstehen durch lseek() weit hinter ein Dateiende gefolgt von einer Schreiboperation.<br />

- (long)lseek:(long)pos from:(int)where {<br />

long result = lseek(fd, pos, where);<br />

}<br />

if (result == -1)<br />

[IOException throw:errno info:"lseek \"%s\"", [self name]];<br />

return result;<br />

Prozeß<br />

Prozeß<br />

Benutzer System<br />

Fd per Prozeß<br />

Positionszeiger aktive Inodes<br />

a<br />

b<br />

Blöcke der Platten<br />

Positionen können durch dup() und fork() zu mehreren File-Deskriptoren gehören.<br />

5-7


ReadFd — Implementierung<br />

ReadFd — Implementierung<br />

ReadFd dient zum Lesezugriff.<br />

Die Initialisierung erfolgt mit den vererbten Methoden von Fd:<br />

@implementation ReadFd<br />

+ open:(const char *)_path {<br />

return [self open:_path flags:O_RDONLY mode:0];<br />

}<br />

- initFd:(int)_fd {<br />

return [super initFd:_fd io:O_RDONLY];<br />

}<br />

Einlesen — read<br />

int read (int fd, void * buf, int len);<br />

read() transferiert bis zu len Bytes von fd zu buf im Prozeß, setzt den Positionszeiger<br />

weiter und liefert die Anzahl der übertragenen Bytes. Nach Konvention(!) gilt 0 als<br />

Datei-Ende.<br />

- (int)read:(void *)buf len:(int)len {<br />

int result = read(fd, buf, len);<br />

}<br />

if (result < 0)<br />

[IOException throw:errno info:"read \"%s\"", [self name]];<br />

return result;<br />

cat: und cp: kopieren jeweils alle Daten, wobei cp: davon ausgeht, daß der Inhalt der<br />

Zieldatei ersetzt wird. Beide Methoden sollen je nach Unterklasse ersetzt werden, und<br />

sie geben dem Ausgabe-Objekt die Möglichkeit, selbst tätig zu werden.<br />

- cat:fout {<br />

if (! [fout isKindOf:[WriteFd class]])<br />

[self error:"cannot use %s\n", [[fout class] name]];<br />

if (! [fout respondsTo:@selector(cat:)] || ! [fout cat:self])<br />

[self transfer:fout];<br />

return self;<br />

}<br />

- cp:fout {<br />

if (! [fout isKindOf:[WriteFd class]])<br />

[self error:"cannot use %s\n", [[fout class] name]];<br />

if (! [fout respondsTo:@selector(cp:)] || ! [fout cp:self])<br />

[self transfer:fout];<br />

return self;<br />

}<br />

In ReadFd stützen sich beide Methoden auf die private Methode transfer:.<br />

5-8


- transfer:fout {<br />

int n;<br />

char buf [BUFSIZ];<br />

}<br />

while ((n = [self read:buf len:sizeof buf]))<br />

if ([fout write:buf len:n] != n)<br />

[IOException throw:errno info:"write \"%s\"", [fout name]];<br />

return self;<br />

WriteFd — Implementierung<br />

WriteFd dient zum Schreibzugriff.<br />

Die Initialisierung erfolgt mit den vererbten Methoden von Fd, wobei allerdings der<br />

Zugriffsschutz einer neuen Datei als Argument angegeben werden kann.<br />

@implementation WriteFd<br />

+ create:(const char *)_path mode:(int)mode {<br />

return [self open:_path flags:O_WRONLY|O_CREAT|O_TRUNC mode:mode];<br />

}<br />

- initFd:(int)_fd {<br />

return [super initFd:_fd io:O_WRONLY];<br />

}<br />

- (int)write:(const void *)buf len:(int)len<br />

{ int result = write(fd, buf, len);<br />

if (result < 0)<br />

[IOException throw:errno info:"write \"%s\"", [self name]];<br />

return result;<br />

}<br />

@end<br />

Ausgeben — write<br />

int write (int fd, const void * buf, int len);<br />

write() transferiert bis zu len Bytes von buf im Prozeß zu fd, setzt den Positionszeiger<br />

weiter und liefert die Anzahl der übertragenen Bytes, die unter Umständen geringer als<br />

verlangt sein kann.<br />

5-9


PrintFd — Implementierung<br />

PrintFd — Implementierung<br />

PrintFd ist eine Unterklasse von WriteFd, die einfache Methoden zur formatierten<br />

Textausgabe besitzt.<br />

Eine relativ elegante Lösung besteht darin, daß PrintFd mit fdopen() einen FILE* auf<br />

einen File-Deskriptor aufsetzt, um dann die üblichen Funktionen zur formatierten<br />

Ausgabe zu nutzen. Standard- und Diagnose-Ausgabe werden mit setvbuf()<br />

zeilenweise bzw. gar nicht gepuffert.<br />

#include <br />

#include "Fd.h"<br />

@implementation PrintFd<br />

- initPrintFd:_wfd {<br />

if (! [_wfd respondsTo:@selector(write:len:)])<br />

[self error:"cannot use %s\n", [[_wfd class] name]];<br />

[super initFd:((PrintFd *)_wfd)->fd]; // circumvent protected<br />

wfd = _wfd;<br />

if (! (fp = fdopen(fd, "w"))) {<br />

id e = [IOException new:errno info:"cannot create fp for \"%s\"",<br />

[_wfd name]];<br />

[self free], [e throw];<br />

}<br />

switch (fd) {<br />

case 1: setvbuf(fp, 0, _IOLBF, 0); break; // line buffer stdout<br />

case 2: setvbuf(fp, 0, _IONBF, 0); break; // unbuffered stderr<br />

}<br />

return self;<br />

}<br />

PrintFd ist eine zusätzliche Leistung, die letztlich nur write() benötigt. Damit diese<br />

Leistung nachträglich aufgesetzt werden kann, verwendet PrintFd ein Fd-Objekt als<br />

Aggregat, das dann allerdings als Besitz angesehen wird.<br />

Zur Vereinfachung kann PrintFd auch auf self aufgesetzt werden, womit eine Zahl in<br />

einen PrintFd verwandelt werden kann:<br />

- initFd:(int)_fd {<br />

fd = _fd; // kludge...<br />

return [self initPrintFd:self];<br />

}<br />

Bei close wird der FILE* mit fclose() freigegeben — implizit damit aber auch der File-<br />

Deskriptor:<br />

- close {<br />

if (fp) {<br />

int result = fclose(fp);<br />

}<br />

5-10<br />

fp = 0, ((PrintFd *)wfd)->fd = fd = -1;<br />

if (result)<br />

[IOException throw:errno info:"fclose \"%s\"", [self name]];<br />

}<br />

if (wfd != self)<br />

[wfd close], wfd = nil;<br />

return [super close];


(PrintFd *) umgeht die Einschränkung, daß eine Unterklasse nicht auf die<br />

Instanzvariablen eines fremden Oberklassen-Objekts zugreifen kann.<br />

name und free werden entsprechend angepaßt:<br />

- (const char *)name {<br />

return wfd != self ? [wfd name] : [super name];<br />

}<br />

- free {<br />

[self close];<br />

if (wfd != self)<br />

[wfd free];<br />

return [super free];<br />

}<br />

write:len: muß auf die stdio-Puffer Rücksicht nehmen:<br />

- (int)write:(const void *)buf len:(int)len {<br />

int result;<br />

[self flush];<br />

if ((result = write(fd, buf, len)) < 0)<br />

[IOException throw:errno info:"write \"%s\"", [self name]];<br />

return result;<br />

}<br />

- flush {<br />

if (fflush(fp), ferror(fp))<br />

[IOException throw:errno info:"flush \"%s\"", [self name]];<br />

return self;<br />

}<br />

Damit können die neuen Methoden mit den stdio-Funktionen implementiert werden:<br />

- (int)putc:(char)ch {<br />

return putc(ch, fp) == ch ? 1 : -1;<br />

}<br />

- (int)puts:(const char *)string {<br />

return fputs(string, fp) == EOF ? -1 : strlen(string);<br />

}<br />

- (int)printf:(const char *)fmt, ... {<br />

va_list ap;<br />

va_start(ap, fmt);<br />

return [self printf:fmt :ap];<br />

}<br />

- (int)printf:(const char *)fmt :(va_list)ap {<br />

return vfprintf(fp, fmt, ap);<br />

}<br />

@end<br />

5-11


MappedReadFd — Implementierung<br />

MappedReadFd — Implementierung<br />

MappedReadFd bildet eine Datei für Lesezugriff ab. Bei Mach gibt es dafür eine (alte)<br />

<strong>UNIX</strong>-Funktion map_fd(), die allerdings nur Lesezugriff ermöglicht.<br />

Um Probleme zu vermeiden, werden hier nur Dateien, keine Geräte, abgebildet und als<br />

Ersatz steht ein ReadFd zur Verfügung — würde kein Fd-Objekt geliefert, müßte die<br />

aufwendige open-Operation sonst wiederholt werden.<br />

#include <br />

#include <br />

#ifdef NeXT<br />

#include <br />

#else // linux || sun || __FreeBSD__<br />

#include <br />

#ifdef sun<br />

int munmap();<br />

#endif<br />

#ifndef MAP_FILE<br />

#define MAP_FILE 0<br />

#endif<br />

#endif<br />

#include "FdP.h"<br />

#include "MappedFd.h"<br />

#ifndef MAXSIZE<br />

#define MAXSIZE (16 * 1024 * 1024) // map at most 16 mb<br />

#endif<br />

@implementation MappedReadFd<br />

- initFd:(int)_fd {<br />

char * _path;<br />

}<br />

[super initFd:_fd];<br />

if ([[self fstat] isFile])<br />

return self;<br />

_path = path, path = 0, fd = -1, [self free];<br />

self = [[ReadFd alloc] initFd:_fd]; // cannot map<br />

path = _path;<br />

return self;<br />

5-12


Datei abbilden — mmap<br />

caddr_t mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t<br />

offset);<br />

mmap() verknüpft len Bytes beginnend bei offset zwischen fd und dem Hauptspeicher.<br />

addr ist ein Vorschlag, die eigentliche Adresse wird als Resultat geliefert. prot limitiert,<br />

im Rahmen der Hardware-Möglichkeiten, den Zugriff ( PROT_READ, PROT_WRITE und<br />

PROT_EXEC). flags definiert, unter anderem, ob beim Schreiben eine lokale Kopie<br />

entsteht ( MAP_PRIVATE), oder ob sich Änderungen auf die Datei auswirken (MAP_SHARED).<br />

Je nach Betriebssystem ist Schreibzugriff offenbar instabil: FreeBSD 2.0.1 bleibt<br />

anschließend stehen, Linux verweigert ihn in frühen Versionen (es funktioniert in<br />

1.3.88).<br />

MappedReadFd besitzt jeweils einen abgebildeten Bereich. at:len: liefert entweder T eile<br />

davon aus oder richtet ihn möglichst groß neu ein — es ist denkbar, daß eine Datei<br />

nicht in den Adreßraum paßt oder daß sie wächst.<br />

- (const void *)at:(size_t)_offset len:(size_t *)lenp {<br />

if (content && _offset >= offset && _offset < offset+size)<br />

{ *lenp = offset+size - _offset; // rest of mapped area<br />

return (char *)content + _offset-offset;<br />

}<br />

[self unmap];<br />

size = [[self fstat] size];<br />

if (_offset >= size) // nothing left<br />

{ size = 0;<br />

return 0;<br />

}<br />

size -= _offset, offset = _offset; // try for rest of file<br />

if (size > MAXSIZE)<br />

size = MAXSIZE;<br />

#ifdef NeXT<br />

if (map_fd(fd, offset, (vm_offset_t *)&content, TRUE,<br />

(vm_size_t)size) != KERN_SUCCESS) {<br />

content = 0, offset = size = 0;<br />

[IOException throw:errno info:"map_fd \"%s\"", [self name]];<br />

}<br />

#else<br />

content = mmap(0, size, PROT_READ, MAP_FILE|MAP_SHARED, fd, offset);<br />

if (content == (caddr_t)-1) {<br />

content = 0, offset = size = 0;<br />

[IOException throw:errno info:"mmap \"%s\"", [self name]];<br />

}<br />

#endif<br />

*lenp = size;<br />

return content;<br />

}<br />

5-13


Dateiabbildung entfernen — munmap<br />

int munmap (caddr_t addr, size_t len);<br />

Eine Abbildung wird entweder explizit mit munmap() entfernt oder implizit, wenn mit<br />

mmap() eine neue Abbildung so erzwungen wird, daß sie eine vorhandene berührt. Falls<br />

nötig wird dabei die Datei aktualisiert. close() impliziert aber munmap() nicht.<br />

Bei Mach muß der Speicher mit vm_deallocate() dem Speichersystem zurückgegeben<br />

werden, die Datei wird dabei nicht aktualisiert.<br />

unmap gibt eine Abbildung frei und wird bei close (und damit bei free) aufgerufen:<br />

- unmap { // remove previous map if any<br />

if (content && size) {<br />

#ifdef NeXT<br />

if (vm_deallocate(task_self(), (vm_address_t)content,<br />

(vm_size_t)size) != KERN_SUCCESS)<br />

[IOException throw:errno info:"vm_deallocate \"%s\"",<br />

[self name]];<br />

#else<br />

if (munmap((caddr_t)content, size) == -1)<br />

[IOException throw:errno info:"munmap \"%s\"", [self name]];<br />

#endif<br />

content = 0, offset = size = 0;<br />

}<br />

return self;<br />

}<br />

- close {<br />

[self unmap];<br />

return [super close];<br />

}<br />

Damit kann MappedReadFd die transfer-Methode effizienter implementieren:<br />

- transfer:fout {<br />

const void * buf;<br />

size_t n, len;<br />

}<br />

5-14<br />

for (n = 0; (buf = [self at:n len:&len]); n += len)<br />

if ([fout write:buf len:len] != len)<br />

[IOException throw:errno info:"write \"%s\" truncated",<br />

[self name]];<br />

return self;


MappedWriteFd — Implementierung<br />

MappedWriteFd — Implementierung<br />

MappedWriteFd bildet eine Datei für Schreibzugriff ab. Das ist zum Beispiel bei Mach<br />

nicht effizient möglich.<br />

@implementation MappedWriteFd<br />

+ create:(const char *)_path mode:(int)mode {<br />

return [self open:_path flags:O_RDWR|O_CREAT|O_TRUNC mode:mode];<br />

}<br />

- initFd:(int)_fd {<br />

char * _path;<br />

[super initFd:_fd io:O_RDWR]; // oops, need both...<br />

#ifndef __FreeBSD__ // PROT_WRITE apparently crashes kernel<br />

if ([[self fstat] isFile])<br />

return self;<br />

#endif<br />

_path = path, path = 0, fd = -1, [self free];<br />

self = [[WriteFd alloc] initFd:_fd]; // cannot map<br />

path = _path;<br />

return self;<br />

}<br />

Wieder besitzt MappedWriteFd einen abgebildeten Bereich, der mit unmap freigegeben<br />

wird. Bei Mach muß dann die Datei aktualisiert werden.<br />

- unmap { // remove previous map if any<br />

if (content && size) {<br />

#ifdef NeXT<br />

off_t current = [self lseek:0 from:1];<br />

#else<br />

#endif<br />

if (current != offset)<br />

[self lseek:offset from:0];<br />

if ([self write:content len:size] != size)<br />

[IOException throw:errno info:"write \"%s\" truncated",<br />

[self name]];<br />

[self lseek:current from:0];<br />

free(content);<br />

if (munmap(content, size) == -1)<br />

[IOException throw:errno info:"munmap \"%s\"", [self name]];<br />

content = 0, offset = size = 0;<br />

}<br />

return self;<br />

}<br />

- close {<br />

[self unmap];<br />

return [super close];<br />

}<br />

Der Aufbau der Abbildung ist dadurch erschwert, daß der abzubildende Bereich in der<br />

Datei existieren muß. Existiert er nicht, muß man mit lseek:from: und write:len: für<br />

ein Loch sorgen.<br />

5-15


- (void *)at:(size_t)_offset size:(size_t)_size {<br />

[self unmap];<br />

if (_size < 1)<br />

[IOException throw:"\"%s\": map 0 bytes", [self name]];<br />

#ifdef NeXT<br />

if (! (content = calloc(_size, sizeof(char))))<br />

[self error:"no room\n"];<br />

if (_offset < [[self fstat] size])<br />

{ off_t current = [self lseek:0 from:1];<br />

[self lseek:_offset from:0]; // read current content<br />

if (read(fd, content, _size) == -1)<br />

[IOException throw:errno info:"map read \"%s\"",<br />

[self name]];<br />

[self lseek:current from:0];<br />

}<br />

#else<br />

if (_offset+_size > [[self fstat] size])<br />

{ off_t current = [self lseek:0 from:1];<br />

[self lseek:_offset+_size - 1 from:0]; // make a hole<br />

if ([self write:"" len:1] != 1)<br />

[IOException throw:errno info:"write \"%s\" truncated",<br />

[self name]];<br />

[self lseek:current from:0];<br />

}<br />

content = mmap(0, _size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED,<br />

fd, _offset);<br />

if (content == (caddr_t)-1) {<br />

content = 0;<br />

[IOException throw:errno info:"mmap \"%s\"", [self name]];<br />

}<br />

#endif<br />

offset = _offset, size = _size;<br />

return content;<br />

}<br />

Damit kann dann cp: zwischen zwei Abbildungen mit Hilfe von memcpy() effizienter<br />

implementiert werden:<br />

- cp:fin {<br />

const void * ibuf;<br />

size_t n, len;<br />

}<br />

if (! [fin isKindOf:[MappedReadFd class]])<br />

return nil;<br />

for (n = 0; (ibuf = [fin at:n len:&len]); n += len)<br />

memcpy([self at:n size:len], ibuf, len);<br />

return self;<br />

5-16


Path — Implementierung<br />

Path — Implementierung<br />

Ein Path-Objekt macht die Stat-Information mit Hilfe eines Pfads zugänglich und<br />

erlaubt Manipulationen von Dateien per Namen.<br />

#include <br />

#include "Path.h"<br />

@implementation Path<br />

- initPath:(const char *)_path {<br />

[super init];<br />

if (! (path = malloc(strlen(_path)+1)))<br />

[self free], [Exception throw:"%s: no room", _path];<br />

strcpy(path, _path);<br />

return self;<br />

}<br />

- free {<br />

free(path), path = 0;<br />

return [super free];<br />

}<br />

- (const char *)name {<br />

return path;<br />

}<br />

Path verwaltet dynamische Strings mit den üblichen Methoden.<br />

Für einen Dateinamen kann man die Stat-Information mit den Systemaufrufen stat()<br />

und lstat() aktualisieren:<br />

- stat {<br />

if (stat(path, &s) == -1)<br />

[IOException throw:errno info:"stat \"%s\"", [self name]];<br />

return self;<br />

}<br />

- lstat {<br />

if (lstat(path, &s) == -1)<br />

[IOException throw:errno info:"stat \"%s\"", [self name]];<br />

return self;<br />

}<br />

- chmod:(short)mode {<br />

if (chmod(path, mode) == -1)<br />

[IOException throw:errno info:"chmod \"%s\"", [self name]];<br />

return self;<br />

}<br />

- unlink {<br />

if (unlink(path) == -1)<br />

[IOException throw:errno info:"unlink \"%s\"", [self name]];<br />

return self;<br />

}<br />

@end<br />

Manipulationen für Pfade folgen fast immer dem gleichen Muster .<br />

5-17


Attribute einer Datei — stat<br />

int stat (const char * path, struct stat * sp);<br />

stat() legt die Attribute für path in *sp ab.<br />

Attribute eines symbolischen Links — lstat<br />

int lstat (const char * path, struct stat * sp);<br />

lstat() legt die Attribute für path in *sp ab, wobei für einen symbolischen Link<br />

allerdings, anders bei stat(), die Information zum Link selbst geliefert wird.<br />

Zugriffsschutz ändern — chmod<br />

int chmod (const char * path, int mode);<br />

chmod() ändert den Zugriffsschutz der Inode und kann auch die speziellen Bits für set<br />

userid, set groupid und sticky text ändern.<br />

Name entfernen — unlink<br />

int unlink (const char * path);<br />

unlink() löscht die letzte Komponente von path in ihrem Katalog, darf aber keine<br />

Kataloge löschen. Wenn auf eine Inode kein Pfad mehr verweist, wird sie freigegeben.<br />

5-18


5-19


Prozesse<br />

Prozesse<br />

Dieser Abschnitt beschäftigt sich mit Prozessen und ihren verschiedenen<br />

Gruppierungen sowie mit Prozeßmanipulation und Signalen.<br />

Die Erklärung folgt Stevens’ Beschreibung der Verhältnisse bei POSIX, denn das wird<br />

von System V und neueren Berkeley-Versionen realisiert. NeXT leistet dies nur bedingt.<br />

Themen<br />

Systemaufrufe<br />

Prozeßbegriff<br />

Prozeßmanipulationen<br />

Signale<br />

brk<br />

chdir<br />

chroot<br />

execve<br />

fork<br />

getegid<br />

geteuid<br />

getgid<br />

getgroups<br />

getpgrp<br />

getpid<br />

getppid<br />

gettimeofday<br />

getuid<br />

kill<br />

killpg<br />

pipe<br />

profil<br />

ptrace<br />

sbrk<br />

setgroups<br />

setpgid<br />

setpgrp<br />

setregid<br />

setreuid<br />

setrlimit<br />

settimeofday<br />

shutdown<br />

6-1


sigaction<br />

sigblock<br />

sigpause<br />

sigpending<br />

sigprocmask<br />

sigreturn<br />

sigsetmask<br />

sigstack<br />

sigsuspend<br />

sigvec<br />

sync<br />

uname<br />

utimes<br />

vfork<br />

vhangup<br />

wait<br />

wait3<br />

wait4<br />

Wichtige Bibliotheksfunktionen<br />

signal<br />

6-2


Prozeßbegriff<br />

Prozeßbegriff<br />

Ein Prozeß ist die Ausführung eines Programms — mit eventuell mit anderen<br />

Prozessen gemeinsam genutztem Programmtext, eigenem Programmzähler, eigener<br />

Datenfläche, eigenen Dateiverbindungen und eigenem Systemzustand wie Registern<br />

und Speicherabbildungen.<br />

Ein Thread oder light-weight process ist einer von mehreren unabhängigen<br />

Programmzählern im gleichen Prozeß, also praktisch ein Prozeß, der nur einen<br />

Programmzähler und einen kleinen Teil des Systemzustands eigenständig besitzt.<br />

Threads gibt es nicht auf jeder <strong>UNIX</strong>-Plattform.<br />

Aus Systemsicht besteht der Prozeßzustand aus einem proc-Element, das der Kern<br />

ständig im Speicher hält, und einer user-Struktur, die mit dem Prozeß verdrängt wird.<br />

Vom proc-Element aus sind beispielsweise offene Dateien im Kern erreichbar.<br />

ps zeigt die Information in verschiedenen Formaten und mit verschiedenen Techniken<br />

— entweder durch Zugriff auf /dev/kmem oder gar /dev/proc, oder mit einem proc-<br />

Dateisystem. Da ps unterbrochen werden kann, ist die Ausgabe nicht unbedingt<br />

korrekt. Die Optionen und die Ausgabe variieren je nach System:<br />

a auch andere Benutzer, nicht nur eigene Prozesse<br />

g auch Prozeßgruppenführer (z.B. login-Shell)<br />

m (NeXT) auch Threads, (Berkeley) Memory<br />

x auch Prozesse ohne Kontroll-Terminal<br />

c nur Kommandoname<br />

e auch Environment<br />

j (Berkeley) joborientiert, also mit Session<br />

l viel Information<br />

s (Berkeley) Signale<br />

u benutzungsorientiert, also mit Zeit- und Speicherverhalten<br />

v (Berkeley) speicherorientiert, also mit Speicherstatistik<br />

w 132 Spalten<br />

ww unlimitiert<br />

6-3


Man kann die Ausgabe auch auf Prozesse oder Benutzer nach Nummern oder Terminal<br />

nach (abgekürztem) Namen einschränken:<br />

next$ ps -tp3<br />

PID TT STAT TIME COMMAND<br />

21203 p3 SW 0:01 -bash (bash)<br />

24551 p3 R 0:00 ps -tp3<br />

next$ ps -ctp3<br />

PID TT STAT TIME COMMAND<br />

21203 p3 SW 0:02 bash<br />

24555 p3 R 0:00 ps<br />

next$ ps -utp3<br />

USER PID %CPU %MEM VSIZE RSIZE TT STAT TIME COMMAND<br />

axel 21203 0.0 1.6 1.84M 512K p3 SW 0:01 -bash (bash)<br />

root 24552 0.0 0.9 1.68M 280K p3 R 0:00 ps -utp3<br />

next$ ps -lwwtp3<br />

F UID PID PPID CP PRI BASE VSIZE RSIZE WCHAN STAT TT TIME COMMAND<br />

c0000201 200 21203 21200 0 10 10 1.84M 512K 0 SW p3 0:02 -bash<br />

(bash)<br />

c0000001 0 24561 21203 0 10 10 1.68M 280K 0 R p3 0:00 ps -<br />

lwwtp3<br />

linux$ ps j<br />

PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND<br />

157 158 158 158 pp0 189 S N 200 0:00 -bash<br />

158 189 189 158 pp0 189 R N 200 0:00 ps j<br />

linux$ ps s<br />

UID PID SIGNAL BLOCKED IGNORED CATCHED STAT TTY TIME COMMAND<br />

200 158 00000000 00010000 7fffffff 07813efb S N pp0 0:00 -bash<br />

200 194 00000000 00000000 7fffffff 00000000 R N pp0 0:00 ps s<br />

6-4


proc als Dateisystem<br />

Linux liefert sehr viel Information über den Kern und die Prozesse als fiktive Dateien im<br />

Katalog /proc:<br />

linux$ ls /proc<br />

1/ 5/ 81/ 95/ kcore self@<br />

13/ 56/ 83/ 96/ kmsg stat<br />

157/ 6/ 86/ cmdline ksyms sys/<br />

158/ 7/ 88/ cpuinfo loadavg uptime<br />

2/ 70/ 89/ devices meminfo version<br />

245/ 72/ 91/ dma modules<br />

248/ 74/ 92/ filesystems mounts<br />

3/ 76/ 93/ interrupts net/<br />

4/ 78/ 94/ ioports scsi/<br />

linux$ ls -R /proc/self<br />

cmdline environ fd/ mem stat status<br />

cwd@ exe@ maps| root@ statm<br />

/proc/250/fd:<br />

0@ 1@ 2@ 3@<br />

Viele der ‘‘Dateien’’ sind benutzbare symbolische Links, deren fiktive Inhalte sich aus<br />

st_dev und st_ino ableiten:<br />

linux$ ls -ldi /proc/self/cwd<br />

16973829 lrwx------ 1 axel root 64 Jun 3 17:53<br />

/proc/self/cwd -> [0002]:16008<br />

linux$ ls -lLi /proc/self/cwd/cwd<br />

total 24<br />

14412 drwxr-xr-x 4 axel root 1024 Apr 4 10:32 Apps/<br />

40004 drwxr-xr-x 33 axel root 1024 Feb 3 16:11 Library/<br />

14422 drwx------ 18 axel root 1024 Jun 3 09:47 Mailboxes/<br />

...<br />

6-5


Prozeßmanipulationen<br />

Prozeßmanipulationen<br />

int fork (void);<br />

fork() kopiert den aufrufenden Prozeß und liefert im neuen Prozeß 0, im alten Prozeß<br />

die Prozeßnummer des neuen Prozesses.<br />

int execl (const char * path, const char * arg0, ... 0);<br />

int execle (const char * path,const char * arg0, ... 0, const char * envp<br />

[]);<br />

int execlp (const char * file, const char * arg0, ... 0);<br />

int execv (const char * path, char *const argv []);<br />

int execve (const char * path, char *const argv [], const char * envp []);<br />

int execvp (const char * file, char *const argv []);<br />

exec lädt im aufrufenden Prozeß ein neues Programm und übergibt Argumente sowie<br />

das aktuelle oder ein neues Environment. Das Programm wird aus einer Datei gelesen,<br />

die auch über PATH gesucht und dann, falls nötig, von einer Shell interpretiert werden<br />

kann.<br />

exec interpretiert #! path arg am Dateianfang: path wird ausgeführt und path und arg<br />

bilden den Anfang von argv[] für das Programm, der ursprüngliche Vektor folgt<br />

danach.<br />

void _exit (int code);<br />

_exit() beendet den aufrufenden Prozeß ohne aufzuräumen.<br />

int wait (int * status);<br />

wait() blockiert den aufrufenden Prozeß, bis einer seiner mit fork() erzeugten<br />

Abkömmlinge terminiert. *status enthält entweder code


Prozeßzustand<br />

Über fork() und exec hinweg bleiben große Teile des Prozeßzustands erhalten:<br />

ändern fork() exec<br />

getpid() neu<br />

getppid() Erzeuger<br />

getpgrp() setpgrp()<br />

getsid() setsid()<br />

times() alle 0<br />

Kontroll-Terminal setsid()<br />

getuid() setuid()<br />

geteuid() seteuid() neu, falls S_ISUID<br />

S_ISUID chmod()<br />

getgid() setgid()<br />

getegid() setegid() neu, falls S_ISGID<br />

S_ISGID chmod()<br />

Environment kopiert kopiert<br />

getcwd() chdir()<br />

chroot()<br />

umask() umask()<br />

Profiling profil() wird beendet<br />

nice() nice()<br />

shm-Segmente werden abgehängt<br />

semadj-Werte alle 0<br />

File-Deskriptoren gleiche Position gleiche Position<br />

FD_CLOEXEC werden geschlossen<br />

Locks flock() nicht geerbt<br />

Signale signal()<br />

SIG_DFL<br />

SIG_IGN<br />

SIG_HOLD<br />

Funktion SIG_DFL<br />

pending keine<br />

6-7


Prozeßgruppen<br />

Prozeßgruppen gliedern Prozesse zur Verteilung von Signalen und zur Zugehörigkeit zu<br />

einem Kontrollterminal.<br />

Jeder Prozeß gehört zu einer Prozeßgruppe, die einen Anführer haben kann — für ihn<br />

sind Prozeß- und Prozeßgruppennummer gleich.<br />

Jede Prozeßgruppe enthält wenigstens einen Prozeß, der aber nicht unbedingt der<br />

Anführer sein muß.<br />

int getpgrp (void);<br />

liefert die Prozeßgruppennummer des Aufrufers.<br />

int setpgrp (int pid, int pgrp);<br />

setzt für den Prozeß pid (0 bezeichnet den Aufrufer) die Prozeßgruppennummer pgrp<br />

(0 bezeichnet pid als Prozeßgruppe) — das darf ein Prozeß für sich und seine<br />

Abkömmlinge machen, aber für einen Abkömmling nur , wenn er exec noch nicht<br />

ausgeführt hat.<br />

setpgrp() ist so kompliziert definiert, um eine Race-Bedingung zu vermeiden:<br />

setpgrp(pid = fork(), 0);<br />

Im Erzeuger bezeichnet pid den Abkömmling. Der Erzeuger setzt also die<br />

Prozeßgruppe für den Abkömmling auf pid — der Abkömmling erhält seine eigene<br />

Prozeßgruppe.<br />

Im Abkömmling liefert fork() Null und das bezeichnet den Aufrufer, also den<br />

Abkömmling. Auch hier erhält der Abkömmling seine eigene Prozeßgruppe.<br />

Egal wer den Aufruf zuerst ausführt — der Abkömmling wird in jedem Fall zum<br />

Anführer seiner eigenen, neuen Prozeßgruppe.<br />

6-8


Sessions<br />

Sessions gliedern Prozeßgruppen zur Verteilung von Signalen und zur Zugehörigkeit zu<br />

einem Kontroll-Terminal.<br />

Jeder Prozeß gehört zu einer Session.<br />

Jede Session enthält wenigstens eine Prozeßgruppe.<br />

int setsid (void);<br />

Wenn der Aufrufer nicht Anführer einer Prozeßgruppe ist, wird er wird Anführer einer<br />

neuen Prozeßgruppe, die einziges Mitglied einer neuen Session mit der<br />

Prozeßnummer des Aufrufers ist. Der Aufrufer verliert außerdem sein Kontroll-<br />

Terminal, falls er eines hat.<br />

Der Aufrufer kann als Anführer der Session ein Kontroll-Terminal bekommen — je nach<br />

System durch Zugriff auf ein freies Terminal oder durch ioctl() — das dann die<br />

Session kontrolliert.<br />

/dev/tty vertritt immer das Kontroll-Terminal — falls der zugreifende Prozeß eines<br />

besitzt. Es kann aber nicht zum Setzen oder Bestimmen der Terminalgruppe verwendet<br />

werden.<br />

Terminalgruppen<br />

Eine Session kann eine sogenannte Vordergrund-Prozeßgruppe haben, an die das<br />

Kontroll-Terminal seine Signale liefert und die von diesem Terminal lesen oder (stty<br />

tostop) zu diesem Terminal schreiben dürfen. Die anderen Prozeßgruppen bilden den<br />

Hintergrund.<br />

int tcgetpgrp (int fd);<br />

int tcsetpgrp (int fd, int pgrp);<br />

tcgetpgrp() liefert die Nummer der Vordergrund-Prozeßgruppe für das Terminal, mit<br />

dem fd verbunden ist — das kann nicht /dev/tty sein.<br />

tcsetpgrp() setzt für das Kontroll-Terminal, mit dem fd verbunden ist — das kann nicht<br />

/dev/tty sein — pgrp als Vordergrund-Prozeßgruppe, wobei man in der Session von fd<br />

bleiben muß.<br />

6-9


Job-Control<br />

Eine Job-Control-Shell verwaltet jedes Kommando in einer eigenen Prozeßgruppe, die<br />

folglich Hintergrund-Prozeßgruppe ist.<br />

Signale gehen vom Terminal-Treiber nur an die Vordergrund-Prozeßgruppe des Kontroll-<br />

Terminals.<br />

Hintergrund-Prozesse handeln sich Signale ein, wenn sie vom Kontroll-Terminal lesen<br />

oder (stty tostop) dorthin schreiben wollen.<br />

Die Shell läßt sich über den Zustand der Abkömmlinge berichten und schaltet die<br />

Prozeßgruppen jeweils um, damit auch Hintergrundprozesse in den Vordergrund<br />

kommen und vom Terminal lesen können.<br />

Bei Linux kann man die Unterschiede mit ps -j beobachten. Bei NeXT verwendet man<br />

das folgende Programm proc/psj:<br />

#include <br />

#include <br />

int main () {<br />

#if defined _POSIX_SOURCE || defined __FreeBSD__<br />

int tgrp = tcgetpgrp(2);<br />

int pgrp = getpgrp();<br />

if (tgrp < 0)<br />

perror("tcgetpgrp");<br />

#else<br />

# define tgrp -1<br />

int pgrp = getpgrp(0);<br />

#endif<br />

}<br />

fprintf(stderr, "pid %d, ppid %d, pgrp %d, tgrp %d\n",<br />

getpid(), getppid(), pgrp, tgrp);<br />

fflush(stderr);<br />

return 0;<br />

Es liefert folgendes — wenn auch nicht immer:<br />

sh$ psj<br />

pid 25633, ppid 25632, pgrp 25632, tgrp 25632<br />

sh$ psj &<br />

25634<br />

pid 25634, ppid 25632, pgrp 25632, tgrp 25632<br />

csh% psj<br />

pid 25636, ppid 25635, pgrp 25636, tgrp 25636<br />

csh% psj &<br />

[1] 25637<br />

pid 25637, ppid 25635, pgrp 25637, tgrp 25635<br />

bash$ psj<br />

pid 25638, ppid 21203, pgrp 25638, tgrp 25638<br />

bach$ psj &<br />

[1] 25639<br />

6-10


pid 25639, ppid 21203, pgrp 25639, tgrp 21203<br />

Man sieht, wie die Bourne-Shell die Prozeßgruppe unverändert läßt, während die C-<br />

Shell jedem Prozeß eine eigene Gruppe gibt.<br />

Mit psj kann man auch die verschiedenen Modelle untersuchen, nach denenShells<br />

mehrstufige Pipelines aufbauen:<br />

sh$ psj | psj | psj<br />

pid 25649, ppid 25648, pgrp 25648, tgrp 25648<br />

pid 25650, ppid 1, pgrp 25648, tgrp 25648<br />

pid 25651, ppid 1, pgrp 25648, tgrp 25648<br />

sh$ psj | psj | psj &<br />

25652<br />

pid 25652, ppid 25648, pgrp 25648, tgrp 25648<br />

pid 25653, ppid 1, pgrp 25648, tgrp 25648<br />

pid 25654, ppid 1, pgrp 25648, tgrp 25648<br />

csh% psj | psj | psj<br />

pid 25657, ppid 25655, pgrp 25656, tgrp 25656<br />

pid 25658, ppid 25655, pgrp 25656, tgrp 25656<br />

pid 25656, ppid 25655, pgrp 25656, tgrp 25656<br />

csh% psj | psj | psj &<br />

[1] 25659 25660 25661<br />

pid 25661, ppid 25655, pgrp 25659, tgrp 25655<br />

pid 25659, ppid 25655, pgrp 25659, tgrp 25655<br />

pid 25660, ppid 25655, pgrp 25659, tgrp 25655<br />

bash$ psj | psj | psj<br />

pid 25642, ppid 21203, pgrp 25642, tgrp 25642<br />

pid 25644, ppid 21203, pgrp 25642, tgrp 25642<br />

pid 25643, ppid 21203, pgrp 25642, tgrp 25642<br />

bash$ psj | psj | psj &<br />

[1] 25647<br />

pid 25647, ppid 21203, pgrp 25645, tgrp 21203<br />

pid 25645, ppid 21203, pgrp 25645, tgrp 21203<br />

pid 25646, ppid 21203, pgrp 25645, tgrp 21203<br />

Es stellt sich heraus, daß auf NeXT bei einer Posix-konformen Übersetzung die<br />

Ausgabe von einem Hintergrund-Prozeß zum Kontroll-Terminal nicht korrekt bearbeitet<br />

wird:<br />

$ stty tostop; cc -I../include -o psj psj.c; psj &<br />

[1] 11755<br />

[1]+ Stopped (tty output) psj<br />

$ fg<br />

psj<br />

pid 11755, ppid 11607, pgrp 11755, tgrp -1<br />

$ cc -I../include -posix -o psj-posix psj.c; psj-posix &<br />

[1] 11756<br />

$<br />

[1]+ Done psj-posix<br />

6-11


Dämonen<br />

Waisen<br />

Sessions mit Kontroll-Terminal entstehen von init aus für die login-Terminals oder von<br />

inetd aus für Netzverbindungen mit Pseudo-Terminals.<br />

Dämonen wie lpd oder sendmail erhalten dadurch eigene Sessions und isolieren sich<br />

von den Kontroll-Terminals, daß sie verwaist werden:<br />

if (fork() != 0)<br />

wait(...); // Shell wartet auf Kommando...<br />

... // und arbeitet dann weiter<br />

else if (fork() != 0) // Kommando erzeugt Dämon...<br />

exit(0); // ...und endet sofort<br />

else<br />

setsid(); // Dämon ist verwaist...<br />

... // und in eigener Session<br />

Damit passiert dem Dämon nichts mehr , wenn zum Beispiel die login-Shell beendet<br />

wird.<br />

Erzeuger müssen Abkömmlinge mit wait() abschöpfen, damit die Prozeßtabelle nicht<br />

durch Aufbewahren der exit-Codes abgelaufener Prozesse schließlich überläuft.<br />

Wird ein Erzeuger vorzeitig beendet, erbt init (Prozeß 1) seine Abkömmlinge. init führt<br />

praktisch nur wait() aus und reinigt folglich die Prozeßtabelle. Endet init, terminieren<br />

manche <strong>UNIX</strong>-Kerne selbständig.<br />

Wenn ein Prozeß verwaist, der wegen Zugriff zum Kontroll-Terminal gestoppt ist, kann<br />

er in einer verwaisten Prozeßgruppe sein.<br />

Eine verwaiste Prozeßgruppe hat nur Prozesse, deren Erzeuger in der gleichen Gruppe<br />

oder in einer anderen Session sind — sie können folglich einen gestoppten Prozeß<br />

nicht mehr in den Vordergrund bringen, damit er zum Terminal kommt.<br />

Jeder Prozeß der verwaisten Gruppe erhält ein SIGHUP und ein SIGCONT — dadurch wird<br />

er entweder vernichtet oder fortgesetzt. Greift er dann weiter auf das T erminal zu, wird<br />

dann der Zugriff mit einem Fehler und nicht mehr mit Abstoppen quittiert.<br />

Von Stevens stammt die Idee zum Programm orphan, mit dem man zeigen kann, was<br />

passiert.<br />

6-12


* based on Stevens */<br />

#include <br />

#include <br />

#include <br />

static void trap (int sig) {<br />

printf("[%d]\treceived signal %d\n", getpid(), sig);<br />

fflush(stdout);<br />

}<br />

static void info (const char * who) {<br />

printf("%s\tpid %d, ppid %d, pgrp %d, tgrp %d\n",<br />

who, getpid(), getppid(), getpgrp(), tcgetpgrp(2));<br />

fflush(stdout);<br />

}<br />

int main () {<br />

char ch;<br />

}<br />

info("parent");<br />

switch (fork()) {<br />

case -1: /* error */<br />

perror("fork"), exit(1);<br />

default: /* parent */<br />

sleep(5); /* time for child to stop */<br />

break;<br />

case 0: /* child */<br />

info("child");<br />

signal(SIGHUP, trap); /* catch HUP */<br />

kill(getpid(), SIGTSTP); /* stop for terminal input */<br />

info("cont’d");<br />

if (read(0, &ch, 1) == -1) /* try to read */<br />

perror("read");<br />

}<br />

return 0;<br />

orphan verwaist seinen Abkömmling, der damit eine verwaiste Prozeßgruppe<br />

bevölkert, wenn er von einer Job-Control-Shell gestartet wird. Bei Linux 1.3.88 oder<br />

FreeBSD beobachtet man folgendes korrekte Vorhalten:<br />

linux$ orphan<br />

parent pid 865, ppid 857, pgrp 857, tgrp 857<br />

child pid 866, ppid 865, pgrp 857, tgrp 857<br />

[866] received signal 1<br />

cont’d pid 866, ppid 1, pgrp 857, tgrp 158<br />

read: I/O error<br />

linux$ orphan &<br />

[1] 867<br />

parent pid 867, ppid 158, pgrp 867, tgrp 158<br />

child pid 868, ppid 867, pgrp 867, tgrp 158<br />

[868] received signal 1<br />

cont’d pid 868, ppid 1, pgrp 867, tgrp 158<br />

read: I/O erro<br />

Man sieht auch, daß orphan in eine Hintergrund-Prozeßgruppe wechselt. Bei Linux<br />

1.2.13 klappt es nicht und auch bei NeXT funktioniert es trotz -posix nicht ganz:<br />

6-13


next$ orphan<br />

parent pid 25553, ppid 21203, pgrp 25553, tgrp -1<br />

child pid 25554, ppid 25553, pgrp 25553, tgrp -1<br />

[25554] received signal 1<br />

cont’d pid 25554, ppid 1, pgrp 25553, tgrp -1<br />

next$ echo $?<br />

0<br />

6-14


Signale<br />

Signale<br />

Signale sind asynchron eintretende Ereignisse, die meistens den normalen Ablauf<br />

eines Programms unterbrechen und häufig zum Abbruch des Programms führen. Sie<br />

sind kein guter Mechanismus zur Prozeßkommunikation.<br />

Signale können absichtlich durch kill() oder killpg() an andere oder auch den<br />

gleichen Prozeß verschickt werden, oder sie werden vom System erzeugt. Fast alle<br />

Signale können ignoriert oder mit einer Funktion abgefangen werden.<br />

Bei System V bleibt eine Funktion bei Eintreffen des Signals weiter eingestellt, bei<br />

Berkeley früher und bei Linux nicht. Durch ein Signal unterbrochene Systemaufrufe<br />

werden oft implizit wiederholt, oder man erhält errno == EINTR.<br />

Einfache Signal-Operationen<br />

int kill(int pid, int sig);<br />

int killpg(int pgrp, int sig);<br />

kill() und killpg() schicken das Signal sig an einen Prozeß (pid > 0), an die eigene<br />

Prozeßgruppe (pid == 0 oder pgrp == 0), an eine andere Prozeßgruppe (pid < -1<br />

oder pgrp > 0), an alle eigenen Prozesse (pid == -1) oder an alle nicht zum System<br />

gehörenden Prozesse (pid == -1 für den Super-User). SIGCONT darf immer geschickt<br />

werden, alle anderen Signale nur an eigene Prozesse oder durch den Super-User.<br />

long alarm(long seconds);<br />

alarm() sorgt dafür, daß seconds später SIGALRM an den Aufrufer geschickt wird. Das<br />

Resultat ist das vorher verbleibende Intervall.<br />

int setitimer(int which, const struct itimerval *value, struct itimerval<br />

*ovalue);<br />

int getitimer(int which, struct itimerval *value);<br />

Mit diesen Funktionen können das Echtzeit-Signal (ITIMER_REAL, SIGALRM), das User-<br />

CPU-Zeit-Signal (ITIMER_VIRT, SIGVTALRM) und das profiling-Signal (ITIMER_PROF,<br />

SIGPROF) in Mikrosekunden auf einem Raster (10 ms) kontrolliert werden.<br />

int pause(void);<br />

Der Prozeß wartet auf ein Signal. pause() liefert immer -1.<br />

int (*signal (int sig, void (*func)(int)))(int);<br />

typedef void (*sighandler_t)(int);<br />

sighandler_t signal(int sig, sighandler_t h);<br />

Diese vereinfachte Schnittstelle vereinbart die Reaktion auf ein Signal sig: Ignorieren<br />

(SIG_INT), Voreinstellung (SIG_DFL) oder Abfangen per Funktion h, die dann die Signal-<br />

Nummer als Argument erhält. signal() liefert die vorherige Einstellung; typisch ist:<br />

6-15


if ((alt = signal(sig, SIG_IGN)) != SIG_IGN)<br />

signal(sig, neu);<br />

So vermeidet man, ein ignoriertes Signal zu reaktivieren.<br />

Signal-Namen und -Nummern<br />

Signale werden durch Namen oder Nummern bezeichnet, die man zum Beispiel durch<br />

kill -l als eingebautes Kommando von bash erfahren kann:<br />

$ kill -l<br />

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL<br />

5) SIGTRAP 6) SIGIOT 7) SIGEMT 8) SIGFPE<br />

9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS<br />

13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGURG<br />

17) SIGSTOP 18) SIGTSTP 19) SIGCONT 20) SIGCHLD<br />

21) SIGTTIN 22) SIGTTOU 23) SIGIO 24) SIGXCPU<br />

25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH<br />

29) SIGLOST 30) SIGUSR1 31) SIGUSR2<br />

Nicht alle Namen — und insbesondere ihre Bindungen an spezielle Nummern — sind<br />

portabel. Die folgende Liste ist aus NeXT, Linux und Stevens kombiniert.<br />

6-16


Kontroll-Terminal SIGHUP A Verwaisen eines Prozesses, Aufhängen<br />

einer Modem-Verbindung<br />

SIGINT A Unterbrechung (control-C o.ä.)<br />

SIGQUIT C Abbruch mit core (control-\ o.ä.)<br />

SIGTSTP S Stop (control-Z o.ä.)<br />

SIGTTIN S Eingabe in Hintergrund-Prozeßgruppe<br />

SIGTTOU S Ausgabe in Hintergrund-Prozeßgruppe<br />

Fehlverhalten SIGILL C illegaler Maschinenbefehl<br />

SIGTRAP C* trace oder breakpoint<br />

SIGEMT C* EMT-Befehl<br />

SIGIOT C* IOT-Befehl<br />

SIGABRT C abort()<br />

SIGFPE C Gleitkomma-Fehler<br />

SIGBUS C* illegale Adresse (nicht bei Linux)<br />

SIGSEGV C illegale Adresse<br />

SIGSYS C* falscher Systemaufruf (nicht bei Linux)<br />

SIGSTKFLT A* (Linux) Coprozessor-Fehler<br />

SIGPWR I* (System V) Power-Fail und -Restart<br />

Kommunikation SIGPIPE A Schreiben ohne Leser (Pipe, Socket)<br />

SIGURG I* out-of-band Daten an Socket<br />

SIGIO I* asynchrones I/O ist möglich<br />

SIGPOLL A* (System V) poll()<br />

SIGWINCH I* Änderung der Fenstergröße<br />

Zeit SIGALRM A alarm()<br />

SIGVTALRM A* virtuelle Zeitscheibe, setitimer()<br />

SIGPROF A* profiling Zeitscheibe, setitimer()<br />

Prozeßkontrolle SIGKILL AU unbedingter Abbruch<br />

SIGTERM A bedingter Abbruch<br />

SIGSTOP SU unbedingtes Anhalten<br />

SIGCONT I fortsetzen wenn angehalten<br />

SIGCHLD I Abkömmling ändert Zustand<br />

Ressourcen SIGXCPU A* Zeitüberschreitung, setrlimit()<br />

SIGXFSZ A* Dateigrößenüberschreitung,<br />

setrlimit()<br />

frei definierbar SIGUSR1 A<br />

SIGUSR2 A<br />

6-17


Dabei bedeuten:<br />

A Voreinstellung ist Abbruch des Prozesses.<br />

C Voreinstellung ist Abbruch plus core-Dump.<br />

I Voreinstellung ist Ignorieren des Signals.<br />

S Voreinstellung ist Stoppen des Prozesses.<br />

U SIGKILL und SIGSTOP können nicht abgefangen oder ignoriert werden<br />

* gibt’s nicht in Posix<br />

6-18


Prozeßkommunikation<br />

Themen<br />

• Pipes<br />

• Named Pipes<br />

• Messages<br />

• Semaphore<br />

• Shared Memory<br />

1996/5/20<br />

1996/5/20

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!