08.09.2014 Aufrufe

Betriebssystem-Programmierung unter UNIX (1997)

Betriebssystem-Programmierung unter UNIX (1997)

Betriebssystem-Programmierung unter UNIX (1997)

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

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

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!