fork & exec vs. posix_spawn (Fork ;-) aus Programm unter Linux und Windows)



  • 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()


  • Mod

    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()

    http://publib.boulder.ibm.com/infocenter/idshelp/v115/index.jsp?topic=%2Fcom.ibm.esqlc.doc%2Fsii-xbsql-14203.htm

    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=193631

    eine 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
    

Anmelden zum Antworten