Was kommt Windows am nächsten an fork()?

Lesezeit: 12 Minuten

Was kommt Windows am nachsten an fork
rlbond

Ich denke die Frage sagt alles.

Ich möchte unter Windows forken. Was ist die ähnlichste Operation und wie verwende ich sie?

Was kommt Windows am nachsten an fork
Laurynas Biveinis

Cygwin hat unter Windows alle Funktionen von fork(). Wenn also die Verwendung von Cygwin für Sie akzeptabel ist, dann ist das Problem gelöst, falls die Leistung kein Problem darstellt.

Ansonsten können Sie sich ansehen, wie Cygwin fork() implementiert. Von einer ziemlich alten Cygwin-Architektur Dok:

5.6. Prozesserstellung Der Fork-Aufruf in Cygwin ist besonders interessant, weil er sich nicht gut auf die Win32-API abbilden lässt. Dies macht es sehr schwierig, es richtig umzusetzen. Derzeit ist der Cygwin-Fork eine Non-Copy-on-Write-Implementierung, ähnlich dem, was in frühen Varianten von UNIX vorhanden war.

Das erste, was passiert, wenn ein übergeordneter Prozess einen untergeordneten Prozess verzweigt, ist, dass der übergeordnete Prozess einen Platz in der Cygwin-Prozesstabelle für den untergeordneten Prozess initialisiert. Anschließend erstellt es mithilfe des Win32-CreateProcess-Aufrufs einen angehaltenen untergeordneten Prozess. Als nächstes ruft der übergeordnete Prozess setjmp auf, um seinen eigenen Kontext zu speichern, und setzt einen Zeiger darauf in einem gemeinsam genutzten Cygwin-Speicherbereich (der von allen Cygwin-Tasks gemeinsam genutzt wird). Dann füllt es die .data- und .bss-Abschnitte des Kindes aus, indem es aus seinem eigenen Adressraum in den Adressraum des ausgesetzten Kindes kopiert. Nachdem der Adressraum des Kindes initialisiert wurde, wird das Kind ausgeführt, während das Elternteil auf einen Mutex wartet. Das Kind entdeckt, dass es gegabelt wurde, und macht Weitsprünge unter Verwendung des gespeicherten Sprungpuffers. Das Kind setzt dann den Mutex, auf den der Elternteil wartet, und blockiert einen anderen Mutex. Dies ist das Signal für den Elternteil, seinen Stack und Heap in den Kindteil zu kopieren, woraufhin er den Mutex freigibt, auf den der Kindteil wartet, und vom Fork-Aufruf zurückkehrt. Schließlich wacht das Kind vom Blockieren auf dem letzten Mutex auf, erstellt alle speicherabgebildeten Bereiche neu, die ihm über den gemeinsam genutzten Bereich übergeben wurden, und kehrt vom Fork selbst zurück.

Obwohl wir einige Ideen haben, wie wir unsere Fork-Implementierung beschleunigen können, indem wir die Anzahl der Kontextwechsel zwischen dem übergeordneten und dem untergeordneten Prozess reduzieren, wird Fork mit ziemlicher Sicherheit immer unter Win32 ineffizient sein. Glücklicherweise kann in den meisten Fällen die von Cygwin bereitgestellte Spawn-Familie von Aufrufen mit nur geringem Aufwand durch ein Fork/Exec-Paar ersetzt werden. Diese Aufrufe werden sauber auf der Win32-API abgebildet. Dadurch sind sie viel effizienter. Das Ändern des Treiberprogramms des Compilers zum Aufrufen von spawn anstelle von fork war eine triviale Änderung und erhöhte die Kompilierungsgeschwindigkeit in unseren Tests um zwanzig bis dreißig Prozent.

Spawn und Exec haben jedoch ihre eigenen Schwierigkeiten. Da es unter Win32 keine Möglichkeit gibt, eine tatsächliche Ausführung durchzuführen, muss Cygwin seine eigenen Prozess-IDs (PIDs) erfinden. Wenn ein Prozess mehrere Exec-Aufrufe ausführt, werden daher mehrere Windows-PIDs mit einer einzigen Cygwin-PID verknüpft. In einigen Fällen können Stubs von jedem dieser Win32-Prozesse verweilen und darauf warten, dass ihr ausgeführter Cygwin-Prozess beendet wird.

Klingt nach viel Arbeit, oder? Und ja, es ist slooooow.

