Prozesse mit der Organisation eines Token-Rings

Content

Das Vorlesungsbeispiel pingpong.c lässt sich erweitern, indem nicht nur zwei Prozesse sich einander den Ball zuspielen, sondern \(n\) Prozesse beteiligt sind. Dabei kennt jeder der \(n\) Prozesse den nachfolgenden Prozess, wobei die Prozesse in einem Ring organisiert sind. D.h. bei den Prozessen \(P_0, \dots, P_{n-1}\) hat \(P_1\) den Prozess \(P_0\) als Nachfolger, \(P_2\) den Prozess \(P_1\) und \(P_0\) den Prozess \(P_{n-1}\).

Wie lässt sich der Ring organisieren? Zunächst wird \(P_0\) erzeugt. Dieser wiederum erzeugt die Kinder \(P_1, \dots, P_{n-1}\). Jedem der Kindprozesse wird der Nachfolger mitgeteilt. Am Ende akzeptiert \(P_0\) den zuletzt erzeugten Prozess \(P_{n-1}\) als Nachfolger, womit der Ring geschlossen ist.

Es gibt nun genau ein Token, der zu Beginn bei \(P_0\) liegt, der über das Signal SIGUSR1 an \(P_{n-1}\) übergibt, der das wiederum an \(P_{n-2}\) übermittelt etc. bis schließlich \(P_1\) das Signal an \(P_0\) übermittelt, womit eine Runde geschlossen ist.

Aufgabe

Entwickeln Sie eine Funktion tokenring mit folgender Schnittstelle:

pid_t create_tokenring(unsigned int members);

Der Parameter members spezifiziert \(n\), zurückzugeben ist dabei die Prozess-ID von \(P_0\). Der Ring soll über SIGUSR1 das Token im untereinander wie beschrieben weitergeben. Zu Beginn soll dies wie im originalen pingpong.c dadurch terminiert werden, dass das Token nicht mehr als zehnmal weitergegeben wird. Sollte der Nachfolger nicht mehr existieren, wird ebenfalls sofort aufgehört.

Ergänzen Sie das mit einem Testprogramm, das die Funktion aufruft und anschließend mit waitpid darauf wartet, dass \(P_0\) terminiert.

Damit Sie mitbekommen, ob der Ring funktioniert, sollten Sie eine Testausgabe hinzufügen, die ein Prozess zusammen mit der Prozess-ID ausgibt, sobald der Prozess im Besitz des Tokens ist.

Im Fehlerfalle sollten die neu erzeugten Prozesse mit dem Exit-Code 255 terminieren.

Fragen dazu

Vorlage

Hier ist das Beispiel aus der Vorlesung. Es wäre aber angemessen, statt signal die Funktion sigaction zu verwenden. Es genügt ein traditioneller Signalbehandler mit einem Parameter. Entsprechend ist in der struct sigaction die Komponente sa_handler zu initialisieren.

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static volatile sig_atomic_t ball_count = 0;

void sighandler(int sig) {
   ++ball_count;
   if (signal(sig, sighandler) == SIG_ERR) _Exit(1);
}

static void playwith(pid_t partner) {
   for(int i = 0; i < 10; ++i) {
      if (!ball_count) pause();
      printf("[%d] send signal to %d\n",
         (int) getpid(), (int) partner);
      if (kill(partner, SIGUSR1) < 0) {
         printf("[%d] %d is no longer alive\n",
            (int) getpid(), (int) partner);
         break;
      }
      --ball_count;
   }
   printf("[%d] finishes playing\n", (int) getpid());
}

int main() {
   /* this signal setting is inherited to our child */
   if (signal(SIGUSR1, sighandler) == SIG_ERR) {
      perror("signal SIGUSR1"); exit(1);
   }

   pid_t parent = getpid(); 
   pid_t child = fork();
   if (child < 0) {
      perror("fork"); exit(1);
   }
   if (child == 0) {
      ball_count = 1; /* give the ball to the child... */
      playwith(parent);
   } else {
      playwith(child);
   }
}