Albert: Wie programmiert man einen Daemon in C?

Moin!

Ich bringe mir gerade ein bisschen C bei und versuche nun unter Linux einen einfachen Daemon zu programmieren. Bisher schaut das ganze ungefähr so aus:

...
fork(); // abfrage ob erfolgreich ...
setsid();
umask(0);
chdir("/");
...

Wenn ich nun aber das Programm in der Konsole starte: ./program so gibt es mir nach wie vor den Prompt erst nach einem strg+C wieder zurück. Da es aber offensichtlich weiterläuft (siehe ps) denke ich, dass es insgesamt aber funktioniert.

Kennt ihr eine gute Anleitung hierzu? Bzw. vielleicht sogar ien Beispielprojekt auf sourceforge, etc. wo man sich das mal "live" anschauen kann?

Danke, Albert

    1. Hello,

      ?

      Es gibt noch mehr...
       http://forum.unixfreunde.de/anwendungsentwicklung-und-netzwerke/entwicklung-allgemein/1564-daemon-programmieren-c/
      http://pronix.linuxdelta.de/C/Linuxprogrammierung/Linuxsystemprogrammieren_C_Kurs_Kapitel3.shtml

      Dabei wundert mich immer nur, dass in der Schleife keine Signale verarbeitet werden...
      Irgendwie muss man das Ding ja auch wieder auf ordentliche Art anhalten können

      Liebe Grüße aus Syburg

      Tom vom Berg

      --
      Nur selber lernen macht schlau
      http://bergpost.annerschbarrich.de
  1. Hallo,

    fork(); // abfrage ob erfolgreich ...
    [...]
    Wenn ich nun aber das Programm in der Konsole starte: ./program so gibt es mir nach wie vor den Prompt erst nach einem strg+C wieder zurück. Da es aber offensichtlich weiterläuft (siehe ps) denke ich, dass es insgesamt aber funktioniert.

    Du hast fork() nicht so ganz verstanden: fork() dupliziert den Prozess. Sowohl der duplizierte als auch der ursprüngliche Prozess laufen dann weiter. Beide laufen aber an der gleichen Stelle weiter. Das heißt: Je nach Rückgabewert von fork() kannst Du feststellen, in welchem Prozess Du bist!

    Beispiel:

    pid_t pid;  
      
    /* ... */  
      
    pid = fork ();  
      
    if (pid < 0) {  
      /* Ein Fehler ist aufgetreten. Zum Beispiel wurde das user-spezifische  
         Prozesslimit erreicht. */  
    } else if (pid == 0) {  
      /* Dieser Code wird im Kindprozess ausgeführt. */  
    } else {  
      /* Dieser Code wird im Elternprozess ausgeführt, pid ist die PID  
         des Kindprozesses. */  
    }  
      
    /* Dieser Code wird in beiden Prozessen ausgeführt wenn in den  
       if/else-Blöcken keine return-Anweisung o.ä. enthalten ist. */
    

    Ansonsten war das, was Du sonst so getan hast, gar nicht so verkert: setsid() erstellt eine neue Session (unter UNIX heißt das was anderes als im Webkontext ;-)), umask(0) löscht die aktuelle Umask, chdir ("/") wechselt in das Hauptverzeichnis.

    Was Du noch machen solltest:

    close (STDIN_FILENO);  
    close (STDOUT_FILENO);  
    close (STDERR_FILENO);
    

    setsid() sorgt zwar bereits dafür, dass Du kein Kontrollterminal mehr besitzt, aber die Dateideskriptoren sind noch offen - d.h. Du hast das aktuelle Terminal noch geöffnet. Du willst aber als Daemon garantiert nicht auf das Terminal schreiben, von dem Du gestartet wurdest (der soll ja im Hintergrund laufen) - d.h. Du willst die 3 Standarddateideskriptoren schließen.

    Sonst fällt mir ad hoc nichts mehr zur Daemonisierung ein, das sollte eigentlich ausreichen, wenn mich nicht alles täuscht. Halt bei jeder Operation noch die Rückgabewerte prüfen, um zu sehen, ob alles geklappt hat. Außer bei close() - es könnte nämlich sein, dass die von vorne herein schon nicht offen sind, dann funktioniert das Schließen natürlich nicht, was in dem Falle aber egal ist.

    Viele Grüße,
    Christian

    1. Danke Christian für deine Antwort :-)

      Hallo,

      »» fork(); // abfrage ob erfolgreich ...
      »» [...]
      »» Wenn ich nun aber das Programm in der Konsole starte: ./program so gibt es mir nach wie vor den Prompt erst nach einem strg+C wieder zurück. Da es aber offensichtlich weiterläuft (siehe ps) denke ich, dass es insgesamt aber funktioniert.

      Du hast fork() nicht so ganz verstanden: fork() dupliziert den Prozess. Sowohl der duplizierte als auch der ursprüngliche Prozess laufen dann weiter. Beide laufen aber an der gleichen Stelle weiter. Das heißt: Je nach Rückgabewert von fork() kannst Du feststellen, in welchem Prozess Du bist!

      Beispiel:

      pid_t pid;

      /* ... */

      pid = fork ();

      if (pid < 0) {
        /* Ein Fehler ist aufgetreten. Zum Beispiel wurde das user-spezifische
           Prozesslimit erreicht. /
      } else if (pid == 0) {
        /
      Dieser Code wird im Kindprozess ausgeführt. /
      } else {
        /
      Dieser Code wird im Elternprozess ausgeführt, pid ist die PID
           des Kindprozesses. */
      }

      /* Dieser Code wird in beiden Prozessen ausgeführt wenn in den
         if/else-Blöcken keine return-Anweisung o.ä. enthalten ist. */

      
      >   
      > Ansonsten war das, was Du sonst so getan hast, gar nicht so verkert: setsid() erstellt eine neue Session (unter UNIX heißt das was anderes als im Webkontext ;-)), umask(0) löscht die aktuelle Umask, chdir ("/") wechselt in das Hauptverzeichnis.  
      >   
      > Was Du noch machen solltest:  
      >   
      > ~~~c
      
      close (STDIN_FILENO);  
      
      > close (STDOUT_FILENO);  
      > close (STDERR_FILENO);
      
      

      setsid() sorgt zwar bereits dafür, dass Du kein Kontrollterminal mehr besitzt, aber die Dateideskriptoren sind noch offen - d.h. Du hast das aktuelle Terminal noch geöffnet. Du willst aber als Daemon garantiert nicht auf das Terminal schreiben, von dem Du gestartet wurdest (der soll ja im Hintergrund laufen) - d.h. Du willst die 3 Standarddateideskriptoren schließen.

      Sonst fällt mir ad hoc nichts mehr zur Daemonisierung ein, das sollte eigentlich ausreichen, wenn mich nicht alles täuscht. Halt bei jeder Operation noch die Rückgabewerte prüfen, um zu sehen, ob alles geklappt hat. Außer bei close() - es könnte nämlich sein, dass die von vorne herein schon nicht offen sind, dann funktioniert das Schließen natürlich nicht, was in dem Falle aber egal ist.

      Jetzt hätte ich nur noch eine Frage: Bei einigen Seiten, die ich in der Zwischenzeit gefunden habe, wird zweimal geforkt: z.B. hier http://www.pronix.de/pronix-170.html Warum ist dies nötig? Hierdurch wird doch von dem Terminallosen Kindprozess doch bloß ein weiteres Kind erzeugt oder? Irgendwie will mir dies nicht ganz einleuchten ...

      Albert

      1. Hallo,

        Jetzt hätte ich nur noch eine Frage: Bei einigen Seiten, die ich in der Zwischenzeit gefunden habe, wird zweimal geforkt: z.B. hier http://www.pronix.de/pronix-170.html Warum ist dies nötig? Hierdurch wird doch von dem Terminallosen Kindprozess doch bloß ein weiteres Kind erzeugt oder? Irgendwie will mir dies nicht ganz einleuchten ...

        Damit wird sichergestellt, dass man nie wieder ein Kontrollterminal acquirieren kann, weil man als nochmal geforkter Prozess nicht mehr Chef der Sitzung ist. Ich persönlich halte das für irgendwie überflüssig (man bekommt ja nicht aus heiterem Himmel ein Kontrollterminal - man muss das ja explizit wollen). Vermutlich ist in der Vergangenheit mal jemand auf die Schnauze geflogen weil in irgend einer Konstellation ein Systemaufruf plötzlich zu einem Kontrollterminal geführt hat, was dann Probleme verursacht hat. Schaden kann's jedenfalls nicht.

        Viele Grüße,
        Christian

  2. Moin Moin!

    Ich überlasse den daemontools (mehr...) die schmutzige Arbeit inklusive Logging und kann mich dafür in meiner Software auf das Wesentliche konzentrieren. Weniger Code, weniger Fehler. Und ganz nebenbei kann man die Software auch direkt auf der Kommandozeile testen.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".