BEARBEITEN: Das Dokument ist veraltet, bitte sehen Sie sich diese ausgezeichnete Antwort für ein Update an

  • Dies ist eine gute Antwort, wenn Sie eine Cygwin-App unter Windows schreiben möchten. Aber im Allgemeinen ist es nicht das Beste, was zu tun ist. Grundsätzlich sind die *nix- und Windows-Prozess- und Thread-Modelle ziemlich unterschiedlich. CreateProcess() und CreateThread() sind die allgemein äquivalenten APIs

    – Vordecker

    13. Juni 2009 um 4:08 Uhr

  • Entwickler sollten bedenken, dass dies ein nicht unterstützter Mechanismus ist und IIRC tatsächlich dazu neigt, zu brechen, wenn ein anderer Prozess auf dem System Code-Injektion verwendet.

    – Harry Johnston

    6. April 2014 um 2:33 Uhr

  • Der andere Implementierungslink ist nicht mehr gültig.

    – PythonNut

    27. November 2014 um 18:28 Uhr

  • Bearbeitet, um nur den anderen Antwortlink zu belassen

    – Laurynas Biveinis

    28. November 2014 um 5:14 Uhr

  • @Foredecker, eigentlich du sollte es nicht tun auch wenn Sie versuchen, eine “Cygwin-App” zu schreiben. Es versucht, Unix zu imitieren fork dennoch erreicht es dies mit a undichte Lösung und Sie müssen auf unerwartete Situationen vorbereitet sein.

    – Schrittmacher

    13. August 2015 um 11:11 Uhr

1646966417 441 Was kommt Windows am nachsten an fork
Michael Burr

Ich kenne die Details dazu sicherlich nicht, weil ich es noch nie gemacht habe, aber die native NT-API hat die Fähigkeit, einen Prozess zu forken (das POSIX-Subsystem unter Windows benötigt diese Fähigkeit – ich bin mir nicht sicher, ob das POSIX-Subsystem wird sogar noch unterstützt).

Eine Suche nach ZwCreateProcess() sollte Ihnen mehr Details liefern – zum Beispiel diese Information von Maxim Shatskih:

Der wichtigste Parameter hier ist SectionHandle. Wenn dieser Parameter NULL ist, verzweigt der Kernel den aktuellen Prozess. Andernfalls muss dieser Parameter ein Handle des SEC_IMAGE-Abschnittsobjekts sein, das in der EXE-Datei erstellt wurde, bevor ZwCreateProcess() aufgerufen wird.

Beachten Sie das jedoch Corinna Vinschen gibt an, dass Cygwin die Verwendung von ZwCreateProcess() immer noch als unzuverlässig empfunden hat:

Iker Arizmendi schrieb:

> Because the Cygwin project relied solely on Win32 APIs its fork
> implementation is non-COW and inefficient in those cases where a fork
> is not followed by exec.  It's also rather complex. See here (section
> 5.6) for details:
>  
> http://www.redhat.com/support/wpapers/cygnus/cygnus_cygwin/architecture.html

Dieses Dokument ist ziemlich alt, 10 Jahre oder so. Während wir immer noch Win32-Aufrufe verwenden, um Forks zu emulieren, hat sich die Methode merklich geändert. Insbesondere erstellen wir den untergeordneten Prozess nicht mehr im suspendierten Zustand, es sei denn, bestimmte Datenstrukturen erfordern eine spezielle Behandlung im übergeordneten Prozess, bevor sie in den untergeordneten Prozess kopiert werden. In der aktuellen Version 1.5.25 sind offene Sockets im Elternteil der einzige Fall für ein suspendiertes Kind. Die kommende Version 1.7.0 wird überhaupt nicht ausgesetzt.

Ein Grund, ZwCreateProcess nicht zu verwenden, war, dass wir bis zur Version 1.5.25 noch Benutzer von Windows 9x unterstützen. Zwei Versuche, ZwCreateProcess auf NT-basierten Systemen zu verwenden, schlugen jedoch aus dem einen oder anderen Grund fehl.

