ein Kapitel zurück                                           ein Kapitel weiter

Kommen wir nun zum Blockieren von Prozessen durch gegenseitiges Aussperren (Deadlock). Dies kann dadurch entstehen in dem zum Beispiel Prozess Nummer 1 versucht in einer Datei, nennen wir sie dead.txt, bei Byte 10 eine Sperre einrichtet.

Ein weiterer Prozess, wir geben ihm die Nummer 2, richtet nun in der selben Datei (dead.txt) bei Byte 20 eine Sperre ein. Soweit ist alles noch OK. Nun will der will der Prozess 1 eine weitere Sperre einrichten bei Byte 20 wo bereits der Prozess Nummer 2 eine Sperre eingerichtet hat. Wir benutzen dabei das Kommando F_SETLKW. Damit wird unser Prozess Nummer 1 solange suspendiert bis der Prozess mit der Nummer 2 seinerseits diese Sperre bei Byte 20 wieder freigibt. Bis hierhin ist immer noch alles in Ordnung. Jetzt richtet der Prozess Nummer 2 bei Byte 10, wo Prozess Nummer 1 bereits eine Sperre eingerichtet hat, ebenfalls eine Sperre mit dem Kommando F_SETLKW ein und wird ebenso suspendiert und wartet bis Prozess Nummer 1 die Sperre wieder aufhebt. Nun da beide Prozesse, Nummer 1 und Nummer 2, nun suspendiert sind und beide darauf warten (F_SETLKW) auf die Freigabe der Sperre eines anderen haben wir ein Deadlock. Keiner der Prozesse wird hier jemals mehr aufgeweckt.

Auf Grund das diese Situation passieren kann so gering ist, hat man unter UNIX keine Mechanismen dazu geschrieben die eine Deadlock erkennen könnten. Solch ein Erkennungs- und Vermeidungsverfahren würde nur unnötig die Performance des Betriebssystem beeinträchtigen. Das heißt also wieder das wir wieder selbst dafür Verantwortlich sind.

Hierzu nun ein Programmbeispiel wie sie ein Deadlock aufspüren können.........

