Systemaufrufe fork, waitpid, execvp



  • Lies doch mal die Manpage zu system(3). (man.cx spuckt nur Mist aus, nimm die OpenBSD-Manpage zu system(3) oä.)

    Ja, damit spawnst du einen frischen Prozess. Der macht aber den Umweg über eine Shell und dein Prozess wartet dann bis die Shell sich beendet hat, bevor er weiterläuft. Intern wird dafür zwar waitpid verwendet, aber SeppJ hat trotzdem etwas lowleveligeres erklärt.

    edit: OpenBSD-Manpage


  • Mod

    system ist ungefähr so etwas wie

    int system(const char *cmd)
    {
      pid_t pid = fork();
      if (pid == 0)
      {
        exec(cmd);
      }
      else
        return waitpid(pid);
    }
    

    Bewusst ausgelassen:
    1. system macht noch allerlei Prüfungen, z.B ob cmd ein Nullzeiger ist und bestimmt auch, ob der fork überhaupt geklappt hat.
    2. system macht nicht direkt exec(cmd) sondern exec auf eine Shell mit dem Kommando als Parameter

    Aber Moral von der Geschichte:
    -system ist ein high-Level Kommando, das intern ungefähr macht, was deine Aufgabe ist.
    -An der Stelle, wo in deinem Programm "HIER" steht, ist der Kindprozess längst beendet.



  • #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <cstdlib>
    #include <iostream>
    #include <unistd.h>  
    #include <sys/wait.h>   
    #include <sys/types.h>  
    
    void parse(char *line, char **argv) {
        while (*line != '\0') {
            while (*line == ' ' || *line == '\t' || *line == '\n'){
                *line++ = '\0';
            }            
            *argv++ = line;
            while (*line != '\0' && *line != ' ' && *line != '\t' && *line != '\n'){
                line++;
            }   
        }
        *argv = '\0';
    }
    
    void execute(char **argv) {
        pid_t pid;
        int status;
    
        // Ein Fork darf nicht kleiner 0 sein, sonst schlägt dieser fehl!
        if ((pid = fork()) < 0) { 
            printf("FEHLER: 'forking' eines Kind-Prozesses fehlgeschlagen!\n");
            exit(1);
    
        // KIND-PROZESS:
        // Erst wenn die PID uns eine 0 zurückgibt kommen wir in das Kindprozess!!
        } else if (pid == 0) {
            if (execvp(*argv, argv) < 0) { 
                // Zum Hinweis 5:
                // Falls das COMMAND nicht exisiert kommt es zum ERROR
                printf("FEHLER: 'exec'!\n");
                exit(1);
            }
    
        //ELTERN-PROZESS:
        //da der Wert nur noch größer 0 sein kann also, PID > 0!
        } else { 
            while (wait(&status) != pid) // Wartet auf Beendigung des aktuellen Prozesses
            /* do nothing :))) */   ;
        }
    }
    
    int main() {
        char line[1024];
        char *argv[64]; 
    
        while (1) {
            printf("SHELL-COMMAND: ");
            gets(line);
            printf("\n");
            parse(line, argv);
            // Vergleiche ob Eingabe ein Logout ist [Aufgabe 1.4]
            if (strcmp(argv[0], "logout") == 0){  
                exit(0); 
            }
            execute(argv); 
        }
    
        return 0;
    }
    

    Danke für die zahlreichen hilfen :p :p

    Wie könnte ich denn eventuell meine "Shell" so erweitern, dass Befehle mit einem & am Ende auch akzeptiert werden, also dass er nicht mehr wartet sondern er fortführt? Aktuell funktioniert dies bei meinem Code noch nicht.. Bin schon wieder mal am zweifeln 😕


  • Mod

    depream schrieb:

    Wie könnte ich denn eventuell meine "Shell" so erweitern, dass Befehle mit einem & am Ende auch akzeptiert werden, also dass er nicht mehr wartet sondern er fortführt? Aktuell funktioniert dies bei meinem Code noch nicht.. Bin schon wieder mal am zweifeln 😕

    Wer soll fortführen? Die Frage macht keinen Sinn. Du hast hier doch nur ein Programm, welches einen Prozess spawnt, keine Shell. Das '&' ist etwas spezifisches für Shells, das von der Shell ausgewertet wird und ihr sagt, wie diese ihre Unterprozesse spawnt. Wenn du selber so etwas wie eine Shell programmieren möchtest, dann musst du die Kommandozeile in deinem Programm auswerten und dann entsprechend beim Spawnen deiner Unterprozesse vorgehen; also beispielsweise nicht auf einen Kindprozess warten.



  • SeppJ hat ja bereits deine Kernfrage ja bereits beantwortet, aber noch ein paar Anmerkungen zum Code:

    • Die Header in deinem Code sind C/C++-Mischmasch der übleren Sorte: cstdlib+stdlib.h, iostream+stdio.h usw. Bitte entferne die C++-Header, dein Code sieht eher so aus als wolltest du C programmieren.
    • gets ist deprecated und gefährlich. Nimm man: getline.
    • Bitte verwende für deine Code-Snippets code="c" statt nur code .
    • Hast du irgendein C-Grundlagen-Buch? Falls ja lies es, falls nein, hol dir eines.


  • Danke für die Antworten. Werde dieses in Zukunft in acht nehmen, jedoch hat einer meiner Kollegen mir dabei helfen können. Wahrscheinlich lag es eher daran, dass ich mein Problem nicht richtig erläutern konnte.

    Hab es jetzt so gemacht und es funktioniert so wie ich es möchte. Hab auch meinen Prof heute gefragt, er meinte dasselbe wie 'nman'. Werde es wie gesagt versuchen in Zukunft so umzusetzen.

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <cstdlib>
    #include <iostream>
    #include <unistd.h>  
    #include <sys/wait.h>  
    #include <sys/types.h>  
    
    using namespace std;
    
    void zerlegen(char *line, char **argv) {
        while (*line != '\0') {
            while (*line == ' ' || *line == '\t' || *line == '\n') {
                *line++ = '\0';
            }
            *argv++ = line;
            while (*line != '\0' && *line != ' ' && *line != '\t' && *line != '\n') {
                line++;
            }
        }
        *argv = '\0';
    }
    
    bool aufUNDpruefen(char *argv) {
        for (int i = 0; i <= sizeof (argv); i++) {
            if (argv[i] == '&') {
                // cout << "TRUE" << endl;
                return true;
            }
        }
    }
    
    char* UNDentfernen(char *argv) {
        for (int i = 0; i <= sizeof (argv); i++) {
            if (argv[i] == '&') {
                argv[i] = '\0';
                return argv;
            }
        }
    }
    
    void ausfuehren(char **argv) {
        pid_t pid = fork();
        int status;
        bool containsAnd = false;
    
        containsAnd = aufUNDpruefen(*argv);
    
        if (containsAnd == true) {
            *argv = UNDentfernen(*argv);
        }
    
        // Ein Fork darf nicht kleiner 0 sein, sonst schlägt dieser fehl!
        if (pid < 0) {
            printf("FEHLER: 'forking' eines Kind-Prozesses fehlgeschlagen!\n");
            exit(1);
    
            // KIND-PROZESS:
            // Erst wenn die PID uns eine 0 zurückgibt kommen wir in das Kindprozess!!
        } else if (pid == 0) {
            if (execvp(*argv, argv) < 0) {
                // Zum Hinweis 5:
                // Falls das COMMAND nicht exisiert kommt es zum ERROR
                printf("FEHLER: 'exec'!\n");
                exit(1);
            }
    
            //ELTERN-PROZESS:
            //da der Wert nur noch größer 0 sein kann also, PID > 0!
        } else {
            if (containsAnd == true) {
                ;
            } else {
                while (wait(&status) != pid) // Wartet auf Beendigung des aktuellen Prozesses
                    /* do nothing :))) */;
            }
    
        }
    }
    
    int main() {
        char line[1024];
        char *argv[64];
    
        while (1) {
            printf("SHELL-COMMAND: ");
            gets(line);
            printf("\n");
            zerlegen(line, argv);
            // Vergleiche ob Eingabe ein Logout ist [Aufgabe 1.4]
            if (strcmp(argv[0], "logout") == 0) {
                exit(0);
            }
            ausfuehren(argv);
        }
        return 0;
    }
    //fgets od. getline
    

    So sieht mein fertiger Code nun aus. Ich werde wohl möglich noch weitere Fragen haben, DANKE nochmals an alle.

    Ansonsten hätte sich dieses Thema erledigt. 👍



  • Bei mir ist eben noch eine kleine Frage aufgefallen:

    Durch den Befehl "ps ax" kann man ja die Prozesse sehen, jedoch gibt meiner "leider" keine Zombie's zurück. Was habe ich denn "falsch" programmiert, dass es nicht erscheint.

    $ firefox
    Ctrl+C
    // firefox wird unterbrochen
    $ ps ax
    .... [kein Z für Zombie und firefox wird auch nicht angezeigt]
    

  • Mod

    Damit das zu einem Zombie wird, dürfte der Mutterprozess nicht auf den abgebrochenen Kindprozess warten.



  • SeppJ schrieb:

    Damit das zu einem Zombie wird, dürfte der Mutterprozess nicht auf den abgebrochenen Kindprozess warten.

    Ich habe den "Eltern-Prozess" komplett weggelassen und es kommt ständig zu Fehlern. Etwas läuft nicht so wie ich es geplant habe.

    Könntest du mir eventuell mit einem Code helfen?


  • Mod

    depream schrieb:

    Ich habe den "Eltern-Prozess" komplett weggelassen

    Dann kann es natürlich auch keine Zombies geben.

    Könntest du mir eventuell mit einem Code helfen?

    Das hier sollte dir einen Zombieprozess erzeugen:

    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int main ()
    {
      pid_t child_pid;
    
      child_pid = fork ();
      if (child_pid > 0) {
        sleep (60);
      }
      else {
        exit (0);
      }
      return 0;
    }
    


  • Liege ich bei der Aufgabe eigentlich in der richtigen Spur?

    http://abload.de/img/unbenanntrrsk5.jpg

    #include <string.h>
    #include <cstdlib>
    #include <iostream>
    #include <unistd.h>  
    #include <sys/wait.h>  
    #include <sys/types.h>  
    #include <signal.h> // für SIGINT
    
    using namespace std;
    
    pid_t childpid = 0;
    
    // SIGINT (Ctrl+C) = Unterbrechung eines Prozesses
    // Beispiel: firefox -> Ctrl+C unterbricht den Prozess!
    
    void signalhandler_SIGINT(int signum) {
        cout << endl;
        cout << "Ein SIGINT wurde erkannt!\n";
    }
    
    // SIGTSTP (Ctrl+Z) = Anhalten eines Prozesses
    
    void signalhandler_SIGTSTP(int signum) {
        //    if (childpid!=0){
        //        vpid.push_back(childpid);
        //        cout << "Stopped "<< child
        //        pid<<"\n";
        //        kill(childpid,SIGTSTP);
        //        childpid=0;
        //    }
        cout << "\nCaught SIGTSTP\n";
    }
    
    // Bringt einen "angehaltenen Prozess" durch 'Ctrl+C' in den Vordergrund!
    
    void fg() {
        ;
    }
    
    // Zerlegt die "Getline"
    
    void zerlegen(char *line, char **argv) {
        while (*line != '\0') {
            while (*line == ' ' || *line == '\t' || *line == '\n') {
                *line++ = '\0';
            }
            *argv++ = line;
            while (*line != '\0' && *line != ' ' && *line != '\t' && *line != '\n') {
                line++;
            }
        }
        *argv = '\0';
    }
    
    // Prüft ob ein &-Symbol im Char enthalten ist
    
    bool aufUNDpruefen(char *argv) {
        for (int i = 0; i <= sizeof (argv); i++) {
            if (argv == '&') {
                return true;
            }
        }
    }
    
    // Falls ein &-Zeichen vorhanden ist wird dieser gelöscht
    
    char* UNDentfernen(char *argv) {
        for (int i = 0; i <= sizeof (argv); i++) {
            if (argv[i] == '&') {
                argv[i] = '\0';
                return argv;
            }
        }
    }
    
    void ausfuehren(char **argv) {
        pid_t pid = fork();
    
        bool containsAnd = false;
    
        containsAnd = aufUNDpruefen(*argv);
    
        if (containsAnd == true) {
            *argv = UNDentfernen(*argv);
        }
    
        int status;
        // Ein Fork darf nicht kleiner 0 sein, sonst schlägt dieser fehl!
        if (pid < 0) {
            cout << "FEHLER: 'forking' eines Kind-Prozesses fehlgeschlagen!\n";
            exit(1);
    
            // KIND-PROZESS:
            // Erst wenn die PID uns eine 0 zurückgibt kommen wir in das Kindprozess!!
        } else if (pid == 0) {
            if (execvp(*argv, argv) < 0) {
                // Zum Hinweis 5:
                // Falls das COMMAND nicht exisiert kommt es zum ERROR
                cout << "FEHLER: 'exec'!\n";
                exit(1);
            }
    
            //ELTERN-PROZESS:
            //da der Wert nur noch größer 0 sein kann also, PID > 0!
        } else {
            if (containsAnd == true) {
                ;
            }
            while (waitpid(pid, &status, WNOHANG) != pid) { // Wartet auf Beendigung des aktuellen Prozesses
    
                    ;
    
            }
    
        }
    }
    
    int main() {
    
        signal(SIGINT, signalhandler_SIGINT);
        signal(SIGTSTP, signalhandler_SIGTSTP);
        cout << "Signal Handler Installed\n";
    
        char line[1024];
        char *argv[64];
    
        //    if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
        //        exit(1);
        //    }
    
        while (1) {
            cout << "SHELL-COMMAND: ";
            cin.getline(line, 1024);
    
            cout << "\n";
            zerlegen(line, argv);
            // Vergleiche ob Eingabe ein Logout ist [Aufgabe 1.4]
            if (strcmp(argv[0], "logout") == 0) {
                exit(0);
            } 
            ausfuehren(argv);
    
        }
    
        return 0;
    }
    

    Problem:
    [i]Ctrl+C* "bricht" den aktuellen Prozess ab, aber bei ping google.de funktioniert es nicht. Funktioniert aktuell nur bei firefox und bestimmt bei anderen Befehlen. Ctrl+Z sollte auch funktionieren, nur weil mein Elternprozess auf das Beenden des Kindprozesses wartet kann ich es auch nicht weiter-testen. Das ich etwas falsch mache ist mir bewusst 😕

    Bin leider schon verzweifelt...


  • Mod

    depream schrieb:

    Liege ich bei der Aufgabe eigentlich in der richtigen Spur?

    Bis jetzt ist es ja nur ein leeres Gerüst.

    Was mir vor allem fehlt, ist erst einmal eine vernünftige Verwaltung der Prozesse. Du musst die Daten der Prozesse irgendwo speichern, sonst kannst du sie schließlich nirgendwo verarbeiten. Es könnte nötig sein, dass du dein Programm dafür vollkommen umbauen musst.


Anmelden zum Antworten