Es wäre wirklich schön, wenn diese Dinge besser oder überhaupt dokumentiert wären, insbesondere ein paar Datenstrukturen und wie man einen Prozess mit einem Subsystem verbindet. Fork ist zwar kein Win32-Konzept, aber ich sehe nicht, dass es schlecht wäre, die Implementierung von Fork zu vereinfachen.

  • Das ist die falsche Antwort. CreateProcess() und CreateThread() sind die allgemeinen Äquivalente.

    – Vordecker

    13. Juni 2009 um 4:05 Uhr

  • Interix ist in Windows Vista Enterprise/Ultimate als „Subsystem für UNIX-Anwendungen“ verfügbar: de.wikipedia.org/wiki/Interix

    – bk1e

    13. Juni 2009 um 18:03 Uhr

  • @Foredecker – das ist vielleicht eine falsche Antwort, aber CreateProcess()/CreateThread() könnte auch falsch sein. Es hängt davon ab, ob man nach „der Win32-Art, Dinge zu erledigen“ oder „so nah wie möglich an der fork()-Semantik“ sucht. CreateProcess() verhält sich deutlich anders als fork(), weshalb cygwin viel Arbeit leisten musste, um es zu unterstützen.

    – Michael Burr

    13. Juni 2009 um 18:29 Uhr

  • @jon: Ich habe versucht, die Links zu reparieren und den relevanten Text in die Antwort zu kopieren (damit zukünftige defekte Links kein Problem darstellen). Diese Antwort ist jedoch lange genug her, dass ich nicht 100% sicher bin, dass das Zitat, das ich heute gefunden habe, das ist, auf das ich mich 2009 bezog.

    – Michael Burr

    19. Juli 2013 um 23:31 Uhr

  • Wenn die Leute wollen“fork mit sofort exec“, dann ist CreateProcess vielleicht ein Kandidat. Aber fork ohne exec ist oft wünschenswert und das ist es, was die Fahrer wirklich verlangen fork.

    – Aaron McDaid

    16. Oktober 2014 um 17:56 Uhr


1646966417 386 Was kommt Windows am nachsten an fork
Evan Teran

Nun, Windows hat nicht wirklich etwas Vergleichbares. Zumal sich mit fork konzeptionell ein Thread oder ein Prozess in *nix erzeugen lässt.

Also ich muss sagen:

CreateProcess()/CreateProcessEx()

und

CreateThread() (Ich habe gehört, dass für C-Anwendungen _beginthreadex() ist besser).

1646966418 46 Was kommt Windows am nachsten an fork
Eric des Courtis

Es wurde versucht, Forks unter Windows zu implementieren. Das kommt dem am nächsten, was ich finden kann:

Genommen von: http://doxygen.scilab.org/5.3/d0/d8f/forkWindows_8c_source.html#l00216

static BOOL haveLoadedFunctionsForFork(void);

