PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Frage zu Syscall "fork" unter Linux (aus C++ heraus)


lima~
2010-05-04, 23:06:47
Hallo!

Muss mich gerade gezwungenermaßen mit Systemcalls und ähnlichem aus C heraus beschäftigen, wobei ich bisher weder mit C, noch mit Linux gearbeitet habe. Ein Grundverständnis was Programmierung angeht ist zwar vorhanden, das hilft mir hier aber nur bedingt weiter. Ergo: auch wenn ihr unterm Tisch liegt, die folgenden Fragen sind durchaus ernst gemeint.

1. Was ich von fork bisher grob weiß: der Befehl erzeugt aus dem aktuellen Prozess heraus (Vater) einen neuen Prozess (Sohn), welcher auf alle Daten (Variablen) des Vaterprozesses zugreifen kann. so sind sich die Prozesse ziemlich ähnlich (oder gar identisch?), allerdings wird natürlich eine neue PID zugewiesen. Stimmt das soweit?

2. Sobald fork einmal aufgerufen wurde, kann man danach mit exec (oder einer seiner Varianten) ein neues Programm in den Prozess laden, z.B. Firefox, KWrite oder whatever. Auch noch grob richtig?

3. Gut, spätestens an diesem Punkt hänge ich ab. Für mich ist es im Code absolut untransparent, an welcher Stelle jetzt Vater- und an welcher Stelle Sohnprozes werkeln. Ich habe dazu folgenden code:


switch (fork())
{
case -1:
cout << "Fehler bei fork" << endl;
existat = 1; break;
case 0: /* nur Sohn */
execl("/bin/rm","rm",argv[1],0); /* nur Sohn */
cout <<"Fehler beim Laden"<<endl; /* nur Sohn */
existat = 2; break; /* nur Sohn */
default:
if(wait(&status) < 0 || status != 0)
{
cout << "Problem beim Loeschen" << endl;
cout << "Dateiname: " << argv[1] << endl;
cout << "status = " << status << endl;
existat = 3;
}
}



So, wie man sieht ist der case 0 mit "nur Sohn" Kommentaren beschmückt. Warum sollte jetzt aber ausgerechnet nur exakt der case 0-Teil im Sohn-Prozess ausgeführt werden? gut, bei -1 und default konnte der Sohnprozess garnicht erst gestartet werden und folglich kann auch nichts darin ausgeführt werden, ich frage mich aber trotzdem, wie einem ersichtlich wird, dass case 0 "Sohn-only" ist und just bei break; wieder aufhören soll.
Falls dem so ist: was müsste man tun, wenn man in case 0 auch Code im Vaterprozess ausführen lassen wollte? Vor das exec schreiben?

4. Ebenfalls wichtiger Punkt: case 0 ist ja eigentlich Erfolgsfall. Dort ist aber zu lesen "Fehler beim laden". Ich bin mir sicher, dass das aufgrund der Vater-Sohn Geschichte schon seinen Grund hat, komme aber nicht dahinter. Vermutlich wird das coutr garnicht mehr ausgegeben, falls exec erfolgreich ist oder so ähnlich... aber eine genaue Begründung fällt mir nicht ein.

5. Okay, letzte Frage:

Habe hier im Skript stehen: fork() ist parameterlos und liefert beim Vater als Rückgabewert die PID des Sohnes, und beim Sohn den Wert 0.

Wie ist das zu interpretieren? Schließlich rufe ich doch oben im Code fork auf und switche durch dessen Rückgabewert (-1, 0, default) und nicht etwa durch eine PID, obwohl der switch doch offensichtlich im Vater stattfindet? Und wie soll der Sohn überhaupt an den Rückgabewert kommen (ich meine weil der oben erwähnte Satz zwischen Vater und Sohn unterscheidet).


Danke vielmals!

Gnafoo
2010-05-05, 01:26:57
Der switch findet nicht im Vater-Prozess statt, er findet in beiden Prozessen statt. Du musst dir das etwa so vorstellen: du hast einen Prozess und rufst fork() auf. An der Stelle wird der Prozess dupliziert, d. h. du kehrst in zwei verschiedenen Prozessen aus fork() zurück. Um jetzt zu unterscheiden, welcher Prozess welcher ist, liefert dir fork() in jedem Prozess einen anderen Rückgabewert.

Sehen wir uns zunächst mal den Vater-Prozess an. Laut Dokumentation liefert fork() hier die PID des Sohnes zurück. Das ist eine Zahl ungleich 0 und -1. D. h. im Vater-Prozess wird der default-Teil des switch ausgeführt. Der wartet bis sich etwas am Zustand der Kind-Prozesse (hier gibt es nur einen) ändert und gibt eine Rückmeldung.

Was passiert jetzt (gleichzeitig) im Kind-Prozess? Naja laut Dokumentation liefert fork() hier 0 zurück. Also wird im switch des Kind-Prozesses der 0-Zweig ausgewählt. Dort kommt es zunächst zum exec(). Jetzt muss man wissen, dass exec() den kompletten Prozess ersetzt. Das bedeutet: die Funktion wird nie wieder verlassen (schließlich existiert der alte Prozess mit dem aktuellen Code und Variablen nicht mehr). Der einzige Fall, in dem das doch passiert, ist ein Fehler beim Aufruf. Daher auch die Fehlermeldung direkt nach dem exec()-Aufruf.

Was passiert, wenn fork() scheitert? Nun dann gibt es zunächst einmal keinen Kind-Prozess. Im Vater-Prozess gibt fork() -1 zurück. D. h. der switch läuft auf case -1, liefert eine Fehlermeldung und bricht ab.




