Betriebssystem-Programmierung unter UNIX (1997)
Betriebssystem-Programmierung unter UNIX (1997)
Betriebssystem-Programmierung unter UNIX (1997)
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Software-Schule Schweiz<br />
Ingenieurschule Bern HTL<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong><br />
<strong>unter</strong> <strong>UNIX</strong><br />
Stephan Fischli<br />
Sommer <strong>1997</strong>
Inhaltsverzeichnis<br />
1 Einführung ............................................................................. 1<br />
System-Calls und Funktionen ................................................................................... 2<br />
Fehlerbehandlung ..................................................................................................... 3<br />
Benutzer-Identifikation .............................................................................................. 5<br />
Zeitfunktionen............................................................................................................ 6<br />
2 Standards .............................................................................. 7<br />
Existierende Standards............................................................................................. 8<br />
Inhalt der Standards................................................................................................ 10<br />
Abfragen von Systemkonstanten ............................................................................ 13<br />
3 Filesystem ........................................................................... 15<br />
Aufbau eines Filesystems ....................................................................................... 16<br />
Inodes ..................................................................................................................... 17<br />
Directories und Links............................................................................................... 18<br />
Abfragen der Attribute eines Files........................................................................... 19<br />
Lesen eines Directory ............................................................................................. 22<br />
4 Ein- und Ausgabe ................................................................ 25<br />
Filedeskriptoren....................................................................................................... 26<br />
Öffnen und Erzeugen eines Files............................................................................ 27<br />
Schliessen und Löschen eines Files....................................................................... 29<br />
Lesen und Schreiben eines Files............................................................................ 31<br />
Positionieren eines Files ......................................................................................... 33<br />
Duplizieren eines Filedeskriptors ............................................................................ 35<br />
Filekontrolle............................................................................................................. 37<br />
5 Prozesse.............................................................................. 39<br />
Prozesszustände..................................................................................................... 40<br />
Prozesskontext........................................................................................................ 41<br />
Virtueller Adressraum.............................................................................................. 42<br />
Argumente und Umgebungsvariablen .................................................................... 43<br />
Identifier eines Prozesses....................................................................................... 45<br />
Erzeugen eines Prozesses ..................................................................................... 47<br />
Ausführen eines Programms .................................................................................. 50<br />
Terminieren eines Prozesses ................................................................................. 51<br />
Warten auf einen Prozess....................................................................................... 52<br />
6 Job-Control .......................................................................... 55<br />
Prozessgruppen und Sessions ............................................................................... 56<br />
Abfragen und Setzen der Prozessgruppe............................................................... 57<br />
Erzeugen einer Session.......................................................................................... 58<br />
Bestimmen der Vordergrund-Prozessgruppe ......................................................... 59<br />
7 Signale................................................................................. 61<br />
Erzeugung von Signalen......................................................................................... 62<br />
Signaldisposition ..................................................................................................... 64<br />
Probleme mit Signalen............................................................................................ 65<br />
Schicken eines Signals ........................................................................................... 66<br />
Definieren einer Signalaktion .................................................................................. 67<br />
Ändern der Signalmaske......................................................................................... 69<br />
Weitere Signal-Funktionen...................................................................................... 71<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong><br />
i
8 Pipes.................................................................................... 73<br />
Funktionsweise ....................................................................................................... 74<br />
Erzeugen einer unnamed Pipe ............................................................................... 75<br />
Erzeugen einer named Pipe ................................................................................... 77<br />
9 Fortgeschrittene Ein-/Ausgabe............................................. 79<br />
I/O-Multiplexing ....................................................................................................... 80<br />
Record-Locking ....................................................................................................... 83<br />
10 Netzwerk-Kommunikation .................................................. 87<br />
Kommunikationsmodell........................................................................................... 88<br />
TCP/IP-Protokolle ................................................................................................... 89<br />
Eigenschaften der TCP/IP-Protokolle ..................................................................... 90<br />
Rahmenbildung ....................................................................................................... 91<br />
Internet-Adressen.................................................................................................... 92<br />
Port-Nummern......................................................................................................... 93<br />
Sockets ................................................................................................................... 94<br />
Verbindungslose Kommunikation ........................................................................... 95<br />
Verbindungsorientierte Kommunikation.................................................................. 96<br />
Erzeugen eines Sockets ......................................................................................... 97<br />
Socket-Adressen..................................................................................................... 98<br />
Verknüpfen eines Sockets mit einer Adresse ......................................................... 99<br />
Datenaustausch über unverbundene Sockets...................................................... 100<br />
Akzeptieren einer Verbindung............................................................................... 103<br />
Aufbauen einer Verbindung .................................................................................. 104<br />
Datenaustausch über verbundene Sockets.......................................................... 105<br />
Abfragen von Host-Informationen ......................................................................... 109<br />
Abfragen von Service-Informationen .................................................................... 111<br />
11 Threads ........................................................................... 115<br />
Threads eines Prozesses ..................................................................................... 116<br />
POSIX-Threads..................................................................................................... 117<br />
Erzeugen eines Threads....................................................................................... 118<br />
Terminieren eines Threads ................................................................................... 119<br />
Mutex-Locks.......................................................................................................... 121<br />
Initialisieren eines Mutex-Locks ............................................................................ 122<br />
Sperren und Freigeben eines Mutex-Locks.......................................................... 123<br />
Condition-Variablen............................................................................................... 124<br />
Initialisieren einer Condition-Variablen ................................................................. 125<br />
Warten auf einer Condition-Variablen................................................................... 126<br />
Scheduling ............................................................................................................ 129<br />
Weitere Möglichkeiten........................................................................................... 130<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong><br />
ii
1 Einführung<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 1
Einführung<br />
System-Calls und Funktionen<br />
Benutzerprozess<br />
Anwendungsprogramm<br />
C-Bibliotheksfunktionen<br />
System-Calls<br />
Kernel<br />
System-Calls bilden die Schnittstelle zum <strong>Betriebssystem</strong>, indem sie grundlegende<br />
Dienste für den Zugriff auf System-Ressourcen zur Verfügung stellen. So kann z.B. mit<br />
dem System-Call write in ein File geschrieben werden. System-Calls sind direkte<br />
Eingangspunkte in den Kernel: Wird in einem Anwendungsprogramm ein System-Call<br />
aufgerufen, so verzweigt das Programm in den Kernel, wobei der Prozess vom User-<br />
Modus in den privilegierten Kernel-Modus wechselt.<br />
In der Standard-C-Bibliothek sind eine Reihe von allgemein-nützlichen Funktionen<br />
definiert. Diese sind nicht Teil des Kernels, können aber System-Calls aufrufen. So ruft<br />
z.B. die Funktion fprintf den System-Call write auf. Andere Funktionen wie strcpy<br />
(Kopieren eines Strings) benötigen keine Dienste des <strong>Betriebssystem</strong>s.<br />
Die System-Calls sind in Sektion 2 des „<strong>UNIX</strong> Programmer’s Manual“ beschrieben, die<br />
Standard-C-Funktionen in Sektion 3. Dies wird oft mit einer Nummer in Klammern hinter<br />
dem Namen angedeutet, z.B. write(2) bzw. fprintf(3).<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 2
Einführung<br />
Fehlerbehandlung<br />
#include <br />
#include <br />
void perror(const char *msg);<br />
char *strerror(int errcode);<br />
Tritt in einem System-Call ein Fehler auf, so wird je nach Typ des Rückgabewerts der<br />
Wert –1 oder ein Nullpointer zurückgegeben. Gleichzeitig wird in der globalen Variablen<br />
errno ein Code gespeichert, der zusätzliche Information über den aufgetretenen Fehler<br />
enthält (siehe Tabelle 1-1). Die Variable errno und die symbolischen Fehlercodes sind<br />
im Header-File definiert.<br />
Die Funktion perror(3) schreibt eine zum aktuellen Wert von errno gehörende<br />
Fehlermeldung auf den Standard-Fehlerkanal. Zuvor wird der String, auf den msg zeigt,<br />
und ein Doppelpunkt ausgegeben.<br />
Die Funktion strerror(3) gibt einen Pointer auf die zum Fehlercode errcode<br />
gehörende Fehlermeldung zurück.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 3
Einführung<br />
Name<br />
E2BIG<br />
EACCES<br />
EAGAIN<br />
EBADF<br />
EBUSY<br />
ECHILD<br />
EDEADLK<br />
EDOM<br />
EEXIST<br />
EFAULT<br />
EFBIG<br />
EINTR<br />
EINVAL<br />
EIO<br />
EISDIR<br />
EMFILE<br />
EMLINK<br />
ENAMETOOLONG<br />
ENFILE<br />
ENODEV<br />
ENOENT<br />
ENOEXEC<br />
ENOLCK<br />
ENOMEM<br />
ENOSPC<br />
ENOSYS<br />
ENOTDIR<br />
ENOTEMPTY<br />
ENOTTY<br />
ENXIO<br />
EPERM<br />
EPIPE<br />
ERANGE<br />
EROFS<br />
ESPIPE<br />
ESRCH<br />
EXDEV<br />
Beschreibung<br />
Argumentenliste eines neuen Prozesses ist zu lang<br />
Sucherlaubnis für ein Directory wird verweigert<br />
Ein- oder Ausgabeoperation würde blockieren<br />
Filedeskriptor ist ungültig<br />
Directory ist in Gebrauch<br />
Prozess hat keine Sohnprozesse<br />
Record-Lock würde zu einem Deadlock führen<br />
Argument ist ausserhalb des gültigen Bereichs<br />
File existiert bereits<br />
Argument hat eine ungültige Adresse<br />
Maximale Filegrösse wurde beim Schreiben überschritten<br />
System-Call wurde durch ein Signal <strong>unter</strong>brochen<br />
Argument ist ungültig<br />
Fehler bei einer Ein- oder Ausgabeoperation ist aufgetreten<br />
Directory kann nicht zum Schreiben geöffnet werden<br />
Prozess hat zu viele Filedeskriptoren<br />
File hat zu viele Links<br />
Länge eines Filenamens ist zu gross<br />
System hat zu viele offene File<br />
Operation ist auf dem Device nicht möglich<br />
File oder Directory existiert nicht<br />
File kann nicht als Programm ausgeführt werden<br />
Lock ist nicht verfügbar<br />
Speicher ist nicht verfügbar<br />
Disk ist voll<br />
Funktion ist nicht implementiert<br />
Komponente des Pfadnamens ist nicht ein Directory<br />
Directory ist nicht leer<br />
File ist nicht ein Terminal<br />
Device existiert nicht<br />
Prozess hat nicht genügend Rechte, um Operation<br />
auszuführen<br />
Pipe, in die geschrieben wurde, hat keinen Leser<br />
Resultat der Funktion ist zu gross<br />
Filesystem kann nur gelesen werden<br />
Offset einer Pipe kann nicht gesetzt werden<br />
Prozess existiert nicht<br />
Link kann nicht über Filesystem-Grenzen erstellt werden<br />
Tabelle 1-1: Fehlercodes<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 4
Einführung<br />
Benutzer-Identifikation<br />
#include <br />
#include <br />
struct passwd *getpwnam(const char *name);<br />
struct passwd *getpwuid(uid_t uid);<br />
struct passwd {<br />
char *pw_name;<br />
uid_t pw_uid;<br />
gid_t pw_gid;<br />
char *pw_dir;<br />
char *pw_shell;<br />
};<br />
Jeder Benutzer hat einen eindeutigen User-Identifier (UID), der mit dem Login-Namen<br />
fest verknüpft ist und im Passwortfile /etc/passwd steht. Zudem gehört jeder Benutzer<br />
einer Benutzergruppe an, die durch einen Group-Identifier (GID) gekennzeichnet ist.<br />
Anhand dieser Identifier können den Benutzern <strong>unter</strong>schiedliche Rechte für bestimmte<br />
Operationen vergeben werden. Bei einem Zugriff eines Prozesses auf ein File werden<br />
z.B. die User- und Group-Identifier des Prozesses mit denjenigen des Files verglichen,<br />
und je nach Übereinstimmung gelten die entsprechenden Zugriffsrechte.<br />
Mittels getpwnam(3) und getpwuid(3) kann ein Eintrag des Passwortfiles gelesen<br />
werden, indem der Name des Benutzers bzw. sein Identifier übergeben wird. Die<br />
Komponenten der Struktur passwd haben folgende Bedeutungen:<br />
pw_name Benutzername<br />
pw_uid User-Identifier<br />
pw_gid Group-Identifier<br />
pw_dir Home-Directory<br />
pw_shell Login-Shell<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 5
Einführung<br />
Zeitfunktionen<br />
String<br />
formatierter String<br />
asctime(3)<br />
strftime(3)<br />
struct tm<br />
ctime(3)<br />
gmtime(3)<br />
localtime(3)<br />
time_t<br />
time(2)<br />
Die grundlegende Zeit in einem <strong>UNIX</strong>-<strong>Betriebssystem</strong> ist die Anzahl vergangener<br />
Sekunden seit dem 1. Januar 1970, 0.00 Uhr UTC (Coordinated Universal Time). Diese<br />
Anzahl Sekunden wird als Kalenderzeit bezeichnet.<br />
Der System-Call time(2) gibt die aktuelle Kalenderzeit zurück. Diese kann dann mit der<br />
Funktion gmtime(3) oder localtime(3) in die üblichen Zeiteinheiten Jahr, Monat, Tag,<br />
Stunden, Minuten, Sekunden aufgespaltet werden. Die Funktionen asctime(3) und<br />
ctime(3) erzeugen eine Standard-Stringdarstellung der aktuellen Zeit, während bei der<br />
Funktion strftime(3) ein eigenes Stringformat spezifiziert werden kann.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 6
2 Standards<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 7
Standards<br />
Existierende Standards<br />
• ANSI C<br />
• IEEE POSIX (Portable Operating System Interface)<br />
• X/Open XPG4 (Portability Guide, Issue 4)<br />
• Single <strong>UNIX</strong> Specification<br />
Der C-Standard wurde 1989 vom ANSI (American National Standards Institute)<br />
anerkannt und 1990 von der ISO (International Organization for Standardization)<br />
übernommen. Er definiert die Syntax und Semantik der Programmiersprache C sowie die<br />
Standard-C-Bibliothek.<br />
POSIX ist eine Familie von Standards, die vom IEEE (Institute of Electrical and<br />
Electronics Engineers) entwickelt werden (siehe Tabelle 2-1). Sie beschreiben die<br />
Schnittstelle zu einem <strong>Betriebssystem</strong>, nicht aber deren Implementation. Der für die<br />
<strong>Programmierung</strong> wichtigste Teilstandard ist POSIX.1 (System Application Programming<br />
Interface). Obschon POSIX für <strong>UNIX</strong>-<strong>Betriebssystem</strong>e entwickelt wurde, ist er nicht auf<br />
solche beschränkt.<br />
Der von der Vereinigung X/Open herausgegebene Portability Guide besteht aus fünf<br />
Bänden. Band 2 (System Interfaces und Headers) beschreibt die Schnittstelle eines<br />
<strong>UNIX</strong>-ähnlichen <strong>Betriebssystem</strong>s und ist eine Erweiterung von POSIX.1.<br />
Aus dem X/Open Portability Guide entstand die Single <strong>UNIX</strong> Specification (ursprünglich<br />
Spec 1170). Sie umfasst neben dem Portability Guide die System V Interface Definition,<br />
die OSF Application Environment Specification und einige weitverbreitete Interfaces aus<br />
bestehenden <strong>UNIX</strong>-Anwendungen.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 8
Standards<br />
Standard Beschreibung Status<br />
POSIX.1 <strong>Betriebssystem</strong>-Programmierschnittstelle 1990 anerkannt<br />
POSIX.1a Erweiterungen in Bearbeitung<br />
POSIX.1b Echtzeit-<strong>Programmierung</strong> 1993 anerkannt<br />
POSIX.1c Unterstützung von Threads 1995 anerkannt<br />
POSIX.1d Echtzeit-Erweiterungen in Bearbeitung<br />
POSIX.1e Sicherheit in Bearbeitung<br />
POSIX.1f Transparenter Filezugriff zurückgezogen<br />
POSIX.1g Protokoll-unabhängige Netzwerk-Schnittstelle in Bearbeitung<br />
POSIX.2 Shell und Utilities 1992 anerkannt<br />
POSIX.3 Test- und Verifikationsmethoden 1991 anerkannt<br />
POSIX.5 ADA-Anbindung von POSIX.1 1992 anerkannt<br />
POSIX.7 System-Administration in Bearbeitung<br />
POSIX.9 FORTRAN-Anbinding von POSIX.1 1992 anerkannt<br />
Tabelle 2-1: POSIX-Standards<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 9
Standards<br />
Inhalt der Standards<br />
• Systemfunktionen<br />
• Primitive Systemdatentypen<br />
• Systemkonstanten (Optionen und Grenzwerte)<br />
Die Prototypen der Systemfunktionen sind als Gruppen in verschiedenen Headerfiles<br />
enthalten (siehe Tabelle 2-2).<br />
Primitive Systemdatentypen sind Definitionen von System-abhängigen Datentypen, die in<br />
den Prototypen der Systemfunktionen verwendet werden. Sie enden auf _t undsindim<br />
Headerfile definiert (siehe Tabelle 2-3).<br />
Optionen definieren das Verhalten des Systems (z.B. das Abschneiden zu langer Pfadund<br />
Filenamen) oder geben an, ob optionale Teile eines Standards <strong>unter</strong>stützt werden<br />
(z.B. Job-Control). Die Optionen beginnen mit _POSIX_ und sind entweder im Headerfile<br />
definiert oder können mit den Funktionen sysconf(3), pathconf(3) und<br />
fpathconf(3) geprüft werden (siehe Tabelle 2-4).<br />
Bei den Grenzwerten gibt es solche, die bereits zur Kompilationszeit bekannt sind (z.B.<br />
der Maximalwert einer Integer-Zahl), und andere, die erst zur Laufzeit bestimmt werden<br />
können (z.B. die Maximallänge eines Filenamens). Die meisten Grenzwerte enthalten<br />
MAX in ihrem Namen und sind entweder im Headerfile definiert oder<br />
können mit den Funktionen sysconf(3), pathconf(3) und fpathconf(3) bestimmt<br />
werden (siehe Tabelle 2-4).<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 10
Standards<br />
Headerfile<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Beschreibung<br />
Directory-Einträge<br />
Filekontrolle<br />
Gruppenfile<br />
Passwortfile<br />
Signalhandling<br />
Terminalkontrolle<br />
symbolische Konstanten<br />
Filezeiten<br />
Fileattribute<br />
Prozesszeiten<br />
primitive Systemdatentypen<br />
Systemidentifikation<br />
Prozesskontrolle<br />
Tabelle 2-2: Headerfiles<br />
Datentyp<br />
clock_t<br />
dev_t<br />
fd_set<br />
fpos_t<br />
gid_t<br />
ino_t<br />
mode_t<br />
nlink_t<br />
off_t<br />
pid_t<br />
sigset_t<br />
size_t<br />
ssize_t<br />
time_t<br />
uid_t<br />
wchar_t<br />
Beschreibung<br />
Clock-Ticks<br />
Device-Nummer<br />
Filedeskriptormenge<br />
Fileposition<br />
Gruppen-Identifier<br />
Inode-Nummer<br />
Filetyp und -zugriffsrechte<br />
Anzahl Links<br />
Filegrösse und -offset<br />
Prozess- und Prozessgruppen-Identifier<br />
Signalmenge<br />
Anzahl Bytes (vorzeichenlos)<br />
Anzahl Bytes (mit Vorzeichen)<br />
Anzahl Sekunden<br />
User-Identifier<br />
Zeichencodes<br />
Tabelle 2-3: primitive Systemdatentypen<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 11
Standards<br />
Name<br />
_POSIX_CHOWN_RESTRICTED<br />
_POSIX_JOB_CONTROL<br />
_POSIX_NO_TRUNC<br />
_POSIX_SAVED_IDS<br />
_POSIX_VDISABLE<br />
_POSIX_VERSION<br />
ARG_MAX<br />
CHILD_MAX<br />
Clock-Ticks/Sekunde<br />
LINK_MAX<br />
MAX_CANON<br />
MAX_INPUT<br />
NAME_MAX<br />
NGROUPS_MAX<br />
OPEN_MAX<br />
PASS_MAX<br />
PATH_MAX<br />
PIPE_BUF<br />
STREAM_MAX<br />
TZNAME_MAX<br />
Beschreibung<br />
gibt an, ob der Gebrauch des chown-System-Calls<br />
eingeschränkt ist<br />
gibt an, ob Job-Control <strong>unter</strong>stützt wird<br />
gibt an, ob zu lange Pfadnamen einen Fehler<br />
erzeugen<br />
gibt an, ob Prozesse gespeicherte Set-User- und Set-<br />
Group-Identifiers haben<br />
gibt an, ob die Behandlung der speziellen Terminal-<br />
Eingabezeichen <strong>unter</strong>bunden werden kann<br />
gibt das Datum der POSIX.1-Version an<br />
maximale Argumentenlänge der exec-System-Calls<br />
maximale Anzahl Prozesse pro Benutzer<br />
Anzahl Clock-Ticks pro Sekunde<br />
maximale Anzahl Links eines Files<br />
maximale Anzahl Zeichen in einer kanonischen<br />
Eingabezeile eines Terminals<br />
maximale Anzahl Zeichen, die im Eingabepuffer eines<br />
Terminals Platz haben<br />
maximale Anzahl Zeichen in einem Filenamen<br />
maximale Anzahl zusätzlicher Gruppen-Identifiers<br />
eines Prozesses<br />
maximale Anzahl offener Files pro Prozess<br />
maximale Anzahl signifikanter Zeichen in einem<br />
Passwort<br />
maximale Anzahl Zeichen in einem relativen<br />
Pfadnamen<br />
maximale Anzahl Zeichen, die atomar in eine Pipe<br />
geschrieben werden können<br />
maximale Anzahl Ein-/Ausgabe-Streams pro Prozess<br />
maximale Anzahl Zeichen im Namen einer Zeitzone<br />
Tabelle 2-4: POSIX.1-Systemkonstanten<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 12
Standards<br />
Abfragen von Systemkonstanten<br />
#include <br />
long sysconf(int name);<br />
long pathconf(const char *path, int name);<br />
long fpathconf(int fd, int name);<br />
Mittels sysconf(3) können Systemkonstanten bestimmt werden, die eventuell erst zur<br />
Laufzeit feststehen. pathconf(3) und fpathconf(3) werden für File-abhängige<br />
Konstanten verwendet, wobei path der Pfadname des Files ist und fd ein Filedeskriptor,<br />
der das bereits geöffnete File referenziert.<br />
Die möglichen Werte von name leiten sich aus den Namen der gesuchten Konstanten ab,<br />
wobei die Argumente von sysconf(3) mit _SC_ beginnen und diejenigen von<br />
pathconf(3) oder fpathconf(3) mit _PC_.<br />
Als Funktionswert geben alle drei Funktionen den Wert der entsprechenden Konstanten<br />
zurück. Ist der Funktionswert –1, die Variable errno aber unverändert, so bedeutet dies,<br />
dass die entsprechende Option nicht definiert oder der entsprechende Grenzwert<br />
unbestimmt ist.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 13
Standards<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
void sysprint( char *, int );<br />
void pathprint( char *, int, char * );<br />
int main( void )<br />
{<br />
pathprint( "_POSIX_CHOWN_RESTRICTED", _PC_CHOWN_RESTRICTED, "." );<br />
sysprint( "_POSIX_JOB_CONTROL ", _SC_JOB_CONTROL );<br />
pathprint( "_POSIX_NO_TRUNC ", _PC_NO_TRUNC, "." );<br />
sysprint( "_POSIX_SAVED_IDS ", _SC_SAVED_IDS );<br />
pathprint( "_POSIX_VDISABLE ", _PC_VDISABLE, "/dev/tty" );<br />
sysprint( "_POSIX_VERSION ", _SC_VERSION );<br />
sysprint( "ARG_MAX ", _SC_ARG_MAX );<br />
sysprint( "CHILD_MAX ", _SC_CHILD_MAX );<br />
sysprint( "clock ticks/second ", _SC_CLK_TCK );<br />
pathprint( "LINK_MAX ", _PC_LINK_MAX, "." );<br />
pathprint( "MAX_CANON ", _PC_MAX_CANON, "/dev/tty" );<br />
pathprint( "MAX_INPUT ", _PC_MAX_INPUT, "/dev/tty" );<br />
pathprint( "NAME_MAX ", _PC_NAME_MAX, "." );<br />
sysprint( "NGROUPS_MAX ", _SC_NGROUPS_MAX );<br />
sysprint( "OPEN_MAX ", _SC_OPEN_MAX );<br />
pathprint( "PATH_MAX ", _PC_PATH_MAX, "." );<br />
pathprint( "PIPE_BUF ", _PC_PIPE_BUF, "." );<br />
sysprint( "STREAM_MAX ", _SC_STREAM_MAX );<br />
sysprint( "TZNAME_MAX ", _SC_TZNAME_MAX );<br />
}<br />
exit( EXIT_SUCCESS );<br />
void sysprint( char *name, int constant )<br />
{<br />
long val;<br />
}<br />
printf( "%s = ", name );<br />
errno = 0;<br />
if ( (val = sysconf( constant )) == -1 )<br />
if ( errno == 0 )<br />
printf( "(nicht <strong>unter</strong>stuetzt)\n" );<br />
else panic( "sysconf" )<br />
else printf( "%ld\n", val );<br />
void pathprint( char *name, int constant, char *path )<br />
{<br />
long val;<br />
}<br />
printf( "%s = ", name );<br />
errno = 0;<br />
if ( (val = pathconf( path, constant )) == -1 )<br />
if ( errno == 0 )<br />
printf( "(unbestimmt)\n" );<br />
else if ( errno == EINVAL )<br />
printf( "(nicht <strong>unter</strong>stuetzt)\n" );<br />
else panic( "pathconf" )<br />
else printf( "%ld\n", val );<br />
Programm 2-1: Ausgabe der POSIX.1-Systemkonstanten<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 14
3 Filesystem<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 15
Filesystem<br />
Aufbau eines Filesystems<br />
Boot-Block<br />
Superblock<br />
Inode-Liste<br />
Datenbereich<br />
Das traditionelle System V-Filesystem besteht aus den folgenden Komponenten:<br />
• Der Boot-Block enthält ein optionales Bootstrapping-Programm zum Laden des<br />
Kernels.<br />
• Im Superblock sind Informationen über das Filesystem abgelegt wie die Grösse<br />
des Filesystems, die Grösse der Inode-Liste, die Anzahl der freien Inodes, die<br />
Anzahl der freien Datenblöcke usw.<br />
• In der Inode-Liste gibt es für jedes existierende File einen Inode, der das File<br />
beschreibt.<br />
• Im Datenbereich befinden sich die aktuellen Daten der Files <strong>unter</strong>teilt in<br />
Datenblöcke.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 16
Filesystem<br />
Inodes<br />
Inode<br />
Datenblöcke<br />
Filetyp<br />
Zugriffsrechte<br />
Anzahl Links<br />
UID GID<br />
Grösse<br />
Zeitstempel<br />
0<br />
1<br />
10<br />
11<br />
12<br />
Indirekt-Blöcke<br />
Zu jedem File gibt es einen Inode, in dem die folgenden Informationen über das File<br />
gespeichert sind:<br />
• Filetyp<br />
• Zugriffsrechte<br />
• Anzahl Links<br />
• User- und Group-Identifier des Besitzers<br />
• Grösse<br />
• Zeitstempel<br />
Ausserdem enthält der Inode 13 Adressen, die auf die Datenblöcke des Files zeigen. Die<br />
ersten zehn Adressen zeigen direkt auf die ersten zehn Datenblöcke des Files. Die<br />
restlichen drei Adressen zeigen auf einfach, zweifach und dreifach indirekte Blöcke, die<br />
Adressen weiterer Daten- bzw. Indirekt-Blöcke enthalten.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 17
Filesystem<br />
Directories und Links<br />
Datenblöcke<br />
Directory /usr<br />
13<br />
Inode-Liste<br />
13 .<br />
2 ..<br />
208 bin<br />
512 lib<br />
Directory /usr/bin<br />
208<br />
227<br />
208<br />
13<br />
227<br />
227<br />
1189<br />
.<br />
..<br />
more<br />
page<br />
cc<br />
symbolischer Link cc<br />
1189<br />
../lib/cmplrs/cc/driver<br />
Ein Directory ist ein File, das aus Einträgen besteht, die je einen Filenamen und die<br />
dazugehörende Inode-Nummer enthalten.<br />
Ein Link ist ein Directory-Eintrag, der auf denselben Inode zeigt wie ein anderer Eintrag.<br />
Die Anzahl Links wird im Linkzähler des Inodes festgehalten. Ein File kann erst gelöscht<br />
werden, wenn der Linkzähler 0 wird. Links können nur auf Files im gleichen Filesystem<br />
erstellt werden.<br />
Ein symbolischer Link ist ein File, das den Namen eines andern Files enthält. Die meisten<br />
System-Calls, denen ein symbolischer Link als Parameter übergeben wird, folgen dem<br />
Link und referenzieren das File, auf welches der Link zeigt. Symbolische Links können<br />
auch auf Files definiert werden, die sich in andern Filesystemen befinden.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 18
Filesystem<br />
Abfragen der Attribute eines Files<br />
#include <br />
#include <br />
int stat(const char *path, struct stat *buf);<br />
int fstat(int fd, struct stat *buf);<br />
struct stat {<br />
mode_t st_mode;<br />
ino_t st_ino;<br />
dev_t st_dev;<br />
nlink_t st_nlink;<br />
uid_t st_uid;<br />
gid_t st_gid;<br />
off_t st_size;<br />
time_t st_atime;<br />
time_t st_mtime;<br />
time_t st_ctime;<br />
};<br />
Mittels stat(2) können die Attribute eines Files abgefragt werden. path ist der<br />
Pfadname des Files und buf zeigt auf eine Struktur, in der die Attribute gespeichert<br />
werden:<br />
st_mode Filetyp und Zugriffsrechte<br />
st_ino Inode-Nummer<br />
st_dev Nummer des Devices, auf dem sich das File befindet<br />
st_nlink Anzahl Links auf das File<br />
st_uid User-Identifier des Besitzers<br />
st_gid Group-Identifier des Besitzers<br />
st_size Grösse des Files in Bytes<br />
st_atime Zeit des letzten Zugriffs<br />
st_mtime Zeit der letzten Datenänderung<br />
st_ctime Zeit der letzten Statusänderung<br />
Mittels fstat(2) können die Attribute eines Files abgefragt werden, das auf dem<br />
Deskriptor fd geöffnet wurde.<br />
Mit den folgenden Makros kann aus der Komponente st_mode der Filetyp bestimmt<br />
werden:<br />
S_ISREG() reguläres File<br />
S_ISDIR() Directory<br />
S_ISCHR() Character-Special-File<br />
S_ISBLK() Block-Special-File<br />
S_ISFIFO() FIFO (named Pipe)<br />
(Daneben gibt es noch die Filetypen Socket und symbolischer Link, für die aber in<br />
POSIX.1 keine Makros definiert sind.)<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 19
Filesystem<br />
Durch bitweise Und-Verknüpfung von st_mode mit den folgenden Konstanten kann<br />
geprüft werden, welche Zugriffsrechte für ein File definiert sind:<br />
S_IRUSR Leserecht für den Besitzer<br />
S_IWUSR Schreibrecht für den Besitzer<br />
S_IXUSR Ausführungsrecht für den Besitzer<br />
S_IRGRP Leserecht für die Gruppe<br />
S_IWGRP Schreibrecht für die Gruppe<br />
S_IXGRP Ausführungsrecht für die Gruppe<br />
S_IROTH Leserecht für andere<br />
S_IWOTH Schreibrecht für andere<br />
S_IXOTH Ausführungsrecht für andere<br />
Die bitweise Und-Verknüpfung von st_mode mit den Konstanten S_ISUID und S_ISGID<br />
gibt schliesslich an, ob das Set-User-ID-Bit bzw. das Set-Group-ID-Bit des Files gesetzt<br />
sind.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 20
Filesystem<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
struct stat buf;<br />
if ( argc != 2 ) {<br />
fprintf( stderr, "Verwendung: %s file\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( stat( argv[1], &buf ) == -1 )<br />
panic( "stat" )<br />
printf( "%-24s: ", "Filetyp" );<br />
if ( S_ISREG( buf.st_mode ) ) printf( "regulaeres File" );<br />
else if ( S_ISDIR( buf.st_mode ) ) printf( "Directory" );<br />
else if ( S_ISCHR( buf.st_mode ) ) printf( "Character-Special-File" );<br />
else if ( S_ISBLK( buf.st_mode ) ) printf( "Block-Special-File" );<br />
else if ( S_ISFIFO( buf.st_mode ) ) printf( "FIFO" );<br />
else printf( "unbekannt" );<br />
printf( "\n%-24s: ", "Zugriffsrechte Besitzer" );<br />
if ( buf.st_mode & S_IRUSR ) printf( "lesen " );<br />
if ( buf.st_mode & S_IWUSR ) printf( "schreiben " );<br />
if ( buf.st_mode & S_IXUSR ) printf( "ausfuehren" );<br />
printf( "\n%-24s: ", "Zugriffsrechte Gruppe" );<br />
if ( buf.st_mode & S_IRGRP ) printf( "lesen " );<br />
if ( buf.st_mode & S_IWGRP ) printf( "schreiben " );<br />
if ( buf.st_mode & S_IXGRP ) printf( "ausfuehren" );<br />
printf( "\n%-24s: ", "Zugriffsrechte Andere" );<br />
if ( buf.st_mode & S_IROTH ) printf( "lesen " );<br />
if ( buf.st_mode & S_IWOTH ) printf( "schreiben " );<br />
if ( buf.st_mode & S_IXOTH ) printf( "ausfuehren" );<br />
printf( "\n%-24s: ", "Zusaetzliche Bits" );<br />
if ( buf.st_mode & S_ISUID ) printf( "Set-User-ID " );<br />
if ( buf.st_mode & S_ISGID ) printf( "Set-Group-ID" );<br />
printf( "\n%-24s: %ld\n", "Inode-Nummer", buf.st_ino );<br />
printf( "%-24s: %ld\n", "Device-Nummer", buf.st_dev );<br />
printf( "%-24s: %ld\n", "Anzahl Links", buf.st_nlink );<br />
printf( "%-24s: %ld (%ld)\n", "Besitzer", buf.st_uid, buf.st_gid );<br />
printf( "%-24s: %ld\n", "Groesse", buf.st_size );<br />
printf( "%-24s: %s", "letzter Zugriff", ctime( &buf.st_atime ) );<br />
printf( "%-24s: %s", "letzte Datenaenderung", ctime( &buf.st_mtime ) );<br />
printf( "%-24s: %s", "letzte Statusaenderung", ctime( &buf.st_ctime ) );<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 3-1: Ausgabe der Attribute eines Files<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 21
Filesystem<br />
Lesen eines Directory<br />
#include <br />
#include <br />
DIR *opendir(const char *dirname);<br />
struct dirent *readdir(DIR *dir);<br />
void rewinddir(DIR *dir);<br />
int closedir(DIR *dir);<br />
struct dirent {<br />
char d_name[];<br />
};<br />
Da das aktuelle Format eines Directory-Files implementationsabhängig ist, wurden in<br />
POSIX.1 spezielle Funktionen zum Lesen von Directories definiert.<br />
opendir(3) öffnet ein Directory und gibt einen Pointer auf die interne Struktur DIR<br />
zurück, die einen Directory-Stream darstellt. Der Stream wird auf den ersten Directory-<br />
Eintrag positioniert.<br />
readdir(3) liest den nächsten Directory-Eintrag und gibt einen Pointer auf die Struktur<br />
dirent zurück oder NULL, falls das Ende des Directory erreicht ist. Die Komponente<br />
d_name der Struktur dirent enthält den Filenamen des gelesenen Eintrags.<br />
rewinddir(3) setzt die Position des Directory-Streams auf den ersten Eintrag des<br />
Directory zurück.<br />
closedir(3) schliesst den Directory-Stream.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 22
Filesystem<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
DIR<br />
*dir;<br />
struct dirent *direntry;<br />
if ( argc != 2 ) {<br />
fprintf( stderr, "Verwendung: %s directory\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( (dir = opendir( argv[1] )) == NULL )<br />
panic( "opendir" )<br />
while ( 1 ) {<br />
errno = 0;<br />
if ( (direntry = readdir( dir )) == NULL )<br />
if ( errno != 0 ) panic( "readdir" )<br />
else break;<br />
printf( "%s\n", direntry->d_name );<br />
}<br />
if ( closedir( dir ) == -1 )<br />
panic( "closedir" )<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 3-2: Lesen eines Directory<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 23
Filesystem<br />
Name<br />
access(2)<br />
chdir(2)<br />
chmod(2)<br />
chown(2)<br />
getcwd(3)<br />
link(2)<br />
mkdir(2)<br />
rename(2)<br />
rmdir(2)<br />
umask(2)<br />
unlink(2)<br />
utime(2)<br />
Beschreibung<br />
testet den Zugriff auf ein File<br />
Wechselt das aktuelle Directory<br />
Ändert die Zugriffsrechte eines Files<br />
Ändert den Besitzer eines Files<br />
gibt das aktuelle Directory zurück<br />
Erzeugt einen Link auf ein File<br />
Erzeugt ein neues Directory<br />
Ändert den Namen eines Files<br />
Entfernt ein Directory<br />
setzt die Zugriffsmaske für die Erzeugung von Files<br />
Entfernt einen Link auf ein File und löscht das File, falls der<br />
Linkzähler 0 wird<br />
setzt die letzte Zugriffs- und Änderungszeit eines Files<br />
Tabelle 3-1: Weitere System-Calls zur Handhabung von Files<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 24
4 Ein- und Ausgabe<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 25
Ein- und Ausgabe<br />
Filedeskriptoren<br />
Filedeskriptor-<br />
Tabellen<br />
0<br />
1<br />
2<br />
3<br />
Flags<br />
Flags<br />
Flags<br />
Filetabelle<br />
Status-Flags<br />
File-Offset<br />
Status-Flags<br />
File-Offset<br />
Vnode-Liste<br />
0<br />
1<br />
2<br />
Flags<br />
Status-Flags<br />
File-Offset<br />
0<br />
1<br />
2<br />
Flags<br />
Der Kernel referenziert alle offenen Files durch sogenannte Filedeskriptoren und<br />
<strong>unter</strong>hält die folgenden Datenstrukturen:<br />
• pro Prozess eine Tabelle der offenen Filedeskriptoren, die in der User-Struktur<br />
des Prozesses abgelegt ist<br />
• eine systemweite Tabelle aller offenen Files<br />
• für jedes offene File einen Vnode, der Informationen über das File enthält und<br />
Zeiger auf die Filesystem-abhängigen Funktionen, die auf dem File operieren<br />
Durch diese Anordnung ist es möglich, dass mehrere Prozesse gleichzeitig auf dasselbe<br />
File zugreifen. Dies geschieht entweder über verschiedene Einträge in der Filetabelle,<br />
wenn z.B. zwei unabhängige Prozesse dasselbe File öffnen, oder über denselben<br />
Eintrag, wenn z.B. ein Prozess seine offenen Filedeskriptoren an einen Sohnprozess<br />
vererbt. Zudem können auch verschiedene Filedeskriptoren desselben Prozesses auf<br />
denselben Eintrag in der Filetabelle zeigen.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 26
Ein- und Ausgabe<br />
Öffnen und Erzeugen eines Files<br />
#include <br />
#include <br />
#include <br />
int open(const char *path, int flags, ...);<br />
int creat(const char *path, mode_t mode);<br />
open(2) öffnet ein File und gibt einen Filedeskriptor zurück, mit dem das File referenziert<br />
wird. path ist dabei der Pfadname des zu öffnenden Files. Falls das File noch nicht<br />
existiert, kann es mittels open(2) erzeugt werden.<br />
Im Parameter flags können verschiedene Optionen durch bitweise Oder-Verknüpfung<br />
der folgenden Konstanten spezifiziert werden:<br />
O_RDONLY öffnet das File nur zum Lesen<br />
O_WRONLY öffnet das File nur zum Schreiben<br />
O_RDWR öffnet das File zum Lesen und Schreiben<br />
O_APPEND<br />
O_CREAT<br />
O_EXCL<br />
O_NOCTTY<br />
O_NONBLOCK<br />
setzt den File-Offset vor jedem Schreiben an das Ende des<br />
Files<br />
erzeugt das File, falls es noch nicht existiert<br />
erzeugt einen Fehler (errno=EEXIST), falls O_CREAT<br />
spezifiziert wurde und das File bereits existiert<br />
falls path ein Terminal-Device referenziert, wird das Device<br />
nicht als Kontroll-Terminal des Prozesses alloziert<br />
ein auf das File zugreifender Prozess wird nicht blockiert, wenn<br />
das File nicht bereit ist, sondern kehrt mit einem Fehler<br />
(errno=EAGAIN) zurück<br />
O_TRUNC setzt die Grösse des Files auf 0, falls das File zum Schreiben<br />
geöffnet wird<br />
Von den ersten drei Konstanten muss genau eine angegeben werden, die restlichen sind<br />
optional.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 27
Ein- und Ausgabe<br />
Der dritte Parameter ist vom Typ mode_t und wird nur gebraucht, wenn ein neues File<br />
erzeugt wird. Er enthält die Zugriffsrechte des neu zu erzeugenden Files, wobei diese<br />
noch mit der Umaske des Prozesses verknüpft werden.<br />
creat(2) erzeugt ein neues File und öffnet es zum Schreiben. Ein Aufruf von creat(2)<br />
ist äquivalent zu einem Aufruf von open(2) mit den Flags O_WRONLY, O_CREAT und<br />
O_TRUNC.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 28
Ein- und Ausgabe<br />
Schliessen und Löschen eines Files<br />
#include <br />
int close(int fd);<br />
int unlink(const char *path);<br />
close(2) schliesst das offene File, das durch den Deskriptor fd referenziert wird.<br />
unlink(2) entfernt den Directory-Eintrag des Files path und dekrementiert den Link-<br />
Zähler des zugehörigen Inodes. Wird der Link-Zähler 0 und hat kein Prozess mehr das<br />
File geöffnet, so wird der Inhalt des Files gelöscht.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 29
Ein- und Ausgabe<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int fd;<br />
if ( argc != 2 ) {<br />
fprintf( stderr, "Verwendung: %s file\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( (fd = open( argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR )) == -1 )<br />
panic( "open" )<br />
printf( "File erzeugt\n" );<br />
sleep( 10 );<br />
if ( unlink( argv[1] ) == -1 )<br />
panic( "unlink" )<br />
printf( "File geloescht\n" );<br />
sleep( 10 );<br />
if ( close( fd ) == -1 )<br />
panic( "close" )<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 4-1: Verwendung eines temporären Files<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 30
Ein- und Ausgabe<br />
Lesen und Schreiben eines Files<br />
#include <br />
ssize_t read(int fd, void *buf, size_t nbytes);<br />
ssize_t write(int fd, const void *buf, size_t nbytes);<br />
Mittels read(2) können Daten aus einem offenen File gelesen werden. fd ist der<br />
Deskriptor des Files, buf zeigt auf einen Speicherbereich, in den die gelesenen Daten<br />
abgelegt werden, und nbytes ist die maximale Anzahl zu lesender Bytes. Als<br />
Funktionswert gibt read(2) die Anzahl gelesener Bytes zurück. Diese kann kleiner als<br />
nbytes sein, z.B. wenn das Ende des Files erreicht wird. In diesem Fall kehrt read(2)<br />
beim nächsten Aufruf mit dem Funktionswert 0 zurück.<br />
Mittels write(2) können Daten in ein offenes File geschrieben werden. fd ist der<br />
Deskriptor des Files, buf zeigt auf einen Speicherbereich, der die zu schreibenden Daten<br />
enthält, und nbytes ist die Anzahl zu schreibender Bytes. Als Funktionswert gibt<br />
write(2) die Anzahl geschriebener Bytes zurück, die im Normalfall mit nbytes<br />
übereinstimmt.<br />
Das Lesen und Schreiben beginnt jeweils beim aktuellen File-Offset, und nach der<br />
Ausführung wird der File-Offset um die Anzahl gelesener bzw. geschriebener Bytes<br />
erhöht.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 31
Ein- und Ausgabe<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define BUFSIZE 1024<br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int fd1, fd2;<br />
char buf[BUFSIZE];<br />
ssize_t n;<br />
if ( argc != 3 ) {<br />
fprintf( stderr, "Verwendung: %s sourcefile destfile\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( (fd1 = open( argv[1], O_RDONLY )) == -1 )<br />
panic( "open" )<br />
if ( (fd2 = creat( argv[2], S_IRUSR | S_IWUSR )) == -1 )<br />
panic( "creat" )<br />
while ( 1 ) {<br />
if ( (n = read( fd1, buf, BUFSIZE )) == -1 )<br />
panic( "read" )<br />
if ( n == 0 ) break;<br />
if ( write( fd2, buf, n ) == -1 )<br />
panic( "write" )<br />
}<br />
if ( close( fd1 ) == -1 )<br />
panic( "close" )<br />
if ( close( fd2 ) == -1 )<br />
panic( "close" )<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 4-2: Kopieren eines Files<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 32
Ein- und Ausgabe<br />
Positionieren eines Files<br />
#include <br />
#include <br />
off_t lseek(int fd, off_t offset, int whence);<br />
Mittels lseek(2) kann der aktuelle Offset eines offenen Files explizit gesetzt und damit<br />
das File zum Lesen oder Schreiben positioniert werden. fd ist der Deskriptor des Files<br />
und offset ein relativer Offset, der abhängig vom Parameter whence wie folgt<br />
interpretiert wird:<br />
SEEK_SET setzt den File-Offset auf offset<br />
SEEK_CUR erhöht den File-Offset um offset<br />
SEEK_END setzt den File-Offset auf die Filegrösse plus offset<br />
Als Funktionswert wird der neue File-Offset zurückgegeben. Ist dieser grösser als die<br />
aktuelle Filegrösse, so entsteht bei einem nachfolgenden Schreiben mittels write(2) ein<br />
Loch im File.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 33
Ein- und Ausgabe<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int fd;<br />
if ( argc < 4 ) {<br />
fprintf( stderr, "Verwendung: %s file position text\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( (fd = open( argv[1], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR )) == -1 )<br />
panic( "open" )<br />
if ( lseek( fd, atoi( argv[2] ), SEEK_SET ) == -1 )<br />
panic( "lseek" )<br />
if ( write( fd, argv[3], strlen( argv[3] ) ) == -1 )<br />
panic( "write" )<br />
if ( close( fd ) == -1 )<br />
panic( "close" )<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 4-3: Schreiben an eine beliebige Stelle eines Files<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 34
Ein- und Ausgabe<br />
Duplizieren eines Filedeskriptors<br />
#include <br />
int dup(int fd);<br />
int dup2(int fd, int fd2);<br />
dup(2) dupliziert den offenen Filedeskriptor fd auf den kleinsten noch frei verfügbaren<br />
Filedeskriptor und gibt diesen als Funktionswert zurück. Der neue Filedeskriptor zeigt auf<br />
denselben Eintrag der Filetabelle wie fd und referenziert somit dasselbe File.<br />
dup2(2) funktioniert gleich wie dup(2), ausser dass der Wert des neuen Filedeskriptors<br />
durch den Parameter fd2 spezifiziert wird. Ist der Filedeskriptor fd2 bereits vor dem<br />
Aufruf von dup2(2) offen, so wird er zuvor geschlossen, es sei denn fd und fd2 stimmen<br />
überein.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 35
Ein- und Ausgabe<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int fd;<br />
if ( argc < 3 ) {<br />
fprintf( stderr, "Verwendung: %s file program [args]\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( (fd = creat( argv[1], S_IRUSR | S_IWUSR )) == -1 )<br />
panic( "creat" )<br />
if ( dup2( fd, STDOUT_FILENO ) == -1 )<br />
panic( "dup2" )<br />
if ( close( fd ) == -1 )<br />
panic( "close" )<br />
}<br />
if ( execvp( argv[2], &argv[2] ) == -1 )<br />
panic( "execvp" )<br />
Programm 4-4: Umleiten der Ausgabe eines Programms in ein File<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 36
Ein- und Ausgabe<br />
Filekontrolle<br />
#include <br />
#include <br />
#include <br />
int fcntl(int fd, int cmd, int arg);<br />
Mittels fcntl(2) können die Eigenschaften eines bereits geöffneten Files abgefragt oder<br />
verändert werden. fd ist der Deskriptor des Files und cmd einer der folgenden Befehle:<br />
F_GETFD gibt die Filedeskriptor-Flags zurück<br />
F_SETFD setzt das Filedeskriptor-Flag FD_CLOEXEC auf den Wert von arg<br />
F_GETFL gibt die Filestatus-Flags zurück<br />
F_SETFL<br />
setzt die Filestatus-Flags O_APPEND und O_NONBLOCK auf die<br />
Werte, wie sie in arg spezifiziert sind<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 37
Ein- und Ausgabe<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 38
5 Prozesse<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 39
Prozesse<br />
Prozesszustände<br />
fork(2)<br />
Created<br />
Ready<br />
Scheduler<br />
User<br />
Running<br />
System-Call<br />
Kernel<br />
Running<br />
I/O<br />
Asleep<br />
_exit (2)<br />
Zombie<br />
Ein Prozess ist die Ausführung eines Programms. Während seiner Lebensdauer kann ein<br />
Prozess verschiedene Zustände durchlaufen. Im <strong>UNIX</strong>-System V werden folgende<br />
Prozesszustände <strong>unter</strong>schieden:<br />
Created Der Prozess existiert bereits, hat aber noch nicht alle<br />
benötigten System-Ressourcen.<br />
Ready<br />
Der Prozess ist lauffähig und wartet darauf, dass ihm der<br />
Scheduler der Prozessor zuteilt.<br />
User-Running Der Prozess wird ausgeführt und befindet sich im User-Modus.<br />
Kernel-Running Der Prozess führt einen System-Call aus und befindet sich im<br />
Kernel-Modus.<br />
Asleep<br />
Der Prozess ist blockiert, weil er z.B. auf eine Ein- oder<br />
Ausgabe wartet.<br />
Zombie<br />
Der Prozess existiert nicht mehr, es ist aber noch ein Eintrag<br />
mit dem Terminierungs-Status vorhanden.<br />
Die Prozesse im Zustand Ready werden in einer Queue verwaltet, in welcher der<br />
Scheduler jeweils den Prozess mit der höchsten Priorität zur Ausführung auswählt. Im<br />
Gegensatz zum Zustand User-Running kann ein Prozess im Zustand Kernel-Running<br />
nicht vom Scheduler <strong>unter</strong>brochen werden.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 40
Prozesse<br />
Prozesskontext<br />
Software-<br />
Kontext<br />
Hardware-<br />
Kontext<br />
Text<br />
(Code)<br />
Memory<br />
Mapping<br />
Wenn ein Prozess ausgeführt wird, so läuft er in einem bestimmten Kontext ab. Der<br />
Kontext eines Prozesses besteht aus den folgenden Komponenten:<br />
• der Software-Kontext umfasst die Prozess-spezifischen Datenstrukturen des<br />
Kernels:<br />
− die Speicher-residente Prozess-Struktur, die u.a. den Zustand des<br />
Prozesses, seine Identifier und Scheduling-Information enthält<br />
− die User-Struktur, die weitere Attribute des Prozesses wie die offenen<br />
Filedeskriptoren enthält, aber wenn nötig auf den Sekundärspeicher<br />
ausgelagert wird<br />
− der Kernel-Stack, der gebraucht wird, wenn der Prozess einen System-<br />
Call aufruft<br />
• der Hardware-Kontext ist durch den Inhalt der verschiedenen Hardware-<br />
Register wie Programm-Co<strong>unter</strong>, Prozessorstatus-Register, Stack-Pointer usw.<br />
bestimmt<br />
• das Memory-Mapping definiert anhand der Page-Tabellen die Abbildung des<br />
virtuellen Adressraums des Prozesses in physikalische Speicheradressen<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 41
Prozesse<br />
Virtueller Adressraum<br />
Befehlszeilenargumente<br />
Umgebungsvariabeln<br />
Stack<br />
Heap<br />
Daten<br />
Text<br />
Der virtuelle Adressraum eines Prozesses ist die Menge der Speicheradressen, die der<br />
Prozess referenziert. Er kann in folgende Teile <strong>unter</strong>teilt werden:<br />
• das Text-Segment, das die ausführbaren Maschinen-Instruktionen enthält<br />
• das Daten-Segment, in dem sich die globalen Variablen befinden, die entweder<br />
bereits vom Compiler oder erst zur Laufzeit initialisiert werden<br />
• der Heap, auf dem dynamisch allozierter Speicherplatz angelegt wird<br />
• der Stack, auf dem lokale Variablen und Informationen für die Rückkehr aus<br />
Funktionen gespeichert werden<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 42
Prozesse<br />
Argumente und Umgebungsvariablen<br />
extern char **environ;<br />
int main(int argc, char *argv[])<br />
{<br />
...<br />
}<br />
Wenn ein Programm ausgeführt wird, können ihm Befehlszeilenargumente übergeben<br />
werden, die dann als Parameter der main-Funktion zur Verfügung stehen. argc ist die<br />
Anzahl der Argumente und argv ein Array von Pointern, die auf die Argumente zeigen.<br />
Mit der Funktion getopt(3) können die Programmoptionen <strong>unter</strong> den Argumenten<br />
einfach verarbeitet werden.<br />
Zudem wird jedem Programm über die globale Variable environ eine Liste von<br />
Umgebungsvariablen zugänglich gemacht. environ ist ein Pointer auf einen Array von<br />
Pointern, die auf Strings der Form Name=Wert zeigen. Das letzte Element des Arrays ist<br />
immer ein Nullpointer. Mit den Funktionen getenv(3) und putenv(3) kann der Wert<br />
einer einzelnen Umgebungsvariablen abgefragt bzw. gesetzt werden.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 43
Prozesse<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
extern char<br />
**environ;<br />
int main( int argc, char *argv[] )<br />
{<br />
int i;<br />
printf( "Befehlszeilenargumente:\n" );<br />
for ( i = 0; i < argc; i++ )<br />
printf( "%s\n", argv[i] );<br />
printf( "\nUmgebungsvariablen:\n" );<br />
for ( i = 0; environ[i] != NULL; i++ )<br />
printf( "%s\n", environ[i] );<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 5-1: Ausgabe der Befehlszeilenargumente und Umgebungsvariablen<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 44
Prozesse<br />
Identifier eines Prozesses<br />
#include <br />
#include <br />
pid_t getpid(void);<br />
pid_t getppid(void);<br />
uid_t getuid(void);<br />
gid_t getgid(void);<br />
uid_t geteuid(void);<br />
gid_t getegid(void);<br />
Jeder Prozess hat mehrere Identifier, die mit den folgenden System-Calls abgefragt<br />
werden können:<br />
• getpid(2) gibt den Prozess-Identifier eines Prozesses zurück, der den Prozess<br />
eindeutig identifiziert. getppid(2) gibt den Prozess-Identifier des<br />
Vaterprozesses zurück.<br />
• getuid(2) und getgid(2) geben die realen User- und Group-Identifier eines<br />
Prozesses zurück. Sie identifizieren den Benutzer, <strong>unter</strong> welchem der Prozess<br />
läuft.<br />
• geteuid(2) und getegid(2) geben die effektiven User- und Group-Identifier<br />
eines Prozesses zurück, welche die Zugriffsrechte des Prozesses auf Files<br />
bestimmen. Sie stimmen mit den realen Identifiern überein, ausser wenn das<br />
Programmfile das Set-User-ID-Bit bzw. das Set-Group-ID-Bit gesetzt hat.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 45
Prozesse<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
int main( void )<br />
{<br />
printf( "Prozess-Identifier : %d\n", getpid() );<br />
printf( "Vaterprozess-Identifier : %d\n", getppid() );<br />
printf( "realer User-Identifier : %d\n", getuid() );<br />
printf( "realer Group-Identifier : %d\n", getgid() );<br />
printf( "effektiver User-Identifier : %d\n", geteuid() );<br />
printf( "effektiver Group-Identifier : %d\n", getegid() );<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 5-2: Ausgabe der Identifier eines Prozesses<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 46
Prozesse<br />
Erzeugen eines Prozesses<br />
#include <br />
#include <br />
pid_t fork(void);<br />
Mittels fork(2) wird ein neuer Prozess erzeugt. Der aufrufende Prozess heisst dann<br />
Vaterprozess, der neu erzeugte Prozess Sohnprozess. Der Sohnprozess erhält eine<br />
Kopie des Adressraums des Vaterprozesses und hat damit denselben Code und<br />
dieselben Daten. Auch die meisten Attribute des Software-Kontexts werden vom<br />
Vaterprozess auf den Sohnprozess vererbt (siehe Tabelle 5-1).<br />
Beide Prozesse kehren aus dem System-Call fork(2) zurück und führen die<br />
nachfolgenden Instruktionen aus. Der einzige Unterschied besteht im Funktionswert von<br />
fork(2): Der Vaterprozess erhält den Prozess-Identifier des erzeugten Sohnprozesses<br />
zurück, der Sohnprozess den Wert 0. Welcher Prozess schliesslich zuerst zur<br />
Ausführung kommt, hängt vom Scheduling-Algorithmus ab.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 47
Prozesse<br />
Attribut<br />
Vererbung<br />
bei<br />
fork(2)<br />
Erhaltung<br />
bei<br />
exec(2)<br />
Prozess-Identifier nein ja<br />
Vaterprozess-Identifier nein ja<br />
reale User-/Group-Identifier ja ja<br />
effektive User-/Group-Identifier ja eventuell<br />
Prozessgruppen-Identifier ja ja<br />
Session-Identifier ja ja<br />
Kontroll-Terminal ja ja<br />
Arbeitsverzeichnis ja ja<br />
Root-Directory ja ja<br />
offene Filedeskriptoren ja eventuell<br />
Maske der Zugriffsrechte ja ja<br />
File-Locks nein ja<br />
Signalmaske ja ja<br />
Signaldisposition ja zum Teil<br />
hängige Signale nein ja<br />
hängige Alarme nein ja<br />
Umgebungsvariablen ja eventuell<br />
Ressourcen-Grenzwerte ja ja<br />
Prozesszeiten nein ja<br />
Tabelle 5-1: Vererbung der Prozessattribute<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 48
Prozesse<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( void )<br />
{<br />
pid_t pid;<br />
if ( (pid = fork()) == -1 )<br />
panic( "fork" )<br />
printf( "Prozess %d: Rueckgabewert von fork() = %d\n", getpid(), pid );<br />
sleep( 1 );<br />
if ( pid == 0 )<br />
printf( "Sohnprozess: Identifier = %d\n", getpid() );<br />
else printf( "Vaterprozess: Identifier = %d\n", getpid() );<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 5-3: Erzeugen eines Sohnprozesses<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 49
Prozesse<br />
Ausführen eines Programms<br />
#include <br />
int execv(const char *path, char *const argv[]);<br />
int execl(const char *path, const char *arg,...);<br />
int execve(const char *path, char *const argv[],<br />
char *const envp[]);<br />
int execle(const char *path, const char *arg,...);<br />
int execvp(const char *file, char *const argv[]);<br />
int execlp(const char *file, const char *arg,...);<br />
Mit den exec-System-Calls kann innerhalb eines Prozesses ein neues Programm<br />
gestartet werden. Der Adressraum des Prozesses wird dabei mit dem neuen Programm<br />
überschrieben, der Software-Kontext dagegen bleibt weitgehend erhalten (siehe Tabelle<br />
5-1). Die Ausführung des neuen Programms beginnt mit der main-Funktion.<br />
path ist der Pfad- und file der Filename des auszuführenden Programms. Dabei<br />
suchen execvp(2) und execlp(2) das Programm in allen Directories, die in der<br />
Umgebungsvariable PATH aufgeführt sind.<br />
argv ist ein Array von Pointern auf Strings, die dem neuen Programm als Befehlszeilenargumente<br />
übergeben werden. Alternativ können bei execl(2), execle(2) und<br />
execlp(2) die Argumente auch einzeln aufgezählt werden. In beiden Fällen muss das<br />
letzte Argument ein Nullpointer sein.<br />
envp ist ein Array von Pointern auf Umgebungsvariablen, der bei execve(2) und<br />
execle(2) zusätzlich übergeben wird. Auch hier muss das letzte Variable ein Nullpointer<br />
sein.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 50
Prozesse<br />
Terminieren eines Prozesses<br />
#include <br />
#include <br />
void exit(int status);<br />
void _exit(int status);<br />
Es gibt fünf Möglichkeiten, einen Prozess zu terminieren:<br />
1. Ausführen eines return-Statements in der main-Funktion, was äquivalent<br />
zum Aufruf von exit(3) ist<br />
2. Aufruf der Funktion exit(3), die zunächst die mittels atexit(3) installierten<br />
Exit-Handler ausführt, dann alle Ein- und Ausgabe-Streams schliesst und<br />
schliesslich _exit(2) aufruft<br />
3. Aufruf des System-Calls _exit(2), der alle offenen Filedeskriptoren schliesst<br />
und die nicht mehr benötigten System-Ressourcen freigibt<br />
4. Aufruf der Funktion abort(3), die das Signal SIGABRT erzeugt<br />
5. Empfangen eines Signals, das vom Prozess selbst, von einem andern Prozess<br />
oder vom Kernel erzeugt wurde<br />
Bei einer normalen Terminierung mittels exit(3) oder _exit(2) wird dem Vaterprozess<br />
über das Argument status ein Exit-Status zurückgegeben. Bei einer anormalen<br />
Terminierung erzeugt der Kernel einen Terminierungs-Status, der den Grund für die<br />
Terminierung angibt.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 51
Prozesse<br />
Warten auf einen Prozess<br />
#include <br />
#include <br />
pid_t wait(int *statloc);<br />
pid_t waitpid(pid_t pid, int *statloc, int options);<br />
Mittels wait(2) kann ein Prozess auf das Terminieren irgendeines Sohnprozesses<br />
warten. Hat ein Sohnprozess bereits terminiert, so kehrt wait(2) sofort zurück,<br />
andernfalls wird der aufrufende Prozess blockiert, bis der erste Sohnprozess terminiert.<br />
Als Funktionswert gibt wait(2) den Prozess-Identifier des terminierten Sohnprozesses<br />
zurück, und speichert im Parameter statloc dessen Terminierungs-Status, falls<br />
statloc nicht ein Nullpointer ist. Existiert kein Sohnprozess, so kehrt wait(2) mit einem<br />
Fehler (errno=ECHILD) zurück.<br />
waitpid(2) funktioniert gleich wie wait(2), ausser dass der aufrufende Prozess auf<br />
einen spezifischen Sohnprozess warten kann. Der Parameter pid hat dabei folgende<br />
Bedeutung:<br />
pid = –1 wartet auf irgendeinen Sohnprozess<br />
pid > 0 wartet auf den Sohnprozess mit Identifier pid<br />
pid = 0 wartet auf irgendeinen Sohnprozess in derselben Prozessgruppe<br />
pid < –1<br />
wartet auf irgendeinen Sohnprozess in der Prozessgruppe mit dem<br />
Absolutwert von pid als Identifier<br />
Im Parameter options können durch bitweise Oder-Verknüpfung der folgenden<br />
Konstanten zusätzliche Optionen definiert werden:<br />
WNOHANG<br />
WUNTRACED<br />
kehrt sofort zurück, auch wenn der spezifizierte Sohnprozess<br />
noch nicht terminiert hat; in diesem Fall ist der Funktionswert 0<br />
gibt auch den Status von gestoppten Sohnprozessen zurück<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 52
Prozesse<br />
Mit den folgenden Makros kann dann der Terminierungs-Status weiter <strong>unter</strong>sucht<br />
werden:<br />
WIFEXITED()<br />
WIFSIGNALED()<br />
WIFSTOPPED()<br />
ist wahr, falls der Prozess normal terminierte;<br />
WEXITSTATUS() gibt den Exit-Status zurück<br />
ist wahr, falls der Prozess durch ein Signal terminiert wurde;<br />
WTERMSIG() gibt die Signalnummer zurück<br />
ist wahr, falls der Prozess durch ein Signal gestoppt wurde;<br />
WSTOPSIG() gibt die Signalnummer zurück<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 53
Prozesse<br />
Das folgende Programm führt in einem Sohnprozess ein Programm aus. Der<br />
Vaterprozess wartet auf die Terminierung des Sohnprozesses und gibt den<br />
Terminierungs-Status aus.<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
pid_t pid;<br />
int status;<br />
if ( argc < 2 ) {<br />
fprintf( stderr, "Verwendung: %s program [args]\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( (pid = fork()) == -1 )<br />
panic( "fork" )<br />
if ( pid == 0 ) {<br />
printf( "Programm %s wird gestartet\n", argv[1] );<br />
if ( execvp( argv[1], &argv[1] ) == -1 )<br />
panic( "execvp" )<br />
}<br />
if ( waitpid( pid, &status, WUNTRACED ) == -1 )<br />
panic( "wait" )<br />
if ( WIFEXITED( status ) )<br />
printf( "Programm hat normal terminiert (Exit-Status = %d)\n",<br />
WEXITSTATUS( status ) );<br />
else if ( WIFSIGNALED( status ) )<br />
printf( "Programm hat anormal terminiert (Signalnummer = %d)\n",<br />
WTERMSIG( status ) );<br />
else if ( WIFSTOPPED( status ) )<br />
printf( "Programm wurde gestoppt (Signalnummer = %d)\n",<br />
WSTOPSIG( status ) );<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 5-4: Ausführen eines Programms in einem Sohnprozess<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 54
6 Job-Control<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 55
Job-Control<br />
Prozessgruppen und Sessions<br />
Session<br />
Hintergrund-<br />
Prozessgruppe<br />
Kontrollprozess<br />
ksh<br />
Hintergrund-<br />
Prozessgruppe<br />
pr<br />
lpr<br />
Vordergrund-<br />
Prozessgruppe<br />
cat<br />
grep<br />
sort<br />
Kontroll-<br />
Terminal<br />
Terminal-Signale<br />
Terminal-<br />
Ein-/Ausgabe<br />
Job-Control erlaubt dem Benutzer, mehrere Prozesse von einem Terminal aus zu starten,<br />
und regelt den Zugriff der Prozesse auf das Terminal. Dazu werden die Prozesse in<br />
Prozessgruppen <strong>unter</strong>teilt, und mehrere Prozessgruppen bilden eine sogenannte<br />
Session. Eine Session hat normalerweise ein Kontroll-Terminal. Der Prozess, der die<br />
Verbindung zu diesem Terminal aufgebaut hat, heisst Kontrollprozess.<br />
Hat eine Session ein Kontroll-Terminal, so gibt es eine Vordergrund-Prozessgruppe und<br />
alle andern Prozessgruppen sind Hintergrund-Prozessgruppen. Die Prozesse der<br />
Vordergrund-Prozessgruppe erhalten die Eingabe vom Terminal und dürfen auch auf das<br />
Terminal schreiben. Ebenso werden die vom Terminal erzeugten Signale an alle<br />
Vordergrundprozesse geschickt.<br />
Die Prozesse der Hintergrund-Prozessgruppen dagegen haben keinen Zugriff auf das<br />
Terminal und erhalten das Signal SIGTTIN oder SIGTTOU, wenn sie trotzdem<br />
versuchen, vom Terminal zu lesen bzw. auf das Terminal zu schreiben. Diese Signale<br />
bewirken defaultmässig, dass die Prozesse gestoppt werden.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 56
Job-Control<br />
Abfragen und Setzen der Prozessgruppe<br />
#include <br />
#include <br />
pid_t getpgrp(void);<br />
int setpgid(pid_t pid, pid_t pgid);<br />
Mittels getpgrp(2) kann der Prozessgruppen-Identifier eines Prozesses abgefragt<br />
werden. Stimmt der Prozessgruppen-Identifier mit dem Identifier eines Prozesses<br />
überein, so heisst der Prozess Prozessgruppen-Führer.<br />
Mittels setpgid(2) kann ein Prozess einer bestehenden oder einer neu zu erzeugenden<br />
Prozessgruppe zugeordnet werden. pid ist der Identifier des Prozesses und pgid der<br />
Identifier der Prozessgruppe, welcher der Prozess zugeordnet wird.<br />
Ein Prozess kann nur seine eigene Prozessgruppe und die Prozessgruppen seiner<br />
Sohnprozesse ändern. Beim Erzeugen eines neuen Prozesses, wird die<br />
Prozessgruppenzugehörigkeit vom Vaterprozess auf den Sohnprozess vererbt.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 57
Job-Control<br />
Erzeugen einer Session<br />
#include <br />
#include <br />
pid_t setsid(void);<br />
Mittels setsid(2) kann eine neue Session erzeugt werden. Dabei werden folgende<br />
Schritte ausgeführt:<br />
1. Hat der Prozess ein Kontroll-Terminal, so wird die Verbindung zu diesem<br />
aufgehoben.<br />
2. Der Prozess wird Prozessgruppen-Führer einer neuen Prozessgruppe.<br />
3. Der Prozess wird Session-Führer einer neuen Session.<br />
Ist der aufrufende Prozess bereits Prozessgruppen-Führer, so kehrt setsid(2) mit einem<br />
Fehler zurück. Im Erfolgsfall gibt setsid(2) den Identifier des aufrufenden Prozesses<br />
zurück.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 58
Job-Control<br />
Bestimmen der Vordergrund-Prozessgruppe<br />
#include <br />
#include <br />
pid_t tcgetpgrp(int fd);<br />
int tcsetpgrp(int fd, pid_t pgid);<br />
Hat eine Session ein Kontroll-Terminal, so kann mittels tcgetpgrp(2) und<br />
tcsetpgrp(2) die Vordergrund-Prozessgruppe bestimmt werden. fd ist ein<br />
Filedeskriptor, auf dem das entsprechende Terminal geöffnet ist. tcgetpgrp(2) gibt den<br />
Identifier der Vordergrund-Prozessgruppe zurück. tcsetpgrp(2) setzt die Vordergrund-<br />
Prozessgruppe auf pgid, wobei pgid der Identifier einer Prozessgruppe in derselben<br />
Session ist.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 59
Job-Control<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 60
7 Signale<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 61
Signale<br />
Erzeugung von Signalen<br />
• der Benutzer drückt am Terminal eine spezielle Taste<br />
• ein Hardware-Fehler tritt auf<br />
• ein Software-Ereignis geschieht<br />
• ein anderer Prozess schickt ein Signal<br />
Signale informieren einen Prozess über das Auftreten von asynchronen Ereignissen:<br />
• Drückt der Benutzer am Terminal eine spezielle Taste wie , <br />
oder , so schickt der Terminal-Treiber das entsprechende Signal<br />
(SIGINT, SIGTSTP, SIGQUIT) an alle Prozesse der Vordergrund-<br />
Prozessgruppe, die dadurch terminiert oder gestoppt werden.<br />
• Tritt ein Hardware-Fehler auf, z.B. eine Division durch Null oder ein Zugriff auf<br />
eine ungültige Speicheradresse, so wird der Kernel benachrichtigt, der das<br />
entsprechende Signal (SIGFPE, SIGSEGV) an den verursachenden Prozess<br />
schickt.<br />
• Ein Software-Ereignis kann ein Signal auslösen, z.B. das Schreiben in eine<br />
Pipe, nachdem der Leseprozess terminiert hat (SIGPIPE), das Ablaufen eines<br />
Timers (SIGALRM) oder das Terminieren eines Sohnprozesses (SIGCHLD).<br />
• Ein Prozess kann einem andern Prozess explizit ein Signal schicken.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 62
Signale<br />
Name Beschreibung Default-Aktion<br />
SIGABRT anormales Terminieren eines Prozesses Terminieren<br />
SIGALRM Ablaufen eines Timers Terminieren<br />
SIGCHLD Terminieren oder Stoppen eines Sohnprozesses Ignorieren<br />
SIGCONT Starten eines gestoppten Prozesses Starten/<br />
Ignorieren<br />
SIGFPE arithmetischer Hardware-Fehler Terminieren<br />
SIGHUP Unterbruch der Verbindung zum Terminal Terminieren<br />
SIGILL illegale Hardware-Anweisung Terminieren<br />
SIGINT Drücken der Interrupt-Taste Terminieren<br />
SIGKILL Terminieren eines Prozesses Terminieren<br />
SIGPIPE Schreiben in eine Pipe ohne Leseprozesse Terminieren<br />
SIGQUIT Drücken der Quit-Taste Terminieren<br />
SIGSEGV ungültige Speicher-Referenzierung Terminieren<br />
SIGSTOP Stoppen eines Prozesses Stoppen<br />
SIGTERM Default-Signal Terminieren<br />
SIGTSTP Drücken der Stop-Taste Stoppen<br />
SIGTTIN Lesen vom Terminal durch einen Hintergrundprozess<br />
Stoppen<br />
SIGTTOU Schreiben aufs Terminal durch einen<br />
Stoppen<br />
Hintergrundprozess<br />
SIGUSR1 Benutzer-definiertes Signal Terminieren<br />
SIGUSR2 Benutzer-definiertes Signal Terminieren<br />
Tabelle 7-1: POSIX.1-Signale<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 63
Signale<br />
Signaldisposition<br />
• Default-Aktion ausführen<br />
• Signal ignorieren<br />
• Signal abfangen und behandeln<br />
• Signal blockieren<br />
Ein Prozess kann dem Kernel mitteilen, was zu tun ist, wenn ein bestimmtes Signal<br />
eintrifft:<br />
• Für jedes Signal ist eine Default-Aktion definiert, die für die meisten Signale das<br />
Terminieren des Prozesses ist (siehe Tabelle 7-1).<br />
• Ein Signal kann ignoriert werden.<br />
• Ein Signal kann abgefangen werden, d.h. der Kernel wird angewiesen, eine<br />
Benutzer-definierte Funktion (Signal-Handler) auszuführen, wenn das Signal<br />
eintrifft.<br />
• Ein Signal kann blockiert werden. Trifft ein blockiertes Signal ein, so bleibt es<br />
hängig, bis es entweder deblockiert oder ignoriert wird.<br />
Die beiden Signale SIGKILL und SIGSTOP können weder ignoriert noch abgefangen<br />
noch blockiert werden. Die Signaldisposition wird bei fork(2) vom Vater- auf den<br />
Sohnprozess vererbt; exec(2) setzt den Status der abgefangenen Signale auf die<br />
Default-Aktion zurück.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 64
Signale<br />
Probleme mit Signalen<br />
• Signal-Handler werden asynchron zum normalen<br />
Programmfluss ausgeführt.<br />
• Langsame System-Calls können durch Signale <strong>unter</strong>brochen<br />
werden.<br />
• Blockierte Signale, die mehrmals eintreffen, werden nur<br />
einmal gespeichert.<br />
Da durch ein abgefangenes Signal der normale Programmfluss an einer beliebigen Stelle<br />
<strong>unter</strong>brochen wird, sollten in Signal-Handlern keine System-Calls und Funktionen<br />
verwendet werden, die nicht reentrant sind. Nicht reentrant sind z.B. die Funktionen der<br />
Standard-Ein-/Ausgabe-Bibliothek und Funktionen, die statische Datenstrukturen<br />
verwenden.<br />
Wird ein langsamer System-Call durch ein Signal <strong>unter</strong>brochen, so kehrt er je nach<br />
<strong>Betriebssystem</strong> mit einem Fehler (errno=EINTR) zurück oder wird automatisch<br />
wiedergestartet. Langsame System-Calls sind solche, die für immer blockieren können.<br />
Dies sind z.B. read(2), write(2) und open(2), wenn sie auf langsame Devices<br />
angewendet werden.<br />
Trifft ein blockiertes Signal mehrmals ein, so wird es nur einmal gespeichert. Da ein<br />
abgefangenes Signal während der Ausführung des Signal-Handlers automatisch blockiert<br />
wird, können dadurch gleiche kurz nacheinander eintreffende Signale verlorengehen.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 65
Signale<br />
Schicken eines Signals<br />
#include <br />
#include <br />
int kill(pid_t pid, int sig);<br />
Mittels kill(2) kann einem Prozess oder einer Prozessgruppe ein Signal geschickt<br />
werden. Der Parameter pid hat dabei folgende Bedeutung:<br />
pid > 0 schickt das Signal sig dem Prozess mit Identifier pid<br />
pid = 0 schickt das Signal sig allen Prozessen in derselben Prozessgruppe<br />
pid < 0<br />
schickt das Signal sig allen Prozessen in der Prozessgruppe, deren<br />
Identifier gleich dem Absolutbetrag von pid ist<br />
Ein Prozess kann nur einem andern Prozess ein Signal schicken, wenn der reale oder<br />
der effektive User-Identifier der beiden Prozesse übereinstimmt.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 66
Signale<br />
Definieren einer Signalaktion<br />
#include <br />
int sigaction(int sig, const struct sigaction *act,<br />
struct sigaction *oact);<br />
struct sigaction {<br />
void (*sa_handler)();<br />
sigset_t sa_mask;<br />
int sa_flags;<br />
};<br />
Mittels sigaction(2) kann ein Prozess die Aktion eines Signals definieren oder die<br />
bereits definierte Aktion abfragen. Ist act nicht ein Nullpointer, so zeigt act auf die neue<br />
Aktion des Signals sig. Istoact nicht ein Nullpointer, so wird in der Struktur, auf die<br />
oact zeigt, die bisherige Aktion des Signals sig gespeichert.<br />
Die Komponenten der Struktur sigaction haben folgende Bedeutungen:<br />
sa_handler Pointer auf den Benutzer-definierten Signal-Handler oder<br />
SIG_DFL für die Default-Aktion oder SIG_IGN, um das Signal<br />
zu ignorieren<br />
sa_mask Menge der Signale, die zusätzlich zum Signal sig während der<br />
Ausführung des Signal-Handlers blockiert werden sollen<br />
sa_flags SA_NOCLDSTOP, wenn das Signal SIGCHLD für gestoppte<br />
Sohnprozesse nicht erzeugt werden soll<br />
In gewissen <strong>Betriebssystem</strong>en kann zusätzlich das Flag SA_RESTART angegeben<br />
werden, um System-Calls, die durch das entsprechende Signal <strong>unter</strong>brochen werden,<br />
automatisch wiederzustarten.<br />
Signalmengen können mit den folgenden Funktionen manipuliert werden:<br />
sigemptyset(3) entfernt alle Signale aus einer Signalmenge<br />
sigfillset(3) fügt alle Signale zu einer Signalmenge hinzu<br />
sigaddset(3) fügt ein bestimmtes Signal zu einer Signalmenge hinzu<br />
sigdelset(3) entfernt ein bestimmtes Signal aus einer Signalmenge<br />
sigismember(3) prüft, ob ein bestimmtes Signal zur Signalmenge gehört<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 67
Signale<br />
Im folgenden Programm werden die beiden Signale SIGUSR1 und SIGUSR2 durch einen<br />
Signal-Handler abgefangen.<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
void signal_handler( int );<br />
int main( void )<br />
{<br />
struct sigaction<br />
act;<br />
act.sa_handler = signal_handler;<br />
sigemptyset( &act.sa_mask );<br />
act.sa_flags = 0;<br />
if ( sigaction( SIGUSR1, &act, NULL ) == -1 )<br />
panic( "sigaction" )<br />
if ( sigaction( SIGUSR2, &act, NULL ) == -1 )<br />
panic( "sigaction" )<br />
while ( 1 ) {<br />
printf( "Hauptprogramm wartet auf Signal...\n" );<br />
pause();<br />
}<br />
}<br />
exit( EXIT_SUCCESS );<br />
void signal_handler( int sig )<br />
{<br />
printf( "Signal-Handler hat Signal %d erhalten\n", sig );<br />
sleep( 10 );<br />
}<br />
Programm 7-1: Abfangen von Signalen<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 68
Signale<br />
Ändern der Signalmaske<br />
#include <br />
int sigprocmask(int how, const sigset_t *set,<br />
sigset_t *oset);<br />
int sigpending(sigset_t *set);<br />
Jeder Prozess hat eine Signalmaske, die angibt, welche Signale blockiert werden sollen.<br />
Mittels sigprocmask(2) kann diese geändert oder abfragt werden. Ist set nicht ein<br />
Nullpointer, so zeigt set auf die Menge der zu modifzierenden Signale und how gibt an,<br />
wie sie zu modifizieren sind:<br />
SIG_BLOCK fügt die Signale in set zur Signalmaske hinzu<br />
SIG_UNBLOCK entfernt die Signale in set aus der Signalmaske<br />
SIG_SETMASK setzt die Signalmaske auf set<br />
Ist oset nicht ein Nullpointer, so wird die bisherige Signalmaske des Prozesses in der<br />
Menge, auf die oset zeigt, gespeichert.<br />
sigpending(2) speichert in der Menge, auf die set zeigt, diejenigen blockierten Signale<br />
eines Prozesses, die momentan hängig sind.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 69
Signale<br />
Im folgenden Programm wird ein kritischer Bereich vor dem Eintreffen des Signals<br />
SIGINT geschützt.<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( void )<br />
{<br />
sigset_t blockmask, oldmask, pendmask;<br />
sigemptyset( &blockmask );<br />
sigaddset( &blockmask, SIGINT );<br />
if ( sigprocmask( SIG_BLOCK, &blockmask, &oldmask ) == -1 )<br />
panic( "sigprocmask" )<br />
printf( "Kritischer Bereich wird ausgefuehrt...\n" );<br />
sleep( 10 );<br />
if ( sigpending( &pendmask ) == -1 )<br />
panic( "sigpending" )<br />
if ( sigismember( &pendmask, SIGINT ) )<br />
printf( "Interrupt-Signal ist haengig\n" );<br />
if ( sigprocmask( SIG_SETMASK, &oldmask, NULL ) == -1 )<br />
panic( "sigprocmask" )<br />
}<br />
printf( "Programm terminiert\n" );<br />
exit( EXIT_SUCCESS );<br />
Programm 7-2: Schützen eines kritischen Bereichs vor Signalen<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 70
Signale<br />
Weitere Signal-Funktionen<br />
#include <br />
unsigned int alarm(unsigned int secs);<br />
int pause(void);<br />
unsigned int sleep(unsigned int secs);<br />
alarm(3) schickt dem aufrufenden Prozess nach secs Sekunden das Signal SIGALRM.<br />
War vor dem Aufruf von alarm(3) bereits ein Alarm gesetzt, so wird dieser gelöscht und<br />
die Anzahl der noch verbleibenden Sekunden des früheren Aufrufs zurückgegeben. Es ist<br />
zu beachten, dass die Auslieferung des Signals durch andere Systemaktivitäten<br />
verzögert werden kann.<br />
pause(3) blockiert den aufrufenden Prozess, bis ein Signal eintrifft, das nicht ignoriert<br />
oder blockiert wird.<br />
sleep(3) blockiert den aufrufenden Prozess, bis secs Sekunden abgelaufen sind oder<br />
ein Signal eintrifft. Wird sleep(3) durch ein Signal <strong>unter</strong>brochen, so wird die Anzahl der<br />
noch verbleibenden Sekunden zurückgegeben.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 71
Signale<br />
Im folgenden Programm wird eine Eingabe vom Terminal gelesen, wobei eine obere<br />
Zeitgrenze gesetzt wird.<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#define TIMEOUT 10<br />
#define MAXLINE 80<br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
void sigalrm_handler( int );<br />
int main( void )<br />
{<br />
char<br />
struct sigaction<br />
line[MAXLINE];<br />
act;<br />
act.sa_handler = sigalrm_handler;<br />
sigemptyset( &act.sa_mask );<br />
act.sa_flags = 0;<br />
if ( sigaction( SIGALRM, &act, NULL ) == -1 )<br />
panic( "sigaction" )<br />
printf( "Eingabe: " );<br />
alarm( TIMEOUT );<br />
if ( fgets( line, MAXLINE, stdin ) == NULL )<br />
if ( errno != EINTR ) panic( "fgets" )<br />
alarm( 0 );<br />
}<br />
exit( EXIT_SUCCESS );<br />
void sigalrm_handler( int sig )<br />
{<br />
printf( "Timeout ist abgelaufen\n" );<br />
}<br />
Programm 7-3: Lesen einer Eingabe mit Timeout<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 72
8 Pipes<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 73
Pipes<br />
Funktionsweise<br />
• Eine Pipe ist ein Puffer fester Grösse, der vom Kernel<br />
verwaltet wird.<br />
• Über eine Pipe können Prozesse Daten austauschen.<br />
• Der Zugriff auf die Daten erfolgt nach dem FIFO-Prinzip.<br />
• Die zugreifenden Prozesse werden synchronisiert.<br />
Liest ein Prozess mittels read(2) aus einer Pipe, so werden so viele Zeichen gelesen,<br />
wie gerade in der Pipe vorhanden sind. Ist die Pipe leer, so wird der lesende Prozess<br />
blockiert, bis ein anderer Prozess Daten in die Pipe schreibt. Hat kein Prozess mehr die<br />
Pipe zum Schreiben geöffnet, so kehrt read(2) mit dem Funktionswert 0 zurück.<br />
Versucht umgekehrt ein Prozess, mittels write(2) mehr Zeichen in eine Pipe zu<br />
schreiben als Platz haben, so wird er blockiert, bis ein anderer Prozess Daten aus der<br />
Pipe liest. Hat kein Prozess mehr die Pipe zum Lesen geöffnet, so erhält der schreibende<br />
Prozess das Signal SIGPIPE.<br />
Das Schreiben ist garantiert un<strong>unter</strong>brechbar, falls nicht mehr als PIPE_BUF Zeichen<br />
geschrieben werden. Dies kann dann wichtig sein, wenn mehrere Prozesse in dieselbe<br />
Pipe schreiben.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 74
Pipes<br />
Erzeugen einer unnamed Pipe<br />
#include <br />
int pipe(int fd[2]);<br />
pipe(2) erzeugt eine unnamed Pipe und gibt zwei Filedeskriptoren zurück: fd[0] zum<br />
Lesen aus der Pipe, fd[1] zum Schreiben in die Pipe. Da eine unnamed Pipe<br />
ausschliesslich über ihre Filedeskriptoren referenzierbar ist, kann sie nur vom<br />
erzeugenden Prozess und seinen Sohnprozessen verwendet werden.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 75
Pipes<br />
Das folgende Programm führt zwei Programme aus, wobei die Ausgabe des ersten<br />
Programms in eine unnamed Pipe umgeleitet wird, aus der das zweite Programm seine<br />
Eingabe liest.<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int fd[2];<br />
pid_t pid;<br />
if ( argc < 3 ) {<br />
fprintf( stderr, "Verwendung: %s program1 program2 [args]\n",<br />
argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
/* Pipe erzeugen */<br />
if ( pipe( fd ) == -1 )<br />
panic( "pipe" )<br />
/* Sohnprozess erzeugen */<br />
if ( (pid = fork()) == -1 )<br />
panic( "fork" )<br />
if ( pid == 0 ) {<br />
/* Standardausgabe auf Pipe umleiten */<br />
if ( close( fd[0] ) == -1 )<br />
panic( "close" )<br />
if ( dup2( fd[1], STDOUT_FILENO ) != STDOUT_FILENO )<br />
panic( "dup2" )<br />
if ( close( fd[1] ) == -1 )<br />
panic( "close" )<br />
}<br />
/* Programm ausfuehren */<br />
if ( execlp( argv[1], argv[1], NULL ) == -1 )<br />
panic( "execlp" )<br />
/* Standardeingabe auf Pipe umleiten */<br />
if ( close( fd[1] ) == -1 )<br />
panic( "close" )<br />
if ( dup2( fd[0], STDIN_FILENO ) != STDIN_FILENO )<br />
panic( "dup2" )<br />
if ( close( fd[0] ) == -1 )<br />
panic( "close" )<br />
}<br />
/* Programm ausfuehren */<br />
if ( execvp( argv[2], &argv[2] ) == -1 )<br />
panic( "execvp" )<br />
Programm 8-1: Verwendung einer unnamed Pipe<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 76
Pipes<br />
Erzeugen einer named Pipe<br />
#include
Pipes<br />
Das folgende Programm erzeugt eine named Pipe, liest aus ihr, bis kein Prozess mehr in<br />
die Pipe schreibt, und entfernt sie danach.<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define BUFSIZE 1024<br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int fd;<br />
char buf[BUFSIZE];<br />
ssize_t n;<br />
if ( argc != 2 ) {<br />
fprintf( stderr, "Verwendung: %s file\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
/* Pipe erzeugen und oeffnen */<br />
if ( mkfifo( argv[1], S_IRUSR | S_IWUSR ) == -1 )<br />
panic( "mkfifo" )<br />
if ( (fd = open( argv[1], O_RDONLY, 0 )) == -1 )<br />
panic( "open" )<br />
while ( 1 ) {<br />
}<br />
/* Daten aus Pipe lesen und ausgeben */<br />
if ( (n = read( fd, buf, BUFSIZE )) == -1 )<br />
panic( "read" );<br />
if ( n == 0 ) break;<br />
if ( write( STDOUT_FILENO, buf, n ) == -1 )<br />
panic( "write" )<br />
/* Pipe schliessen und entfernen */<br />
if ( close( fd ) == -1 )<br />
panic( "close" )<br />
if ( unlink( argv[1] ) == -1 )<br />
panic( "unlink" )<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 8-2: Verwendung einer named Pipe<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 78
9 Fortgeschrittene Ein-/Ausgabe<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 79
Fortgeschrittene Ein-/Ausgabe<br />
I/O-Multiplexing<br />
#include <br />
#include <br />
#include <br />
int select(int nfds, fd_set *readfds,<br />
fd_set *writefds, fd_set *exceptfds,<br />
struct timeval *timeout);<br />
struct timeval {<br />
long tv_sec;<br />
long tv_usec;<br />
};<br />
Oft stellt sich das Problem, dass ein Prozess von mehreren Filedeskriptoren lesen muss,<br />
ohne dass im voraus bekannt ist, in welcher Reihenfolge die zu lesenden Daten zur<br />
Verfügung stehen. Mittels select(2) kann ein Prozess gleichzeitig mehrere<br />
Filedeskriptoren überwachen, wobei er so lange blockiert wird, bis mindestens einer der<br />
Deskriptoren zum Lesen bereit ist.<br />
nfds ist 1 grösser als die höchste Nummer der Deskriptoren, die überwacht werden<br />
sollen, und die Deskriptormengen readfds, writefds und exceptfds geben an,<br />
welche es sind:<br />
readfds<br />
writefds<br />
Deskriptoren, auf die gewartet wird, bis Daten gelesen werden<br />
können oder das Fileende erreicht ist<br />
Deskriptoren, auf die gewartet wird, bis Daten geschrieben<br />
werden können<br />
exceptfds Deskriptoren, auf die gewartet wird, bis eine Ausnahmebedingung<br />
eintritt<br />
Im Parameter timeout kann eine maximale Wartezeit spezifiziert werden. Wird ein<br />
Nullpointer übergeben, so gibt es keine zeitliche Beschränkung.<br />
Nach der Rückkehr von select(2) hat der Funktionswert folgende Bedeutung:<br />
= –1 ein Fehler ist aufgetreten<br />
= 0 die Wartezeit ist abgelaufen, bevor ein Deskriptor bereit war<br />
> 0 Anzahl der bereiten Deskriptoren; in diesem Fall sind in den<br />
Deskriptormengen readfds, writefds und exceptfds diejenigen<br />
Deskriptoren gesetzt, die bereit sind<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 80
Fortgeschrittene Ein-/Ausgabe<br />
Deskriptormengen können mit den folgenden Makros manipuliert werden:<br />
FD_ZERO() löscht alle Bits<br />
FD_SET() setzt ein bestimmtes Bit<br />
FD_CLR() löscht ein bestimmtes Bit<br />
FD_ISSET() prüft, ob ein bestimmtes Bit gesetzt ist<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 81
Fortgeschrittene Ein-/Ausgabe<br />
Das folgende Programm liest gleichzeitig aus mehreren Files, bis bei allen Files das<br />
Fileende erreicht ist.<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define BUFSIZE 1024<br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int nfiles, i;<br />
int fd, maxfd;<br />
fd_set openfds, readfds;<br />
char buf[BUFSIZE];<br />
ssize_t n;<br />
if ( argc < 2 ) {<br />
fprintf( stderr, "Verwendung: %s file1 [file2 ...]\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
nfiles = argc - 1;<br />
/* Files oeffnen und Deskriptormenge setzen */<br />
maxfd = -1;<br />
FD_ZERO( &openfds );<br />
for ( i = 0; i < nfiles; i++ ) {<br />
if ( (fd = open( argv[i+1], O_RDONLY )) == -1 )<br />
panic( "open" )<br />
if ( maxfd < fd ) maxfd = fd;<br />
FD_SET( fd, &openfds );<br />
}<br />
while ( nfiles > 0 ) {<br />
/* auf bereite Deskriptoren warten */<br />
readfds = openfds;<br />
if ( select( maxfd + 1, &readfds, NULL, NULL, NULL ) == -1 )<br />
panic( "select" )<br />
}<br />
/* von bereiten Deskriptoren lesen */<br />
for ( fd = 0; fd
Fortgeschrittene Ein-/Ausgabe<br />
Record-Locking<br />
#include <br />
#include <br />
#include <br />
int fcntl(int fd, int cmd, struct flock *lock);<br />
struct flock {<br />
short l_type;<br />
off_t l_start;<br />
short l_whence;<br />
off_t l_len;<br />
pid_t l_pid;<br />
};<br />
Mittels fcntl(2) kann ein Prozess auf einem beliebigen Bereich eines Files ein Lock<br />
erzeugen. Dabei gibt es exklusive und geteilte Locks. Besitzt ein Prozess einen<br />
exklusiven Lock auf einem bestimmten Bereich, so kann kein anderer Prozess einen<br />
Lock auf demselben Bereich erzeugen. Mehrere Prozesse können aber gleichzeitig einen<br />
geteilten Lock auf demselben Bereich haben.<br />
Der Parameter fd ist der Deskriptor des Files, auf dem ein Lock erzeugt werden soll, und<br />
lock beschreibt den gewünschten Lock:<br />
l_type<br />
l_start<br />
l_whence<br />
l_len<br />
l_pid<br />
Typ des Locks: F_RDLCK für einen geteilten Lock, F_WRLCK für<br />
einen exklusiven Lock und F_UNLCK, um einen Lock freizugeben<br />
Offset des Bereichs relativ zu l_whence<br />
Interpretation des Offsets: bei SEEK_SET relativ zum Anfang des<br />
Files, bei SEEK_CUR relativ zum aktuellen Fileoffset und bei<br />
SEEK_END relativ zum Ende des Files<br />
Grösse des Bereichs in Bytes; 0 bedeutet bis ans Ende des Files<br />
Identifier des Prozesses, der den Lock hat (wird beim Befehl<br />
F_GETLCK zurückgegeben)<br />
Der Parameter cmd ist einer der folgenden Befehle:<br />
F_GETLK bestimmt, ob der gewünschte Lock erzeugt werden kann; existiert<br />
ein anderer Lock, der dies verhindert, so wird die Information über<br />
den andern Lock in die Struktur lock geschrieben, andernfalls<br />
wird die Komponente l_type auf F_UNLCK gesetzt<br />
F_SETLK setzt den Lock bzw. gibt ihn wieder frei; kann ein gewünschter<br />
Lock nicht erzeugt werden, so kehrt der aufrufende Prozess mit<br />
einem Fehler (errno=EAGAIN) zurück<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 83
F_SETLKW<br />
Fortgeschrittene Ein-/Ausgabe<br />
blockierende Version von F_SETLK: der aufrufende Prozess wird<br />
blockiert, bis der gewünschte Lock erzeugt werden kann<br />
Ein Lock ist jeweils einem Prozess und einem File zugeordnet, so dass er automatisch<br />
freigegeben wird, wenn der zugehörige Prozess terminiert oder das entsprechende File<br />
geschlossen wird.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 84
Fortgeschrittene Ein-/Ausgabe<br />
Mit dem folgenden Programm können beliebige Bereiche eines Files gesperrt und wieder<br />
freigegeben werden.<br />
#define _POSIX_SOURCE 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int<br />
fd;<br />
struct flock lock;<br />
char<br />
cmd[10];<br />
if ( argc != 2 ) {<br />
fprintf( stderr, "Verwendung: %s file\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( (fd = open( argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR )) == -1 )<br />
panic( "open" )<br />
while ( 1 ) {<br />
printf( "> " );<br />
if ( scanf( "%9s%d%d", cmd, &lock.l_start, &lock.l_len ) < 3 )<br />
break;<br />
if ( strcmp( cmd, "readlock" ) == 0 )<br />
lock.l_type = F_RDLCK;<br />
else if ( strcmp( cmd, "writelock" ) == 0 )<br />
lock.l_type = F_WRLCK;<br />
else if ( strcmp( cmd, "unlock" ) == 0 )<br />
lock.l_type = F_UNLCK;<br />
else {<br />
printf( "ungueltiger Befehl\n" );<br />
continue;<br />
}<br />
lock.l_whence = SEEK_SET;<br />
if ( fcntl( fd, F_SETLKW, &lock ) == -1 )<br />
panic( "fcntl" )<br />
}<br />
if ( close( fd ) == -1 )<br />
panic( "close" )<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 9-4: Record-Locking<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 85
Fortgeschrittene Ein-/Ausgabe<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 86
10 Netzwerk-Kommunikation<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 87
Netzwerk-Kommunikation<br />
Kommunikationsmodell<br />
lokaler Host<br />
fremder Host<br />
lokaler<br />
Prozess<br />
fremder<br />
Prozess<br />
Netzwerk<br />
Netzwerkmodul<br />
Netzwerkmodul<br />
Eine Netzwerkverbindung kann durch folgendes 5-Tupel charakterisiert werden:<br />
(Protokoll, lokaler Host, lokaler Prozess, fremder Host, fremder Prozess)<br />
Das Format der Host-Adressen und die Identifikation der kommunizierenden Prozesse<br />
wird durch das verwendete Protokoll definiert. In der TCP/IP-Protokollfamilie werden<br />
dafür Internet-Adressen und Port-Nummern verwendet, z.B.<br />
(TCP, 193.5.80.21, 1078, 130.59.1.2, 21)<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 88
Netzwerk-Kommunikation<br />
TCP/IP-Protokolle<br />
ISO/OSI-Modell<br />
TCP/IP-Protokolle<br />
Anwendungsschicht<br />
FTP, TELNET, SMTP, ...<br />
Darstellungsschicht<br />
Sitzungsschicht<br />
Transportschicht<br />
TCP<br />
UDP<br />
Netzwerkschicht<br />
IP/ICMP<br />
Datensicherungsschicht<br />
Physikalische Schicht<br />
Die TCP/IP-Protokolle wurden in den frühen Achtzigerjahren als Standard für das<br />
ARPANET (Paketvermittlungsnetz der „Defense Advanced Research Projects Agency“)<br />
definiert, aus dem das heutige Internet entstand. Kurz darauf wurden sie ins Berkeley-<br />
<strong>UNIX</strong> integriert, so dass sie eine schnelle Verbreitung fanden.<br />
Die TCP/IP-Protokolle stellen offene Standards dar, die Hardware- und <strong>Betriebssystem</strong>unabhängig<br />
sind, und werden in sogenannten „Requests for Comments“ (RFC) publiziert.<br />
Die wichtigsten Protokolle sind:<br />
IP Internet Protocol (RFC 791)<br />
ICMP Internet Control Message Protocol (RFC 792)<br />
TCP Transmission Control Protocol (RFC 793)<br />
UDP User Datagram Protocol (RFC 768)<br />
FTP File Transfer Protocol (RFC 959)<br />
TELNET Network Terminal Protocol (RFC 854)<br />
SMTP Simple Mail Transfer Protocol (RFC 821)<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 89
Netzwerk-Kommunikation<br />
Eigenschaften der TCP/IP-Protokolle<br />
TCP UDP IP<br />
verbindungsorientiert ja nein nein<br />
meldungsorientiert nein ja ja<br />
zuverlässig ja nein nein<br />
Flusskontrolle ja nein nein<br />
voll-duplex<br />
ja<br />
dringende Daten ja<br />
Protokolle können verschiedene Eigenschaften haben:<br />
• Bei einem verbindungsorientierten Protokoll bauen die beiden<br />
Kommunikationspartner eine logische Verbindung auf, die während dem<br />
Datenaustausch bestehen bleibt und danach wieder abgebaut wird.<br />
• Bei einem meldungsorientierten Protokoll werden die Meldungsgrenzen<br />
beibehalten, die der Sender festgelegt hat.<br />
• Ein zuverlässiges Protokoll garantiert, dass die Daten fehlerfrei und in<br />
derselben Reihenfolge beim Empfänger ankommen, wie sie der Sender<br />
abgeschickt hat.<br />
• Eine Flusskontrolle stellt sicher, dass der Sender die Daten nicht schneller<br />
schickt, als sie der Empfänger verarbeiten kann.<br />
• Eine voll-duplexes Protokoll ermöglicht die gleichzeitige Übertragung von Daten<br />
in beide Richtungen.<br />
• Erlaubt ein Protokoll das Verschicken von dringenden Daten, so werden diese<br />
nicht zwischengespeichert sondern sofort übermittelt.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 90
Netzwerk-Kommunikation<br />
Rahmenbildung<br />
Anwendungsschicht<br />
Daten<br />
Transportschicht<br />
TCP/UDP-<br />
Header<br />
Daten<br />
Netzwerkschicht<br />
IP-Header<br />
TCP/UDP-<br />
Header<br />
Daten<br />
Datensicherungsschicht<br />
Ethernet-<br />
Header<br />
IP-Header<br />
TCP/UDP-<br />
Header<br />
Daten<br />
In jeder Schicht des ISO/OSI-Modells fügt der Sender den Daten einen Header mit<br />
Kontrollinformation hinzu, der dann vom Empfänger in umgekehrter Reihenfolge wieder<br />
entfernt wird.<br />
Der IP-Header enthält u.a. die Internet-Adressen des Sender- und Empfänger-Hosts<br />
sowie einen Protokoll-Identifier, damit die Daten an das entsprechende Modul der<br />
Transportschicht weitergegeben werden können. Die TCP- und UDP-Header enthalten<br />
u.a. die Port-Nummern der beiden kommunizierenden Prozesse.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 91
Netzwerk-Kommunikation<br />
Internet-Adressen<br />
Bytes<br />
1 2 3 4<br />
Class A<br />
0<br />
Netz-Id<br />
Host-Id<br />
Class B<br />
10<br />
Netz-Id<br />
Host-Id<br />
Class C<br />
110<br />
Netz-Id<br />
Host-Id<br />
Jeder Host im Internet hat eine eindeutige 32 bit-Internet-Adresse, die aus einem<br />
Netzwerk-Identifier und einem dazu relativen Host-Identifier besteht. Es gibt drei Typen<br />
von Internet-Adressen: Class A-Adressen werden für Netzwerke mit vielen Hosts<br />
gebraucht, Class B-Adressen für Netzwerke mit einer mittleren Anzahl Hosts und Class<br />
C-Adressen für Netzwerke mit wenigen Hosts.<br />
Die vier Bytes einer Internet-Adresse werden gewöhnlich als Dezimalzahlen geschrieben<br />
und durch einen Punkt voneinander getrennt, z.B. 130.59.1.2. Mit den Funktionen<br />
inet_addr(3) und inet_ntoa(3) können Strings, die eine Internet-Adresse in dieser<br />
Dezimaldarstellung enthalten, in Integer-Zahlen umgewandelt werden und umgekehrt.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 92
Netzwerk-Kommunikation<br />
Port-Nummern<br />
Bereich Kategorie<br />
1 – 255 reservierte Port-Nummern<br />
1 – 1023 privilegierte Port-Nummern<br />
1024 – 5000 kurzlebige Port-Nummern<br />
5001 – 65535 Benutzer-definierte Port-Nummern<br />
Port-Nummern sind 16 bit-Dezimalzahlen, die vom TCP- und UDP-Protokoll zur<br />
Identifikation der kommunizierenden Prozesse verwendet werden. Die Port-Nummern<br />
sind in vier Kategorien eingeteilt:<br />
• Reservierte Port-Nummern sind „wohlbekannte“ Port-Nummern, die für<br />
Standard-Anwendungen reserviert sind, z.B. 21 für FTP.<br />
• Privilegierte Port-Nummern werden von Superuser-Prozessen für <strong>UNIX</strong>spezifische<br />
Anwendungen verwendet, z.B. 514 für Remote-Shell.<br />
• Kurzlebige Port-Nummern sind Port-Nummern, die vom System automatisch an<br />
Benutzer-Prozesse vergeben werden, die nicht auf eine bestimmte Port-<br />
Nummer angewiesen sind.<br />
• Benutzer-definierte Port-Nummern können von nicht-privilegierten Server-<br />
Prozessen beansprucht werden, die ihre Port-Nummer den Client-Prozessen<br />
bekanntgeben müssen.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 93
Netzwerk-Kommunikation<br />
Sockets<br />
Datenaustausch<br />
verbindungsorientiert<br />
verbindungslos<br />
Protokoll<br />
lokale<br />
Adresse<br />
fremde<br />
Adresse<br />
socket(2) bind(2) accept(2)<br />
connect(2)<br />
recv(2)<br />
send(2)<br />
socket(2) bind(2) recvfrom(2)<br />
sendto(2)<br />
Sockets bilden die Schnittstelle zwischen den Anwendungsprogrammen und dem<br />
Netzwerkmodul; sie stellen die logischen Endpunkte einer Verbindung dar.<br />
Die Netzwerk-Schnittstelle ist ähnlich aufgebaut wie die Schnittstelle zum Filesystem. So<br />
entspricht das Erzeugen eines Sockets mittels socket(2) dem Öffnen eines Files. Für<br />
den Datenaustausch über einen Socket können u.a. die System-Calls read(2) und<br />
write(2) verwendet werden. Und mittels close(2) kann ein Socket schliesslich wieder<br />
geschlossen werden, wenn er nicht mehr gebraucht wird.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 94
Netzwerk-Kommunikation<br />
Verbindungslose Kommunikation<br />
Server<br />
Client<br />
socket(2)<br />
bind(2)<br />
socket(2)<br />
bind(2)<br />
recvfrom(2)<br />
sendto(2)<br />
Datenaustausch<br />
sendto(2)<br />
recvfrom(2)<br />
close(2)<br />
Bei einer verbindungslosen Kommunikation wird der Server meistens iterativ<br />
implementiert. D.h. er empfängt eine Anfrage nach der andern und schickt die Antworten<br />
an die entsprechenden Clients zurück. Dabei spielt es keine Rolle, ob mehrere Anfragen<br />
vom gleichen Client oder alle Anfragen von verschiedenen Clients stammen. Ein<br />
iterativer Server ist aber nur geeignet, wenn er nicht lange braucht, um seine Antworten<br />
zu erzeugen.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 95
Verbindungsorientierte Kommunikation<br />
Netzwerk-Kommunikation<br />
Server<br />
Client<br />
socket(2)<br />
bind(2)<br />
listen(2)<br />
accept(2)<br />
fork(2)<br />
Verbindungsaufbau<br />
socket(2)<br />
bind(2)<br />
connect(2)<br />
recv(2)<br />
send(2)<br />
Datenaustausch<br />
send(2)<br />
recv(2)<br />
close(2)<br />
close(2)<br />
Bei einer verbindungsorientierten Kommunikation wird der Server meistens parallel<br />
implementiert. D.h. der Server-Prozess erzeugt nach einem Verbindungsaufbau einen<br />
Sohnprozess, der mit dem entsprechenden Client kommuniziert, während er selbst<br />
weitere Verbindungsanforderungen von Clients akzeptieren kann. Ein paralleler Server<br />
hat den Vorteil, dass er nicht durch einen Client blockiert werden kann.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 96
Netzwerk-Kommunikation<br />
Erzeugen eines Sockets<br />
#include <br />
#include <br />
int socket(int domain, int type, int protocol);<br />
Mittels socket(2) kann ein Socket erzeugt werden. Als Funktionswert wird ein Deskriptor<br />
zurückgegeben, mit dem der Socket referenziert wird.<br />
domain ist der Kommunikationsbereich. Je nach System stehen verschiedene Bereiche<br />
zur Auswahl, überall aber AF_<strong>UNIX</strong> für die lokale Kommunikation auf einem Rechner und<br />
AF_INET für die Kommunikation über das Internet.<br />
type definiert die Semantik der Kommunikation: SOCK_STREAM für eine<br />
verbindungsorientierte zuverlässige Kommunikation oder SOCK_DGRAM für eine<br />
verbindungslose meldungsorientierte Kommunikation.<br />
protocol bestimmt das zu verwendende Protokoll. Ist protocol = 0, so wird ein vom<br />
Kommunikationsbereich und von der Semantik abhängiges Default-Protokoll gewählt. Im<br />
Internet-Bereich ist dies TCP für SOCK_STREAM und UDP für SOCK_DGRAM.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 97
Netzwerk-Kommunikation<br />
Socket-Adressen<br />
#include <br />
#include <br />
struct sockaddr {<br />
unsigned char<br />
sa_family_t<br />
char<br />
};<br />
sa_len;<br />
sa_family;<br />
sa_data[14];<br />
struct sockaddr_in {<br />
unsigned char sin_len;<br />
sa_family_t sin_family;<br />
in_port_t sin_port;<br />
struct in_addr sin_addr;<br />
unsigned char sin_zero[8];<br />
};<br />
struct in_addr {<br />
in_addr_t s_addr;<br />
};<br />
Die Struktur sockaddr ist eine allgemeine Adressstruktur, die in vielen Socket-System-<br />
Calls als Parameter vorkommt. Der Inhalt der Protokoll-spezifischen Adresse sa_data<br />
wird abhängig von der Adressfamilie sa_family interpretiert.<br />
Für die Internet-Familie wird die Struktur sockaddr_in verwendet, bei der sin_family<br />
die Konstante AF_INET ist. Die Komponenten sin_port und sin_addr enthalten die<br />
Port-Nummer des Prozesses und die Internet-Adresse des Hosts, sin_zero wird nicht<br />
gebraucht.<br />
Da nicht alle Rechner Integer-Zahlen intern gleich darstellen, müssen die Internet-<br />
Adresse und die Port-Nummer in der sogenannten Netzwerk-Byte-Reihenfolge<br />
übermittelt werden (d.h. mit dem höchstwertigen Byte am Anfang). Zur Umwandlung von<br />
Integer-Zahlen mit Host-Byte-Reihenfolge in Integer-Zahlen mit Netzwerk-Byte-<br />
Reihenfolge und umgekehrt stehen die Funktionen htonl(3), htons(3), ntohl(3) und<br />
ntohs(3) zur Verfügung.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 98
Verknüpfen eines Sockets mit einer Adresse<br />
Netzwerk-Kommunikation<br />
#include <br />
#include <br />
int bind(int sd, const struct sockaddr *addr,<br />
size_t addrlen);<br />
Mittels bind(2) wird der Socket sd mit der Adresse addr verknüpft. Da die Adresse<br />
Protokoll-abhängig ist, gibt addrlen ihre Länge an. Es gibt zwei Anwendungsfälle von<br />
bind(2):<br />
• Ein Server-Prozess registriert seine „wohlbekannte“ Port-Nummer.<br />
• Ein Client-Prozess stellt sicher, dass er eine eindeutige Adresse hat, an die ein<br />
Server seine Antworten schicken kann.<br />
Wird im zweiten Fall als Port-Nummer 0 angegeben, so weist das System dem Socket<br />
automatisch eine freie kurzlebige Port-Nummer zu. In beiden Fällen kann als Host-<br />
Adresse INADDR_ANY verwendet werden, was bedeutet, dass der Prozess bereit ist, auf<br />
allen Internet-Adressen des Systems Daten zu empfangen.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 99
Datenaustausch über unverbundene Sockets<br />
Netzwerk-Kommunikation<br />
#include <br />
#include <br />
ssize_t recvfrom(int sd, void *buf, size_t nbytes,<br />
int flags, struct sockaddr *addr,<br />
size_t *addrlen);<br />
ssize_t sendto(int sd, const void *buf, size_t nbytes,<br />
int flags, const struct sockaddr *addr,<br />
size_t addrlen);<br />
Mittels recvfrom(2) und sendto(2) können über den unverbundenen Socket sd<br />
Meldungen empfangen bzw. geschickt werden. Bei recvfrom(2) zeigt buf auf einen<br />
Speicherbereich, in den die empfangenen Daten abgelegt werden, bei sendto(2) auf<br />
einen Speicherbereich, der die zu schickenden Daten enthält. nbytes ist die maximale<br />
Anzahl zu empfangender bzw. zu schickender Bytes.<br />
Im Parameter flags kann u.a. die Option MSG_OOB gesetzt werden kann, um dringende<br />
Daten zu empfangen bzw. zu schicken. addr und addrlen enthalten nach der Rückkehr<br />
von recvfrom(2) die Adresse des Absenders und ihre Länge. Bei sendto(2) wird in<br />
addr und addrlen die Adresse des Empfängers und ihre Länge übergeben.<br />
Als Funktionswert geben recvfrom(2) und sendto(2) die Anzahl empfangener bzw.<br />
geschickter Bytes zurück.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 100
Netzwerk-Kommunikation<br />
#define _XOPEN_SOURCE_EXTENDED 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define MAXLINE 80<br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int<br />
sd;<br />
struct sockaddr_in server, client;<br />
size_t<br />
len;<br />
char<br />
line[MAXLINE];<br />
ssize_t n;<br />
if ( argc != 2 ) {<br />
fprintf( stderr, "Verwendung: %s port\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
/* Socket erzeugen */<br />
if ( (sd = socket( AF_INET, SOCK_DGRAM, 0 )) == -1 )<br />
panic( "socket" )<br />
/* Socket mit eigenen Adresse verknuepfen */<br />
len = sizeof( server );<br />
bzero( &server, len );<br />
server.sin_len<br />
= len;<br />
server.sin_family = AF_INET;<br />
server.sin_port = htons( atoi( argv[1] ) );<br />
server.sin_addr.s_addr = htonl( INADDR_ANY );<br />
if ( bind( sd, (struct sockaddr *)&server, len ) == -1 )<br />
panic( "bind" )<br />
printf( "Warte auf Meldungen auf Port %d...\n", ntohs( server.sin_port ) );<br />
while ( 1 ) {<br />
}<br />
}<br />
/* Meldung empfangen und zurueckschicken */<br />
if ( (n = recvfrom( sd, line, MAXLINE, 0,<br />
(struct sockaddr *)&client, &len )) == -1 )<br />
panic( "recvfrom" )<br />
printf( "Meldung von Client %s (Port %d) empfangen\n",<br />
inet_ntoa( client.sin_addr ), ntohs( client.sin_port ) );<br />
if ( sendto( sd, line, n, 0, (struct sockaddr *)&client, len ) == -1 )<br />
panic( "sendto" )<br />
Programm 10-1: Verbindungsloser Echo-Server<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 101
Netzwerk-Kommunikation<br />
#define _XOPEN_SOURCE_EXTENDED 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define MAXLINE 80<br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int<br />
sd;<br />
struct sockaddr_in client, server;<br />
size_t<br />
len;<br />
char<br />
line[MAXLINE];<br />
if ( argc != 3 ) {<br />
fprintf( stderr, "Verwendung: %s host port\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
/* Socket erzeugen */<br />
if ( (sd = socket( AF_INET, SOCK_DGRAM, 0 )) == -1 )<br />
panic( "socket" )<br />
/* Socket mit eigenen Adresse verknuepfen */<br />
len = sizeof( client );<br />
bzero( &client, len );<br />
client.sin_len<br />
= len;<br />
client.sin_family = AF_INET;<br />
client.sin_port = htons( 0 );<br />
client.sin_addr.s_addr = htonl( INADDR_ANY );<br />
if ( bind( sd, (struct sockaddr *)&client, len ) == -1 )<br />
panic( "bind" )<br />
/* Server-Adresse bestimmen */<br />
len = sizeof( server );<br />
bzero( &server, len );<br />
server.sin_len<br />
= len;<br />
server.sin_family = AF_INET;<br />
server.sin_port = htons( atoi( argv[2] ) );<br />
server.sin_addr.s_addr = inet_addr( argv[1] );<br />
while ( 1 ) {<br />
/* Meldung einlesen und schicken */<br />
printf( "> " );<br />
if ( fgets( line, MAXLINE, stdin ) == NULL )<br />
break;<br />
if ( sendto( sd, line, strlen( line ) + 1, 0,<br />
(struct sockaddr *)&server, len ) == -1 )<br />
panic( "sendto" )<br />
}<br />
/* Antwort empfangen und ausgeben */<br />
if ( recvfrom( sd, line, MAXLINE, 0,<br />
(struct sockaddr *)&server, &len ) == -1 )<br />
panic( "recvfrom" )<br />
fputs( line, stdout );<br />
/* Socket schliessen */<br />
if ( close( sd ) == -1 )<br />
panic( "close" )<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 10-2: Verbindungsloser Echo-Client<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 102
Netzwerk-Kommunikation<br />
Akzeptieren einer Verbindung<br />
#include <br />
#include <br />
int listen(int sd, int backlog);<br />
int accept(int sd, struct sockaddr *addr,<br />
size_t *addrlen);<br />
Mittels listen(2) kann ein Server-Prozess den Socket sd in einen passiven Zustand<br />
versetzen. Dabei wird eine Warteschlange bereitgestellt, in welcher die Verbindungsanforderungen<br />
von Client-Prozessen zwischengespeichert werden. backlog gibt die<br />
Grösse dieser Warteschlange an, wobei SOMAXCONN die Maximalgrösse ist.<br />
Mittels accept(2) kann ein Server-Prozess eine Verbindungsanforderung in der<br />
Warteschlange des passiven Sockets sd entgegennehmen und dadurch eine Verbindung<br />
mit dem entsprechenden Client-Prozess aufbauen. Ist die Warteschlange leer, so wird<br />
der Server-Prozess blockiert.<br />
In addr wird die Adresse des Client-Prozesses gespeichert, mit dem die Verbindung<br />
aufgebaut wurde. addrlen ist sowohl ein Argument- als auch ein Resultatparameter: Vor<br />
dem Aufruf wird addrlen auf die Länge der Adressstruktur addr gesetzt, nach der<br />
Rückkehr enthält addrlen die aktuelle Anzahl Bytes in addr.<br />
Als Funktionswert gibt accept(2) den Deskriptor eines neu erzeugten Sockets zurück.<br />
Dieser Socket ist mit dem Client verbunden und kann somit für den Datenaustausch mit<br />
diesem verwendet werden. Auf dem ursprünglichen Socket sd kann der Server-Prozess<br />
dagegen weitere Verbindungsanforderungen akzeptieren.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 103
Netzwerk-Kommunikation<br />
Aufbauen einer Verbindung<br />
#include <br />
#include <br />
int connect(int sd, const struct sockaddr *addr,<br />
size_t addrlen);<br />
Mittels connect(2) kann ein Client-Prozess über den Socket sd eine Verbindung zu<br />
einem Server-Prozess aufbauen. In addr wird die Adresse des Servers übergeben und<br />
in addrlen ihre Länge. Wurde der Socket sd vorher nicht mit der Adresse des Clients<br />
verknüpft, so geschieht dies implizit beim Aufruf von connect(2).<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 104
Datenaustausch über verbundene Sockets<br />
Netzwerk-Kommunikation<br />
#include <br />
#include <br />
ssize_t recv(int sd, void *buf, size_t nbytes,<br />
int flags);<br />
ssize_t send(int sd, const void *buf, size_t nbytes,<br />
int flags);<br />
Mittels recv(2) und send(2) können über den verbundenen Socket sd Daten empfangen<br />
bzw. geschickt werden. recv(2) und send(2) funktionieren gleich wie recvfrom(2) und<br />
sendto(2), ausser dass sie keine Parameter für die Adresse des Absenders bzw. des<br />
Empfängers haben.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 105
Netzwerk-Kommunikation<br />
#define _XOPEN_SOURCE_EXTENDED 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define MAXLINE 80<br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int<br />
sd, sd2;<br />
struct sockaddr_in server, client;<br />
size_t<br />
len;<br />
pid_t<br />
pid;<br />
char<br />
line[MAXLINE];<br />
ssize_t n;<br />
if ( argc != 2 ) {<br />
fprintf( stderr, "Verwendung: %s port\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
/* Verbindungssocket erzeugen */<br />
if ( (sd = socket( AF_INET, SOCK_STREAM, 0 )) == -1 )<br />
panic( "socket" )<br />
/* Socket mit eigenen Adresse verknuepfen */<br />
len = sizeof( server );<br />
bzero( &server, len );<br />
server.sin_len<br />
= len;<br />
server.sin_family = AF_INET;<br />
server.sin_port = htons( atoi( argv[1] ) );<br />
server.sin_addr.s_addr = htonl( INADDR_ANY );<br />
if ( bind( sd, (struct sockaddr *)&server, len ) == -1 )<br />
panic( "bind" )<br />
/* Socket in passiven Zustand versetzen */<br />
if ( listen( sd, SOMAXCONN ) == -1 )<br />
panic( "listen" )<br />
printf( "Akzeptiere Verbindungen auf Port %d...\n",<br />
ntohs( server.sin_port ) );<br />
while ( 1 ) {<br />
/* Verbindung von Client akzeptieren */<br />
len = sizeof( client );<br />
if ( (sd2 = accept( sd, (struct sockaddr *)&client, &len )) == -1 )<br />
panic( "accept" )<br />
printf( "Verbindung von Client %s (Port %d) akzeptiert\n",<br />
inet_ntoa( client.sin_addr ), ntohs( client.sin_port ) );<br />
/* Sohnprozess erzeugen */<br />
if ( (pid = fork()) == -1 )<br />
panic( "fork" )<br />
if ( pid == 0 ) {<br />
/* Verbindungssocket schliessen */<br />
if ( close( sd ) == -1 )<br />
panic( "close" )<br />
while ( 1 ) {<br />
}<br />
/* Meldung empfangen und zurueckschicken */<br />
if ( (n = recv( sd2, line, MAXLINE, 0 )) == -1 )<br />
panic( "recv" )<br />
if ( n == 0 ) break;<br />
if ( send( sd2, line, n, 0 ) == -1 )<br />
panic( "send" )<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 106
Netzwerk-Kommunikation<br />
/* Datensocket schliessen */<br />
if ( close( sd2 ) == -1 )<br />
panic( "close" )<br />
}<br />
exit( EXIT_SUCCESS );<br />
}<br />
}<br />
/* Datensocket schliessen */<br />
if ( close( sd2 ) == -1 )<br />
panic( "close" )<br />
/* terminierte Sohnprozesse abholen */<br />
while ( waitpid( -1, NULL, WNOHANG ) > 0 );<br />
Programm 10-3: Verbindungsorientierter Echo-Server<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 107
Netzwerk-Kommunikation<br />
#define _XOPEN_SOURCE_EXTENDED 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define MAXLINE 80<br />
#define panic( str ) { perror( str ); exit( EXIT_FAILURE ); }<br />
int main( int argc, char *argv[] )<br />
{<br />
int<br />
sd;<br />
struct sockaddr_in client, server;<br />
size_t<br />
len;<br />
char<br />
line[MAXLINE];<br />
if ( argc != 3 ) {<br />
fprintf( stderr, "Verwendung: %s host port\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
/* Socket erzeugen */<br />
if ( (sd = socket( AF_INET, SOCK_STREAM, 0 )) == -1 )<br />
panic( "socket" )<br />
/* Socket mit eigenen Adresse verknuepfen */<br />
len = sizeof( client );<br />
bzero( &client, len );<br />
client.sin_len<br />
= len;<br />
client.sin_family = AF_INET;<br />
client.sin_port = htons( 0 );<br />
client.sin_addr.s_addr = htonl( INADDR_ANY );<br />
if ( bind( sd, (struct sockaddr *)&client, len ) == -1 )<br />
panic( "bind" )<br />
/* Verbindung zum Server aufbauen */<br />
len = sizeof( server );<br />
bzero( &server, len );<br />
server.sin_len<br />
= len;<br />
server.sin_family = AF_INET;<br />
server.sin_port = htons( atoi( argv[2] ) );<br />
server.sin_addr.s_addr = inet_addr( argv[1] );<br />
if ( connect( sd, (struct sockaddr *)&server, len ) == -1 )<br />
panic( "connect" )<br />
printf( "Verbindung zu Server %s (Port %d) aufgebaut\n",<br />
inet_ntoa( server.sin_addr ), ntohs( server.sin_port ) );<br />
while ( 1 ) {<br />
/* Meldung einlesen und schicken */<br />
printf( "> " );<br />
if ( fgets( line, MAXLINE, stdin ) == NULL )<br />
break;<br />
if ( send( sd, line, strlen( line ) + 1, 0 ) == -1 )<br />
panic( "send" )<br />
}<br />
/* Antwort empfangen und ausgeben */<br />
if ( recv( sd, line, MAXLINE, 0 ) == -1 )<br />
panic( "recv" )<br />
fputs( line, stdout );<br />
/* Socket schliessen */<br />
if ( close( sd ) == -1 )<br />
panic( "close" )<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 10-4: Verbindungsorientierter Echo-Client<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 108
Netzwerk-Kommunikation<br />
Abfragen von Host-Informationen<br />
#include <br />
struct hostent *gethostbyname(const char *name);<br />
struct hostent *gethostbyaddr(const void *addr,<br />
size_t len, int type);<br />
struct hostent {<br />
char *h_name;<br />
char **h_aliases;<br />
int h_addrtype;<br />
int h_length;<br />
char **h_addr_list;<br />
};<br />
Mittels gethostbyname(3) und gethostbyaddr(3) kann Information über einen<br />
Internet-Host abgefragt werden. Diese wird stammt entweder aus dem File /etc/hosts<br />
oder wird von einem Name-Server erhalten. name ist der offizielle Name oder ein<br />
Aliasname des Hosts. addr ist ein Pointer auf eine Struktur vom Typ in_addr, welche<br />
die Internet-Adresse des Hosts enthält, und len gibt die Länge dieser Struktur an. Als<br />
type muss die Konstante AF_INET übergeben werden.<br />
Die Komponenten der Struktur hostent haben folgende Bedeutungen:<br />
h_name offizieller Name des Hosts<br />
h_aliases NULL-terminierte Liste der Aliasnamen<br />
h_addrtype Adresstyp<br />
h_length Länge der Adresse<br />
h_addr_list NULL-terminierte Liste der Internet-Adressen<br />
Es ist zu beachten, dass h_addr_list eigentlich ein Array von Pointern auf Strukturen<br />
vom Typ in_addr ist und nicht von Pointern auf char.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 109
Netzwerk-Kommunikation<br />
#define _XOPEN_SOURCE_EXTENDED 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
int main( int argc, char *argv[] )<br />
{<br />
struct in_addr inaddr;<br />
struct hostent *host;<br />
int i;<br />
if ( argc != 2 ) {<br />
fprintf( stderr, "Verwendung: %s host\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( (inaddr.s_addr = inet_addr( argv[1] )) != INADDR_NONE )<br />
host = gethostbyaddr( (char *)&inaddr, sizeof( inaddr ), AF_INET );<br />
else host = gethostbyname( argv[1] );<br />
if ( host == NULL ) {<br />
fprintf( stderr, "Host nicht gefunden\n" );<br />
exit( EXIT_FAILURE );<br />
}<br />
printf( "Offizieller Name : %s\n", host->h_name );<br />
for ( i = 0; host->h_aliases[i] != NULL; i++ )<br />
printf( "Aliasname : %s\n", host->h_aliases[i] );<br />
if ( host->h_addrtype == AF_INET )<br />
for ( i = 0; host->h_addr_list[i] != NULL; i++ ) {<br />
bcopy( host->h_addr_list[i], (char *)&inaddr, sizeof( inaddr ) );<br />
printf( "Internet-Adresse : %s\n", inet_ntoa( inaddr ) );<br />
}<br />
else fprintf( stderr, "unbekannter Adresstyp\n" );<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 10-5: Abfragen einer Host-Information<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 110
Netzwerk-Kommunikation<br />
Abfragen von Service-Informationen<br />
#include <br />
struct servent *getservbyname(const char *name,<br />
const char *proto);<br />
struct servent *getservbyport(int port,<br />
const char *proto);<br />
struct servent {<br />
char *s_name;<br />
char **s_aliases;<br />
int s_port;<br />
char *s_proto;<br />
};<br />
Mittels getservbyname(3) und getservbyport(3) kann die Information über einen<br />
Service abgefragt werden, die im File /etc/services steht. name ist der offizielle<br />
Name oder ein Aliasname des Services, port die Port-Nummer, die der Service hat, und<br />
proto der Name des zu verwendenden Protokolls.<br />
Die Komponeneten der Struktur servent haben folgende Bedeutungen:<br />
s_name offizieller Name des Services<br />
s_aliases NULL-terminierte Liste der Aliasnamen<br />
s_port Port-Nummer des Services<br />
s_proto Name des zu verwendenden Protokolls<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 111
Netzwerk-Kommunikation<br />
#define _XOPEN_SOURCE_EXTENDED 1<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
int main( int argc, char *argv[] )<br />
{<br />
int<br />
port;<br />
struct servent *service;<br />
int i;<br />
if ( argc < 2 ) {<br />
fprintf( stderr, "Verwendung: %s port|service [protocol]\n",<br />
argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
if ( (port = atoi( argv[1] )) != 0 )<br />
service = getservbyport( htons( port ), argv[2] );<br />
else service = getservbyname( argv[1], argv[2] );<br />
if ( service == NULL ) {<br />
fprintf( stderr, "Service nicht gefunden\n" );<br />
exit( EXIT_FAILURE );<br />
}<br />
printf( "Offizieller Name : %s\n", service->s_name );<br />
for ( i = 0; service->s_aliases[i] != NULL; i++ )<br />
printf( "Aliasname : %s\n", service->s_aliases[i] );<br />
printf( "Port-Nummer : %d\n", ntohs( service->s_port ) );<br />
printf( "Protokoll : %s\n", service->s_proto );<br />
}<br />
exit( EXIT_SUCCESS );<br />
Programm 10-6: Abfragen einer Service-Information<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 112
Netzwerk-Kommunikation<br />
Name<br />
recvmsg(2)<br />
sendmsg(2)<br />
getsockname(2)<br />
getpeername(2)<br />
getsockopt(2)<br />
setsockopt(2)<br />
shutdown(2)<br />
Beschreibung<br />
Empfangen von mehreren Meldungen<br />
Schicken von mehreren Meldungen<br />
Abfragen der lokalen Adresse eines<br />
Sockets<br />
Abfragen der fremden Adresse eines<br />
verbundenen Sockets<br />
Abfragen von Optionen eines Sockets<br />
Setzen von Optionen eines Sockets<br />
Abbauen einer Netzwerkverbindung<br />
Tabelle 10-1: Weitere Socket-System-Calls<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 113
Netzwerk-Kommunikation<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 114
11 Threads<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 115
Threads<br />
Threads eines Prozesses<br />
Prozess<br />
Thread<br />
Thread<br />
Register<br />
Register<br />
Adressraum<br />
Stack<br />
Stack<br />
Identifier<br />
Resources<br />
Heap<br />
Data<br />
Code<br />
Threads sind parallel ausführbare Folgen von Instruktionen innerhalb eines Prozesses.<br />
Jeder Thread hat einen eigenen Programm-Co<strong>unter</strong>, einen eigenen Stack und eine<br />
eigene Kopie der Prozessorregister. Die verschiedenen Threads laufen aber im gleichen<br />
Adressraum ab und haben somit Zugriff auf dieselben globalen Daten. Zudem stehen<br />
ihnen die gemeinsamen Prozess-Ressourcen wie offene Files, Netzwerkverbindungen<br />
usw. zur Verfügung.<br />
Threads haben gegenüber Prozessen den Vorteil, dass zu ihrer Verwaltung nur ein<br />
minimaler Overhead des <strong>Betriebssystem</strong>s notwendig ist. Zudem eignen sie sich zum<br />
Verteilen auf die Prozessoren eines Multiprozessor-Rechners.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 116
Threads<br />
POSIX-Threads<br />
Thread-Verwaltung<br />
pthread_create(3)<br />
pthread_exit(3)<br />
pthread_join(3)<br />
pthread_detach(3)<br />
Mutex-Locks<br />
pthread_mutex_init(3)<br />
pthread_mutex_destroy(3)<br />
pthread_mutex_lock(3)<br />
pthread_mutex_trylock(3)<br />
pthread_mutex_unlock(3)<br />
Condition-Variablen<br />
pthread_cond_init(3)<br />
pthread_cond_destroy(3)<br />
pthread_cond_wait(3)<br />
pthread_cond_timedwait(3)<br />
pthread_cond_signal(3)<br />
pthread_cond_broadcast(3)<br />
Im Juni 1995 wurde vom IEEE der definitive Standard POSIX.1c anerkannt, auch kurz<br />
Pthreads genannt. Er ist ein optionaler Teil von POSIX.1 und enthält die Spezifikation<br />
einer Thread-Bibliothek, die aus über 60 Funktionen besteht. Die meisten dieser<br />
Funktionen geben eine Integer-Zahl als Funktionswert zurück. Im Erfolgsfall ist dieser<br />
Wert 0, im Fehlerfall gibt er die Art des aufgetretenen Fehlers an.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 117
Threads<br />
Erzeugen eines Threads<br />
#include <br />
int pthread_create(pthread_t *tid,<br />
const pthread_attr_t *attr,<br />
void *(*routine)(void *),<br />
void *arg);<br />
Mittels pthread_create(3) kann ein Thread erzeugt werden. tid ist ein<br />
Resultatparameter, in dem der Identifier des erzeugten Threads zurückgegeben wird.<br />
attr ist ein Pointer auf ein Attributobjekt, in dem die Stackgrösse, die Stackadresse, der<br />
Detach-Zustand, der Scope, die Priorität und die Scheduling-Politik des Threads<br />
festgelegt werden können. Ist attr ein Nullpointer, so werden die Default-Attribute<br />
verwendet.<br />
routine ist eine Funktion, die als Startroutine des Threads ausgeführt wird. Sie hat<br />
einen void-Pointer als Parameter, dem der Wert von arg zugewiesen wird. Über diesen<br />
Pointer kann dem Thread eine beliebige globale Datenstruktur übergeben werden, wobei<br />
darauf zu achten ist, dass diese nicht durch einen andern Thread nachträglich<br />
überschrieben wird.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 118
Threads<br />
Terminieren eines Threads<br />
#include <br />
int pthread_exit(void *status);<br />
int pthread_join(pthread_t tid, void **status);<br />
int pthread_detach(pthread_t tid);<br />
Ein Thead terminiert, wenn er aus der Startroutine zurückkehrt. Dies geschieht entweder<br />
durch ein return-Statement oder mittels pthread_exit(3). Der Ausdruck des<br />
return-Statements bzw. der Parameter status von pthread_exit(3) ist der Exit-<br />
Status des Threads.<br />
Mittels pthread_join(3) kann ein Thread auf die Terminierung eines andern Threads<br />
warten. Im Parameter status erhält er dann den Exit-Status des terminierten Threads<br />
zurück.<br />
Muss nicht auf die Terminierung eines Threads gewartet werden, so kann er mittels<br />
pthread_detach(3) für die Löschung vorgesehen werden. In diesem Fall werden der<br />
Speicherplatz und der Exit-Status des Threads nach dessen Terminierung automatisch<br />
gelöscht.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 119
Threads<br />
Das folgende Programm liest gleichzeitig aus mehreren Files, indem für jedes File ein<br />
eigener Thread erzeugt wird.<br />
#define _POSIX_C_SOURCE 199506L<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define BUFSIZE 1024<br />
#define panic( str, code ) { \<br />
fprintf( stderr, "%s: %s\n", str, strerror( code ) ); exit( EXIT_FAILURE ); }<br />
void *reader( void *fd );<br />
int main( int argc, char *argv[] )<br />
{<br />
int nfiles, i;<br />
int *fd;<br />
pthread_t *tid;<br />
int rc;<br />
if ( argc < 2 ) {<br />
fprintf( stderr, "Verwendung: %s file1 [file2 ...]\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
nfiles = argc - 1;<br />
/* Files oeffnen */<br />
fd = malloc( nfiles*sizeof( int ) );<br />
for ( i = 0; i < nfiles; i++ )<br />
if ( (fd[i] = open( argv[i+1], O_RDONLY )) == -1 )<br />
panic( "open", errno )<br />
/* Threads erzeugen */<br />
tid = malloc( nfiles*sizeof( pthread_t ) );<br />
for ( i = 0; i < nfiles; i++ )<br />
if ( (rc = pthread_create( &tid[i], NULL, reader, &fd[i] )) != 0 )<br />
panic( "pthread_create", rc )<br />
/* auf Terminierung der Threads warten */<br />
for ( i = 0; i < nfiles; i++ )<br />
if ( (rc = pthread_join( tid[i], NULL )) != 0 )<br />
panic( "pthread_join", rc )<br />
}<br />
exit( EXIT_SUCCESS );<br />
void *reader( void *arg )<br />
{<br />
int fd = *(int *)arg;<br />
char buf[BUFSIZE];<br />
ssize_t n;<br />
while ( 1 ) {<br />
/* Daten aus File lesen und ausgeben */<br />
if ( (n = read( fd, buf, BUFSIZE )) == -1 )<br />
panic( "read", errno )<br />
if ( n == 0 ) break;<br />
if ( write( STDOUT_FILENO, buf, n ) == -1 )<br />
panic( "write", errno )<br />
}<br />
/* File schliessen */<br />
if ( close( fd ) == -1 )<br />
panic( "close", errno )<br />
}<br />
return NULL;<br />
Programm 11-1: I/O-Multiplexing mittels Threads<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 120
Threads<br />
Mutex-Locks<br />
Thread 1<br />
Thread 2<br />
pthread_mutex_lock(3)<br />
pthread_mutex_lock(3)<br />
pthread_mutex_unlock(3)<br />
pthread_mutex_unlock(3)<br />
Mit einem Mutex-Lock kann ein exklusiver Ausschluss zwischen Threads, die auf eine<br />
gemeinsame Ressource zugreifen, implementiert werden. Dazu muss ein Thread<br />
während des Zugriffs auf die Ressource den zugehörigen Mutex-Lock sperren. Versucht<br />
dann ein anderer Thread denselben Mutex-Lock zu sperren, so wird er blockiert, bis<br />
derjenige Thread, der den Mutex-Lock gesperrt hat, ihn wieder freigibt.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 121
Threads<br />
Initialisieren eines Mutex-Locks<br />
#include <br />
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;<br />
int pthread_mutex_init(pthread_mutex_t *mutex,<br />
const pthread_mutexattr_t *attr);<br />
int pthread_mutex_destroy(pthread_mutex_t *mutex);<br />
Bevor ein Mutex-Lock verwendet werden kann, muss er initialisiert werden. Dies<br />
geschieht entweder statisch bei der Definition der Mutex-Variablen durch den Initialisierer<br />
PTHREAD_MUTEX_INITIALIZER oder dynamisch mittels pthread_mutex_init(3).<br />
attr ist ein Pointer auf ein Attributobjekt, in dem verschiedene Attribute des Mutex-<br />
Locks festgelegt werden können. Ist attr ein Nullpointer, so werden die Default-Attribute<br />
verwendet.<br />
Wenn ein Mutex-Lock nicht mehr gebraucht wird, so kann er mittels<br />
pthread_mutex_destroy(3) zerstört werden.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 122
Threads<br />
Sperren und Freigeben eines Mutex-Locks<br />
#include <br />
int pthread_mutex_lock(pthread_mutex_t *mutex);<br />
int pthread_mutex_trylock(pthread_mutex_t *mutex);<br />
int pthread_mutex_unlock(pthread_mutex_t *mutex);<br />
Mittels pthread_mutex_lock(3) kann ein Thread einen Mutex-Lock sperren. Ist der<br />
Mutex-Lock bereits von einem andern Thread gesperrt, so wird der aufrufende Thread<br />
blockiert.<br />
Mittels pthread_mutex_unlock(3) kann ein Thread einen Mutex-Lock freigeben, den<br />
er zuvor gesperrt hat. Gibt es Threads, die versucht haben, den Mutex-Lock zu sperren<br />
und dabei blockiert wurden, so wird derjenige mit der höchsten Priorität geweckt.<br />
pthread_mutex_trylock(3) versucht ebenfalls einen Mutex-Lock zu sperren, kehrt<br />
aber sofort mit dem Funktionswert EBUSY zurück, falls er bereits gesperrt ist.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 123
Threads<br />
Condition-Variablen<br />
Thread 1<br />
Thread 2<br />
pthread_mutex_lock(3)<br />
pthread_mutex_lock(3)<br />
pthread_cond_wait(3)<br />
pthread_cond_signal(3)<br />
pthread_mutex_unlock(3)<br />
pthread_mutex_unlock(3)<br />
Mit einer Condition-Variablen kann ein Thread auf eine Bedingung warten. Da eine<br />
solche Bedingung meistens vom Zustand globaler Daten abhängt, wird eine Condition-<br />
Variable immer zusammen mit einem Mutex-Lock verwendet, der die Zugriffe auf die<br />
Daten schützt. Dieser Mutex-Lock wird dann beim Warten auf der Condition-Variablen<br />
implizit freigegeben, so dass ein anderer Thread ihn sperren und die Daten verändern<br />
kann. Wird dabei die Bedingung erfüllt, so kann der andere Thread dies dem wartenden<br />
Thread signalisieren. Der wartende Thread wird dadurch geweckt, kann aber erst<br />
weiterlaufen, nachdem er den zugehörigen Mutex-Lock implizit wieder gesperrt hat.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 124
Threads<br />
Initialisieren einer Condition-Variablen<br />
#include <br />
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;<br />
int pthread_cond_init(pthread_cond_t *cond,<br />
const pthread_condattr_t *attr);<br />
int pthread_cond_destroy(pthread_cond_t *cond);<br />
Bevor eine Condition-Variable verwendet werden kann, muss sie initialisiert werden. Dies<br />
geschieht entweder statisch bei der Definition der Condition-Variablen durch den<br />
Initialisierer PTHREAD_COND_INITIALIZER oder dynamisch mittels<br />
pthread_cond_init(3). attr ist ein Pointer auf ein Attributobjekt, in dem<br />
verschiedene Attribute der Condition-Variablen festgelegt werden können. Ist attr ein<br />
Nullpointer, so werden die Default-Attribute verwendet.<br />
Wenn eine Condition-Variable nicht mehr gebraucht wird, so kann sie mittels<br />
pthread_cond_destroy(3) zerstört werden.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 125
Threads<br />
Warten auf einer Condition-Variablen<br />
#include <br />
int pthread_cond_wait(pthread_cond_t *cond,<br />
pthread_mutex_t *mutex);<br />
int pthread_cond_timedwait(pthread_cond_t *cond,<br />
pthread_mutex_t *mutex<br />
const struct timespec *time);<br />
int pthread_cond_signal(pthread_cond_t *cond);<br />
int pthread_cond_broadcast(pthread_cond_t *cond);<br />
Mittels pthread_cond_wait(3) kann ein Thread auf einer Condition-Variablen warten.<br />
mutex ist der zur Condition-Variablen gehörende Mutex-Lock, der vom aufrufenden<br />
Thread gesperrt sein muss.<br />
Mittels pthread_cond_signal(3) kann ein Thread eine Condition-Variable<br />
signalisieren und dadurch einen auf ihr wartenden Thread wecken. Warten mehrere<br />
Threads, so wird derjenige mit der höchsten Priorität geweckt.<br />
Mittels pthread_cond_timedwait(3) kann ein Thread eine beschränkte Zeit auf einer<br />
Condition-Variablen warten. time ist der absolute Zeitpunkt, zu dem das Warten<br />
abgebrochen wird und pthread_cond_timedwait(3) mit dem Funktionswert<br />
ETIMEDOUT zurückkehrt.<br />
Mittels pthread_cond_broadcast(3) können alle Threads, die auf derselben<br />
Condition-Variablen warten, geweckt werden.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 126
Threads<br />
Das folgende Programm kopiert ein File, wobei für das Lesen des Files und das<br />
Schreiben des neuen Files je ein Thread erzeugt wird, die über einen Puffer die zu<br />
kopierenden Daten austauschen.<br />
#define _POSIX_C_SOURCE 199506L<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#define BUFSIZE 1024<br />
#define panic( str, code ) { \<br />
fprintf( stderr, "%s: %s\n", str, strerror( code ) ); exit( EXIT_FAILURE ); }<br />
void *reader( void *fd );<br />
void *writer( void *fd );<br />
char<br />
buffer[BUFSIZE];<br />
int length = 0;<br />
int done = 0;<br />
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;<br />
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;<br />
int main( int argc, char *argv[] )<br />
{<br />
int fd1, fd2;<br />
pthread_t tid1, tid2;<br />
int rc;<br />
if ( argc != 3 ) {<br />
fprintf( stderr, "Verwendung: %s sourcefile destfile\n", argv[0] );<br />
exit( EXIT_FAILURE );<br />
}<br />
/* Files oeffnen */<br />
if ( (fd1 = open( argv[1], O_RDONLY )) == -1 )<br />
panic( "open", errno )<br />
if ( (fd2 = creat( argv[2], S_IRUSR | S_IWUSR )) == -1 )<br />
panic( "creat", errno )<br />
/* Threads erzeugen */<br />
if ( (rc = pthread_create( &tid1, NULL, reader, &fd1 )) != 0 )<br />
panic( " pthread_create", rc )<br />
if ( (rc = pthread_create( &tid2, NULL, writer, &fd2 )) != 0 )<br />
panic( " pthread_create", rc )<br />
/* auf Terminierung der Threads warten */<br />
if ( (rc = pthread_join( tid1, NULL )) != 0 )<br />
panic( "pthread_join", rc )<br />
if ( (rc = pthread_join( tid2, NULL )) != 0 )<br />
panic( "pthread_join", rc )<br />
}<br />
exit( EXIT_SUCCESS );<br />
void *reader( void *arg )<br />
{<br />
int fd = *(int *)arg;<br />
ssize_t n;<br />
int rc;<br />
while ( !done ) {<br />
/* Mutex-Lock sperren */<br />
if ( (rc = pthread_mutex_lock( &mutex )) != 0 )<br />
panic( "pthread_mutex_lock", rc )<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 127
Threads<br />
/* auf nicht-vollen Puffer warten */<br />
while ( length >= BUFSIZE )<br />
if ( (rc = pthread_cond_wait( &cond, &mutex )) != 0 )<br />
panic( "pthread_cond_wait", rc )<br />
/* Daten in Puffer einlesen */<br />
if ( (n = read( fd, buffer + length, BUFSIZE - length )) == -1 )<br />
panic( "read", errno )<br />
length += n;<br />
if ( n == 0 ) done = 1;<br />
/* Bedingung signalisieren */<br />
if ( (rc = pthread_cond_signal( &cond )) != 0 )<br />
panic( "pthread_cond_signal", rc )<br />
}<br />
/* Mutex-Lock freigeben */<br />
if ( (rc = pthread_mutex_unlock( &mutex )) != 0 )<br />
panic( "pthread_mutex_unlock", rc )<br />
/* File schliessen */<br />
if ( close( fd ) == -1 )<br />
panic( "close", errno )<br />
}<br />
return NULL;<br />
void *writer( void *arg )<br />
{<br />
int fd = *(int *)arg;<br />
int rc;<br />
while ( !done || length > 0 ) {<br />
/* Mutex-Lock sperren */<br />
if ( (rc = pthread_mutex_lock( &mutex )) != 0 )<br />
panic( "pthread_mutex_lock", rc )<br />
/* auf nicht-leeren Puffer warten */<br />
while ( length == 0 && !done )<br />
if ( (rc = pthread_cond_wait( &cond, &mutex )) != 0 )<br />
panic( "pthread_cond_wait", rc )<br />
/* Daten aus Puffer ausgeben */<br />
if ( write( fd, buffer, length ) == -1 )<br />
panic( "write", errno )<br />
length = 0;<br />
/* Bedingung signalisieren */<br />
if ( (rc = pthread_cond_signal( &cond )) != 0 )<br />
panic( "pthread_cond_signal", rc )<br />
}<br />
/* Mutex-Lock freigeben */<br />
if ( (rc = pthread_mutex_unlock( &mutex )) != 0 )<br />
panic( "pthread_mutex_unlock", rc )<br />
/* File schliessen */<br />
if ( close( fd ) == -1 )<br />
panic( "close", errno )<br />
}<br />
return NULL;<br />
Programm 11-2: Kopieren eines Files mittels Threads<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 128
Threads<br />
Scheduling<br />
• Scope<br />
• Priorität<br />
• Scheduling-Politik<br />
Das Scheduling von Threads wird durch die Thread-Attribute Scope, Priorität und<br />
Scheduling-Politik beeinflusst.<br />
Der Scope legt fest, welche Threads sich gemeinsam um die Prozessorzeit bewerben:<br />
Hat ein Thread Prozess-Scope, so konkurriert er nur mit den Threads desselben<br />
Prozesses, hat er System-Scope, so konkurriert er mit allen Threads des Systems.<br />
Unter den sich konkurrierenden Threads wird die Prozessorzeit nach einem preemptive<br />
Prioritäten-Scheduling aufgeteilt. D.h. bei einem Kontext-Switch wird <strong>unter</strong> den<br />
lauffähigen Threads derjenige mit der höchsten Priorität ausgewählt, und ein laufender<br />
Thread wird <strong>unter</strong>brochen, wenn ein anderer Thread mit höherer Priorität lauffähig wird.<br />
Die Scheduling-Politik bestimmt, ob zusätzlich ein Kontext-Switch nach Ablauf eines<br />
bestimmten Zeitquantums erfolgt.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 129
Threads<br />
Weitere Möglichkeiten<br />
• Once-Mechanismus<br />
• Thread-spezifische Daten<br />
• Cancellation<br />
• Signal-Handling<br />
Die Pthreads-Bibliothek bietet folgende weitere Möglichkeiten:<br />
• Der Once-Mechanismus stellt ein spezielles Synchronisationsmittel dar, um<br />
eine Initialisierungsroutine genau einmal auszuführen, unabhängig davon,<br />
wieviele Threads die Routine aufrufen.<br />
• Thread-spezifische Daten sind Daten, die über einen sogenannten Key mit<br />
einem Thread verknüpft werden. Sie werden vor allem in Bibliotheken mit<br />
internen Zuständen gebraucht.<br />
• Cancellation ermöglicht, dass ein Thread einen andern Thread terminiert. Da<br />
ein Thread aber nicht zu einem beliebigen Zeitpunkt terminiert werden sollte,<br />
kann jeder Thread selbst festlegen, ob und wann er terminiert werden darf.<br />
• Beim Signal-Handling <strong>unter</strong>scheidet man zwei Arten von Signalen: synchrone,<br />
die bei einem Fehler entstehen, und asynchrone, die durch ein externes<br />
Ereignis ausgelöst werden. Synchrone Signale werden an den verursachenden<br />
Thread ausgeliefert, asynchrone an einen beliebigen Thread, der das<br />
entsprechende Signal nicht blockiert hat. Welche Signale ein Thread blockiert<br />
hat, steht in seiner Signalmaske.<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 130
Literatur<br />
[1] M.J. Bach<br />
The Design of the <strong>UNIX</strong> Operating System<br />
Prentice-Hall, 1986<br />
[2] S.J. Leffler, M.K. McKusick, M.J. Karels, J.S. Quarterman<br />
The Design and Implementation of the 4.3BSD <strong>UNIX</strong> Operating System<br />
Addison-Wesley, 1989<br />
[3] B.W. Kernighan, D.M. Ritchie<br />
The C Programming Language, Second Edition<br />
Prentice-Hall, 1988<br />
[4] D. Lewine<br />
POSIX Programmer’s Guide<br />
O’Reilly, 1992<br />
[5] B.O. Gallmeister<br />
POSIX.4: Programming for the Real World<br />
O’Reilly, 1995<br />
[6] W.R Stevens<br />
Advanced Programming in the <strong>UNIX</strong> Environment<br />
Addison-Wesley, 1992<br />
[7] W.R. Stevens<br />
<strong>UNIX</strong> Network Programming<br />
Prentice-Hall, 1990<br />
[8] K.A. Roobins, S. Robbins<br />
Practical <strong>UNIX</strong> Programming<br />
Prentice Hall, 1996<br />
[9] B. Nichols, D. Buttlar, J. Proulx Farrell<br />
Pthreads Programming<br />
O'Reilly, 1996<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 131
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 132
Index<br />
_exit(2)............................................. 51<br />
abort(3) ............................................ 51<br />
accept(2)........................................ 103<br />
access(2)......................................... 24<br />
alarm(3) ........................................... 71<br />
asctime(3).......................................... 6<br />
bind(2) ............................................. 99<br />
chdir(2) ............................................ 24<br />
chmod(2) ......................................... 24<br />
chown(2).......................................... 24<br />
close(2)............................................ 29<br />
closedir(3)........................................ 22<br />
connect(2)...................................... 104<br />
creat(2) ............................................ 28<br />
ctime(3).............................................. 6<br />
dup(2) .............................................. 35<br />
dup2(2) ............................................ 35<br />
execl(2)............................................ 50<br />
execle(2).......................................... 50<br />
execlp(2).......................................... 50<br />
execv(2)........................................... 50<br />
execve(2)......................................... 50<br />
execvp(2)......................................... 50<br />
exit(3)............................................... 51<br />
fcntl(2)........................................ 37, 83<br />
FD_CLR() ........................................ 81<br />
FD_ISSET()..................................... 81<br />
FD_SET() ........................................ 81<br />
FD_ZERO() ..................................... 81<br />
fork(2) .............................................. 47<br />
fpathconf(3) ..................................... 13<br />
fstat(2) ............................................. 19<br />
getcwd(3)......................................... 24<br />
getegid(2) ........................................ 45<br />
getenv(3) ......................................... 43<br />
geteuid(2) ........................................ 45<br />
getgid(2) .......................................... 45<br />
gethostbyaddr(3) ........................... 109<br />
gethostbyname(3) ......................... 109<br />
getopt(3) .......................................... 43<br />
getpeername(2)............................. 113<br />
getpgrp(2) ........................................ 57<br />
getpid(2) .......................................... 45<br />
getppid(2) ........................................ 45<br />
getpwnam(3)...................................... 5<br />
getpwuid(3)........................................ 5<br />
getservbyname(3) ......................... 111<br />
getservbyport(3) ............................ 111<br />
getsockname(2)............................. 113<br />
getsockopt(2)................................. 113<br />
getuid(2) .......................................... 45<br />
gmtime(3) .......................................... 6<br />
htonl(3) ............................................ 98<br />
htons(3) ........................................... 98<br />
inet_addr(3) ..................................... 92<br />
inet_ntoa(3) ..................................... 92<br />
kill(2) ................................................ 66<br />
link(2)............................................... 24<br />
listen(2).......................................... 103<br />
localtime(3)........................................ 6<br />
lseek(2)............................................ 33<br />
mkdir(2) ........................................... 24<br />
mkfifo(3) .......................................... 77<br />
ntohl(3) ............................................ 98<br />
ntohs(3) ........................................... 98<br />
open(2) ............................................ 27<br />
opendir(3) ........................................ 22<br />
pathconf(3) ...................................... 13<br />
pause(3) .......................................... 71<br />
perror(3) ............................................ 3<br />
pipe(2) ............................................. 75<br />
pthread_cond_broadcast(3) .......... 126<br />
pthread_cond_destroy(3) .............. 125<br />
pthread_cond_init(3) ..................... 125<br />
pthread_cond_signal(3) ................ 126<br />
pthread_cond_timedwait(3)........... 126<br />
pthread_cond_wait(3) ................... 126<br />
pthread_create(3).......................... 118<br />
pthread_detach(3) ......................... 119<br />
pthread_exit(3) .............................. 119<br />
pthread_join(3) .............................. 119<br />
pthread_mutex_destroy(3) ............ 122<br />
pthread_mutex_init(3) ................... 122<br />
pthread_mutex_lock(3) ................. 123<br />
pthread_mutex_trylock(3) ............. 123<br />
pthread_mutex_unlock(3) ............. 123<br />
read(2)............................................. 31<br />
readdir(3)......................................... 22<br />
recv(2) ........................................... 105<br />
recvfrom(2) .................................... 100<br />
recvmsg(2) .................................... 113<br />
rename(2)........................................ 24<br />
rewinddir(3) ..................................... 22<br />
rmdir(2)............................................ 24<br />
S_ISBLK() ....................................... 19<br />
S_ISCHR() ...................................... 19<br />
S_ISDIR() ........................................ 19<br />
S_ISFIFO() ...................................... 19<br />
S_ISREG() ...................................... 19<br />
select(2)........................................... 80<br />
send(2) .......................................... 105<br />
sendmsg(2) ................................... 113<br />
sendto(2) ....................................... 100<br />
setpgid(2) ........................................ 57<br />
setsid(2)........................................... 58<br />
setsockopt(2)................................. 113<br />
shutdown(2)................................... 113<br />
sigaction(2)...................................... 67<br />
sigaddset(3)..................................... 67<br />
sigdelset(3)...................................... 67<br />
sigemptyset(3)................................. 67<br />
sigfillset(3) ....................................... 67<br />
sigismember(3)................................ 67<br />
sigpending(2)................................... 69<br />
sigprocmask(2)................................ 69<br />
sleep(3) ........................................... 71<br />
socket(2).......................................... 97<br />
stat(2) .............................................. 19<br />
strerror(3) .......................................... 3<br />
strftime(3) .......................................... 6<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 133
sysconf(3) ........................................ 13<br />
tcgetpgrp(2) ..................................... 59<br />
tcsetpgrp(2) ..................................... 59<br />
time(2) ............................................... 6<br />
umask(2).......................................... 24<br />
unlink(2)..................................... 24, 29<br />
utime(2) ........................................... 24<br />
wait(2).............................................. 52<br />
waitpid(2)......................................... 52<br />
WEXITSTATUS() ............................ 53<br />
WIFEXITED() .................................. 53<br />
WIFSIGNALED() ............................. 53<br />
WIFSTOPPED() .............................. 53<br />
write(2) ............................................ 31<br />
WSTOPSIG()................................... 53<br />
WTERMSIG() .................................. 53<br />
<strong>Betriebssystem</strong>-<strong>Programmierung</strong> 134