int fork(void) 
{
    HANDLE hProcess = 0, hThread = 0;
    OBJECT_ATTRIBUTES oa = { sizeof(oa) };
    MEMORY_BASIC_INFORMATION mbi;
    CLIENT_ID cid;
    USER_STACK stack;
    PNT_TIB tib;
    THREAD_BASIC_INFORMATION tbi;

    CONTEXT context = {
        CONTEXT_FULL | 
        CONTEXT_DEBUG_REGISTERS | 
        CONTEXT_FLOATING_POINT
    };

    if (setjmp(jenv) != 0) return 0; /* return as a child */

    /* check whether the entry points are 
       initilized and get them if necessary */
    if (!ZwCreateProcess && !haveLoadedFunctionsForFork()) return -1;

    /* create forked process */
    ZwCreateProcess(&hProcess, PROCESS_ALL_ACCESS, &oa,
        NtCurrentProcess(), TRUE, 0, 0, 0);

    /* set the Eip for the child process to our child function */
    ZwGetContextThread(NtCurrentThread(), &context);

    /* In x64 the Eip and Esp are not present, 
       their x64 counterparts are Rip and Rsp respectively. */
#if _WIN64
    context.Rip = (ULONG)child_entry;
#else
    context.Eip = (ULONG)child_entry;
#endif

#if _WIN64
    ZwQueryVirtualMemory(NtCurrentProcess(), (PVOID)context.Rsp,
        MemoryBasicInformation, &mbi, sizeof mbi, 0);
#else
    ZwQueryVirtualMemory(NtCurrentProcess(), (PVOID)context.Esp,
        MemoryBasicInformation, &mbi, sizeof mbi, 0);
#endif

    stack.FixedStackBase = 0;
    stack.FixedStackLimit = 0;
    stack.ExpandableStackBase = (PCHAR)mbi.BaseAddress + mbi.RegionSize;
    stack.ExpandableStackLimit = mbi.BaseAddress;
    stack.ExpandableStackBottom = mbi.AllocationBase;

    /* create thread using the modified context and stack */
    ZwCreateThread(&hThread, THREAD_ALL_ACCESS, &oa, hProcess,
        &cid, &context, &stack, TRUE);

    /* copy exception table */
    ZwQueryInformationThread(NtCurrentThread(), ThreadBasicInformation,
        &tbi, sizeof tbi, 0);
    tib = (PNT_TIB)tbi.TebBaseAddress;
    ZwQueryInformationThread(hThread, ThreadBasicInformation,
        &tbi, sizeof tbi, 0);
    ZwWriteVirtualMemory(hProcess, tbi.TebBaseAddress, 
        &tib->ExceptionList, sizeof tib->ExceptionList, 0);

    /* start (resume really) the child */
    ZwResumeThread(hThread, 0);

    /* clean up */
    ZwClose(hThread);
    ZwClose(hProcess);

    /* exit with child's pid */
    return (int)cid.UniqueProcess;
}
static BOOL haveLoadedFunctionsForFork(void)
{
    HANDLE ntdll = GetModuleHandle("ntdll");
    if (ntdll == NULL) return FALSE;

    if (ZwCreateProcess && ZwQuerySystemInformation && ZwQueryVirtualMemory &&
        ZwCreateThread && ZwGetContextThread && ZwResumeThread &&
        ZwQueryInformationThread && ZwWriteVirtualMemory && ZwClose)
    {
        return TRUE;
    }

    ZwCreateProcess = (ZwCreateProcess_t) GetProcAddress(ntdll,
        "ZwCreateProcess");
    ZwQuerySystemInformation = (ZwQuerySystemInformation_t)
        GetProcAddress(ntdll, "ZwQuerySystemInformation");
    ZwQueryVirtualMemory = (ZwQueryVirtualMemory_t)
        GetProcAddress(ntdll, "ZwQueryVirtualMemory");
    ZwCreateThread = (ZwCreateThread_t)
        GetProcAddress(ntdll, "ZwCreateThread");
    ZwGetContextThread = (ZwGetContextThread_t)
        GetProcAddress(ntdll, "ZwGetContextThread");
    ZwResumeThread = (ZwResumeThread_t)
        GetProcAddress(ntdll, "ZwResumeThread");
    ZwQueryInformationThread = (ZwQueryInformationThread_t)
        GetProcAddress(ntdll, "ZwQueryInformationThread");
    ZwWriteVirtualMemory = (ZwWriteVirtualMemory_t)
        GetProcAddress(ntdll, "ZwWriteVirtualMemory");
    ZwClose = (ZwClose_t) GetProcAddress(ntdll, "ZwClose");

    if (ZwCreateProcess && ZwQuerySystemInformation && ZwQueryVirtualMemory &&
        ZwCreateThread && ZwGetContextThread && ZwResumeThread &&
        ZwQueryInformationThread && ZwWriteVirtualMemory && ZwClose)
    {
        return TRUE;
    }
    else
    {
        ZwCreateProcess = NULL;
        ZwQuerySystemInformation = NULL;
        ZwQueryVirtualMemory = NULL;
        ZwCreateThread = NULL;
        ZwGetContextThread = NULL;
        ZwResumeThread = NULL;
        ZwQueryInformationThread = NULL;
        ZwWriteVirtualMemory = NULL;
        ZwClose = NULL;
    }
    return FALSE;
}

1646966419 960 Was kommt Windows am nachsten an fork
paxdiablo

Bevor Microsoft seine neue Option “Linux-Subsystem für Windows” einführte, CreateProcess() war das, was Windows am nächsten kommen muss fork()aber Windows erfordert, dass Sie eine ausführbare Datei angeben, die in diesem Prozess ausgeführt werden soll.

Die UNIX-Prozesserstellung unterscheidet sich stark von Windows. Es ist fork() call dupliziert im Wesentlichen den aktuellen Prozess fast vollständig, jeder in seinem eigenen Adressraum, und führt sie separat weiter. Obwohl die Prozesse selbst unterschiedlich sind, laufen sie immer noch gleich ab Programm. Hier finden Sie einen guten Überblick über die fork/exec Modell.

Umgekehrt das Äquivalent von Windows CreateProcess() ist der fork()/exec() Paar von Funktionen in UNIX.

