UNIX-Systemprogrammierung
UNIX-Systemprogrammierung
UNIX-Systemprogrammierung
- TAGS
- www.cs.rit.edu
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