Systemaufrufe fork, waitpid, execvp



  • Hi, ich habe eine Übungsaufgabe bei der ich leider nicht weiterkomme. Habe natürlich den Hinweis überlesen und es mit system gemacht 😡

    Aufgabe:

    Die Shell wartet auf Beendigung jedes eingegeben Befehls bevor weitere Befehle akzeptiert werden.
    
    Hinweis: Informieren Sie sich u.a. über die Systemaufrufe [i]fork[/i], [i]waitpid [/i]und [i]execvp [/i]oder eine Variante davon ([b]nicht system![/b])
    

    Ich habe natürlich gegoogelt aber dieser Erläuterungen von den verschiedenen aufrufen habe ich nicht verstanden. Ist mir leider aktuell alles fremd und sagt mir nur Bahnhof.

    Mit den Befehlen sind Programmstarts wie firefox, ls, etc. gemeint.

    Hätte noch die Frage was ein man execvp ist?

    Ich bin hier in diesem Bereich leider nur ein Anfänger.

    Hoffe das Ihr mir weiterhelfen könnt.

    Danke im Voraus.

    Mfg

    #include <cstdlib>
    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    int main() {
    	string eingabe;
    	string wait("wait");
    
    	while(eingabe!="exit")
    
    		cout << "Ihre Eingabe: ";
    		cin >> eingabe;
    		system(eingabe.c_str());
    		system(wait.c_str());
    	}
    	return 0;
    }
    


  • was genau willst du nochmal wissen? Irgendwie verstehe ich die Frage nicht.

    Hätte noch die Frage was ein man execvp ist?

    fragst du hier, was die man: exec(2)-Familie von Funktionen machen oder was man: man-Pages sind? 😕



  • Um ehrlich zu sein habe ich die Befehle an sich als erstes nicht verstanden, obwohl ich mir so viel durchgelesen habe. Verstehe nicht wofür diese fork(), waitpid() etc. sind. Möglicherweise würde ich es ja in einem Code "verstehen" aber so für meinen Fall entsprechend habe ich nichts gefunden.

    Meine Erwartung ist nicht, dass jemand mir das alles runterprogrammiert - schön wärs - nur so etwas müsste ich auch wirklich gut verstehen, denn es wird am Ende des Semesters auch bestimmt in der Prüfung abgefragt.



  • Das sind grundlegende Unix/Linux Systemaufrufe. Es gibt hunderte Erklärungen dazu im Internet. Erwarte nicht, dass hier die besser erklären kann. Wieso sollten wir hier noch eine weitere Erklärung liefern? Meinst Du, sie wird besser, wenn sie an Dich persönlich gerichtet ist? Es ist meines erachtens einfach nur Zeitverschwendung, das noch ein weiteres mal zu erläutern.

    Das ist nicht bösartig. Wir sind hier sehr hilfsbereit. Nimm ein Tutorial und arbeite es durch. Wenn Übungsaufgaben dabei sind, versuche Dich daran. Wenn Du nicht weiter kommst, kommst Du hier her und sagst, wo Du hängst und was Du nicht verstehst. Dann können wir genau auf Dein Problem eingehen und werden das bestimmt gerne tun.

    Und man execvp bedeutet, dass Du im Terminal genau diesen Befehl eingeben musst, um eine Anleitung (eine manual-page) dazu zu bekommen.


  • Mod

    Vereinfacht gesagt: Ein ausgeführtes Programm in Unix wird ein "Prozess" genannt. Damit ist der Maschinencode des Programms, welche Stelle des Maschinencodes gerade abgearbeitet wird, und seine Daten im Arbeitsspeicher gemeint (und noch ein bisschen mehr). Jeder Prozess hat eine Nummer, seine pid, process identification. Mit fork kann ein Prozess eine Kopie seiner selbst erstellen, also aus Eins mach Zwei. Dies startet die Ausführung des Programms nicht neu, beide Prozesse sind danach in ihrer Ausführung bei der Rückkehr der fork-Funktion. Die beiden Prozesse sind identisch, bis auf die Tatsache, dass einer von beiden 0 als Rückgabewert von fork erhalten hat (das ist dann der neue Prozess) und der andere Prozess (also das Original) die pid des neuen Prozesses.

    Mit exec kann ein Prozess durch einen anderen ersetzt werden, das heißt der Maschinencode und alle gespeicherten Daten des alten Prozesses werden verworfen, neuer Maschinencode wird geladen und mit der Ausführung ganz vom Anfang an begonnen.

    Mit waitpid kann ein Prozess darauf warten, dass ein anderer Prozess mit einer bestimmten pid) beendet wird (oder sonstwie seinen Status ändert). Normalerweise lässt man den Elternprozess auf den Kindprozess warten (Elternprozess = der Originalprozess vor einem fork, Kind = der neue Prozess nach einem fork). Warum? Es gibt eine spezielle Verbindung zwischen Eltern- und Kindprozess: Wenn das Kind, äh, stirbt (die Sprache ist etwas makaber 🙂 ), dann wird diese Statusänderung an den Elternprozess geschickt. Der Kindprozess wird nicht komplett beendet, solange der Elternprozess nicht signalisiert, dass er diese Statusänderung zur Kenntnis genommen hat, das Kind lebt als sogenannter Zombie weiter. wait ist eine Möglichkeit diese Statusänderung zur Kenntnis zu nehmen. Wenn umgekehrt der Elternprozess vor dem Kind stirbt passiert nichts besonderes, das Kind wird als Waise vom init-Prozess (der Hauptprozess eines Unixsystems, von dem alle anderen Prozesse ausgehen) adoptiert.

    Wie startet man also von einem Prozess aus einen neuen Prozess, der ein anderes Programm ausführt? Man benutzt fork, um einen neuen Prozess zu erzeugen und benutzt dann exec, um in diesem ein anderes Programm zu starten. Der Elternprozess sollte dann entweder auf sein(e) Kind(er) warten (wait) oder sterben gehen.



  • Also ich denke ich habe es zum "teils" verstanden. Ich versuche es am besten mal zu erklären:

    Wenn ich jetzt nun dieses Programm unter Linux laufen lasse:

    #include <cstdlib>
    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    int main() {
    	string eingabe;
    	cout << "Ihre Eingabe: ";
    	cin >> eingabe;
    	system(eingabe.c_str());
            //////// HIER ///////
    	return 0;
    }
    

    Dann habe ich unter "Hier" einen Prozess am laufen? Nehmen wir mal an ich habe "firefox" eingegeben. So ist Firefox das "Kindprozess" und die Main ein "Elternprozess"?

    Boah ist das heavy zu verstehen 😕



  • 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