Edit: um noch mal kurz auf die restlichen Fragen einzugehen, bzw. das Ganze etwas zu klären:

1.) Die Prozesse sind praktisch identisch, abgesehen von den PIDs. Geöffnete Dateien und Ressourcen gehen zwar (soweit ich weiß) nicht auf den Kind-Prozess über, aber ansonsten wird der gesamte Adressraum kopiert, d. h. alle Variablen, der aktuelle Stapel, gerade aufgerufene Funktionen usw. sind identisch.

2.) Jain. exec() ersetzt den aufrufenden Prozess. D. h. solange der Aufruf nicht scheitert, geht der aktuelle Zustand des Prozesses mit Adressraum und co. verloren und wird komplett ersetzt. Genau genommen wird auch kein neuer Prozess erzeugt, sondern der aktuelle überschrieben. Dementsprechend wird exec() außer bei Fehlern nie verlassen, denn wenn die aufgerufene Anwendung verlassen wird, terminiert ihr Prozess und das ist ja genau der ursprüngliche Prozess, in dem man exec() aufgerufen hat und der sowieso nicht mehr existiert.

Dr.Doom
2010-05-05, 01:39:41
1. Nach dem fork() gibt's zwei identische Prozesse. Die Kindkopie liegt halt nur woanders im Speicher.

2. Keine Ahnung.

3. & 5: Für's Verständnis, da nicht 100%ig genau:
Vater- und Kindprozess beginnen dann nach der fork()-Zeile mit der Arbeit:
Im Vaterprozess wird das fork() durch die Prozessnummer des Kindprozesses ersetzt, sodass das switch() im Vaterprozess was zum Auswerten hat, das ungleich 0 ist.
Im Kindprozess wird das fork() gegen eine 0 getauscht.

4. Das excel benutzt sozusagen den kopierten Kindprozess als Hülle und füllt ihn mit einem neuen Inhalt. Der alte Prozess, der kopiert wurde, ist dann weg und der mit exec geladene läuft.
Das ist nicht mit einem Methodenaufruf oder etwas vergleichbarem zu äh vergleichen. ;)

execl("/bin/rm","rm",argv[1],0); /* nur Sohn */
cout <<"Fehler beim Laden"<<endl; /* nur Sohn */

Wenn das execl(...) erfolgreich war, gibt's im Kindprozess den Code des Kindprozesses gar nciht mehr und man gelangt nicht mehr zu dem cout.
Klappt beim execl irgendwas nicht, DANN und nur dann kommt man zur Fehlermeldung, weil's den kopierten Kindprozess halt noch gibt.


Ich hoffe mal, dass das nicht zu falsch ist. Meine C-Vorlesungen liegen schon ein paar Jahre zurück und ich programmiere sonst nur Java. :eek: :freak:

lima~
2010-05-05, 18:04:55
Vielen Dank euch beiden, mit diesem Hintergrundwissen ist die ganze Sache fast schon zu einfach :)

Eine letzte Frage bleibt noch:

Mit wait(&status) kann man ja schön den Vaterprozess blockieren, bis der Sohnprozess beendet wurde. Außerdem erhalt man durch die übergebene Variable status ein paar Informationen über die Art und Weise wie der Sohnprozess beendet wurde - allerdings als int, wobei man die gewünschten Informationen wohl noch irgendwie extrahieren muss.

Habe hierzu folgende Information gegeben:

Der status aus wait hat folgende Bedeutung:
- Sohnprozess wurde mit exit beendet:
8 lsbs = 0x00, next 8 hobs = exitstatus.
- Sohnprozess wurde durch ein Signal beendet:
8 lsbs = Signal Nr., next 8 hobs = 0x00.


Muss ich jetzt etwa den Status in binär umrechnen, damit ich die least significant bits auslesen kann? Und was genau sind hobs?


Danke nochmal

Dr.Doom
2010-05-05, 18:28:14
Habe hierzu folgende Information gegeben:

Der status aus wait hat folgende Bedeutung:
- Sohnprozess wurde mit exit beendet:
8 lsbs = 0x00, next 8 hobs = exitstatus.
- Sohnprozess wurde durch ein Signal beendet:
8 lsbs = Signal Nr., next 8 hobs = 0x00.


Muss ich jetzt etwa den Status in binär umrechnen, damit ich die least significant bits auslesen kann? Und was genau sind hobs?
lsbs sind dann wohl die least significant bits, hobs dann die higher order bits -- oder so ähnlich. ;)

Naja, jedenfalls wenn du im Kindprozess ein exit(1) machst, dann kommt beim wait(&exitcode) für den exitcode ein 256 an. Dann ziehst du einfach pauschal 255 ab und hast deinen Exitcode, der vom Kindprozess geschickt wurde, nämlich die 1.

Vorher natürlich noch eine Fallunterscheidung, um herauszubekommen, ob ein der Kindprozess durch ein Signal beendet wurde: Ist der exitcode kleiner/gleich 255 ist's ein Signal, ist der exitcode grösser 255, dann wurde im Kindprozess das exit benutzt, um den Prozess zu beenden.

Das Ganze geht sicherlich noch irgendwie schicker/kryptischer mit Bitshifts, aber das ist mir zu blöd. :tongue:

Gnafoo
2010-05-06, 03:55:49
Schau mal in die Manpage von wait, da gibt es nette Macros, die einem die ganze Bit-Fuchtelei ersparen:

http://linux.die.net/man/2/wait

Dürfte ungefähr so funktionieren:


wait(&status);

if (WIFEXITED(status))
{
cout << "Exit Status: " << WEXITSTATUS(status) << endl;
}
else if (WIFSIGNALED(status))
{
cout << "Signal number: " << WTERMSIG(status) << endl;
}

// ...