Ich denke die Frage sagt alles.
Ich möchte unter Windows forken. Was ist die ähnlichste Operation und wie verwende ich sie?
rlbond
Ich denke die Frage sagt alles.
Ich möchte unter Windows forken. Was ist die ähnlichste Operation und wie verwende ich sie?
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
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
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).
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;
}
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
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
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.