exec und rückmeldung im parent ohne zombie



  • Hi
    ich starte einen child-prozess (hintergrund) und möchte den parent informieren, wenn das starten fehlgeschlagen ist.

    wie stell ich das am sinnvollsten an?

    ich habe eine funktion "bool exec(command...)"

    aktuell bleibt noch ein zombi hängen, wenn der client-prozess beendet wird. ich hab da schon einiges drüber gelesen, aber außer warten, dass der prozess beendet wird, habe ich keine lösung gefunden...und warten will ich nicht. da könnte ich auch gleich system nehmen.
    ich habe auch versucht innerhalb des geforkten prozesses nochmal zu forken (hab irgendwo gelesen, dass der eigentliche child-prozess dann von init übernommen wird)

    bool Exec(string command,string parameters,string file)
    {
      //hier wird das args-array zusammengebaut
    
      pid_t pid=fork();
      if (pid==0)//child-process
      {
          if(execvp(command.c_str(), args) == -1) 
          {
            perror("execvp");
            _Exit(EXIT_FAILURE);
            ret=false; 
          }
      } else if (pid==-1)
        g_print("problem while forking\n");//return false; //problem while forking
      else wait();
    }
    }
    

    Gruß Frank



  • in dem thread hier wird eine mögliche Lösung angesprochen...
    http://cboard.cprogramming.com/linux-programming/106762-fork-zombie-processes.html#post784641

    the child must end and the parent must wait on it to remove the zombie. To avoid being blocked in the parent, wait for SIGCHLD and do the wait in the handler.

    wie stell ich das an? wie installier ich so einen handler?

    dann hab ich noch das gefunden:
    http://thinkiii.blogspot.com/2009/12/double-fork-to-avoid-zombie-process.html
    damit habe ich keinen zombi mehr, mein Child-Programm läuft eigenständig (nicht als child meines parents)

    kann ich in der kette abfragen, ob exec korrekt ausgeführt wurde und dieses an den parent-prozess weitergeben?

    pid_t pid1;
      pid_t pid2;
      int status;
    
      if ((pid1 = fork()))
      { /* parent process A */
        waitpid(pid1, &status, NULL);
      } else if (!pid1)
      {/* child process B */
        if ((pid2 = fork()))
        {/* Parent of child process B */
          exit(0);
        } else if (!pid2)
        {/* child process C */
          //execvp("something");
          if(execvp(command.c_str(), args) == -1)
          {
            perror("execvp");
            _Exit(EXIT_FAILURE);
            ret=false;
          }
        } else
        {
          /* error */
          g_print("problem while forking (2)\n");
        }
      } else
      {
        /* error */
        g_print("problem while forking (1)\n");
      }
    

    Gruß Frank



  • Hi,

    irgendwie wirkt das alles etwas seltsam und ich bin mir nicht sicher, ob ich dich richtige verstehe, aber folgende Ideen:
    In deinem zweiten Codebeispiel wird der Fehlerfall möglicherweise nie behandelt. So wie es im ersten Fall ist, ist es auf jedenfall auch besser verständlich denke ich:

    pid_t pid = fork();
    if (pid > 0) {
    // Vater
    } else if (pid == 0) {
    // Kind
    } else {
    // Fehler (also Vater)
    }
    

    (kann man auch mit switch ganz gut machen)
    Wenn du in den letzten Fall gelangst, weiß dein Vater, dass das forken fehlgeschlagen ist.

    SIGNAL-Handler:

    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCHLDSTOP;
    sa.sa_handle = func;
    sigaction(SIGCHLD, &sa, NULL);
    

    func ist eine Funktion die einfach ein wait macht und damit den Zombie entfernt.
    func wird aufgerufen sobald sich das Kind terminiert. Danach macht der Vater da weiter wo er vorher aufgehört hat.

    Das feststellen, ob dein Kind richtig arbeitet wird nicht ganz so einfach, weil es einfach 2 verschiedene Prozesse sind. Also brauchst du irgendene möglichkeit zwischen Prozessen zu kommunizieren(Signals, Pipes, ...).



  • DerBaer schrieb:

    Hi,

    irgendwie wirkt das alles etwas seltsam und ich bin mir nicht sicher, ob ich dich richtige verstehe, aber folgende Ideen:
    In deinem zweiten Codebeispiel wird der Fehlerfall möglicherweise nie behandelt.

    ich habe doch für beide Forks den else-zweig...was ist denn da falsch?

    g_print("problem while forking (1)\n");
    

    mhm...könnte natürlich sein, dass dieser nie erreicht wird...

    DerBaer schrieb:

    func ist eine Funktion die einfach ein wait macht und damit den Zombie entfernt.
    func wird aufgerufen sobald sich das Kind terminiert. Danach macht der Vater da weiter wo er vorher aufgehört hat.

    ich will ja nicht, dass der Vater auf das Kind wartet. er ist ein schlechter Vater und soll einfach weiterlaufen 😉
    oder wird die func von dem sigaction erst aufgerufen, wenn das Kind beendet wurde?
    wie muss func aussehen? muss ja sicher ein bestimmtes format haben

    DerBaer schrieb:

    Das feststellen, ob dein Kind richtig arbeitet wird nicht ganz so einfach, weil es einfach 2 verschiedene Prozesse sind. Also brauchst du irgendene möglichkeit zwischen Prozessen zu kommunizieren(Signals, Pipes, ...).

    sind pipes "Echtzeit"?
    also kann ich in der gleichen fork-verzweigung im child-teil schreiben und im parent-teil lesen?

    pseudocode:

    pid_t pid = fork();
    if (pid > 0) {
      // Vater
      if (pipe=false) res=false
    } else if (pid == 0) {
      // Kind
      false>>pipe
    } else {
      // Fehler (also Vater)
      res=false
    }
    return res;
    

    Gruß Frank



  • frank schrieb:

    ich habe doch für beide Forks den else-zweig...was ist denn da falsch?

    mhm...könnte natürlich sein, dass dieser nie erreicht wird...

    Das meinte ich^^

    frank schrieb:

    wie muss func aussehen? muss ja sicher ein bestimmtes format haben

    z.B. so:

    void func() {
    	pid_t pid;
    	int status;
    	while( (pid=waitpid(-1, &status, WNOHANG)) > 0 ) {
    		// mache was
    	}
    }
    

    waitpid wartet auf Prozesse.
    -1: Irgendein Kinder
    status: enthält danach Infos über Kind(z.B. wie es beendet wurde)
    WNOHANG: wenn kein Zombie gefunden wurde kehr waitpid sofort zurückund liefert -1 (ansonsten wartet es bis sich einer beendet)
    pid: ID des gefundenen Zombies

    Ich weiß nicht, ob du nur ein Kind erzeugst, aber bei mehreren bietet sich so eine while Schleife an.
    In der Funktion kannst du eigentlich fast alles machen. Du musst nur aufpassen, weil der Signalhandler deinen Vater irgendwo unterbricht, was zu problemen führen kann, wenn du auf gleichen Daten arbeitest. (sigprocmask() falls du das Problem hast). unter "man 7 signal" findest du eine Liste mit Funktionen, die du sicher in func verwenden kannst. Bei anderen kann es zu Problemen führen.

    frank schrieb:

    sind pipes "Echtzeit"?
    also kann ich in der gleichen fork-verzweigung im child-teil schreiben und im parent-teil lesen?

    Zu Pipes kann ich dir momentan noch eigentlich nichts sagen, weil ich sie noch nie verwendet habe, aber anscheinend funktionieren sie so Dateien. Das Beispiel ist glaube ich genau was du willst^^:
    http://linux.die.net/man/2/pipe
    Da kannst du natürlich ein write und read machen und das read wird solange blockieren, bis was da ist (mit Echtzeit hat das aber nichts zu tun)

    Mfg
    DerBaer



  • DerBaer schrieb:

    z.B. so:

    void func() {
    	pid_t pid;
    	int status;
    	while( (pid=waitpid(-1, &status, WNOHANG)) > 0 ) {
    		// mache was
    	}
    }
    

    ...
    Ich weiß nicht, ob du nur ein Kind erzeugst, ...
    In der Funktion kannst du eigentlich fast alles machen. Du musst nur aufpassen, weil der Signalhandler deinen Vater irgendwo unterbricht, was zu problemen führen kann, wenn du auf gleichen Daten arbeitest. (sigprocmask() falls du das Problem hast). unter "man 7 signal" findest du eine Liste mit Funktionen, die du sicher in func verwenden kannst. Bei anderen kann es zu Problemen führen.

    Hi Baer,
    ich erzeuge nur ein child (media-player).Wenn ich das richtig interpretiere (mit der while-schleife) würde ich mit der func vermutlich den gleichen Effekt haben, wie mit system...die Grafische Oberfläche (GTK) wird nicht mehr gezeichnet und navigieren darauf wird unmöglich.
    ich nehme ja fork/exec (bzw. will das), um mein Programm von dem Player zu entkoppeln.
    als Status im parent bräuchte ich eigentlich nur:
    - konnte programm gestartet werden
    - wurde programm beendet
    ob ich das per pipes oder anders realisiere ist mir eigentlich egal...sollte halt möglichst einfach sein 🙂
    evtl. geht das auch mit signals einfacher...dazu müsste der child-prozess nur den parent-prozess kennen, oder?

    Gruß Frank



  • OK sry, hab das exec vergessen. Dein Prozess kann nicht sagen, wenn es geklappt hat (wurde ja ersetzt durch exec). Du kannst nur mitteilen, falls es nicht geklappt hat. Da du darauf aber nicht warten kann(weil ja möglichcerweise nichts kommt), sind pipes vielleicht doch nicht so geeignet.

    Aber es könnte so gehen:
    Du machst den Signalhandler für SIGCHLD, der ein waitpid(-1, &status, WNOHANG) aufruft (braucht keine Schleife bei einem Kind).
    Und dann untersuchst du den Statuscode.

    Also:
    V: Vater, K:Kind
    V: installiere Signalhandler
    V: fork()
    V: arbeite normal weiter K: mache ein exec
    V: arbeite ... K: falls erfolgreich passiert nix(dein Programmcode ist ja weg)
    V: arbeite ... K: falls Fehler: exit(237)
    V: Arbeit unterbrochen von func K: Zombie
    V: waitpid(-1, &status, WNOHANG) K: Zombie verschwindet
    V: teste Staus mit WIFEXITED(status)
    V: falls ja: teste ob WEXITSTATUS(status) = 237
    wobei du
    - keine Benachrichtigung kriegst, falls es erfolgreich war
    - nicht weißt, wo der Fehler aufgetreten ist (wenn exec klappt aber das neue Programm sich zufällig mit z.B. 237 als code beendet siehst du den Unterschied nicht.

    Und die Signalhandler warten nur auf Signale, blockieren aber den Ablauf nicht, sodass die GUI trotzdem laufen sollte.

    frank schrieb:

    Wenn ich das richtig interpretiere (mit der while-schleife) würde ich mit der func vermutlich den gleichen Effekt haben, wie mit system...die Grafische Oberfläche (GTK) wird nicht mehr gezeichnet und navigieren darauf wird unmöglich.

    du rufst func selbst nicht auf. Das macht das betriebsystem für dich und zwar sobald ein Prozess beendet wurde. Dann geht die Schleife her, rattert einmal über alle Prozesse, deren Status sich geändert hat drüber,(bei dir genau dieser eine) entsorgt diesen Zombie und wird wegen dem WNOHANG danach gleich 0 zurückliefern und die func wieder verlassen. Da sollte nichts blockieren.



  • Hallo,
    ich hab jetzt so, und es funktioniert super, soweit ich es nach bisherigen Tests beurteilen kann

    void child_callback(int param)
    {
      pid_t pid;
      int status;
      pid=waitpid(-1, &status, WNOHANG);
      g_print("child terminated (%d)\n",status);
      if (status>0)
        ShowError("Fehler bei der Ausführung!");
    }
    
    bool Exec(string command,string parameters,string file)
    {
      bool ret=true;
      //arg-array bauen
    
      pid_t pid=fork();  
      if (pid==-1)
      {
        g_print("problem while forking\n");//return false; //problem while forking
        ret=false;
      }else if (pid==0)//child-process
      {
        if(execvp(command.c_str(), args) == -1) 
        {
          perror("execvp");
          _Exit(EXIT_FAILURE);//errors in child are handled by signal-handler
        }
      } else 
      {
        //parent process
        struct sigaction sa;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
        sa.sa_handler = child_callback;
        sigaction(SIGCHLD, &sa, NULL); 
      }
      return ret;
    }
    

    Danke Baer 🙂

    Gruß Frank



  • Hi,

    glück gehabt 😉
    Prinzipiell alles richtig, nur musst du den Signal-Handler nicht im Vaterprozess anlegen, sondern noch vor dem fork();
    Problem sonst: fork() -> Kind wird als erstes ausgeführt -> terminiert
    und erst dann kommt der Vater wieder dran und darf weiterrechnen -> installiert Handler, aber Signal kam ja schon vorher = verloren
    Wenn du vor dem fork den Handler installierst, hat ihn der Vater schon und wenn dann das Kind zuerst rechnen darf und sich gleich beendet wird das erkannt.

    P.S fork() übernimmt die eingestellten Handler(heißt Kind hat den auch installiert, wobei es kein eigenes Kind hat, also nutzlos^^), aber beim exec wird der SIgnalhandler dann ausm Kind gelöscht.



  • wenn ich den signal-handler-block vor dem fork platziere, hängt der parent 😢



  • hm. da bin ich überfragt 😕 . Bei mir hat das immer tadellos funktioniert.
    Ich werde das demnächst mal bei mir testen.

    int Exec(char * command, char ** parameters, char * file)
    {
      int ret=1;
      //arg-array bauen
    
    	struct sigaction sa;
    	sigemptyset(&sa.sa_mask);
    	sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
    	sa.sa_handler = child_callback;
    	sigaction(SIGCHLD, &sa, NULL);
    
      pid_t pid=fork();  
      if (pid==-1)
      {
        g_print("problem while forking\n");//return false; //problem while forking
        ret=false;
      }else if (pid==0)//child-process
      {
        execlp(command, NULL);
        perror("execvp");
        _Exit(EXIT_FAILURE);//errors in child are handled by signal-handler
      } else
      {
    	//parent process
    	/*int i = 0;
    	for (;i < 100000000; i++) {
    		printf("%c", 0);
    	}*/
      }
    	printf("Vater fertig\n");
      return ret;
    }
    

    So funktioniert das bei mir.
    - Die Schleife mit printf() habe ich testhalber drinnen gehabt um zu simulieren, dass das Kind zuerst terminiert bevor der Signalhandler installiert werden konnte (->< Signal war verloren).
    - Nachdem ich den Signal-Block nach vorne geschoben habe, lief es mit und ohne Schleife
    - Ich hab die If-Abfrage bei execvp (habs tsethalber in execlp geändert) weggelassen, da execvp sowieso immer -1 zurückliefert (im Erfolgsfall ist dein Programmcode ja weg)

    Hast du in der call_back oder in der Exec noch Code der unerwünschte Nebeneffekte haben könnte? Oder wo genau hängt der Vater?

    Tut mir leid, dass das immer noch nicht ganz richtig läuft.



  • habe meine komplette callback geposted...das einzige "komplexe" ist ShowError,
    welches den Text in einer GtkInfoBar (im parent) anzeigt (wär hier vermutlich bisschen viel zum posten [in Klasse eingebetter])

    die zeile mit dem perror wird trotzdem nicht erreicht (nur im fehlerfall)?

    der parent hängt nachdem das child beendet wurde:

    letzte Ausgabe:
    execvp: No such file or directory
    child terminated (256)

    der child-prozess ist bei "ps auxf" nicht (mehr) sichtbar



  • frank schrieb:

    letzte Ausgabe:
    execvp: No such file or directory
    child terminated (256)

    der child-prozess ist bei "ps auxf" nicht (mehr) sichtbar

    das heißt immerhin, dass das Signal richtig abgefangen wird und waitpid() den Zombie richtig aufräumt.
    Ich denke du solltest nicht status verwenden zum Anzeigen,sondern WEXITSTATUS(status). Da stecken mehrere Daten drinnen als der reine Exitcode, aber das sind erstmal Details.

    Das mit der komplexen ShowMessage könnte möglicherweise das Problem sein, weil nicht alle Funktionen sicher vor überlagerung sind, also wenn dein Vater die Funktion aufruft und mittendrin durch das Signal unterbrochen wird und der die gleiche Funktion aufruft.
    Wenn du die ShowMessage auskommentierst, sollte es eigentlich laufen, oder?
    Wenn das das Problem ist, fällt mir jetzt nur ein, diese problematische Funktion möglichst zu ersetzten, oder im Vaater vor jedem Aufruf das SIGCHLD temporär zu blockieren.



  • mhm..scheint an der error-funktion zu liegen...

    diese sieht erstmal so aus:

    void ShowError(string message)
    {
       g_print("Error: %s\n",message.c_str());
       message="Fehler: "+message;
       Mainform.InfoBar->ShowBar(GTK_MESSAGE_ERROR,message.c_str(),true);
    }
    

    die erste zeile ist ja ein g_print...welches aber scheinbar schon nicht ausgeführt wird...
    selbst wenn ich die beiden letzten Zeilen auskommentiere, hängt sich der parent auf...warum ist mir unklar, da ja auch nur ein g_print drin ist...

    meine callback sieht jetzt so aus:

    void child_callback(int param)
    {
      pid_t pid;
      int status;
      while( (pid=waitpid(-1, &status, WNOHANG)) > 0 ) 
      {
        int exitcode=WEXITSTATUS(status);
        g_print("child terminated (%d)\n",exitcode);
        if (exitcode>0)
          g_print("Error in child...\n");
          //ShowError("Wiedergabeprogramm konnte nicht gestartet werden!");
      } 
    }
    


  • also so wie ich das sehe funktioniert das erste g_print(), aber das zweite nicht?

    Ich kannte die Funktion g_print() leider nicht, aber vielleicht hat sie einen Puffer der nicht automatisch bei '\n' geleert wird oder hat sonstige Probleme, die nichts mit dem Signalhandler zu tun haben?
    Interessant fände ich auch mal, was in errorcode drinsteht, denn die "least significant 8 bits" wie in der Man-page steht ergeben 0 wenn der statuscode 256 ist.
    Funktioniert es, wenn du z.B. ein Printf() an die stelle machst?

    Das sind jetzt nur noch wage Vermutungen, weil es langsam absolut keinen Sinn mehr ergibt.
    Tut mir echt leid, dass ich da keine Idee mehr habe...



  • Die ersten beiden g_print in der child_callback funktionieren.nur das g_print in der showerror nicht. Der exitcode ist da ubrigends 1 (exit_failure)



  • ich habe den code mal isoliert in ein separetes Programm kopiert, da funktioniert er...irgendwas blockiert im Haupt-programm scheinbar...aber keine Ahnung, wo und was...

    ich könnte dir zwar den kompletten Code geben, aber ich denke, es ist zuviel verlangt, dass du mein Programm debuggst 😞

    Gruß Frank


Log in to reply