fork & exec vs. posix_spawn (Fork ;-) aus Programm unter Linux und Windows)
-
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
-
dd++ schrieb:
der Unterschied zwischen fork() und vfork() zeigt, daß bei fork() eben doch so einiges kopiert wird, auch wenn einige Leute behaupten, fork() kopiere nichts.
Schau dir doch einfach mal die fork-Implementierung deiner libc an und fertig.
Ich kann die Benchmarks auch nicht nachvollziehen.
Um Horst Hannelores Benchmarks zu ergänzen hier noch ein 32-bittiges Squeeze auf x86-Hardware (interessant weil 32 bit und uralte Paketversionen):
~/fork_bench % time ./fork_bench 1000 100 v ./fork_bench 1000 100 v 0.13s user 0.73s system 99% cpu 0.866 total ~/fork_bench % time ./fork_bench 1000 100 f ./fork_bench 1000 100 f 0.12s user 0.74s system 100% cpu 0.858 total ~/fork_bench % time ./fork_bench 1000 100 f 1 ./fork_bench 1000 100 f 1 0.15s user 0.70s system 100% cpu 0.847 total ~/fork_bench % time ./fork_bench 1000 100 v 1 ./fork_bench 1000 100 v 1 0.13s user 0.72s system 94% cpu 0.893 total
~ % uname -r 2.6.32-5-686-bigmem ~ % apt-cache show libc6-i686 | grep Version Version: 2.11.3-4 ~ % apt-cache show gcc | grep Version Version: 4:4.4.5-1
-
Ich finde, dass die Performance eines Systemaufrufes generell nicht vorhersehbar ist und glaube daher nicht, dass man sein Programm-Design deshalb an die Effizienz eines Betriebssystems anpassen sollte.
Will man fork()/vfork() und exec() einsetzen, möchte man oft ein Fremdprogram starten bzw steuern und da finde ich ein paar andere Dinge viel wichtiger, auf die oft vergessen wird:
Offene Datei-Deskriptoren werden an einen Fremdprozess übertragen!
Das kann eine Bibliothek sein, die im Stammprozess einen Thread erzeugt hat und dort eine Sperr-Datei für einen exklusiven Zugriff auf eine Datenbank geöffnet hat und so entsteht dann in dem neuen Prozess eine nie wieder freigegebene Sperre. Ein close() auf alle Deskriptoren nach dem fork() im Kind ist wirklich sehr unschön und vfork() könnt soetwas gar nicht realisieren. Dann noch die Frage ob der neue Prozess eine eigene Session (setsid()) bekommen soll oder nicht und dann auch mit dem Stammprozess gekillt wird oder eben nicht.Dass andere Threads aus dem Stammprozess im Kind einfach terminiert werden und nur pthread_atfork() aufräumen könnte, hilft auch selten. Wie erkläre ich einem std::vector in einem anderen Thread aus dem Stammprozess, dass er bei einem fork() im Kind seine Daten freigeben soll?
Dann gibt es auch noch das Problem, wie ein Kind-Prozess den Erfolg eines exec() seinem Parent mitteilt, damit dieser das Kind fernsteuern kann ... OK, dafür gibt es zwar Lösungen mit zusätzlichen Pipes und Auto-close aber alles in allem kann ich da ein CreateProcess() aus der WinAPI nur vorziehen.
Ich habe mich inzwischen zwar an dieses Coding-Schema gewöhnt aber stelle trotzdem fest, dass man die posix-Funktionen sehr schwer in RAII-konforme C++ Klassen kapseln kann. Die WinAPI ist da kompakter und das Design lässt sich da mit weit weniger Seiteneffekten kapseln. Signale sind dann auch noch so ein Sonderfall ... aber naja ... nobody is perfect ... dafür versagt Windows wieder an ganz ander Stellen jämmerlich
PS: Ich kenn da so ein Embedded System mit 2 GB RAM und Java schafft es mit 3 Prozessen auch dort regelmäßig den Start neuer Prozesse zu versauen
... und immer genau dann leuchten die Dollar- bzw Euro-Symbole in meinen C++-Augäpfeln auf
-
Horst Hannelore schrieb:
Mal gucken, ob sich ausgraben lässt, woran genau das liegt.
Auch interessant. Zumal Deine Konfiguration meiner recht ähnlich ist. Einen Unterschied gibt es allerdings in der Ausführung: Ich habe fork_bench mit -O2 übersetzt. Das scheint aber nicht wirklich einen großen Unterschied zu machen.
Bei weiteren Untersuchungen habe ich festgestellt, dass die Streuung der Ergebnisse groß ist. Abhilfe schafft die Erhöhung der Wiederholungen:
$ time ./fork_bench 1000 1000 f real 0m20.121s user 0m0.122s sys 0m17.393s $ time ./fork_bench 1000 1000 v real 0m0.417s user 0m0.105s sys 0m0.274s
vfork bleibt deutlich schneller.
Hast Du eigentlich wirklich Fedora 18? Das ist noch nicht released.
-
Under Linux, fork(2) is implemented using copy-on-write pages, so the only
penalty incurred by fork(2) is the time and memory required to duplicate the
parent's page tables, and to create a unique task structure for the child.
However, in the bad old days a fork(2) would require making a complete copy of
the caller's data space, often needlessly, since usually immediately afterward
an exec(3) is done.