fork & exec vs. posix_spawn (Fork ;-) aus Programm unter Linux und Windows)
-
Skym0sh0 schrieb:
jop, fork ist unix proprietär
Nein, es darf auch von Microsoft genutzt werden
und, wie ich finde, aäusserst hässlich und unsauber...
Das musst du mal genauer erklären
-
Ich finde es deshalb unsauber, weil das gesamte Programm inklsuive Program-Counter kopiert wird und dann halt zweimal läuft. Threads fände ich viel angebrachter.
Hässlich finde ich es, weil man nach dem forken erstmal testen darf, welche ProcessID man jetzt nun hat um so festzustellen, in welchem Prozess man sich nun befindet. Man hat also in jeden Fall immer das hier:
PID id = fork(); if ( id == 0 ) { } else { }
Aber ich kann mich auch nicht zu weit aus dem Fenster lehnen, da ich kein Linux-Programmierer bin (ehrlich gesagt, halte ich seit der Betriebssysteme-Vorlesung Linux für Chaos, aber das kann auch daran liegen, dass wir nicht wirklich tief eingesteigen sind.)
-
Skym0sh0 schrieb:
Ich finde es deshalb unsauber, weil das gesamte Programm inklsuive Program-Counter kopiert wird und dann halt zweimal läuft. Threads fände ich viel angebrachter.
Um einen neuen Prozess zu erzeugen findest du Threads angebrachter? Ehm, was?
In der Tat solltest du bei diesem Thema wohl das Fenster eher meiden.
-
nein nein. wofür gibts denn die exec-Familie?!
in den Übungsaufgaben (wahrscheinlich sehr praxisnah!!!!!!) wurde fork immer nur verwendet/gefordert um quasi Nebenläufigkeit zu erhalten.
Ich finde es halt fraglich den Programmcode für 2 verschiedene Programmabläufe in das gleiche Programm nur durch ein if getrennt zu implementieren.
Verschiedene Funktionalitäten = verschiedene Funktionen
Nebenläufigkeit = Threads
Prozesserzeugung = exec-Familie (da gibts ja drölfzig-Brillionen von exec's)Ist wie gesagt meine halbgare persönliche Meinung dazu...
-
Skym0sh0 schrieb:
Ist wie gesagt meine halbgare persönliche Meinung dazu...
Das trifft es wohl auf den Punkt.
-
ich empfehle für Windows CreateProcess und für Linux posix_spawn
fork() stammt aus PDP-11 Zeiten, als Programme max. 64 Kb groß waren. Heutzutage kommt es regelmäßig zu einem Speicherüberlauf, wenn ein Hostprozeß, der rel. viel Speicher verbraucht, mit fork() ein kleines ext. Programm starten möchte.
-
Skym0sh0 schrieb:
Prozesserzeugung = exec-Familie (da gibts ja drölfzig-Brillionen von exec's)
exec* erzeugt keinen Prozess.
-
Skym0sh0 schrieb:
nein nein. wofür gibts denn die exec-Familie?!
in den Übungsaufgaben (wahrscheinlich sehr praxisnah!!!!!!) wurde fork immer nur verwendet/gefordert um quasi Nebenläufigkeit zu erhalten.
Ich finde es halt fraglich den Programmcode für 2 verschiedene Programmabläufe in das gleiche Programm nur durch ein if getrennt zu implementieren.
Verschiedene Funktionalitäten = verschiedene Funktionen
Nebenläufigkeit = Threads
Prozesserzeugung = exec-Familie (da gibts ja drölfzig-Brillionen von exec's)Ist wie gesagt meine halbgare persönliche Meinung dazu...
Wie kannst Du es fraglich finden, wenn Du nicht weißt, wofür das gut ist? Gehe doch einfach mal davon aus, dass Du den Sinn und Konzept von fork nicht verstanden hast und daher einfach nicht das Wissen hast, um Dir ein Urteil zu bilden.
Fork wird praktisch immer in Verbindung mit exec verwendet und damit wird das ganze sehr flexibel.
dd++ schrieb:
fork() stammt aus PDP-11 Zeiten, als Programme max. 64 Kb groß waren. Heutzutage kommt es regelmäßig zu einem Speicherüberlauf, wenn ein Hostprozeß, der rel. viel Speicher verbraucht, mit fork() ein kleines ext. Programm starten möchte.
Der ist richtig gut . Wenn man keine Ahnung hat, sollte man sich ein wenig zurück halten. Mit fork werden unter Unix neue Prozesse erzeugt. Also ich habe nicht regelmäßig Speicherüberläufe, obwohl ich ständig Prozesse starte (nur als Stichwort: copy-on-write ).
-
wenn man von einem kleinen Programm ein kleines anderes Programm starten möchte (so wie auf der PDP-11), kann man das auch mit fork+exec machen. wenn man von einem "großen" Programm (mit viel belegtem Speicher) ein kleines Programm starten möchte, ist posix_spawn in den meisten Fällen die bessere Alternative.
wenn man z.B. von Java oder Ruby mit fork+exec einen neuen Prozeß startet, wird die komplette VM dupliziert. deshalb gibt es spzezielle posix_spawn Bindungen, gucke https://github.com/rtomayko/posix-spawn#readme und http://bryanmarty.com/blog/2012/01/14/forking-jvm/
-
Es wird überhaupt nichts dupliziert. Der Kibnbdprozess bekommt seinen eigenen Kontext + Stack, die Filedeskriptortabelle und Speicher ist mit dem Elternprozess geteilt und wird erst bei Änderungen kopiert ... was bei einem sofortigem exec* nie auftritt. Von daher ist fork ähnlich schlank wie das Erzeugen eines Threads, solange der neue Prozess nur seine eigenen Resourcen anfässt.
-
wenn ich direkt nach fork() exec*() aufrufe, wozu brauche ich dann fork()? kann man das nicht auch in einem einzigen Funktionsaufruf zusammenfassen? ja, man kann, dieser Aufruf heißt posix_spawn()
-
Die Diskussion ist mittlerweile so offtopic, dass ich sie abgetrennt und in ein passenderes Forum verschoben habe.
-
Dieser Thread wurde von Moderator/in SeppJ aus dem Forum C++ (auch C++0x und C++11) in das Forum Linux/Unix verschoben.
Im Zweifelsfall bitte auch folgende Hinweise beachten:
C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?Dieses Posting wurde automatisch erzeugt.
-
dd++ schrieb:
wenn ich direkt nach fork() exec*() aufrufe, wozu brauche ich dann fork()? kann man das nicht auch in einem einzigen Funktionsaufruf zusammenfassen? ja, man kann, dieser Aufruf heißt posix_spawn()
So einfach ist es nicht. In der Regel wird eben nicht direkt nach fork gleich exec aufgerufen, sondern erst mal die Umgebung für den neuen Prozess angepasst. Zwischen fork und exec kann ich beispielsweise die Standardeingabe und -ausgabe umbiegen oder den Benutzer wechseln, wenn das Programm unter root-Rechten gestartet wurde oder Umgebungsvariablen setzen. Einiges geht mit posix_spawn aber eben nicht alles. fork und exec sind halt noch eine Etage tiefer angesiedelt.
-
dd++ schrieb:
wenn man von einem kleinen Programm ein kleines anderes Programm starten möchte (so wie auf der PDP-11), kann man das auch mit fork+exec machen. wenn man von einem "großen" Programm (mit viel belegtem Speicher) ein kleines Programm starten möchte, ist posix_spawn in den meisten Fällen die bessere Alternative.
wenn man z.B. von Java oder Ruby mit fork+exec einen neuen Prozeß startet, wird die komplette VM dupliziert. deshalb gibt es spzezielle posix_spawn Bindungen, gucke https://github.com/rtomayko/posix-spawn#readme und http://bryanmarty.com/blog/2012/01/14/forking-jvm/
Danke übrigens für den Hinweis. Das erinnert mich mal wieder daran, wie krank Java ist. Der 2. Artikel von Bryan Marty erwähnt ein erwähnt ein embedded System mit extrem wenig Arbeitsspeicher. Nämlich 512M . Das ist schon ein krankes System, welches mit so einem gigantischen Speicher noch Probleme bekommt.
-
dd++ schrieb:
wenn man z.B. von Java oder Ruby mit fork+exec einen neuen Prozeß startet, wird die komplette VM dupliziert.
Liest du eigentlich auch, was du verlinkst? Das steht da nämlich so nirgends. Wäre auch bei jeder halbwegs vernünftigen Fork-Implementierung riesiger Unfug.
Jedes Unix, das etwas auf sich hält, implementiert den Fork-Systemcall so, dass nur solche Speicherseiten kopiert werden, auf die nach dem fork(2)-Aufruf schreibend zugegriffen wird. Copy-on-Write eben. Erreichen lässt sich das ohne großen Mehraufwand: Seiten als PROT_READ markieren und ein Schreibzugriff führt ganz natürlich (unterstützt durch die Hardware) dazu, dass du in den Kernel trapst, die Seite kopieren und anschließend die Berechtigung wieder auf PROT_READ|PROT_WRITE setzen kannst.
-
in den beiden Artikeln steht recht deutlich, daß die Leute Probleme mit fork() haben. bei dem Ruby Artikel sind auch noch Performance Vergleiche angeführt. wenn man eine Weile sucht, findet man noch mehr solche Beispiele
posix_spawn ist zunächst mal eine POSIX Definition. die Implementierung ist von System zu System unterschiedlich. auf Solaris ist die Implementierung lightweight und threadsafe. ich hoffe mal, die Linux Kernel Developer das irgendwann einmal auch noch auf die Reihe bekommen
-
Ich bin auf http://lwn.net/Articles/360720/ gestoßen. Und das verlinkte fork_bench.c habe ich mir geholt und ein wenig weiter gebaut. Mich hat interessiert, wie fork sich zu vfork verhält und wie relevant das in Zusammenhang mit exec ist. Hier ist das Programm:
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> extern char ** environ; int main(int argc, char *argv[]) { long c = atoi(argv[1]) * 1024 * 1024; int n = atoi(argv[2]); int i = 0; int e_argc = 2; char* e_argv[] = { "/bin/true", NULL }; if (n) { char *ptr = malloc(c); memset(ptr, 0, c); } for (; i < n; i++) { pid_t childpid = argv[3] && argv[3][0] == 'v' ? vfork() : fork(); if (childpid) { waitpid(childpid, NULL, 0); } else { if (argv[3] && argv[4]) execve(e_argv[0], e_argv, environ); exit(0); } } }
Hier die Ausführung:
`$ time ./fork_bench 1000 100 v
real 0m0.539s
user 0m0.140s
sys 0m0.350s
$ time ./fork_bench 1000 100 f
real 0m3.480s
user 0m0.132s
sys 0m3.090s
$ time ./fork_bench 1000 100 f 1
real 0m3.602s
user 0m0.136s
sys 0m3.135s
$ time ./fork_bench 1000 100 v 1
real 0m0.572s
user 0m0.117s
sys 0m0.353s`
Die Tests laufen also mit einem malloc von 1GB jeweils 100 mal. Die Tests mit dem 'v' als 3. Parameter verwenden vfork und die anderen fork. Mit der zusätzlichen 1 wird noch ein exec auf /bin/true ausgeführt. Bemerkenswert ist, dass das exec kaum einen Unterschied macht. vfork ist allerdings signifikant schneller. Und zwar hier um den Faktor 10, wie man an der Zeile mit "sys" erkennen kann.
Die Tests liefen auf einem Fedora 17, 64 Bit, Intel-i5 mit 2.7GHz, 6G RAM.
Bitte schlagt mich nicht wegen der fehlenden Parameterprüfung. Ich habe das einfach so stupide weiter gebaut. Dennoch würde ich mich dafür feuern, wenn ich bei mir angestellt wäre .
-
tntnet schrieb:
Bemerkenswert ist, dass das exec kaum einen Unterschied macht. vfork ist allerdings signifikant schneller. Und zwar hier um den Faktor 10, wie man an der Zeile mit "sys" erkennen kann.
Interessant. Ganz nachvollziehen kann ich deine Ergebnisse bei mir aber nicht.
$ make fork_bench cc fork_bench.c -o fork_bench $ time ./fork_bench 1000 100 v real 0m0.217s user 0m0.098s sys 0m0.113s $ time ./fork_bench 1000 100 f real 0m0.306s user 0m0.105s sys 0m0.157s $ time ./fork_bench 1000 100 v 1 real 0m0.285s user 0m0.104s sys 0m0.125s $ time ./fork_bench 1000 100 f 1 real 0m0.358s user 0m0.098s sys 0m0.169s
Fork ist bei mir nur etwas langsamer als vfork, nicht wie bei dir gleich um eine Größenordnung. Der Code wurde 1:1 aus deinem Posting in eine neue Datei fork_bench.c kopiert. System ist Fedora 18 (d.h. glibc 2.16, gcc 4.7.2) mit 3.7.0-Kernel auf Core i7 920 (2,67 GHz) und 12 GiB RAM. Alles x86_64 natürlich.
Mal gucken, ob sich ausgraben lässt, woran genau das liegt.
-
der Unterschied zwischen fork() und vfork() zeigt, daß bei fork() eben doch so einiges kopiert wird, auch wenn einige Leute behaupten, fork() kopiere nichts. die Ergebnisse sind auch vergleichbar mit dem Test in dem oben verlinkten Ruby Artikel
damals auf der PDP-11 war die Welt noch in Ordnung. einige Leute verwenden fork() seit 1980 und hatten noch nie Probleme damit ... herzlichen Glückwunsch
heutzutage hat man im Programm offene Databaseconnections, mehrere Threads, Mutexobjekte usw., und da kommt es mit fork/vfork regelmäßig zu Deadlocks, Memory Leaks und anderen Problemen. deshalb gibt es z.B. solche Funktionen wie pthread_atfork()
http://linux.die.net/man/3/pthread_atfork
und sqldetach()
optimal ist eine posix_spawn Implementierung, die threadsafe ist und vfork oder einen nativen Systemcall verwendet, wie z.B. unter Solaris
https://bugzilla.redhat.com/show_bug.cgi?id=131938
https://bugzilla.redhat.com/show_bug.cgi?id=193631eine relativ einfache und sichere Alternative ist auch, daß man am Anfang vom Hauptprogramm, bevor man den ersten Thread startet und bevor man die erste Databaseconnection öffnet, einen Hilfsprozeß startet. dieser Hilfsprozeß hat nur einen Thread und macht nichst weiter, als über IPC auf Kommandos vom Hauptprogramm zu warten. immer wenn das Hauptprogramm einen ext. Prozeß starten möchte, schickt es ein Kommando zum Hilfsprozeß und der startet das dann