/*Download:lock4.c*/
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <errno.h> extern int errno; void status(struct flock *lock) { printf("Status: "); switch(lock->l_type) { case F_UNLCK: printf("F_UNLCK\n"); break; case F_RDLCK: printf("F_RDLCK (pid: %d)\n", lock->l_pid); break; case F_WRLCK: printf("F_WRLCK (pid: %d)\n", lock->l_pid); break; default : break; } } void schreibsperre(char *prozess, int fd, off_t von, off_t bis) { struct flock lock; lock.l_type = F_WRLCK; lock.l_start=von; lock.l_whence = SEEK_SET; lock.l_len=bis; lock.l_pid = getpid(); if (fcntl(fd, F_SETLKW, &lock) < 0) { printf("%s : fcntl(fd, F_SETLKW, F_WRLCK) failed (%s)\n", prozess,strerror(errno)); printf("\nDEADLOCK erkannt (%s - prozess)!!!!!!!!!\n\n",prozess); exit(0); } else printf("%s : fcntl(fd, F_SETLKW, F_WRLCK) succeeded\n",prozess); status(&lock); } int main() { int fd, i; pid_t pid; if(( fd=creat("dead.txt", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) <0) { fprintf(stderr, "Fehler bei creat......\n"); exit(0); } /*Beschreiben dead.txt mit 50 Bytes mit dem Buchstaben X*/ for(i=0; i<50; i++) write(fd, "X", 1); if((pid = fork()) <0) { fprintf(stderr, "Fehler bei fork()......\n"); exit(0); } else if(pid == 0) //Kindprozess { schreibsperre("Kind ", fd, 20, 0); sleep(3); schreibsperre("Kind" , fd, 0, 20); } else //Elternprozess { schreibsperre("Eltern ", fd, 0, 20); sleep(1); schreibsperre("Eltern " , fd, 20, 0); } exit(0); }

In diesem Programmbeispiel erzeugen wir mit Absicht ein Deadlock. Wir erzeugen eine Datei dead.txt und beschreiben diese mit 50 Buchstaben (X).

Dann richtet unser Elternprozess eine Sperre von Byte 0 bis Byte 19 ein und unser Kindprozess errichtet eine Sperre von Byte 20 bis zum Ende (EOF). Wir legen unseren Kindprozess für 3 sec. zum Schlafen damit richtet nun unser Elternprozess eine Sperre von Byte 20 bis Byte EOF ein und wird dabei suspendiert, da Byte 20 bis EOF momentan noch vom Kindprozess gesperrt wird und wir das Kommando F_SETLKW benutzen. Zum Schluss versucht unser Kindprozess eine Schreibsperre von Byte 0 bis Byte 19 einzurichten wobei dieser ebenfalls suspendiert wird, da in diesem Bereich bereits eine Sperre von dem Elternprozess vorliegt und wir das Kommando F_SETLKW benutzt haben. Hier haben wir nun ein Deadlock, was uns die Ausgabe auch bestätigen wird. Der Fehlercode für errno ist EDEADLK (Resource deadlock avoided). Deadlock können also nur Auftreten wenn sie das Kommando F_SETLKW benutzen. Sie können wenn sie wollen das Programm mal mit dem Kommando F_SETLK testen. In diesem Fall ist der Fehlercode für errno EAGAIN (Resource temporarily unavailable).

Exkurs: Deadlock

Deadlock ist ein Thema von den man sagt, es passiert mir sowieso nie, wozu soll ich mich darum kümmern. Deadlocks können in unterschiedlichen Arten von Computersystemen auftreten, wie Beispielsweise in Betriebssystemen, in Datenbanken oder Rechnernetzwerken. Egal wo sie auftreten, im Aufbau sind sich diese alle ähnlich.

Deadlock sind klassische Syncronisationsprobleme bei denen eine Situation auftritt, in der sich mehrer Prozesse um die selben Resourcen streiten und dabei nicht mehr weiterkommen. Ein Beispiel und Problemlösung haben sie ja bereits oben kennengelernt.

Ein weitere Möglichkeit Deadlocks zu verhindern wäre es Resourcen durchzunummerieren und in Aufsteigender Form anzufordern. Ist eine Resource nicht freigegeben müssen alle anderen bisher angeforderten Resourcen ebenfalls wieder freigegeben werden. Wenn sich alle Prozesse daran halten, kann kein Deadlock entstehen.

Wann könnten Deadlocks auftreten?
Ob es sich überhaupt lohnt sich Gedanken darüber zu machen klären die nächsten 4 Punkte auf. Trifft einer der 4 Punkte zu, kann theoretisch ein Deadlock auf Ihrem System auftreten.

  1. Wechselseitiger Ausschluß (mutual exclusion) : Resourcen sind nur für einen Prozeß nutzbar. Jeder Prozeß kann somit nur auf diese eine Resource, oder auf keine zugreifen.
  2. Nichtentziehbarkeit (non preemtion) : Resourcen können von Prozessen nicht entzogen werden. Wenn ein Prozess erst mal eine Resource besitz kann nur dieser Diese wieder freigeben, falls er die Resource nicht mehr benötigt.
  3. Wartebedienung (hold&wait) : Dies ist das klassische Problem von unserem Programmbeispiel. Hat ein Prozeß eine Resource die Betriebsmittel enthalten um weiterzumachen, während andere Prozesse ebenfalls auf die Freigabe weitere Betriebsmittel warten.
  4. Kreisbedienung (circular wait) : Dies ist eine Kette von Prozesse bei der jeder Prozeß auf die Betriebsmittel wartet die sein Vorgänger oder Nachfolger im freigibt.

Natürlich treten Deadlock häufiger auf Systemen bei denen mehr Prozesse in einem System aktiv sind als normal und auf dem weniger Resourcen für Betriebsmittel vorhanden sind.

Verhindern von Deadlocks
Außer den vorhin genannten Möglichkeiten, könen sie folgende Möglichkeiten durchdenken um Deadlocks zu verhindern....

  1. Es wird nur eine Resource zur Verfügung gestellt. Andere Prozesse die darauf zugreifen wollen werden in einem Wartezustand gesetzt. Somit ist eine 2.Resource nötig die dies übernimmt (Wartezustand).
  2. Alle Resourcen eines Prozesses werden schon zum Start vergeben. Der Prozess kann also nur dann starten wenn er Zugriff auf diese Resourcen hat. Somit fällt der Wartezustand weg. Damit fallen die Nachforderungen und hold&wait-Situationen weg.
  3. Bevor ein Prozess eine Resource anfordern kann muss diese freigegeben sein.

Livelocks
Livelocks sind Prozesse die in einer ständigen Aktivität gefangen sind. Sie kommen Beispielsweise nur zur Ruhe, wenn alles anderen Prozesse ebenfalls zur Ruhe kommen. Und natürlich anderherum genauso. Ein Beispiel wären Beispielsweise 2 Prozesse die immer wieder Daten hin-und-herschicken ohne das dabei etwas herauskommt. Eine Art Endlosschleife zwischen 2 Prozessen.

ein Kapitel zurück          nach oben           ein Kapitel weiter


© 2001,2002 Jürgen Wolf