262
Systemnahe Software (Systemnahe Software II) F. Schweiggert, A. Borchert, M. Grabert und J. Mayer 22. Mai 2006 Fakultät Mathematik u. Wirtschaftswissenschaften Abteilung Angewandte Informationsverarbeitung (SAI) Vorlesungsbegleiter (gültig ab Sommersemester 2005) U N I V E R S I T Ä T U L M · S C I E N D O · D O C E N D O · C U R A N D O · Hinweise: Das Urheberrecht ( c ) liegt bei den Autoren Auf eine detaillierte Unterscheidung zwischen BSD Unix und System V Unix wird hier ver- zichtet. Die enthaltenen Beispiel-Programme wurden zum großen Teil unter Linux entwickelt und sind weitestgehend unter Solaris getestet (für konstruktive Hinweise sind die Autoren dankbar). Die Beispiele sollen jeweils gewisse Aspekte verdeutlichen und erheben nicht den An- spruch von Robustheit und Zuverlässigkeit. Man kann alles anders und besser machen. Details zu den behandelten bzw. verwendeten System-Calls sollten jeweils im Manual bzw. den entsprechenden Header-Files nachgelesen werden. Sehr nützliche Hinweise zur Verwendung des C-Compilers finden sich unter der von Dr. Andreas Borchert erstellten Seite http://www.mathematik.uni-ulm.de/sai/ss05/soft/cc/ i

Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Systemnahe Software(Systemnahe Software II)

F. Schweiggert, A. Borchert, M. Grabert und J. Mayer

22. Mai 2006

Fakultät Mathematik u. WirtschaftswissenschaftenAbteilung Angewandte Informationsverarbeitung (SAI)

Vorlesungsbegleiter (gültig ab Sommersemester 2005)

UN

IVERSI TÄ T ULM

· SC

IEN

DO

· DOCENDO · CU

RA

ND

O ·

Hinweise:

• Das Urheberrecht ( c©) liegt bei den Autoren

• Auf eine detaillierte Unterscheidung zwischen BSD Unix und System V Unix wird hier ver-zichtet.

• Die enthaltenen Beispiel-Programme wurden zum großen Teil unter Linux entwickelt undsind weitestgehend unter Solaris getestet (für konstruktive Hinweise sind die Autorendankbar).

• Die Beispiele sollen jeweils gewisse Aspekte verdeutlichen und erheben nicht den An-spruch von Robustheit und Zuverlässigkeit. Man kann alles anders und besser machen.

• Details zu den behandelten bzw. verwendeten System-Calls sollten jeweils im Manual bzw.den entsprechenden Header-Files nachgelesen werden.

• Sehr nützliche Hinweise zur Verwendung des C-Compilers finden sich unter der von Dr.Andreas Borchert erstellten Seite

http://www.mathematik.uni-ulm.de/sai/ss05/soft/cc/

i

Page 2: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

ii

Page 3: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Inhaltsverzeichnis

1 Das Unix-Prozess-Subsystem 11.1 ”Theorie” der Prozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Prozess-Baum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.3 Prozessmanagement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.4 Synchrone und asynchrone Prozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.5 CPU-intensive Prozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.6 Prozesszustände und Zustandsübergänge . . . . . . . . . . . . . . . . . . . . . . . . 91.7 Virtueller Adressraum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101.8 Das proc-Filesystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2 Prozesse unter Unix 152.1 System Call “fork” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152.2 System Call “exit” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.3 System Call “exec” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222.4 System Call “wait” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292.5 n-Damen-Problem mit Prozessen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352.6 Datei-Umlenkung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402.7 Eine kleine Shell (tinysh) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

2.7.1 Allgemeines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422.7.2 Anforderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422.7.3 Grundlegender Ablauf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432.7.4 Einschub: stralloc.h, libowfat-Bibliothek . . . . . . . . . . . . . . . . . . . . . 442.7.5 Einlesen der Kommandozeile – readline() . . . . . . . . . . . . . . . . . . . . 452.7.6 Zerlegen in Tokens – tokenizer() . . . . . . . . . . . . . . . . . . . . . . . . . 472.7.7 Hauptprogramm unserer Shell . . . . . . . . . . . . . . . . . . . . . . . . . . 52

2.8 Bootstrapping – klassisch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 542.9 Der init-Prozess . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

3 Signale 593.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593.2 Signalbehandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 613.3 Reaktion auf Signale: signal() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 613.4 Wecksignale mit alarm() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653.5 Das Versenden von Signalen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 673.6 Reaktion auf Signale: sigaction() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733.7 Die Zustellung von Signalen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 783.8 Signale als Indikatoren für terminierte Prozesse . . . . . . . . . . . . . . . . . . . . 823.9 Signalbehandlung in einer (Mini-)Shell . . . . . . . . . . . . . . . . . . . . . . . . . 883.10 Überblick der Signale aus dem POSIX-Standard . . . . . . . . . . . . . . . . . . . . 106

iii

Page 4: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

iv INHALTSVERZEICHNIS

4 Inter-Prozess-Kommunikation (IPC) 1094.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1094.2 IPC - Client-Server Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1104.3 System Calls “dup()”, “dup2()” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1114.4 Unnamed Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1114.5 Client-Server mit “Unnamed Pipes” . . . . . . . . . . . . . . . . . . . . . . . . . . . 1154.6 Standard I/O Bibliotheksfunktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1204.7 Über Standarddeskriptoren in Pipe schreiben / lesen . . . . . . . . . . . . . . . . . 1224.8 Termination von Pipeline-Verbindungen . . . . . . . . . . . . . . . . . . . . . . . . . 1234.9 Wie die Shell eine Pipeline macht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1254.10 SIGPIPE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

5 Netzwerk-Kommunikation 1275.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1275.2 Ethernet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1355.3 Internetworking - Konzept & Grundlegende Architektur . . . . . . . . . . . . . . . 1365.4 Transport-Protokolle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

5.4.1 Ports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1475.4.2 UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

6 Berkeley Sockets 1516.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1516.2 Ein erstes Beispiel: Timeserver an Port 11011 . . . . . . . . . . . . . . . . . . . . . . 1536.3 Die Socket-Abstraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1566.4 Die Socket-Programmierschnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . 157

6.4.1 Vorbemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1576.4.2 Überblick/Einordnung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1596.4.3 Erzeugung eines Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1616.4.4 Benennung eines Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1626.4.5 Aubau einer Kommunikationsverbindung . . . . . . . . . . . . . . . . . . . 1656.4.6 Client-Beispiel: Timeclient für Port 11011 . . . . . . . . . . . . . . . . . . . . 1696.4.7 Überblick: Gebrauch von TCP-Ports . . . . . . . . . . . . . . . . . . . . . . . 1706.4.8 Der Datentransfer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1756.4.9 Terminierung einer Kommunikationsverbindung . . . . . . . . . . . . . . . 1776.4.10 Verbindungslose Kommunikation . . . . . . . . . . . . . . . . . . . . . . . . 1776.4.11 Feststellen gebundener Adresse . . . . . . . . . . . . . . . . . . . . . . . . . 1836.4.12 Socket-Funktionen im Überblick . . . . . . . . . . . . . . . . . . . . . . . . . 184

6.5 Konstruktion von Adressen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1856.5.1 Socket-Adressen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1856.5.2 Socket-Adressen der UNIX-Domäne . . . . . . . . . . . . . . . . . . . . . . . 1876.5.3 Socket-Adressen in der Internet-Domäne . . . . . . . . . . . . . . . . . . . . 1886.5.4 Byte-Ordnung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1896.5.5 Spezifikation von Internet-Adressen . . . . . . . . . . . . . . . . . . . . . . . 1906.5.6 Hostnamen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1926.5.7 Lokale Hostnamen und IP-Adressen . . . . . . . . . . . . . . . . . . . . . . . 1956.5.8 Portnummern und Dienste . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197

6.6 Gepufferte Ein- / Ausgabe für Netzwerkverbindungen . . . . . . . . . . . . . . . . 1996.7 Ein kleiner TCP-Server (concurrent) mit Eingabe-Pufferung . . . . . . . . . . . . . 2066.8 Ein- / Ausgabe von Paketen für Netzwerkverbindungen . . . . . . . . . . . . . . . 2126.9 Parallele Sitzungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217

Page 5: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

INHALTSVERZEICHNIS v

7 Threads 2237.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2237.2 Grundlegende Funktionen: Erzeugen und Beenden . . . . . . . . . . . . . . . . . . 225

7.2.1 pthread_create() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2257.2.2 pthread_join() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2277.2.3 pthread_self() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2277.2.4 pthread_detach() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2277.2.5 pthread_exit() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228

7.3 Beispiel: Matrix-Multiplikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2287.4 TCP Echo Server mit Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2317.5 Thread-sichere Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2367.6 Echo Client mit Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2377.7 Synchronisation – Mutual Exclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239

Anhang 243

Literatur 245

Abbildungsverzeichnis 247

Beispiel-Programme 249

Index 249

Page 6: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

vi INHALTSVERZEICHNIS

Page 7: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Kapitel 1

Das Unix-Prozess-Subsystem

1.1 ”Theorie” der Prozesse

• Die Ausführung eines Programms heißt Prozess.

• Der Kontext eines Prozesses ist seine Ausführungsumgebung. Dazu gehören

– der Adressraum, in dem u.a. der Programmtext (als Maschinencode) und die Datenuntergebracht sind

– ein Satz Maschinenregister einschließlich der Stackverwaltung (Stack-Zeiger, Frame-Zeiger) und dem PC – der Program Counter verweist auf die nächste auszuführendeInstruktion

– weitere Statusinformationen, die vom Betriebssystem verwaltet werden wie z.Bsp. of-fene I/O-Verbindungen

Die Kombination aus Adressraum und Statusinformationen wird bei Unix als Prozess be-zeichnet. Zu einem Prozess gehoeren mindestens ein Satz Maschinenregister einschließlicheinem PC (program counter). Es können aber auch mehrere sein; in diesem Fall spricht manvon Threads: es existieren mehrere “Ausführungsfäden”, die parallel abgearbeitet werden.Unix verwaltet allerdings für einzelne Threads auch Statusinformationen, so dass bei einemProzess auch manchmal von einer “Rechtegemeinschaft” gesprochen wird.

• Ausführbare Programme sind z.B.:

– vom Compiler generierter Objektcode (“a.out”)

– Datei mit einer Folge von Shell-Kommando’s → chmod+x)

• Das Betriebssystem verwaltet eine endliche Menge von Prozessen und versucht, die vor-handenen Resourcen (Speicher, Rechenzeit, I/O-Operationen) fair auf die einzelnen Pro-zesse zu verteilen.Ein Prozess folgt bei der Ausführung einer genau festgelegten Folge von Anweisungen,die in sich abgeschlossen sind. Schutzmechanismen des Betriebssystems und der Hardwareverhindern, dass ein Prozess auf Anweisungen oder Daten außerhalb seines Adressraumszugreift. Die gesamte Kommunikation mit anderen Prozessen (IPC – Interprocess Communi-cation) oder mit dem Kernel muss über System Calls erfolgen.

• (Fast) alle Prozesse entstehen aus einem bereits existierenden Prozess durch Aufruf von intfork().

1

Page 8: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM

• Jeder Prozess besitzt eine Prozessnummer (PID). als eindeutige Identifikation, die einen In-dex in die Prozesstabelle darstellt. In dieser Tabelle verwaltet das Betriebssystem die wich-tigsten Daten aller Prozesse.Mit der Funktion int getpid() kann ein Prozess seine eigene PID abfragen.

• (Fast) jeder Prozess hat einen ”Erzeuger” (siehe oben: fork()), der sog. parent processDessen PID kann ein Prozess mit der Funktion int getppid() erhalten.

Programm 1.1: Prozess-ID abfragen (pids.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4

5 int main() {6

7 printf ( "My pid is %d, that of my parent ist %d\n",8 getpid (), getppid ());9 exit (0);

10 }

Ausführung:

spatz$ echo $$ # PID der Shell4223spatz$ gcc -Wall -o pids pids.cspatz$ pidsMy pid is 4236, that of my parent ist 4223spatz$

Anm.: Die Shell-Variablen $ enthält die PID der aktuellen Shell – wird einer Shell-Variablenein $ vorangestellt, so wird diese von der Shell durch ihren Wert substituiert.

• Jeder Prozess gehört zu einer Prozessgruppe, die ebenfalls durch eine kleine positive Inte-gerzahl identifiziert wird (process group ID).Ist die process ID eines Prozesses gleich der process group ID, so wird dieser Prozess als pro-cess group leader bezeichnet.Mit kill (Shell-Kommando und auch System Call) können Signale an alle Prozesse einerprocess group gesandt werden.Bestimmung der process group ID: Funktion int getpgrp();in BSD4.3: int getpgrp(int pid); ist das Argument pid 0, liefert diese Funktion die process groupID des aktuellen Prozessen, ansonsten die der Prozessgruppe des über pid angegebenenProzesses.

Die Zuordnung zu einer Prozessgruppe kann geändert werden:

– unter System V: int setpgrp();damit wird der aufrufende Prozess zum process group leader; als process group ID wirddie process ID geliefert.

– unter 4.3.BSD: int setpgrp(int pid, int pgrp);ist pid 0, so ist der aktuelle Prozess gemeint, ansonsten muss der angesprochene Pro-zess dieselbe effektive user ID wie der aktuelle Prozess haben oder der aktuelle Prozessmuss Superuser Privilegien haben.

Page 9: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

1.1. ”THEORIE” DER PROZESSE 3

• Jeder Prozess kann Mitglied einer terminal group sein, die selbst wieder über eine positiveganze Zahl (terminal group ID) identifiziert wird. Diese ist die process ID des process groupleader, der das Terminal ”geöffnet” hat - typischerweise die login shell. Dieser Prozess wirdcontrol process für dieses Terminal genannt. Jedes Terminal hat nur einen control process.

Die terminal group ID identifiziert das Kontroll-Terminal für jede Prozessgruppe. Das Kontroll-Terminal wird benutzt, um Signale abzuarbeiten (von der Tastatur aus erzeugt, oder wenndie login shell terminiert).

Wenn der Prozess, der gleichzeitig Kontroll-Prozess eines Terminals und Prozessgruppen-führer ist (typischerweise die login shell), ein exit zum Beenden aufruft, so wird das hangupSignal an alle Prozesse dieser Gruppe gesandt. Das Kontrollterminal wird automatisch re-ferenziert durch /dev/tty. Ein Dämon-Prozess hingegen ist ein Hintergrundprozess ohnekontrollierendes Terminal. (z.B. httpd)

• Terminierung eines Prozesses:

Prozesse können sich jederzeit mit exit() beenden und dabei einen Statuswert (Exit-Statusoder Beendigungsstatus) im Bereich von 0 bis 255 angeben. In C-Programmen kann dieexit-Funktion auch implizit aufgerufen werden: ein return in der main-Funktion führt zueinem entsprechenden exit, wenn das Ende der main-Funktion erreicht wird, so entsprichtdies einem exit(0).

Ein Exit-Wert 0 deutet eine erfolgreiche Terminierung an, andere Werte, so EXIT_FAILURE,werden als Misserfolg gewertet (allerdings Programm abhängig: egrep liefert 1, wenn kei-ne Treffer vorliegen). Diese Konventionen orientieren sich zwar an UNIX, sind aber auchBestandteil von ISO/IEC 9899:1999 (C99-Standard).

Page 10: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

4 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM

1.2 Prozess-Baum

• Hierarchie und Vererbung (siehe Abbildung 1.1

parent process

child process

(pid1 > 0)

pid1 = fork()

(pid1 == 0)

pid2 = fork()

(pid2 > 0)

child process

(pid2 == 0)

Abbildung 1.1: Prozess-Hierarchie

Vererbung: Prozesse vererben Teile ihrer Umgebung an ihre Kindprozesse:

• die Standard-Dateiverbindungen

• den aktuellen Katalog

• Umgebungsvariablen

Auch die Regeln für die Weitergabe von Informationen folgen streng der hierarchischen Verer-bungslehre. Elternprozesse können ihren Kindern Teile ihrer Umgebung als Kopie mitgeben. Dieumgekehrte Richtung ist nicht möglich. Prozesse können Daten untereinander nur über IPC-Mechanismen (interprocess communication) austauschen.

Eine Ausnahme bildet der Beendigungsstatus, den ein Prozess bei seiner Beendigung an seinenErzeugerprozess zurückgeben kann (exit() → wait()). Auf Shell-Eeben kann der Exit-Status deszuletzt terminierten Programm über die Variable ? abgefragt werden:

spatz$ egrep stdio *.clongrun.c:# include <stdio.h>pids.c:# include <stdio.h>spatz$ echo $?0spatz$ egrep stdin *.cswg@spatz:~/skripte/soft2.05/1/progs> echo $?1spatz$

Page 11: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

1.3. PROZESSMANAGEMENT 5

1.3 Prozessmanagement

• Kommando ps - Ausgabe der Prozesstabelle

Das Kommando ps gibt alle wesentlichen Informationen über die aktuelle Prozesshierarchie(→ Prozesstabelle) aus.

Je nach Option (und Betriebssystemversion) besteht die Ausgabe von ps aus mehr oderweniger Informationen zu den einzelnen Prozessen. Die Ausgabe ist, wie bei ls, in tabella-rischer Form.

spatz$ ps -o pid,comm,ppid,tty,stat,start_timePID COMMAND PPID TT STAT START

3875 bash 3866 pts/0 Ss 09:333934 gv 3875 pts/0 S 09:404926 gs 3934 pts/0 S 15:095009 ps 3875 pts/0 R+ 15:26

spatz$

Bedeutung:

PID die eindeutige Prozessnummer (process id)COMMAND der zum Prozess gehörende KommandonamePPID die Prozessnummer des ErzeugersTT Name des Terminals (Windows), an dem der Prozess gestartet wurdeSTAT Prozessstatus

R für Running (runnable, on run queue, S für Sleeping, T für Traced/Stopped,Z für Zombie (tot, aber nicht vom Vater beerdigt), s für Session Leader, + fürMitglied in der im Vordergrund laufenden Prozessgruppe

Mehr dazu siehe in den jeweiligen Manualseiten!

• Kommando top – Darstellung der CPU-intensiven Prozesse → man top

• Kommando kill – Prozesse abbrechen

Mit dem Kommando kill werden Signale an Prozesse verschickt. Es gibt Signale, von Pro-zessen ignoriert werden können. Nicht ignoriert werden können das Signal mit der Num-mer 9 (SIGKILL) – gewaltsames Beenden – und das Signal 23 (SIGSTOP – Anhalten. Letz-teres kann mit ctrl-z über die Tastatur erzeugt werden.

Siehe signal.h, man kill oder kill -l).

Auf diesem Weg lassen sich Hintergrundprozesse abbrechen, falls sie blockiert sind oderungewöhnlich viel CPU -Zeit verbrauchen (Endlosschleife?).

Ein blockiertes Terminal lässt sich durch ein kill-Kommando für die betreffende Login-Shell,von einem anderen Terminal aus, im Normalfall wieder in einen benutzbaren Zustand zu-rückbringen. Die Login-Shell ist in der Spalte COMMAND als -sh oder schlicht als - aufge-führt.

Als gewöhnlicher Benutzer darf man nur seine eigenen Prozesse mit kill beenden. AlsSuper-User darf man alle Prozesse beenden; von dieser Möglichkeit sollte man aber nurdann Gebrauch machen, wenn man sich über die Konsequenzen im klaren ist!

Page 12: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM

1.4 Synchrone und asynchrone Prozesse

• Vordergrundverarbeitung

In der Regel verarbeitet die Shell Kommandos synchron. Sie startet ein Kommando (→ er-zeugt Prozess) und erst nach dessen Beendigung meldet sie sich mit dem Prompt (beginntsie mit dem nächsten Kommando). Der Erzeugerprozess wartet auf das Ende seines Kind-prozesses.

• Hintergrundverarbeitung

Kommandos, deren Abarbeitung länger dauert (etwa in der Statistik oder Numerik), blockie-ren bei synchroner Abarbeitung den interaktiven Betrieb. Schließt ein &-Zeichen das Kom-mando ab, wartet die Shell (Erzeugerprozess) nicht auf das Ende des gerade gestartetenProzesses. Sie ist statt dessen sofort bereit, das nächste Kommando entgegenzunehmen.

• Fußangeln

Hintergrundkommandos, die von der Standardeingabe lesen wollen, erhalten das SignalSIGTTIN – der Prozess wird angehalten, existiert aber weiterhin! Damit wird verhindert,dass sich ein Vordergrundprozess (meistens die Shell selbst) und mehrere aktive Hinter-grundprozesse um die Eingabe vom Terminal streiten.

Programm 1.2: Von stdin lesen (read-stdin.c)

1 # include < stdio .h>2 # include < stdlib .h>3

4 int main() {5 char buf [256];6 puts ( "Eingabe: " );7 fgets ( buf , 255, stdin );8 printf ( "gelesen : %s\n", buf );9 exit (0);

10 }

Mit dem Kommando fg kann ein via SIGTTIN angehaltener Prozess in den Vordergrundgeholt werden:

spatz$ gcc -Wall read-stdin.cspatz$ a.out &Eingabe:[2] 5150

[2]+ Stopped a.outspatz$ ps -o pid,comm,ppid,tty,stat

PID COMMAND PPID TT STAT3875 bash 3866 pts/0 Ss5150 a.out 3875 pts/0 T5153 ps 3875 pts/0 R+

spatz$ fg 2a.outUnd jetzt?gelesen: Und jetzt?

spatz$

Page 13: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

1.5. CPU-INTENSIVE PROZESSE 7

Am.: Die in eckigen Klammern angegebene Zahl bezeichnet einen sog. Job: mehrere zueiner Einheit zusammengefasste Prozesse (z.Bsp. in einer Pipeline).

Ein beliebiges Hintergrundkommando terminiert automatisch mit der Terminalsitzung,aus der es gestartet wurde: es erhält das SIGHUP-Signal , voreingestellte Reaktion dar-auf ist Termination. Dies lässt sich für Programme mit extrem langer Laufzeit auch beimStart verhindern:

nohup kommando &

1.5 CPU-intensive Prozesse

Prozesse laufen in einer bestimmten Prioritätsklasse – diese wird vom Erzeuger-Prozess geerbt.Die Priotitätsstufen gehen von -20 (höchste Stufe bis 19 niedrigste Stufe). Mit dem Kommandonice bzw. der Funktion nice() kann die Priorität eines Prozesses verändert (herabgestuft) werden!Die Erhöhung der Priorität verlangt Privilegien!

Programm 1.3: Priorität verändern (priority.c)

1 # include <unistd .h>2 # include < stdio .h>3 # include < stdlib .h>4

5 int main() {6 printf ( "My priority is %d!\n", nice (0));7 perror ( "nice " );8 printf ( "And now it’s 5 points decreased to %d!\n", nice(5));9 perror ( "nice " );

10 printf ( "And now it’s 8 points decreased to %d!\n", nice(8));11 perror ( "nice " );12 printf ( "Let ’ s try to increase it by 5 to %d!\n", nice(−5));13 perror ( "nice " );14 exit (0);15 }

Ausführung von Programm 1.3:

My priority is 0!nice: SuccessAnd now it’s 5 points decreased to 5!nice: SuccessAnd now it’s 8 points decreased to 13!nice: SuccessLet’s try to increase it by 5 to -1!nice: Permission denied

Page 14: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

8 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM

Ausführung von Programm 1.3 als root:

My priority is 0!nice: SuccessAnd now it’s 5 points decreased to 5!nice: SuccessAnd now it’s 8 points decreased to 13!nice: SuccessLet’s try to increase it by 5 to 8!nice: Success

Üblicherweise nutzt man das Kommando nice beim Start eines Programmes:

spatz$ nice -n 3 a.outMy priority is 3!nice: SuccessAnd now it’s 5 points decreased to 8!nice: SuccessAnd now it’s 8 points decreased to 16!nice: SuccessLet’s try to increase it by 5 to -1!nice: Permission deniedspatz$

Page 15: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

1.6. PROZESSZUSTÄNDE UND ZUSTANDSÜBERGÄNGE 9

1.6 Prozesszustände und Zustandsübergänge

1

29

7

34

6 5

8fork()

not enoughmemory

enough memory

swap outswapin

wakeup

swap out

wakeup

sleep

exit()

preempt

reschedule

return

systemcall

Abbildung 1.2: Prozess – Zustandsmodell

1 Ausführung im User Mode (Prozess arbeitet in seinem Adressraum)

2 Ausführung im Kernel Mode (Prozess arbeitet im Kernel-Adressraum)

3 Prozess wartet auf Zuteilung der CPU durch Scheduler

4 Prozess ”schläft” im Hauptspeicher, wartet auf bestimmtes Ereignis, das ihn ”aufweckt”

5 Prozess ist ”ready to run”, aber ausgelagert - muss vom ”Swapper” (Prozess 0) in Hauptspei-cher geholt werden, bevor der Kern ihn zuteilen kann

6 Prozess ”schläft” im Sekundärspeicher (ausgelagert)

7 Prozess geht vom Kernel Mode in User Mode, aber der Kern supendiert ihn und vollzieht einenKontext-Switch (anderer Prozess kommt zur Ausführung (bis auf Kleinigkeiten identischmit 3)

8 Prozess neu erzeugt (Startzustand für jeden Prozess - ausgenommen Prozess 0)

9 Prozess hat exit -Aufruf ausgeführt, existiert nicht weiter, hinterlässt aber Datensatz (Exit-Code, Statistik-Angaben) für Vater-Prozess - Endzustand

• Übergänge

System Calls und Kernel-Serviceroutinen, sowie äußere Ereignisse (Interrupts durch Uhr,HW, SW, ...) können Zustandswechsel bei den Prozessen bewirken.Ein ”Context Switch” zwischen Prozessen ist nur beim Zustandswechsel des aktiven Pro-zesses von Zustand (2) ”Running in Kernel Mode” nach (4) ”Asleep” möglich. Beim ”Context

Page 16: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

10 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM

Switch” wählt der Scheduler den Prozess mit der größten Priorität (Zur Erinnerung: Kom-mando nice!) aus der Ready-Liste (alle Prozesse im Zustand (3) ”Ready to Run”) aus undbefördert ihn in den Zustand (2). Der Scheduler teilt ihm die CPU zu und bringt ihn damitzur Ausführung.

1.7 Virtueller Adressraum

Bei einfachen Prozessoren gibt es keinen Unterschied zwischen physischen und virtuellen Spei-cheradressen und damit auch keine scharfe Trennlinie zwischen Betriebssystem und Anwen-dung. Dies macht nicht nur die Speicheraufteilung unübersichtlich, es führt auch dazu, dass einAbsturz einer Anwendung, beispielsweise hervorgerufen durch einen ungültigen Zeigerzugriff,das gesamte System beeinträchtigen kann.

Bessere Prozessoren besitzen eine Speicherverwaltung (memory management unit, abgekürzt MMU),die die Einrichtung virtueller Speicherumgebungen ermöglicht. Anwendungen verwenden dannvirtuelle Speicheradressen, die mittels einer vom Betriebssystem konfigurierten Funktion in phy-sische Speicheradressen abgebildet werden.

virtuelle Adresse

physische Adresse

Abbildungstabelle

der MMU

Abbildung 1.3: Virtuelle und physische Adressen

Abbildung 1.3 zeigt schematisch, wie typischerweise virtuelle Adressen in physische Adressenabgebildet werden. Um die Abbildung effizient (in der Hardware) durchführen zu können, wirdder gesamte Speicher in Form von Seiten organisiert. Typische Seitengrößen liegen zwischen 4und 64 Kilobyte. Entsprechend lassen sich virtuelle Adressen in zwei Teile zerlegen: Der niedrig-wertige Teil spezifiziert nur die Position innerhalb einer Seite, der höherwertige gibt die Seiten-nummer an. In einer Tabelle der MMU können nun die Adressen physischer Seiten den Seiten-nummern virtueller Adressen zugeordnet werden.

Mehrere Abbildungstabellen können parallel nebeneinander existieren: Eine für das Betriebssy-stem selbst und eine für jede Anwendung. Ein Wechsel lässt sich effizient realisieren, indem einspezielles Hardware-Register verwendet wird, das auf die derzeitig gültige Abbildungstabelleverweist.

Page 17: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

1.7. VIRTUELLER ADRESSRAUM 11

Die Abbildungstabellen einer MMU bieten typischerweise noch Platz für Zugriffsrechte. So kanngeregelt werden, welche Seiten aus der Sicht einer gegebenen Abbildungstabelle vorhanden, les-bar, schreibbar und ausführbar sind. Die Gesamtheit aus Abbildungstabelle und Zugriffsrechtenwird als virtueller Adressraum bezeichnet. Kommt es bei einem Zugriff zu einem Fehler durchdie MMU, kann das Betriebssystem entscheiden, ob es sich dabei um einen Fehler der Anwen-dung handelt oder ob es den Zugriff nach einigen Manipulationen doch noch ermöglicht.

Viele Tricks sind auf diese Weise möglich:

• Zwei oder mehr Adressräume können gemeinsam auf die gleiche Bibliothek zugreifen.Hierbei ist es sinnvoll, für den gemeinsamen Bereich nur Lese- und Ausführungsrechteeinzuräumen.

• Teile eines belegten Adressraumes können auf die Platte ausgelagert werden. Bei einem Zu-griff kommt es zuerst zu einem Fehler der MMU, der dann vom Betriebssystem abgefangenwird, um den Bereich wieder von Platte einzulesen und zur Verfügung zu stellen.

• Kopien von Speicherbereichen werden verzögert angefertigt, indem der zunächst noch ge-meinsam gehaltene Bereich mit einem Schreibschutz versehen wird. Sobald eine Schreib-operation erfolgt, wird die betroffene Seite dupliziert und bei beiden Kopien werden danndie Schreibrechte wieder zurückgegeben.

Der Kernel unterteilt den virtuellen Adressraum eines Prozesses in logische (abstrakte) Bereiche(regions) zusammenhängender Adressen; diese werden als verschiedene Objekte behandelt, dieals Ganzes geschützt (protected) bzw. geteilt (shared) werden könnenText, Daten, Stack bilden typischerweise separate Bereiche

Der Kernel unterhält eine Tabelle mit Verweisen auf die aktiven Bereiche (u.a. Information, wodiese im physischen Speicher liegen)

Jeder Prozess unterhält eine private per process region table (z.B. in der u area)

• Context (Abb. 1.4, S. 12)Der UNIX-Jargon unterteilt den Context in drei disjunkte Bereiche: ”Text”,”User Data” und ”System Data”. Davon hält der Kernel die für ihn wichtigen Daten (”System-Daten” ) an zwei definerten Plätzen in seinem Adressraum - in der Prozesstabelle und inder ”User Area” (”per process data region”)

Text und User-Daten Bereich bilden den ”User Adressraum”.

• Context Switch

Ein ”Context Switch” findet statt, wenn der Scheduler beschließt, dass ein anderer Prozesszur Ausführung kommen soll. Der Kernel hält den laufenden Prozess an, sichert dessenContext und lädt den Context des nächsten Prozesses.

Bei jedem Wechsel sichert der Kernel soviel Informationen, dass er später zu dem unter-brochenen Prozess zurückkehren und dessen Ausführung fortsetzen kann. Für den Prozessbleibt die Unterbrechung völlig transparent.

Ein ”Context Switch” kann nur stattfinden, während der Prozess im Kernel Mode arbeitet.Ein Wechsel des ”Execution Mode” ist kein ”Context Switch” .

Page 18: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

12 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM

Benutzer−Adressraum

Kernel−

Adressraum

je Prozess auslagerbar (swappable)

Prozess−Tabelle

für alle Prozess zusammen resident

System−

Daten

(Programm−)

Text Daten

Abbildung 1.4: Prozess-Kontext

Einträge in der Prozesstabelle:

• Prozesszustand (siehe obiges Beispiel bg.c: Zustand T)

• Information, wo der Prozess und die u area im Haupt-/Sekundärspeicher liegen sowie überSpeicherbedarf (☞ context switch)

• ”Verwandtschaftsverhältnisse”

• scheduling Parameter

• diverse ”Uhren” (→ time)

• u.a.m.

Mehr dazu: man ps !

Einträge in der ”u area”

- Reale und effektive UserID

- Vektor zur Signalverarbeitung

- Hinweis auf login terminal

- Fehlereintrag für Fehler bei einem Systemaufruf

- Rückgabewert von Systemaufrufen

- Aktueller Katalog, Aktuelle Wurzel

- User File Descriptor Table

- u.a.m.

Page 19: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

1.8. DAS PROC-FILESYSTEM 13

1.8 Das proc-Filesystem

Das Verzeichnis /proc ist ein Pseudo-Dateisystem, das als Schnittstelle zu den Datenstrukturendes Kernels dient und den direkten Zugriff auf /dev/kmem erspart.

Mehr dazu: man proc

Page 20: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

14 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM

Page 21: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Kapitel 2

Prozesse unter Unix

2.1 System Call “fork”

Syntax

# include <sys/types.h># include <unistd.h>

pid_t fork (void) /* create new processes *//* returns PID and 0 on success or -1 on error */

Beschreibung

Der System Call fork erzeugt einen neuen Prozess, indem er den Context des ausführenden Pro-zesses dupliziert – siehe Abb. 2.1

• Der neu erzeugte Prozess wird als Kind-Prozess (child process), der aufrufende als Erzeuger-oder Vater- oder Eltern-Prozess ( parent process) bezeichnet (die weibliche Bezeichnung“Mutter”-Prozess ist wenig gebräuchlich!).

• fork wird einmal aufgerufen und kehrt im Erfolgsfall zweimal zurück: der Aufrufer erhältals Rückgabewert die PID des erzeugten Prozesses, der Kindprozess erhält 0; im Fehlerfall(z.B. bereits zuviele Prozesse erzeugt) kehrt er einmal mit -1 zurück.

15

Page 22: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

16 KAPITEL 2. PROZESSE UNTER UNIX

user

stack

data

Kernel−Stack

open Files

u area

data

user

stack

text

shared

u area

open Files

Kernel−Stack

current dir

current dirchanged root

UFDT

KIT

Abbildung 2.1: Ein neuer Prozess und sein Erzeuger

Page 23: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.1. SYSTEM CALL “FORK” 17

Beispiel

Programm 2.1: Ein neuer Prozess mit fork() (fork1.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4

5 int main()6 {7 int pid = fork ();8

9 if ( pid < 0 ) {10 perror ( "fork () − can’t fork a child" );11 exit (1);12 }13

14 if ( pid > 0 ) {15 printf ( "Parent : created process %d, my pid is %d\n",16 pid , ( int ) getpid ());17 }18 else /∗ pid == 0 ∗/ {19 printf ( "Child: after fork, my pid is %d\n", (int) getpid ());20 printf ( "Child: my parent is %d\n", (int) getppid ());21 }22

23 printf ( " finished (PID = %ld)\n", getpid ());24 exit ( 0 );25 }

Ausgabe:

Parent: created process 4851, my pid is 4850finished (PID = 4850)Child: after fork, my pid is 4851Child: my parent is 4850finished (PID = 4851)

Der Vater-Prozess terminiert hier (rein “zufällig”) vor dem Sohn, der Init-Prozess ”erbt” den ”ver-lorenen Sohn”. Das Geschehen ist nicht deterministisch, hängt u.a. von der Systemauslastung ab!

Page 24: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

18 KAPITEL 2. PROZESSE UNTER UNIX

Oder so: Erzeuger ”schläft” ein bisschen, bevor er terminiert

Programm 2.2: fork() zum zweiten (fork2.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4

5 int main()6 {7 int pid ;8 switch ( pid = fork ()) {9 case −1:

10 perror ( "fork () − can’t fork a child");11 exit (1);12 case 0:13 printf ( "Child: after fork, my pid is %d\n", (int) getpid ());14 printf ( "Child: my parent is %d\n", (int) getppid ());15 break;16 default :17 sleep (5);18 printf ( "Parent : created process %d, my pid is %d\n",19 pid , ( int ) getpid ());20 break;21 }22 printf ( "%d: finished\n", getpid ());23 exit (0);24 }

Ausgabe:

Child: after fork, my pid is 4870Child: my parent is 48694870: finishedParent: created process 4870, my pid is 48694869: finished

Page 25: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.1. SYSTEM CALL “FORK” 19

Vererbt werden:

• real user ID

• real group ID

• effective user ID

• effective group ID

• process group ID

• terminal group ID

• root directory

• current working directory

• signal handling settings

• file mode creation mask

Unterschiede:

• process ID

• parent process ID

• eigene File Deskriptoren (Kopie)

• Zeit bis zu einem ggf. gesetzten alarm ist beim Kind auf 0 gesetzt

Gebrauch:

1. Ein Prozess erzeugt von sich selbst eine Kopie, so dass diese ”die eine oder andere” Opera-tionen ausführt (→ Server).

2. Ein Prozess will ein anderes Programm zur Ausführung bringen - der Kind-Prozess führtvia exec ein neues Programm aus (überlagert sich selbst) (→Shell)

Page 26: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

20 KAPITEL 2. PROZESSE UNTER UNIX

2.2 System Call “exit”

Der eigentliche SystemCall ist _exit():

Syntax:

#include <unistd.h>void _exit(int status);

Meist verwendet man eine entsprechende Bibliotheksfunktion, die auch noch etwas aufräumt:

Syntax

#include <stdlib.h>void exit( int status ) /* terminate process */

/* does NOT return *//* 0 <= status < 256 */

Beschreibung

• Mit dem System Call exit beendet ein Prozess aktiv seine Existenz. Von diesem System Callgibt es keine Rückkehr in den User Mode.

• Unterscheide: System Call _exit und C-Bibliotheks-Funktion exitC-Funktion leert erst alle Puffer und ruft dann den System Call auf.

• Der Kernel gibt den User Adressraum (Text und User-Daten) des Prozesses frei, sowie aucheinen Teil des System-Daten Bereichs (User Area). Übrig bleibt nur der Eintrag in der Pro-zesstabelle, der den Beendigungsstatus und die Markierung des Prozesses als Zombie ent-hält, bis ihn der init-Prozess erbt und abräumt.

Beispiel:

Programm 2.3: Beenden mit exit() (exit1.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4

5 int main() {6 printf ( "This is some text to write on stdout");7 exit (0);8 }

Programm 2.4: Beenden mit _exit() (exit2.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4

5 int main() {6 printf ( "This is some text to write on stdout");7 _exit (0);8 }

Page 27: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.2. SYSTEM CALL “EXIT” 21

Manchmal ist es praktisch, selbst vor dem (selbst gewollten) Beenden noch einiges zu erledigen:Funktion atexit()

Programm 2.5: Beenden mit eigenem Aufräumen (exit3.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4

5 void do_on_exit (void){6 printf ( "That’s all −− BaBa\n");7 }8 void bye (void) {9 printf ( "That was all, folks\n" );

10 }11

12 int main( int argc , char ∗∗ argv ) {13 if ( ( atexit ( do_on_exit ) == 0 ) && ( atexit (bye ) == 0)){14 printf ( " functions to be called at normal termination set\n");15 printf ( "================================================\n");16 }17 if ( argc == 1 )18 printf ( " exit 1\n"), exit (1);19 if ( argc == 2 )20 printf ( " exit 2\n"), exit (2);21 printf ( "end of text reached\n");22 exit (0);23 }

spatz$ gcc -Wall exit3.cspatz$ a.outfunctions to be called at normal termination set================================================exit 1That was all, folksThat’s all -- BaBaspatz$ a.out 1 2 3functions to be called at normal termination set================================================end of text reachedThat was all, folksThat’s all -- BaBaspatz$

Page 28: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

22 KAPITEL 2. PROZESSE UNTER UNIX

2.3 System Call “exec”

Syntax

#include <unistd.h>extern char ** environ;

int execl( char *path, /* path of program file */char *arg0, /* 1st argument (cmd name) */char *arg1, ..., /* 2nd, ... argument */char *argn, /* last argument */(char *) 0

);

int execlp( char *filename, /* name of program file */char *arg0, char *arg1, ... char *argn,(char *) 0

);

int execle( char *path, /*path of program file */char *arg0, char *arg1, ... char *argn,(char *) 0,char **envp /* pointer to environment */

);

int execv( char *path, /* path of program file */char *argv[]; /* pointer to array of argument */

);

int execvp( char *filename, /* name of program file */char *argv[]; /* pointer to array of argument */

);

int execve( char *path, /* path of program file */char *argv[], /* pointer to array of argument */char *envp[], /* pointer to environment */

);/* all return with -1 on error only */

Beschreibung

Die exec - System Calls überlagern im Context des ausführenden Prozesses den Text und denUser-Daten Bereich mit dem Inhalt der Image-Datei. Anschließend bringen sie die neuen In-struktionen zur Ausführung.

Page 29: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.3. SYSTEM CALL “EXEC” 23

Zusammenhang

execv(path,argv)

execl(path,arg, ..., 0)

create argv

to path

execve(path,argv,envp)add

envp

execle(path, arg, ...,0,envp)

create argvcreate argv

execlp(file, arg, ...,0)

execvp(file,argv)convert file

system call

Abbildung 2.2: Die exec()-Familie

1. Die drei Funktionen in der oberen Reihe enthalten jedes Kommando-Argument als sepa-raten Parameter, der NULL-Zeiger ( (char *) 0 - !!!) schließt die variable Anzahl ab (keinargc!). Die drei Funktionen in der unteren Reihe fassen die Kommando-Argumente in einenParameter argv zusammen, das Ende wird entsprechend wieder durch den NULL-Zeigerdefiniert.

2. Die zwei Funktionen in der linken Spalte definieren die Programm-Datei nur durch denDatei-Namen; diese wird über die Einträge in der Umgebungsvariable PATH gesucht (kon-vertiert in vollen Pfadnamen).Falls diese nicht gesetzt ist, wird als default-Suchpfad ”:/bin:/usr/bin” genommen. Enthältdas Argument path einen slash, so wird die Variable PATH nicht verwendet.

3. Bei den vier Funktion in den beiden linken Spalten wird das Environment über die externeVariable environ an das neue Programm übergeben. Die beiden Funktionen in der rech-ten Spalte spezifizieren explizit eine Environment-Liste (muss ebenfalls mit einem NULL-Zeiger abgeschlossen sein).

Page 30: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

24 KAPITEL 2. PROZESSE UNTER UNIX

„Vererbung“ (erhalten bleibt) bei exec():

• process ID

• parent process ID

• process group ID

• terminal group ID

• time left until an alarm clock signal

• root directory

• current working directory

• file mode creation mask

• real user ID

• real group ID

• file locks

Mögliche Änderungen mit exec():

set user ID / set group ID - bit der neuen Datei gesetzt

• effective user ID auf user ID des Besitzers der Programm-Datei

• effective group ID auf group ID des Besitzers der Programm-Datei

Signale:

• Terminieren bleibt Terminieren

• Ignorieren bleibt Ignorieren

• Speziell abgefangene Signale werden wegen des Überlagerns auf Terminieren gesetzt

Page 31: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.3. SYSTEM CALL “EXEC” 25

Beispiel:

Programm 2.6: Neues Programm ausführen (exec1.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4

5 void exec_test () /∗ execl version ∗/ {6 printf ( "The quick brown fox jumped over " );7 fflush ( stdout );8 execl ( "/bin/echo", "echo", "the" , "lazy" , "dogs." ,9 (char ∗) 0 );

10 perror ( " execl − can’t exec /bin/echo" );11 }12

13 int main() {14 int pid ;15

16 pid = fork ();17 if ( pid < 0) {18 perror ( "fork () − can’t fork");19 exit (1);20 }21 if ( pid > 0)22 printf ( "PP: Parent−PID: %d / Child−PID: %d\n",23 ( int ) getpid (), pid );24 else /∗ pid == 0 ∗/ {25 printf ( "CP: Child−PID: %d\n", (int) getpid () );26 exec_test ();27 }28 exit (0);29 }

Ausgabe:

CP: Child-PID: 4881The quick brown fox jumped over PP: Parent-PID: 4880 / Child-PID: 4881the lazy dogs.

Lässt man in Programm 2.6 den Funktionsaufruf fflush() weg (siehe Programm 2.7, S. 26), sopassiert folgendes:

the lazy dogs.PP: Parent-PID: 4914 / Child-PID: 4915

Page 32: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

26 KAPITEL 2. PROZESSE UNTER UNIX

Programm 2.7: Programm 2.6 ohne fflush() (exec2.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4

5

6 void exec_test () /∗ execl version ∗/ {7 printf ( "The quick brown fox jumped over " );8 // fflush ( stdout );9 execl ( "/bin/echo", "echo", "the" , "lazy" , "dogs." ,

10 (char ∗) 0 );11 perror ( " execl − can’t exec /bin/echo" );12 }13

14 int main() {15 int pid ;16

17 pid = fork ();18 if ( pid < 0) {19 perror ( "fork () − can’t fork");20 exit (1);21 }22 if ( pid > 0)23 printf ( "PP: Parent−PID: %d / Child−PID: %d\n",24 ( int ) getpid (), pid );25 else /∗ pid == 0 ∗/ {26 printf ( "CP: Child−PID: %d\n", (int) getpid () );27 exec_test ();28 }29 exit (0);30 }

Erläuterung:

Wenn in eine Datei oder in eine Pipe geschrieben wird, erledigt printf() die Ausgabe gepuffert (inBlöcken zu 512 Bytes),

Bei der Ausgabe ans Terminal (s.o.) kann die Ausgabe auch dann bruchstückhaft (oder gar nicht)erscheinen, wenn Zeilenpufferung stattfindet. Normalerweise führt dies zu keinen Problemen;der letzte Puffer wird automatisch entleert, wenn der Prozess endet. In obigem Beispiel war derProzess noch nicht beendet, als der exec-Aufruf erfolgte – der im Benutzerdatensegment liegendePuffer wurde überlagert, bevor er entleert werden konnte.

Page 33: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.3. SYSTEM CALL “EXEC” 27

Programm 2.8: Argumentvektor übergeben (exec3.c)

1 # include < stdio .h>2 # include <unistd .h>3

4 int main() {5 char ∗ argv [4];6 argv [0] = "my−ls";7 argv [1] = "−?";8 argv [2] = " ∗. c" ;9 argv [3] = NULL;

10 execvp ( " ls " , argv );11 perror ( "exevp");12 return 1;13 }

Für das Programm 2.10 brauchen wir das Programm 2.9 zur Ausgabe des Environment:

Programm 2.9: Environment ausgeben (env.c)

1 # include < stdio .h>2

3 int main() {4 extern char ∗∗ environ ;5 while( ∗environ != NULL)6 printf ( "%s\n", ∗ environ ++);7 return 0;8 }

Programm 2.10: Umgebung ändern (exec4.c)

1 # include < stdio .h>2 # include <unistd .h>3

4 int main() {5 char ∗ envp [2];6 envp [0] = "x=1";7 envp [1] = NULL;8 execle ( " ./env", "env", NULL, envp);9 perror ( " execle " );

10 return 1;11 }

Übersetzen und Ausführen:

spatz$ gcc -Wall -o env env.cspatz$ gcc -Wall exec4.cspatz$ a.outx=1spatz$ echo $x # und jetzt?

spatz$

Page 34: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

28 KAPITEL 2. PROZESSE UNTER UNIX

Bei den “Nicht-exec?e()”-Varianten wird die Umgebung über environ übergeben (siehe Pro-gramm 2.11

Programm 2.11: Umgebung ändern bei execl() (exec5.c)

1 # include < stdio .h>2 # include <unistd .h>3

4 int main() {5 extern char ∗∗ environ ;6 char ∗ envp [2];7 envp [0] = "x=100";8 envp [1] = NULL;9 environ = envp ;

10 execl ( " ./env", "env", NULL);11 perror ( " execl " );12 return 1;13 }

Page 35: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.4. SYSTEM CALL “WAIT” 29

2.4 System Call “wait”

Syntax

#include <sys/types.h>#include <sys/wait.h>

pid_t wait( int * status );pid_t waitpid(pid_t pid, int *status, int options);

Beschreibung von wait() (zu waitpid() siehe Manuals)

• Falls der Aufrufer von wait() keinen Kind-Prozess erzeugt hat, kehrt wait() mit -1 zurück.

• Kehrt zurück, falls ein Kind bereits vorher terminierte

• Ansonsten blockiert wait() (wird vom Kernel suspendiert), bis einer der erzeugten Prozesseterminiert. In diesem Fall liefert wait() die PID des terminierten Kind-Prozesses. Über dasArgument von wait() erhält der Prozess Informationen über den terminierten Kind-Prozess(Abb. 2.3)

exit()

Termination durch

0x00Argument von exit()

Signalnummer0x00

core−Flag

Signal

Signalnummer 0x7FProzess

angehalten

(nur in Spezialfällen)

Abbildung 2.3: Information über Kind-Prozess

NB: Signal-Nummern sind größer 0!

Page 36: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

30 KAPITEL 2. PROZESSE UNTER UNIX

Ein erstes Beispiel mit wait():

Programm 2.12: Auf Kind-Prozess warten (wait1.c)

1 # include <sys/types .h>2 # include <sys/wait .h>3 # include <unistd .h>4 # include < stdlib .h>5 # include < stdio .h>6

7 int main() {8 pid_t pid ;9

10 if ( ( pid = fork () ) < 0 )11 perror ( "fork" ), exit (1);12

13 if ( pid == 0) { // Child14 printf ( "Child −− my pid is %d\n", (int) getpid());15 sleep (10);16 return 0;17 } else {18 // Parent19 int status , signal_nr , exit_st ;20 pid_t child_pid = wait(& status );21 if ( child_pid == −1)22 perror ( "wait" ), exit (2);23 signal_nr = status & 0x3f ;24 exit_st = ( status >> 8) & 0 xff ;25 printf ( "Parent −− child with pid %d finished\n", (int) child_pid );26 printf ( "SignalNr: %d || ExitSt: %d\n", signal_nr, exit_st );27 return 0;28 }29 }

Makros erleichtern die Dekodierung: (aus /usr/include/sys/wait.h)

• WIFEXITED(status): liefert TRUE, wenn der Kindprozess normal terminierte (freiwilligesexit() oder _exit() bzw. Rückkehr aus der main()-Funktion (siehe Programm 2.13, 31)

• WIFSIGNALED(status): liefert TRUE, wenn der Kindprozess durch ein nicht abgefange-nes Signal zur Termination kam

• WEXITSTATUS(status: liefert den durch exit() oder return in der main()-Funktion gesetztenExit-Status – kann nur benutzt werden, wenn WIFEXITED(status) TRUE lieferte

• WTERMSIG(status: liefert die Nummer des Signals, durch das der Kindprozess zur Ter-mination kam – kann nur benutzt werden, wenn WEXITSTATUS(status) TRUE lieferte

Page 37: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.4. SYSTEM CALL “WAIT” 31

Programm 2.13: Auf Kind-Prozess warten (forkandwait.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4 # include <sys/wait .h>5

6 int main() {7 pid_t child , pid ;8 int stat ;9

10 child = fork ();11 if ( child == −1) {12 perror ( "unable to fork" ); exit (1);13 }14 if ( child == 0) { /∗ child process ∗/15 srand ( getpid ()); char randval = rand ();16 printf ( "Child is going to exit with %d\n", randval & 0xFF);17 exit ( randval );18 }19

20 /∗ parent process ∗/21 pid = wait(& stat );22 if ( pid == child ) {23 if (WIFEXITED(stat)) {24 printf ( " exit code of child = %d\n", WEXITSTATUS(stat));25 } else {26 printf ( " child terminated abnormally\n");27 }28 } else {29 perror ( "wait" );30 }31 return 0;32 }

waitpid():

pid_t waitpid(pid_t pid, int * status, int options);

Parameter pid:

< −1 auf alle Kind-Prozesse, warten, deren Prozessgruppen-ID gleich dem Betrag von pid ist

−1 auf alle Kind-Prozesse warten (wie wait())

0 auf alle Kind-Prozesse warten, deren Prozessgruppen-ID gleich der der Aufrufers ist

> 0 auf den Kindprozess mit der angegebenen pid warten

Parameter options: bitweises ODER folgender Konstanten

• WNOHANG: nicht blockieren, falls kein Kindprozess terminierte

Page 38: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

32 KAPITEL 2. PROZESSE UNTER UNIX

• WUNTRACED: auch bei gestoppten Kindprozessen zurückkehren

Ansonsten wird der dritte Parameter einfach auf 0 gesetzt!

Beispiel mit waitpid()

Programm 2.14: Warten mit waitpid() (waitpid1.c)

1 # include <sys/types .h>2 # include <sys/wait .h>3 # include <unistd .h>4 # include < stdlib .h>5 # include < stdio .h>6

7 int main() {8 pid_t pid ;9

10 if ( ( pid = fork () ) < 0 )11 perror ( "fork" ), exit (1);12

13 if ( pid == 0) {14 printf ( "Child −− my pid is %d\n", (int) getpid());15 sleep (20);16 printf ( "Child −− going to terminate!\n");17 return 0;18 } else {19 // Parent20 pid_t child_pid ;21 int status ;22 if (( child_pid = waitpid ( pid , & status , WUNTRACED )) < 0)23 perror ( "wait" ), exit (2);24 // WUNTRACED: return for children which are stopped , and25 // whose status has not been reported26

27 printf ( "Parent −− child with pid %d finished, ", (int) child_pid );28 if WIFEXITED(status)29 printf ( "with exit value %d\n", WEXITSTATUS(status) );30 if WIFSIGNALED(status)31 printf ( "caused by signal %d\n", WTERMSIG(status) );32 if WIFSTOPPED(status)33 printf ( "not really , is stopped by %d and now dead!\n",34 WSTOPSIG(status) );35 sleep (50);36 printf ( "Parent −− going to terminate!\n");37 return 0;38 }39 }

Aufgaben des Kernel, wenn ein Prozess exit() aufruft:

• Wartet der Erzeuger-Prozess mit wait(), so wird er von der Termination des (eines) Kind-Prozesses benachrichtigt (wait() kehrt zurück). Im Argument von wait() wird der exit-Status(das Argument in exit()) geliefert, als Rückgabewert die PID des Kind-Prozesses.

Page 39: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.4. SYSTEM CALL “WAIT” 33

• Wartet der Erzeuger-Prozess nicht, so wird der Kind-Prozess als Zombie-Prozess markiert.Der Kernel gibt dessen Ressourcen frei, bewahrt aber den exit-Status (Beendigungsstatus)in der Prozesstabelle auf.

• Sind process ID, process group ID, terminal group ID des terminierenden Prozesses alle gleich,so wird das Signal SIGHUP an jeden Prozess mit gleicher process group ID gesandt.

Wenn der Erzeuger vor dem Kind-Prozess terminiert:

• Die PPID der Kind-Prozesse wird auf 1 gesetzt, der init-Prozess ”erbt” die ”Waisen” – inProgramm 2.15 wird ein Waisenkind erzeugt. Übersetzung und Ausführung liefert:

spatz$ gcc -Wall orphan.cspatz$ a.outHi, my parent is 5174spatz$ My parent is now 1

spatz$

Der init-Prozess terminiert nie, und wenn doch, so wäre es im Multiuser-Betrieb nicht mehrmöglich, dass sich weitere Benutzer anmelden. In 4.3BSD würde es zu einem automatischenreboot kommen.

Programm 2.15: Ein Prozess wird zum Waisenkind (orphan.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4

5 int main() {6 pid_t child ;7 child = fork ();8 if ( child == −1) {9 perror ( "fork" ); exit (1);

10 }11 if ( child == 0) {12 printf ( "Hi, my parent is %d\n", (int) getppid ());13 sleep (5);14 printf ( "My parent is now %d\n", (int) getppid ());15 }16 sleep (3);17 exit (0);18 }

Wenn ein Prozess mit exit() terminiert, so wird dem Erzeuger das Signal SIGCLD (SIGCHLD)zugeleitet (default-Reaktion: ignorieren). In System V ist es möglich, dass ein Prozess verhindert,dass seine Kind-Prozesse zu Zombies werden; dazu hat er nur den Aufruf

signal(SIGCLD, SIG_IGN)

Page 40: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

34 KAPITEL 2. PROZESSE UNTER UNIX

abzusetzen; der exit-Status der Kindprozesse wird bei deren Termination nicht weiter aufbe-wahrt.

Programm 2.16, S. 34, demonstriert die Erzeugung eines Zombies. Der neu erzeugte Prozess ver-abschiedet sich sofort mit exit(), während der übergeordnete Prozess mit Hilfe eines sleep()-Aufrufes sich für 60 Sekunden zur Ruhe legt. Während dieser Zeit verbleibt der Unterprozess imZombie-Status, wie das ps-Kommando belegt:

spatz$ genzombie &4792[1] 4791spatz$ ps -fUID PID PPID C STIME TTY TIME CMDswg 4784 4783 0 16:27 pts/3 00:00:00 bash -iswg 4791 4784 0 16:27 pts/3 00:00:00 genzombieswg 4792 4791 0 16:27 pts/3 00:00:00 [genzombie] <defunct>swg 4808 4784 0 16:28 pts/3 00:00:00 ps -fspatz$ ps -f # 1 Minute spaeterUID PID PPID C STIME TTY TIME CMDswg 4784 4783 0 16:27 pts/3 00:00:00 bash -iswg 4809 4784 0 16:28 pts/3 00:00:00 ps -f[1]+ Done genzombiespatz$

Programm 2.16: Erzeugen eines Zombie-Prozesses (genzombie.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4

5 int main() {6 pid_t child = fork ();7 if ( child == −1) {8 perror ( "fork" ); exit (1);9 }

10 if ( child == 0) exit (0);11 /∗ parent : ∗/12 printf ( "%d\n", child );13 sleep (60);14 exit (0);15 }

Page 41: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.5. N-DAMEN-PROBLEM MIT PROZESSEN 35

2.5 n-Damen-Problem mit Prozessen

Ein etwas trickreicheres Beispiel ist in Programmtext 2.18 zu finden. Beim n-Damen-Problem gehtes darum, n Damen auf einem n×n Schachbrett so unterzubringen, dass sie sich gegenseitig nichtbedrohen. Nach den üblichen Schachregeln bedroht ein Dame horizontal, vertikal und diagonal(siehe Abb. 2.4).

0 2 3 6 74 51

0

2

3

5

6

4

7

1

Abbildung 2.4: n-Damen-Problem: das Schachbrett

Die Bedrohungssituation kann leicht festgestellt werden:

Sei (i, j) eine mit einer Dame zu besetzende Position. Bedroht wären dadurch:

• Zeile i

• Spalte j

• alle Felder (k, l) mit

(1) k − l = i − j (Diagonale von links oben nach rechts unten)

(2) k + l = i + j (Diagonale von links unten nach rechts oben)

Meist wird das Problem im Backtracking-Verfahren gelöst, bei dem rekursiv alle Varianten durch-probiert werden (siehe Programm 2.17, S. 36).

Klappt es mit einem Weg nicht, werden die bereits gesetzten Damen wieder sukzessive abgebaut,bis sich andere bislang noch nicht getestete Varianten eröffnen (Abb. 2.5, S. 36).

Zunächst soll zum Verständnis die klassische Lösung betrachtet werden:

• Der gesamte Stand auf dem n × n Schachbrett (im folgenden n = 8) wird in einer Reiheglobaler Variablen verwaltet. Da es nur um die Feststellung geht, ob z.B. eine Zeile belegtist oder nicht, kann man Bitmaps verwenden – ist das i-te Bit auf 1, so ist die i-te Zeile /Spalte bedroht (siehe Abb. 2.6, S. 36).

Die Bedrohungssituation in den beiden Diagonalen kann ebenfalls in einer Integer-Zahl(Bitmap) geführt werden:

Page 42: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

36 KAPITEL 2. PROZESSE UNTER UNIX

Start

(0,3)(0,2)(0,1)(0,0)

(1,0) (1,1) (1,0) (1,1)

(2,0) (2,1)

Abbildung 2.5: n-Damen-Problem: der Lösungsbaum

1 0 0 1 1 0 int row, col;

Bit: 0 1 2 3 4 5

Abbildung 2.6: n-Damen-Problem: Zeilen-/Spaltenbedrohung

– Diagonalen von links unten nach rechts oben: Werte von 0 bis 2 × (n − 1) (Variablediags2)

– Diagonalen von links oben nach rechts unten: Werte von −(n − 1) bis +(n − 1), insPositive mit (n − 1) transformiert: Werte von 0 bis 2 × (n − 1) (Variable diags1)

Dies ergibt dann das Programm 2.17, S. 36. Anzumerken bleibt, dass von den 92 Lösungen bein = 8 sehr viele symmetrisch sind.

Programm 2.17: 8-Damen – klassisch (classicqueens.c)

1 # include < stdio .h>2

3 # define INSET(member,set) ((1<<(member))&set)4 # define INCL(set ,member) (( set ) |= 1<<(member))5 # define EXCL(set,member) (( set ) &= ( ~(1<<(member))) )6 # define size 87

8 struct positions { int row, col ; } pos [ size ]; /∗ queen positions ∗/9 int rows = 0; /∗ bitmaps of [0.. n−1] ∗/

10 int diags1 = 0; /∗ bitmap of [0..2∗( n−1)] used for row−col+n−1 ∗/11 int diags2 = 0; /∗ bitmap of [0..2∗( n−1)] used for row+col ∗/12 /∗ rows( i ) == 1 means: line i is threatened13 diags1 ( i−j+size−1) == 1 : diag through ( i , j ) from left up14 to right down is threatened15 diags1 ( i+j ) == 1 : diag through ( i , j ) from left down to16 right up is threatened17 ∗/18

19 void printpos () {20 int j = 0;21 printf ( " (next) positioning :\n" );22 printf ( "Row : Col\n");23 for ( j = 0; j < size ; j++ )24 printf ( "%3d : %3d\n", pos[j].row, pos [ j ]. col );

Page 43: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.5. N-DAMEN-PROBLEM MIT PROZESSEN 37

25 }26

27 void try ( int i ){28 int j ;29 for ( j = 0; j < size ; j ++){30 if ( !( INSET(j,rows )) &&31 !( INSET((i+j ), diags2 )) &&32 !( INSET((i−j+size−1), diags1 ))33 ) {34 pos [ j ]. row = j ; pos [ j ]. col = i ;35 INCL(rows, j ); INCL(diags2 , i+j ); INCL(diags1 , i−j+size−1);36 if ( i < size − 1 )37 try ( i +1);38 else39 printpos ();40 EXCL(rows,j ); EXCL(diags2, i+j ); EXCL(diags1, i−j+size−1);41 }42 }43 }44

45 int main (){46 try (0);47 return 0;48 }

Diese sequentielle Vorgehensweise lässt sich auch parallelisieren. Auf der ersten Reihe auf demSchachbrett gibt es n mögliche Positionen für die erste Dame. Entsprechend können n Prozesseerzeugt werden, die sich jeweils um einen Teilbaum (Abb. 2.5, S. 36) kümmern. Das Verfahrenkann auch weiter mit Parallelisierung fortgesetzt werden. Bei größeren Brettgrößen stößt diesjedoch rasch an die Grenze der Prozesstabelle, so dass die vorgestellte Lösung nicht wirklichskalierbar ist (und auf den Unterrichtsrechnern auf keinen Fall ausprobiert werden sollte).

Um das Problem zu vereinfachen, soll das Programm nur die Zahl der gefundenen Lösungenermitteln. Eine Ausgabe der gefundenen Lösungen ist nicht trivial, da hier eine Synchronisie-rung stattfinden müsste: wenn viele Prozesse konkurrierend versuchen, Ausgabe zu produzie-ren, würde dies zu einer wilden Textmischung führen. Über den Exit-Wert kann für nicht zugroße Werte von n bequem die Zahl der gefundenen Lösungen eines Teilbaumes zurückgeliefertwerden, so dass der übergeordnete Prozess die Gelegenheit hat, die Einzelresultate zusammen-zuzählen.

Zur Prozesslösung:

• Der gesamte Stand auf dem n × n Schachbrett (im folgenden wieder n = 8) wird in einerReihe lokaler Variablen von main() verwaltet.

• Abgesehen von col wird keine dieser Variablen im ersten Prozess modifiziert. Stattdessenerfolgen sämtliche Änderungen nur bei Unterprozessen, die dank der Magie des fork()-Aufrufes auf einer Kopie davon arbeiten.

• Die entscheidende Schleife beginnt in Zeile 21. Auf den Reihen 0 bis nofqueens −1 sinddie Damen bereits gesetzt. Als nächstes ist nun eine Dame auf Reihe nofqueens zu plazie-ren. Dazu geht die Schleife sämtliche Spaltenpositionen durch und überprüft in den Zeilen22 bis 25, ob die einzelnen Varianten im Konflikt zu den bereits plazierten Damen stehen.

Page 44: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

38 KAPITEL 2. PROZESSE UNTER UNIX

• Falls eine weitere Dame gefahrlos gesetzt werden kann, dann wird diese Variante einemProzess überlassen, der in Zeile 26 neu erzeugt wird. Dieser setzt die Dame innerhalb derDatenstruktur in den Zeilen 31 bis 35. Wenn damit size Damen gesetzt sind, ist eine Lö-sung gefunden worden und der Unterprozess signalisiert dies mit einem exit(1) in Zeile36. Ansonsten gibt es einen Sprung zur Schleife auf Zeile 21, die dann die nächste Zeile zubesetzen versucht.

• Die Anweisungen ab Zeile 41 werden von allen Prozessen ausgeführt mit Ausnahme der-jenigen, die entweder bei fork() scheitern oder selbst eine Lösung gefunden haben. DieAufgabe besteht hier darin, all die Zahlen der gefundenen Lösungen der einzelnen Unter-prozesse zu addieren. Dies erledigt die while-Schleife auf Zeile 43, die wait() solangeaufruft, bis -1 zurückgeliefert wird, d.h. alle Unterprozesse berücksichtigt worden sind.Die Exit-Werte werden in Zeile 47 einfach aufaddiert. Es findet nur eine zusätzliche Über-prüfung statt, ob der Unterprozess normal terminierte und selbst keine Probleme mit derErzeugung weiterer Unterprozesse hatte. So ein Problem wird mit einem Exit-Wert von 255in Zeile 28 signalisiert.

Dieses Beispiel ist nicht zur Nachahmung geeignet, da es sehr schnell am Mangel an zur Ver-fügung stehender Einträge in der Prozesstabelle scheitert. Dennoch ist der Ansatz brauchbar,wenn es darum geht, auf einer Mehrprozessor-Maschine die vorhandenen Ressourcen auszunut-zen. Allerdings macht es dann nicht Sinn, mehr Prozesse zu erzeugen als tatsächlich Prozessorenvorhanden sind. Ein pragmatischer Ansatz könnte es sein, die erste Stufe der Rekursion (alsohier die erste Reihe auf dem Schachbrett) zu parallelisieren und ansonsten sequentiell weiterzu-arbeiten. Elegant wird der Programmtext jedoch durch so einen Mischansatz nicht.

Ein weiterer Problempunkt ist die Rückgabe gefundener Lösungen. Dies geht entweder nur unterVerwendung von Dateien (leicht zu programmieren, jedoch nicht sehr effizient) oder der direktenKommunikation zwischen den Prozessen (effizienter, jedoch leider nicht einfach zu programmie-ren).

Für eine Dateiausgabe könnte der folgende Text zwischen Zeile 35 und 36 eingefügt werden:

if (col == size) {char file[32]; FILE * fp;sprintf(file,"%d",getpid());fp = fopen(file,"a");fprintf(fp,"Process %d:\n", getpid());for (i=0; i< size; i++)

fprintf(fp,"row %d / col %d\n", pos[i].row, pos[i].col);}

Weitere Minuspunkte ergeben sich aus der Verwendung einer goto-Anweisung und gemeinsa-mer Programmpfade nach dem fork().

Page 45: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.5. N-DAMEN-PROBLEM MIT PROZESSEN 39

Programm 2.18: Rekursion mit Unterprozessen (forkqueens.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <sys/wait .h>4 # include <unistd .h>5 # define INSET(member,set) ((1<<(member))&set)6 # define INCL(set ,member) (( set ) |= 1<<(member))7

8 int main() {9 const int size = 4; /∗ square size of the board ∗/

10 struct { int row, col ; } pos [ size ]; /∗ queen positions ∗/11 /∗ a queen on (row, col ) threatens a row, a column, and 2 diagonals ;12 rows and columns are characterized by their number (0.. n−1),13 the diagonals by row−col+n−1 and row+col,14 (n is a shorthand for the square size of the board )15 ∗/16 int rows = 0, cols = 0; /∗ bitmaps of [0.. n−1] ∗/17 int diags1 = 0; /∗ bitmap of [0..2∗( n−1)] used for row−col+n−1 ∗/18 int diags2 = 0; /∗ bitmap of [0..2∗( n−1)] used for row+col ∗/19 int row; int col = 0;20

21 setnextqueen : for (row = 0; row < size ; ++row) {22 if (INSET(row, rows )) continue;23 if (INSET(col , cols )) continue;24 if (INSET(row − col + size − 1, diags1 )) continue;25 if (INSET(row + col , diags2 )) continue;26 int child = fork ();27 if ( child == −1) {28 perror ( "fork" ); exit (255);29 }30 if ( child == 0) {31 INCL(rows, row ); INCL(cols , col );32 INCL(diags1 , row − col + size − 1);33 INCL(diags2 , row + col );34 pos [row ]. row = row; pos [row ]. col = col ;35 ++col ; /∗ set next queen in next col ∗/36 if ( col == size ) exit (1); /∗ solution found ∗/37 goto setnextqueen ;38 }39 }40

41 /∗ count the results of all children ∗/42 int stat ; pid_t child ; int nofsolutions = 0;43 while (( child = wait(& stat )) > 0) {44 if (! WIFEXITED(stat)) continue;45 int exitval = WEXITSTATUS(stat);46 if ( exitval == 255) exit (255);47 nofsolutions += exitval ;48 }49 exit ( nofsolutions );50 }

Page 46: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

40 KAPITEL 2. PROZESSE UNTER UNIX

2.6 Datei-Umlenkung

Viele Unix-Kommandos sind als Filter konzipiert: wenn nichts anderes über die Kommandozeileangegeben ist, so lesen sie von der Standardeingabe (0), schreiben auf die Standardausgabe (1)und setzen ihre Fehler- / Diagnoseausgaben auf Standarderror (2) – sofern dies jeweils Sinnmacht.

Programm 2.19 zeigt eine einfache Anwendung: wir setzen den Filedeskriptor 0 auf eine Dateiund starten dann ein Programm, das von 0 liest.

Übersetzung und Ausführung:

spatz$ gcc -Wall umlenkung1.cspatz$ a.out# include <stdio.h># include <fcntl.h># include <unistd.h>spatz$

Programm 2.19: Über 0 aus einer Datei lesen (umlenkung1.c)

1 # include < stdio .h>2 # include < fcntl .h>3 # include <unistd .h>4 # include < stdlib .h>5 # include <sys/wait .h>6

7 int main() {8 pid_t pid ;9 if ( ( pid = fork () ) < 0 )

10 perror ( "fork" ), exit (1);11 if ( pid == 0 ) {12 close (0);13 if ( open( "umlenkung1.c", O_RDONLY) < 0)14 perror ( "open"), exit (2);15 execlp ( "head", "head", "−3", NULL);16 perror ( "exec" ), exit (3);17 } else {18 wait (0);19 return 0;20 }21 }

Page 47: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.6. DATEI-UMLENKUNG 41

Das geht natürlich auch ausgabeseitig, wie Programm 2.20 (S. 41) zeigt:

spatz$ gcc -Wall umlenkung2.cspatz$ a.outEin kleiner Textauf 2 Zeilenspatz$ cat XXXEin kleiner Textauf 2 Zeilenspatz$

Programm 2.20: Über 1 in eine Datei schreiben (umlenkung2.c)

1 # include < stdio .h>2 # include < fcntl .h>3 # include <unistd .h>4 # include <sys/types .h>5 # include <sys/wait .h>6

7 int main() {8 pid_t pid ;9 if ( ( pid = fork () ) < 0 ) {

10 perror ( "fork" );11 return 1;12 }13 if ( pid == 0 ) {14 close (1);15 if ( open( "XXX", O_WRONLY | O_CREAT | O_TRUNC, 0666) < 0) {16 perror ( "open");17 return 2;18 }19 execlp ( " cat " , " cat " , NULL);20 perror ( "exec" );21 return 3;22 } else {23 wait (0);24 return 0;25 }26 }

Page 48: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

42 KAPITEL 2. PROZESSE UNTER UNIX

2.7 Eine kleine Shell (tinysh)

2.7.1 Allgemeines

Wenn man in der Shell ein Kommando startet (oder auf einer grafischen Oberfläche ein Pro-gramm durch “Anklicken” startet), so läuft die traditionelle Erzeugung eines Prozesses ab:

• Erzeuge einen neuen Prozess mit einem gegebenen Programmtext. (Die Kombination ausfork() und exec().

• Einrichtung der Umgebung für den neuen Prozess.

• Start des neuen Prozesses.

fork()

Umgebung

einrichten

exec()

Anwendung

exit()

Shell

wait()

Abbildung 2.7: Start einer Anwendung von der Shell

Die Trennung in fork() und exec() eröffnet die Möglichkeit, dass der Programmtext des über-geordneten Prozesses direkt im neu erzeugten Prozess die Umgebung vorbereitet, die dann beiexec() von dem gleichen Prozess mit dem neuen Programmtext vorgefunden wird. Wie Ab-bildung 2.7 zeigt, wird genau dies von den UNIX-Shells genutzt, um die Umgebung für An-wendungen einzurichten. Der übergeordnete Prozess der Shell wartet dann normalerweise mitwait() auf das Ende des untergeordneten Prozesses.

2.7.2 Anforderungen

• Jede Eingabezeile ist entweder leer oder enthält genau ein Kommando. Die einzelnen Worte(Token) sind durch Leerzeichen getrennt.

• Ein-/Ausgabe-Umlenkung von / auf Dateien ist möglich. Dazu dienen spezielle Worte:

– beginnt ein Wort mit einem “<”, so sind die darauf folgenden Zeichen der Name derEingabedatei;

– beginnt ein Wort mit “>”, so sind die darauf folgenden Zeichen der Name der Aus-gabedatei – sollte die Datei bereits existieren, wird sie auf Länge 0 verkürzt – existiertdie Datei noch nicht, wird sie angelegt;

Page 49: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.7. EINE KLEINE SHELL (TINYSH) 43

– beginnt ein Wort mit “>>”, so sind die darauf folgenden Zeichen der Name der Ausga-bedatei; das Schreiben erfolgt am Ende der Datei; existiert die Datei noch nicht, wirdsie angelegt;

• Es wird der Exit-Status des Programmes ausgegeben, falls dieser ungleich 0 ist. Wenn dasProgramm nicht gestartet werden kann, so soll der Exit-Status 255 ausgegeben werden.

• Es wird kein Signal-Handling durchgeführt ; es gibt keine Unterstützung von Hintergrund-kommandos, Pipelines, Builtin-Kommandos oder Shell-Variablen

2.7.3 Grundlegender Ablauf

Prompt ausgeben

Kommandozeile einlesen

readline()

I/O−Umlenkung vorbereiten

fassign()

Argumentvektor aufbauen

Warten

Kommando ausführen

Zerlegen in Worte

tokenizer()

Abbildung 2.8: Ablaufprinzip der tinysh

Page 50: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

44 KAPITEL 2. PROZESSE UNTER UNIX

2.7.4 Einschub: stralloc.h, libowfat-Bibliothek

Es ist zwar sehr einfach möglich – mittels calloc() bzw. strdup() – einen dynamischen Stringanzulegen, jedoch bieten die Funktionen strcpy(), strcat(), sprintf(), etc. keine Möglichkeit derÜberprüfung, ob die allozierte Länge nicht überschritten wird – was nicht zuletzt an der fehlen-den Größeninformation bei C-Arrays liegt.

Es bietet sich also die Verwendung der Bibliothek libowfat (http://www.fefe.de/libowfat/) an,die von Felix von Leitner nach einem Vorbild von Dan J. Bernstein nachprogrammiert und unterdie GPL (GNU General Public License) gestellt wurde. Diese Bibliothek ist bei uns lokal unter/usr/local/diet installiert.

Sie kann sehr leicht auch unter Linux installiert werden: herunterladen und entpacken, ma-ke aufrufen (GNUmakefile ist enthalten), im GNUmakefile den prefix anpassen (z.B. auch auf/usr/local/diet) und danach make install aufrufen.

Bei C-Arrays fehlt im Wesentlichen die Größeninformation. Dies behebt die folgende Datenstruk-tur für Strings in der stralloc-Bibliothek:

typedef struct stralloc {char∗ s ; /∗ Zeichen des Strings ( i .A. ohne ’\0’ am Ende) ∗/unsigned int len ; /∗ Laenge des Strings ( len <= a) ∗/unsigned int a ; /∗ Laenge des Arrays s ∗/

} stralloc ;

Hinter s verbirgt sich das Zeichenarray, das im Allgemeinen nicht nullterminiert ist. Dies hatden Vorteil, dass Strings auch das Null-Byte enthalten können. Die Komponente len enthält diemomentane Länge des Strings und a ist die momentante Länge des Arrays s; also gilt folglichimmer len ≤ a.

Da C lokale Variablen nicht automatisch initialisiert, müssen diese jeweils „von Hand“ initiali-siert werden:

stralloc sa = {0};

Man beachte, dass durch obige Initialisierung nicht nur sa . s, sondern auch sa . len und sa .a auf0 initialisiert werden.

Im Folgenden sind die wesentlichen Funktionen der stralloc-Bibliothek kurz beschrieben. DerRückgabewert dieser Funktionen ist bei Erfolg 1 und sonst 0.

int stralloc_ready(stralloc* sa,unsigned int len)Stellt sicher, dass genügend Platz für len Zeichen vorhanden ist.

int stralloc_readyplus(stralloc* sa,unsigned int len)Stellt sicher, dass genügend Platz für weitere len Zeichen vorhanden ist.

int stralloc_copys(stralloc* sa,const char* buf)Kopiert den nullterminierten String buf nach sa.

int stralloc_copy(stralloc* sa,const stralloc* sa2)Kopiert sa2 nach sa.

Page 51: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.7. EINE KLEINE SHELL (TINYSH) 45

int stralloc_cats(stralloc* sa,const char* buf)Hängt den nullterminierten String buf an sa an.

int stralloc_cat(stralloc* sa,stralloc* sa2)Hängt sa2 an sa an.

int stralloc_0(stralloc* sa)Hängt ein Null-Byte an sa an.

void stralloc_free(stralloc* sa)Gibt den von sa belegten Speicher wieder frei – also den Speicher von sa−>s (und nichtden Speicher für die stralloc-Struktur).

2.7.5 Einlesen der Kommandozeile – readline()

Damit können wir die Funktion readline() zum Einlesen der Kommandozeile implementie-ren. Programm 2.21 (S. 45) definiert die Schnittstelle, Programm 2.22 (S. 45) enthält die Imple-mentierung.

Programm 2.21: tinysh: readline() – Schnittstelle (tinysh/sareadline.h)

1 # ifndef SA_READLINE_H2 # define SA_READLINE_H3

4 # include < stdio .h>5 # include < stralloc .h>6 int readline (FILE∗ fp , stralloc ∗ sa );7

8 # endif

Programm 2.22: tinysh: readline() – Implementierung (tinysh/sareadline.c)

1 /∗2 ∗ Read a string of arbitrary length from a3 ∗ given file pointer . LF is accepted as terminator .4 ∗ 1 is returned in case of success , 0 in case of errors .5 ∗ afb 4/20036 ∗/7

8 # include < stralloc .h>9 # include < stdio .h>

10

11 int readline (FILE∗ fp , stralloc ∗ sa ) {12 if (! stralloc_copys ( sa , " " )) return 0; // FALSE13 for (;;) {14 if (! stralloc_readyplus ( sa , 1)) return 0; // FALSE15 if ( fread ( sa−>s + sa−>len, sizeof (char ), 1, fp ) <= 0)16 return 0; // FALSE17 if ( sa−>s[sa−>len] == ’\n’) break;18 ++sa−>len;19 }20 return 1; // TRUE21 }

Page 52: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

46 KAPITEL 2. PROZESSE UNTER UNIX

Mit der C-Funktionsize_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);für binären Stream-Input werden nmemb Datenelemente mit je size Bytes Länge vom Streamstream gelesen und in ptr abgelegt. Rückgabewert ist die Anzahl der gelesenen Elemente.

Ein kleines Testprogramm zeigt 2.23, S. 46.

Programm 2.23: tinysh: readline() – Test (tinysh/test-readline.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include " sareadline .h"4

5 void print ( stralloc ∗sa ) {6 int i ;7 for ( i = 0; i < sa−>len; i++)8 putchar ( sa−>s[i ]);9 }

10

11 int main() {12 stralloc line = {0};13 printf ( "Eingabe: " );14 readline ( stdin , &line );15 printf ( "Gelesen: " );16 print (& line );17 puts ( " " );18 exit (0);19 }

Übersetzung und Ausführung:

spatz$ gcc -Wall -I /usr/local/diet/include/ \-L /usr/local/diet/lib/ sareadline.c test-readline.c -lowfat

swg@spatz:~/skripte/soft2.05/2/progs/tinysh> a.outEingabe: aaa bbb >xxx >>zzzGelesen: aaa bbb >xxx >>zzzspatz$

Page 53: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.7. EINE KLEINE SHELL (TINYSH) 47

2.7.6 Zerlegen in Tokens – tokenizer()

Der Wortzerleger tokenizer() arbeitet auf einer als stralloc repräsentierten Zeichenketteund generiert eine Liste vom Typ strlist, die auf die einzelnen Wörter verweist. Dabei wirdein Umkopieren der Wörter vermieden. Stattdessen verweisen die Zeiger in die originale Zei-chenkette und die Leerzeichen werden in Nullbytes verwandelt, um als Begrenzer dienen zukönnen. Abbildung 2.9 zeigt die resultierende Datenstruktur an einem Beispiel.

s ’p’’c’ 0 ’x’ 0 ’y’ 0

input

tokens

len

a

7

30

list

len

allocated 8

4

0

Abbildung 2.9: Datenstruktur zur Wortzerlegung

Die Datenstruktur für tokens ist ähnlich der in der stralloc-Bibliothek aufgebaut, die Struktur wiedie notwendigen Funktionen sind in 2.24, S. 48, definiert und in 2.25, S. 49 implementiert.

Page 54: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

48 KAPITEL 2. PROZESSE UNTER UNIX

Programm 2.24: tinysh: Datenstruktur für den tokenizer() – Schnittstelle (tinysh/strlist.h)

1 /∗2 ∗ Data structure for dynamic string lists that works3 ∗ similar to the stralloc library .4 ∗ Return values : 1 if successful , 0 in case of failures .5 ∗ afb 4/20036 ∗/7

8 # ifndef STRLIST_H9 # define STRLIST_H

10

11 typedef struct strlist {12 char∗∗ list ;13 unsigned int len ; /∗ # of strings in list ∗/14 unsigned int allocated ; /∗ allocated length for list ∗/15 } strlist ;16

17 /∗ assure that there is at least room for len list entries ∗/18 int strlist_ready ( strlist ∗ list , unsigned int len );19

20 /∗ assure that there is room for len additional list entries ∗/21 int strlist_readyplus ( strlist ∗ list , unsigned int len );22

23 /∗ truncate the list to zero length ∗/24 int strlist_clear ( strlist ∗ list );25

26 /∗ append the string pointer to the list ∗/27 int strlist_push ( strlist ∗ list , char∗ string );28 # define strlist_push0 ( list ) strlist_push (( list ), 0)29

30 /∗ free the strlist data structure but not the strings ∗/31 int strlist_free ( strlist ∗ list );32

33 # endif

Page 55: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.7. EINE KLEINE SHELL (TINYSH) 49

Programm 2.25: tinysh: Datenstruktur für den tokenizer() – Implementierung (tinysh/strlist.c)

1 /∗2 ∗ Data structure for dynamic string lists that works3 ∗ similar to the stralloc library .4 ∗ Return values : 1 if successful , 0 in case of failures .5 ∗ afb 4/20036 ∗/7 # include < stdlib .h>8 # include " strlist .h"9

10 /∗ assure that there is at least room for len list entries ∗/11 int strlist_ready ( strlist ∗ list , unsigned int len ) {12 if ( list −> allocated < len ) {13 unsigned int wanted = len + ( len>>3) + 8;14 char∗∗ newlist = (char∗∗) realloc ( list −> list ,15 sizeof (char∗) ∗ wanted );16 if ( newlist == 0) return 0;17 list −> list = newlist ;18 list −> allocated = wanted ;19 }20 return 1;21 }22

23 /∗ assure that there is room for len additional list entries ∗/24 int strlist_readyplus ( strlist ∗ list , unsigned int len ) {25 return strlist_ready ( list , list −>len + len );26 }27

28 /∗ truncate the list to zero length ∗/29 int strlist_clear ( strlist ∗ list ) {30 list −>len = 0;31 return 1;32 }33

34 /∗ append the string pointer to the list ∗/35 int strlist_push ( strlist ∗ list , char∗ string ) {36 if (! strlist_ready ( list , list −>len + 1)) return 0;37 list −> list [ list −>len++] = string ;38 return 1;39 }40

41 /∗ free the strlist data structure but not the strings ∗/42 int strlist_free ( strlist ∗ list ) {43 free ( list −> list ); list −> list = 0;44 list −> allocated = 0;45 list −>len = 0;46 return 1;47 }

Page 56: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

50 KAPITEL 2. PROZESSE UNTER UNIX

Das Programm für den tokenizer() hierzu besteht wieder aus Headerfile (2.26, S. 50) undImplementierung (2.27, S. 50)

Programm 2.26: tinysh: tokenizer() – Schnittstelle (tinysh/tokenizer.h)

1 # ifndef TOKENIZER_H2

3 # define TOKENIZER_H4 # include < stralloc .h>5 # include " strlist .h"6 int tokenizer ( stralloc ∗ input , strlist ∗ tokens );7

8 # endif

Programm 2.27: tinysh: tokenizer() – Implementierung (tinysh/tokenizer.c)

1 /∗2 ∗ Simple tokenizer : Take a 0−terminated stralloc object and return a3 ∗ list of pointers in tokens that point to the individual tokens .4 ∗ Whitespace is taken as token− separator and all whitespaces within5 ∗ the input are replaced by null bytes .6 ∗ afb 4/20037 ∗/8

9 # include <ctype .h>10 # include < stdlib .h>11 # include < stralloc .h>12 # include " strlist .h"13 # include " tokenizer .h"14

15 int tokenizer ( stralloc ∗ input , strlist ∗ tokens ) {16 char∗ cp ;17 int white = 1;18

19 strlist_clear ( tokens );20 for (cp = input−>s; ∗cp && cp < input−>s + input−>len; ++cp) {21 if ( isspace (∗cp )) {22 ∗cp = ’\0’ ; white = 1; continue;23 }24 if (! white ) continue;25 white = 0;26 if (! strlist_push ( tokens , cp )) return 0;27 }28 return 1;29 }

Page 57: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.7. EINE KLEINE SHELL (TINYSH) 51

Ein kleines Test-Programm zeigt 2.28, S. 51.

Programm 2.28: tinysh: tokenizer() – Test (tinysh/test-tokenizer.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include " sareadline .h"4 # include " strlist .h"5 # include " tokenizer .h"6

7 int main() {8 stralloc line = {0};9 printf ( "Eingabe: " );

10 readline ( stdin , &line );11

12 strlist tokens = {0};13 stralloc_0 (& line ); /∗ required by tokenizer () ∗/14 if (! tokenizer (& line , &tokens )) printf ( "ERROR\n");15 if ( tokens . len == 0) printf ( "No Tokens\n");16 for ( int i = 0; i < tokens . len ; ++i)17 printf ( "Token %d: %s \n", i,tokens. list [ i ]);18 exit (0);19 }

Übersetzung und Ausführung:

spatz$ gcc -Wall -std=c99 -I /usr/local/di et/include/ \-L /usr/local/diet/lib/ \sareadline.c strlist.c tokenizer.c test-tokenizer.c -lowfat

spatz$ a.outEingabe: aaa >bbb >> >>xxx " "Token 0: aaaToken 1: >bbbToken 2: >>Token 3: >>xxxToken 4: "Token 5: "spatz$

Page 58: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

52 KAPITEL 2. PROZESSE UNTER UNIX

2.7.7 Hauptprogramm unserer Shell

Programm 2.29: tinysh: Hauptprogramm (tinysh/tinysh.c)

1 # include < fcntl .h>2 # include < stdio .h>3 # include < stdlib .h>4 # include <unistd .h>5 # include <sys/wait .h>6 # include " sareadline .h"7 # include " strlist .h"8 # include " tokenizer .h"9

10 /∗11 ∗ assign an opened file with the given flags and mode to fd12 ∗/13 void fassign ( int fd , char∗ path , int oflags , mode_t mode) {14 int newfd = open( path , oflags , mode);15 if (newfd < 0) {16 perror ( path ); exit (255);17 }18 if (dup2(newfd , fd ) < 0) {19 perror ( "dup2"); exit (255);20 }21 close (newfd );22 }23

24 int main() {25 stralloc line = {0};26 while ( printf ( "%% "), readline ( stdin , &line )) {27 strlist tokens = {0};28 stralloc_0 (& line ); /∗ required by tokenizer () ∗/29 if (! tokenizer (& line , &tokens )) break;30 if ( tokens . len == 0) continue;31 pid_t child = fork ();32 if ( child == −1) {33 perror ( "fork" ); continue;34 }35 if ( child == 0) {36 strlist argv = {0}; /∗ list of arguments ∗/37 char∗ cmdname = 0; /∗ first argument ∗/38 char∗ path ; /∗ of output files ∗/39 int oflags ;40

41 for ( int i = 0; i < tokens . len ; ++i) {42 switch ( tokens . list [ i ][0]) {43 case ’<’ :44 fassign (0, &tokens . list [ i ][1], O_RDONLY, 0);45 break;46 case ’>’ :47 path = &tokens . list [ i ][1];48 oflags = O_WRONLY|O_CREAT;49 if (∗path == ’>’) {50 ++path; oflags |= O_APPEND;

Page 59: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.7. EINE KLEINE SHELL (TINYSH) 53

51 } else {52 oflags |= O_TRUNC;53 }54 fassign (1, path , oflags , 0666);55 break;56 default :57 strlist_push (&argv , tokens . list [ i ]);58 if (cmdname == 0) cmdname = tokens . list [ i ];59 }60 }61 if (cmdname == 0) exit (0);62 strlist_push0 (&argv );63 execvp (cmdname, argv . list );64 perror (cmdname);65 exit (255);66 }67

68 /∗ wait for termination of child ∗/69 int stat ;70 pid_t pid = wait(& stat );71 if ( pid == child ) {72 if (WIFEXITED(stat)) {73 int code = WEXITSTATUS(stat);74 if ( code && code != 255) {75 printf ( "terminated with exit code %d\n", code);76 }77 } else {78 printf ( "terminated abnormally\n");79 }80 } else {81 perror ( "wait" );82 }83 }84 }

Page 60: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

54 KAPITEL 2. PROZESSE UNTER UNIX

Dazu noch ein kleines Makefile:

CC = gccCFLAGS = -Wall -std=c99 -I /usr/local/diet/include/ \

-L /usr/local/diet/lib/OBJ = tinysh.o sareadline.o strlist.o tokenizer.o

tinysh: $(OBJ)$(CC) $(CFLAGS) -o tinysh $(OBJ) -lowfat

tinysh.o: tinysh.c sareadline.h strlist.h tokenizer.h$(CC) $(CFLAGS) -c tinysh.c

sareadline.o: sareadline.h sareadline.c$(CC) $(CFLAGS) -c sareadline.c

strlist.o: strlist.h strlist.c$(CC) $(CFLAGS) -c strlist.c

tokenizer.o: tokenizer.h tokenizer.c strlist.h$(CC) $(CFLAGS) -c tokenizer.c

.PHONY: clean realclean

clean:rm -f $(OBJ) core

realclean: cleanrm -f tinysh

Stattdessen hätten wir auch das Makefile-Template (siehe Titelseite) verwenden können!

2.8 Bootstrapping – klassisch

• Henne oder Ei?

Alle Aktionen in einem UNIX System erfolgen durch Prozesse. Für alle Prozesse bestehteine Erzeuger-Kindprozess-Beziehung.

Bleibt die Frage, woher kommt der erste Prozess, die Wurzel dieses Prozessbaums?

• Bootstrapping

Das Starten eines Betriebssystems heißt Bootstrapping.

Für UNIX fallen darunter alle Aktionen vom Stromeinschalten bis zum Erreichen des sta-bilen Zustands in dem Prozess 1 läuft. Anschließend können alle weiteren Aktionen mitProzessen und nach den durch die System Calls festgelegten Regeln erfolgen.

• Phase 0 Hardware

Das Einschalten der Stromversorgung bewirkt verschiedene Aktionen, die sehr von derHardware und der Architektur des Rechners abhängen. Alle haben aber das gleiche Ziel:Selbsttest und Grundinitialisierung der einzelnen Komponenten. Die Hardware befindetsich danach in einem definierten Startzustand.

Page 61: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.8. BOOTSTRAPPING – KLASSISCH 55

• Phase 1: First Stage Boot

Die Hardware verfügt über einige “festeingebaute” Routinen, die in der Lage sind, einenBoot-Block von der Platte zu lesen und zur Ausführung zu bringen.

Der Boot-Block enthält ein loader - Programm. Dieses Programm muss mit der Consolekommunizieren können und bereits über das UNIX File System Bescheid wissen. Falls derPlatz im Boot-Block für ein Programm mit diesen Fähigkeiten nicht ausreicht, muss das mi-nimale Boot Loader Programm ein weiteres, größeres Loader Programm laden und ausführenkönnen.

• Phase 2: Second Stage Boot

Aufgabe des loader - Programms ist es, den Kernel, meist die Datei /unix, von der Platte inden Hauptspeicher zu laden und zu starten.

Anschließend beginnt UNIX zu “leben”.

Loader Programme sind meist trickreiche Assemblerprogramme. Im Boot-Block könnennur wenige Anweisungen untergebracht werden, die müssen aber komplexe, Hardware-nahe Aufgaben erledigen.

• Phase 3: Standalone

Sobald der UNIX Kernel gestartet wurde, übernimmt er die gesamte Kontrolle über den Rechner.Der Kernel kann jetzt alles weitere aus eigener Kraft erledigen (Standalone).

In seiner Startphase☞ setzt er die Interruptvektoren im low mem,☞ initialisiert die Speicherverwaltungs-Hardware,☞ baut seine Tabellen (Prozess-, Open File-, Inode-, usw.) auf☞ führt eine mount-ähnliche Operation für das root File System aus...

Nun fehlt noch etwas ”Magie”, um den ersten Prozess zu erschaffen. Die Prozessverwaltung ge-neriert in ihrer Startphase den Prozess 0 ”von Hand”. Dieser Prozess, meist swapper genannt,besteht nur aus dem System-Daten Bereich: Slot 0 in der Prozesstabelle plus per process data re-gion. Er besitzt keinen Text oder User-Daten Bereich. Dafür existiert er während der gesamtenSystemlaufzeit und ist für das Scheduling zuständig. Er benötigt hierfür nur Instruktionen undDaten aus dem Kernel-Adressraum.

Da Prozess 0 eigentlich kein echter Prozess ist, erschafft der Kernel auch Prozess 1 manuell.Soweit als möglich benutzt oder imitiert der Kernel hier bereits den fork Mechanismus, um denProzess 1 vom Prozess 0 abzuspalten. Prozess 1 erhält jedoch einen ganz regulären Context undkann anschließend vom Scheduler als normaler Prozess zur Ausführung gebracht werden. Sein“hand crafted” Text Bereich enthält einzig die Anweisung

execl( ’’/etc/init’’, ’’init’’, 0 )

Nach dem exec System Call läuft im Context des Prozesses 1 das Programm /etc/init und Prozess1 heißt nun init - Prozess.

Page 62: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

56 KAPITEL 2. PROZESSE UNTER UNIX

2.9 Der init-Prozess

• user Id 0

• Operationen:

1. 4.3BSDinit führt das Shell-Skript /etc/rc aus. Dabei werden u.a. einige Daemon-Prozesse ge-startet. init entnimmt der Datei /etc/ttys, welche Terminals für den Multiuser-Betriebaktiviert werden müssen.

2. System Vinit liest die Datei /etc/inittab, in der spezifiziert ist, was zu tun ist. Zum normalenMultiuser-Betrieb wird die Datei /etc/rc ausgeführt; dieses Programm startet die mei-sten Daemon-Prozesse. Nach dessen Beendigung werden wie in /etc/inittab definiertdie angeschlossenen Terminals aktiviert. (mehr dazu in einem Administrator ReferenceManual).

login

/bin/sh

fork()fork()

fork()

init init init

exec()exec()

gettygetty getty

init

wartet auf

login−NameexecArg.: Login−Name

wartet auf Passwort

login

exec()

exec()

exec()

process ID = 1

Abbildung 2.10: Der Init-Prozess

• getty setzt die Übertragungsgeschwindigkeit des Terminals, gibt irgendeine Begrüßungs-formel aus und wartet auf Eingabe eines login-Namens.

• Login-Name eingegeben → exec(/bin/login)

• login sucht den eingegebenen Login-Namen in der Passwortdatei und fordert die Eingabeeines Passwortes.Alle bis hier ausgeführten Programme (init, getty, login) laufen als Prozesse mit user ID undeffective user ID 0 (superuser) - mit dem System Call exec ändert sich die process ID nicht.Danach wird das current working directory auf den entsprechenden Eintrag für das login di-rectory aus der Passwortdatei gesetzt.

Page 63: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

2.9. DER INIT-PROZESS 57

Group ID und user ID (in dieser Reihenfolge) werden via setgid und setuid wie in der Passwort-datei definiert gesetzt.Über exec wird das in der Passwortdatei spezifizierte Programm gestartet (falls keine An-gabe: /bin/sh).

Falls der sich anmeldende Benutzer nicht der Superuser ist (login-Name meist root):

• setgid und setuid reduzieren Prozessprivilegien

• beide System Calls sind dem Superuser vorbehalten (daher diese Reihenfolge)

Page 64: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

58 KAPITEL 2. PROZESSE UNTER UNIX

Page 65: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Kapitel 3

Signale

3.1 Einführung

Signale werden für vielfältige Zwecke eingesetzt. Sie können verwendet werden,

• um den normalen Ablauf eines Prozesses für einen wichtigen Hinweis zu unterbrechen,

• um die Terminierung eines Prozesses zu erbitten oder zu erzwingen und

• um schwerwiegende Fehler bei der Ausführung zu behandeln wie z.B. den Verweis durcheinen invaliden Zeiger.

Signale ersetzen keine Interprozesskommunikation, da sie fast keine Informationen mit sich füh-ren. In Abhängigkeit von der jeweiligen Systemumgebung gibt es mehr oder weniger fest defi-nierte Signale, die über natürliche Zahlen identifiziert werden.

Der ISO-Standard 9899-1999 für die Programmiersprache C definiert eine einfache und damitrecht portable Schnittstelle für die Behandlung von Signalen. Hier gibt es neben der Signalnum-mer selbst keine weiteren Informationen. Der IEEE Standard 1003.1 (POSIX) bietet eine Ober-menge der Schnittstelle des ISO-Standards an, bei der wenige zusätzliche Informationen (wiez.B. die Angabe des invaliden Zeigers) dabei sein können und der insbesondere eine sehr vielfeinere Kontrolle der Signalbehandlung erlaubt.

Signale können von verschiedenen Parteien bzw. unter unterschiedlichen Bedingungen ausgelöstwerden:

1. System Call kill(): damit kann ein Prozess sich oder einem anderen Prozess ein Signal sen-den (führt nicht notwendig zur Termination)

int kill(int pid, int sig);

Der sendende Prozess muss entweder ein superuser-Prozess sein oder der sendende undempfangende Prozess müssen dieselbe effektive userId haben.

59

Page 66: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

60 KAPITEL 3. SIGNALE

• Ist das pid-Argument 0, so geht das Signal an alle Prozesse in der Prozess-Gruppe desSenders

• Ist das pid-Argument -1 und der Sender nicht der Superuser, so geht das Signal an alleProzesse, deren real user ID gleich der effective user ID des Senders ist

• Ist das pid-Argument -1 und der Sender der Superuser, so geht das Signal an alleProzesse ausgenommen die Superuser-Prozesse (i.a. PID’s 0 oder 1)

• Ist das pid-Argument negativ, aber ungleich -1, so geht das Signal an alle Prozesse,deren Prozess-Gruppen-Nummer gleich dem Absolutbetrag von pid ist

• Ist das sig-Argument 0, so wird nur eine Fehler-Prüfung durchgeführt, aber kein Si-gnal geschickt (z.B. zur Prüfung der Gültigkeit des pid-Arguments)

2. Kommando kill: nutzt den System Call kill() (s.o. und man)

3. Tastatureingaben, z.B.:

• ctrl-c (oder delete) beendet einen im Vordergrund laufenden Prozess (→ SIGINT),genauer alle Prozesse in der Kontrollgruppe dieses Terminals - vom Kernel verschickt

• ctrl-\ erzeugt SIGQUIT

• ctrl-z hält einen im Vordergrund laufenden Prozess an(→ SIGSTOP)kann mit SIGCONT fortgesetzt werden

4. Hardware-Bedingungen, z.B.:Gleitpunkt-Arithmetik-Fehler (Floating Point Exception) (SIGFPE)Adressraum-Verletzungen (Segmentation Violation) (SIGSEGV)

5. Software-Bedingungen, z.B.:Schreiben in eine Pipe, an der kein Prozess zum Lesen ”hängt” (SIGPIPE).

Typisch für die Auslösung durch das Betriebssystem ist die Terminalschnittstelle unter UNIX.Diese wurde ursprünglich für ASCII-Terminals mit serieller Schnittstelle entwickelt, die nur fol-gende Eingabemöglichkeiten anboten:

• Einzelne ASCII-Zeichen, jeweils ein Byte (zusammen mit etwas Extra-Kodierung wie Prüf-und Stop-Bits).

• Ein BREAK, das als spezielles Signal repräsentiert wird, das länger als die Kodierung fürein ASCII-Zeichen währt.

• Ein HANGUP, bei dem ein Signal wegfällt, das zuvor die Existenz der Leitung bestätigthat. Dies benötigt einen weiteren Draht in der seriellen Leitung.

Diese Eingaben werden auf der Seite des Betriebssystems vom Terminal-Treiber bearbeitet, derin Abhängigkeit von den getroffenen Einstellungen

• die eingegebenen Zeichen puffert und das Editieren der Eingabe ermöglicht (beispielsweisemittels BACKSPACE, CTRL-u und CTRL-w) und

• bei besonderen Eingaben Signale an alle Prozesse schickt, die mit diesem Terminal verbun-den sind.

Page 67: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.2. SIGNALBEHANDLER 61

Ziel war es, dass im Normalfall ein BREAK zu dem Abbruch oder zumindest der Unterbrechungder gerade laufenden Anwendung führt. Und ein HANGUP sollte zu dem Abbruch der gesam-ten Sitzung führen, da bei einem Wegfall der Leitung keine Möglichkeit eines regulären Abmel-dens besteht.

Heute sind serielle Terminals rar geworden, aber das Konzept wurde dennoch beibehalten. Zwi-schen einem virtuellen Terminal (beispielsweise einem xterm) und den Prozessen, die zur zu-gehörigen Sitzung gehören, ist ein sogenanntes Pseudo-Terminal im Betriebssystem geschaltet,das der Sitzung die Verwendung eines klassischen Terminals vorspielt. Da es BREAK in die-sem Umfeld nicht mehr gibt, wird es durch ein beliebiges Zeichen ersetzt wie beispielswei-se CTRL-c. Wenn das virtuelle Terminal wegfällt (z.B. durch eine gewaltsame Beendigung derxterm-Anwendung), dann gibt es weiterhin ein HANGUP für die Sitzung.

Auf fast alle Signale können Prozesse, die sie erhalten, auf dreierlei Weise reagieren:

• Voreinstellung: Terminierung des Prozesses.

• Ignorieren.

• Bearbeitung durch einen Signalbehandler.

Es mag harsch erscheinen, dass die Voreinstellung zur Terminierung eines Prozesses führt. Abergenau dies führt bei normalen Anwendungen genau zu den gewünschten Effekten wie Abbruchdes laufenden Programms bei BREAK (die Shell ignoriert das Signal) und Abbau der Sitzung beiHANGUP. Wenn ein Prozess diese Signale ignoriert, sollte es genau wissen, was es tut, da derNutzer auf diese Weise eine wichtige Kontrollmöglichkeit seiner Sitzung verliert. Sinnvoll ist esnatürlich, eine Anwendung mit einem Signalbehandler zu ergänzen, wenn dadurch Datenverlu-ste bei einer Terminierung vermieden werden können.

3.2 Signalbehandler

3.3 Reaktion auf Signale: signal()

• Ein Prozess kann eine Funktion definieren, die bei Eintreten eines bestimmten Signals aus-geführt werden soll (signal handler).

• Signale (außer SIGKILL und SIGSTOP) können ignoriert werden.

System Call signal:

#include <signal.h>

int (*signal (int sig, void (*func) (int))) (int);/* ANSI C signal handling */

Page 68: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

62 KAPITEL 3. SIGNALE

Also:

• signal ist eine Funktion, die einen Zeiger auf eine Funktion zurückliefert, die selbst eine intzurückliefert (die bisherige Reaktion auf das Signal oder bei Fehler SIG_ERR)

• Das erste Argument ist die Signalnummer (Makro aus signal.h), das zweite (func) ist Zeigerauf eine Funktion, die void liefert (die neue Reaktion auf das Signal)

Wegen der detaillierteren Reaktionsmöglichkeit und einiger anderen Fußangeln ist die Funktionsigaction() vorzuziehen!

Programm 3.1 demonstriert die Behandlung des Signals SIGINT. Als Signalbehandler operiertdie Funktion signal_handler(). Signalbehandler erhalten als Argument eine ganze Zahl,worüber sie die Nummer des Signals erhalten, das sie gerade bearbeiten. Einen Rückgabewertgibt es nicht.

Programm 3.1: Behandlung eines SIGINT-Signals (sigint.c)

1 # include < signal .h>2 # include < stdio .h>3 # include < stdlib .h>4

5 volatile sig_atomic_t signal_caught = 0;6

7 void signal_handler ( int signal ) {8 signal_caught = signal ;9 }

10

11 int main() {12 if ( signal (SIGINT, signal_handler ) == SIG_ERR) {13 perror ( "unable to setup signal handler for SIGINT");14 exit (1);15 }16 printf ( "Try to send a SIGINT signal!\n");17 int counter = 0;18 while (! signal_caught ) {19 for ( int i = 0; i < counter ; ++i)20 ;21 ++counter ;22 }23 printf ( "Got signal %d after %d steps!\n", signal_caught , counter );24 }

Erläuterungen zu Programm 3.1 (S. 62):

• Der Signalbehandler signal_handler() setzt nur eine globale Variable auf den Wert deserhaltenen Signals (Zeile 8).

• Die Verwendung der Speicherklasse volatile und des Datentyps sig_atomic_t wirdspäter diskutiert.

Page 69: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.3. REAKTION AUF SIGNALE: SIGNAL() 63

• main() richtet auf Zeile 12 mit der Funktion signal() für das Signal mit der NummerSIGINT die Funktion signal_handler() als Reaktion auf dieses Signal ein.

Alternativen zu diesem Funktionszeiger wären SIG_DFL für die Prozessterminierung oderSIG_IGN für das Ignorieren.

Im Erfolgsfalle liefert signal() die frühere Einstellung zurück, ansonsten SIG_ERR.

• Die Schleife in Zeile 18 bricht ab, wenn sich der Wert von signal_caught ändert. Dieskann in diesem Beispiel nur durch den Signalbehandler passieren.

• Wenn dies geschieht, wird auf Zeile 22 die Nummer des eingetroffenen Signals ausgegebenund das Programm beendet.

So könnte ein Aufruf dieses Programms aussehen, wenn das Versenden des Signals SIGINTdurch den Terminaltreiber durch die Eingabe von CTRL-c recht schnell geschieht:

spatz$ sigintTry to send a SIGINT signal!^CGot signal 2 after 29074 steps!spatz$

Die 2 ist dabei die Nummer des Signals SIGINT:

thales$ grep SIGINT /usr/include/sys/iso/signal_iso.h#define SIGINT 2 /* interrupt (rubout) */thales$

Zur Speicherklasse volatile:

Leider sind mit der Bearbeitung von Signalen große Probleme verbunden. Wenn ein optimieren-der Übersetzer den Programmtext 3.1 (S. 62) analysiert, könnten folgende Punkte auffallen:

• Die Schleife in den Zeilen 18 bis 21 ruft keine externen Funktionen auf.

• Innerhalb der Schleife wird signal_caught nirgends verändert.

Daraus könnte vom Übersetzer der Schluss gezogen werden, dass die Schleifenbedingung nurzu Beginn einmal überprüft werden muss. Findet der Eintritt in die Schleife statt, könnte derweitere Test der Bedingung ersatzlos wegfallen. Analysen wie diese sind für heutige optimieren-de Übersetzer Pflicht, um guten Maschinen-Code erzeugen zu können. Es wäre also fatal, wenndarauf nur wegen der Existenz von asynchron aufgerufenen Signalbehandlern verzichtet werdenwürde.

Um beides zu haben, die fortgeschrittenen Optimierungstechniken und die Möglichkeit, Varia-blen innerhalb von Signalbehandlern setzen zu können, wurde in C die Speicherklasse volatileeingeführt. Damit lassen sich Variablen kennzeichnen, deren Wert sich jederzeit ändern kann —selbst dann, wenn dies aus dem vorliegenden Programmtext nicht ersichtlich ist. Entsprechendgilt dann auch in C, dass alle anderen Variablen, die nicht als volatile klassifiziert sind, sich nichtdurch “magische” Effekte verändern dürfen.

Daraus folgt, dass korrekte Signalbehandler in ihren Möglichkeiten stark eingeschränkt sind. Soist es nur zulässig,

Page 70: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

64 KAPITEL 3. SIGNALE

• lokale Variablen zu verwenden,

• mit volatile deklarierte Variablen zu benutzen und

• Funktionen aufzurufen, die sich an die gleichen Spielregeln halten.

Letzteres schließt insbesondere die Verwendung von Ein- und Ausgabe innerhalb eines Signal-behandlers aus. Der ISO-Standard 9899-1999 nennt nur abort(), _Exit()1 und signal() alszulässige Bibliotheksfunktionen. Beim POSIX-Standard werden noch zahlreiche weitere System-aufrufe genannt. Auf den Manualseiten von Solaris wird dies dokumentiert durch die Angabe“Async-Signal-Safe” bei “MT-Level”2

Zum Datentyp sig_atomic_t:

• ganzzahliger Typ

• Lese- und Schreiboperationen laufen garantiert atomar ab

• Datentyp, den der verwendete Prozessor mit einer einzigen ununterbrechbaren Instrukti-on laden oder speichern kann

Bei allen anderen Datentypen ist es nicht ausgeschlossen, dass mitten in einer Zuweisung einasynchrones Signal eintreffen kann und der zugehörige Signalbehandler dann eine partiell mo-difizierte Variable vorfindet.

1_Exit() unterlässt im Vergleich zu exit() sämtliche Aufräumarbeiten.2“MT” steht hier für Multi-Threading, da dabei ähnliche Probleme auftreten, wenngleich in einem noch größeren

Umfang.

Page 71: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.4. WECKSIGNALE MIT ALARM() 65

3.4 Wecksignale mit alarm()

Zu den Signalen, den der POSIX-Standard definiert, gehört auch SIGALRM, das sich als Wecksi-gnal verwenden lässt. Der Wecker wird mit alarm() unter Angabe einer relativen Weckzeit inSekunden gestellt. Am Ende dieser Frist kommt es zum Eintreffen des Signals SIGALRM.

Programmtext 3.2 (S. 65) zeigt, wie alarm() verwendet werden kann, um eine Operation zeit-lich zu befristen. Die Funktion timed_read() arbeitet genauso wie read(), wobei jedoch dieWartezeit auf die gegebene Zahl von Sekunden begrenzt wird. Wenn die Zeit verstreicht, ohnedass eine Eingabe erfolgte, wird 0 zurückgeliefert.

Programm 3.2: read-Operation mit Zeitlimit (tread/tread.c)

1 /∗2 ∗ Timed read operation . timed_read () works like read () but returns 03 ∗ if no input was received within the given number of seconds .4 ∗/5

6 # include < signal .h>7 # include <unistd .h>8 # include "tread .h"9

10 static volatile sig_atomic_t time_exceeded = 0;11

12 static void alarm_handler ( int signal ) {13 time_exceeded = 1;14 }15

16 int timed_read ( int fd , void∗ buf , size_t nbytes , unsigned seconds ) {17 if ( seconds == 0) return 0;18 /∗ setup signal handler and alarm clock but19 ∗ remember the previous settings :20 ∗/21 void (∗ previous_handler )( int ) = signal (SIGALRM, alarm_handler);22 if ( previous_handler == SIG_ERR) return −1;23

24 time_exceeded = 0;25 int remaining_seconds = alarm ( seconds );26 if ( remaining_seconds > 0) {27 if ( remaining_seconds <= seconds ) {28 remaining_seconds = 1;29 } else {30 remaining_seconds −= seconds ;31 }32 }33 int bytes_read = read ( fd , buf , nbytes );34 /∗ restore previous settings ∗/35 if (! time_exceeded ) alarm (0);36 signal (SIGALRM, previous_handler );37 if ( remaining_seconds ) alarm ( remaining_seconds );38 if ( time_exceeded ) return 0;39 return bytes_read ;40 }

Page 72: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

66 KAPITEL 3. SIGNALE

Was geschieht, wenn mehrere Wecksignale nebeneinander eingerichtet werden? Da alarm()nur die Verwaltung einer einzigen Weckzeit unterstützt, ist eine kooperative Vorgehensweise not-wendig. Dies wird dadurch erleichtert, dass alarm() bei der Einrichtung einer neuen Weckzeitentweder 0 zurückgibt (vorher war keine Weckzeit aktiv) oder eine positive Zahl von Sekundenals Restzeit zum vorher eingestellten Wecksignal.

• Entsprechend wird auf Zeile 24 in Programmtext 3.2 (S. 65) in der Variablenremaining_seconds die alternative Weckzeit notiert und später auf Zeile 36 findet eineWiedereinsetzung statt, nachdem zuvor auf Zeile 29 die verbleibende Restzeit neu berech-net worden ist.

• Analog wird in Zeile 21 der frühere Signalbehandler für SIGALRM in der Variablenprevious_handler gesichert, so dass er in Zeile 35 wieder restauriert werden kann.

• Bei der Restaurierung ist es wichtig, dass kein Fenster offenbleibt, das zum Verlust einerSignalbehandlung führt oder den alten Signalbehandler auf das noch nicht eingetreteneSignal für das von timed_read() eingerichtete Zeitlimit reagieren lässt. Wenn der alteSignalbehandler SIG_DFL war, also die Voreinstellung, dann würde das sogar unerwartetzur Terminierung unseres Prozesses führen.

• Deswegen wird in Zeile 34 der Wecker zuerst deaktiviert, wenn er bislang sein Signal nochnicht von sich gegeben hat.

• Danach kann in Zeile 35 der alte Signalbehandler eingesetzt werden, ohne dass wir Gefahrlaufen, daß er sofort verwendet wird.

• In Zeile 36 wird dann der Wecker neu aufgesetzt, falls er vor dem Aufruf von timed_read()eingeschaltet war.

Es bleibt hier noch anzumerken, dass es noch bessere Wege gibt, Leseoperationen mit Zeitbe-schränkungen zu versehen. Es empfiehlt sich hier entweder die Verwendung der Systemaufrufepoll() oder select() oder der Einsatz asynchroner Lesetechniken. Dennoch ist die Verwendungvon alarm() sinnvoll, da es genügend Operationen gibt, bei denen es keine Variante mit Zeit-beschränkung gibt.

Page 73: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.5. DAS VERSENDEN VON SIGNALEN 67

3.5 Das Versenden von Signalen

Programm 3.3: Versenden eines Signals an den übergeordneten Prozess (killparent.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4 # include < signal .h>5 # include <sys/wait .h>6 # include <sys/types .h>7

8 void sigterm_handler ( int signo ) {9 const char msg[] = "Goodbye, cruel world!\n";

10 write (1, msg, sizeof msg − 1);11 _exit (1);12 }13

14 int main() {15 if ( signal (SIGTERM, sigterm_handler) == SIG_ERR) {16 perror ( " signal " ); exit (1);17 }18

19 pid_t child = fork ();20 if ( child == 0) {21 if ( kill ( getppid (), SIGTERM) < 0 ) {22 perror ( " kill " ); exit (1);23 }24 exit (0);25 }26 int wstat ;27 wait(&wstat );28 exit (0);29 }

Der ISO-Standard für C sieht nur eine Funktion raise() vor, die es erlaubt, ein Signal an den eige-nen Prozess zu versenden. Im POSIX-Standard kommt die Funktion kill() hinzu, die es erlaubt,ein Signal an einen anderen Prozess zu verschicken, sofern die dafür notwendigen Privilegienvorliegen. Programmtext 3.3 zeigt ein Beispiel, bei dem ein neu erzeugter Prozess ein Signal anden Erzeuger sendet.

Programm 3.4 (S. 68) dreht den Spiess um: der Erzeuger schickt ein Signal an das erzeugte Kindund untersucht nach dessen Termination (in Kommentaren ist beim Kindprozess das Setzen desSignalbehandlers ausgeblendet) dessen Exit-Status.

Page 74: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

68 KAPITEL 3. SIGNALE

Programm 3.4: Versenden eines Signals an den erzeugten Prozess (killchild.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4 # include < signal .h>5 # include <sys/wait .h>6 # include <sys/types .h>7

8 void sigterm_handler ( int signo ) {9 const char msg[] = "Goodbye, cruel world!\n";

10 write (1, msg, sizeof msg − 1);11 _exit (1);12 }13

14 int main() {15 pid_t child = fork ();16 if ( child == 0) {17

18 if ( signal (SIGTERM, sigterm_handler) == SIG_ERR) {19 perror ( " signal " ); exit (1);20 }21

22 sleep (10);23 exit (0);24 }25 if ( kill ( child , SIGTERM) < 0 ) { perror ( " kill " ); exit (1); }26 int wstat ;27 wait(&wstat );28 int sig = wstat & 0177;29 int exitnumber = ( wstat >> 8) & 0377;30 if ( sig )31 printf ( "Child terminated with signal %d\n", sig);32 else33 printf ( "Child terminated with exit number %d\n", exitnumber);34 exit (0);35 }

Übersetzung und Ausführung ohne Signalbehandler beim Kind:

spatz$ gcc -Wall killchild.cspatz$ a.outChild terminated with signal 15spatz$

Übersetzung und Ausführung mit Signalbehandler beim Kind:

spatz$ a.outGoodbye, cruel world!Child terminated with exit number 1spatz$

Page 75: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.5. DAS VERSENDEN VON SIGNALEN 69

Zur Abfrage des Exit-Codes wie auch der Signalnummer im Exit-Status, wie er über wait()zurückgeliefert wird, sei nochmals auf Abb. 2.3 (S. 29) verwiesen.

Trotz ihres geringen Informationsgehalts dienen Signale gelegentlich zur Kommunikation. Sogibt es eine weitverbreitete Konvention, dass bei langfristig laufenden Diensten SIGHUP daserneute Einlesen der Konfiguration veranlasst und SIGTERM eine geordnete Terminierung ein-leiten soll. Gelegentlich sind für Dienste auch Reaktionen für SIGUSR1 und SIGUSR2 definiert.So wird der Apache-Webserver beispielsweise bei SIGUSR1 veranlasst, bei nächster Gelegenheitauf sanfte Weise neu zu starten. Anders als bei SIGHUP kommt es dann nicht zur Unterbrechungaktueller Netzwerkverbindungen.

Der Systemaufruf kill() erfüllt aber auch noch einen weiteren Zweck. Bei einer Signalnum-mer von 0 wird nur die Zulässigkeit des Signalversendens überprüft. Programmtext 3.5 (S. 69)demonstriert, wie dies dazu verwendet werden kann, um die Existenz eines Prozesses zu über-prüfen.

Programm 3.5: Verwendung von kill() zur Überprüfung der Existenz eines Prozesses (lookfor.c)

1 # include <errno .h>2 # include < signal .h>3 # include < stdio .h>4 # include < stdlib .h>5 # include <unistd .h>6

7 int main( int argc , char∗∗ argv ) {8 char∗ cmdname = ∗argv++; −−argc;9 if ( argc != 1) {

10 fprintf ( stderr , "Usage: %s pid\n", cmdname);11 exit (1);12 }13

14 /∗ convert first argument to pid ∗/15 char∗ endptr = argv [0];16 pid_t pid = strtol (argv [0], &endptr, 10);17 if ( endptr == argv [0]) {18 fprintf ( stderr , "%s: integer expected as argument\n",19 cmdname);20 exit (1);21 }22

23 if ( kill ( pid ,0) == 0 )24 printf ( "Process %d exists!\n", pid );25 else26 printf ( "Process %d doesn’t exist!\n",pid );27

28 if ( errno == ESRCH) exit (0);29 perror (cmdname); exit (1);30 }

Page 76: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

70 KAPITEL 3. SIGNALE

Übersetzung und Ausführung von Programm 3.5 (S. 69):

spatz$ gcc -Wall lookfor.cspatz$ ps -uswg

PID TTY TIME CMD3976 ? 00:00:02 fvwm4015 ? 00:00:00 ssh-agent4018 ? 00:00:00 FvwmTheme4030 ? 00:00:02 xterm4031 ? 00:00:00 FvwmButtons4034 ? 00:00:00 xeyes4036 ? 00:00:00 xclock4037 ? 00:00:00 FvwmPager4039 pts/0 00:00:00 bash4057 ? 00:00:06 xterm4059 pts/1 00:00:00 bash4075 pts/1 00:00:00 myxtel4170 pts/1 00:00:01 gv4208 pts/1 00:00:02 gs4690 ? 00:00:00 xterm4692 pts/2 00:00:00 bash5002 pts/ 2 00:00:00 ps

spatz$ a.out 4690Process 4690 exists!a.out: Successspatz$ a.out 5002Process 5002 doesn’t exist!spatz$

Gelegentlich kommt es vor, dass Prozesse nur auf das Eintreffen eines Signals warten möchtenund sonst nichts zu tun haben. Theoretisch könnte ein Prozess dann in eine Dauerschleife mitleerem Inhalt treten (auch busy loop bezeichnet), aber dies wäre nicht sehr fair auf einem Systemmit mehreren Prozessen, da dadurch Rechenzeit vergeudet würde. Abhilfe schafft hier der Sy-stemaufruf pause(), der einen Prozess schlafen legt, bis ein Signal eintrifft.

Programm 3.6 demonstriert das Warten auf ein Signal anhand eines virtuellen Ballspiels zweierProzesse.

Page 77: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.5. DAS VERSENDEN VON SIGNALEN 71

Programm 3.6: Virtuelles Ballspiel zweier Prozesse (pingpong/pingpong.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4 # include <sys/types .h>5 # include < signal .h>6

7 # define PINGPONGS 108

9 static volatile sig_atomic_t sigcount = 0;10

11 void sighandler ( int sig ) {12 ++sigcount ;13 if ( signal ( sig , sighandler ) == SIG_ERR) _exit (1);14 }15

16 static void playwith ( pid_t partner , int start ) {17 int i ;18 if ( signal (SIGUSR1, sighandler ) == SIG_ERR) {19 perror ( " signal SIGUSR1"); exit (1);20 }21 /∗ give our partner some time for preparation ∗/22 if ( start ) sleep (1);23 /∗ start the ping pong game ∗/24 if ( start ) sigcount = 1;25 for ( i = 0; i < PINGPONGS; ++i) {26 if (! sigcount ) pause ();27 printf ( "[%d] send signal to %d\n", (int) getpid (), ( int ) partner );28 if ( kill ( partner , SIGUSR1) < 0) {29 printf ( "[%d] %d is no longer alive\n", (int) getpid (), ( int ) partner );30 return ;31 }32 −−sigcount;33 }34 printf ( "[%d] finishes playing\n", ( int ) getpid ());35 }36

37 int main() {38 pid_t parent = getpid ();39 pid_t child = fork ();40

41 if ( child < 0) {42 perror ( "fork" ); exit (1);43 }44 if ( child == 0) {45 playwith ( parent , 1);46 } else {47 playwith ( child , 0);48 }49 exit (0);50 }

Page 78: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

72 KAPITEL 3. SIGNALE

Erläuterungen zu Programm 3.6 (S. 71):

• In Zeile 39 wird ein weiterer Prozess als Spielpartner erzeugt.

• Beide Prozesse rufen anschließend playwith() auf, um das Spiel durchzuführen.

• Der neu erzeugte Prozess hat zuerst den virtuellen Ball und darf mit dem Spiel beginnen.

• Der Besitzer des virtuellen Balles sendet in Zeile 28 den Ball mit Hilfe des Signals SIGUSR1an den Partner und legt sich anschließend in Zeile 26 mittels pause() schlafen, um auf dasWiedereintreffen des Balls zu warten, das wiederum durch SIGUSR1 signalisiert wird.

• Die Variable sigcount in Zeile 9 repräsentiert die Zahl der Bälle, die sich im Augenblickim Besitz des Prozesses befinden. Diese Zahl wird von dem Signalbehandler sighandler()in Zeile 12 hochgezählt, wenn ein SIGUSR1-Signal eintrifft und in Zeile 32 wieder herun-tergezählt, nachdem das SIGUSR1-Signal an den Partner verschickt wurde.

• Zu Beginn setzen beide Spieler in Zeile 18 sighandler() als Signalbehandler ein. Derje-nige, der mit dem Spiel beginnt, wartet dann in Zeile 22 noch eine Sekunde, um zu vermei-den, dass ein Signal geschickt wird, bevor der Spielpartner die Gelegenheit hatte, seinenSignalbehandler aufzusetzen.

• Zu beachten ist dabei, dass der Signalbehandler in Zeile 13 sich selbst wieder einsetzt, dader POSIX-Standard nicht festlegt, ob diese Einstellung nach Eintreffen eines Signals erhal-ten bleibt. signal() gehört mit zu den vom POSIX-Standard genannten Funktionen, dieauch innerhalb eines Signalbehandlers aufgerufen werden dürfen.

Page 79: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.6. REAKTION AUF SIGNALE: SIGACTION() 73

3.6 Reaktion auf Signale: sigaction()

Für eine genauere Behandlung eintreffender Signal bietet POSIX (jedoch nicht ISO-C) den Sy-stemaufruf sigaction() an.

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

Datentyp Feldname Beschreibungvoid(*) (int) sa_handler Funktionszeiger (wie bisher)void(*) (int, siginfo_t*, void*) sa_sigaction alternativer Zeiger auf einen Si-

gnalbehandler, der mehr Infor-mationen zum Signal erhält

sigset_t sa_mask Menge von Signalen, die wäh-rend der Signalbehandlung die-ses Signals zu blockieren sind

int sa_flags Menge von Boolean-wertigenOptionen

Tabelle 3.1: Felder der struct sigaction

Während bei signal() zur Spezifikation der Signalbehandlung nur ein Funktionszeiger ge-nügte, kommen bei der struct sigaction, die sigaction() verwendet, die in Tabelle 3.1genannten Felder zum Einsatz.

Programm 3.8 zeigt ein einfaches Beispiel, ein Testprogramm ist 3.9. Zunächst wird für eine be-stimmte Zeiteinheit das Signal, das durch ctrl-c erzeugt wird, abgefangen, danach wird deralte Signalbehandler (Termination) wieder eingesetzt.

Übersetzung und Ausführung (auf SuSE Linux, gcc 3.3.3):

spatz$ makegcc -std=c99 -Wall -g -D_POSIX_C_SOURCE=200112L -D__EXTENSIONS__=1-D_Exit=_exit -I/usr/local/diet/include -c -o show.o show.cgcc -std=c99 -Wall -g -D_POSIX_C_SOURCE=200112L -D__EXTENSIONS__=1-D_Exit=_exit -I/usr/local/diet/include -c -o sign.o sign.cgcc -L/usr/local/diet/lib show.o sign.o -lowfat -o showspatz$ showBreak?go asleep for 5 sec^Csignal handler: SIGNAL = >2<^C ^C ^\Quitspatz$

Die (eingefügten) Zeichen ^C und ^\ sollen die erzeugten Signale wiedergeben: nach dem er-sten ^C meldet sich der Signalbehandler mit signal handler: SIGNAL = >2<, die weiteren

Page 80: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

74 KAPITEL 3. SIGNALE

^Cs werden die nächsten 10 Sekunden zurückgehalten, während das ^\ während der Abwick-lung des Signalbehandlers (sleep(10) zugestellt wird (Funktion sigemptyset() in Zeile 18 desProgramms 3.8 auf S. 74): durch das Leeren der Komponente newact.sa_mask wird erreicht,dass während der Ausführung der installierten Signalbehandlungsfunktion mit Ausnahme desim Argument sig angegebenen Signals keine weiteren Signale von der Zustellung durch denSystemkern zurückgehalten werden (deswegen kommt ja auch unser ^\ sofort durch!)

Programm 3.7: Header-File zu 3.8 (sigaction1/sign.h)

1 # include < signal .h>2

3 # ifndef SIGN_H4 # define SIGN_H5

6 typedef void (∗ Sigfunc )( int );7 Sigfunc ignoresig ( int );8 Sigfunc entrysig ( int );9 # endif

Programm 3.8: Erstes Beispiel zu sigaction() (sigaction1/sign.c)

1 # include "sign .h"2 # include < stdio .h>3 # include <unistd .h>4

5 void myignore( int sig ){6 printf ( " signal handler: SIGNAL = >%d<\n",sig);7 sleep (10);8 return ;9 }

10

11 struct sigaction newact , oldact ;12

13 Sigfunc ignoresig ( int sig ) {14 static int first = 1; // static : bleibt erhalten !15 newact . sa_handler = myignore ;16 if ( sigemptyset (&newact.sa_mask) < 0)17 return SIG_ERR;18 /∗ Durch diese Initialisierung der Komponente sa_mask mit19 ∗ der leeren Menge wird bewirkt , dass waehrend der Aus−20 ∗ fuehrung der installierten Signalbehandlungsfunktion21 ∗ mit Ausnahme des im Argument sig angegebenen Signals22 ∗ keine weiteren Signale von der Zustellung durch den23 ∗ Systemkern zurueckgehalten werden24 ∗/25

26 if ( first ) {27 first = 0;28 if ( sigaction ( sig , &newact, &oldact ) < 0)29 return SIG_ERR;30 else31 return oldact . sa_handler ;32 } else {33 if ( sigaction ( sig , &newact, NULL) < 0)34 return SIG_ERR;

Page 81: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.6. REAKTION AUF SIGNALE: SIGACTION() 75

35 else36 return NULL;37 }38 }39

40 Sigfunc entrysig ( int sig ) {41 if ( sigaction ( sig , &oldact , NULL) < 0 )42 return SIG_ERR;43 else44 return NULL;45 }

Programm 3.9: Testprogramm zu 3.8 (sigaction1/show.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4 # include "sign .h"5

6 int main() {7 int sleep_time ;8 /∗ install reaction ∗/9 if ( ( ignoresig (SIGINT) == SIG_ERR) ) {

10 perror ( " ignoresig " );11 exit (1);12 }13 printf ( "Break?\n");14 while(1) { /∗ loop forever ∗/15 sleep_time = 5;16 do {17 printf ( "go asleep for %d sec\n", sleep_time );18 sleep_time = sleep ( sleep_time );19 } while( sleep_time != 0);20 ;21 printf ( "And now?\n");22 /∗ restore reaction ∗/23 if ( ( entrysig (SIGINT) == SIG_ERR) ||24 ( entrysig (SIGQUIT) == SIG_ERR) ) {25 perror ( " entrysig " );26 exit (2);27 }28 }29 exit (0);30 }

Page 82: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

76 KAPITEL 3. SIGNALE

Mit der Funktion sigaddset() können zu der Menge newact.sa_mask gezielt Signale hinzuge-nommen werden. Dies zeigt Programm 3.10 auf S. 76, eine kleine Modifikation von ProgrammProgramm 3.8 (S. 74).

Programm 3.10: Modifikation von 3.8 (sigaction2/sign.c)

1 # include "sign .h"2 # include < stdio .h>3 # include <unistd .h>4

5 void myignore( int sig ){6 printf ( " signal handler: SIGNAL = >%d<\n",sig);7 sleep (10);8 return ;9 }

10

11 struct sigaction newact , oldact ;12

13 Sigfunc ignoresig ( int sig ) {14 static int first = 1; // static : bleibt erhalten !15 newact . sa_handler = myignore ;16 if ( sigemptyset (&newact.sa_mask) < 0)17 return SIG_ERR;18 if ( sigaddset (&newact.sa_mask , 3) < 0)19 return SIG_ERR;20 if ( first ) {21 first = 0;22

23 if ( sigaction ( sig , &newact, &oldact ) < 0)24 return SIG_ERR;25 else26 return oldact . sa_handler ;27 } else {28 if ( sigaction ( sig , &newact, NULL) < 0)29 return SIG_ERR;30 else31 return NULL;32 }33 }34

35 Sigfunc entrysig ( int sig ) {36 if ( sigaction ( sig , &oldact , NULL) < 0 )37 return SIG_ERR;38 else39 return NULL;40 }

Page 83: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.6. REAKTION AUF SIGNALE: SIGACTION() 77

Ausführung:

spatz$ showBreak?go asleep for 5 sec^Csignal handler: SIGNAL = >2<^\Quitspatz$

Unmittelbar nach der Eingabe von ^C wurde ein ^\ gegeben – es dauert allerdings einige Sekun-den (Ablauf von sleep(10) im Signalbehandler) bis ^\ zugestellt wird und logischerweise zurTermination führt (Quit).

Mehr zu sigaction() im nächsten Abschnitt!

Page 84: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

78 KAPITEL 3. SIGNALE

3.7 Die Zustellung von Signalen

Die vorangegangenen Beispiele werfen die Frage auf, wie UNIX bei der Zustellung von Signalenvorgeht, wenn

• der Prozess zur Zeit nicht aktiv ist,

• gerade ein Systemaufruf für den Prozess abgearbeitet wird oder

• gerade ein Signalbehandler aktiv ist.

Vom ISO-Standard 9899-1999 für C wird in dieser Beziehung nichts festgelegt. Der POSIX-Standardgeht jedoch genauer darauf ein:3

• Wenn ein Prozess ein Signal erhält, wird dieses Signal zunächst in den zugehörigen Ver-waltungsstrukturen des Betriebssystems vermerkt. Signale, die für einen Prozess vermerktsind, jedoch noch nicht zugestellt worden sind, werden als anhängige Signale bezeichnet.

• Wenn mehrere Signale mit der gleichen Nummer anhängig sind, ist nicht festgelegt, ob eineMehrfachzustellung erfolgt. Es können also Signale wegfallen.

• Nur aktiv laufende Prozesse können Signale empfangen. Prozesse werden normalerweisedurch die Existenz eines anhängigen Signals aktiv — aber dieses kann auch längere Zeit inAnspruch nehmen, wenn dem zwischenzeitlich mangelnde Ressourcen entgegenstehen.

• Für jeden Prozess gibt es eine Menge blockierter Signale, die im Augenblick nicht zuge-stellt werden sollen. Dies hat nichts mit dem Ignorieren von Signalen zu tun, da blockierteSignale anhängig bleiben, bis die Blockierung aufgehoben wird.

• Der POSIX-Standard legt nicht fest, was mit der Signalbehandlung geschieht, wenn ein Si-gnalbehandler aufgerufen wird. Möglich ist das Zurückfallen auf SIG_DFL (Voreinstellungmit Prozessterminierung) oder die temporäre automatische Blockierung des Signals bis zurBeendigung des Signalbehandlers. Alle modernen UNIX-Systeme wählen die zweite Va-riante. Dies lässt sich aber gemäß dem POSIX-Standard auch erzwingen, indem die um-fangreichere Schnittstelle sigaction() anstelle von signal() verwendet wird. Allerdingsist sigaction() nicht mehr Bestandteil des ISO-Standards für C (aber eben des POSIX-Standards!).

• UNIX unterscheidet zwischen unterbrechbaren und unterbrechungsfreien Systemaufru-fen.

Zur ersteren Kategorie gehören weitgehend alle Systemaufrufe, die zu einer längeren Blockie-rung eines Prozesses führen können. Ist ein nicht blockiertes Signal anhängig, kann ein un-terbrechbarer Systemaufruf aufgrund des Signals mit einer Fehlerindikation beendet wer-den. errno wird dann auf EINTR gesetzt.

Dabei ist zu beachten, dass der unterbrochene Systemaufruf nach Beendigung der Signal-behandlung nicht fortgesetzt wird, sondern manuell erneut gestartet werden muss. Dieskann leider zu unerwarteten Überraschungseffekten führen, weil insbesondere auch diestdio-Bibliothek keinerlei Vorkehrungen trifft, Systemaufrufe automatisch erneut aufzu-setzen, falls es zu einer Unterbrechung kam. Dies ist eine wesentliche Schwäche sowohldes POSIX-Standards als auch der stdio-Bibliothek und ein Grund mehr dafür, auf dieVerwendung der stdio in kritischen Anwendungen völlig zu verzichten.

3Siehe “Signal Concepts”, im Web unterhttp://www.opengroup.org/onlinepubs/007904975/functions/xsh_chap02_04.html

Page 85: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.7. DIE ZUSTELLUNG VON SIGNALEN 79

Für die genauere Regulierung der Signalbehandlung bietet POSIX (jedoch nicht ISO-C) wie be-reits erwähnt den Systemaufruf sigaction() an.

Ein wesentlicher Unterschied zwischen sigaction()und signal() besteht bereits darin, dassper Voreinstellung das Signal, das eine Signalbehandlung auslöst, während der Bearbeitung au-tomatisch blockiert wird. Ferner findet (solange nichts gegenteiliges in den Optionen angegebenwurde) keine implizite Veränderung des Signalbehandlers auf SIG_DFL nach der Signalbehand-lung statt. Bei signal() ist dies ebenfalls möglich, jedoch nicht garantiert. In neueren UNIX-Version (ab System V.3) wurde der Signalmechanismus erweitert:

• man kann nun in einem Programm mit sighold(sig) einen kritischen Abschnitt beginnenund mit sigrelse(sig) (sigrelease, s.u.) abschliessen

• tritt in diesem Abschnitt das Signal sig auf, so wird es bis zur Bendigung des Abschnittszurückgehalten

(siehe z.B. http://www.opengroup.org/onlinepubs/009695399/functions/sigpause.html)

Programm 3.11: Verlust von Signalen (sigfire/sigfire.c)

1 # include < signal .h>2 # include < stdio .h>3 # include < stdlib .h>4 # include <unistd .h>5

6 static const int NOF_SIGNALS = 1000;7 static volatile sig_atomic_t received_signals = 0;8 static volatile sig_atomic_t terminated = 0;9

10 static void count_signals ( int sig ) {11 ++ received_signals ;12 }13

14 void termination_handler ( int sig ) {15 terminated = 1;16 }17

18 int main() {19 sighold (SIGUSR1); sighold (SIGTERM);20

21 pid_t child = fork ();22 if ( child < 0) {23 perror ( "fork" ); exit (1);24 }25 if ( child == 0) {26 struct sigaction action = {0};27 action . sa_handler = count_signals ;28 if ( sigaction (SIGUSR1, &action, 0) != 0) {29 perror ( " sigaction " ); exit (1);30 }31 action . sa_handler = termination_handler ;32 if ( sigaction (SIGTERM, &action, 0) != 0) {33 perror ( " sigaction " ); exit (1);34 }

Page 86: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

80 KAPITEL 3. SIGNALE

35 sigrelse (SIGUSR1); sigrelse (SIGTERM);36 while (! terminated ) pause ();37 printf ( "[%d] received %d signals\n",38 ( int ) getpid (), received_signals );39 exit (0);40 }41

42 sigrelse (SIGUSR1); sigrelse (SIGTERM);43 for ( int i = 0; i < NOF_SIGNALS; ++i) {44 kill ( child , SIGUSR1);45 }46 printf ( "[%d] sent %d signals\n",47 ( int ) getpid (), NOF_SIGNALS);48 kill ( child , SIGTERM); wait(0);49 }

Programm 3.11 demonstriert den möglichen Verlust von Signalen trotz umfangreicher Vorsichts-maßnahmen (hierbei werden spezielle ISO-C Funktionen wie verwendet. Das Experiment be-steht hier im Versenden von 1000 SIGUSR1-Signalen, die beim Empfänger nachgezählt werden.In den Zeilen 26 bis 30 setzt der neu erzeugte Prozess die Funktion count_signals() als Si-gnalbehandler für SIGUSR1 auf. Dabei wird hier sigaction() anstelle von signal() verwen-det. Dies garantiert, dass während der Bearbeitung des Signals SIGUSR1 weitere eintreffendeSIGUSR1-Signale aufgeschoben und nicht sofort in verschachtelter Form bearbeitet werden.

Auf diese Weise ist die Atomizität des Hochzählens der Variable received_signals garan-tiert. Zu bedenken ist dabei, dass der Datentyp sig_atomic_t selbst nur die Atomizität einereinzelnen Lese- oder Schreiboperation gewährleistet. Bei einem Inkrement liegt jedoch eine Lese-und eine Schreib-Operation vor. Zwischen diesen Operationen wäre eine Unterbrechung wegeneiner Signalbehandlung denkbar.

In diesem Beispiel versendet der übergeordnete Prozess 1000 Signale und der neu erzeugte Pro-zess zählt die eingetroffenen Signale. Wann darf der übergeordnete Prozess davon ausgehen,dass der neu erzeugte Prozess seinen Signalbehandler fertig aufgesetzt hat, so dass er mit demZählen beginnen kann? Wenn der übergeordnete Prozess zu früh Signale versendet, währendnoch die voreingestellte Reaktion für SIGUSR1 eingerichtet ist, würde dies nur zur vorzeitigenTerminierung des erzeugten Prozesses führen. Diese Problematik lässt sich vermeiden, indemdie Signale, für die der erzeugte Prozess Behandler aufsetzt, vor der Prozesserzeugung blockiertwerden. Das geht am einfachsten mit der Funktion sighold() (auf Zeile 19), die zum POSIX-Standard gehört. Zu jedem Prozess unterhält das Betriebssystem eine Menge blockierter Signale.Mit sighold() wird das angegebene Signal zu der Menge hinzugefügt. Entscheidend ist hier,dass die Menge der blockierten Signale an den neu erzeugten Prozess vererbt wird. So könnennach dem fork() in aller Ruhe auf den Zeilen 26 bis 34 die Signalbehandler für SIGUSR1 undSIGTERM aufgesetzt werden, bevor auf Zeile 35 diese Signale wieder mit Hilfe von sigrelse()(eine unglückliche Abkürzung von signal release) wieder aus der Menge der blockierten Signaleentfernt werden. Auch der übergeordnete Prozess nimmt die beiden Signale wieder heraus aufder Zeile 42.

Wie wird das Experiment beendet? Da, wie das Experiment zeigen soll, Signale verloren gehenkönnen, sollte der erzeugte Prozess nicht darauf warten, bis NOF_SIGNALS eingetroffen sind.Stattdessen wird SIGTERM vom übergeordneten Prozess an den erzeugten Prozess verwendet,um das Ende des Experiments zu signalisieren.

Hier sind einige Läufe des Experiments, die demonstrieren, wie sehr die Werte voneinander ab-weichen können:

Page 87: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.7. DIE ZUSTELLUNG VON SIGNALEN 81

doolin$ sigfire[22073] sent 1000 signals[22074] received 264 signalsdoolin$ sigfire[22075] sent 1000 signals[22076] received 227 signalsdoolin$ sigfire[22077] sent 1000 signals[22078] received 481 signalsdoolin$ sigfire[22079] sent 1000 signals[22080] received 136 signalsdoolin$

Wenn anstelle von nur SIGUSR1 zwei Signale SIGUSR1 und SIGUSR2 abwechselnd verwendetwerden, können höhere Werte erzielt werden, wobei der Erfolg keinesfalls garantiert ist:

doolin$ sigfire2[22142] sent 1000 signals[22143] received 495 signalsdoolin$ sigfire2[22144] sent 1000 signals[22145] received 462 signalsdoolin$ sigfire2[22146] sent 1000 signals[22147] received 468 signalsdoolin$ sigfire2[22151] sent 1000 signals[22152] received 688 signalsdoolin$

Dieses Experiment untermauert die Regel, dass einzelne Signale zuverlässig zugestellt werden,während es bei dem Mehrfachen Eintreffen des gleichen Signals zu Verlusten kommen kann. Inder Praxis zeigen sich jedoch Signalverluste nur bei härteren Rahmenbedingungen, sei es durchein explizites Dauerfeuer (wie in diesem Experiment) oder durch eine hohe Belastung der Ma-schine.

Page 88: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

82 KAPITEL 3. SIGNALE

3.8 Signale als Indikatoren für terminierte Prozesse

Wenn ein von einem Prozess P erzeugter Prozess K terminiert, so wird P das Signal SIGCHLDzugestellt. Die voreingestellte Reaktion (SIG_DFL) darauf ist Ignorieren (siehe bei Linux z.B.man -S7 signal).

In Programm 3.12 (S. 82) werden nacheinander drei Prozesse erzeugt, die jeweils eine zufälliggewählte Zeit (zwischen 0 und 4 Sekunden) warten und dann mit einem zufälligen Exit-Status(zwischen 0 und 255) terminieren (diese “Zufälligkeit” ist zunächst ohne Bedeutung). Der Erzeu-ger legt sich 10 Sekunden schlafen und gibt danach seine und seiner Abkömmlinge Einträge inder Prozesstabelle aus:

euklid$ showCreate child processes:I’m child 1 with PID = 4241!signal handler: SIGNAL = >17<I’m child 2 with PID = 4242!signal handler: SIGNAL = >17<I’m child 3 with PID = 4243!signal handler: SIGNAL = >17<

PID TTY TIME CMD4233 pts/3 00:00:00 bash4240 pts/3 00:00:00 show4241 pts/3 00:00:00 show <defunct>4242 pts/3 00:00:00 show <defunct>4243 pts/3 00:00:00 show <defunct>4244 pts/3 00:00:00 ps

signal handler: SIGNAL = >17<Parent: going to exit!euclid$

Auch hier ist wie oben bereits beschrieben zu beachten, dass einzelne Signale zuverlässig zu-gestellt werden, während es bei dem Mehrfachen Eintreffen des gleichen Signals zu Verlustenkommen kann.

Programm 3.12: Prozesse, auf die der Erzeuger nicht wartet (sigchld/show.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4 # include < signal .h>5

6 typedef void (∗ Sigfunc )( int );7

8 void myignore( int sig ){9 printf ( " signal handler: SIGNAL = >%d<\n",sig);

10 return ;11 }12

13 struct sigaction newact , oldact ;14

15 Sigfunc ignoresig ( int sig ) {

Page 89: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.8. SIGNALE ALS INDIKATOREN FÜR TERMINIERTE PROZESSE 83

16 newact . sa_handler = myignore ;17 if ( sigemptyset (&newact.sa_mask) < 0)18 return SIG_ERR;19 newact . sa_flags = 0;20 if ( sigaction ( sig , &newact, &oldact ) < 0)21 return SIG_ERR;22 else23 return oldact . sa_handler ;24 }25

26 int main() {27 int pid [3], i = 3;28 if ( ignoresig (SIGCHLD) == SIG_ERR) {29 perror ( " ignoresig " );30 exit (1);31 }32

33 printf ( "Create child processes:\n" );34 while(i>0) {35 switch(pid[3−i]= fork ()) {36 case −1: perror ( "fork" );37 exit (1);38 case 0 : printf ( " I ’m child %d with PID = %d!\n", 4−i, (int) getpid());39 srand ( getpid ()); sleep (rand () % 5);40 exit (( char) rand ());41 default :42 sleep (1);43 }44 i−−;45 }46 sleep (10);47 switch( fork ()) {48 case −1: perror ( "fork" );49 exit (1);50 case 0:51 execlp ( "ps" , "ps" , "−l" , NULL);52 default :53 sleep (1);54 }55 sleep (1);56 printf ( "Parent : going to exit!\n" );57 exit (0);58 }

Page 90: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

84 KAPITEL 3. SIGNALE

Das lässt sich leicht verändern. der Erzeuger bekommt schließlich Nachricht über die Terminati-on eines erzeugten Prozesses. Wenn er auf das Signal einen Signalbehandler mit einem schlichtenwait() einrichtet, wird der Eintrag des eben beendeten Kindprozesses dadurch abgeräumt (sieheProgramm 3.13, S. 84):

euclid$ showCreate child processes:I’m child 1 with PID = 4364!I’m child 2 with PID = 4365!signal handler: SIGNAL = >17<Process 4365 terminated with Status 225signal handler: SIGNAL = >17<Process 4364 terminated with Status 248I’m child 3 with PID = 4366!signal handler: SIGNAL = >17<Process 4366 terminated with Status 154Process table:

PID TTY TIME CMD4356 pts/3 00:00:00 bash4363 pts/3 00:00:00 show4367 pts/3 00:00:00 ps

signal handler: SIGNAL = >17<Process 4367 terminated with Status 0Parent: going to exit!euclid$

Programm 3.13: Prozesse, auf die der Erzeuger ohne zu blockieren wartet (sigchld1/show.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4 # include < signal .h>5 # include <sys/wait .h>6

7 typedef void (∗ Sigfunc )( int );8

9 void myignore( int sig ){10 int status , pid ;11 printf ( " signal handler: SIGNAL = >%d<\n",sig);12 pid = wait(& status );13 printf ( "Process %d terminated with Status %d\n", pid, (status >> 8) & 0377);14 return ;15 }16

17 struct sigaction newact , oldact ;18

19 Sigfunc ignoresig ( int sig ) {20 newact . sa_handler = myignore ;21 if ( sigemptyset (&newact.sa_mask) < 0)22 return SIG_ERR;23 newact . sa_flags = 0;24 if ( sigaction ( sig , &newact, &oldact ) < 0)25 return SIG_ERR;

Page 91: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.8. SIGNALE ALS INDIKATOREN FÜR TERMINIERTE PROZESSE 85

26 else27 return oldact . sa_handler ;28 }29

30 int main() {31 int pid [3], i = 3;32 if ( ignoresig (SIGCHLD) == SIG_ERR) {33 perror ( " ignoresig " );34 exit (1);35 }36

37 printf ( "Create child processes:\n" );38 while(i>0) {39 switch(pid[3−i]= fork ()) {40 case −1: perror ( "fork" );41 exit (1);42 case 0 : printf ( " I ’m child %d with PID = %d!\n", 4−i, (int) getpid());43 srand ( getpid ()); sleep (rand () % 5);44 exit (( char) rand ());45 default :46 sleep (1);47 }48 i−−;49 }50 sleep (10);51 switch( fork ()){52 case −1: perror ( "fork" );53 exit (1);54 case 0:55 printf ( "Process table : \n");56 execlp ( "ps" , "ps" ,NULL);57 default :58 sleep (1);59 }60 printf ( "Parent : going to exit!\n" );61 exit (0);62 }

Page 92: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

86 KAPITEL 3. SIGNALE

Diese Lösung ist einfach, aber nicht allzu sicher. Das Problem ist, dass bei einer zeitgleichen Ter-minierung mehrerer Prozesse es wiederum zu Verlusten des SIGCHLD-Signals kommen kann.Somit kann man sich nicht darauf verlassen, dass für jeden terminierten Prozess der Signalbe-handler genau einmal aufgerufen wird.

Deswegen empfiehlt es sich, den Status aller bereits terminierter Prozesse abzurufen. Dies wirdmit waitpid() unter der Verwendung der Option WNOHANG erreicht. Diese verhindert einBlockieren des Systemaufrufs waitpid() und somit kann waitpid() gefahrlos solange auf-gerufen werden, bis waitpid() 0 oder einen negativen Wert zurückliefert.

Programm 3.14: Prozesse, auf die der Erzeuger ohne zu blockieren wartet (sigchld2/show.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4 # include < signal .h>5 # include <sys/wait .h>6 # include <sys/types .h>7

8 typedef void (∗ Sigfunc )( int );9

10 void myignore( int sig ){11 int status , pid ;12 printf ( " signal handler: SIGNAL = >%d<\n",sig);13 while (( pid = waitpid (( pid_t )−1, &status , WNOHANG)) > 0) {14 printf ( "%d terminated with %d\n", pid, (status >> 8) & 0377);15 };16 return ;17 }18

19 struct sigaction newact , oldact ;20

21 Sigfunc ignoresig ( int sig ) {22 newact . sa_handler = myignore ;23 if ( sigemptyset (&newact.sa_mask) < 0)24 return SIG_ERR;25 newact . sa_flags = 0;26 if ( sigaction ( sig , &newact, &oldact ) < 0)27 return SIG_ERR;28 else29 return oldact . sa_handler ;30 }31

32 int main() {33 int pid [3], i = 3;34 if ( ignoresig (SIGCHLD) == SIG_ERR) {35 perror ( " ignoresig " );36 exit (1);37 }38

39 printf ( "Create child processes:\n" );40 while(i>0) {41 switch(pid[3−i]= fork ()) {42 case −1: perror ( "fork" );43 exit (1);

Page 93: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.8. SIGNALE ALS INDIKATOREN FÜR TERMINIERTE PROZESSE 87

44 case 0 : printf ( " I ’m child %d with PID = %d!\n", 4−i, (int) getpid());45 srand ( getpid ()); sleep (rand () % 5);46 exit (( char) rand ());47 default :48 sleep (1);49 }50 i−−;51 }52 sleep (10);53 switch( fork ()){54 case −1: perror ( "fork" );55 exit (1);56 case 0:57 printf ( "Process table : \n");58 execlp ( "ps" , "ps" ,NULL);59 default :60 sleep (1);61 }62 printf ( "Parent : going to exit!\n" );63 exit (0);64 }

Um die Prozesse und deren Status zu erfassen, sind umfangreichere Datenstrukturen erforder-lich. Wenn auf gemeinsame Datenstrukturen von mehreren Seiten in asynchroner Form zugegrif-fen werden kann, dann sind die zugreifenden Programmbereiche sogenannte kritische Regio-nen. Nur durch den gegenseitigen Ausschluss wird verhindert, dass die betroffene Datenstruk-tur durch einen ungünstigen Unterbrechungszeitpunkt inkonsistent wird. Da sigaction() an-stelle von signal() verwendet wird, ist bereits sichergestellt, dass der Signalbehandler nichtmehrfach in verschachtelter Form aufgerufen wird. Somit müssen nur alle verbliebenen Pro-grammbereiche, die auf die gleiche Datenstruktur zugreifen, in sighold() und sigrelse()geklammert werden. Dies soll aber hier nicht weiter behandelt werden!

Page 94: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

88 KAPITEL 3. SIGNALE

3.9 Signalbehandlung in einer (Mini-)Shell

Die mit dem Programmtext 2.29 vorgestellte einfache Shell kümmerte sich nicht um die Signalbe-handlung. Dies kann zu überraschenden Effekten führen, wenn der Versuch unternommen wird,aufgerufene Prozesse beispielsweise mit SIGINT zu unterbrechen:

euclid$ tinysh% cat >OUTsome input ...^Ceuclid$

Hier wurde zunächst cat aufgerufen und nach der ersten eingegebenen Zeile CTRL-c eingege-ben, welches bei den aktuellen Einstellungen zu einem SIGINT an alle Prozesse der aktuellen Sit-zung führte. Zur aktuellen Sitzung gehört jedoch nicht nur das gerade laufende cat-Kommando,sondern natürlich auch tinysh. Da tinysh keinerlei Vorkehrungen traf, wurde es genauso wiecat einfach terminiert, weil dies die voreingestellte Reaktion ist. Wäre tinysh die Login-Shellgewesen, wäre damit die gesamte Sitzung beendet. Hier in diesem Beispiel wurde SIGINT of-fensichtlich von der normalen Shell ignoriert.

Signalbehandlung einer Shell:

• Wenn ein Kommando im Vordergrund läuft, muss die Shell die Signale SIGINTund SIGQUITignorieren (sie soll ja schließlich weiterlaufen).

• Wenn ein Kommando im Hintergrund läuft, müssen für diesen ProzessSIGINT und SIGQUITignoriert werden.

• Wenn die Shell ein Kommando einliest, sollten SIGINT und SIGQUIT die Neu-Eingabe desKommandos ermöglichen.

• Bezüglich SIGHUP muss nichts unternommen werden.

Die Worte auf der Kommandozeile werden jetzt differenzierter betrachtet. Zulässige Symbole(token) der Kommandozeilen-Sprache sind:

• > (T_GT)

Ausgabeumlenkung - danach muss ein Dateiname kommen

• >> (T_GTGT)

Ausgabeumlenkung zum Anfügen - danach muss ein Dateiname kommen

• < (T_LT)

Eingabeumlenkung - danach muss ein Dateiname kommen

• 2> (T_TWO_GT)

Umlenkung der Diagnose-Ausgabe - danach muss ein Dateiname kommen

• & (T_AMP)

Ausführung des Kommandos im Hintergrund - die darauf folgenden Zeichen bis zum Zei-lenende werden hier ignoriert

Die Erkennung der verschiedenen Symbole (token) beschreibt der in Abb. 3.1 dargestellte Auto-mat. Die Syntax-Analyse der Kommandozeile wird wie in Abb. 3.2 (S. 89) dargestellt durchge-führt.

Anm.: In Abb. 3.2, S. 89 sind Fehlerzustände nicht dargestellt; von den vier Arten der I/O-Umlenkung >, >>, 2>, < ist jeweils maximal eine zulässig!

Page 95: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 89

TWO_GT

WORD

GTGT

>2

<,>,&,\n,space −>ungetc; out(T_WORD)

< −> out(T_LT)& −> out(T_AMP)

\n −> out(T_NL)

space

> −>

out(T

_GTGT)

> −> out(T_TWO_GT)

else −

> un

getc;

out(T

_GT); else −> word[0] = c; i++

else −>word[i]=c;i++

else −> ungetc; word[0]=’2’; i++

NEUTRAL

Abbildung 3.1: Token-Erkennung

start

infileexpected

outfile

expected

command backgroundcommand

T_WORD

T_LT

T_WORD

T_WORD

T_GT

T_GTGTT_TWO_GT

T_AMPT_NL

Abbildung 3.2: Syntax-Analyse der Kommandozeile

Ablauf-Schema:

Page 96: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

90 KAPITEL 3. SIGNALE

Prompt ausgeben

Kommandozeile lesen

−> Token erkennen und Syntax prüfen

Neuen Prozess erzeugen

ggf. I/O−Umlenkungvornehmen

falls im Hintergrund:Signal Handler einrichten

via ‘‘exec’’ Kommando‘‘starten’’

ggf. Warten

LOOP FOREVER

Signal−Handler einrichten

Abbildung 3.3: Ablauf der MidiShell

Modularisierung:

main.c:

sign.c

sign.h

termination.h

termination.c

Signal-Handlerexit-Status ausgeben

cmd.h

cmd.cSyntax-AnalyseI/O-Umlenkung

gettoken.h

gettoken.c

Token holen

defs.h

grundlegende

Vereinbarungen

Kommando startenggf. Signalhandler

Abbildung 3.4: Struktur der MidiShell

Page 97: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 91

Die Programme im Einzelnen:

Programm 3.15: Midi-Shell: Grundlegende Vereinbarungen (midishell/defs.h)

1 /∗ Grundlegende Vereinbarungen : ∗/2

3 typedef enum {FALSE, TRUE} BOOLEAN;4

5 /∗ Token−Symbole: ∗/6 typedef enum {T_WORD, T_GT, T_GTGT, T_TWO_GT, T_AMP,7 T_LT, T_NL, T_EOF, T_ERR} TOKEN;8

9 # define BADFD −210 # define MAXARG 3211 # define MAXWORD 25612 # define MAXFNAME 256

Programm 3.16: Midi-Shell: Schnittstelle zur Tokenbestimmung (midishell/gettoken.h)

1 # ifdef GET_H2 # include < stdio .h>3 # include "defs .h"4 #else5 /∗ lexikalische Analyse der Kommandozeile ∗/6 extern TOKEN gettoken(char ∗ word );7 extern void skip_line ();8 # endif

Programm 3.17: Midi-Shell: Schnittstelle zum Signalbehandler (midishell/sign.h)

1 # ifdef SIGN_H2 # include < signal .h>3 # include <sys/wait .h>4 # include <unistd .h>5

6 typedef void (∗ Sigfunc )( int );7

8 #else9

10 # define SIGN_H11 /∗ avoid multiple includes ∗/12 # include < signal .h>13

14 typedef void (∗ Sigfunc )( int );15

16 Sigfunc ignoresig ( int );17 /∗ ignore interrupt and avoid zombies18 ∗ just for midishell ( parent )19 ∗/20 Sigfunc ignoresig_bg ( int );21 /∗ ignore interrupt −22 ∗ just for execution of background commands23 ∗/24 Sigfunc entrysig ( int );25 /∗ restore reaction on interrupt ∗/

Page 98: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

92 KAPITEL 3. SIGNALE

26 # endif

Programm 3.18: Midi-Shell: Schnittstelle zur Kommandoausführung (midishell/cmd.h)

1 # ifdef CMD_H2 # include < stdio .h>3 # include < strings .h>4 # include < fcntl .h>5 # include <errno .h>6 # include <unistd .h>7 # include < stdlib .h>8 # include "defs .h"9 # include "gettoken.h"

10 # include "sign .h"11 #else12

13 extern void redirect ( int srcfd , char ∗ srcfile ,14 int dstfd , char ∗ dstfile ,15 int errfd , char ∗ errfile ,16 BOOLEAN append, BOOLEAN bckgrnd);17 /∗ I /O − redirection ∗/18

19 extern int invoke ( int argc , char ∗argv [],20 int srcfd , char ∗ srcfile ,21 int dstfd , char ∗ dstfile ,22 int errfd , char ∗ errfile ,23 BOOLEAN append, BOOLEAN bckgrnd);24 /∗ invoke () − execute simple command ∗/25

26 extern TOKEN command(int ∗waitpid);27 /∗ collect a simple command from stdin28 ∗ by calling gettoken () − do redirection29 ∗ if necessary by redirect () and execute30 ∗ command by invoke ()31 ∗/32

33 # endif

Programm 3.19: Midi-Shell: Schnittstelle zur Ausgabe des Exitstatus (midishell/termination.h)

1 # ifdef TERM_H2

3 # include < stdio .h>4 # define lowbyte (w) (( w) & 0377)5 # define highbyte (w) lowbyte (( w) >> 8)6 # define MAXSIG 197

8 #else9 void statusprt ( int status );

10 # endif

Page 99: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 93

Programm 3.20: Midi-Shell: main-Funktion – Start (midishell/main.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include < string .h>4 # include <unistd .h>5 # include <sys/wait .h>6

7 # include "defs .h"8 # include "sign .h"9 # include "cmd.h"

10 # include "termination .h"11

12 int main() {13 char ∗ prompt;14 int pid , status ;15 TOKEN term;16

17 if ( ( ignoresig (SIGINT) == SIG_ERR) ||18 ( ignoresig (SIGCHLD) == SIG_ERR)) {19 perror ( " ignoresig " );20 exit (1);21 }22

23 prompt = "midish> ";24

25 while (1) {26 printf ( "%s", prompt );27 term = command(&pid);28 if (term == T_ERR) {29 continue;30 }31 if ( (term != T_AMP) && (pid != 0) ) {32 /∗ wait for foreground process −33 ∗ if fg process terminates34 ∗ the signal handler will handle the exit status35 ∗ ( will do his wait !) and36 ∗ the following waitpid will return with −1!37 ∗/38 waitpid ( pid ,& status ,0);39 }40 }41 printf ( "\n\n");42 exit (0);43 }

Page 100: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

94 KAPITEL 3. SIGNALE

Programm 3.21: Midi-Shell: Tokenbestimmung (midishell/gettoken.c)

1 # define GET_H2 # include "gettoken.h"3

4 void skip_line () {5 int c ;6 while( (c = getchar ()) != ’\n’ );7 }8

9 /∗ lexikalische Analyse der Kommandozeile ∗/10

11 TOKEN gettoken(char ∗ word) {12 int c ;13 char ∗ w;14 enum {NEUTRAL, TWO_GT, GTGT, INWORD } state = NEUTRAL;15

16 w = word;17 while ( (c= getchar () ) != EOF ) {18 switch ( state ) {19

20 case NEUTRAL:21 switch (c ) {22 case ’&’:23 /∗ read rest from line : ∗/24 skip_line ();25 return (T_AMP);26 case ’<’ :27 return (T_LT);28 case ’\n’ :29 return (T_NL);30 case ’ ’ :31 case ’\t ’ :32 continue;33 case ’>’ :34 state = GTGT;35 continue;36 case ’2’ :37 state = TWO_GT;38 continue;39 default :40 state = INWORD;41 ∗ w++ = c;42 continue;43 }44 case GTGT:45 if (c == ’>’)46 return (T_GTGT);47 ungetc (c , stdin );48 return (T_GT);49 case TWO_GT:50 if (c == ’>’)51 return (T_TWO_GT);52 ∗w++ = ’2’ ;

Page 101: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 95

53 ungetc (c , stdin );54 state = INWORD;55 continue;56 case INWORD:57 switch (c ) {58 case ’&’:59 case ’<’ :60 case ’>’ :61 case ’\n’ :62 case ’ ’ :63 case ’\t ’ :64 ungetc (c , stdin );65 ∗ w = ’\0’ ;66 return (T_WORD);67 default :68 ∗ w++ =c;69 continue;70 }71 }72 }73 return (T_EOF);74 }

Page 102: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

96 KAPITEL 3. SIGNALE

Programm 3.22: Midi-Shell: Kommandoausführung (midishell/cmd.c)

1 # include < string .h>2 # define CMD_H3 # include "cmd.h"4

5 /∗ Redirection of I /O:6 ∗ after redirect the caller has file descriptor 07 ∗ for input , descriptor 1 for output , and 2 for stderr !8 ∗/9 static void redirect ( int srcfd , char ∗ srcfile ,

10 int dstfd , char ∗ dstfile ,11 int errfd , char ∗ errfile ,12 BOOLEAN append, BOOLEAN bckgrnd) {13 int flags , fd ;14 /∗ we expect for srcfd :15 ∗ 0: nothing to do , −2: redirect 0 ( stdin ) to file16 ∗ (−1 indicates error )17 ∗18 ∗ we expect for dstfd :19 ∗ 1: nothing to do , −2: redirect 1 ( stdout ) to file20 ∗ (with respect to parameter ’append’21 ∗22 ∗ we expect for errfd :23 ∗ 2: nothing to do24 ∗ −2: redirect 2 ( stderr ) to file25 ∗/26

27 if ( ( srcfd == 0) && bckgrnd ) {28 strcpy ( srcfile , "/dev/null");29 /∗ /dev / null −>30 ∗ there is nothing to read , only EOF31 ∗ a background command couldn’t get any32 ∗ input from stdin ;33 ∗/34 srcfd = BADFD;35 /∗ so redirect 0 to srcfile36 ∗ set to /dev / null above37 ∗/38 }39

40 if ( srcfd != 0) {41 /∗ 0 should point to file for input42 ∗/43 if ( close (0) == −1)44 perror ( " close " );45 else if (open( srcfile , O_RDONLY, 0) == −1) {46 fprintf ( stderr , "can’ t open %s\n", srcfile );47 exit (1);48 }49 }50

51 /∗now file is referenced by file descriptor 052 ∗/

Page 103: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 97

53

54 /∗ now the same for std_output ∗/55 if ( dstfd != 1) {56 /∗ output to file (>,>> ( dstfd ==−2)57 ∗/58 if ( close (1) == −1)59 perror ( " close " );60 else {61 flags = O_WRONLY | O_CREAT;62 if (! append) /∗ > file ∗/63 flags |= O_TRUNC;64 else65 flags |= O_APPEND;66 if (open( dstfile , flags , 0666) == −1) {67 /∗ open returns the smallest68 ∗ free file descriptor69 ∗/70 fprintf ( stderr , "can’ t create %s\n", dstfile );71 exit (1);72 }73 }74 }75 /∗ now the same for std_error ∗/76 if ( errfd != 2) {77 /∗ output to file78 ∗/79 if ( close (2) == −1)80 perror ( " close " );81 else {82 flags = O_WRONLY | O_CREAT | O_TRUNC;83

84 if (open( errfile , flags , 0664) == −1) {85 /∗ open returns the smallest86 ∗ free file descriptor87 ∗/88 fprintf ( stderr , "can’ t create %s\n", errfile );89 exit (1);90 }91 }92 }93

94 for ( fd =3; fd < 20; fd++) (void) close ( fd );95 /∗ the caller now only needs 0,1,2 !!! ∗/96 }97

98 /∗ invoke () − execute simple command99 ∗ in a new process

100 ∗/101 static int invoke ( int argc , char ∗ argv [],102 int srcfd , char ∗ srcfile ,103 int dstfd , char ∗ dstfile ,104 int errfd , char ∗ errfile ,105 BOOLEAN append, BOOLEAN bckgrnd) {106 /∗ uses redirect () ∗/

Page 104: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

98 KAPITEL 3. SIGNALE

107 int pid ;108

109 /∗ empty commandline??? ∗/110 if ( argc == 0 )111 return (0);112 switch ( pid = fork () ) {113 case −1:114 fprintf ( stderr , "Can’t create new process\n");115 return (0);116 case 0:117 /∗ CHILD ∗/118

119 redirect ( srcfd , srcfile , dstfd , dstfile , errfd , errfile ,120 append , bckgrnd );121

122 /∗ restore reaction on interrupt and quit ???123 ∗ not necessary , but could be as follows :124 ∗ if (! bckgrnd )125 ∗ entrysig (SIGINT);126 ∗/127

128 /∗ install signal handler for background : ∗/129 if (bckgrnd ) {130 if ( ignoresig_bg (SIGINT) == SIG_ERR) {131 perror ( "ignorsig_bg − SIGINT");132 exit (1);133 }134 }135

136

137 execvp (argv [0], argv );138 /∗ this shouldn ’ t be reached ∗/139 fprintf ( stderr , "can’ t execute %s\n", argv [0]);140 exit (1);141 default :142 /∗ PARENT ∗/143 if ( srcfd > 0 && close ( srcfd ) == −1)144 perror ( " close src" );145 if ( dstfd > 1 && close ( dstfd ) == −1)146 perror ( " close dst" );147 if ( errfd > 2 && close ( errfd ) == −1)148 perror ( " close error" );149

150 if (bckgrnd )151 printf ( "%d\n", pid );152 return ( pid );153 }154 }155

156 TOKEN command(int ∗ waitpid) {157 /∗ int ∗ waitpid : perhaps we have to wait158 ∗ for the command159 ∗160 ∗ return value : T_NL or T_AMP on success, T_ERR on error

Page 105: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 99

161 ∗162 ∗ uses : gettoken (), invoke ()163 ∗/164

165 TOKEN token, term ;166 int argc , srcfd , dstfd , errfd , pid ;167 char ∗ argv [MAXARG+1];168 char srcfile [MAXFNAME+1];169 char dstfile [MAXFNAME+1];170 char errfile [MAXFNAME+1];171 char word[MAXWORD], ∗malloc();172 BOOLEAN append;173

174 argc = 0; srcfd = 0; dstfd = 1; errfd = 2;175 /∗ defaults ∗/176

177 while (1) {178 switch ( token = gettoken (word)) {179 case T_WORD:180 if ( argc == MAXARG) {181 fprintf ( stderr , "Too many args\n");182 break;183 }184 if (( argv [ argc]= malloc ( strlen (word)+1))==NULL) {185 fprintf ( stderr , "Out of arg memory\n");186 break;187 }188 strcpy (argv [ argc ], word );189 argc++;190 continue;191 case T_LT:192 if ( srcfd != 0) {193 fprintf ( stderr , "syntax error : EXTRA <\n");194 skip_line ();195 return T_ERR;196 }197 if ( gettoken ( srcfile ) != T_WORD) {198 fprintf ( stderr , "syntax error : Illegal <\n");199 skip_line ();200 return T_ERR;201 }202 /∗ we have to redirect 0 to a file ∗/203 srcfd = BADFD;204 continue;205 case T_GT:206 case T_GTGT:207 if ( dstfd != 1) {208 fprintf ( stderr , "syntax error : EXTRA > or >>\n");209 skip_line ();210 return T_ERR;211 }212 if ( gettoken ( dstfile ) != T_WORD) {213 fprintf ( stderr , "syntax error : Illegal > or >>\n");214 skip_line ();

Page 106: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

100 KAPITEL 3. SIGNALE

215 return T_ERR;216 }217 dstfd = BADFD;218 append = ( token == T_GTGT);219 continue;220 case T_TWO_GT:221 if ( errfd != 2) {222 fprintf ( stderr , "syntax error : EXTRA 2>\n");223 skip_line ();224 return T_ERR;225 }226 if ( gettoken ( errfile ) != T_WORD) {227 fprintf ( stderr , "syntax error : Illegal 2>\n");228 skip_line ();229 return T_ERR;230 }231 errfd = BADFD;232 continue;233

234 case T_AMP:235 case T_NL:236 term = token ;237

238 /∗ one simple command is read ∗/239 argv [ argc ] = NULL;240

241 /∗ Eingabe von ’> file ’ allein : loeschen / anlegen242 ∗ einer Datei :243 ∗/244 if ( ( argc == 0 ) && (dstfd == BADFD) && (!append)) {245 dstfd = open( dstfile , O_WRONLY | O_CREAT | O_TRUNC, 0664);246 close ( dstfd );247 return T_NL;248 }249

250 pid = invoke ( argc , argv , srcfd , srcfile , dstfd ,251 dstfile , errfd , errfile ,252 append , term == T_AMP);253 ∗ waitpid = pid ;254

255 while (−−argc >= 0)256 free (argv [ argc ]);257 return (term );258 case T_EOF:259 printf ( "\n\n");260 exit (0);261 default : exit (1); /∗ not reached ! ∗/262 }263 }; /∗end of while (1) ∗/264 /∗ if reached , then error : ∗/265 return T_ERR;266 }

Page 107: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 101

Programm 3.23: Midi-Shell: Signalbehandler (midishell/sign.c)

1 # define SIGN_H2

3 # include < stdio .h>4 # include "sign .h"5

6 void shell_handler ( int sig ){7 if ( ( sig == SIGCHLD) || (sig == SIGCLD)) {8 int status ; long gone ;9 gone = waitpid (0, &status , WNOHANG);

10 if (gone <= 0 ) return ;11 printf ( "Terminated: %ld with ", gone);12 if ( status & 0177)13 printf ( "Signal %d\n", status & 0177);14 else15 printf ( " exit status : %d\n", ( status >> 8)&0xff );16 }17 return ;18 }19

20 struct sigaction newact , oldact ;21

22 Sigfunc ignoresig ( int sig ) {23 static int first = 1;24 newact . sa_handler = shell_handler ;25 if ( first ) {26 first = 0;27 if ( sigemptyset (&newact.sa_mask) < 0)28 return SIG_ERR;29 newact . sa_flags = 0;30 newact . sa_flags |= SA_RESTART;31 if ( sigaction ( sig , &newact, &oldact ) < 0)32 return SIG_ERR;33 else34 return oldact . sa_handler ;35 } else {36 if ( sigaction ( sig , &newact, NULL) < 0)37 return SIG_ERR;38 else39 return NULL;40 }41 }42

43 struct sigaction newact_bg , oldact_bg ;44

45 Sigfunc ignoresig_bg ( int sig ) {46 newact_bg . sa_handler = SIG_IGN;47 if ( sigemptyset (&newact_bg.sa_mask) < 0)48 return SIG_ERR;49 newact_bg . sa_flags = 0;50 newact_bg . sa_flags |= SA_RESTART;51 if ( sigaction ( sig , &newact_bg, &oldact_bg ) < 0)52 return SIG_ERR;

Page 108: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

102 KAPITEL 3. SIGNALE

53 else return oldact_bg . sa_handler ;54 }55

56 Sigfunc entrysig ( int sig ) {57 if ( sigaction ( sig , &oldact , NULL) < 0 )58 return SIG_ERR;59 else return NULL;60 }

Programm 3.24: Midi-Shell: Termination (midishell/termination.c)

1 # define TERM_H2 # include "termination .h"3

4 static char ∗ sigmsg [] = {5 "" ,6 "Hangup", "Interrupt " , "Quit" ,7 " Illegal instruction " , "Trace trap" ,8 "IOT instruction" , "EMT instruction",9 " Floating point exception" ,

10 " Kill " , "Bus error" ,11 "Segmentation violation" ,12 "Bad arg to system call",13 "Write on pipe", "Alarm clock",14 "Terminate signal" ,15 "User signal 1", "User signal 2",16 "Death of child", "Power fail"17 };18

19 void statusprt ( int status ) {20 int code ;21

22 if ( lowbyte ( status ) == 0) {23 // normal termination24 code = highbyte ( status );25 printf ( " Exit code %d\n", code);26 } else {27 if (( code = ( status & 0177)) <= MAXSIG)28 printf ( "%s", sigmsg[ code ]);29 else30 printf ( "Signal# %d", code );31 if (( status & 0200) == 0200)32 printf ( " − core dumped");33 printf ( "\n" );34 }35 }

Page 109: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 103

Programm 3.25: Midi-Shell: Testprogramm 1 (midishell/sleepwell.c)

1 # include <unistd .h>2 # include < stdlib .h>3

4 int main() {5 sleep (20);6 exit (0);7 }

Programm 3.26: Midi-Shell: Testprogramm 2 (midishell/read-something.c)

1 # include < stdio .h>2 # include < stdlib .h>3

4 int main() {5 int n;6 printf ( "Give number:");7 if ( scanf ( "%d", &n) == 1) {8 printf ( "got : %d\n", n);9 exit (0);

10 } else {11 exit (1);12 }13 }

Das Makefile:

# eine kleine Shell - die midishell

midish: main.o cmd.o sign.o termination.o gettoken.ogcc -Wall -o midish main.o cmd.o sign.o termination.o gettoken.o# ausfuehrbares Programm: midishgcc -Wall -o sleepwell sleepwell.c# sleepwell: ein Dauerlaeufergcc -Wall -o read-something read-something.c# read-something: lies von stdin

main.o: main.c defs.h sign.h cmd.h termination.hgcc -Wall -c main.c

cmd.o: cmd.c cmd.h gettoken.h defs.h sign.hgcc -Wall -c cmd.c

gettoken.o: gettoken.c gettoken.h defs.hgcc -Wall -c gettoken.c

sign.o: sign.c sign.hgcc -Wall -c sign.c

termination.o: termination.c termination.hgcc -Wall -c termination.c

.PHONY: clean realcleanclean:

rm -f *.o corerealclean:

rm -f *.o core midish sleepwell read-something

Page 110: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

104 KAPITEL 3. SIGNALE

Ausführung:

hypatia$ makegcc -Wall -c main.cgcc -Wall -c cmd.cgcc -Wall -c sign.cgcc -Wall -c termination.cgcc -Wall -c gettoken.cgcc -Wall -o midish main.o cmd.o sign.o termination.o gettoken.o# ausfuehrbares Programm: midishgcc -Wall -o sleepwell sleepwell.c# sleepwell: ein Dauerlaeufergcc -Wall -o read-something read-something.c# read-something: lies von stdin

hypatia$ midishmidish>ps

PID TTY TIME CMD212 tty1 00:00:00 bash846 pts/0 00:00:00 bash895 pts/0 00:00:00 midish896 pts/0 00:00:00 ps

midish>sleepwell# Eingabe von ctrl-cInterruptmidish>sleepwell &898midish>ps

PID TTY TIME CMD212 tty1 00:00:00 bash846 pts/0 00:00:00 bash895 pts/0 00:00:00 midish898 pts/0 00:00:00 sleepwell899 pts/0 00:00:00 ps

midish> # ctrl-cmidish>ps

PID TTY TIME CMD212 tty1 00:00:00 bash846 pts/0 00:00:00 bash895 pts/0 00:00:00 midish898 pts/0 00:00:00 sleepwell900 pts/0 00:00:00 ps

midish>read-somethingGive number:5got: 5

Page 111: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 105

midish>read-something &902Give number:midish>5can’t execute 5Exit code 1midish>cat < textdies istein Textmidish>cat < text > new > very_newsyntax error: EXTRA > or >>midish>cat -?cat: invalid option -- ?Try ‘cat --help’ for more information.Exit code 1midish>cat -? 2> errExit code 1midish>cat errcat: invalid option -- ?Try ‘cat --help’ for more information.midish> # ctrl-d

hypatia$

Page 112: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

106 KAPITEL 3. SIGNALE

3.10 Überblick der Signale aus dem POSIX-Standard

Signal Voreinstellung BeschreibungSIGABRT A Process abort signal.SIGALRM T Alarm clock.SIGBUS A Access to an undefined portion of a memory object.SIGCHLD I Child process terminated, stopped, or continued.SIGCONT C Continue executing, if stopped.SIGFPE A Erroneous arithmetic operation.SIGHUP T Hangup.SIGILL A Illegal instruction.SIGINT T Terminal interrupt signal.SIGKILL T Kill (cannot be caught or ignored).SIGPIPE T Write on a pipe with no one to read it.SIGQUIT A Terminal quit signal.SIGSEGV A Invalid memory reference.SIGSTOP S Stop executing (cannot be caught or ignored).SIGTERM T Termination signal.SIGTSTP S Terminal stop signal.SIGTTIN S Background process attempting read.SIGTTOU S Background process attempting write.SIGUSR1 T User-defined signal 1.SIGUSR2 T User-defined signal 2.SIGPOLL T Pollable event.SIGPROF T Profiling timer expired.SIGSYS A Bad system call.SIGTRAP A Trace/breakpoint trap.SIGURG I High bandwidth data is available at a socket.SIGVTALRM T Virtual timer expired.SIGXCPU A CPU time limit exceeded.SIGXFSZ A File size limit exceeded.

Voreinstellung BeschreibungT Abbruch des Prozesses. Bei dem bei wait() zurückgelieferten Status ist

WIFSIGNALED wahr und über WTERMSIG lässt sich das Signal ermitteln.A Analog zu T. Hinzu kommt möglicherweise noch die Erzeugung eines

Speicherauszugs (in der Datei core). Letzteres lässt sich mit WCOREDUMPuntersuchen.

I Das Signal wird ignoriert.S Der Prozess wird gestoppt.C Der Prozess wird fortgesetzt.

Tabelle 3.2: Im POSIX-Standard genannte Signale (Quelle: www.opengroup.org)

Die Tabelle 3.2 liefert einen Überblick aller vom POSIX-Standard genannten Signale. EinzelneImplementierungen können noch weitere Signale unterstützen. Die Signale lassen sich dabei inmehrere Gruppen aufteilen:

• Programmierfehler: SIGBUS, SIGFPE, SIGILL, SIGSEGV und SIGSYS.

• Ressourcenverbrauch: SIGVTALRM, SIGXCPU und SIGXFSZ.

Page 113: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

3.10. ÜBERBLICK DER SIGNALE AUS DEM POSIX-STANDARD 107

• Prozeßkontrolle: SIGCONT, SIGKILL, SIGSTOP, SIGTERM und SIGTRAP.

• Sitzungskontrolle: SIGHUP, SIGINT, SIGQUIT und SIGTSTP.

• Ereignis-Indikatoren:SIGALRM,SIGCHLD, SIGPIPE,SIGPOLL,SIGPROF,SIGTTIN,SIGTTOU,SIGURG, SIGUSR1, SIGUSR2 und SIGVTALRM.

Page 114: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

108 KAPITEL 3. SIGNALE

Page 115: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Kapitel 4

Inter-Prozess-Kommunikation (IPC)

4.1 Einführung

Jeder UNIX-Prozess besitzt seinen eigenen Context. Innerhalb eines Prozesses können die ver-schiedenen Moduln über Parameter und Rückgabewerte bei Funktionsaufrufen oder über globa-le Variablen Daten austauschen. Wollen jedoch zwei eigenständige Prozesse Daten miteinanderaustauschen, so kann dies nur über den Kernel via System Calls erfolgen. Denn der Kernel ver-hindert unkontrollierte Übergriffe eines Prozesses in den Adressraum eines anderen Prozesses.

KERNEL

user processuser process

Abbildung 4.1: IPC – nur über den Kernel

Bei diesem Konzept müssen beide Prozesse explizit der Kommunikation zustimmen. Der UNIX-Kernel bietet mit seinen Interprocess Communication Facilities nur die Möglichkeit zur Kommuni-kation an.

109

Page 116: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

110 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC)

Netzwerk-Kommunikation

Das UNIX-IPC-Konzept lässt sich orthogonal erweitern von der lokalen Kommunikation zwi-schen Prozessen innerhalb eines Systems auf Netzwerk-Kommunikation zwischen Prozessen,die auf verschiedenen System laufen. Vor allem die Entwicklungsarbeiten der University of Ca-lifornia at Berkeley brachte hier einige neue Ansätze zur Interprocess Communication in UNIXein.

User Process UserProcess

KERNEL KERNEL

Abbildung 4.2: IPC – auch über Rechnergrenzen

Für die Prozesse kann es völlig transparent sein, wo der jeweilige Partnerprozess abläuft.

4.2 IPC - Client-Server Beispiel

Der Client liest einen Dateinamen von stdin ein und schreibt ihn in den IPC-Kanal. Anschliessendwartet er auf die Reaktion des Servers.

Der Server liest einen Dateinamen von dem IPC-Kanal und versucht die Datei zu öffnen. Gelingtes dem Server, die Datei zu öffnen, kopiert er ihren Inhalt in den IPC-Kanal. Lässt sich die Dateinicht öffnen, schickt der Server eine Fehlermeldung über den IPC-Kanal.

Der Client wartet auf Daten am IPC-Kanal, er liest sie von dort und schreibt sie nach stdout.Konnte der Server die Datei öffnen, zeigt der Client so den Dateiinhalt an, sonst kopiert derClient die Fehlermeldung durch.

file contenstor errormessage

client server file

filename

file contents or error message

stdin

stdout

filename

Abbildung 4.3: Client-Server-Beispiel

Die beiden gestrichelten Pfeile zwischen dem Server und dem Client entsprechen dem jeweiligen”Interprocess Communication” Kanal.

Page 117: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

4.3. SYSTEM CALLS “DUP()”, “DUP2()” 111

4.3 System Calls “dup()”, “dup2()”

Im Zusammenhang mit unnamed pipes ist der Systemaufruf dup nützlich:

#include <unistd.h>int dup(int oldfd) /*duplicate file descriptor*/int dup2(int oldfd, int newfd) /*newfd is closed before (if open)*//* returns new file descriptor or -1 on error */

dup verdoppelt einen bestehenden Filedeskriptor und liefert als Resultat einen neuen Filede-skriptor (mit der kleinsten verfügbaren Nummer), der mit der gleichen Datei oder der gleichenPipe verbunden ist. Beide File Deskriptoren haben denselben Positionszeiger. Damit kann z.B.ein Filedeskriptor mit der Nummer 0 erhalten werden, falls dieser vorher geschlossen wurde.Falls so mit exec ein Programm ausgeführt wird, das von Filedeskriptor 0 liest, kann es so dazugebracht werden, aus einer Pipe zu lesen. Ähnlich kann so der Filedeskriptor 1 ”manipuliert”werden.

4.4 Unnamed Pipes

• System Call pipe()

#include <unistd.h>int pipe( int pipefd[2] ) /* create a pipe */

/* pipefd[2]: file descriptors *//* returns 0 on success or -1 on error */

Pipes sind der älteste IPC-Mechanismus. Seit Mitte der 70er Jahre existieren sie auf allen Versio-nen und Arten von UNIX.

Eine Pipe besteht aus einem unidirektionalen Datenkanal. Zwei File Deskriptoren repräsentierendie Pipe im User Prozess. Der System Call pipe() kreiert die Pipe, er liefert die beiden Enden alsFile Deskriptoren über sein Vektorargument an den Prozess. Dabei ist pipefd[1] das ”Ende” zumSchreiben, pipefd[0] das ”Ende” zum Lesen (siehe Abb. 4.4, S. 112)

In dieser Konstellation lässt sich die Pipe nur als “Zwischenspeicher” für Daten außerhalb desUser Adressraums benutzen.

Page 118: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

112 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC)

write fd

read fd

user process

Kernel

pipe

flow of data

Abbildung 4.4: Unnamed Pipe - erster Schritt

VORSICHT! Der Kernel synchronisiert Prozesse, die in Pipes schreiben oder aus Pipes lesen –Producer / Consumer Modell. Sollte hier in dem Ein-Prozess-Beispiel ein read() oder write SystemCall blockieren, entsteht ein Deadlock, denn der blockierte Prozess kann den befreienden, kom-plementären write() oder read() System Call nicht absetzen.

Kommunikation zwischen verwandten Prozessen

Durch einen fork() System Call entstehen zwei eigenständige, aber verwandte Prozesse, die ins-besondere die gleichen I/O-Verbindungen besitzen (siehe Abb. 4.5, S. 112).

write fd

read fd

flow of data

pipe

Kernel

child processparent process

read fd

write fd

fork

Abbildung 4.5: Unnamed Pipe – zweiter Schritt

Schließt nun ein Prozess sein Lese-Ende und der andere Prozess sein Schreib-Ende, so entstehtein unidirektionaler Kommunikationspfad zwischen den beiden Prozessen (Abb. 4.6, S. 113).

Page 119: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

4.4. UNNAMED PIPES 113

write fd

read fd

flow of data

pipe

Kernel

child processparent process

Abbildung 4.6: Unnamed Pipe – dritter Schritt

Wiederholen die Prozesse die fork, pipe() und close System Calls, entstehen längere Pipelines.Dieses Datenverarbeitungs-Prinzip ist untrennbar mit UNIX verbunden.

Beispiel: Pipe zwischen zwei Prozessen

Das Programm kreiert eine pipe und einen zweiten Prozess, dem diese beiden pipe-Deskriptorenverebt werden. Der Erzeugerprozess schreibt Text in die Pipe und wartet auf das Ableben desKindprozesses. Der Kindprozess liest Text aus der Pipe, schreibt ihn nach stdout und beendetseine Ausführung. Folgende Schritte sind der Reihe nach auszuführen:

1. Parent führt pipe() aus

2. Parent führt fork() aus

3. Child schließt sein Schreib-Ende der Pipeund wartet an seinem Lese-Ende der Pipe.

4. Parent schließt sein Lese-Ende der Pipe,schreibt Text in sein Schreib-Ende der Pipe,und führt wait() für sein Kind aus.

5. Child liest von seinem Lese-Ende, gibt gelesenen Text ausund terminiert.

6. Parent hat auf Child gewartet und kann jetzt auch terminieren.

Page 120: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

114 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC)

Realisierung:

Programm 4.1: Einfache Kommunikation via Pipe (pipe.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4 # include <sys/wait .h>5

6 # define RD_FD 07 # define WR_FD 18

9 int main() {10 int childpid , gone , pipefd [2];11

12 if ( pipe ( pipefd ) < 0 ) {13 perror ( "pipe" ); exit (1);14 }15

16 switch( childpid = fork () ){17 case −1:18 perror ( "fork" ); exit (1);19 case 0: /∗ child : ∗/ {20 char buf [ 128 ]; int nread ;21

22 close ( pipefd [ WR_FD ] );23

24 /∗ read from pipe and copy to stdout ∗/25 nread = read ( pipefd [ RD_FD ], buf , sizeof ( buf ));26 write (1, buf , nread );27 break;28 }29

30 default : /∗ parent : ∗/31

32 close ( pipefd [ RD_FD ] );33 write ( pipefd [ WR_FD ], "hello world\n", 12 );34

35 do /∗ wait for child ∗/ {36 if ( (gone = wait ( ( int ∗) 0 )) < 0 ) {37 /∗no interest for exit status ∗/38 perror ( "wait" ); exit (2);39 }40 } while ( gone != childpid );41 }42 exit (0);43 }

Page 121: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

4.5. CLIENT-SERVER MIT “UNNAMED PIPES” 115

4.5 Client-Server mit “Unnamed Pipes”

• Bidirektional

Durch eine Pipe fließen Daten nur in genau eine Richtung. Zur Realisierung unseres Client-ServerBeispiels benötigen wir aber einen bidirektionalen Kommunikationskanal. Wir müssen dazu zweiPipes kreieren und eine Pipe für jede Richtung konfigurieren.

parent process

(client)

file con

tent

filenam

e

file

co

nte

nt

child process

(server)

pipe_2

pipe_1

file

co

nte

nt

filenam

e file

nam

e

stdout stdin

Abbildung 4.7: Client-Server mit bidirektionaler Kommunikation

Vorgehen:

1. Pipe1 und Pipe2 kreieren

2. fork() ausführen

3. Linker Prozess (Erzeuger) schließt

• Lese-Ende von Pipe1 und

• Schreib-Ende von Pipe2

4. Rechter Prozess (Kind) schließt

• Schreib-Ende von Pipe1 und

• Lese-Ende von Pipe2

Die Realisierung zeigen die Programme 4.2 (S. 116), 4.4 (S. 117), 4.6 (S. 118),

Page 122: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

116 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC)

Programm 4.2: Hauptprogramm zum Client/Server-Beispiel (cli-srv/main.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <unistd .h>4 # include <sys/wait .h>5

6 # include " client .h"7 # include "server .h"8

9 # define RD_FD 010 # define WR_FD 111

12 int main() {13 int childpid , gone , pipe_1 [2], pipe_2 [2];14

15 if ( pipe ( pipe_1 ) < 0 || pipe ( pipe_2 ) < 0 ) {16 perror ( "pipe (): can’t creat pipes" ); exit (1);17 }18 if ( ( childpid = fork ()) < 0 ) {19 perror ( "fork" ); exit (1);20 }21

22 if ( childpid > 0 ) { // client / parent23 printf ( " Client/Parent: my pid is %d\n",24 ( int ) getpid () );25

26 close ( pipe_1 [ RD_FD ] ); close ( pipe_2 [ WR_FD ] );27

28 client ( pipe_2 [ RD_FD ], pipe_1 [ WR_FD ] );29

30 /∗ wait for child ∗/31 do {32 if ( (gone = wait ( ( int ∗) 0 )) < 0 ) {33 perror ( "wait" ); exit (2);34 }35 } while ( gone != childpid );36

37 printf ( "Cli/Par: server/child %d terminated\n", gone );38 printf ( "Cli/Par: going to exit\n" );39

40 } else { // server / child41 printf ( "Server/Child: after fork, my pid is %d\n",42 ( int ) getpid () );43

44 close ( pipe_1 [ WR_FD ] ); close ( pipe_2 [ RD_FD ] );45

46 server ( pipe_1 [ RD_FD ], pipe_2 [ WR_FD ] );47

48 printf ( "Server/Child: going to exit\n" );49 }50 exit ( 0 );51 }

Page 123: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

4.5. CLIENT-SERVER MIT “UNNAMED PIPES” 117

Programm 4.3: Client (cli-srv/client.h)

1 /∗2 ∗ read line ( filename ( from stdin , write it to IPC−channel,3 ∗ copy text ( file content ) from IPC−channel to stdout4 ∗/5

6 # ifndef CLIENT_H7 # define CLIENT_H8 extern void client ( int readfd , int writefd );9 # endif

Programm 4.4: Client (cli-srv/client.c)

1 # define CLIENT_H2 # include " client .h"3

4 # include < stdio .h>5 # include <unistd .h>6 # include < stdlib .h>7 # include < string .h>8

9 # define BUFSIZE 25610

11 void client ( int readfd , int writefd ) {12 char buf [ BUFSIZ ];13 int n;14

15 /∗ read filename from stdin , write it to IPC−channel ∗/16 printf ( "give filename: " ); /∗ not safe !!! ∗/17

18 if ( fgets ( buf , BUFSIZ, stdin ) == (char ∗) 0 ) {19 fprintf ( stderr , " Client : filename read error" );20 exit (1);21 }22

23 n = strlen ( buf );24 if ( buf [ n−1 ] == ’\n’ ) n−−; /∗ zap NL ∗/25

26 if ( write ( writefd , buf , n ) != n ) {27 perror ( "write" ); exit (2);28 }29

30 /∗ read data from IPC−channel, write it to stdout ∗/31 while ( (n = read ( readfd , buf , BUFSIZ )) > 0 )32 if ( write ( 1, buf , n ) != n ) {33 perror ( "write" ); exit (3);34 }35

36 if ( n < 0 ) {37 fprintf ( stderr , "read (): Client : can’t read from IPC−channel" );38 exit (4);39 }40 }

Page 124: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

118 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC)

Programm 4.5: Server (cli-srv/server.h)

1 /∗2 ∗ read filename from IPC−channel, open this file ,3 ∗ copy data from file to IPC−channel.4 ∗/5 # ifndef SERVER_H6 # define SERVER_H7 extern void server ( int readfd , int writefd );8 # endif

Programm 4.6: Server (cli-srv/server.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4 # include < string .h>5 # include < fcntl .h>6

7 # define BUFSIZE 5128

9 void server ( int readfd , int writefd ) {10 char buf [ BUFSIZ ];11 int n, fd ;12

13 /∗ read filename from IPC−channel ∗/14 if ( (n = read ( readfd , buf , BUFSIZ )) < 0 ) {15 perror ( "read" ); exit (1);16 }17 buf [ n ] = ’\0’ ;18

19 /∗ try to open file ∗/20

21 if ( ( fd = open( buf , O_RDONLY )) < 0 ) {22 /∗ Format and send error mesg to client ∗/23 char errmesg [ BUFSIZ ];24

25 (void) sprintf ( errmesg , "Server : can’t open infile (%.∗s)\n",26 BUFSIZ/2, buf );27 n = strlen ( errmesg );28 if ( write ( writefd , errmesg , n ) != n ) {29 perror ( "write" ); exit (2);30 }31 return ;32 }33

34 while ( (n = read ( fd , buf , BUFSIZ )) > 0 )35 if ( write ( writefd , buf , n ) != n ) {36 perror ( "write" ); exit (3);37 }38 if ( n < 0 ) {39 fprintf ( stderr , "Server : can’t read file " );40 exit (3);41 }42 }

Page 125: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

4.5. CLIENT-SERVER MIT “UNNAMED PIPES” 119

Page 126: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

120 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC)

4.6 Standard I/O Bibliotheksfunktion

• popen() und pclose()

#include <stdio.h>

FILE *popen( char * cmd, char * mode )/* create a pipe to a cmd:

cmd: cmd to be executedmode: read from or write to pipe/cmd

returns file pointer on success or NULL on error

*/

int pclose( FILE * fp )/* close pipe with cmd *//* returns exit status of cmd or -1 on error */

• Beschreibung

Die Funktion popen() aus der Standard I/O Bibliothek kreiert eine unidirektionale (!) Pipe undeinen neuen Prozess, der von der Pipe liest oder in die Pipe schreibt. In dem neuen Prozess starteteine Shell und führt die mitgegebene Kommandozeile cmd (unter Berücksichtigung von PATH)aus. Die Pipe wird abhängig vom Argument mode (entweder ”r” oder ”w”!) so konfiguriert,dass sie stdout oder stdin des erzeugten Kommandos mit dem aufrufenden Prozess verbindet.Der aufrufende Prozess erhält sein Pipe-Ende als ”File Pointer” von popen.

pclose() schließt eine mit popen() geöffnete I/O-Verbindung ab (NICHT fclose()). Die Funktionblockiert bis das Kommando terminiert und liefert den Exit-Status des Kommandos zurück.

Page 127: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

4.6. STANDARD I/O BIBLIOTHEKSFUNKTION 121

• Beispiel:

Programm 4.7: Beispiel mit popen(), pclose() (popen/popen.c)

1 # include < stdio .h>2 # include < stdlib .h>3

4 # define BUFFER_SIZE 10245

6 int main() {7

8 char buf [ BUFFER_SIZE ];9 FILE ∗ fp ;

10

11 if ( ( fp = popen( "/bin/pwd", "r" )) == (FILE ∗) 0 ) {12 perror ( "popen()" );13 exit (1);14 }15

16 if ( fgets ( buf , BUFFER_SIZE, fp ) == (char ∗) 0 ) {17 fprintf ( stderr , " fgets error" );18 exit (2);19 }20

21 printf ( "The current working directory is:\n" );22 printf ( "\t%s", buf ); /∗ pwd inserts newline ∗/23

24 pclose ( fp );25 exit (0);26 }

Page 128: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

122 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC)

4.7 Über Standarddeskriptoren in Pipe schreiben / lesen

Programm 4.8: 0/1 auf Pipe (pipe-2.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4 int main() { int pid ; int pfd [2];5 pipe ( pfd );6 pid = fork ();7

8 if ( pid < 0) { perror ( "fork" ); exit (1);}9

10 if ( pid == 0) { /∗CHILD∗/11 char buf [128]; int n;12

13 close (0); dup(pfd [0]);14

15 if ( (n = read (0, buf , 15)) < 0 ) {16 perror ( "read" ); exit (1);17 } else {18 buf [n] = ’\0’ ;19 printf ( " child : read > %s <\n", buf);20 exit (0);21 }22 } else { /∗PARENT∗/23

24 close (1); dup(pfd [1]);25

26 if ( write (1, " I ’m your parent", 15) < 0) {27 perror ( "write" ); exit (2);28 }29 exit (0);30 }31 }

Page 129: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

4.8. TERMINATION VON PIPELINE-VERBINDUNGEN 123

4.8 Termination von Pipeline-Verbindungen

Programm 4.9: Termination? (pipe-3.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4

5 int main() {6 int pid ; int pfd [2];7

8 pipe ( pfd );9 pid = fork ();

10

11 if ( pid < 0) { perror ( "fork" ); exit (1); }12

13 if ( pid == 0) { /∗CHILD∗/14 char buf [128]; int n;15

16 close (0); dup(pfd [0]);17

18 while( (n = read (0, buf ,7)) > 0) {19 buf [n] = ’\0’ ;20 printf ( " child : read > %s <\n", buf);21 }22 exit (0);23 } else { /∗PARENT∗/24 close (1);25 dup(pfd [1]);26 if ( write (1, " I ’m your parent", 15) < 0) {27 perror ( "write" ); exit (2);28 }29 exit (0);30 }31 }

Page 130: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

124 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC)

Übersetzung und Ausführung:

spatz$ gcc -Wall pipe-3.cspatz$ a.outspatz$ child: read > I’m you <child: read > r paren <child: read > t <

spatz$ psPID TTY TIME CMD

5692 pts/3 00:00:00 bash5705 pts/3 00:00:00 a.out5706 pts/3 00:00:00 ps

spatz$ kill 5705spatz$ ps

PID TTY TIME CMD5692 pts/3 00:00:00 bash5707 pts/3 00:00:00 ps

spatz$

So terminiert alles:

Programm 4.10: Termination! (pipe-4.c)

1 // Termination !!!2 # include < stdio .h>3 # include <unistd .h>4 # include < stdlib .h>5

6 int main() {7 int pid ; int pfd [2];8 pipe ( pfd ); pid = fork ();9 if ( pid < 0) { perror ( "fork" ); exit (1); }

10

11 if ( pid == 0) { /∗CHILD∗/12 char buf [128]; int n;13 close (0); dup(pfd [0]);14

15 close ( pfd [1]); /∗ <−−−−−−−−−−−−− ∗/16

17 while( (n = read (0, buf ,7)) > 0) {18 buf [n] = ’\0’ ;19 printf ( " child : read > %s <\n", buf);20 }21 exit (0);22 } else { /∗PARENT∗/23 close (1); dup(pfd [1]);24 if ( write (1, " I ’m your parent", 15) < 0) {25 perror ( "write" ); exit (2);26 }27 exit (0);28 }29 }

Page 131: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

4.9. WIE DIE SHELL EINE PIPELINE MACHT 125

4.9 Wie die Shell eine Pipeline macht

Programm 4.11: Pipelining der Shell (pipe-5.c)

1 /∗ ls −l | wc −l ∗/2 # include < stdio .h>3 # include <unistd .h>4 # include < stdlib .h>5

6 int main() {7 int pid1 , pid2 ; int pfd [2];8

9 pipe ( pfd );10

11 pid1 = fork ();12 if ( pid1 < 0) { perror ( "fork" ); exit (1); }13 if ( pid1 == 0) { /∗CHILD 1: ’’ wc −l ’’ ∗/14

15 close (0); dup(pfd [0]);16 close ( pfd [1]); /∗ without this line ??? ∗/17 execlp ( "wc", "wc", "−l" , NULL);18 perror ( "exec" ); exit (2);19

20 } else { /∗PARENT∗/21

22 pid2 = fork ();23 if ( pid2 < 0) { perror ( "fork" ); exit (3); }24 if ( pid2 == 0) { /∗CHILD 2: ’’ ls −l ’’ ∗/25

26 close (1); dup(pfd [1]);27 execlp ( " ls " , " ls " , "−l", NULL);28 perror ( "exec" ); exit (4);29 } else { /∗PARENT∗/30 close ( pfd [1]); /∗ Why? ∗/31 close ( pfd [0]); /∗ nice ! ∗/32 /∗ terminate , don’ t wait ∗/33 exit (0);34 }35 }36 }

Page 132: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

126 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC)

4.10 SIGPIPE

Das Kommando xlsfonts produziert sehr große Ausgaben, mehr als auf einmal in eine Pipelinepasst – diese werden in eine Pipeline gelenkt – über das Kommando head lesen wir ein bisschenheraus und terminieren dann – was passiert mit xlsfonts???

Programm 4.12: Schreiben in eine Pipe ohne Leseende (pipe-6.c)

1 # include < stdio .h>2 # include <unistd .h>3 # include < stdlib .h>4 # include <sys/wait .h>5 int main() {6 int pid1 , pid2 ; int pfd [2];7 pipe ( pfd );8 pid1 = fork ();9 if ( pid1 < 0) { perror ( "fork" ); exit (1); }

10 if ( pid1 == 0) { /∗CHILD 1 fuer ’’ head −2’’ ∗/11 close (0); dup(pfd [0]);12 close ( pfd [1]); /∗ without this line ? ∗/13 execlp ( "head", "head", "−2", NULL);14 perror ( "exec" ); exit (2);15 } else { /∗PARENT∗/16 pid2 = fork ();17 if ( pid2 < 0) { perror ( "fork" ); exit (3); }18 if ( pid2 == 0) { /∗CHILD 2 fuer ’’ xlsfonts ’’ ∗/19 close (1); dup(pfd [1]);20 close ( pfd [0]); /∗without this line ???∗/21 execlp ( " xlsfonts " , " xlsfonts " , NULL);22 perror ( "exec" ); exit (4);23 } else { /∗PARENT∗/24 int status ;25 close ( pfd [0]); close ( pfd [1]); /∗ without? ∗/26 /∗ wait for second child ∗/27 waitpid ( pid2 , &status , 0);28 /∗ terminated because of Signal ?∗/29 if ( status & 0x7F) {30 printf ( "terminated by signal %d\n", status & 0x7F);31 }32 exit (0);33 }34 }35 }

Übersetzung und Ausführung:

spatz$ gcc -Wall pipe-6.cspatz$ a.out-adobe-courier-bold-o-normal--10-100-75-75-m-60-iso10646-1-adobe-courier-bold-o-normal--10-100-75-75-m-60-iso8859-1terminated by signal 13spatz$

Page 133: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Kapitel 5

Netzwerk-Kommunikation

5.1 Übersicht

Netzwerk

• räumlich verteiltes System von Rechnern, Steuereinheiten und Peripheriegeräten, verbun-den mit Datenübertragungseinrichtungen

• aktive / passive Komponenten

Unterscheidung nach Ausbreitung

• globales Netz (GAN global area network)Internet, EUNet, VNET (IBM), u.a.

• Weitverkehrsnetz (WAN wide area network)DATEV-Netz, DFN (Deutsches Forschungsnetz)

• lokale Netze (LAN local area network, WLAN wireless LAN)innerhalb eines Unternehmens; i.a. mehrere, die selbst wieder vernetzt sind☞ Front-end-Lan (Netz innerhalb einer Abteilung / Instituts)☞ Backbone-LAN (Verbindung von Front-end-LAN’s

Netzwerktopologie

• physikalische / logische Verbindung der Rechner im Netz (Stern, Ring, Bus)

Protokolle

• Regeln (Vereinbarungen), nach denen Kommunikationspartner (Rechner) eine Verbindungaufbauen, die Kommunikation durchführen und die Verbindung wieder abbauen

127

Page 134: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

128 KAPITEL 5. NETZWERK-KOMMUNIKATION

Grundlegendes herstellerunabhängiges Konzept:

DIN/ISO-OSI-Referenzmodell

• OSI - Open Systems Interconnection

Transport

Session

Presentation

Application Anwendungs−schicht

Darstellungs−

Sitzungs−

Transport−

Vermittlungs−

Sicherungs−

Bitübertragungs−

schicht

schicht

schicht

schicht

schicht

schichtPhysical

Data Link

Network

1

3

2

4

5

6

7

D a t e n s t r o m

H

H

H

H

H

H T

Bitfolge

Paket i

Sender Empfänger

Medium Medium

Abbildung 5.1: ISO-OSI-Referenzmodell

1. BitübertragungsschichtRegelung aller physikalisch-technischer Eigenschaften der Übertragungsmedien zwischenden verschiedenen End-/TransitsystemenDarstellung von Bits via Spannungen, Stecker, . . .

2. SicherungsschichtSicherung der Schicht 1 gegen auf den Übertragungsstrecken auftretenden Übertragungs-fehler (elektromagnetische Einflüsse)z.B. Prüfziffern, parity bits

3. VermittlungsschichtAdressierung der Zielssysteme über das (die) Transitsystem(e) hinweg sowie Wegsteue-rung der Nachrichten durch das NetzFlusskontrolle zwischen End- und Transitsystemen (Überlastung von Übertragungswegenund Rechnern / Transitsystemen, faire Verteilung der Bandbreite)

Page 135: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.1. ÜBERSICHT 129

4. TransportschichtStellt die mithilfe der Schichten 1 2 3 hergestellten Endsystemverbindungen für die An-wender zur Verfügungz.B. Abbildung logischer Rechnernamen auf Netzadressen

5. KommunikationssteuerungsschichtBereitsstellung von Sprachmitteln zur Steuerung der Kommunikationsbeziehung (session)Aufbau, Wiederaufnahme nach Unterbrechung, Abbau

6. DatendarstellungsschichtVereinbarungen bzgl. Datenstrukturen für Datentransfer

7. AnwendungsschichtBerücksichtigung inhaltsbezogener Aspekte (Semantik)

Quasistandard

• TCP/IPTransmission Control Protocol / Internet Protocol

����������������������������������������������������������������

����������������������������������������������������������������

��������������������������������������������������������������������������������

��������������������������������������������������������������������������������

������������������������������

��������������������������������������

��������������������������������

����������������������������������������

����������������������������������������

����������������������������������������

�����������������������������������

����������������������������������������������������������������������

�����������������������������������

7

6

5

4

3

2

1

or

Protokoll−ImplementierungenOSI

local network lay

network access

Internet layer

Host−to−host

Process /

Application

emailsimple mailtransf.prot.

terminalemulation

telnetprotocol

network manag.simplenetworkmanag.prot.

User Datagram Prot.UDP

internet prot.

TCP

internet controlmessage prot.

addressresolution

Ethernet, IEEE 802, Arcnet, X.25

twisted pair, Koaxial,Glasfaser

FTP

TCP/IP

(SMTP)

FileTransfer:

Abbildung 5.2: TCP/IP

Page 136: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

130 KAPITEL 5. NETZWERK-KOMMUNIKATION

Kopplung von Netzen

• Kopplungseinheiten zur Verbindung von Netzen (internetworking units)Adressumwandlung, Wegewahl (routing), Flusskontrolle, Fragmentierung und Wiederzu-sammenfügung von Datenpaketen, Zugangskontrolle, Netzwerkmanagement

– RepeaterVerstärker; Empfangen, Verstärken, Weitersenden der Signaleauf Bitübertragungsschicht; die zu verbindenden Netze müssen identisch sein; Ver-bindung von Netzsegmenten

– BridgeVerbindung von Netzen mit unterschiedlichen ÜBertragungsmedien, aber mit glei-chem Schichtaufbau; operiert auf Sicherungsschicht

– Routeroperiert auf Vermittlungsschicht

– GatewayVerknüpfung von Netzen, die in Schicht 3 (und aufwärts) unterschiedliche Strukturaufweisen

– HubEine Art Multiplexer, der das Eingangssignal sternförmig an die angeschlossenen Ge-räte weiterleitet (keine eigene Adresse); reduziert den Verkabelungsaufwand

– Switchspezieller Hub – der Unterschied ist die Arbeitsweise: ein Hub empfängt ein Paketund sendet es einfach an alle Ports weiter, ein Switch hingegen schickt ein Paket nuran den Port weiter der das Paket auch benötigtVorteil eines Switches: wesentlich höhere Geschwindigkeit, die durch das intelligenteRouting erreicht wird

Dabei übernehmen weiter unten stehende Geräte teilweise die Funktionalität darüberste-hender Geräte!

Page 137: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.1. ÜBERSICHT 131

Übertragungsmedien

(siehe z.B. de.wikipedia.org/wiki/)

• Twisted-Pair-Kabel:

Kabeltypen, bei denen die beiden Adern eines Adernpaares miteinander verdrillt (auch ver-seilt oder verdreht) sind; durch die Verdrillung jeweils einer Datenleitung mit einer Masse-leitung ist die Datenübertragung weniger störanfällig;

Sie bestehen grundsätzlich aus

– Ader: Kunststoffisolierter Kupferleiter

– Paar: Je zwei Adern sind zu einem Paar (englisch pair) verdrillt

– Seele: Bezeichnet die vier miteinander verseilten Paare

– Kabelmantel: Umfasst die Seele. Besteht aus PVC oder halogenfreiem Material

Zusätzlich zu den Aderpaaren können weitere Elemente im Kabel vorhanden sein: Beidräh-te als elektrische Masseleitung, Fülladern aus Kunststoff zum Ausfüllen von Hohlräumenzwischen den Paaren oder Trennelemente aus Kunststoff um die Paare auseinander zu hal-ten.

Twisted-Pair-Kabel gibt es in zwei- und vierpaariger Ausführung. Bei aktuellen Netzwer-kinstallation werden fast nur vierpaarige Kabel verwendet.

Man unterscheidet

– UTP (Unshielded Twisted Pair): Kabel mit ungeschirmten Paaren und ohne Gesamt-schirm (siehe Abb. 5.3, S. 131)

– STP (Shielded Twisted Pair): Die Adernpaare sind mit einem metallischem Schirm (meisteine Alu-kaschierte Kunststofffolie) umgeben (siehe Abb. 5.4, S. 132)

– S/UTP (Screened Unshielded Twisted Pair): Aufbau wie bei UTP, jedoch mit zusätzlicherGesamtschirmung um die Seele

– S/STP (Screened Shielded Twisted Pair): Aufbau wie bei STP, jedoch mit zusättzlicherGesamtschirmung um die Seele (Abb. 5.5, S. 132)

KupferleiterAderisolierung

Paar

Kabelmantel

UTP

Abbildung 5.3: Unshielded-Twisted-Pair-Kabel

Twisted-Pair-Kabel sind billig, einfach zu verlegen; geringe Bandbreite, geringe Abhörsi-cherheit, hohe Störanfälligkeit

Page 138: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

132 KAPITEL 5. NETZWERK-KOMMUNIKATION

STP

Adernisolierung

Kabelmantel

Paarschirm

Paar

Kupferleiter

Abbildung 5.4: Shielded-Twisted-Pair-Kabel

S/STPKupferkabel

Adernisolierung

PaarPaarschirm

Gesamtschirm

Kabelmantel

Abbildung 5.5: Screened Shielded-Twisted-Pair-Kabel

• Koaxialkabel, kurz: Koax-Kabel

– bestehen aus einem isolierten Innenleiter (auch Seele genannt), der von einem in kon-stantem Abstand um den Innenleiter angebrachten Außenleiter umgeben ist. Übli-cherweise ist diese Ummantelung ebenfalls nach außen isoliert (Abb. 5.6, S. 132

– Ausnutzung eines breiten Frequenzspektrums für parallele Übertragung

Abbildung 5.6: Koaxial-Kabel

Page 139: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.1. ÜBERSICHT 133

• Glasfaser (Lichtwellenleiter)

– Die Faser besteht aus einem Kern, einem Mantel und einer Beschichtung. Der licht-führende Kern dient zum Übertragen des Signals.

– Ein Standard für lokale Computernetze, der auf Glasfaserkabeln aufbaut, ist z. B. dasFiber Distributed Data Interface (FDDI).

• Drahtlose Übertragung (Funk, Infrarotwellen) – Wireless LAN (WLAN)

– bezeichnet ein „drahtloses“ lokales Funknetz-Netzwerk, wobei meistens ein Standardder IEEE 802.11-Familie gemeint ist

– arbeiten meistens im sog. Infrastruktur-Modus, bei der eine oder mehrere Basisstatio-nen (Wireless Access Points) die Kommunikation zwischen den Clients organisieren; derDatentransport läuft immer über die Basisstation(en)

– Bei einem Infrastruktur-Netzwerk wird über einen zentralen Knotenpunkt (Access Point)die Kommunikation der einzelnen Endgeräte ermöglicht, die sich jeweils mit ihrerMAC-Adresse und/oder IP-Adresse am Knoten anmelden müssen

– Die MAC-Adresse (Media Access Control) ist die Hardware-Adresse eines jeden Netz-werkgerätes (Netzwerkkarte, Switches), die zur eindeutigen Identifikation des Gerätsim Netzwerk dient.

Page 140: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

134 KAPITEL 5. NETZWERK-KOMMUNIKATION

Zugangsverfahren

→ wer darf wann senden ?

a) über strenge Vorschrift wird festgelegt, wer wann senden darf

b) jeder sendet wie er will, bis Fehler auftritt, der dann korrigiert wird

ad a) Token-Verfahren (stark vereinfacht)

– ein besonderes Bitmuster (Token) ”kursiert” im Netz

– senden darf der, der es besitzt

– das Token wird an das Ende der Sendung angefügt

ad b) CSMA/CD-Verfahrencarrier sense multiple access with collision detection

– viele beteiligte Sender (multiple access)

– vor dem Senden in den Kanal ”horchen” (carrier sense)wenn frei, senden, sonst warten

– während des Sendens den Kanal prüfen, ob andere senden, um Kollissionen zu erken-nen (collision detection)

– wenn Kollision, müssen alle Sender abbrechen; jeder wartet eine zufällig gewählteZeitspanne und wiederholt Sendevorgang – Sender mit der kürzesten Zeitspanne ”ge-winnt”

Page 141: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.2. ETHERNET 135

5.2 Ethernet

• ca. 1970 XEROX PARC

• Standardisiert 1978 von XEROX, INTEL, DEC

• Schicht 2

• Kabel rein passiv

• elektronische Komponenten (Netzwerkkarte):

– Transceiver (Übertragen/Empfangen in/von Ethernet)

– host interface (Rechner-Bus)

Eigenschaften:

• alle teilen sich einen Kanal (BUS)

• broadcast - alle Transceiver hören alles, host interface stellt fest, ob Nachricht für diesenRechner gedacht ist

• best-effort delivery - ”Bemühensklausel”: ob Sendung wohl ankommt?

• CSMA/CD-Zugangsverfahren

Adressierung:

Netzwerkkarte (host interface) bildet Filter, alle Pakete werden dahin weitergeleitet, nur die demTransceiver entsprechenden Pakete (Hardware-Adressen) werden an Rechner weitergeleitet

Adresse eines Rechners: 48 Bit Integer, vom Hersteller auf Interface festgelegt, von IEEE gema-naged – MAC-Adresse

Adresstypen:

• physische Adresse einer Schnittstelle

• network broadcast address (alle Bits auf 1, ”an alle”)

• multicast broadcast (Teilmengen broadcast)Betriebssystem initialisiert Schnittstelle: welche Adressen sollen erkannt werden?

Page 142: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

136 KAPITEL 5. NETZWERK-KOMMUNIKATION

Frame Format:

(siehe auch Abb. 5.1, S. 128)

64 48 48 16 368 - 12000 32

Preamble

Destination

Source

Type

Data

(Cyclic

CRC

Redundancy Check)

Abbildung 5.7: Ethernet Frame Format

• Preamble: zur Synchronisation der Knoten, alternierende 0/1-Folge

• Type: welches Protokoll (für BS)? → self-identifying

5.3 Internetworking - Konzept & Grundlegende Architektur

• bislang:

ein Netz (ein physikalisches Netz) - z.B. Token Ring, Bus

Adressen von Hosts waren physikalische Adressen (MAC-Adressen)

• jetzt:

”Netz über Netzen über Netzen über . . . über physikalischen Netzen

notwendig: Abstraktion von zugrundeliegenden physikalischen Netzen

– Ansatz 1:spezielle (Applikations-) Programme, die aus der Heterogenität der physikalischenNetze / Hardware eine softwaremäßige Homogenität herstellen

– Ansatz 2 (Abb. 5.8, S. 137):Verbergen von Details im Betriebssystem des jeweiligen Rechners (layered-system ar-chitecture, Schichtenmodell der Protokolle)

Page 143: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.3. INTERNETWORKING - KONZEPT & GRUNDLEGENDE ARCHITEKTUR 137

application

level

OSA

bstraktio

n

physical level

Abbildung 5.8: Layered System Architecture

Grundlegende Internet Architektur

Netz 2

(Ethernet - 48-Bit-Adressen)

Netz 1

(Token-Ring:8-Bit-Adressen) GATEWAY

Internet-Protocol (IP)

Adressen von Rechnern inNetz-2 aus Sicht der Rechneraus Netz-1 ???

<net_id,host_id>

Abbildung 5.9: Internet Architektur

In a TCP/IP internet, computers called gateways provide all interconnections among physicalnetworks

Page 144: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

138 KAPITEL 5. NETZWERK-KOMMUNIKATION

Frage: Muss ein “Gateway” alle in allen Netzen erreichbare Rechner kennen (ein Super-Computer)?

Antwort: Nein!

gateways route packets based on destination network, not on destination host

Benutzer-Sicht: ein Internet als ein großes, virtuelles oder logisches Netzwerk

Routing:

• Finden eines “optimalen” Weges von einem Rechner A (in einem beliebigen lokalen Netz)zu einem Rechner B (in einem beliebigen lokalen Netz)

• Optimal: kostengünstigster und / oder kürzester und / oder schnellster Weg?

• abhängig vom Routing-Protokoll und somit Einstellungssache des Routers

Internet-Adressen

• globale Identifikation von hosts im (in ihrem) Netz

NAME (was)

ADRESSE (wo)

ROUTE, PFAD (wie dorthin)

abnehmendeAbstraktionderIdentifikation

Abbildung 5.10: Abstraktion der Identifikation

IP-Adresse

• logische Adresse

• eindeutih im gesamten Internet, ausgenommen die privaten Subnetze

• Integer, die das Routing unterstützen: IPv4 32-Bit (siehe Abb. 5.11, S. 139) / IPv6 128 Bit

Page 145: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.3. INTERNETWORKING - KONZEPT & GRUNDLEGENDE ARCHITEKTUR 139

1

1 1

1 1 1

1 1 1 1

0

0

0 muliticast−id

reserved

Class D

Class E

0 Class A

Class B

hostId

netId

netId hostId

hostId

Class C

netId

Abbildung 5.11: 32-Bit IP-Adresse in Version 4

lesbare Adressen ( punktiertes Dezimalformat): (dotted decimal notation)

134.60.66.5

(thales.mathematik.uni−ulm.de)

1000 0110 0011 1100 0100 0010 0000 0101

Abbildung 5.12: IP-Adresse: dotted decimal form

Aus dem in Abb. 5.11 (S. 139) beschriebenem Adressaufbau resultieren die in Abb. 5.13 (S. 139)dargestellten Adressräume für die einzelnen Klassen.

Class A: netid

hostid

1.x.x.x bis 126.x.x.x 126 Subnetze

x.0.0.1 bis x.255.255.254 16.777.214 Hosts

Class B netid

hostid

bis

bis

128.1.x.x 191.254.x.x 16384 Subnetze

x.x.0.1 x.x.255.254 65.534 Hosts

netid

hostid

bis

bis

Class C 192.0.0.x 192.254.x.x

x.x.x.1 x.x.x.254

2.097.512 Subnetze

254 Hosts

Abbildung 5.13: IP-Adressklassen in Version 4

Page 146: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

140 KAPITEL 5. NETZWERK-KOMMUNIKATION

Diese Aufteilung war sehr sehr starr und wurde 1996 durch das Konzept der Netzmaske verall-gemeinert (s.u.): CIDR – Classless Inter-Domain Routing als Übergang zu IPv6 mit 128-Bit-Adressen.Damit spielt es keine Rolle mehr, welcher Netzklasse eine IP-Adresse angehört.

Subnetzmaske

• Subnetz-Masken werden eingesetzt, um die starre Klassenaufteilung der IP-Adressen inNetze und Rechner flexibel an die tatsächlichen Gegebenheiten anzupassen

• Die „Grenze“ zwischen den Bits der Netz- und der Rechneradresse wird verschoben. Da-durch erhöht man zwar die Zahl der möglichen Netze, verringert aber gleichzeitig die An-zahl der jeweiligen Rechner. Diese neuen vielen kleinen Netze werden als Subnetze be-zeichnet

• Die Einrichtung von Subnetzen macht es möglich, viele völlig verschiedene und weit ent-fernte Netze miteinander zu verbinden, da jedes Subnetz seine eindeutige Adresse be-kommt und somit vom IP-Router adressierbar wird

• Ein Subnetz wird dadurch definiert, dass die IP-Adresse mit einer sogenannten Subnetz-Maske verknüpft wird:

– Ist ein Bit in der Subnetz-Maske gesetzt, wird das entsprechende Bit der IP-Adresseals Teil der Netzadresse angesehen

– Ist ein Bit in der Subnetz-Maske nicht gesetzt, dann wird das entsprechende Bit derIP-Adresse als Teil der Rechneradresse benutzt

• Die einzelnen IP-Router der Subnetze können ihre IP-Adresse auch wieder mit einer Subnetz-Maske verknüpfen, um weitere Subnetze zu erzeugen

Beispiele:

a) Netzmaske: 255.224.0.0 (11111111.11100000.00000000.0000000)

Die Subnet-Maske geht bis zum 11.ten Bit, d.h. die ersten 11 Bit einer IP-Adresse bezeichnendas Netzwerk, die restlichen den Host in diesem Netzwerk.

b) Netzmaske: 255.255.240.0 (11111111.11111111.11110000.00000000)

Hier geht die Subnet-Maske bis zum 30. Bit, d.h. die ersten 30 Bit einer IP-Adresse bezeich-nen das Netzwerk, der Rest die Rechner im jeweiligen Netzwerk.

Statt der Netzmaske verwendet man heute einen Präfix, der angibt, bei welchem Bit die Auftei-lung in Netz- und Rechneradresse erfolgt:

Subnetzmaske 32-Bit-Wert Präfix255.0.0.0 1111 1111 0000 0000 0000 0000 0000 0000 /8255.240.0.0 1111 1111 1111 0000 0000 0000 0000 0000 /12255.255.0.0 1111 1111 1111 1111 0000 0000 0000 0000 /16255.255.255.0 1111 1111 1111 1111 1111 1111 0000 0000 /24255.255.255.192 1111 1111 1111 1111 1111 1111 1100 0000 /26

Wird in der IP-Konfiguration einer Netzwerkstation IP-Adresse und Subnetzmaske manuell ein-gegeben erfolgt die Schreibweise separat in Form von 192.168.0.1 / 255.255.255.0 (IP-Adresse /Subnetzmaske) oder 192.168.0.1 / 24 (IP-Adresse / Präfix).

Page 147: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.3. INTERNETWORKING - KONZEPT & GRUNDLEGENDE ARCHITEKTUR 141

Adressenvergabe:

Die Zugehörigkeit eines Netzwerkinterfaces zu einem Adressraum wird durch bestimmte admi-nistrative Behörden geregelt – in Deutschland DE-NIC, weltweit INTER-NIC. Ausnahme hiervonbilden die sog. privaten Adressräume einer jeden Klasse, welche für private Netzwerke, die nicht(unmittelbar) am Internet teilnehmen, reserviert sind. Sie können natürlich auch am Internet teil-nehmen, z.B. über das NAT-Protokoll ( Network Address Translation)

Reservierte Adressen (privat, nicht zur direkten Anbindung an das Internet:

10.0.0.0 bis 10.255.255.255, 172.16.0.0 bis 172.31.255.255, 192.168.0.0 bis 192.168.255.255 (RFC 1918)

Routing

RFC1983 Internet Users’ Glossary:

Routing ist der Prozess der Auswahl der richtigen Schnittstelle und des nächstenHops (Router) für (Daten-) Pakete, die weitergeleitet werden sollen.

Bedingungen an Router:

• Die entsprechenden Routing-Protokolle müssen aktiv sein

• Das Zielzetz muss bekannt sein oder eine Alternative, die zum Zielnetz führen kann

• Der Router muss seine aktive Schnittstelle in Richtung Zielnetz auswählen

Routing-Protokolle:

Protokolle, die die Wegwahl durch spezielle Routing-Algorithmen ermöglichen

• es werden nur Tabellen mit Informationen, die die Weiterleitung von Benutzerinformatio-nen unterstützen, an andere Router weitergegeben (keine Benutzerinformationen)

• Austausch der Routing-Tabellen darf Netz nicht übermäßig beanspruchen

• Routing-Tabellen sollen überschaubar bleiben

• Beispiele:

– RIP Routing Information Protocol

– IGRP Interior Gateway Routing Protocol (von Cisco)

– OSPF Open Shortest Path First

– BGP Border Gateway Protocol

• Unterscheidung nach Einsatzgebieten:

– innerhalb der eigenen Netzwerke

– zwischen Netzen

Page 148: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

142 KAPITEL 5. NETZWERK-KOMMUNIKATION

Autonome Systeme (AN)

Die Netzwerke, die das Internet bilden, werden Autonome Systeme (AS) (manchmal findet sichauch der Begriff Domain) genannt; sie stellen eine Zusammenfassung zentral adminstrierter Net-ze dar und werden über eine 16-Bit-Zahl in IPv4 / 32 in IPv6 (AS-Nummer) identifiziert. DerBetreiber eines solchen autonomen Systems lässt sich von seiner regional zuständigen Vergabe-stelle für IP-Adressen (s.u.) einen Block von IP-Adressen zuteilen, mit denen er sein Netzwerkadressiert. Für die in Europa ansässigen Internet-Dienstleister ist die zuständige VergabestelleRIPE (Réseaux IP Européen), weltweit ist es die IANA (Internet Assigned Numbers Authority).

Die AS-Nummer ermöglicht es demzufolge, verschiedene Subnetze eines Netzbetreibers zu ei-nem Block zusammenzufassen. Diese Zusammenfassung dient vor allem dem Zweck, das Rou-ting (die Wegewahl) auf oberster Ebene, also zwischen autonomen Systemen, entscheidend zuvereinfachen. Nicht jedes autonome System muss den Weg für alle einzelnen Subnetze kennen,sondern jedes autonome System muss die Möglichkeit haben, bei Bedarf durch Abfragen fest-zustellen, zu welchem autonomen System die gewünschte IP-Adresse gehört, um dann die Da-tenübertragung an das entsprechende autonome System weiterzuleiten, das die Ziel-IP-Adressebeinhaltet.

Algorithmen

Distance Vector Routing (Bellman-Ford)

• Bestimmung der Anzahl der Hops (Sprünge) in einem Pfad, um den kürzesten Weg vonQuelle zu Ziele zu finden

• jeder Router überträgt seine vollständige Routing-Tabelle bei jedem Update an seinen Nach-barn

• jedem Router sind nur die Kosten zu jedem Ziel bekannt und der dafür notwendige nächsteKnoten bekannt

• benutzt bei RIP

Link State Routing

• jeder Router überträgt seine Routing Informationen (Kosten / Last zur Erreichung seineNachbarn) an alle Router im Netz (Link State Broadcast)

• Jeder Router kennt gesamt Netztopologie

• lokale Bestimmung des kürzesten Weges anhand des Dijkstra Algorithmus

• benutzt bei OSPF

Page 149: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.3. INTERNETWORKING - KONZEPT & GRUNDLEGENDE ARCHITEKTUR 143

TCP/IP

Application

Services

Reliable TransportService

Connectionless packet deliveryService

TCP

IP

Abbildung 5.14: TCP/IP-Schichtenmodell

1. Concept of Unreliable DeliveryAuslieferung von Paketen ist nicht garantiert (may be lost, duplicated, delayed, delivered out oforder → service will not detect nor inform sender or receiver)

2. ConnectionlessJedes Paket wird losgelöst von den anderen behandelt (evt. auch anders gerouted)

3. best-effort delivery”bemühe mich um Auslieferung”

IP Internet Protocol:

• Regeln, die den unreliable, connectionless, best-effort delivery - Mechanismus definieren

1. basic unit of data transfer durch ein TCP/IP-Internet

2. enthält routing-Funktion (Auswahl des Pfades)

3. Regeln, wie Host’s und Gateway’s Pakete verarbeiten sollen, wie und wann Fehler-meldungen produziert werden sollen, Bedingungen für das ”Wegwerfen” von Pake-ten

Page 150: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

144 KAPITEL 5. NETZWERK-KOMMUNIKATION

Internet Datagram - basic transfer unit

• grober Aufbau (Abb. 5.15, S. 144):

DatagramHeader

DatagramData Area

Abbildung 5.15: IP Datagramm - grober Aufbau

• genauer ist dies in Abb. 5.16, S. 144 dargestellt

Time to live(TTL)

24 310 191684

DATA

DATA

IP Options (if any) Padding

Destination IP Address

Source IP Address

Header ChecksumProtocol

Flags Fragmentation offset

Total LengthTypeServiceVer-sion

Identification

LengthHeader

Abbildung 5.16: IP Datagram - im Detail

Page 151: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.3. INTERNETWORKING - KONZEPT & GRUNDLEGENDE ARCHITEKTUR 145

TCP/IP Internet Layering Model

Objects Passed

Internet

Transport

Application

Network Interface(data link)

Hardware

Between LayersConceptual Layer

StreamsMessages or

Transport ProtocolPackets

IP Datagrams

Network-Specific Frames

Abbildung 5.17: TCP/IP Layering Model

Application LayerAuf der obersten Ebene starten Benutzer Anwendungsprogramme, die auf im Netz ver-fügbare Dienste zugreifen wollen. Ein Anwendungsprogramm interagiert mit dem / denTransport-Protokoll(en), um Daten zu senden / empfangen.Jedes Anwendungsprogramm wählt die benötigte Transport-Art, z.B. eine Folge individu-eller Botschaften (messages) oder einfach eine Folge von Bytes. Das Anwendungsprogrammübergibt diese Daten in der verlangten Form an die Transport-Ebene zur Auslieferung.

Transport LayerDie Hauptaufgabe dieser Schicht besteht darin, die Verbindung zur Kommunikation zwi-schen zwei Anwendungsprogrammen bereitzustellen (end-to-end-communication).

• Regulieren des Informationsflusses

• Bereitsstellen eines zuverlässigen Transports

• Sicherstellen, dass Daten korrekt und in Folge ankommen

• Warten auf Empfangsbestätigung des Empfängers

• erneutes Senden verlorengegangener Pakete

• Aufteilen des Datenstroms in kleine Stücke (packets)

• Übergeben jeden Paketes mit Zieladresse für die nächste Schicht

i.a. arbeiten viele Applikation mit der Transport-Schicht (Senden, Empfangen)

Page 152: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

146 KAPITEL 5. NETZWERK-KOMMUNIKATION

• jedem Paket wird zusätzliche Information beigefügt (welche Appl.?)

• Prüfsumme

Internet Layer

• Erstellen der IP Datagrams

• (weiter-) senden der Datagrams

• ”auspacken” der Datagrams (auf Zielmaschine)

• Übergabe an das richtige Transport-Protokoll (Zielmaschine)

• ICMP

Network Interface Layer

• übernimmt Datagrams und schickt sie auf spezifischem Netz weiter

Page 153: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.4. TRANSPORT-PROTOKOLLE 147

5.4 Transport-Protokolle

5.4.1 Ports

• Internet Protokolle adressieren Host’s

• Adressierung von Applikationen (letztliches Ziel) innerhalb des Zielrechners?

Unterstellt seien multiprocess-Systeme als Zielrechner (z.B. UNIX-Rechner).

• Prozess als letztliches Ziel?Prozesse werden dynamisch erzeugt und terminiert, Sender kann Prozesse auf anderenMaschinen nicht identifizierenDienste (sprich Funktionen, Leistungen) auf anderen Rechnern sind das Ziel, unabhängigvon welchem Prozess (welchen Prozessen) diese realisiert sind!

• Jede Maschine hat eine Menge sog. Protokoll- Port’s (abstrakter Endpunkt), identifiziertdurch positive Integer. Das jeweilige Betriebssystem bietet Mechanismus an, mit dem Pro-zesse Ports spezifizieren und nutzen können.Die meisten Betriebssysteme unterstützen synchronen Zugriff auf Ports. Wenn dabei ei-ne Prozess Daten von einem Port lesen will, noch keine Daten da sind, wird er solangeblockiert, bis (genügend) Daten eingetroffen sind.Ports sind typischerweise gepuffert.

• Ziel-Adresse: (IP Adresse + Port-Nummer)Jede Meldung enthält neben destination port auch source port (z.B. zum Antworten).

5.4.2 UDP

0 16 31

UDP Source Port UDP Destination Port

UDP Message Length UDP Checksum

Data

Data

Abbildung 5.18: UDP - Format

� The User Datagram Protocol (UDP) provides unreliable connectionless delivery service using IP totransport messages between machines. It adds ability to distinguish among multiple destinations within agiven host computer

Page 154: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

148 KAPITEL 5. NETZWERK-KOMMUNIKATION

• keine Empfangsbestätigungen (Acknowledgement)

• Kein Ordnen ankommender Meldungen

• keinerlei Feedback

– UDP messages können verloren gehen, dupliziert werden, ungeordnet ankommen, schnel-ler ankommen als verarbeitet werden!

– Anwendungsprogramme, die auf UDP aufbauen, müssen selbst für ”Zuverlässigkeit”sorgen.

• Source Port ist optional, wenn nicht genutzt, so NULL

• LENGTH Anzahl Bytes (octets) im UDP Datagram inkl. Header.

• CECKSUM ebenfalls optional; wenn keine Prüfsumme berechnet, so NULL.NB: IP berechnet keiner Prüfsumme über den Datenteil!Berechnung der Prüfsumme (wie bei IP): Daten werden in 16-Bits-Einheiten aufgeteilt,Summe über 1-er-Komplement wird gebildet, davon wieder 1-er-Komplement gebildet; istPrüfsumme identisch Null, so wird davon das 1-er-Komplement gebildet (alles auf 1); un-problematisch, da es bei 1-er-Komplement zwei Darstellungen des Zahl Null gibt: alle Bitsauf 0 oder alle auf 1!

IP Layer

UDP: Demultiplexing

Based on Port

Port 1 Port 2 Port 3

arrivesUDP Datagram

Abbildung 5.19: UDP-Demultiplexing

Page 155: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

5.4. TRANSPORT-PROTOKOLLE 149

Port-Nummern:

• einige zentral vergeben (well-known port numbers, universal assignment)

• dynamic binding approachPort ist nicht global bekannt; wenn ein Programm einen Port benötigt, wird von der Netzwerk-Software einer bereitgestellt. Um eine Port-Nummer auf einem anderen Rechner zu erfah-ren, wird eine entsprechende Anfrage gestellt und beantwortet.

Decimal Keyword UNIX Keyword Description0 - - Reserved7 ECHO echo Echo9 DISCARD discard Discard

11 USERS systat Active Users13 DAYTIME daytime Daytime37 TIME time Time42 NAMESERVER name Host Name Server43 NICNAME whois Who Is?53 DOMAIN nameserver Domain Name Server67 BOOTPS bootps Bootstrap Protocol Server68 BOOTPC bootpc Bootstrap Protocol Client69 TFTP tftp Trivial File Transfer

111 SUNRPC sunrpc Sun Microsystems RPC123 NTP ntp Network Time Protocol161 - snmp SNMP net protocol

.

.

.

Page 156: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

150 KAPITEL 5. NETZWERK-KOMMUNIKATION

Beispiel einer TCP/IP Sitzung an Port 80 (Webserver):

thales$ telnet www.mathematik.uni-ulm.de 80Trying 134.60.166.6...Connected to www.mathematik.uni-ulm.de.Escape character is ’^]’.GET /sai/swg/ HTTP/1.0 # <-- Eingabe

# <-- noch ein <return>, dann folgt Ausgabe des ServersHTTP/1.1 200 OKDate: Tue, 18 May 2004 09:05:22 GMTServer: Apache/1.3.27 (Unix)Last-Modified: Tue, 18 May 2004 09:04:47 GMTETag: "b40a3-10fe-40a9d1af"Accept-Ranges: bytesContent-Length: 4350Connection: closeContent-Type: text/html

<HTML><TITLE>Prof. Dr. Franz Schweiggert</TITLE><BODY><H1>Prof. Dr. Franz Schweiggert</H1>

....

<ADDRESS><A HREF="/sai/swg/">Franz Schweiggert</A>, 15. Mai 2004</ADDRESS>

</BODY></HTML>Connection to www.mathematik.uni-ulm.de closed by foreign host.thales$

Page 157: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Kapitel 6

Berkeley Sockets

6.1 Grundlagen

• API (application program interface) zu Kommunikations-Protokollen

• In UNIX: Berkeley sockets und System V Transport Layer Interface (TLI)

• Beide Schnittstellen wurden für C entwickelt

• Die Implementierungen der Internet-Protokolle und der Socket-Schnittstellewurden erstmals 1982 in der Version 4.1c der Berkeley Software Distributions(BSD) an der Universität von Kalifornien in Berkeley integriert.

• Mit der Version 4.2BSD war 1983 das erste Unix-System mit Netzwerkun-terstützung verfügbar.

Die beiden zentralen Abstraktionen der Socket-Schnittstelle sind der Socket unddie Kommunikationsdomäne. Aus Sicht der Anwendung repräsentiert ein Socketeinen Kommunikationsendpunkt eines Benutzerprozesses innerhalb der gewähl-ten Kommunikationsdomäne. Die Domäne spezifiziert die Protokollfamilie unddefiniert allgemeine Eigenschaften und Vereinbarungen wie z.B. die Adressie-rung des Kommunikationsendpunktes.

Durch die Abstraktion Socket und Kommunikationsdomäne wird eine Trennungzwischen den Funktionen der Socket-Schnittstelle und den im System implemen-tierten Kommunikationsprotokollen erreicht. Dem Anwender steht somit eineeinheitliche Schnittstelle zur Verfügung, die verschiedene Netzwerkprotokolleund lokale Interprozesskommuniktaionsprotokolle gleichermaßen unterstützt.Die Implementierung ist vollständig im Betriebssystem integriert (siehe Abb. 6.1,S. 152).

151

Page 158: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

152 KAPITEL 6. BERKELEY SOCKETS

application

socket system call

socket systemcalls

implementation

socket layerfunctions

protocol layerTCP/IP, UNIX, XNS

device driver

user

kernel

Abbildung 6.1: Vereinf. Modell der Implementierung von Sockets unter BSD

• Der Zugriff einer Anwendung auf die Funktionen der Socketschicht (socketlayer functions) im Betriebssystemkern erfolgt über die Systemaufrufe derSocket-Schnittstelle. Hier findet die Umsetzung der protokollunabhängigenOperationen in die protokollspezifischen Implementierungen der darunter-liegenden Protokollschicht (protocol layer) statt.

• Die Auswahl des konkreten Kommunikationsprotokolls wird bei der Erzeu-gung eines Socket über die Spezifikation der Domäne festgelegt.

• In der Protokollschicht (protocol layer) sind die Implementierungen der vomSystem unterstützen Protokollfamilien zusammengefaßt.

• Die darunterliegende Datenübertragungsschicht enthält die Implementie-rungen der Gerätetreiber (device driver) zur Datenübertragung über verschie-dene Medien oder auch systeminterne Mechanismen.

Page 159: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.2. EIN ERSTES BEISPIEL: TIMESERVER AN PORT 11011 153

6.2 Ein erstes Beispiel: Timeserver an Port 11011

Programm 6.1: Timeserver an Port 11011 (timserv.c)

1 # include <netdb .h>2 # include < netinet /in .h>3 # include < stdio .h>4 # include < strings .h>5 # include < string .h>6 # include <sys/ socket .h>7 # include <sys/time .h>8 # include <time .h>9 # include <unistd .h>

10 # include < stdlib .h>11 # define TPORT 1101112 int main() {13 struct sockaddr_in addr , client_addr ;14 size_t client_add_len = sizeof ( client_addr );15 int sfd , fd ; int optval = 1;16

17 // bzero (&addr, sizeof (addr )); // mit 0en fuellen18 // besser :19 memset(&addr ,0, sizeof (addr ));20

21 addr . sin_family = AF_INET; // TCP/IP−Verbindung22 addr . sin_port = htons (TPORT);// Port eintragen , Network Byte Order23

24 if (( sfd = socket (AF_INET, SOCK_STREAM, 0)) <0) // socket erzeugen25 perror ( "socket" ), exit (1);26

27 if ( setsockopt ( sfd ,SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))<0)28 perror ( " setsockopt " ), // rasche Wiederzuweisung ermoeglichen29 exit (1);30

31 if ( bind ( sfd , ( struct sockaddr ∗) &addr, sizeof (addr )) <0)32 perror ( "bind" ), exit (1);33

34 if ( listen ( sfd , SOMAXCONN) <0)35 perror ( " listen " ), exit (1);36

37 while (( fd = accept ( sfd , ( struct sockaddr ∗) & client_addr ,38 & client_add_len )) >=0) {39 time_t clock ; char ∗ tbuf ;40 time(& clock ); // aktuelle Uhrzeit holen41 tbuf = ctime(& clock ); // in String schreiben42 write ( fd , tbuf , strlen ( tbuf )); // in Clientverbindung schreiben43 close ( fd );

Page 160: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

154 KAPITEL 6. BERKELEY SOCKETS

44 }45 exit (0); // seldomly reached46 }

Page 161: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.2. EIN ERSTES BEISPIEL: TIMESERVER AN PORT 11011 155

Man beachte die Funktionen bzero(), memset(). An der Stelle, an der die IP-Adresse eingetragen werden könnten, stehen nun definiert Nullen – dies ent-spricht dem Makro INADDR_ANY:

/* in /usr/include/netinet/in.h: */

/* Address to accept any incoming messages. */#define INADDR_ANY ((in_addr_t) 0x00000000)

Übersetzung und Ausführung:

thales$ make -f make_timservgcc -o timserv -Wall timserv.c -lsocket -lnslthales$ timserv &[1] 28753thales$ telnet thales 11011Trying 134.60.66.5...Connected to thales.Escape character is ’^]’.Tue May 18 11:36:34 2004Connection to thales closed by foreign host.thales$ exit

Page 162: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

156 KAPITEL 6. BERKELEY SOCKETS

6.3 Die Socket-Abstraktion

• Der Socket repräsentiert als zentrale Abstraktion einen Kommunikations-endpunkt eines Benutzerprozesses innerhalb der gewählten Kommunikati-onsdomäne.

• Letztere wiederum ist eine weitere Abstraktion, die allgemeine Kommuni-kationseigenschaften der vom System bereitgestellten Mechanismen für dieProzesskommunikation mit Sockets zusammenfaßt. Dazu zählen beispiels-weise der zu verwendende Namensraum und die damit verbundene Artder Adreßspezifikation bei der Benennung eines konkreten Socket.

• Die Kommunikation zwischen je zwei Sockets verläuft normalerweise im-mer innerhalb derselben Domäne.

Die in der Regel von allen Unix-Systemen unterstützten und heute hauptsächlichverwendeten Kommunikationsdomänen sind die Unix-Domäne für die Prozes-skommunikation auf dem lokalen System und die Internet-Domäne für die Pro-zesskommunikation nach den Internet-Standard-Protokollen. Die innerhalb derInternet-Domäne miteinander kommunizierenden Prozesse können sehr wohlauch auf demselben lokalen System laufen; hierzu bietet jedoch die Verwendungder Unix-Domäne entscheidende Vorteile durch erweiterte protokoll-spezifischeEigenschaften und durch bessere Performance.

Socket-Typen:

• In der Socket-Abstraktion entsprechen die Socket-Typen den für die Anwen-dung jeweils sichtbaren Kommunikationsverfahren (verbindungslos - ver-bindungsorientiert).

• Zusätzlich definiert ein Socket-Typ spezielle Eigenschaften eines Protokollswie z.B. fehlerfreie Datenübertragung, Flusskontrolle, Einhaltung von Da-tensatzgrenzen).

• Letztlich wird über den Socket-Typ die Kommunikationssemantik defi-niert.

• Wie bei den Domänen gilt: Prozesskommunikation nur zwischen Socketsvom selben Typ!

Page 163: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 157

Die wichtigsten Socket-Typen unter Unix:

STREAM: Ein Stream Socket stellt ein Kommunikationsverfahren bereit, daseinen bidirektionalen, kontrollierten und verlässlichen Datenfluss garantiert,d.h. alle transferierten Datenpakete kommen in derselben Reihenfolge voll-ständig und ohne Duplikate beim Empfänger an (vgl. in der Semantik zubidirektionale Unix-Pipes). In BSD-Unix sind Pipes auf diese Weise imple-mentiert.

DGRAM: Ein Datagram Socket stellt ebenfalls ein bidirektionales Kommuni-kationsverfahren bereit, aber ohne Flusskontrolle und ohne Garantie, dassgesendete Pakete den Empfänger erreichen, dass die Reihenfolge erhaltenbeleibt oder dass Duplikate ankommen. Sie sind zudem im Datenvolumenbeschränkt. Datensatzgrenzen bleiben beim Datentransfer allerdings erhal-ten.

RAW: Ein Raw Socket stellt eine allgemeine Schnittstelle zu den meist der Trans-portschicht zugrundeliegenden Kommunikationsprotokollen bereit, welchedie Socket-Abstraktion unterstützen. Dieser Socket-Typ ist normalerweiseDatagram-orientiert, obwohl die exakte Charakteristik von der Kommuni-kationssemantik des konkreten Protokolls abhängt. In der Internet-Protokollfamiliekann mit Raw Sockets beispielsweise direkt das Internet Protocol (IP), dasICMP (Internet Control Message Protocol) oder das IGMP (Internet Group Ma-nagement Protocol) verwendet werden.

6.4 Die Socket-Programmierschnittstelle

6.4.1 Vorbemerkungen

Ein Ziel bei der Entwicklung der Socket-Schnittstelle war, die bestehenden Sy-stemaufrufe des Unix-I/O-Systems weitestgehend auch für die Netzwerkkom-munikation zu nutzen (orthogonale Erweiterung). Aufgrund wesentlicher Un-terschiede in der Semantik von File-I/O und Netzwerk-I/O konnte die Abstrak-tion Everything is a file nicht befriedigend erweitert werden. Als Kompromisswurden deshalb insgesamt 17 neue Systemaufrufe für die Kommunikation mitSockets bereitgestellt.

In BSD-basierten Systemen sind Sockets vollständig im Betriebssystem realisiertund somit erfolgt der Zugriff auf die Socket-Schnittstelle vollständig über Sy-stem Calls. In SVR4-basierten Systemen sind Sockets auf der Basis des Streams-

Page 164: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

158 KAPITEL 6. BERKELEY SOCKETS

Subsystems implementiert. Die Socket-Funktionen sind dabei entweder als Bibliotheks-Funktionen unter Verwendung der Streams-Systemaufrufe oder auch im System-kern mit einer Systemaufrufschnittstelle realisiert. Für die Anwendungsprogram-mierung ist dies in der Regel irrelevant.

Sockets werden in gleicher Weise wie Dateien über Deskriptoren realisiert. VieleSystemaufrufe des I/O-Subsystems (wie z.B. read() oder write()) können so auchauf Sockets angewandt werden.

Page 165: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 159

6.4.2 Überblick/Einordnung

Zuverlässige vs. nicht zuverlässige Verbindung:

Berkeley

Sockets

API

SOCK_STREAM SOCK_DGRAM

ISO/OSI

4

UDPTCP

reliable unreliable

= IP + Port +

Checksum

IP (unreliable) / ARP3

Ethernet−Verkabelung / Protokoll2

Twisted Pair, Glasfaser, Koaxial1

Abbildung 6.2: Sockets – Überblick

Server Client

read()

bind()

listen()

accept()

write()

socket()

connect()

write()

read()

Server Client

socket() socket()

bind()

socket()

bind()

sendto()

recvfrom() sendto()

recvfrom()

Abbildung 6.3: API-Aufrufe

Page 166: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

160 KAPITEL 6. BERKELEY SOCKETS

Kommunikations-Domäne:

• Internet Domain: (bidirektionale) Kommunikation zwischen Rechnern

– TCP/IP oder UDP/IP Adressierung: das 5er-Tupel

(Protocol, SendAddr, SendPort, RecvAddr, RecvPort)

• Unix Domain: (bidirektionale) Kommunikation auf einem (Unix-) Rechner

– 3er-Tupel:

(Protocol, local-pathname, foreign-pathname)

Server Client

bind()

listen()

accept()

socket()

connect()

socket()

recv()

send() recv()

send()

Abbildung 6.4: Kommunikation in der Unix-Domain

Page 167: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 161

6.4.3 Erzeugung eines Socket

Die Erzeugung einer Socket-Instanz erfolgt mit der Funktion socket():

# include <sys/types.h># include <sys/socket.h>

int socket( int domain, int type, int protocol )

• Die zu verwendende Domäne wird über den Parameter domain angegeben.

• Die zu verwendende Kommunikationssemantik wird über den Parameter type angegeben.

• Das konkrete Kommunikationsprotokoll kann über den Parameter protocol angegeben wer-den. Wird hier 0 angegeben (also nichts spezifiziert), so selektiert das System eine geeigne-tes Protokoll passend zu den ersten beiden Parametern.

• Rückgabewert ist ein Deskriptor, der den erzeugten Socket referenziert und in allen weite-ren darauf operierenden Funktionen benutzt wird.

• Für den Parameter domain sind in sys/socket.h Konstanten definiert:

AF_UNIX (auch als PF_UNIX) für die Unix-Domäne, AF_INET (auch als PF_INET) für dieInternet-Domäne. AF steht für address family, PF für protocol family.

• Für den Parameter type sind in sys/socket.h ebenfalls Konstanten definiert: SOCK_STREAMfür den Socket-Typ STREAM, SOCK_DGRAM für DGRAM und SOCK_DGRAM für RAW.

• Der Rückgabewert −1 signalisiert einen Fehler:EPROTONOSUPPORT, falls das spezifizierte Protokoll nicht unterstützt wirdENOPROTOTYPE, falls der Socket-Typ innerhalb der gewählten Domäne unzulässig istoder nicht unterstützt wird;weitere Fehler können aufgrund systeminterner Ressourcenknappheit oder mangelndenZugriffsrechten entstehen.

Beispiel:

int sd = socket(PF_INET, SOCK_STREAM, 0);

Damit wird ein Stream-Socket der Internet-Domäne erzeugt, welches das voreingestellte Trans-portprotokoll (hier TCP) als das dem Socket zugrundeliegende Kommunikationsprotokoll ver-wendet.

Page 168: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

162 KAPITEL 6. BERKELEY SOCKETS

6.4.4 Benennung eines Socket

Mit socket() werden die internen, den Socket repräsentierenden Datenstrukturen allokiert undinitialisiert. In diesem Zustand kann der Socket als unbenannter Socket bezeichnet werden. So-lange der Socket noch mit keiner Adresse verknüpft ist, kann der Socket noch nicht von fremdenProzessen angesprochen werden und somit auch keine Kommunikation stattfinden.

Die Benennung eines Socket erfolgt durch Zuweisung einer Adresse an den Socket:

• Socket-Adresse in der Internet-Domäne:

<protocol,local-address,local-port,foreign-address,foreign-port>

Ein Halb-Tupel <protocol,address,port> definiert dabei jeweils einen Kommunikationsend-punkt, foreign bezieht sich auf den zukünftigen Kommunikationspartner.

• Socket-Adressen in der Unix-Domäne:Hier werden die Kommunikationsverbindungen über Pfadnamen idenitifiziert, also überTupel der Form

<protocol,local-pathname,foreign-pathname>

Mit der Funktion bind() wird ein Kommunikationsendpunkt (ein Halbtupel, s.o.) festgelegt:

# include <sys/types.h># include <sys/socket.h>

int bind(int sd, /*socket descriptor*/struct sockaddr * address, /*address*/int addresslen /*length of address*/

);

Aufgrund der meist unterschiedlichen Adressformate der einzelnen Domänen sind Socket-Adressenals eine Folge von Bytes variabler Länge mittels einer generischen Datenstruktur anzugeben. AlleFunktionen der Socket-Schnittstelle, die Adressen verwenden, referenzieren diese nur über diesegenerische Datenstruktur, die in sys/socket.h wie folgt definiert ist:

struct sockaddr {u_char sa_len; /*total address length */u_char sa_family; /*address family */char sa_data[14]; /*protocol-specific address*/

};

• Die Komponente sa_len gibt die gesamte Länge der Socket-Adresse in Bytes an.

Page 169: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 163

• sa_family bezeichnet die Socket-Adressfamilie, die dem Adressformat der verwendeten Kom-munikationsdomäne entspricht.

• sa_data enthält die ersten 14 Bytes der Adresse selbst. Damit wird die Länge der tatsächli-chen Adresse nicht beschränkt!

Anm.: Dieser Punkt ist sehr implementierungsspezifisch und wird mit dem Übergang auf IPv6geändert werden müssen.

Beispiel für die Initialisierung und Zuweisung einer Adresse in der Unix-Domäne (Pfadname/tmp/my_socket):

# include <sys/un.h># include <string.h>

int sd;struct sockaddr_un sun_addr; /*socket address

*defined in <sys/un.h>

*//* Create a socket in the UNIX domain: */

sd = socket(PF_UNIX,SOCK_STREAM,0);

/* Initialize the socket address: */(void) memset(&sun_addr,0,sizeof(sun_addr));(void) strcpy(sun_addr.sun_path,"/tmp/my_socket");sun_addr.sun_family = AF_UNIX;sun_addr.sun_len = (u_char) ( sizeof(sun_addr.sun_len) +

sizeof(sun_addr.sun_family) +sizeof(sun_addr.sun_path) + 1);

/* bind the address to the socket: */bind(sd,(struct sockaddr *)&sin_addr, sizeof(sun_addr));

Benennung einer Adresse in der Internet-Domäne:

• Hier ist aus Rechneradresse und Portnummer eine Netzwerkadresse zu konstruieren

• Dazu gibt es eine ganze Reihe noch zu besprechender Bibliotheksfunktionen

Page 170: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

164 KAPITEL 6. BERKELEY SOCKETS

Prinzip:

# include <netinet/in.h>

int sd;struct sockaddr_in sin_addr; /*defined in <netinet.h>*/

/* create a socket: */sd = socket(PF_INET,SOCK_STREAM,0);

/* Initialize the address: ... */

/* Bind the name to the socket: */bind(sd, (struct sockaddr *)&sin_addr,sizeof(sin_addr));

Page 171: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 165

6.4.5 Aubau einer Kommunikationsverbindung

Der Aufbau einer Kommunikationsverbindung zwischen zwei nicht notwendigerweise verschie-denen Prozessen verläuft in der Regel asymmetrisch, wobei ein Prozess als Client, der andere alsServer bezeichnet wird. Der Server stellt normalerweise einen Dienst zur Verfügung, wartet alsoauf die Inanspruchnahme dieses Dienstes durch einen anderen Prozess, den Client; entsprechendwerden die Kommunikationsendpunkte als passiver bzw. aktiver Kommunikationsendpunkt be-zeichnet.

socket()

bind()

accept()

listen()

socket()

connect()

send()

recv() send()

recv()

Server Process Client Process

Abbildung 6.5: Verbindungsorientierte Client-Server-Kommunikation

Die Benennung des Server-Socket spezifiziert dabei die eine Hälfte einer möglichen Kommuni-kationsverbindung; die Vervollständigung wird durch einen Client initiiert, der aktiv die Verbin-dung zu einem Server anfordert und dabei die bekannte Adresse des Servers spezifiziert undseine eigene (implizit) mitliefert. Dazu dient die Funktion connect():

# include <sys/types.h># include <sys/socket.h>

int connect(int sd, /*client’s socket descriptor*/struct sockaddr * address, /* server’s address*/int adresslen

);

Das zweite und dritte Argument ist analog zu den enstprechenden Parametern von bind() aufServer-Seite zu verstehen. Der Server hat kein explizites bind() zur Adresszuweisung an seinen

Page 172: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

166 KAPITEL 6. BERKELEY SOCKETS

Socket durchzuführen, da es automatisch vom Betriebssystem vorgenommen wird, sofern derSocket zum Zeitpunkt der Verbindungsaufnahme noch unbenannt ist.

Anm.: Die Socket-Adresse des Kommunikationspartners wird in der Socket-Terminologie auchals Peer-Adresse bezeichnet.

Beispiel:

# include <netinet/in.h>

int sd;struct sockaddr_in sin_addr; /* for address of the server*/

sd = socket(PF_INET, SOCK_STREAM,0);

/* Initialize the socket address of the server (see below)*/

/*connect to the specified server:)connect(sd, (struct sockaddr *)&sin)addr, sizeof(sin_addr));

Der Client wird durch die Ausführung von connect() solange blockiert, bis entweder die Kom-munikationsverbindung vom Server vervollständigt wurde (somit erfolgreich hergestellt wurde)oder bis ein Fehler auftritt.

Bevor der Server Verbindungsanforderungen entgegen nehmen kann, muss sein erzeugter (socket())und benannter (bind()) Socket als passiver Kommunikationsendpunkt gekennzeichnet werden(als Bereitschaft, Verbindungen zu akzeptieren) - dazu dient die Funktion listen():

# include <sys/socket.h>

int listen(int sd,int backlog

);

• Der Parameter backlog spezifiziert die Länge einer Warteschlange des passiven Socket, inder Verbindungsanforderungen von Clients solange gehalten werden, bis sie vom Serverexplizit akzeptiert werden.

Dies erfolgt durch die Funktion accept():

# include <sys/types.h># include <sys/socket.h>

int accept(int sd,struct sockaddr * address,int * addresslen

);

Page 173: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 167

• Der Parameter sd ist der Deskriptor des benannten, passiven Socket.

• Die Argumente address und addresslen sind Wert-/Resultat-Parameter: sie müssen mit einerVariablen der domänen-spezifischen Socket-Adresse bzw. deren Länge initialisiert werden.Nach erfolgreichem Funktionsaufruf enthalten sie die Socket-Adresse des Client bzw. derentatsächliche Länge (also die Peer-Adresse). Ist der Server an der Peer-Adresse nicht interes-siert, so ist im Parameter addresslen der Nullzeiger anzugeben, der Parameter address bleibtdabei unberücksichtigt.

• accept() blockiert, bis eine Verbindungsanforderung eines Clients in der Warteschlange despassiven Sockets zur Verfügung steht.

• Als Resultat liefert accept() - sofern keine Fehler aufgetreten ist - einen Socket-Deskriptorzurück, der einen neuen Socket referenziert; dieser repräsentiert den Kommunikationsend-punkt für die nun fertige neue Verbindung.

server process client process

accept()

socket

connection

passivesocket

connection

socketconnect()

send(), recv()

Abbildung 6.6: Aufbau einer verbind.-orient. Client/Server-Kommunikation

Page 174: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

168 KAPITEL 6. BERKELEY SOCKETS

• Akzeptieren einer Kommunikationsverbindung in der UNIX-Domäne:

int sd, /*server socket descriptor*/conn_sd; /*connection socket desc. */

struct sockaddr_un client_addr; /*client socket address */int client_len; /*length of client address*/

/** sd = socket(); bind(sd,...); listen(sd,...);

*/

client_len = sizeof(client_addr);conn_sd = accept(sd, (struct sockaddr *) &client_addr,

&client_len);

• Akzeptieren einer Kommunikationsverbindung in der Internet-Domäne:

int sd, /*server socket descriptor*/conn_sd; /*connection socket desc. */

struct sockaddr_in client_addr; /*client socket address */int client_len; /*length of client address*/

/** sd = socket(); bind(sd,...); listen(sd,...);

*/

client_len = sizeof(client_addr);conn_sd = accept(sd, (struct sockaddr *) &client_addr,

&client_len);

Page 175: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 169

6.4.6 Client-Beispiel: Timeclient für Port 11011

Programm 6.2: Timeclient für Port 11011 (timcli.c)

1 # include <netdb .h>2 # include < netinet /in .h>3 # include < stdio .h>4 # include < string .h>5 # include <sys/ socket .h>6 # include <unistd .h>7 # include < stdlib .h>8

9 # define TPORT 1101110

11 int main( int argc , char ∗∗argv )12 { struct sockaddr_in addr ; struct hostent ∗hp ;13 int fd ; int nbytes ; char buf [BUFSIZ];14

15 if ( argc !=2)16 printf ( "usage: %s hostname\n", argv[0]), exit (1);17

18 if (!( hp = gethostbyname (argv [1]))) // IP−Adresse des Servers holen19 fprintf ( stderr , "unknown host: %s\n", argv[1]),20 exit (1);21

22 // bzero (&addr, sizeof ( addr )); // mit 0en fuellen23 // besser :24 memset(&addr ,0, sizeof (addr ));25 addr . sin_family = AF_INET; // TCP/IP−Verbindung26 addr . sin_port = htons (TPORT);// Port eintragen , Network Byte Order27

28 // Serveraddresse eintragen :29 // bcopy(hp−>h_addr, &addr. sin_addr , hp−>h_length);30 // besser ( aber Achtung auf Reihenfolge der Parameter )31 memcpy(&addr.sin_addr , hp−>h_addr, hp−>h_length);32

33 if (( fd = socket (AF_INET, SOCK_STREAM, 0)) <0)34 perror ( "socket " ), // Socket erzeugen35 exit (1);36

37 if ( connect ( fd , ( struct sockaddr ∗) &addr, sizeof (addr )) <0)38 perror ( "connect" ), // an Socketport verbinden39 exit (1);40 // vom Socket lesen41 while (( nbytes = read ( fd , buf , sizeof ( buf ))) >0)42 printf ( "%.∗s" , nbytes , buf );43

44 close ( fd );45 exit (0);46 }

Man beachte hier die Funktionen bcopy() und memcpy() und achte insbesondere auf die Reihen-folge der Parameter “Ziel” und “‘Quelle”!

Page 176: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

170 KAPITEL 6. BERKELEY SOCKETS

6.4.7 Überblick: Gebrauch von TCP-Ports

process table tcp ports well−known

ports, only

superuser

0

1023

49151

65535

80

11011

50123

registered ports,

some are still

free

free / dynamic

ports

CMD UID PID

timserv 1023130

0 11234httpd

netscape 130 15235client

server

server

liest / schreibt

liest / schreibt

liest / schreibt

Unix Operating System

Abbildung 6.7: Ports und Prozesse in Unix

• Wie erfahre ich, welche Prozesse aktuell einen bestimmten Port belegen?

lsof – list open files (and ports

lsof | grep TCP | grep 4711

Page 177: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 171

Beispiel:

• netscape liest Webseite www.uni-ulm.de

netscape:

gethostbyname("www.uni-ulm.de"); // get IP-Addr. serverfd = socket(AF_INET,SOCK_STREAM,0);sin_port = htons(8);sin_family = AF_INET;connect(fd, [to hsot www.uni-ulm.de; port 80]);write(fd, "GET /HTTP/1.0\m\n", 16);read(fd,buf, sizeof(buf));

thales (client) www.uni−ulm.de (server)

processes ports

netscape 45123

ports processes

80 httpd

temp.

network

connection

const

tem

p. c

onn.

Abbildung 6.8: netscape als Client

• httpd (HTTP Dämon, Webserver-Prog.) auf www.uni-ulm.de erhält Anfrage von netscapeauf thales.mathematik.uni-ulm.de

Page 178: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

172 KAPITEL 6. BERKELEY SOCKETS

httpd:

fd = socket(AF_INET, SOCK_STREAM,0);sin_port = htons(80);sin_family = AF_INET;bind(fd, [Internetdomain; port 80]);listen(fd, SOMAXCON); // Maximale Anzahl in Queuenfd = accept(fd, ...);read(nfd, buf, sizeof(buf));write(nfd,"<html><body> ... ", 586);

thales (client) www.uni−ulm.de (server)

processes ports

netscape 45123

ports processes

80 httpd

temp.

network

connection

const

bind

tem

p

Abbildung 6.9: Server httpd antwortet

Page 179: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 173

struct sockaddr

• zentraler Informationsträger für bind(), accept() und connect()

– bei AF_INET: struct sockaddr_in

– bei AF_UNIX: struct sockaddr_un

• wichtigster Inhalt, wenn Komponente sin_family auf AF_INET gesetzt:

– sin_port = Portnummer

– sin_addr = IP-Adresse Server / Client (bei accept)

• wer braucht was?

– bind(): braucht Infos für Server (Protokoll + Port)

– accept(): schreibt Infos über Client (Protokoll + IP-Adresse + Port)

– connect(): braucht Infos über Server (Protokoll + IP-Adresse + Port)

Wie erfahre ich, welcher Rechner von welchem Port aus sich an meinen Server angedockt hat?

• accept(fd, (struct sockaddr *) & cli_addr, ...)

accept() schreibt Infos über den Client in die Struktur cli_addr

• printf("port: %d\n", ntohs(cli_addr.sin_port));

ntohs() konvertiert Network-Byte-Order in Host-Byte-Order

• printf("host: %s\n", inet_ntoa(cli_addr.sin_addr));

inet_ntoa() konvertiert IP-Adresse in String (dotted decimal form)

Page 180: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

174 KAPITEL 6. BERKELEY SOCKETS

Programm 6.3: Time-Server, der nach dem Woher fragt (timserv-1.c)

1 # include <netdb .h>2 # include < netinet /in .h>3 # include <arpa/ inet .h>4 # include < stdio .h>5 # include < strings .h>6 # include < string .h>7 # include <sys/ socket .h>8 # include <sys/time .h>9 # include <unistd .h>

10 # include < stdlib .h>11 # include <sys/time .h>12 # include <time .h>13

14 # define TPORT 1101115 int main()16 { struct sockaddr_in addr , client_addr ;17 size_t client_add_len = sizeof ( client_addr );18 int sfd , fd ;19 /∗20 int optval = 1;21 ∗/22 memset(&addr ,0, sizeof (addr )); // mit 0en fuellen23 addr . sin_family = AF_INET; // TCP/IP−Verbindung24 addr . sin_port = htons (TPORT);// Port eintragen , Network Byte Order25 if (( sfd = socket (AF_INET, SOCK_STREAM, 0)) <0) // socket erzeugen26 perror ( "socket " ), exit (1);27 /∗28 if ( setsockopt ( sfd ,SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))<0)29 perror (" setsockopt "), // rasche Wiederzuweisung ermoeglichen30 exit (1);31 ∗/32 if ( bind ( sfd , ( struct sockaddr ∗) &addr, sizeof (addr )) <0)33 perror ( "bind" ), exit (1);34

35 if ( listen ( sfd , SOMAXCONN) <0)36 perror ( " listen " ), exit (1);37 while (( fd = accept ( sfd , ( struct sockaddr ∗) & client_addr ,38 & client_add_len )) >=0) {39 time_t clock ; char ∗ tbuf ;40 time(& clock ); // aktuelle Uhrzeit holen41 tbuf = ctime(& clock ); // in String schreiben42 write ( fd , tbuf , strlen ( tbuf )); // in Clientverbindung schreiben43 printf ( "Server : client port is %d\n", ntohs( client_addr . sin_port ));44 printf ( "Server : client IP is %s\n", inet_ntoa ( client_addr . sin_addr ));45 close ( fd );46 }47 exit (0); // seldomly reached48 }

Page 181: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 175

6.4.8 Der Datentransfer

Nachdem zwischen Client und Server eine Kommunikationsverbindung aufgebaut ist, kann einDatentransfer stattfinden. Dazu können in gewohnter Weise die System Calls des UNIX-I/O-Systems benutzt werden: read(), readv() (read from multiple buffers), write(), writev() (write intomultiple buffers).

Die Socket-Schnittstelle stellt 3 weitere Funktionspaare zur Verfügung, die die Semantik derUNIX-I/O-Funktionen um Socket- und protokollspezifische Eigenschaften und Mechanismenerweitern.

• send(), recv()

# include <sys/types.h># include <sys/socket.h>

ssize_t send ( int sd, void * buf, size_t len, int flags);ssize_t recv ( int sd, void * buf, size_t len, int flags);

Ist bei beiden Funktion für flags der Wert 0 angegeben, so sind sie identisch zu write() und read().Die Spezifikation bestimmter Methoden des Datentransfers oder das Versenden / Empfangenvon Kontrollinformationen kann durch entsprechende flag-Werte aktiviert werden:

MSG_OOB Die spezifizierten Daten sollen mit hoher Priorität gesendet bzw. empfangen wer-den. Solche Daten werden im Kontext von Sockets als out-of-band-Daten (OOB-Daten)bezeichnet. Sie werden im Vergleich zu normalen Daten auf einem logisch unabhängigenKanal gesendet. Bei OOB-Daten kann zudem der übliche Pufferungsmechanismus umgan-gen werden. Dieser Mechanismus unterliegt allerdings Beschränkungen und ist nur fürwenige, verbindungsorientierte Protokolle realisiert.

MSG_PEEK Auf der Seite des Empfängers wird hiermit spezifiziert, dass gepufferte Daten nurgelesen, aber nicht “konsumiert” werden sollen. Der nächste Lesezugriff liefert dieselbenDaten noch einmal zutücl.

MSG_WAITALL Der Lesezugriff soll solange blockieren, bis die angeforderte Datenmenge ins-gesamt zur Verfügung steht.

MSG_DONTWAIT Ausführung der Operation im nicht-blockierenden Modus.

MSG_DONTROUTE Ausgehende Datenpakete werden ohne Berücksichtigung einer Routing-Tabelle mur in das lokal angeschlossene Netzwerk gesendet. Dies ist nur für spezielle Dia-gnoseprogramme interessant.

Page 182: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

176 KAPITEL 6. BERKELEY SOCKETS

MSG_EOR Die Flagge end-of-record markiert das logische Ende eines Datensatzes; die Da-ten werden dabei mit zusätzlicher Kontrollinformation versendet. Sie kann aber nur ver-wendet werden, wenn das zugrundeliegende Kommunikationsprotokoll das Konzept derDatensatz-Übermittlung unterstützt.

MSG_EOF Hiermit wird das Ende der Datenübertragung markiert und als Kontrollinformationzusammen mit den angegebenen Daten übertragen.

Die für den Datentransfer bereitgestellten Flaggen sind zumeist auf spezielle Kommunikations-protokolle beschränkt und auch nur definiert, wenn die diese Protokolle auf dem System im-plementiert sind. Die protokollunabhängigen Flaggen sind MSG_PEEK, MSG_WAITALL undMSG_DONTWAIT; die beiden letzteren stehen nur in neueren Implementierungen zur Verfü-gung!

• sendto(), recvfrom()

# include <sys/types.h># include <sys/socket.h>

ssize_t sendto ( int sd, void * buf, size_t len, int flags,struct sockaddr * address, int addresslen);

ssize_t recvfrom ( int sd, void * buf, size_t len, int flags,struct sockaddr * address, int * addresslen);

Diese Funktionen stehen für den Datentransfer mit verbindungslosen Kommunikationsverfah-ren zur Verfügung. Dabei ist die Angabe der Sender- und Empfänger-Adresse in jedem Datenpa-ket erforderlich, da keine virtuelle Verbindung zwischen den Partnern besteht.

Die Angabe der Adressen (Parameter address und addresslen) sind wie bei den Funktionen connect()bzw. accept() anzugeben. Läßt man diese Parameter weg, so entsprechen diese Funktionen denobigen Funktionen send() und recv().

• sendmsg(), recvmsg()

# include <sys/types.h># include <sys/socket.h>

ssize_t sendmsg ( int sd, struct msghdr * msg, int flags );

ssize_t recvmsg ( int sd, struct msghdr * msg, int flags );

Diese beiden Funktionen stellen die allgemeinste Schnittstelle dar und erweitern die Funktiona-lität der obigen Funktionen. Mehr dazu siehe z.B. im Manual!

Page 183: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 177

6.4.9 Terminierung einer Kommunikationsverbindung

Das Schliessen und die damit verbundene Freigabe der systeminternen Ressourcen erfolgt mitder Funktion close() auf den entsprechenden Socket-Deskriptor. Bei Prozess-Termination werdendie Socket-Verbindungen in gleicher Weise wie die Datei- oder terminal-Verbindungen geschlos-sen.

In verbindungsorientierten Kommunikationsprotokollen, die ja einen verlässlichen Datentrans-fer garantieren, versucht das System beim Schliessen eines Socket für eine gewisse Zeit evt. nochausstehende, zwischengepufferte Daten zu transferieren. Dies lässt sich durch entsprechendeOperationen ändern.

Eine Verbindung zwischen zwei Sockets ist voll-duplex; dies bedeutet, dass Sende- und Emp-fangskanal logisch voneinander unabhängig sind. Mit der Funktion shutdown() lässt sich dieVerbindung auch nur in einer Richtung terminieren. Dies wird typischerweise bei verbindungs-orientierten Protokollen vom Sender dazu verwendet, dem Empfänger das Ende der Eingabeanzuzeigen. Die Empfängerseite bleibt für den Datentransfer weiterhin geöffnet. Das Schliessender Empfängerseite bewirkt, dass noch nicht konsumierte, zwischengepufferte Daten wie auchalle zukünftig noch eintreffenden Daten verworfen werden.

• shutdown()

# include <sys/socket.h>

int shutdown ( int sd, int how );

Der Wert von how:

0 Leseseite (Empfängerseite) wird geschlossen

1 Schreibseite (Senderseite) wird geschlossen

2 Beide Seiten werden geschlossen

6.4.10 Verbindungslose Kommunikation

In diesem Fall läuft die Kommunikation nach einem symmetrischen Modell, auch wenn ggf. ei-ner der Prozesse die Funktion des Servers, der andere die des Clients einnehmen kann (aberes findet kein Verbindungsaufbau statt). Die Adressen der Kommunikationspartner werden al-so nicht über eine virtuelle Kommunikationsverbindung festgelegt; in jedem Datenpaket mussstattdessen die Empfängeradresse beigefügt werden. Die Datenpakete werden bei verbindungs-loser Kommunikation oft auch als Datagramme und die Kommunikationsendpunkte als Data-gram Sockets (Socket-Typ: SOCK_DGRAM) bezeichnet.

Soll der Socket mit einer bestimmten lokalen Adresse benannt werden, so muss die Zuweisungder Adresse über die Socket-Funktion bind() vor dem ersten Datentransfer stattfinden; ansonstenerfolgt die benennung des lokalen Socket beim Senden des ersten Datagrams implizit durch dasBetriebssystem.

Page 184: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

178 KAPITEL 6. BERKELEY SOCKETS

• Versenden von Daten mit gleichzeitiger Spezifikation der Empängeradresse: Funktionensendto() und sendmsg()

• Empfang von Daten mit gleichzeitiger Gewinnung der Absenderadresse: Funktionen recv-from() und recvmsg()

Ein Datentransfer zwischen zwei Datagram-Sockets kann nur dann stattfinden, wenn beide Kom-munikationsendpunkte explizit über die Funktion bind() oder implizit über die Funktionensendto() oder sendmsg() benannt sind; ansonsten werden die gesendeten Datagramme verwor-fen!

Da verbindungslose Kommunikation auch unzuverlässige Datenzustellung bedeutet, sollte mansich in Client/Server-Anwendungen die gesendeten Datagramme vom Empfänger bestätigenlassen, sofern auf eine Anfrage keine Daten vom Empfänger erwartet werden. Ist ein kontrollier-ter und zuverlässiger Datentransport nötig, so müssen dies die Anwendungen in diesem selbstregeln!

• Prinzip der Kommunikation zwischen zwei Datagram Sockets:

socket()

bind()

recvfrom()

sendto()

socket()

bind()

sendto()

recfrom()

server process client process

Abbildung 6.10: Aufbau einer verb.-losen Client/Server-Kommunikation

• Der Server-Prozess benennt den erzeugten Datagram Socket mit einer nach aussen hin be-kannten Adresse.

• Die explizite Benennung des Client-Socket mit bind() ist optional und i.d.R. nicht erforder-lich, da nur der Server die Adresse des Kommunikationspartners als Empfängeradresse fürdie zu sendende Rückantwort benötigt!

• Wichtig ist in diesem Zusammenhang nur, dass die Kommunikationsverbindung innerhalbder gewählten Domäne eindeutig ist; bei der impliziten Benennung eines Socket durch dasBetriebssystem ist dies gewährleistet!

Page 185: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 179

Auf Datagram Sockets kann die Funktion connect() angewandt werden; damit wird jedoch keineKommunikationsverbindung aufgebaut, sondern die dabei spezifizierte Adresse dem Socket alsEmpfängeradresse zugewiesen, an die alle folgenden Datagramme zu senden sind. Ausserdemwerden dann an diesem Socket nur Datagramme empfangen, deren Adresse mit der in connect()angegebenen Adresse übereinstimmt.

Damit erübrigt sich die Identifizierung der empfangenen Datagramme. Da hiermit sowohl dieSender- wie Empfängeradresse festgelegt sind, können zum Datentransfer auch die Funktio-nen send() und recv() bzw. die UNIX-I/O-Systemaufrufe verwendet werden. In den Funktionensendto() und sendmsg() sollten die Socket-Adressen aus Gründen der Portabilität unspezifiziertbleiben. Die Empfängeradresse kann durch einen erneuten Aufruf von connect() jederzeit geän-dert werden sowie eine Beziehung durch Angabe einer ungültigen Adresse gelöscht werden.

Die Programme 6.4 (S. 180) und 6.5 (S. 181) setzen den früheren Time-Server und zugehörigenClient auf Basis UDP um – dies macht wenig Sinn in dieser Art der Anwendung, soll hier nurzur Demonstration dienen!

Page 186: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

180 KAPITEL 6. BERKELEY SOCKETS

Programm 6.4: Time-Server auf Basis UDP (nur zur Demo) (timserv-udp.c)

1 /∗ Modifikation unseres Timeservers ,2 ∗ nur zur DEMO, so nicht sinnvoll3 ∗/4

5 # include <netdb .h>6 # include < netinet /in .h>7 # include < stdio .h>8 # include < strings .h>9 # include < string .h>

10 # include <sys/ socket .h>11 # include <sys/time .h>12 # include <time .h>13 # include <unistd .h>14 # include < stdlib .h>15 # define SERVPORT 1101116 # define MAXLINE 25617

18 int main() {19 struct sockaddr_in addr , client_addr ;20 size_t client_addr_len = sizeof ( client_addr );21 char recvline [MAXLINE];22 int sfd ;23

24 memset(&addr, 0, sizeof (addr )); // mit 0en fuellen25

26 addr . sin_family = AF_INET; // UDP−Verbindung27 addr . sin_port = htons (SERVPORT);// Port eintragen , Network Byte Order28 addr . sin_addr . s_addr = htonl (INADDR_ANY);29

30 if (( sfd = socket (AF_INET, SOCK_DGRAM, 0)) <0) // socket erzeugen31 perror ( "socket " ), exit (1);32

33 if ( bind ( sfd , ( struct sockaddr ∗) &addr, sizeof (addr )) <0)34 perror ( "bind" ), exit (2);35

36 while (1) {37 if ( ( recvfrom ( sfd , recvline ,MAXLINE,0,38 ( struct sockaddr ∗) & client_addr , & client_addr_len )) < 0) {39 perror ( "Server −− recfrom"); exit (3);40 }41 time_t clock ; char ∗ tbuf ;42 time(& clock ); // aktuelle Uhrzeit holen43 tbuf = ctime(& clock ); // in String schreiben44 if ( sendto ( sfd , tbuf , strlen ( tbuf ),0, ( struct sockaddr ∗) & client_addr ,45 sizeof ( client_addr )) < strlen ( tbuf ) ) {46 perror ( "Server −− sendto"); exit (4);47 }48 }49 exit (0); // seldomly reached50 }

Page 187: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 181

Programm 6.5: Time-Client auf Basis UDP (nur zur Demo) (timcli-udp.c)

1 /∗ Modifikation unseres Time−Clients −− nur zur2 ∗ Demo, so nicht sinnvoll3 ∗/4

5 # include <netdb .h>6 # include < netinet /in .h>7 # include < stdio .h>8 # include < strings .h>9 # include < string .h>

10 # include <sys/ socket .h>11 # include <unistd .h>12 # include < stdlib .h>13 # include <arpa/ inet .h>14

15 # define SERVPORT 1101116

17 int main( int argc , char ∗∗argv )18 { struct sockaddr_in serv_addr , cli_addr ; struct hostent ∗hp ;19 int fd ; int n; char buf [BUFSIZ];20

21 if ( argc !=2)22 printf ( "usage: %s hostname\n", argv[0]), exit (1);23

24 if (!( hp = gethostbyname (argv [1]))) // IP−Adresse des Servers holen25 fprintf ( stderr , "unknown host: %s\n", argv[1]),26 exit (1);27

28 memset(&serv_addr ,0, sizeof ( serv_addr )); // mit 0en fuellen29 serv_addr . sin_family = AF_INET; // TCP/IP−Verbindung30 serv_addr . sin_port = htons (SERVPORT);// Port eintragen , Network Byte Order31 // Serveraddresse eintragen :32 memcpy(&serv_addr.sin_addr . s_addr ,hp−>h_addr, hp−>h_length);33

34 memset(&cli_addr ,0, sizeof ( cli_addr )); // mit 0en fuellen35 cli_addr . sin_family = AF_INET; // TCP/IP−Verbindung36 cli_addr . sin_port = htons (0); // Port eintragen , Network Byte Order37 cli_addr . sin_addr . s_addr = htonl (INADDR_ANY);38

39 if (( fd = socket (AF_INET, SOCK_DGRAM, 0)) <0)40 perror ( "socket " ), exit (1);41

42

43 if ( bind ( fd , ( struct sockaddr ∗) & cli_addr , sizeof ( cli_addr )) <0)44 perror ( "bind" ), exit (1);45

46 if ( sendto ( fd , "TIME", 5, 0, ( struct sockaddr ∗) & serv_addr ,47 sizeof ( serv_addr )) < 0) {48 perror ( " Client : sendto" ); exit (2);49 }50

51 shutdown( fd ,1);52

Page 188: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

182 KAPITEL 6. BERKELEY SOCKETS

53 if (( n = recvfrom ( fd , buf , sizeof ( buf ),0, ( struct sockaddr ∗) 0,54 ( int ∗) 0)) < 0 ) {55 perror ( " Client : recvfrom"); exit (3);56 }57 buf [n] = ’\0’ ;58 printf ( "%.∗s" , n, buf );59

60 close ( fd );61 exit (0);62 }

Page 189: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.4. DIE SOCKET-PROGRAMMIERSCHNITTSTELLE 183

6.4.11 Feststellen gebundener Adresse

Manchmal ist es notwendig, die lokal oder entfernt gebundene Socket-Adresse allein anhanddes Socket Deskriptors festzustellen; dazu dienen die Funktionen getsockname() und getpeer-name(), die als Resultat die lokale bzw. entfernt gebundene Adresse zurückliefern:

# include <sys/socket.h>int getsockname ( int sd,

struct sockaddr * address, int * addresslen);

int getpeername ( int sd,struct sockaddr * address, int * addresslen

);

Die Parameter sd, address, addresslen sind dabei in gleicher Weise wie in der Funktion accept() zuspezifizieren!

Die Funktion getsockname() ist insbesondere dann nützlich, wenn die lokale Socket-Adressevom System zugewiesen wurde. Das Feststellen der entfernt gebundenen Adresse mit der Funk-tion getpeername() ist dann notwendig, wenn ein Prozess diese Adresse benötigt und allein denSocket Deskriptor einer bereits akzeptierten Kommunikationsverbindung erhält und somit kei-nen Zugriff auf die Peer-Adresse besitzt. Dies ist beispielsweise bei durch den Internet Super-server inetd gestarteten Server-Prozessen der Fall.

Page 190: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

184 KAPITEL 6. BERKELEY SOCKETS

6.4.12 Socket-Funktionen im Überblick

• Aufbau

– socket():Erzeugen eines unbenannten Socket

– socketpair():Erzeugen eines Paares von miteinander verbundenen Sockets, siehe Manual

– bind():Zuweisung einer lokalen Adresse an einen unbenannten Socket

• Server

– listen():Einen Socket auf Verbindungsanforderungen vorbereiten

– accept():Eine Verbindungsanforderung akzeptieren

• Client

– connect():Eine Verbindung zu einem Socket anfordern

• Empfangen

– read():Daten einlesen

– readv():Daten in mehrere Puffer einlesen

– recv():Daten einlesen und Angabe von Optionen

– recvfrom():Daten einlesen, optional Senderadresse empfangen, Angabe von Optionen

– recvmsg():Daten in mehrere Puffer einlesen, optional Senderadresse und Kontrollinformationenempfangen, Angabe von Optionen

• Senden

– write():Daten senden

– writev():Daten aus mehreren Puffern senden

– send():Daten senden und Angabe von Optionen

– sendto():Daten an die spezifizierte Empfängeradresse senden, Angabe von Optionen

– sendmsg():Daten aus mehreren Puffern senden, und Kontrollinformationen an den spezifiziertenEmpfäger senden, Angabe von Optionen

• Ereignisse

– select():Multiplexen und auf I/O-Bedingungen warten, siehe Manual

Page 191: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.5. KONSTRUKTION VON ADRESSEN 185

• Terminieren

– shutdown():Eine Verbindung in eine oder beide Richtungen terminieren

– close():Eine Verbindung terminieren und Socket schliessen

• Administration

– getsockname():Feststellen der lokal gebundenen Socket-Adresse

– getpeername():Feststellen der entfernt gebundenen Socket-Adresse

– setsockopt():Ändern von Socket- und Protokoll-Optionen, siehe Manual

– getsockopt():Auslesen von Socket- und Protokoll-Optionen, siehe Manual

– fcntl():Ändern der I/O-Semantik, siehe Manual

– ioctl():Verschiedene Socketoperationen, siehe Manual

6.5 Konstruktion von Adressen

Adressen für die Lokalisierung eines Dienstes auf einem nicht notwendig entfernten System sindprotokoll-spezifisch und setzen sich in der Internet-Domäne aus einer Rechneradresse und einerden Dienst identifizierenden Portnummer zusammen. Anwendungen spezifizieren den anzu-fordernden Dienst i.r.G. über einen Namen statt über Rechneradresse plus Portnummer, so z.B.den WWW-Server auf dem Rechner www.mathematik.uni-ulm.de, dessen bereitgestellter Dienstmit http bezeichnet wird. Namen lassen sich schliesslich leichter merken als numerische Adres-sen, man erreicht dadurch auch eine gewisse Unabhängigkeit (Änderung von Adresse und Port-nummer unter Beibehaltung des Namens).

Für die Konvertierung von Namen in Adressen bzw. Portnummern, für die Konstruktion undManipulation von Netzwerkadressen sowie für die Lokalisierung eines Rechners gibt es eineReihe von Bibliotheksfunktionen, die allerdings nicht Bestandteil der Socket-Schnittstelle sind.

6.5.1 Socket-Adressen

• Alle Funktionen der Socket-Schnittstelle, die auf Socket-Adressen operieren, verwendeneine generische Socket-Adressstruktur.

• Damit wird die Unabhängigkeit der Socket-Schnittstelle von den konkreten Implementie-rungen der bereitgestellten Kommunikationsprotokolle erreicht.

• Die Interpretation der Socket-Adressen erfolgt in den protokollspezifischen Funktionen,die bei der Erzeugung einer Socket-Instanz durch die Domäne festgelegt sind.

Page 192: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

186 KAPITEL 6. BERKELEY SOCKETS

• Deklaration der Socket-Adressstruktur in auf 4.3BSD basierenden Betriebssystemen:

struct sockaddr {u_short sa_family; /* address family */char sa_data[14]; /* protocol-specific address */

}

– Die Komponente sa_family enthält das Adressformat.– Die Komponente sa_data maximal die ersten 14 Bytes der protokollspezifischen Adres-

se. Dies ist ein Implementierungsdetail der Socket-Schnittstelle und beschränkt nichtdie Länge der protokollspezifischen Adresse.

Die Implementierung von Netzwerkprotokollen stellt mit Blick auf die Performance viele Anfor-derungen an die Speicherverwaltung des Betriebssystems, so z.B.

• die Handhabung von Datenpuffern unterschiedlicher Länge,

• das einfache Hinzufügen / Entfernen von Kopfdaten oder

• die Datenübergabe zwischen den eigenverantwortlichen Funktionen der verschiedenenNetzwerkschichten.

Auf BSD-Systemen ist für die Kommunikation mit Sockets eine spezielle Speicherverwaltungimplementiert, die sogenannten memory buffers; dabei wird versucht, Kopieroperationen derDatenpakete zu minimieren. SVR6 basierende Systeme verwenden i.d.R. die Mechanismen desStreams-Subsystems.

Die Schnittstellen zwischen den Schichten des Socket-Modells sind zwar wohldefiniert, die Gren-zen in der Implementierung sind eher fließend, da die einer Schicht zugrundeliegenden Daten-strukturen oft auch den Funktionen der darüberliegenden Schicht zugänglich sind. So sind dasAdressformat und die ersten 14 Bytes der protokollspezifischen in der Socket-Schicht bekannt,sie sind aber auch der Anwendungsschicht bekannt (Abhängigkeit der Anwendung von denkonkreten Kommunikationsprotokollen!). Bezüglich der Kompatibilität und Portabilität entstehteine weitere Abhängigkeit dadurch, dass die generische Socket-Adresse eine Datenstruktur desBetriebssystemkerns ist und dass sich diese Struktur ab der Version 4.3BSD-Reno (und aller dar-auf aufbauenden Systeme) wie folgt geändert hat:

struct sockaddr {u_char sa_len; /* total address length */u_char sa_family; /* address family */char sa_data[14]; /* protocol specific address */

}

Die Komponente sa_len enthält die Länge der protokollspezifisichen Adresse in Bytes; die Ge-samtlänge der Datenstruktur ist unverändert 16 Bytes! Die Hinzunahme dieser Komponente istfür die systeminterne Implementierung notwendig, damit Adressen variabler Länge protokollu-nabhängig behandelt werden können. Die Anwendung hat davon keinen echten Vorteil, da dieLängeninformation Argument der entsprechenden Socket-Funktionen ist, somit bereits verfüg-bar ist.

Die sehr systemnahe Implementierung von Netzwerkanwendungen mittels der Socket-Schnittstellehat einige Nachteile, die sich insbesondere auf die Portabilität auswirken. Darauf soll im Folgen-den jedoch nicht weiter eingegangen werden (Fragen und Lösungen diesbezüglich wurden inder Dissertation von M. Etter behandelt).

Page 193: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.5. KONSTRUKTION VON ADRESSEN 187

6.5.2 Socket-Adressen der UNIX-Domäne

Hier werden für die Benennung von Sockets UNIX-Pfadnamen benutzt; die folgende Socket-Adressstruktur ist in sys/un.h definiert:

struct sockaddr_un {u_char sun_len; /*total address length incl. nullbyte*/u_char sun_family; /*address family AF_UNIX */char sun_path[104]; /*path name */

}

• In der Komponente sun_len ist die Länge der konkreten Socket-Adresse in Bytes anzuge-ben; diese ergibt sich aus der Länge der beiden Komponenten sun_len und sun_familysowie der Stringlänge des Pfadnamens in sun_path plus 1 (terminierendes Null-Byte!).

• Die Komponente sun_family bezeichnet die Adressfamilie bzw. das Adressformat und iststets mit der Konstanten AF_UNIX zu initialisieren.

• Die Dimension der Komponente sun_path mit 104 Bytes ist systemabhängig; in den mei-sten UNIX-Systemen ist die maximale Länge eines Pfadnamens auf 1024 Bytes (KonstanteMAX_PATH in limits.h) festgelegt. In Socket-Implementierungen, die auf dem Streams Sy-stem basieren, können Sockets mit Pfadnamen bis zu dieser Länge benannt werden. Werdenin Socket-Implementierungen die Adressstrukturen der Version 4.3BSD-Reno verwendet, soist die maximale Pfadlänge durch den Datentyp u_char der Komponente sun_len auf 256Bytes beschränkt!

Die Socket-Adressen sollten einer sicheren Konvention folgend immer mit Null-Bytes initialisiertwerden. Die Verwendung von absoluten Pfadnamen ist in einigen System erforderlich.

• Initialisierung:

struct sockaddr_un addr;(void) memset(&addr, 0, sizeof(addr));(void) strcpy(addr.sun_path, "/tmp/my_socket");addr.sun_family = AF_UNIX;# ifdef HAVE_SOCKADDR_SA_LENaddr.sun_len = (u_char) (sizeof(addr.sun_len) +

sizeof(addr.sun_family) +strlen(addr.sun_path) +1);

# endif

Page 194: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

188 KAPITEL 6. BERKELEY SOCKETS

6.5.3 Socket-Adressen in der Internet-Domäne

Hier ist die Socket-Adressstruktur in netinet/in.h wie folgt definiert:

struct in_addr {u_long s_addr; /* 32 bit netid/hostid */

};

struct sockaddr_in {u_char sin_len; /*total address length (16 bytes)*/u_char sin_family; /*address family AF_INET */u_short sin_port; /*16 bit port number */struct in_addr sin_addr; /*IP address */char sin_zero[8];/*unused */

};

• Die Datenstruktur in_addr enthält nur die Komponente s_addr, in der eine 32 Bit langeAdresse des Internetprotokolls IP in Netzwerkbyteordnung gespeichert wird.

• Die Komponente sin_len der Datenstruktur sockaddr_in ist immer sizeof(struct sockad-dr_in) = 16 Bytes.

• Die Adressfamilie in Komponente sin_family ist immer AF_INET.

• Die Komponente sin_port ist eine 16 Bit lange Port-Nummer in Netzwerkbyteordnung, diezusammen mit der Internet-Adresse sin_addr einen Kommunikationsendpunkt eindeutigdefiniert.

• Die Komponente sin_zero ist aus Gründen der Portabilität mit Null-Bytes zu initialisie-ren und dient lediglich zur Ausweitung der Datenstruktur auf die Länge der generischenSocket-Adressstruktur sockaddr (siehe Abb. 6.11, S. 189).

Für die implizite Selektierung einer geeigneten lokalen IP-Adresse oder auch Portnummer durchdas System existieren jeweils eine ausgezeichnete IP-Adresse und eine Portnummer mit Sonder-bedeutung: die IP-Adresse 0 ( INADDR_ANY) bzw. 0.0.0.0 in punktiertem Dezimalformat sowiedie Portnummer 0. Bei der Spezifikation der Komponenten sin_addr.s_addr und sin_port mitdiesen sog. Wildcards wird die lokale IP-Adresse nach einem erfolgreichen Verbindungsaufbauentsprechend der ein- / ausgehenden Netzwerkschnittstelle automatisch vom System festgelegtund bei der Benennung eines Socket eine frei Portnummer aus dem Bereich der kurzlebigenPortnummern gewählt.

Page 195: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.5. KONSTRUKTION VON ADRESSEN 189

fam

ily

len

d a t a

1 14 Bytes1

sockaddr{}

len

(16

)

AF

_IN

ET

portaddr

\0 \0 \0 \0\0

zero

\0\0 \0 \0

1 2 4 81

in_addr{}

sockaddr_in{}

addr.s_addr

4 Bytes

Abbildung 6.11: Organisation der Adress-Struktur sockaddr_in

6.5.4 Byte-Ordnung

Die Anordnung von Bytes bei Mehr-Byte-Grössen erfolgt nicht bei allen Computersystemen inder gleichen Reihenfolge. Für die Speicherung einer 2-Byte-Grösse gibt es zwei Möglichkeiten:

• das niederwertige Byte liegt an der Startadresse(Little-Endian-Anordnung)

• das höherwertige Byte liegt an der Startadresse(Big-Endian-Anordnung)

Bei 4-Byte-Grössen können zusätzlich noch die 2-Byte-Grössen unterschiedlich angeordnet sein!

Für den Austausch protokollspezifischer Daten werden in den Internet-Protokollen nur 2- und4-Byte-Integerwerte im Big-Endian-Format verwendet (network byte order), die Bit-Ordnungselbst ist ebenfalls in diesem Format!

Die Implementierungen von Netzwerkprotokollen sind somit auf jedem System dafür verant-wortlich, dass protokollspezifische Daten in Netzwerkbyteordnung transferiert werden, d.h. dieDaten sind von der Byteordnung des Computersystems (host) in die Netzwerkbyteordnung zutransferieren, sofern sich die Anordnungen unterscheiden. Zur Konvertierung von 2-Byte-Grössen(short) und 4-Byte-Grössen (long) gibt es folgende Funktionen (Makros):

u_short htons(u_short hostshort); /*host-to-network short*/

Page 196: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

190 KAPITEL 6. BERKELEY SOCKETS

u_long htonl(u_long hostlong); /*host-to-network long*/

u_short ntohs(u_short netshort); /*network-to-host short*/

u_long ntohl(u_long netlong); /*network-to-host long*/

Die protokollspezifischen Mehrbytegrössen, die bei Internet-Protokollen zu spezifizieren sind,sind die Internet-Adresse und die Portnummer in sockaddr_in (Komponenten sin_addr.s_addr undsin_prt).

6.5.5 Spezifikation von Internet-Adressen

Die 32-Bit-IP-Adressen (IPv4) werden meist in der sog. punktiertes Dezimalformat (dotted deci-mal notation) angebenen; diese entsteht dadurch, dass byte-weise Dezimalzahlen gebildet wer-den, die durch Punkt getrennt sind.

(thales.mathematik.uni−ulm.de)134.60.66.5

1000 0110 0011 1100 0100 0010 0000 0101

Abbildung 6.12: dotted-decimal notation

Jeder Host in einem IP-Netzwerk ist über eine IP-Adresse eindeutig identifiziert. Ist ein Host anmehrere IP-Netze angeschlossen (multi-homed host), so muss dieser für jedes angeschlosseneNetz eine IP-Adresse besitzen. Über einen Alias-Mechanismus können einem Host auch mehrereIP-Adressen zugeordnet werden.

Zur Manipulation und Konvertierung von IP-Adressen gibt es wieder einige Bibliotheksfunktio-nen, die im Headerfile arpa/inet.h definiert sind.

• Umwandlung eines als String in dotted-decimal notation vorliegende IP-Adresse in eine 32-Bit-IP-Adresse: Funktion inet_addr()

# include <arpa/inet.h>

unsigned long inet_addr ( char * ipaddr);

Rückgabewerte ist eine 32-Bit-IP-Adresse oder im Fehlerfall die Konstante INADDR_NONE(0xffffffff),die allerdings einer gültigen Broadcast-Adresse entspricht. Zu beachten ist auch,dass der Rückgabewert unsigned long und nicht struct in_addr. Dies behebt die folgendeFunktion:

Page 197: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.5. KONSTRUKTION VON ADRESSEN 191

• inet_aton()

# include <sys/socket.h># include <netinet/in.h># include <arpa/inet.h>

int inet_aton (char * ipaddr; /*dotted decimal notation */struct in_addr * in_addr; /*result: 32-bit-IP-address*/

);

Rückgabewert ist 1 im Erfolgsfall, 0 sonst!

• Konvertierung einer 32-Bit-IP-Adresse in dotted-decimal notation: Funktion inet_ntoa()

# include <sys/socket.h># include <netinet/in.h># include <arpa/inet.h>

char * inet_ntoa ( struct in_addr inaddr);

• Beispiel für eine Anwendung:

struct sockaddr_in addr;

if(inet_aton("134.60.66.5", &addr.sin_addr))(void) printf("IP-Address: %s\n",

inet_ntoa(addr.sin_addr));

Page 198: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

192 KAPITEL 6. BERKELEY SOCKETS

6.5.6 Hostnamen

Die Beziehung zwischen Hostnamen und IP-Adressen werden in der Internet-Domäne jeweils inder Datenstruktur hostent repräsentiert, die als Resultat der Funktionen gethostbyname() undgethostbyaddr() geliefert wird. Diese Funktionen zur Adress-Resolution werden als Resolverbezeichnet.

Die Struktur hostent (in netdb.h definiert):

struct hostent {char * h_name; /*official name of host */char ** h_aliases; /*alias list */int h_addrtype; /*host address type (address family)*/int h_length; /*length of address */char ** h_addr_list; /*address list from name server */

}# define h_addr h_addr_list[0]

/*address for backward compatibility*/

Diese Datenstruktur beschreibt den offiziellen Namen des Rechners in der Komponente h_name,eine Liste seiner öffentlichen Alias-Namen in h_aliases, den Adress-Typ in h_addrtype, die Län-ge einer Adresse in h_length und eine Liste der IP-Adressen (in Netzwerkbyteordnung) in h_addr_list.Falls es sich bei dem Rechner um einen multi-homed host handelt oder Alias-Adressen definiertsind, so enthält die Liste der IP-Adressen entsprechend viele Elemente. Aus Kompatibilitätsgrün-den verweist die Makrodefinition h_addr auf das erste Element dieser Liste.

Die Funktionen gethostbyname() und gethostbyaddr():

# include <netdb.h>struct hostent * gethostbyname (char * name);struct hostent * gethostbyaddr ( char * addr,

int len,int type

);

In der Funktion gethostbyaddr() ist die protokollspezifische Adresse in Netzwerkbyteordnung,deren Länge und der Adress-Typ anzugeben. In der Internet-Domäne sind als Parameter eineIP-Adresse, deren Länge, zu erhalten als sizeof(struct in_addr), und die Konstante AF_INETanzugeben.

Die Spezifikation von Hostnamen erfolgt entweder über einfache Namen wie beispielsweise tha-les oder über absolute Namen wie thales.mathematik.uni-ulm.de.. Ein absoluter Namen wirdauch als Fully Qualified Domain Name (FQDN) bezeichnet; diese müssen mit einem Punkt en-den, der die Wurzel des hierarchisch geordneten Namensraums bezeichnet. Relative Hostnamenwerden abhängig von der administrativen Konfiguration des Systems mit Hilfe der auf dem lo-kalen System voreingestellten Namensdomäne vervollständigt. In Benutzeranwendungen wirdder abschliessende Punkt bei absoluten Namen meist weggelassen.

Page 199: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.5. KONSTRUKTION VON ADRESSEN 193

Programm 6.6: Hostnamen ermitteln (hostent.c)

1 /∗ hostent . c : print hostent ∗/2

3 # include < stdio .h>4 # include < stdlib .h>5 # include <netdb .h>6 # include <sys/ socket .h>7 # include < netinet /in .h>8 # include <arpa/ inet .h>9

10 void print_hostent ( char ∗ host ) {11 struct hostent ∗ hp ;12

13 (void) printf ( "%s:\n", host );14 if ( (hp = gethostbyname ( host )) ) {15 char ∗∗ ptr ;16

17 (void) printf ( " Offizieller Host−Name: %s\n", hp−>h_name);18

19 for ( ptr = hp−>h_aliases ; ptr && ∗ptr; ptr++)20 (void) printf ( " alias : %s\n", ∗ptr );21

22 if ( hp−>h_addrtype == AF_INET)23 for ( ptr = hp−>h_addr_list ; ptr && ∗ptr; ptr++)24 (void) printf ( " Adresse: %s\n",25 inet_ntoa (∗(( struct in_addr ∗) ∗ptr )));26 } else27 (void) printf ( "−−−−> Kein Eintrag!\n");28

29 (void) printf ( "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−\n");30 }31

32 int main( int argc , char ∗∗ argv ) {33 int i ;34 for ( i = 1 ; i < argc ; i++)35 print_hostent (argv [ i ]);36 exit (0);37 }

thales$ gcc -o hostent -Wall -lxnet hostent.cthales$ hostent thales turingthales:Offizieller Host-Name: thales

alias: ftpalias: wwwalias: popalias: glueckalias: adiAdresse: 134.60.66.5

------------------------------------------turing:Offizieller Host-Name: turing

Page 200: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

194 KAPITEL 6. BERKELEY SOCKETS

alias: loghostalias: mailhostAdresse: 134.60.166.1

------------------------------------------thales$

Die Beziehungen zwischen Hostnamen und Internet-Adressen werden in der verteilten Daten-bank des Domain Name System (DNS) verwaltet; die darin enthaltenen Informationen sindüber sogenannte Nameserver zugänglich. Alternativ dazu werden Informationen über Hostna-men und Internet-Adressen auf dem lokalen System in der Datei /etc/hosts oder über den Net-work Information Service (NIS) bereitgestellt. Die unterschiedlichen Möglichkeiten der Adress-Resolution zeigt die folgende Abbildung:

localname serverresolver

local hostsdatabase

NIS

application

internetother

name servers

configuration

resolver

gethostbyXYZ()

Abbildung 6.13: Methoden der Adress-Resolution

Die auf eine Anfrage gelieferten Informationen variieren aufgrund der verschiedenen Zugangs-verfahren und auch unterschiedlichen Organisationen der Datenbanken. Die Zugangsverfahrenhängen auch von der Implementierung wie der administrativen Konfiguration des Resolvers ab.Die Resolver-Funktionen liefern auf jeden Fall den offiziellen Hostnamen und eine IP-Adressein der Struktur hostent zurück. Bei Verwendung lokaler Mechanismen liefern einige Systemejedoch nur einfache und keine absoluten Hostnamen zurück. Die wesentlichen Unterschiede zei-gen sich bei Aliasnamen und Aliasadressen. Wird beispielsweise die Host-Tabelle oder NIS ver-wendet, erhält man genau eine Adresse und alle Aliasnamen. Werden die Informationen überNameserver angefordert, so erhält man eventuell Aliasadressen und höchstens einen Aliasna-men, sofern es sich bei demi in der Anfrage spezifizierten Hostnamen um einen Aliasnamenhandelt; dazu noch einmal obiges Programm:

thales$ hostent www #using NISwww:Offizieller Host-Name: thales

alias: ftpalias: wwwalias: popalias: glueckalias: adiAdresse: 134.60.66.5

------------------------------------------thales$ hostent www.mathematik #using DNS

Page 201: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.5. KONSTRUKTION VON ADRESSEN 195

www.mathematik:Offizieller Host-Name: thales.mathematik.uni-ulm.de

Adresse: 134.60.66.5------------------------------------------

6.5.7 Lokale Hostnamen und IP-Adressen

Besteht bereits eine Verbindung, so können der lokale Hostname und die lokale IP-Adresse mitHilfe der Socket-Funktion gethostname() und der Resolver-Funktion gethostbyaddr() ermitteltwerden.

• Die Funktion gethostname():

# include <unistd.h>

int gethostname(char * name, size_t namelen);

Programm 6.7: Hostnamen bestimmen (gethost.c)

1 # include < stdio .h>2 # include <unistd .h>3

4 int main() {5

6 char name [20];7 if ( gethostname (name,20) == 0 )8 (void) printf ( "Hostname: %s\n", name);9 return 0;

10 }

Die maximale Länge eines Hostnamens ist auf den meisten Systemen über die KonstanteMAXHOSTNAMELEN im Headerfile sys/param.h festgelegt.

• Das Kommando uname:

thales$ uname -aSunOS thales 5.9 Generic_117171-13 sun4u sparc SUNW,Sun-Fire-V240

Page 202: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

196 KAPITEL 6. BERKELEY SOCKETS

• Die Funktion uname():Dazu ist im Headerfile sys/utsname.h folgende Datenstruktur definiert:

struct utsname {char sysname[SYS_NMLN]; /*operating system name */char nodename[SYS_NMLN]; /*node name (host name) */char release[SYS_NMLN]; /*operating system release level*/char version[SYS_NMLN]; /*operating system version level*/char machine[SYS_NMLN]; /*hardware type */

}

Die Funktion selbst ist

int uname( struct utsname * buf);

Beispiel:

Programm 6.8: Funktion uname() (uname.c)

1 # include < stdio .h>2 # include <sys/utsname.h>3

4 int main (){5

6 struct utsname buf ;7 if ( uname(&buf) >= 0 ) {8 (void) printf ( "Host: %s\nOS: %s\nRelease: %s\n",9 buf .nodename, buf .sysname, buf . release );

10 (void) printf ( "Version : %s\nHardware: %s\n",11 buf . version , buf .machine );12 return 0;13 } else { return 1; }14 }

Page 203: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.5. KONSTRUKTION VON ADRESSEN 197

6.5.8 Portnummern und Dienste

In der Internet-Protokollfamilie werden in den Protokollen der Transportschicht (TCP und UDP)16 Bit lange Portnummern für die Identifizierung eines Dienstes bereitgestellt. Diese 65536 Port-nummern werden von der IANA ( Internet Assigned Numbers Authority) in drei Bereiche eingeteilt:well-known ports (0− 1023), registered ports (1024− 49151) und dynamic and/or private ports(49152− 65535). Der aktuelle Stand ist in der Datei

• ftp://ftp.isi.edu/in-notes/iana/assigments/port-numbers

verfügbar.

Well-known ports identifizieren bekannte Internet-Dienste. So wird in allen TCP/IP-Implementierungendem Telnet-Server telnetd die TCP-Portnummer 23 und dem TFTP-Server tftpd (Trivial FileTransfer Protocol) die UDP-Portnummer 69 zugewiesen, sofern dieses Anwedungsprotokoll unter-stützt wird und die entsprechenden Netzwerkanwendungen auf dem System bereitgestellt sind.Auf UNIX-Systemen werden die Portnummern aus dem Bereich 1 − 1023 als reservierte Port-nummern bezeichnet und können nur von Prozessen mit Superuser-Privilegien zur Benennungvon Sockets verwendet werden. Die sog. well-known ports belegen hier die Portnummern 1 − 511und die Portnummern 512− 1023 sind für Client-Anwendungen mit Supreuser-Privilegien reser-viert, die eine reservierte Portnummer als Bestandteil der Client/Server-Authentifizierung benö-tigen. Beispiele dafür sind rlogin und rsh.

Von der IANA nicht verwaltet werden Dienste, die registrierte Portnummern verwendet (ledig-lich als Konvention aufgelistet). So sind z.B. die Portnummern 6000− 6063 für einen X WindowServer für beide Protokolle (allerdings derzeit nur TCP verwendet) registriert. Nach Konventionbinden X-Server für passive Sockets die Portnummern 6000 + x, wobei x die Nummer des Dis-plays angibt. Wengleich die registrierten Portnummern frei verfügbar sind zur Benennung einesSocket beliebig genutzt werden können, ist dies keinesfalls zu empfehlen.

Über die dynamischen und privaten Portnummern, häufig auch als kurzlebige Portnummern(ephemeral ports) bezeichnet, wird von der IANA nichts ausgesagt. Sie sind für eine impliziteBenennung eines Socket durch das Betriebssystem reserviert. Die meisten UNIX-Systeme bindenheute noch die Nummern 1024− 5000 für kurzlebige Portnummern; damit können maximal 3977Sockets (typischerweise für Client-Anwendungen) zu einem Zeitpunkt implizit benannt sein.Solaris-Betriebssysteme verwenden kurzlebige Portnummern aus dem Bereich 32768− 65535.

Die Beziehungen zwischen Portnummern und den offiziellen Namen der Dienste sowie derenAliasnamen werden in der Datei /etc/services oder einer entsprechenden NIS-Tabelle definiert.Dazu ist im Headerfile netdb.h folgende Struktur definiert:

struct servent {char * s_name; /*official service name */char ** s_aliases; /*alias list */int s_port; /*port number (network byte order*/char * s_proto; /*protocol to use */

}

Page 204: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

198 KAPITEL 6. BERKELEY SOCKETS

Die Zugriffsfunktionen:

• getservbyname()

# include <netdb.h>

struct servent * getservbyname(char * name,char * protocol

);

• getservbyport()

# include <netdb.h>

struct servent * getservbyport( int port; char * protocol );

Programm 6.9: Port und Protokoll eines Dienstes (getserv.c)

1 # include < stdio .h>2 # include < stdlib .h>3 # include <netdb .h>4 # include < netinet /in .h>5

6 void print_servent ( char ∗ name, char ∗ protocol ) {7 struct servent ∗ sp ;8

9 if ( (sp = getservbyname (name, protocol )) ) {10 char ∗∗ ptr ;11

12 (void) printf ( " Offizieller Name des Dienstes: %s\n",13 sp−>s_name);14

15 for ( ptr = sp−> s_aliases ; ptr && ∗ptr; ptr++)16 (void) printf ( " Alias: %s\n", ∗ptr );17

18 (void) printf ( " Port−#: %d\n",19 ntohs ( ( u_short ) sp−>s_port ));20 (void) printf ( " Protokoll: %s\n", sp−>s_proto );21 } else22 (void) printf ( "Kein Eintrag!\n" );23 }24

25 int main( int argc , char ∗∗ argv ) {26 if ( argc != 3 ) {27 fprintf ( stderr , "Usage: %s service protocol\n", argv [0]);28 exit (1);29 } else30 print_servent (argv [1], argv [2]);31 exit (0);32 }

Page 205: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.6. GEPUFFERTE EIN- / AUSGABE FÜR NETZWERKVERBINDUNGEN 199

Übersetzung aud Ausführung:

thales$ gcc -Wall -o getserv -lxnet getserv.cthales$ getserv telnet tcpOffizieller Name des Dienstes: telnet

Port-#: 23Protokoll: tcp

thales$

6.6 Gepufferte Ein- / Ausgabe für Netzwerkverbindungen

Die Ein- und Ausgabe über Netzwerkverbindungen bringt in Vergleich zur Behandlungen vonDateien und interaktiven Benutzern einige Veränderungen mit sich. Wenn SOCK_STREAM zumEinsatz gelangt, so kommen die Daten zwar in der korrekten Reihenfolge an, jedoch nicht in derursprünglichen Paketisierung. Als ursprüngliche Pakete werden hier die Daten betrachtet, diemit Hilfe eines einzigen Aufrufs von write() geschrieben werden:

const char greeting[] = "Hi, how are you?\r\n";ssize_t nbytes = write(sfd, greeting, sizeof greeting);

Wenn beispielsweise bei einer Netzwerkverbindung immer vollständige Zeilen mit write()geschrieben werden, ist es möglich, dass die korrespondierende |read()|-Operation nur einenTeil einer Zeile zurückliefert oder auch ein Fragment, das sich über mehr als eine Zeile erstreckt.Diese Problematik legt es nahe, nur zeichenweise einzulesen, wenn genau eine einzelne Zeileeingelesen werden soll:

char ch;stralloc line = {0};while (read(fd, &ch, sizeof ch) == 1 && ch != ’\n’) {

stralloc_append(&line, &ch);}

Diese Vorgehensweise ist außerordentlich ineffizient, weil Systemaufrufe wie read() zu einemKontextwechsel zwischen dem aufrufenden Prozess und dem Betriebssystem führen. Wenn einKontextwechsel für jedes einzulesende Byte initiiert wird, dann ist der betroffene Rechner mehrmit Kontextwechseln als mit sinnvollen Tätigkeiten beschäftigt. Wenn jedoch mit

char buf[512];ssize_t nbytes = read(fd, buf, sizeof buf)

eingelesen wird, ist möglicherweise mehr als nur die gewünschte Zeile in buf zu finden, mögli-cherweise auch nur ein Teil der Zeile.

Entsprechend ist eine gepufferte Eingabe notwendig, bei der die Eingabe-Operationen aus einemPuffer versorgt werden, der, wenn er leer wird, mit Hilfe einer read()-Operation aufzufüllen ist.Die Datenstruktur für einen Eingabe-Puffer benötigt entsprechend einen Dateideskriptor, einenPuffer und einen Positionszeiger innerhalb des Puffers (siehe Abbildung 6.14, S. 200):

Page 206: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

200 KAPITEL 6. BERKELEY SOCKETS

fd

buf

s

len

a

pos

0 pos bu

f.le

n

bu

f.a

Abbildung 6.14: Struktur eines Eingabepuffers

typedef struct inbuf {int fd;stralloc buf;unsigned int pos;

} inbuf;

Entsprechend dem letzten erfolgreichen Aufruf von read() ergibt sich ein Füllgrad des Puf-fers, der von buf.len repräsentiert wird. Der Positionszeiger pos begann unmittelbar nach derread()-Operation auf Position 0 und wandert bei jeder Einlese-Operation aus dem Puffer derGrenze von buf.len entgegen. Wird die Grenze erreicht, so ist die nächste read()-Operationfällig.

Programmtext 6.10 (S. 201) zeigt eine Schnittstelle für diesen Eingabepuffer.

Die Funktion inbuf_alloc() dient dazu, die Größe des Puffers einzurichten, wobei eine sinn-volle Voreinstellung automatisch gewählt wird, wenn der Aufruf dieser Funktion unterbleibt.Als Einlese-Operationen vom Puffer dienen inbuf_read() und inbuf_getchar(), die sichin ihrer Aufrufsemantik an read() bzw. fgetc() orientieren. Ein zuviel gelesenes Zeichenkann mit inbuf_back() wieder zum erneuten Einlesen zur Verfügung gestellt werden. Mitinbuf_free() wird der Puffer freigegeben (de-alloziert).

Page 207: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.6. GEPUFFERTE EIN- / AUSGABE FÜR NETZWERKVERBINDUNGEN 201

Programm 6.10: Schnittstelle für einen Eingabe-Puffer (buffered/inbuf.h)

1 # ifndef INBUF_H2 # define INBUF_H3

4 # include < stralloc .h>5 # include <unistd .h>6

7 typedef struct inbuf {8 int fd ;9 stralloc buf ;

10 unsigned int pos ;11 } inbuf ;12

13 /∗ set size of input buffer ∗/14 int inbuf_alloc ( inbuf∗ ibuf , unsigned int size );15

16 /∗ works like read (2) but from ibuf ∗/17 ssize_t inbuf_read ( inbuf∗ ibuf , void∗ buf , size_t size );18

19 /∗ give buffer parameters −− it’ s just for demonstration ∗/20 int inbuf_pars ( inbuf ibuf , int ∗ len , int ∗ a , int ∗ pos );21

22 /∗ works like fgetc but from ibuf ∗/23 int inbuf_getchar ( inbuf ∗ ibuf );24

25 /∗ move backward one position ∗/26 int inbuf_back ( inbuf∗ ibuf );27

28 /∗ release storage associated with ibuf ∗/29 void inbuf_free ( inbuf∗ ibuf );30

31 # endif

Page 208: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

202 KAPITEL 6. BERKELEY SOCKETS

Programm 6.11: Implementierung des Eingabe-Puffers (buffered/inbuf.c)

1 # include <errno .h>2 # include < string .h>3 # include <unistd .h>4 # include "inbuf .h"5

6 # include < stdio .h>7

8 /∗ set size of input buffer ∗/9 int inbuf_alloc ( inbuf∗ ibuf , unsigned int size ) {

10 ibuf−>pos = 0;11 ibuf−>buf = ( stralloc ) {0};12 return stralloc_ready (& ibuf−>buf, size );13 }14

15 /∗ works like read (2) but from ibuf ∗/16 ssize_t inbuf_read ( inbuf∗ ibuf , void∗ buf , size_t size ) {17 if ( size == 0) return 0;18 if ( ibuf−>pos >= ibuf−>buf.len ) {19 if ( ibuf−>buf.a == 0 && ! inbuf_alloc ( ibuf , 512)) return −1;20 /∗ fill input buffer ∗/21 ssize_t nbytes ;22 do {23 errno = 0;24 nbytes = read ( ibuf−>fd, ibuf−>buf.s , ibuf−>buf.a );25 } while ( nbytes < 0 && errno == EINTR);26 if ( nbytes <= 0) return nbytes ;27 ibuf−>buf.len = nbytes ;28 ibuf−>pos = 0;29 }30 ssize_t nbytes = ibuf−>buf.len − ibuf−>pos;31 if ( size < nbytes ) nbytes = size ;32 memcpy(buf, ibuf−>buf.s + ibuf−>pos, nbytes );33 ibuf−>pos += nbytes ;34 return nbytes ;35 }36

37 /∗ give buffer parameters ∗/38 int inbuf_pars ( inbuf ibuf , int ∗ len , int ∗ a , int ∗ pos ) {39 ∗ len = ibuf . buf . len ; ∗ a = ibuf . buf .a ;40 ∗ pos = ibuf . pos ;41 return 1;42 }43

44

45 /∗ works like fgetc but from ibuf ∗/46 int inbuf_getchar ( inbuf ∗ ibuf ) {47 char ch ;48 ssize_t nbytes = inbuf_read ( ibuf , &ch, sizeof ch );49 if ( nbytes <= 0) return −1;50 return ch ;51 }52

Page 209: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.6. GEPUFFERTE EIN- / AUSGABE FÜR NETZWERKVERBINDUNGEN 203

53 /∗ move backward one position ∗/54 int inbuf_back ( inbuf∗ ibuf ) {55 if ( ibuf−>pos == 0) return 0;56 ibuf−>pos−−;57 return 1;58 }59

60 /∗ release storage associated with ibuf ∗/61 void inbuf_free ( inbuf∗ ibuf ) {62 stralloc_free (& ibuf−>buf);63 }

Programmtext 6.11 (S. 202) zeigt die Implementierung der Schnittstelle für diesen Eingabepuffer.Hier ist insbesondere inbuf_read() interessant.

• In Zeile 14 wird untersucht, ob der Puffer bereits geleert ist, d.h. ob pos bereits buf.lenerreicht hat; falls ja, wird in den Zeilen 15 bis 24 der Puffer neu gefüllt.

• In Zeile 15 wird zunächst untersucht, ob der Puffer bereits alloziert worden ist; falls nicht,wird dies mit der Standardgröße von 512 Bytes versucht.

• In Zeile 20 erfolgt die read()-Operation, bei der grundsätzlich versucht wird, den gesam-ten Puffer zu füllen. Allerdings ist damit zu rechnen, dass die Zahl der tatsächlich gelesenenBytes nbytes niedriger ist als buf.a. Dies liegt daran, dass das Betriebssystem bereits vor-handene Daten sofort zur Verfügung stellt, selbst wenn es sich um eine geringere Quantitätals angefordert handelt. Dies stellt sicher, dass effizientes Einlesen mit größen Puffergrößenohne unnötiges Blockieren möglich ist.

• Die read()-Operation selbst ist in eine Schleife in den Zeilen 18 bis 21 eingebettet, diesicherstellt, dass es zu einem erneuten Versuch kommt, falls read() wegen einer Signal-unterbrechung nicht erfolgreich sein konnte.

• Sobald sichergestellt ist, dass mindestens ein Byte in dem Puffer verfügbar ist, wird nbytesin Zeile 26 auf die maximal mögliche Rückgabequantität gesetzt. Wurden weniger verlangt,so wird nbytes in Zeile 27 entsprechend zurückgesetzt.

• In Zeile 28 wird die zurückzuliefernde Quantität an Bytes aus dem Puffer in buf mit Hil-fe von memcpy() kopiert. Der erste Parameter (buf) zeigt dabei auf das Ziel, der zweiteParameter (buf.s + pos) auf die Quelle und der dritte Parameter gibt die Zahl der zu ko-pierenden Bytes an (nbytes). Nach der Kopieraktion wird pos entsprechend aktualisiert.

Page 210: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

204 KAPITEL 6. BERKELEY SOCKETS

Ausgabepufferung:

Die Ausgabe sollte ebenfalls gepuffert erfolgen, um die Zahl der Systemaufrufe zu minimieren.Ein Positionszeiger ist nicht erforderlich, wenn Puffer grundsätzlich vollständig an write()übergeben werden. Das einzige Problem liegt hier darin, dass die write()-Operation unter Um-ständen nicht den gesamten gewünschten Umfang akzeptiert und nur einen Teil der zu schrei-benden Bytes akzeptiert und entsprechend eine geringere Quantität als Wert zurückgibt.

Programm 6.12: Schnittstelle für einen Ausgabe-Puffer (buffered/outbuf.h)

1 # ifndef OUTBUF_H2 # define OUTBUF_H3

4 # include < stralloc .h>5 # include <unistd .h>6

7 typedef struct outbuf {8 int fd ;9 stralloc buf ;

10 } outbuf ;11

12 /∗ works like write (2) but to obuf ∗/13 ssize_t outbuf_write ( outbuf∗ obuf , void∗ buf , size_t size );14

15 /∗ works like fputc but to obuf ∗/16 int outbuf_putchar ( outbuf∗ obuf , char ch );17

18 /∗ write contents of obuf to the associated fd ∗/19 int outbuf_flush ( outbuf∗ obuf );20

21 /∗ release storage associated with obuf ∗/22 void outbuf_free ( outbuf∗ obuf );23

24 # endif

Programmtext 6.12 zeigt die Schnittstelle für einen Ausgabe-Puffer.

Die Funktion outbuf_write() schreibt in den gegebenen Puffer und entspricht ansonsten demSystemaufruf write(). Mit Hilfe von outbuf_putchar() können bequem einzelne Zeichenin den Puffer ausgegeben werden. Beide Schreiboperationen führen nur zur Verlängerung desPufferinhalts, ohne dass dieser mit Hilfe einer write()-Operation geleert wird. Letzteres ist nurdurch den Aufruf von outbuf_flush() möglich. Wenn der Puffer nicht mehr benötigt wird,kann er durch outbuf_free() freigegeben werden.

Page 211: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.6. GEPUFFERTE EIN- / AUSGABE FÜR NETZWERKVERBINDUNGEN 205

Programm 6.13: Implementierung des Ausgabe-Puffers (buffered/outbuf.c)

1 # include <errno .h>2 # include < stralloc .h>3 # include < string .h>4 # include "outbuf.h"5

6 /∗ works like write (2) but to obuf ∗/7 ssize_t outbuf_write ( outbuf∗ obuf , void∗ buf , size_t size ) {8 if ( size == 0) return 0;9 if (! stralloc_readyplus (&obuf−>buf, size )) return −1;

10 memcpy(obuf−>buf.s + obuf−>buf.len , buf , size );11 obuf−>buf.len += size ;12 return size ;13 }14

15 /∗ works like fputc but to obuf ∗/16 int outbuf_putchar ( outbuf∗ obuf , char ch ) {17 if ( outbuf_write ( obuf , &ch, sizeof ch ) <= 0) return −1;18 return ch ;19 }20

21 /∗ write contents of obuf to the associated fd ∗/22 int outbuf_flush ( outbuf∗ obuf ) {23 ssize_t left = obuf−>buf.len ; ssize_t written = 0;24 while ( left > 0) {25 ssize_t nbytes ;26 do {27 errno = 0;28 nbytes = write ( obuf−>fd, obuf−>buf.s + written , left );29 } while ( nbytes < 0 && errno == EINTR);30 if ( nbytes <= 0) return 0;31 left −= nbytes ; written += nbytes ;32 }33 obuf−>buf.len = 0;34 return 1;35 }36

37 /∗ release storage associated with obuf ∗/38 void outbuf_free ( outbuf∗ obuf ) {39 stralloc_free (&obuf−>buf);40 }

Erläuterungen zu Programm 6.13 (S. 205):

• In outbuf_write() wird in Zeile 9 darauf geachtet, dass der Puffer genügend Platz fürden aufzunehmenden Inhalt aufweist, wonach in Zeile 10 der Kopiervorgang mit Hilfe vonmemcpy() durchgeführt werden kann.

• Danach muss nur noch buf.len in Zeile 11 angepasst werden.

• In der Funktion outbuf_flush() gibt es zwei Schleifen. Die äußere Schleife in den Zei-len 24 bis 32 sorgt dafür, dass der gesamte Puffer-Inhalt geschrieben wird, da einzelne

Page 212: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

206 KAPITEL 6. BERKELEY SOCKETS

write()-Operationen die Freiheit haben, nur einen Teil umzusetzen. Mit Hilfe der Varia-blen left und written wird vermerkt, wieviel noch zu schreiben ist bzw. wieviel bereitsgeschrieben wurde. Die innere Schleife in den Zeilen 26 bis 29 wiederholt die write()-Operation im Falle von Unterbrechungen.

6.7 Ein kleiner TCP-Server (concurrent) mit Eingabe-Pufferung

Programm 6.14: Headerfile für Netzwerkverbindung (buffered/inet.h)

1 // Definitions for TCP and UDP client / server programs2

3 # define SERV_UDP_PORT 60004 # define SERV_TCP_PORT 55005 // # define SERV_HOST_ADDR "134.60.66.5"6 /∗ thales ∗/7 # define SERV_HOST_ADDR "127.0.0.1"8 /∗ local host ∗/

Programm 6.15: Hauptprogramm des TCP-Servers mit Eingabepufferung (buffered/main-srv.c)

1 # include " inet .h"2 # include < stdio .h>3 # include <unistd .h>4 # include < stdlib .h>5 # include < signal .h>6 # include < string .h>7 # include < strings .h>8 # include <errno .h>9 # include <sys/types .h>

10 # include <sys/ socket .h>11 # include < netinet /in .h>12 # include <arpa/ inet .h>13

14

15 extern void str_echo ( int );16

17

18 int main( int argc , char ∗∗argv ) {19

20 int sockfd , newsockfd , clilen , childpid ;21 struct sigaction action ;22 struct sockaddr_in cli_addr , serv_addr ;23

24 /∗ our childs shall not become zombies ∗/25 action . sa_handler = SIG_IGN;26 action . sa_flags |= SA_NOCLDWAIT;27 if ( sigaction (SIGCHLD, &action, 0) < 0) exit (1);28

29

30 if ( ( sockfd = socket (AF_INET, SOCK_STREAM,0)) < 0) {31 perror ( " server : can’t open stream socket");32 exit (1);

Page 213: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.7. EIN KLEINER TCP-SERVER (CONCURRENT) MIT EINGABE-PUFFERUNG 207

33 }34

35 bzero (( char ∗) &serv_addr , sizeof ( serv_addr ));36

37 serv_addr . sin_family = AF_INET;38 serv_addr . sin_addr . s_addr = htonl (INADDR_ANY);39 /∗ INADR_ANY: tells the system that we’ ll accept a connection40 ∗ on any Internet interface on the system , if it is multihomed41 ∗ Address to accept any incoming messages (−> in .h ).42 ∗ INADDR_ANY is defined as (( unsigned long int ) 0x00000000)43 ∗/44

45 serv_addr . sin_port = htons (SERV_TCP_PORT);46

47 if ( bind ( sockfd , ( struct sockaddr ∗)&serv_addr ,48 sizeof ( serv_addr )) < 0) {49 perror ( " server : can’t bind local address" );50 exit (2);51 }52

53 listen ( sockfd ,5);54

55 while(1) {56 /∗57 ∗ wait for a connection from a client process58 ∗ − concurrent server −59 ∗/60 clilen = sizeof ( cli_addr );61 newsockfd = accept ( sockfd , ( struct sockaddr ∗) & cli_addr ,62 & clilen );63

64 if ( newsockfd < 0 ) {65 if ( errno == EINTR)66 continue; /∗ try again ∗/67 perror ( "server : accept error" );68 exit (3);69 }70

71 if ( ( childpid = fork ()) < 0) {72 perror ( "server : fork error" );73 close ( sockfd );74 close ( newsockfd );75 exit (4);76 }77 else if ( childpid == 0) {78 close ( sockfd );79 str_echo ( newsockfd );80 exit (0);81 }82

83 close ( newsockfd ); /∗ parent ∗/84 }85 exit (0);86 }

Page 214: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

208 KAPITEL 6. BERKELEY SOCKETS

Page 215: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.7. EIN KLEINER TCP-SERVER (CONCURRENT) MIT EINGABE-PUFFERUNG 209

Programm 6.16: Funktionalität des TCP-Servers mit Eingabepufferung (buffered/str-echo.c)

1 # include <unistd .h>2 # include < stdio .h>3 # include < stdlib .h>4 # include "inbuf .h"5

6 # define MAXLINE 327

8 void str_echo ( int sockfd ) {9 int n;

10 /∗ for testing :11 int a , len , pos ;12 ∗/13 char line [MAXLINE];14 inbuf in ;15 inbuf_alloc (&in , 1024);16 in . fd = sockfd ;17 /∗ for testing :18 inbuf_pars ( in , & len , & a, & pos );19 fprintf ( stderr , "\nServer : len = %d, a = %d, pos = %d\n",20 len , a , pos );21 ∗/22

23 while(1) {24 n = inbuf_read (&in , line , MAXLINE);25 /∗ for testing :26 inbuf_pars ( in , & len , & a, & pos );27 fprintf ( stderr , "\nServer : len = %d, a = %d, pos = %d, n = %d\n",28 len , a , pos ,n );29 ∗/30 if (n == 0)31 return ; /∗ connection terminated ∗/32 else if ( n < 0 ) {33 perror ( "str_echo : read error" );34 exit (5);35 }36 /∗ write to server ’ s stderr : ∗/37 write (2, line ,n );38

39 if ( write ( sockfd , line , n) != n) {40 perror ( "str_echo : write error" );41 exit (6);42 }43 }44 }

Page 216: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

210 KAPITEL 6. BERKELEY SOCKETS

Programm 6.17: Hauptprogramm des TCP-Clients mit Eingabepufferung (buffered/main-cli.c)

1 # include " inet .h"2 # include < stdio .h>3 # include <unistd .h>4 # include < stdlib .h>5 # include < signal .h>6 # include < string .h>7 # include < strings .h>8 # include <errno .h>9 # include <sys/types .h>

10 # include <sys/ socket .h>11 # include < netinet /in .h>12 # include <arpa/ inet .h>13

14 extern void str_cli (FILE ∗, int );15

16 int main( int argc , char ∗∗argv ){17 int sockfd ; FILE ∗ fp ;18 struct sockaddr_in serv_addr ;19

20 if ( argc != 2 ) {21 fprintf ( stderr , "usage: %s file\n", argv [0]);22 exit (1);23 }24 if ( ( fp = fopen (argv [1], "r" ) ) == NULL)25 perror ( "fopen" ), exit (2);26

27 bzero ( (char ∗) &serv_addr , sizeof ( serv_addr ));28 serv_addr . sin_family = AF_INET;29 serv_addr . sin_addr . s_addr = inet_addr (SERV_HOST_ADDR);30 serv_addr . sin_port = htons (SERV_TCP_PORT);31

32 if ( ( sockfd = socket (AF_INET, SOCK_STREAM,0)) < 0 ) {33 perror ( " client : can’t open stream socket");34 exit (3);35 }36

37 if ( connect ( sockfd , ( struct sockaddr ∗) &serv_addr ,38 sizeof ( serv_addr ) ) < 0) {39 perror ( " client : can’t connect to server" );40 exit (4);41 }42

43 str_cli ( fp , sockfd );44

45 close ( sockfd );46 exit (0);47 }

Page 217: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.7. EIN KLEINER TCP-SERVER (CONCURRENT) MIT EINGABE-PUFFERUNG 211

Programm 6.18: Funktionalität des TCP-Clients mit Eingabepufferung (buffered/str-cli.c)

1 /∗ str_cli . c2 ∗ function used by connection −oriented clients3 ∗4 ∗ read the contents of the FILE ∗ fp , write each line to the5 ∗ stream socket ( to the server process ), then read a line back6 ∗ from the socket and write it to stdout7 ∗8 ∗ return to caller when an EOF is encountered on the input file9 ∗/

10

11 # include < stdio .h>12 # include <unistd .h>13 # include < stdlib .h>14 # include < string .h>15 # include <sys/ socket .h>16 # include "inbuf .h"17

18 # define MAXLINE 3219

20 void str_cli (FILE ∗ fp , int sockfd ) {21 int n;22 char sendline [MAXLINE], recvline[MAXLINE+1];23

24 inbuf in ;25 inbuf_alloc (&in , 512);26 in . fd = sockfd ;27 while ( fgets ( sendline , MAXLINE,fp) != NULL) {28 n = strlen ( sendline );29 if ( write ( sockfd , sendline ,n) != n) {30 perror ( " str_cli : write error on socket");31 exit (4);32 }33

34 /∗35 ∗ now read a line from the socket and36 ∗ write it to stdout37 ∗/38 n = inbuf_read (&in , recvline , MAXLINE);39 if (n < 0) {40 perror ( " str_cli : read error" );41 exit (5);42 }43 write (1, recvline ,n );44 }45 shutdown( sockfd ,1);46 return ;47 }

Page 218: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

212 KAPITEL 6. BERKELEY SOCKETS

6.8 Ein- / Ausgabe von Paketen für Netzwerkverbindungen

Zwischen Dienste-Anbietern und ihren Klienten auf dem Netzwerk besteht häufig ein ähnli-ches Verhältnis wie zwischen einer Shell und dem zugehörigen Benutzer: Der Klient gibt einKommando, das typischerweise mit dem Zeilentrenner CR LF1 beendet wird, und der Dienste-Anbieter sendet darauf eine Antwort zurück,

• die zum Ausdruck bringt, ob das Kommando erfolgreich verlief oder fehlschlug, und

• einen Antworttext über eine oder mehrere Zeilen.

Es gibt keine zwingende Notwendigkeit, bei einem Protokoll Zeilentrenner zu verwenden. Esgibt auch Alternativen wie die von Dan Bernstein vorgeschlagenen Net-Strings2, jedoch erlaubtdie Konvention mit dem Zeilentrenner CR LF die interaktive Benutzung eines Dienstes mit demtelnet-Kommando.

Bei jedem Protokoll lohnt es sich, eine gewisse Grundstruktur über die generelle Syntax vonKommandos und deren Antworten festzulegen. Hier ist ein Beispiel für eine (nicht sehr kon-struktive) Sitzung mit einem Dienst, über den E-Mails zugestellt werden können. Zum Einsatzkommt hier das SMTP-Protokoll3:

doolin$ telnet mail.rz.uni-ulm.de smtpTrying 134.60.246.1...Connected to mail.rz.uni-ulm.de.Escape character is ’^]’.220 mail.rz.uni-ulm.de ESMTP Sendmail 8.12.9/8.12.9help214-2.0.0 This is sendmail version 8.12.9214-2.0.0 Topics:214-2.0.0 HELO EHLO MAIL RCPT DATA214-2.0.0 RSET NOOP QUIT HELP VRFY214-2.0.0 EXPN VERB ETRN DSN AUTH214-2.0.0 STARTTLS214-2.0.0 For more info use "HELP <topic>".214-2.0.0 To report bugs in the implementation send email to214-2.0.0 [email protected] For local information send email to Postmaster at your site.214 2.0.0 End of HELP infohuhu500 5.5.1 Command unrecognized: "huhu"helo doolin.andreas-borchert.de250 mail.rz.uni-ulm.de Hello doolin.andreas-borchert.dequit221 2.0.0 mail.rz.uni-ulm.de closing connectionConnection to mail.rz.uni-ulm.de closed by foreign host.doolin$

1carriage return, gefolgt von line feed.2Siehe http://cr.yp.to/proto/netstrings.txt3SMTP steht für simple mail transfer protocol, siehe RFC 2821

Page 219: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.8. EIN- / AUSGABE VON PAKETEN FÜR NETZWERKVERBINDUNGEN 213

Beim SMTP-Protokoll erfolgt zunächst eine Begrüßung des Dienste-Anbieters. Die Begrüßungoder auch eine andere Antwort des Anbieters besteht aus einer dreistelligen Nummer, einemLeerzeichen oder einem Minus und beliebigem Text, der durch CR LF abgeschlossen wird. Dieerste Ziffer der dreistelligen Nummer legt hier fest, ob ein Erfolg oder ein Problem vorliegt. Diebeiden weiteren Ziffern werden zur feineren Unterscheidung der Rückmeldung verwendet. Eineführende 2 bedeutet Erfolg, eine 4 signalisiert ein temporäres Problem und eine 5 signalisierteinen permanenten Fehler.

In der Beispielsitzung ist das erste Kommando ein “help”, gefolgt von CR LF. Da die Antwortsich über mehrere Zeilen erstreckt, werden alle Zeilen, hinter der noch mindestens eine folgt,mit einem Minuszeichen hinter der dreistelligen Zahl gekennzeichnet. Danach wurde mit demunbekannten Kommando “huhu” eine Fehlermeldung provoziert, die durch den Code 500 si-gnalisiert wurde. Das SMTP-Protokoll erlaubt auch eine Fortsetzung des Dialogs nach Fehlern,so dass dann noch ein “helo”-Kommando akzeptiert wurde. Die Verbindung wurde mit dem“quit”-Befehl beendet.

Programm 6.19: Schnittstelle für MXP-Anfrage-Pakete (MXP/mxprequest.h)

1 # ifndef MXP_REQUEST_H2 # define MXP_REQUEST_H3

4 # include < stralloc .h>5 # include "inbuf .h"6 # include "outbuf.h"7

8 typedef struct mxp_request {9 stralloc keyword ;

10 stralloc parameter ;11 } mxp_request ;12

13 /∗ read one request from the given input buffer ∗/14 int read_mxp_request ( inbuf∗ ibuf , mxp_request∗ request );15

16 /∗ write one request to the given outbuf buffer ∗/17 int write_mxp_request ( outbuf∗ obuf , mxp_request∗ request );18

19 /∗ release resources associated with request ∗/20 void free_mxp_request ( mxp_request∗ request );21

22 # endif

Page 220: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

214 KAPITEL 6. BERKELEY SOCKETS

Programm 6.20: Schnittstelle für MXP-Antwort-Pakete (MXP/mxpresponse.h)

1 # ifndef MXP_RESPONSE_H2 # define MXP_RESPONSE_H3

4 # include "inbuf .h"5 # include "outbuf.h"6

7 typedef enum mxp_status {8 MXP_SUCCESS = ’S’,9 MXP_FAILURE = ’F’,

10 MXP_CONTINUATION = ’C’,11 } mxp_status ;12

13 typedef struct mxp_response {14 mxp_status status ;15 stralloc message ;16 } mxp_response ;17

18 /∗ write one ( possibly partial ) response to the given output buffer ∗/19 int write_mxp_response ( outbuf∗ obuf , mxp_response∗ response );20

21 /∗ read one ( possibly partial ) response from the given input buffer ∗/22 int read_mxp_response ( inbuf∗ ibuf , mxp_response∗ response );23

24 void free_mxp_response (mxp_response∗ response );25

26 # endif

Ungeachtet seines Namens ist das SMTP-Protokoll nicht mehr sehr einfach. Um ein überschau-bareres Beispiel zu erhalten, wird im weiteren Text von folgenden Konventionen ausgegangen:

• Kommandos bestehen aus einem Kommandonamen (nur aus Kleinbuchstaben), einem Leer-zeichen und genau einem Parameter. Abgeschlossen wird ein Kommando durch die Se-quenz CR LF.

• Antworten beginnen mit “S”, “F” oder “C”, gefolgt von einem beliebigen Text, der durchCR LF abgeschlossen wird. “S” deutet einen Erfolg an (success), “F” einen Fehlschlag (failu-re) und “C” gibt an, dass noch weitere Zeilen folgen.

Sobald dieser syntaktische Rahmen festliegt, ist es möglich, entsprechende Datenstrukturen fürAnfragen und deren Antworten zu deklarieren und zugehörige Ein- und Ausgabefunktionenanzubieten wie Programmtext 6.19 (S. 213) und 6.20 (S. 214) und zeigen.4 Es ist dabei sinnvoll,jeweils die Ein- und Ausgabefunktionen zusammen zu belassen, da beide Funktionen jeweilsgenau zusammenpassen müssen.

4 MXP steht hierbei für das mutual exclusion protocol, das im übernächsten Abschnitt vorgestellt wird.

Page 221: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.8. EIN- / AUSGABE VON PAKETEN FÜR NETZWERKVERBINDUNGEN 215

Programm 6.21: Das Einlesen eines MXP-Anfrage-Pakets (MXP/mxprequest.c)

1 # include "inbuf .h"2 # include "outbuf.h"3 # include "mxprequest.h"4

5 static int read_keyword ( inbuf ∗ ibuf , stralloc ∗ keyword) {6 int ch ;7 if (! stralloc_copys (keyword , " " )) return 0;8 while (( ch = inbuf_getchar ( ibuf )) >= 0 &&9 ch >= ’a’ && ch <= ’z’ ) {

10 if (! stralloc_readyplus (keyword , 1)) return 0;11 keyword−>s[keyword−>len++] = ch;12 }13 if (ch >= 0) inbuf_back ( ibuf );14 return keyword−>len > 0;15 }16

17 static int read_parameter ( inbuf∗ ibuf , stralloc ∗ parameter ) {18 int ch ;19 if (! stralloc_copys ( parameter , " " )) return 0;20 while (( ch = inbuf_getchar ( ibuf )) >= 0 &&21 ch != ’\r’ && ch != ’\n’ && ch != ’/’ && ch != ’\0’) {22 if (! stralloc_readyplus ( parameter , 1)) return 0;23 parameter−>s[parameter−>len++] = ch ;24 }25 if (ch >= 0) inbuf_back ( ibuf );26 return parameter−>len > 0;27 }28

29 static int expect_delimiter ( inbuf∗ ibuf , char delimiter ) {30 int ch = inbuf_getchar ( ibuf );31 if (ch < 0) return 0;32 return ch == delimiter ;33 }34

35 /∗ read one request from the given input buffer ∗/36 int read_mxp_request ( inbuf∗ ibuf , mxp_request∗ request ) {37 return38 read_keyword ( ibuf , &request−>keyword) &&39 expect_delimiter ( ibuf , ’ ’ ) &&40 read_parameter ( ibuf , &request−>parameter) &&41 expect_delimiter ( ibuf , ’\r’ ) &&42 expect_delimiter ( ibuf , ’\n’ );43 }44

45 /∗ write one request to the given outbuf buffer ∗/46 int write_mxp_request ( outbuf∗ obuf , mxp_request∗ request ) {47 return48 outbuf_write ( obuf , request −>keyword.s,49 request −>keyword.len) == request −>keyword.len &&50 outbuf_putchar ( obuf , ’ ’ ) == ’ ’ &&51 outbuf_write ( obuf , request −>parameter.s ,52 request −>parameter. len ) == request −>parameter. len &&

Page 222: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

216 KAPITEL 6. BERKELEY SOCKETS

53 outbuf_putchar ( obuf , ’\r’ ) == ’\r’ &&54 outbuf_putchar ( obuf , ’\n’) == ’\n’;55 }56

57 /∗ release resources associated with request ∗/58 void free_mxp_request ( mxp_request∗ request ) {59 stralloc_free (& request−>keyword);60 stralloc_free (& request−>parameter);61 }

Programm 6.21 (S. 215) demonstriert das Einlesen eines Anfrage-Pakets:

• Da eine Anfrage aus einem Kommandonamen, einem Leerzeichen, dem Parameter undden Zeilenterminator besteht, wurde das Einlesen entsprechend aufgeteilt.

• Die Funktionen read_keyword() und read_parameter() lesen jeweils ihren Teil in einstralloc-Objekt ein und achten mit Hilfe von inbuf_back() darauf, dass das jeweiligeterminierende Zeichen anschließend zum Einlesen wieder zur Verfügung steht.

• Die Funktion expect_delimiter() liest ein Zeichen ein und vergleicht es mit dem er-warteten Trennzeichen. Auf diese Weise lassen sich Leerzeichen und der Zeilentrennerüberlesen. Übertriebene Toleranz bezüglich mehreren Leerzeichen oder verschiedenen Va-rianten bei Zeilentrennern ist hier fehl am Platze, da dies nur den Programmtext verkompli-ziert ohne einen wahren Vorteil zu bringen, da primär nur Programme über dieses Protokollkommunizieren.

• Zu beachten ist, dass outbuf_flush() nicht von write_mxp_request() aufgerufenwird. Die Kontrolle darüber, wann wirklich der Ausgabepuffer geleert wird, verbleibt sobeim Kern des Programmtexts, der mit dem Protokoll umgeht.

Programm 6.22: Das Einlesen eines MXP-Antwort-Pakets (MXP/mxpresponse.c)

1 # include "inbuf .h"2 # include "mxpresponse.h"3 # include "outbuf.h"4

5 static int read_message ( inbuf ∗ ibuf , stralloc ∗ message ) {6 int ch ;7 if (! stralloc_copys (message , " " )) return 0;8 while (( ch = inbuf_getchar ( ibuf )) >= 0 &&9 ch != ’\r’ && ch != ’\n’) {

10 if (! stralloc_readyplus (message , 1)) return 0;11 message−>s[message−>len++] = ch;12 }13 if (ch >= 0) inbuf_back ( ibuf );14 return 1;15 }16

17 static int expect_delimiter ( inbuf∗ ibuf , char delimiter ) {18 int ch = inbuf_getchar ( ibuf );19 if (ch < 0) return 0;20 return ch == delimiter ;21 }

Page 223: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.9. PARALLELE SITZUNGEN 217

22

23 /∗ read one ( possibly partial ) response from the given input buffer ∗/24 int read_mxp_response ( inbuf∗ ibuf , mxp_response∗ response ) {25 int ch = inbuf_getchar ( ibuf );26 switch (ch ) {27 case MXP_SUCCESS:28 case MXP_FAILURE:29 case MXP_CONTINUATION:30 response −>status = ch ;31 break;32 default :33 return 0;34 }35 if (! read_message ( ibuf , &response−>message)) return 0;36 if (! expect_delimiter ( ibuf , ’\r’ )) return 0;37 if (! expect_delimiter ( ibuf , ’\n’ )) return 0;38 return 1;39 }40

41 /∗ write one ( possibly partial ) response to the given output buffer ∗/42 int write_mxp_response ( outbuf∗ obuf , mxp_response∗ response ) {43 if ( outbuf_putchar ( obuf , response −>status) < 0) return 0;44 if ( response −>message.len > 0) {45 ssize_t nbytes = outbuf_write ( obuf , response −>message.s ,46 response −>message.len );47 if ( nbytes != response −>message.len ) return 0;48 }49 return50 outbuf_putchar ( obuf , ’\r’ ) >= 0 &&51 outbuf_putchar ( obuf , ’\n’) >= 0;52 }53

54 void free_mxp_response (mxp_response∗ response ) {55 stralloc_free (& response−>message);56 }

Programm 6.22 (S. 216) zeigt analog das Einlesen eines Antwort-Pakets.

6.9 Parallele Sitzungen

Der zuvor mit dem Programmtext 6.1 (auf Seite 153) vorgestellte Zeitdienst wartete mit accept()auf den nächsten Aufruf und kümmerte sich dann ausschließlich um diesen. Da eine Sitzung desZeitdienstes ohne Interaktionen auskommt und nur aus der Zeitansage besteht, führte dies nichtzu Problemen bei mehreren parallelen Anfragen. Entsprechend der bei listen() angegebe-nen Zahl (mit SOMAXCONN wird normalerweise das Maximum gewählt) ist es möglich, weitereAnrufe in eine Warteschlange einzureihen, bis die derzeitig laufende Sitzung beendet ist undaccept() erneut aufgerufen wird. Man spricht hier von einem iterative server. Dies ist jedochbei längeren Sitzungen nicht mehr akzeptabel.

Es gibt drei Ansätze, um parallele Sitzungen zu ermöglichen:

Page 224: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

218 KAPITEL 6. BERKELEY SOCKETS

• Für jede neue Sitzung wird mit Hilfe von fork() ein neuer Prozess erzeugt, der sich umdie Verbindung zu genau einem Klienten kümmert (concurrent server). – siehe Programm6.15, S. 206

• Für jede neue Sitzung wird ein neuer Thread gestartet.

• Sämtliche Ein- und Ausgabe-Operationen werden asynchron abgewickelt mit Hilfe derO_NONBLOCK-Option und der Verwendung von poll() oder select(). Hierbei blockie-ren Ein- und Ausgabe-Operationen nicht mehr, sondern geben eine Fehlerindikation zu-rück, falls noch keine Eingabe vorliegt bzw. eine Ausgabe momentan noch nicht akzeptiertwerden kann. Mit Hilfe von poll() oder select() ist es dann möglich, bei einer Vielzahlvon Dateideskriptoren darauf zu waren, dass eine Lese- oder Schreib-Operation möglichist.

Die erste Methode ist am einfachsten umzusetzen. Normalerweise wird nur dann eine der beidenanderen Varianten gewählt, wenn die parallel laufenden Sitzungen auf gemeinsame Datenstruk-turen zugreifen möchten.

Die erste Variante ist so gängig, dass es sich lohnt, sie in verallgemeinerter Form zur Verfügungzu stellen. Von Dan J. Bernstein gibt es hierfür ein Werkzeug namens tcpserver5, das die ge-samte Sequenz von socket() bis accept() so verpackt, dass der eigentliche Dienst die ge-samte Kommunikation über die Standard-Ein- und Ausgabe abwickeln kann. Auf diese Weiselassen sich sogar relativ einfach kleine Dienste auf Basis bestehender Kommandos einrichten.

Mit einem ersten Beispiel – “echo-Server od -bc” – soll dies demonstriert werden. Wir startenin einem xterm den Server:

spatz$ tcpserver 0 11011 /bin/sh -c \> ’while read x; do echo $x | od -bc; done’^Cspatz$

Erläuterungen:

• Allgemeine Aufrufsyntax: tcpserver opts host port prog

• Zu opts (Optionen) siehe: http://cr.yp.to/ucspi-tcp/tcpserver.htm

• Das erste Argumenthost von tcpserver (hier: 0): IP-Adresse des Servers (hier: INADDR_ANY)

• Das zweite Argument: TCP-Port-Nummer (hier: 11011)

• Das dritte Argument prog: Für jede eingehende Verbindung wird ein neuer Prozess er-zeugt, bei dem Standard-Ein- (0) und Ausgabe (1) mit der Netzwerkverbindung verknüpftwerden. Anschließend erfolgt ein |execvp()| zum angegebenen Kommando – hier ist dasProgramm die Shell /bin/sh/:

– Mit der Option -c erhält die Shell das auszuführende Kommando auf der Argument-zeile

– Die Quotierung in einfache Apostrophen hat die Bedeutung, dass das eingechlossenezum einen nicht sofort von der aktuellen Shell interpretiert wird und zum anderen dasdazwischenstehende so wie es ist als ein Argument an /bin/sh übergeben wird.

5Siehe http://cr.yp.to/ucspi-tcp/tcpserver.html

Page 225: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.9. PARALLELE SITZUNGEN 219

– Die Shell als Kommando-Interpreter ist auch eine “Programmiersprache”; die while-Schleife lautet:

while <<statement>>do

<<statements>>done

oder kurz

while <<statement>> ; do <statements>> ; done

– Ist der Exit-Status des Kommandos nach while 0, entspricht dies dem Bool’schenWert true, jeder andere Exit-Status ist false

– Das built-in-Kommado read liest eine Zeile von der Standardeingabe in die angegebe-ne Variable (hier: x)

– das Kommando echo gibt sein Argument (hier: das, was nach der Substitution derVariablen x durch ihren Wert ergibt) an die Standardausgabe (innerhalb des Shell-Kommandos!)

– Das Kommando od ist ein Filter und wandelt seine Ausgabe in Oktaldarstellung

Nun starten wir in einem anderen xterm einen Client:

spatz$ telnet spatz 11011Trying 192.168.0.5...Connected to spatz.Escape character is ’^]’.anton0000000 141 156 164 157 156 015 012

a n t o n \r \n0000007120000000 061 062 015 012

1 2 \r \n0000004Connection closed by foreign host.spatz$

Erläuterungen:

• Erste Eingabe hier ist anton, die zweite 12

• Man beachte die letzten beiden Zeichen in der Ausgabe des Servers: \r\n

Das Kommando od kann nun durch andere (passende) Kommandos ersetzt werden. Hier istein Beispiel auf Basis des factor-Kommandos (siehe man factor), das ganze Zahlen in ihrePrimteiler zerlegt. Bevor wir dies einfügen, sei es zunächst einmal demonstriert:

Page 226: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

220 KAPITEL 6. BERKELEY SOCKETS

spatz$ echo 12 | factor12: 2 2 3spatz$ echo 12x | tr x ’\r’ | factorfactor: ‘12’ is not a valid positive integerspatz$ echo 12x | factorfactor: ‘12x’ is not a valid positive integerspatz$

Obiges war auf einem Linux-Rechner – dasselbe auf der thales:

thales$ echo 12 | factor223

thales$ echo 12x | tr x ’\r’ | factor223

thales$ echo 12x | factor223

thales$

Starten wir uns factor-Server auf obigem Linux-Rechner, so sollten wir auf das Zeichen \rachten:

spatz$ tcpserver 0 23456 /bin/sh -c \> ’while read x; do echo $x | tr \\r \ | factor ; done’^cspatz$

Anm.:

• Das Kommando tr (translate) transformiert Zeichen(-bereiche)

• Es soll das Zeichen \r (ein Zeichen) in ein Blank umsetzen; dazu wird die Backslah-Quotierungverwendet!

• Auf der thales können wir uns den Zwischenschritt mit tr sparen!

Page 227: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

6.9. PARALLELE SITZUNGEN 221

Prinzipiell wäre es natürlich auch möglich, factor direkt von tcpserver aufrufen zu lassen.Wegen der gepufferten Ausgabe von factorwürden wir die Faktorisierung jedoch zu spät oderüberhaupt nicht zu sehen bekommen. Wenn hingegen für jede Eingabezeile ein Kommandoauf-ruf erfolgt, ist damit auch ein implizites Leeren des Ausgabe-Puffers verbunden, sobald das Kom-mando beendet ist. Das Problem tritt bei einer normalen interaktiven Verwendung nicht auf, dadie stdio implizit bei der Verwendung eines Terminals auf eine zeilenweise Pufferung umschal-tet. Dies geschieht jedoch nicht bei Netzwerkverbindungen!

Die Kontrollstruktur, die tcpserver auf der Ebene der Kommandozeile liefert, lässt sich eben-so in genereller Form als Bibliotheksfunktion repräsentieren. Programmtext 6.23 (S. 221) zeigteine entsprechende Schnittstelle und 6.24 (S. 221) die zugehörige Implementierung. Genauso wietcpserver benötigt die Funktion run_service() die lokal zu verwendende Adresse. DerEinfachheit halber wird hier allerdings nur die Portnummer als Parameter übergeben und im-plizit INADDR_ANY verwendet. Für jeden eingehenden Anruf soll run_service() einen neuenProzess mit fork() erzeugen und in diesem die als Parameter übergebene Funktion handleraufrufen. Diese Funktion erhält als Parameter den Dateideskriptor der offenen Netzwerkverbin-dung und – analog zu tcpserver – die verbliebenen Kommandozeilenparameter. Genau wietcpserver endet run_service() nur im Fehlerfalle und läuft ansonsten endlos.

Programm 6.23: Schnittstelle für die Kontrollstruktur von Netzwerkdiensten (service.h)

1 # ifndef SERVICE_H2 # define SERVICE_H3

4 # include < netinet /in .h>5

6 typedef void (∗ session_handler )( int fd , int argc , char∗∗ argv );7

8 /∗9 ∗ listen on the given port and invoke the handler for each

10 ∗ incoming connection11 ∗/12 void run_service ( in_port_t port , session_handler handler ,13 int argc , char∗∗ argv );14

15 # endif

Page 228: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

222 KAPITEL 6. BERKELEY SOCKETS

Programm 6.24: Implementierung für die Kontrollstruktur von Netzwerkdiensten (service.c)

1 # include < netinet /in .h>2 # include < signal .h>3 # include < stdio .h>4 # include < stdlib .h>5 # include < string .h>6 # include <sys/ socket .h>7 # include <sys/time .h>8 # include <time .h>9 # include <unistd .h>

10 # include " service .h"11

12 /∗13 ∗ listen on the given port and invoke the handler for each14 ∗ incoming connection15 ∗/16 void run_service ( in_port_t port , session_handler handler ,17 int argc , char∗∗ argv ) {18 struct sockaddr_in address = {0};19 address . sin_family = AF_INET;20 address . sin_addr . s_addr = htonl (INADDR_ANY);21 address . sin_port = htons ( port );22

23 int sfd = socket (PF_INET, SOCK_STREAM, 0);24 int optval = 1;25 if ( sfd < 0 ||26 setsockopt ( sfd , SOL_SOCKET, SO_REUSEADDR,27 &optval , sizeof optval ) < 0 ||28 bind ( sfd , ( struct sockaddr ∗) &address ,29 sizeof address ) < 0 ||30 listen ( sfd , SOMAXCONN) < 0) {31 return ;32 }33

34 /∗ our childs shall not become zombies ∗/35 struct sigaction action = {0};36 action . sa_handler = SIG_IGN;37 action . sa_flags |= SA_NOCLDWAIT;38 if ( sigaction (SIGCHLD, &action, 0) < 0) return ;39

40 int fd ;41 while (( fd = accept ( sfd , 0, 0)) >= 0) {42 pid_t child = fork ();43 if ( child == 0) {44 handler ( fd , argc , argv );45 exit (0);46 }47 close ( fd );48 }49 }

Page 229: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Kapitel 7

Threads

7.1 Einführung

thread: Faden, Faser, Gedankengang, Strang

Probleme mit fork():

• fork() ist teuer: es wird Speicher vom Erzeuger zum Kind kopiert, Desktip-toren werden dupliziert, . . .

Aktuelle Implementierungen nutzen zwar copy-on-write, d.h. Speicher wirderst dann kopiert, wenn eigene Kopie benötigt wird – fork() ist dennochteuer!

• IPC wird benötigt, um zwischen Erzeuger und Kinde Daten auszutauschen(nach dem fork()). Vor dem “fork” Daten austauschen geht einfach, dadas Kind ja mit einer (fast vollständigen) Kopie des Erzeugers startet. Rück-gabe von Informationen vom Kind zum Erzeuger ist nicht so einfach!

Lösung: Threads (leichtgewichtige Prozesse), deren Erzeugung 10 bis 100 malschneller geht ([Stevens04])

• Alle Threads innerhalb eines Prozesses teilen sich denselben globalen Spei-cher (sprich die globalen Variablen) – damit ist der Informationsaustauscheinfach.

Aber: Synchronisation ist notwendig!

• Alle Threads innerhalb eines Prozesses teilen sich weiterhin

� Prozess-Anweisungen (Text)

� Fast alle Daten

223

Page 230: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

224 KAPITEL 7. THREADS

� Offene Dateiverbindungen (UFDT)

� Signalbehandler und Signaleinstellungen

� Aktuellen Arbeitskatalog

� User und Group IDs

• Jeder Thread hat für sich allein

� eine Thread-ID

� einen Satz von Registern (Befehlszähler, Stack-Pointer)

� die Variable errno

� Signal-Maske (siehe sigaction())

� eine Priorität

Nach Stevens[04]:

One analogy is to think of signal handlers as a tyep of thread as we discussed in. . . . That is, in the traditional Unix model, we have the main flow of execution(one thread) and a signal handler (another thread). If the main flow of executionis in the midlle of updating a linked list when a signal occurs, and the signalhandler also tries to update the linked list, havoc1 normally results. The mainflow and signal handler share the same global variables, but each has its ownstack.

Im folgenden werden Threads nach dem POSIX Standard (1995 als Teil des PO-SIX.1c Standards standardisiert) behandelt. Alle Funktionen beginnen mit pthread_

POSIC Thread Library: libptread

gcc -Wall example.c -lpthread

1Chaos, Verwüstung

Page 231: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

7.2. GRUNDLEGENDE FUNKTIONEN: ERZEUGEN UND BEENDEN 225

one process one thread one process multiple threads

multiple processes

one thread per process

multiple processes

multiple threads per process

Abbildung 7.1: Multithreading vs. Single threading

7.2 Grundlegende Funktionen: Erzeugen und Beenden

7.2.1 pthread_create()

Wir ein Programm via exec gestartet, wird ein einzelner Thread gestartet: initial thread oder mainthread

# include <pthread.h>

int pthread_create(pthread_t * tid, pthread_attr_t * attr,void * (*func)(void *), void * arg);

/* returns 0 on success, positive Exxx value on error */

• Jeder Thread innerhalb eines Prozess hat eine eindeutige ID (* tid) (Typ pthread_t,meist unsigned int)

• Jeder Thread hat zahlreiche Attribute: Priorität, anfängliche Stack-Größe, Dämen-Threadja/nein, . . .

• Diese Attribute können über den Parameterattr spezifiziert werden – i.a. reicht die default-Einstellung, daher wird als formaler Parameter hier der Null-Zeiger angegeben

• Der dritte Parameter spezifiziert die auszuführende Start-Routine: der Thread startet mitder Ausführung dieser Routine und terminiert

– entweder explizit durch Aufruf der Funktion pthread_exit

Page 232: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

226 KAPITEL 7. THREADS

– oder implizit durch normalle Rückkehr aus der Start-Routine

• Die Adresse der Start-Routine wird über den Parameter func; diese wird mit einem einzi-gen Zeiger-Argument arg aufgerufen.

• Werden in der Start-Routine mehrere Argumente benötigt, so müssen diese in eine Strukturzusammengepackt werden und die Adresse dieser Struktur kann übergeben werden.

• Die Funktion func erhält ein Argument – einen generischen Zeiger (void *) – und lieferteinen Wert – ebenfalls ein generischer Zeiger (void *) – zurück.

• Rückgabewert: im Erfolgsfall 0

Im Fehlerfall wird nicht -1 geliefert, sondern der positive Fehlercode (siehe <sys/errno.h>)– die errno Variable wird nicht gesetzt!

Beispiel: Es kann kein Thread mehr erzeugt werden, weil bereits zuviele erzeugt wurden –der Rückgabewert ist dann EAGAIN

Page 233: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

7.2. GRUNDLEGENDE FUNKTIONEN: ERZEUGEN UND BEENDEN 227

7.2.2 pthread_join()

# include <pthread.h>

int pthread_join(pthread_t tid, void ** status);

/* returns 0 on success, positive Exxx value on error */

• Mit pthread_join() wird auf einen bestimmten Thread (tid) gewartet

• pthread_create() ist das Analogon zu fork(), pthread_join() das zu waitpid()zu sehen

• Ein Warten auf einen beliebigen Thread (analog zu waitpid(-1, ...)) ist nicht möglich!

• Ist der Zeiger status nicht Null, so wird der Rückgabewert des Threads an einer Stelleabgelegt, auf die status zeigt

7.2.3 pthread_self()

# include <pthread.h>

ptread_t pthread_self(void);

/* returns thread ID of calling thread */

Das Analogon bei Prozessen ist getpit()!

7.2.4 pthread_detach()

# include <pthread.h>

int pthread_detach(pthread_t tid);

/* returns 0 on success, positive Exxx value on error */

• Ein Thread ist entweder joinable (Default) oder detached.

• Terminiert ein joinable Thread, so werden seine ID und sein Exitstatus solange aufbewahrt,bis ein anderer Thread pthread_join aufruft!

• Ein detached Thread ist wie Dämen-Prozess: bei Termination werden alle seine Ressourcenfreigegeben, man kann nicht auf ihn warten

• Typischer Aufruf: pthread_detach(pthread_self());

Page 234: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

228 KAPITEL 7. THREADS

7.2.5 pthread_exit()

# include <pthread.h>

int pthread_exit(void * status);

/* Does not return to caller */

• Ist der Thread nicht detached, so werden seine ID und sein Exitstatus für einen späterenpthread_join Aufruf aufbewahrt.

• der Zeiger status darf nicht auf ein bzgl. des Thread lokales Objekt zeigen (verschwindet,wenn der Thread terminiert).

• Andere Wege wie ein Thread termniniert:

– Die Startroutine des Thread (drittes Argument von pthread_create()) kehrt zu-rück und liefert den Exitstatus

– Wenn die main Funktion des Prozesses kehrt zurück oder irgendein erzeugter Threadruft exit() auf, so terminiert der Prozess inklusive all seiner Threads.

7.3 Beispiel: Matrix-Multiplikation

Das folgende Beispiel zeigt die Multiplikation einer quadratischen Matrix mit sich selbst – dieParallelisierung bietet sich hier regelrecht, da jedes Ergebniselement unabhängig von den ande-ren berechnet werden kann.

Programm 7.1: Matrix-Multiplikation mit Threads (thread-mult.c)

1 /∗2 ∗ compute square of a matrix with one thread for each element in result matrix3 ∗/4

5 # include < stdio .h>6 # include < stdlib .h>7 # include <pthread .h>8

9 # define DIM 310 int matrix [DIM][DIM];11

12 typedef struct {13 int myid; /∗ id of thread (0 .. nthreads−1) ∗/14 int row, col ; /∗ position ( r , c ) ∗/15 int ∗ localsum ;16 } thread_arg_t ;17

18 void ∗ colrow_sum(void ∗ id );19

20 void init_matrix () {

Page 235: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

7.3. BEISPIEL: MATRIX-MULTIPLIKATION 229

21 int i , j ;22 for ( i = 0; i < DIM; i++) {23 for ( j = 0; j < DIM; j++){24 matrix [ i ][ j ] = i + j ;25 printf ( "%4d ", matrix [ i ][ j ]);26 }27 printf ( "\n" );28 }29 printf ( "\n" );30 }31

32 int main() {33 int i , j ,n;34 int ∗ localsum ; /∗ partial results ∗/35 int nthreads = DIM ∗ DIM; /∗ number of threads ∗/36 pthread_t ∗ thread_id ; /∗ ids of all threads ∗/37 thread_arg_t ∗ thread_arg ; /∗ arguments for the threads ∗/38

39 /∗ allocate and initialize data ∗/40 thread_id = ( pthread_t ∗) malloc ( nthreads ∗ sizeof ( pthread_t ));41 thread_arg = ( thread_arg_t ∗) malloc ( nthreads ∗ sizeof ( thread_arg_t ));42 localsum = ( int ∗) malloc ( nthreads ∗ sizeof ( int ));43

44 init_matrix ();45

46 /∗ compute arguments for all threads ∗/47 for ( i = 0; i < DIM; i++) {48 for ( j = 0; j < DIM; j++){49 n = i ∗ DIM + j;50 thread_arg [n ]. myid = n ;51 thread_arg [n ]. localsum = localsum ;52 thread_arg [n ]. row = i ;53 thread_arg [n ]. col = j ;54 }55 }56

57 /∗ create threads , which all call colrow_sum() ∗/58 for ( i =1; i<nthreads ; i++) {59 pthread_create (& thread_id [ i ], NULL, colrow_sum, &thread_arg [ i ]);60 }61

62 /∗ work myself , as thread 0 ∗/63 colrow_sum(& thread_arg [0]);64

65 /∗ wait for other threads ∗/66 for ( i =1; i<nthreads ; i++) {67 pthread_join ( thread_id [ i ], NULL);68 }69 /∗ write result ∗/70 printf ( "\nResult:\n" );71 for ( i = 0; i < DIM; i++) {72 for ( j = 0; j < DIM; j++)73 printf ( "%4d ", localsum [ i ∗ DIM + j ]);74 printf ( "\n" );

Page 236: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

230 KAPITEL 7. THREADS

75 }76

77 exit (0);78 }79

80 /∗ routine called by the threads81 ∗ gets arguments myid, localsum −− returns result in localsum [myid]82 ∗/83 void ∗colrow_sum(void ∗args )84 {85 thread_arg_t ∗ myarg_p = ( thread_arg_t ∗) args ;86 int myid;87 int ∗ localsum ;88 int i ,row, col ;89

90 /∗ unpack arguments ∗/91 myid = myarg_p−>myid;92 localsum = myarg_p−>localsum;93 row = myarg_p−>row;94 col = myarg_p−>col;95

96 /∗ threads ’ s share of work ∗/97 localsum [myid] = 0;98 for ( i = 0; i < DIM; i++)99 localsum [myid] += matrix [row][ i ] ∗ matrix [ i ][ col ];

100 printf ( "Thread %d has result %d\n", myid, localsum[myid]);101 return(NULL);102 }

Übersetzung und Ausführung:

spatz$ gcc -Wall thread-mult.c -lpthreadspatz$ a.out

0 1 21 2 32 3 4

Thread 1 has result 8Thread 2 has result 11Thread 3 has result 8Thread 4 has result 14Thread 5 has result 20Thread 6 has result 11Thread 7 has result 20Thread 8 has result 29Thread 0 has result 5

Result:5 8 118 14 2011 20 29

spatz$

Page 237: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

7.4. TCP ECHO SERVER MIT THREADS 231

7.4 TCP Echo Server mit Threads

Mit den Programmen 6.15, S.206 und 6.16, S.209 hatten wir einen kleinen TCP-basierten Echo-Server mit Eingabepufferung vorgestellt, der eingehende Verbindung in einem neuen Prozessabwickelt. Programm 7.2, S. 231, zeigt dasselbe mit Threads.

Programm 7.2: Echo Server mit Threads (thread-echo/main-srv.c)

1 /∗ instead of fork () we use threads ∗/2

3 # include " inet .h"4 # include < stdio .h>5 # include <unistd .h>6 # include < stdlib .h>7 # include < string .h>8 # include < strings .h>9 # include <errno .h>

10 # include <sys/types .h>11 # include <sys/ socket .h>12 # include < netinet /in .h>13 # include <arpa/ inet .h>14 # include <pthread .h>15

16

17 extern void str_echo ( int );18 static void ∗ doit (void ∗);19

20 int main( int argc , char ∗∗argv ) {21

22 int sockfd , newsockfd , clilen ;23 pthread_t tid ;24 struct sockaddr_in cli_addr , serv_addr ;25

26 if ( ( sockfd = socket (AF_INET, SOCK_STREAM,0)) < 0) {27 perror ( " server : can’t open stream socket");28 exit (1);29 }30

31 bzero (( char ∗) &serv_addr , sizeof ( serv_addr ));32

33 serv_addr . sin_family = AF_INET;34 serv_addr . sin_addr . s_addr = htonl (INADDR_ANY);35 serv_addr . sin_port = htons (SERV_TCP_PORT);36

37 if ( bind ( sockfd , ( struct sockaddr ∗)&serv_addr ,38 sizeof ( serv_addr )) < 0) {39 perror ( " server : can’t bind local address" );40 exit (2);41 }42

43 listen ( sockfd ,5);44

45 while(1) {46 clilen = sizeof ( cli_addr );

Page 238: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

232 KAPITEL 7. THREADS

47 newsockfd = accept ( sockfd , ( struct sockaddr ∗) & cli_addr ,48 & clilen );49

50 if ( newsockfd < 0 ) {51 if ( errno == EINTR)52 continue; /∗ try again ∗/53 perror ( "server : accept error" );54 exit (3);55 }56

57 pthread_create (&tid , NULL, &doit, (void ∗) newsockfd );58 /∗ this cast works if sizeof ( int ) is less or equal to59 ∗ size of pointers60 ∗/61

62 }63 exit (0);64 }65

66 static void ∗ doit (void ∗ arg ) {67 pthread_detach ( pthread_self ());68 str_echo (( int ) arg );69 close ( ( int ) arg );70 return NULL;71 }

Anmerkungen:

• Beim Aufruf von pthread_creat()wird der von accept gelieferte neue Socket-Deskriptorvom Typ int einfach auf einen Zeiger vom Typ void * ge-cast-et und in der Funktiondoit() wird dieser wieder auf eine Integer zurückge-cast-et – funktioniert nicht immerund ist eher eine unschöne Lösung!

• Eine auf den ersten Blick bessere Alternative könnte der Weg wie im Programm 7.3, S. 233dargestellt sein.

Page 239: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

7.4. TCP ECHO SERVER MIT THREADS 233

Programm 7.3: Echo Server mit Threads – Alternative zu Prog. 7.2, S. 231? (thread-echo/main-srv-a)

1 /∗ ... ∗/2

3 int main( int argc , char ∗∗argv ) {4

5 /∗ ... ∗/6

7 while(1) {8 clilen = sizeof ( cli_addr );9 newsockfd = accept ( sockfd , ( struct sockaddr ∗) & cli_addr ,

10 & clilen );11 /∗ ... ∗/12 // |13 // V14 pthread_create (&tid , NULL, &doit, & newsockfd );15

16 }17 exit (0);18 }19

20 static void ∗ doit (void ∗ arg ) {21 pthread_detach ( pthread_self ());22 // |23 // V24 str_echo ( arg );25 close ( ( int ) arg );26 return NULL;27 }

• Die Lösung in Programm 7.3, S. 233 hat gravierende Nachteile: Die eine Variable newsockfd(shared variable) wird bei jedem Aufruf von accept überschrieben – folgendes Szenario istmöglich:

– beim ersten Aufruf von accept() erhält newsockfd den Wert 5

– der Zeiger auf newsockfd wird als letztes Argument an pthread_create() über-geben.

– ein Thread wird erzeugt und die Funktion doit() wird eingeplant

– zuvor aber kehrt accept() erneut zurück, der Rückgabewert (z.B. 8) wird in newsockfdabgelegt.

– der erste Thread startet statt mit dem Wert 6 mit dem Wert 8!

• Problem also: Zugriff zweier Threads auf eine gemeinsame Variable (newsockfd) ohneSynchronisation!

Eine bessere Lösung zeigt Programm 7.4, S. 234.

Page 240: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

234 KAPITEL 7. THREADS

Programm 7.4: Echo Server mit Threads – Alternative zu Prog. 7.2, S. 231 (thread-echo/main-srv-b.c)

1 /∗ instead of fork () we use threads ∗/2

3 # include " inet .h"4 # include < stdio .h>5 # include <unistd .h>6 # include < stdlib .h>7 # include < string .h>8 # include < strings .h>9 # include <errno .h>

10 # include <sys/types .h>11 # include <sys/ socket .h>12 # include < netinet /in .h>13 # include <arpa/ inet .h>14 # include <pthread .h>15

16

17 extern void str_echo ( int );18 static void ∗ doit (void ∗);19

20 int main( int argc , char ∗∗argv ) {21

22 int sockfd , clilen ;23 int ∗ p_newsockfd ;24 pthread_t tid ;25 struct sockaddr_in cli_addr , serv_addr ;26

27 if ( ( sockfd = socket (AF_INET, SOCK_STREAM,0)) < 0) {28 perror ( " server : can’t open stream socket");29 exit (1);30 }31

32 bzero (( char ∗) &serv_addr , sizeof ( serv_addr ));33

34 serv_addr . sin_family = AF_INET;35 serv_addr . sin_addr . s_addr = htonl (INADDR_ANY);36 serv_addr . sin_port = htons (SERV_TCP_PORT);37

38 if ( bind ( sockfd , ( struct sockaddr ∗)&serv_addr ,39 sizeof ( serv_addr )) < 0) {40 perror ( " server : can’t bind local address" );41 exit (2);42 }43

44 listen ( sockfd ,5);45

46 while(1) {47 clilen = sizeof ( cli_addr );48 p_newsockfd = malloc ( sizeof ( int ));49 ∗ p_newsockfd = accept ( sockfd , ( struct sockaddr ∗) & cli_addr ,50 & clilen );51

52 if (∗ p_newsockfd < 0 ) {

Page 241: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

7.4. TCP ECHO SERVER MIT THREADS 235

53 if ( errno == EINTR)54 continue; /∗ try again ∗/55 perror ( "server : accept error" );56 exit (3);57 }58

59 pthread_create (&tid , NULL, &doit, p_newsockfd );60

61 }62 exit (0);63 }64

65 static void ∗ doit (void ∗ arg ) {66 int connfd = ∗ ( ( int ∗) arg );67 free ( arg );68 pthread_detach ( pthread_self ());69 str_echo ( connfd );70 close ( connfd );71 return NULL;72 }

Anmerkungen zu Programm 7.4, S. 234:

• Vor jedem Aufruf von accept() wird dynamisch Speicherplatz für eine Integer-Größe –den Deskriptor für die neue Socket-Verbindung – alloziert.

• Damit hat jeder Thread seine eigene Kopie!

• Der Thread holt sich diesen Wert in eine eigene lokale Variable (connfd in der Funktiondoit() und gibt danach den Speicherplatz für die von accept() gelieferte Größe wiederfrei!

Page 242: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

236 KAPITEL 7. THREADS

7.5 Thread-sichere Funktionen

Die Funktionen malloc() wie auch free() waren ursprünglich nicht re-entrant-fähig :

• wird ein Thread (oder die Abarbeitung einer Funktion) durch Context-Wechsel (Aufrufeines Signalbehandlers / einer Signalbehandlerfunktion oder Fortsetzung eines anderenThreads) unterbrechen und unterbrochener wie neu ausgeführter Code teilen sich gemein-same Objekte, so kann es zu chaotischen Zuständen kommen

• eine Funktion ist reentrantfähig, wenn mehrfache (überlappende) Aufrufe möglich sind,die sich gegenseitig nicht beeinflussen

Der POSIX-Standard verlangt, dass u.a. die Funktione malloc() und free() reentrant-fähigsind! POSIX.1 verlangt dass alle in POSIX.1 wie im ANSI-C-Standard defineirten Funktionen miteinigen Ausnahmen (!) Thread-sicher sind!

Bei eigenen Funktionen muss man selbst auf die Reentrant-Fähigkeit achten, z.B.:

• Die Funktion sollte sich nicht selbst (ihre Instruktionen) verändern!

• Zugriff auf alle shared Objekte nur atomar (siehe Datentyp sig_atomic_t) – oder: alleObjekte, die von der Funktion modifiziert werden, sind einer speziellen Instanz (Aufruf,Inkarnation) zugeordnet (instanz-spezifische Objekte)!

• keine static-Variable in Funktionen, keinen Rückgabezeiger auf eine static-Variable!

• kein Aufruf nicht-reentrantfähiger Funktionen

• kein nicht-atomarer Zugriff auf die Hardware

mehr dazu siehe z.B. W.R. Stevens e.a.: UNIX Network Programming Volume I: The SocketsNetworking API. Addison Wesley 2004, 3rd Ed., S. 686ff

Page 243: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

7.6. ECHO CLIENT MIT THREADS 237

7.6 Echo Client mit Threads

Wir können auch den Client mit Threads arbeiten lassen (siehe Abb. 7.2, S. 237).

copyto

main

thread

thread

serverpthread_create

stdout

fp

Abbildung 7.2: Echo Client mit Threads

Das Hauptprogramm des Client bleibt wie in Programm 6.17, S/210, dargestellt. Die Funktionali-tät ist in Programm 7.5, S. 237 dargestellt. Die beiden globalen Variablen für den copyto-Thread(fp und sockfd) hätten auch in eine Struktur zusammengefasst werden können und beim Auf-ruf von pthread_create mit einem Zeiger darauf übergeben werden können!

Programm 7.5: Echo Client mit Threads (thread-echo/strcli-thread.c)

1 /∗ str_cli . c −− thread version2 ∗ function used by connection −oriented clients3 ∗4 ∗ one thread reads the contents of the FILE ∗ fp , write each line to the5 ∗ stream socket ( to the server process ), the other thread reads a line back6 ∗ from the socket and writes it to stdout7 ∗8 ∗ return to caller when an EOF is encountered on the input file9 ∗/

10

11 # include < stdio .h>12 # include <unistd .h>13 # include < stdlib .h>14 # include < string .h>15 # include <sys/ socket .h>16 # include <pthread .h>17 # include "inbuf .h"18 # define MAXLINE 3219

20 void ∗ copyto (void ∗);21

22 static int sockfd ;23 static FILE ∗ fp ;24

25 void str_cli (FILE ∗ fp_arg , int sockfd_arg ) {26 int n;27 char recvline [MAXLINE+1];28 pthread_t tid ;

Page 244: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

238 KAPITEL 7. THREADS

29 inbuf in ;30 inbuf_alloc (&in , 512);31 sockfd = sockfd_arg ;32 fp = fp_arg ;33 in . fd = sockfd ;34

35 pthread_create (&tid ,NULL,copyto,NULL);36

37 while ( (n = inbuf_read (&in , recvline , MAXLINE) ) != 0) {38 if (n < 0) {39 perror ( " str_cli : read error" );40 exit (5);41 }42 write (1, recvline ,n );43 }44 return ;45 }46

47 void ∗ copyto (void ∗ arg ) {48 char sendline [MAXLINE]; int n;49 while ( fgets ( sendline , MAXLINE,fp) != NULL) {50 n = strlen ( sendline );51 if ( write ( sockfd , sendline ,n) != n) {52 perror ( " str_cli : write error on socket");53 exit (4);54 }55 }56 shutdown( sockfd ,1);57 return NULL;58 }

Page 245: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

7.7. SYNCHRONISATION – MUTUAL EXCLUSION 239

7.7 Synchronisation – Mutual Exclusion

Wie im vorletzten Abschnitt bereits angesprochen, ist der wechselseitige Zugriff auf gemeinsameDatenbereich kritisch – dies soll ein einfaches Beispiel in Programm 7.6, S. 239, zeigen.

Programm 7.6: Zähler hochzählen ohne Synchronisation (mutex/mutex1.c)

1 # include <pthread .h>2 # include < stdio .h>3 # include < stdlib .h>4

5 # define NLOOP 50006

7 int counter = 0;8 void ∗ doit (void ∗);9

10 int main() {11 pthread_t tidA , tidB ;12

13 // create two threads with same function14 pthread_create (&tidA, NULL, &doit, NULL);15 pthread_create (&tidB , NULL, &doit, NULL);16

17 // wait for both threads to terminate18 pthread_join ( tidA , NULL);19 pthread_join ( tidB , NULL);20

21 exit (0);22 }23

24 void ∗ doit (void ∗ arg ) {25 int i , val ;26 for ( i= 0; i < NLOOP; i++) {27 val = counter ;28 printf ( "%d: %d\n", (int) pthread_self (), val + 1);29 counter = val + 1;30 }31 return NULL;32 }

Das Ergebnis dieses Programms ist nicht-deterministisch, der Fehler offenkundig!

Hier bieten sich im gegebenen Kontext spezielle Objekte, sog. MutexVariablle (Typ:pthread_mutex_t)– eine Art Semaphore ( – mit speziellen Zugriffsoperation zum Sperren (protect nach Dijkstra oderFunktionen lockf() / fcntl()) und Freigeben (vrej nach Dijkstra oder Funktion fcntl()) an.Ein solches Objekt kann die Zustände unlocked (nicht im Besitz eines Thread) und locked (imBesitz genau eines Thread). Versucht ein Thread, ein Mutex-Objekt im Zustand locked zu sperren,so wird er blockiert, bis der Thread, der das Mutex-Objekt besitzt dieses freigibt.

Startwert: PTHREAD_MUTEX_INITIALIZER

Page 246: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

240 KAPITEL 7. THREADS

Funktion zum Sperren und Freigeben:

# include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t * mptr);

int pthread_mutex_unlock(pthread_mutex_t * mptr);

/* both return 0 if OK, positive Exxx value on error */

Damit lässt sich das Programm 7.6 sicherer machen (siehe Programm 7.7, S. 240).

Programm 7.7: Zähler hochzählen mit Synchronisation (mutex/mutex2.c)

1 # include <pthread .h>2 # include < stdio .h>3 # include < stdlib .h>4

5 # define NLOOP 50006

7 int counter = 0;8 pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;9

10 void ∗ doit (void ∗);11

12 int main() {13 pthread_t tidA , tidB ;14

15 // create two threads with same function16 pthread_create (&tidA, NULL, &doit, NULL);17 pthread_create (&tidB , NULL, &doit, NULL);18

19 // wait for both threads to terminate20 pthread_join ( tidA , NULL);21 pthread_join ( tidB , NULL);22

23 exit (0);24 }25

26 void ∗ doit (void ∗ arg ) {27 int i , val ;28 for ( i= 0; i < NLOOP; i++) {29 pthread_mutex_lock (& counter_mutex );30 val = counter ;31 printf ( "%d: %d\n", (int) pthread_self (), val + 1);32 counter = val + 1;33 pthread_mutex_unlock (& counter_mutex );34 }35 return NULL;36 }

Page 247: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

7.7. SYNCHRONISATION – MUTUAL EXCLUSION 241

Deadlock:

Das Arbeiten mit Mutex-Variablen hat allerdings auch seine Tücken. Nehmen wir der Einfachheithalber an, zwei Threads wollen zwei Zähler z1 und z2 hochzählen nach folgender Maßgabe:

Thread A: z1++, if ( z1 > z2 ) { z2 += 2 } else z2++;Thread B: z2++, if ( z2 > z1 ) { z1 += 2 } else z1++;

Ein mögliche Implementierung zeigt Programm 7.8, S. 241.

Programm 7.8: Synchronisation mit Deadlock (mutex/deadlock.c)

1 # include <pthread .h>2 # include < stdio .h>3 # include < stdlib .h>4

5 # define NLOOP 50006

7 int counter_x = 0; // Zaehler z18 int counter_y = 0; // Zaehler z29 pthread_mutex_t counter_x_mutex = PTHREAD_MUTEX_INITIALIZER;

10 pthread_mutex_t counter_y_mutex = PTHREAD_MUTEX_INITIALIZER;11

12 void ∗ A_doit (void ∗);13 void ∗ B_doit (void ∗);14

15 int main() {16 pthread_t tidA , tidB ;17

18 // create two threads with same function19 pthread_create (&tidA, NULL, &A_doit, NULL);20 pthread_create (&tidB , NULL, &B_doit, NULL);21

22 // wait for both threads to terminate23 pthread_join ( tidA , NULL);24 pthread_join ( tidB , NULL);25

26 exit (0);27 }28

29 void ∗ A_doit (void ∗ arg ) {30 int i , val_x , val_y , incr ;31 for ( i= 0; i < NLOOP; i++) {32 pthread_mutex_lock (& counter_x_mutex );33 val_x = counter_x ;34 printf ( "%d: x = %d\n", (int) pthread_self (), val_x + 1);35 counter_x = val_x + 1;36

37 pthread_mutex_lock (& counter_y_mutex );38 val_y = counter_y ;39 incr = ( counter_x > val_y ) ? 2 : 1;40 printf ( "%d: y = %d\n", (int) pthread_self (), val_y + incr );41 counter_y = val_y + incr ;

Page 248: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

242 KAPITEL 7. THREADS

42

43 pthread_mutex_unlock (& counter_y_mutex );44 pthread_mutex_unlock (& counter_x_mutex );45 }46 return NULL;47 }48

49 void ∗ B_doit (void ∗ arg ) {50 int i , val_x , val_y , incr ;51 for ( i= 0; i < NLOOP; i++) {52 pthread_mutex_lock (& counter_y_mutex );53 val_y = counter_y ;54 printf ( "%d: y = %d\n", (int) pthread_self (), val_y + 1);55 counter_y = val_y + 1;56

57 pthread_mutex_lock (& counter_x_mutex );58 val_x = counter_x ;59 incr = ( counter_y > val_x ) ? 2 : 1;60 printf ( "%d: x = %d\n", (int) pthread_self (), val_x + incr );61 counter_x = val_x + incr ;62

63 pthread_mutex_unlock (& counter_x_mutex );64 pthread_mutex_unlock (& counter_y_mutex );65 }66 return NULL;67 }

Klassischer (kausallogischer) Deadlock:

• Prozess P1 hat Ressource R1 exklusiv und benötigt Ressource R2 zum Weitermachen eben-falls exklusiv

• Prozess P2 hat Ressource R2 exklusiv und benötigt Ressource R2 zum Weitermachen eben-falls exklusiv

RIEN NE VA PLUSWann? Dann, wenn man am Wenigsten damit rechnet (Murphy)!

Page 249: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Anhang

243

Page 250: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe
Page 251: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Literatur

[Bach86] M. J. Bach: The Design of the UNIX Operating System. PrenticeHall, 1986.

[Comer84] D. Comer: Operating System Design: The XNIU Approach. Prenti-ce Hall, 1984.

[Darnell01] P. A. Darnell und P. E. Margolis: C: A Software Engineering Ap-proach. Springer, Dritte Auflage, 2001.

[Handschuch93] T. Handschuch: SOLARIS 2 für den Systemadministrator. SolarisGalerie, IWT-Verlag, 1993.

[Herold99] H. Herold: Linux-Unix-Shells. Addison-Wesley, 1999.

[Kernighan86] B. W. Kernighan und R. Pike: Der UNIX-Werkzeugkasten. Han-ser, 1986.

[Kernighan90] B. W. Kernighan und D. Ritchie: Programmieren in C. Hanser,Zweite Auflage, 1990.

[Rochkind04] M. Rochkind: Advanced UNIX Programming. 2nd Ed., PrenticeHall 2004

[Stevens92] W. R. Stevens: Advanced Programming in the UNIX Environment.Addison-Wesley, 1992.

[Stevens04] W.R. Stevens: UNIX Network Programming, Volume 1: The SocketsNetworking API. 3rd ed., Addison Wesley 2004

[Stevens98] W.R. Stevens: UNIX Network Programming, Volume 2: InterprocessCommunications. 2nd. Ed., Addison Wesley 1998

[Tanenbaum02] A. S. Tanenbaum: Moderne Betriebssysteme Addison Wesley2002.

[Tanenbaum03] A. S. Tanenbaum: Computernetzwerke. 4th Ed., Prentice Hall 2003

jeweils auf aktuellste Ausgabe achten!

245

Page 252: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

246 LITERATUR

Page 253: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Abbildungsverzeichnis

1.1 Prozess-Hierarchie . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.2 Prozess – Zustandsmodell . . . . . . . . . . . . . . . . . . . . . . . . 91.3 Virtuelle und physische Adressen . . . . . . . . . . . . . . . . . . . . 101.4 Prozess-Kontext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2.1 Ein neuer Prozess und sein Erzeuger . . . . . . . . . . . . . . . . . . 162.2 Die exec()-Familie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232.3 Information über Kind-Prozess . . . . . . . . . . . . . . . . . . . . . 292.4 n-Damen-Problem: das Schachbrett . . . . . . . . . . . . . . . . . . . 352.5 n-Damen-Problem: der Lösungsbaum . . . . . . . . . . . . . . . . . 362.6 n-Damen-Problem: Zeilen-/Spaltenbedrohung . . . . . . . . . . . . 362.7 Start einer Anwendung von der Shell . . . . . . . . . . . . . . . . . 422.8 Ablaufprinzip der tinysh . . . . . . . . . . . . . . . . . . . . . . . . . 432.9 Datenstruktur zur Wortzerlegung . . . . . . . . . . . . . . . . . . . . 472.10 Der Init-Prozess . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

3.1 Token-Erkennung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 893.2 Syntax-Analyse der Kommandozeile . . . . . . . . . . . . . . . . . . 893.3 Ablauf der MidiShell . . . . . . . . . . . . . . . . . . . . . . . . . . . 903.4 Struktur der MidiShell . . . . . . . . . . . . . . . . . . . . . . . . . . 90

4.1 IPC – nur über den Kernel . . . . . . . . . . . . . . . . . . . . . . . . 1094.2 IPC – auch über Rechnergrenzen . . . . . . . . . . . . . . . . . . . . 1104.3 Client-Server-Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . 1104.4 Unnamed Pipe - erster Schritt . . . . . . . . . . . . . . . . . . . . . . 1124.5 Unnamed Pipe – zweiter Schritt . . . . . . . . . . . . . . . . . . . . . 1124.6 Unnamed Pipe – dritter Schritt . . . . . . . . . . . . . . . . . . . . . 1134.7 Client-Server mit bidirektionaler Kommunikation . . . . . . . . . . 115

5.1 ISO-OSI-Referenzmodell . . . . . . . . . . . . . . . . . . . . . . . . . 1285.2 TCP/IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1295.3 Unshielded-Twisted-Pair-Kabel . . . . . . . . . . . . . . . . . . . . . 1315.4 Shielded-Twisted-Pair-Kabel . . . . . . . . . . . . . . . . . . . . . . . 1325.5 Screened Shielded-Twisted-Pair-Kabel . . . . . . . . . . . . . . . . . 1325.6 Koaxial-Kabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1325.7 Ethernet Frame Format . . . . . . . . . . . . . . . . . . . . . . . . . . 1365.8 Layered System Architecture . . . . . . . . . . . . . . . . . . . . . . 137

247

Page 254: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

248 ABBILDUNGSVERZEICHNIS

5.9 Internet Architektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1375.10 Abstraktion der Identifikation . . . . . . . . . . . . . . . . . . . . . . 1385.11 32-Bit IP-Adresse in Version 4 . . . . . . . . . . . . . . . . . . . . . . 1395.12 IP-Adresse: dotted decimal form . . . . . . . . . . . . . . . . . . . . 1395.13 IP-Adressklassen in Version 4 . . . . . . . . . . . . . . . . . . . . . . 1395.14 TCP/IP-Schichtenmodell . . . . . . . . . . . . . . . . . . . . . . . . . 1435.15 IP Datagramm - grober Aufbau . . . . . . . . . . . . . . . . . . . . . 1445.16 IP Datagram - im Detail . . . . . . . . . . . . . . . . . . . . . . . . . 1445.17 TCP/IP Layering Model . . . . . . . . . . . . . . . . . . . . . . . . . 1455.18 UDP - Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1475.19 UDP-Demultiplexing . . . . . . . . . . . . . . . . . . . . . . . . . . . 148

6.1 Vereinf. Modell der Implementierung von Sockets unter BSD . . . . 1526.2 Sockets – Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1596.3 API-Aufrufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1596.4 Kommunikation in der Unix-Domain . . . . . . . . . . . . . . . . . 1606.5 Verbindungsorientierte Client-Server-Kommunikation . . . . . . . 1656.6 Aufbau einer verbind.-orient. Client/Server-Kommunikation . . . 1676.7 Ports und Prozesse in Unix . . . . . . . . . . . . . . . . . . . . . . . 1706.8 netscape als Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1716.9 Server httpd antwortet . . . . . . . . . . . . . . . . . . . . . . . . . . 1726.10 Aufbau einer verb.-losen Client/Server-Kommunikation . . . . . . 1786.11 Organisation der Adress-Struktur sockaddr_in . . . . . . . . . . . . 1896.12 dotted-decimal notation . . . . . . . . . . . . . . . . . . . . . . . . . 1906.13 Methoden der Adress-Resolution . . . . . . . . . . . . . . . . . . . . 1946.14 Struktur eines Eingabepuffers . . . . . . . . . . . . . . . . . . . . . . 200

7.1 Multithreading vs. Single threading . . . . . . . . . . . . . . . . . . 2257.2 Echo Client mit Threads . . . . . . . . . . . . . . . . . . . . . . . . . 237

Page 255: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Beispiel-Programme

1.1 Prozess-ID abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2 Von stdin lesen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.3 Priorität verändern . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.1 Ein neuer Prozess mit fork() . . . . . . . . . . . . . . . . . . . . . . . 172.2 fork() zum zweiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.3 Beenden mit exit() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.4 Beenden mit _exit() . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.5 Beenden mit eigenem Aufräumen . . . . . . . . . . . . . . . . . . . 212.6 Neues Programm ausführen . . . . . . . . . . . . . . . . . . . . . . . 252.7 Programm 2.6 ohne fflush() . . . . . . . . . . . . . . . . . . . . . . . 262.8 Argumentvektor übergeben . . . . . . . . . . . . . . . . . . . . . . . 272.9 Environment ausgeben . . . . . . . . . . . . . . . . . . . . . . . . . . 272.10 Umgebung ändern . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272.11 Umgebung ändern bei execl() . . . . . . . . . . . . . . . . . . . . . . 282.12 Auf Kind-Prozess warten . . . . . . . . . . . . . . . . . . . . . . . . 302.13 Auf Kind-Prozess warten . . . . . . . . . . . . . . . . . . . . . . . . 312.14 Warten mit waitpid() . . . . . . . . . . . . . . . . . . . . . . . . . . . 322.15 Ein Prozess wird zum Waisenkind . . . . . . . . . . . . . . . . . . . 332.16 Erzeugen eines Zombie-Prozesses . . . . . . . . . . . . . . . . . . . 342.17 8-Damen – klassisch . . . . . . . . . . . . . . . . . . . . . . . . . . . 362.18 Rekursion mit Unterprozessen . . . . . . . . . . . . . . . . . . . . . 392.19 Über 0 aus einer Datei lesen . . . . . . . . . . . . . . . . . . . . . . . 402.20 Über 1 in eine Datei schreiben . . . . . . . . . . . . . . . . . . . . . . 412.21 tinysh: readline() – Schnittstelle . . . . . . . . . . . . . . . . . . . . . 452.22 tinysh: readline() – Implementierung . . . . . . . . . . . . . . . . . . 452.23 tinysh: readline() – Test . . . . . . . . . . . . . . . . . . . . . . . . . . 462.24 tinysh: Datenstruktur für den tokenizer() – Schnittstelle . . . . . . . 482.25 tinysh: Datenstruktur für den tokenizer() – Implementierung . . . . 492.26 tinysh: tokenizer() – Schnittstelle . . . . . . . . . . . . . . . . . . . . 502.27 tinysh: tokenizer() – Implementierung . . . . . . . . . . . . . . . . . 502.28 tinysh: tokenizer() – Test . . . . . . . . . . . . . . . . . . . . . . . . . 512.29 tinysh: Hauptprogramm . . . . . . . . . . . . . . . . . . . . . . . . . 523.1 Behandlung eines SIGINT-Signals . . . . . . . . . . . . . . . . . . . 623.2 read-Operation mit Zeitlimit . . . . . . . . . . . . . . . . . . . . . . . 653.3 Versenden eines Signals an den übergeordneten Prozess . . . . . . 673.4 Versenden eines Signals an den erzeugten Prozess . . . . . . . . . . 68

249

Page 256: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

250 BEISPIEL-PROGRAMME

3.5 Verwendung von kill() zur Überprüfung der Existenz eines Prozesses 693.6 Virtuelles Ballspiel zweier Prozesse . . . . . . . . . . . . . . . . . . . 713.7 Header-File zu 3.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 743.8 Erstes Beispiel zu sigaction() . . . . . . . . . . . . . . . . . . . . . . . 743.9 Testprogramm zu 3.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . 753.10 Modifikation von 3.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . 763.11 Verlust von Signalen . . . . . . . . . . . . . . . . . . . . . . . . . . . 793.12 Prozesse, auf die der Erzeuger nicht wartet . . . . . . . . . . . . . . 823.13 Prozesse, auf die der Erzeuger ohne zu blockieren wartet . . . . . . 843.14 Prozesse, auf die der Erzeuger ohne zu blockieren wartet . . . . . . 863.15 Midi-Shell: Grundlegende Vereinbarungen . . . . . . . . . . . . . . 913.16 Midi-Shell: Schnittstelle zur Tokenbestimmung . . . . . . . . . . . . 913.17 Midi-Shell: Schnittstelle zum Signalbehandler . . . . . . . . . . . . 913.18 Midi-Shell: Schnittstelle zur Kommandoausführung . . . . . . . . . 923.19 Midi-Shell: Schnittstelle zur Ausgabe des Exitstatus . . . . . . . . . 923.20 Midi-Shell: main-Funktion – Start . . . . . . . . . . . . . . . . . . . . 933.21 Midi-Shell: Tokenbestimmung . . . . . . . . . . . . . . . . . . . . . . 943.22 Midi-Shell: Kommandoausführung . . . . . . . . . . . . . . . . . . . 963.23 Midi-Shell: Signalbehandler . . . . . . . . . . . . . . . . . . . . . . . 1013.24 Midi-Shell: Termination . . . . . . . . . . . . . . . . . . . . . . . . . 1023.25 Midi-Shell: Testprogramm 1 . . . . . . . . . . . . . . . . . . . . . . . 1033.26 Midi-Shell: Testprogramm 2 . . . . . . . . . . . . . . . . . . . . . . . 1034.1 Einfache Kommunikation via Pipe . . . . . . . . . . . . . . . . . . . 1144.2 Hauptprogramm zum Client/Server-Beispiel . . . . . . . . . . . . . 1164.3 Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1164.4 Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1174.5 Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1184.6 Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1184.7 Beispiel mit popen(), pclose() . . . . . . . . . . . . . . . . . . . . . . 1214.8 0/1 auf Pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1224.9 Termination? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1234.10 Termination! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1244.11 Pipelining der Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1254.12 Schreiben in eine Pipe ohne Leseende . . . . . . . . . . . . . . . . . 1266.1 Timeserver an Port 11011 . . . . . . . . . . . . . . . . . . . . . . . . . 1536.2 Timeclient für Port 11011 . . . . . . . . . . . . . . . . . . . . . . . . . 1696.3 Time-Server, der nach dem Woher fragt . . . . . . . . . . . . . . . . 1746.4 Time-Server auf Basis UDP (nur zur Demo) . . . . . . . . . . . . . . 1806.5 Time-Client auf Basis UDP (nur zur Demo) . . . . . . . . . . . . . . 1816.6 Hostnamen ermitteln . . . . . . . . . . . . . . . . . . . . . . . . . . . 1936.7 Hostnamen bestimmen . . . . . . . . . . . . . . . . . . . . . . . . . . 1956.8 Funktion uname() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1966.9 Port und Protokoll eines Dienstes . . . . . . . . . . . . . . . . . . . . 1986.10 Schnittstelle für einen Eingabe-Puffer . . . . . . . . . . . . . . . . . 2016.11 Implementierung des Eingabe-Puffers . . . . . . . . . . . . . . . . . 2026.12 Schnittstelle für einen Ausgabe-Puffer . . . . . . . . . . . . . . . . . 204

Page 257: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

BEISPIEL-PROGRAMME 251

6.13 Implementierung des Ausgabe-Puffers . . . . . . . . . . . . . . . . . 2056.14 Headerfile für Netzwerkverbindung . . . . . . . . . . . . . . . . . . 2066.15 Hauptprogramm des TCP-Servers mit Eingabepufferung . . . . . . 2066.16 Funktionalität des TCP-Servers mit Eingabepufferung . . . . . . . . 2096.17 Hauptprogramm des TCP-Clients mit Eingabepufferung . . . . . . 2106.18 Funktionalität des TCP-Clients mit Eingabepufferung . . . . . . . . 2116.19 Schnittstelle für MXP-Anfrage-Pakete . . . . . . . . . . . . . . . . . 2136.20 Schnittstelle für MXP-Antwort-Pakete . . . . . . . . . . . . . . . . . 2146.21 Das Einlesen eines MXP-Anfrage-Pakets . . . . . . . . . . . . . . . . 2156.22 Das Einlesen eines MXP-Antwort-Pakets . . . . . . . . . . . . . . . 2166.23 Schnittstelle für die Kontrollstruktur von Netzwerkdiensten . . . . 2216.24 Implementierung für die Kontrollstruktur von Netzwerkdiensten . 2227.1 Matrix-Multiplikation mit Threads . . . . . . . . . . . . . . . . . . . 2287.2 Echo Server mit Threads . . . . . . . . . . . . . . . . . . . . . . . . . 2317.3 Echo Server mit Threads – Alternative zu Prog. 7.2, S. 231? . . . . . 2337.4 Echo Server mit Threads – Alternative zu Prog. 7.2, S. 231 . . . . . . 2347.5 Echo Client mit Threads . . . . . . . . . . . . . . . . . . . . . . . . . 2377.6 Zähler hochzählen ohne Synchronisation . . . . . . . . . . . . . . . 2397.7 Zähler hochzählen mit Synchronisation . . . . . . . . . . . . . . . . 2407.8 Synchronisation mit Deadlock . . . . . . . . . . . . . . . . . . . . . . 241

Page 258: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

Index

/etc/hosts, 194/etc/services, 197_Exit(), 64_exit(), 20

abort(), 64accept(), 166, 184Adress-Resolution, 192, 194AF_INET, 161AF_UNIX, 161, 187alarm(), 65AN, 142Anwendungsschicht, 129API, 151argv, 23AS, 142atexit(), 21Autonome Systeme, 142

backlog, 166bcopy(), 169Beendigungsstatus, 3, 4, 33best-effort delivery, 135, 143BGP, 141Big-Endian, 189bind(), 162, 177, 178, 184Bitübertragungsschicht, 128Bootstrapping, 54BREAK, 60Bridge, 130broadcast, 135Broadcast-Adresse, 190BSD, 151bzero(), 155

child process, 15Client, 165Client-Server, 115close(), 113, 177, 185

concurrent server, 218connect(), 165, 179, 184Connectionless, 143control process, 3CSMA/CD-Verfahren, 134ctrl-\, 60CTRL-c, 61ctrl-c, 60ctrl-z, 60

SIGSTOP, 5current working directory, 19, 24

Dämon, 3Datagram, 177Datagram Socket, 157, 177–179Datendarstellungsschicht, 129Datentyp

sig_atomic_, 64Deadlock, 112delete, 60DIN/ISO-OSI, 128DNS, 194Domain Name System, 194dotted decimal notation, 139, 190dup(), 111dup2(), 111dynamic and/or private ports, 197

effective group ID, 19effective user ID, 19effektive group ID, 24effektive user ID, 24EINTR, 78end-of-record, 176ephemeral ports, 197errno, 78, 224Ethernet, 135exec, 22execl(), 22

252

Page 259: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

INDEX 253

execle(), 22execlp(), 22execv(), 22execve, 22execvp(), 22exit(), 3, 4, 20, 32, 33Exit-Status, 3exit-Status, 33

fcntl(), 185FDDI, 133fflush(), 25file locks, 24file mode creation mask, 19, 24fork(), 1, 15, 112, 113FQDN, 192fread(), 46Fully Qualified Domain Name, 192

GAN, 127Gateway, 130gethostbyaddr(), 192, 195gethostbyname(), 192gethostname(), 195getpeername(), 183, 185getpgrp(), 2getpid(), 2getppid(), 2getservbyname(), 198getservbyport(), 198getsockname(), 183, 185getsockopt(), 185global area network, 127

HANGUP, 60hangup-Signal, 3Host, 190host interface, 135hostent, 192Hostname, 195Hostnamen, 192, 194htonl(), 189htons(), 189Hub, 130

IANA, 197ICMP, 157

IGMP, 157IGRP, 141INADDR_ANY, 155, 188INADDR_NONE, 190inet_addr(), 190inet_aton(), 191inet_ntoa(), 173, 191inetd, 183init-Prozess, 33, 55, 56Inter-Process Communication, 109Internet Assigned Numbers Authority,

197Internet Datagram, 144Internet Domain, 156Internet Protocol, 143, 157Internet Superserver, 183Internet-Adressen, 138, 190, 194ioctl(), 185IP, 143, 157IP-Adresse, 138, 195IP-Adresse 0, 188IPC, 1, 4, 109, 110iterative server, 217

Job, 7

KabelTwisted-Pair, 131

Kernel Mode, 9kill, 2, 5kill(), 59, 60, 67Kommando, 60

kill, 5nice, 7nohup, 7top, 5

Kommunikationsdomäne, 151Kommunikationsendpunkt, 151Kommunikationssemantik, 156Kommunikationssteuerungsschicht, 129Kontext, 1kurzlebige Portnummern, 188, 197

LAN, 127listen(), 166, 184Little-Endian, 189loader, 55

Page 260: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

254 INDEX

local area network, 127lsof, 170

MAC-Adresse, 133, 135MAX_PATH, 187MAXHOSTNAMELEN, 195memcpy(), 169memory buffers, 186memset(), 155MMU, 10multi-homed host, 190Mutex, 239

Nameserver, 194NAT, 141netdb.h, 197Netwerktopologie, 127network byte order, 189Network Information Service, 194Netzmaske, 140nice, 7nice(), 7NIS, 194ntohl(), 190ntohs(), 173, 190nuhup, 7

OSI, 128OSPF, 141out-of-band-data, 175

parent process, 2, 15parent process ID, 24PATH, 23, 120pause(), 70, 72PC, 1pclose(), 120Peer-Adresse, 166PF_INET, 161PF_UNIX, 161PID, 2pipe(), 111, 113pipefd[0], 111pipefd[1], 111poll(), 66, 218popen(), 120Port, 147

Port-Nummer, 188Port-Nummern, 149Portnummer, 185, 197Portnummer 0, 188process group ID, 19, 24process group leader, 2process ID, 24program counter, 1Protokolle, 127Prozess, 1, 54Prozessgruppe, 2Prozessstatus, 5Prozesstabelle, 12ps-Kommando, 5Pseudo-Terminal, 61pthread_, 224pthread_create(), 225pthread_detach(), 227pthread_exit(), 228pthread_join(), 227pthread_mutex_lock, 240pthread_mutex_t, 239pthread_mutex_unlock, 240pthread_self(), 227punktiertes Dezimalformat, 139, 190

raise(), 67Raw Socket, 157re-entrant, 236read(), 112, 175, 184readv(), 175, 184real group ID, 19, 24real user ID, 19, 24Rechneradresse, 185recv(), 175, 179, 184recvfrom(), 176, 178, 184recvmsg(), 176, 178, 184regions, 11registered ports, 197Repeater, 130reservierte Portnummern (UNIX), 197Resolver, 192RIP, 141rlogin, 197root directory, 19, 24Router, 130

Page 261: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

INDEX 255

Routing, 138, 141rsh, 197

sa_data, 186sa_family, 186sa_len, 186select(), 66, 184, 218Semaphore, 239send(), 175, 179, 184sendmsg(), 176, 178, 179, 184sendto(), 176, 178, 179, 184Server, 165set group ID, 24set user Id, 24setpgrp(), 2setsockopt(), 185Shell-Variable

?, 4$, 2

shutdown(), 177, 185Sicherungsschicht, 128sig_atomic_t, 64SIG_ERR, 62sigaction(), 73, 78, 79sigaddset(), 76SIGALRM, 65SIGCHLD, 33, 82SIGCLD, 33SIGCONT, 60sigemptyset(), 74SIGFPE, 60sighold(), 79, 80SIGHUP, 7, 33, 69SIGINT, 60SIGKILL, 5, 61Signal

9, 5anhangiges, 78ctrl-z, 5SIGHUP, 7SIGKILL, 5SIGSTOP, 5SIGTTIN, 6

signal handler, 61signal handling settings, 19signal(), 33, 61, 63, 64

Signale, 24SIGPIPE, 60SIGQUIT, 60sigrelse(), 79, 80SIGSEGV, 60SIGSTOP, 5, 60, 61

ctrl-z, 5SIGTERM, 69SIGTTIN, 6SIGUSR1, 69SIGUSR2, 69SOCK_DGRAM, 161, 177SOCK_RAW, 161SOCK_STREAM, 161sockaddr, 173Socket Adresse, 162socket(), 161, 184Socket-Typen, 156socketpair(), 184Sockets, 151SOMAXCON, 172SOMAXCONN, 217Speicherklasse

volatile, 63stralloc, 44Stream Socket, 157struct in_addr, 188struct sockaddr, 162, 186struct sockaddr_in, 164, 188struct sockaddr_un, 163, 187sun_family, 187sun_len, 187sun_path, 187swapper, 55Switch, 130sys/param.h, 195sys/utsname.h, 196

TCP, 161, 197TCP/IP, 129, 143tcpserver, 218Telnet-Server, 197telnetd, 197terminal group, 3terminal group ID, 19, 24TFTP-Server, 197

Page 262: Vorlesungsbegleiter zu Systemnahe Software (Systemnahe

256 INDEX

tftpd, 197Thread, 1

initial, 225main, 225

thread, 223Token-Verfahren, 134top, 5Transceiver, 135Transportschicht, 129, 197Trivial File Transfer Protocol, 197Twisted-Pair, 131

u area, 11, 12UDP, 197uname(), 196uname-Kommando, 195UNIX domain, 156unnamed pipes, 111Unreliable Delivery, 143User Mode, 9

verbindungslos, 156, 177verbindungsorientiert, 156Vererbung, 4, 19, 24Vermittlungsschicht, 128volatile, 63voll-duplex, 177

wait(), 4, 29, 30, 32waitpid(), 29, 32, 86WAN, 127well-known ports, 197well-known ports (UNIX), 197WEXITSTATUS(status), 30wide area network, 127WIFEXITED(status), 30WIFSIGNALED(status), 30wireless LAN, 127WLAN, 127, 133WNOHANG, 31, 86write(), 112, 175, 184writev(), 175, 184WTERMSIG(status), 30WUNTRACED, 32

X Window Server, 197xterm, 61

Zombie, 5, 20, 33