Systemaufrufe fork, waitpid, execvp
-
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
-
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 ParameterAber 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
-
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 nurcode
. - 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]
-
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?
-
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 bewusstBin leider schon verzweifelt...
-
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.