Wenn Sie Software auf Windows portieren und Ihnen eine Übersetzungsschicht nichts ausmacht, hat Cygwin die gewünschte Funktion bereitgestellt, aber es war ziemlich klobig.

Natürlich, mit das neue Linux-Subsystemdas, was Windows am nächsten kommen muss fork() ist eigentlich fork() 🙂

  • Kann ich also angesichts der WSL verwenden fork in einer durchschnittlichen Nicht-WSL-Anwendung?

    – Cäsar

    4. März 2019 um 14:42 Uhr

1646966419 431 Was kommt Windows am nachsten an fork
MCCCS

Das folgende Dokument enthält einige Informationen zum Portieren von Code von UNIX nach Win32:
https://msdn.microsoft.com/en-us/library/y23kc048.aspx

Unter anderem weist es darauf hin, dass das Prozessmodell zwischen den beiden Systemen ziemlich unterschiedlich ist, und empfiehlt die Berücksichtigung von CreateProcess und CreateThread, wo ein fork()-ähnliches Verhalten erforderlich ist.

  • Kann ich also angesichts der WSL verwenden fork in einer durchschnittlichen Nicht-WSL-Anwendung?

    – Cäsar

    4. März 2019 um 14:42 Uhr

1646966420 831 Was kommt Windows am nachsten an fork
benrg

Wie andere Antworten bereits erwähnt haben, hat NT (der Kernel, der modernen Windows-Versionen zugrunde liegt) ein Äquivalent zu Unix fork(). Das ist nicht das Problem.

Das Problem ist, dass das Klonen des gesamten Zustands eines Prozesses im Allgemeinen nicht vernünftig ist. Dies gilt in der Unix-Welt ebenso wie in Windows, aber in der Unix-Welt wird fork() ständig verwendet, und Bibliotheken sind darauf ausgelegt, damit umzugehen. Windows-Bibliotheken sind es nicht.

Beispielsweise unterhalten die System-DLLs kernel32.dll und user32.dll eine private Verbindung zum Win32-Serverprozess csrss.exe. Nach einem Fork gibt es zwei Prozesse auf der Client-Seite dieser Verbindung, was zu Problemen führen wird. Der untergeordnete Prozess sollte csrss.exe über seine Existenz informieren und eine neue Verbindung herstellen – aber dafür gibt es keine Schnittstelle, da diese Bibliotheken nicht im Hinblick auf fork() entwickelt wurden.

Sie haben also zwei Möglichkeiten. Eine besteht darin, die Verwendung von kernel32 und user32 und anderen Bibliotheken zu verbieten, die nicht zum Forken entwickelt wurden – einschließlich aller Bibliotheken, die direkt oder indirekt auf kernel32 oder user32 verlinken, was praktisch alle sind. Das bedeutet, dass Sie überhaupt nicht mit dem Windows-Desktop interagieren können und in Ihrer eigenen separaten Unixy-Welt stecken bleiben. Dies ist der Ansatz, den die verschiedenen Unix-Subsysteme für NT verfolgen.

Die andere Möglichkeit besteht darin, auf eine Art schrecklichen Hack zurückzugreifen, um zu versuchen, unbewusste Bibliotheken dazu zu bringen, mit fork() zu arbeiten. Das macht Cygwin. Es erstellt einen neuen Prozess, lässt ihn initialisieren (einschließlich der Registrierung bei csrss.exe), kopiert dann den größten Teil des dynamischen Zustands vom alten Prozess und hofft auf das Beste. Es erstaunt mich, dass dies je funktioniert. Es funktioniert sicherlich nicht zuverlässig – selbst wenn es nicht zufällig aufgrund eines Adressraumkonflikts fehlschlägt, kann jede Bibliothek, die Sie verwenden, stillschweigend in einem defekten Zustand verbleiben. Die Behauptung der derzeit akzeptierten Antwort, dass Cygwin eine “voll funktionsfähige Gabel ()” hat, ist … zweifelhaft.

Zusammenfassung: In einer Interix-ähnlichen Umgebung können Sie forken, indem Sie fork() aufrufen. Ansonsten versuchen Sie bitte, sich die Lust dazu abzugewöhnen. Selbst wenn Sie auf Cygwin abzielen, verwenden Sie nicht fork(), es sei denn, Sie müssen es unbedingt tun.

989640cookie-checkWas kommt Windows am nächsten an fork()?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy