Antworten:
Cygwin hat fork () unter Windows voll ausgestattet. Wenn die Verwendung von Cygwin für Sie akzeptabel ist, ist das Problem gelöst, falls die Leistung kein Problem darstellt.
Ansonsten können Sie sich ansehen, wie Cygwin fork () implementiert. Aus einer ganz alten Cygwin Architektur doc :
5.6. Prozesserstellung Der Fork-Aufruf in Cygwin ist besonders interessant, da er nicht gut auf der Win32-API abgebildet wird. Dies macht es sehr schwierig, richtig zu implementieren. Derzeit handelt es sich bei der Cygwin-Gabel um eine Implementierung, die nicht kopiert und geschrieben werden kann, ähnlich wie dies in früheren UNIX-Versionen der Fall war.
Das erste, was passiert, wenn ein übergeordneter Prozess einen untergeordneten Prozess teilt, ist, dass der übergeordnete Prozess ein Leerzeichen in der Cygwin-Prozesstabelle für das untergeordnete Element initialisiert. Anschließend wird mit dem Win32 CreateProcess-Aufruf ein angehaltener untergeordneter Prozess erstellt. 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 (gemeinsam genutzt von allen Cygwin-Tasks). Anschließend werden die Abschnitte .data und .bss des Kindes ausgefüllt, indem aus dem eigenen Adressraum in den Adressraum des suspendierten Kindes kopiert wird. Nachdem der Adressraum des Kindes initialisiert wurde, wird das Kind ausgeführt, während das Elternteil auf einen Mutex wartet. Das Kind entdeckt mithilfe des gespeicherten Sprungpuffers, dass es gegabelt wurde, und springt weit. Das Kind setzt dann den Mutex, auf den das Elternteil wartet, und blockiert einen anderen Mutex. Dies ist das Signal für das übergeordnete Element, seinen Stapel und Heap in das untergeordnete Element zu kopieren. Danach gibt es den Mutex frei, auf den das untergeordnete Element wartet, und kehrt vom Fork-Aufruf zurück. Schließlich erwacht das Kind vom Blockieren des letzten Mutex, erstellt alle Speicherbereiche neu, die über den gemeinsam genutzten Bereich an es übergeben wurden, und kehrt von der Verzweigung selbst zurück.
Während 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 unter Win32 mit ziemlicher Sicherheit immer ineffizient sein. Glücklicherweise kann in den meisten Fällen die von Cygwin bereitgestellte Spawn-Familie von Anrufen mit nur geringem Aufwand durch ein Fork / Exec-Paar ersetzt werden. Diese Aufrufe werden sauber über der Win32-API zugeordnet. Dadurch sind sie viel effizienter. Das Ändern des Treiberprogramms des Compilers, um Spawn anstelle von Fork aufzurufen, war eine triviale Änderung und erhöhte die Kompilierungsgeschwindigkeit in unseren Tests um 20 bis 30 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 eigene Prozess-IDs (PIDs) erfinden. Wenn ein Prozess mehrere Exec-Aufrufe ausführt, sind daher mehrere Windows-PIDs mit einer einzelnen Cygwin-PID verknüpft. In einigen Fällen können Stubs jedes dieser Win32-Prozesse verweilen und darauf warten, dass der ausgeführte Cygwin-Prozess beendet wird.
Klingt nach viel Arbeit, nicht wahr? Und ja, es ist langsam.
BEARBEITEN: Das Dokument ist veraltet. Bitte lesen Sie diese hervorragende Antwort für ein Update
fork
, erreicht dies jedoch mit einer undichten Lösung, und Sie müssen auf unerwartete Situationen vorbereitet sein.
Ich kenne die Details dazu sicherlich nicht, weil ich es noch nie gemacht habe, aber die native NT-API kann einen Prozess verzweigen (das POSIX-Subsystem unter Windows benötigt diese Funktion - ich bin nicht sicher, ob das POSIX-Subsystem wird sogar noch unterstützt).
Eine Suche nach ZwCreateProcess () sollte Ihnen weitere Details liefern - zum Beispiel diese Informationen von Maxim Shatskih :
Der wichtigste Parameter hier ist SectionHandle. Wenn dieser Parameter NULL ist, teilt der Kernel den aktuellen Prozess auf. Andernfalls muss dieser Parameter ein Handle des Abschnittsobjekts SEC_IMAGE sein, das in der EXE-Datei erstellt wurde, bevor ZwCreateProcess () aufgerufen wird.
Beachten Sie jedoch, dass Corinna Vinschen angibt, 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, ungefähr 10 Jahre. Während wir noch Win32-Aufrufe zum Emulieren von Fork verwenden, hat sich die Methode merklich geändert. Insbesondere erstellen wir den untergeordneten Prozess nicht mehr im angehaltenen Zustand, es sei denn, bestimmte Datenstrukturen benötigen eine spezielle Behandlung im übergeordneten Prozess, bevor sie in das untergeordnete Element 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 immer noch Windows 9x-Benutzer 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 dieses Zeug besser oder überhaupt dokumentiert wäre, insbesondere ein paar Datenstrukturen und wie man einen Prozess mit einem Subsystem verbindet. Obwohl Fork kein Win32-Konzept ist, sehe ich nicht, dass es eine schlechte Sache wäre, die Implementierung von Fork zu vereinfachen.
fork
mit sofort exec
" wollen, dann ist CreateProcess vielleicht ein Kandidat. Aber fork
ohne exec
ist oft wünschenswert und das ist es, was die Leute von einem echten Fahrer verlangen fork
.
Nun, Windows hat so etwas nicht wirklich. Zumal Fork verwendet werden kann, um einen Thread oder einen Prozess in * nix konzeptionell zu erstellen.
Also muss ich sagen:
CreateProcess()
/.CreateProcessEx()
und
CreateThread()
(Ich habe gehört, dass für C-Anwendungen _beginthreadex()
besser ist).
Die Leute haben versucht, Fork unter Windows zu implementieren. Dies ist das Nächste, was ich finden kann:
Entnommen aus: 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;
}
fork
Absturz, das Programm oder der Thread nur abstürzt? Wenn das Programm abstürzt, ist dies nicht wirklich eine Gabelung. Nur neugierig, weil ich nach einer echten Lösung suche und hoffe, dass dies eine anständige Alternative ist.
Vor der Einführung der neuen Option "Linux-Subsystem für Windows" durch Microsoft war Windows CreateProcess()
das Nächste, was Windows tun mussfork()
, aber Windows erfordert, dass Sie eine ausführbare Datei angeben, die in diesem Prozess ausgeführt werden soll.
Die Erstellung des UNIX-Prozesses unterscheidet sich erheblich von Windows. Der fork()
Aufruf dupliziert den aktuellen Prozess im Wesentlichen fast vollständig, jeder in seinem eigenen Adressraum, und führt sie separat weiter aus. Obwohl die Prozesse selbst unterschiedlich sind, führen sie immer noch dasselbe Programm aus. Sehen Sie hier für einen guten Überblick über das fork/exec
Modell.
Möchten Sie in die andere Richtung, das Äquivalent von Windows CreateProcess()
ist das fork()/exec()
Paar von Funktionen in UNIX.
Wenn Sie Software auf Windows portiert haben und eine Übersetzungsschicht nichts ausmacht, hat Cygwin die gewünschte Funktion bereitgestellt, die jedoch ziemlich schwierig war.
Natürlich mit dem neuen Linux - Subsystem , die nächste Sache, hat Windows fork()
ist eigentlich fork()
:-)
fork
bei gegebener WSL eine durchschnittliche Nicht-WSL-Anwendung verwenden?
Das folgende Dokument enthält einige Informationen zum Portieren von Code von UNIX nach Win32: https://msdn.microsoft.com/en-us/library/y23kc048.aspx
Dies weist unter anderem darauf hin, dass das Prozessmodell zwischen den beiden Systemen sehr unterschiedlich ist, und empfiehlt die Berücksichtigung von CreateProcess und CreateThread, wenn ein fork () -ähnliches Verhalten erforderlich ist.
"Sobald Sie Dateizugriff oder Druck ausführen möchten, wird io abgelehnt."
Sie können Ihren Kuchen nicht haben und ihn auch nicht essen ... In msvcrt.dll basiert printf () auf der Konsolen-API, die selbst lpc verwendet, um mit dem Konsolensubsystem (csrss.exe) zu kommunizieren. Die Verbindung mit csrss wird beim Start des Prozesses initiiert. Dies bedeutet, dass bei jedem Prozess, der seine Ausführung "in der Mitte" beginnt, dieser Schritt übersprungen wird. Wenn Sie keinen Zugriff auf den Quellcode des Betriebssystems haben, ist es sinnlos, manuell eine Verbindung zu csrss herzustellen. Stattdessen sollten Sie Ihr eigenes Subsystem erstellen und dementsprechend die Konsolenfunktionen in Anwendungen vermeiden, die fork () verwenden.
Wenn Sie Ihr eigenes Subsystem implementiert haben, vergessen Sie nicht, auch alle Handles der Eltern für den untergeordneten Prozess zu duplizieren ;-)
"Außerdem sollten Sie die Zw * -Funktionen wahrscheinlich nicht verwenden, es sei denn, Sie befinden sich im Kernelmodus. Sie sollten stattdessen wahrscheinlich die Nt * -Funktionen verwenden."
ZwGetContextThread (NtCurrentThread (), & context);
Die Semantik von fork () ist erforderlich, wenn das Kind ab dem Zeitpunkt, zu dem fork () aufgerufen wird, Zugriff auf den tatsächlichen Speicherstatus des Elternteils benötigt. Ich habe eine Software, die sich auf den impliziten Mutex des Speicherkopierens ab dem Zeitpunkt des Aufrufs von fork () stützt, wodurch die Verwendung von Threads unmöglich wird. (Dies wird auf modernen * nix-Plattformen über die Semantik des Kopierens beim Schreiben / Aktualisieren der Speichertabelle emuliert.)
Der nächste, der unter Windows als Systemaufruf vorhanden ist, ist CreateProcess. Das Beste, was getan werden kann, ist, dass das übergeordnete Element alle anderen Threads während der Zeit, in der es Speicher in den Speicher des neuen Prozesses kopiert, einfriert und sie dann auftaut. Weder die Cygwin-Frok-Klasse noch der Scilab-Code, den Eric des Courtis veröffentlicht hat, führen das Einfrieren von Threads durch, wie ich sehen kann.
Außerdem sollten Sie die Zw * -Funktionen wahrscheinlich nicht verwenden, es sei denn, Sie befinden sich im Kernelmodus. Sie sollten stattdessen wahrscheinlich die Nt * -Funktionen verwenden. Es gibt einen zusätzlichen Zweig, der prüft, ob Sie sich im Kernelmodus befinden, und andernfalls alle Grenzüberprüfungen und Parameterüberprüfungen durchführt, die Nt * immer durchführt. Daher ist es etwas weniger effizient, sie aus dem Benutzermodus aufzurufen.
Ihre besten Optionen sind CreateProcess () oder CreateThread () . Weitere Informationen zur Portierung finden Sie hier .
Es gibt keine einfache Möglichkeit, fork () unter Windows zu emulieren.
Ich empfehle Ihnen, stattdessen Threads zu verwenden.
fork
war genau das, was CygWin tat. Aber wenn Sie jemals nachlesen, wie sie es gemacht haben, ist Ihr "kein einfacher Weg" ein grobes Missverständnis :-)
Das nächste, was du sagst ... Lass mich nachdenken ... Das muss fork () sein, denke ich :)
Weitere Informationen finden Sie unter Implementiert Interix fork ()?
Wie bereits in anderen Antworten erwähnt, entspricht NT (der Kernel, der modernen Windows-Versionen zugrunde liegt) Unix fork (). Das ist nicht das Problem.
Das Problem ist, dass das Klonen des gesamten Status eines Prozesses im Allgemeinen nicht sinnvoll ist. Dies gilt in der Unix-Welt genauso wie in Windows, aber in der Unix-Welt wird fork () ständig verwendet, und Bibliotheken sind darauf ausgelegt, damit umzugehen. Windows-Bibliotheken sind 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 dem Client-Ende dieser Verbindung, die Probleme verursachen werden. Der untergeordnete Prozess sollte csrss.exe über seine Existenz informieren und eine neue Verbindung herstellen. Dafür gibt es jedoch keine Schnittstelle, da diese Bibliotheken nicht für fork () entwickelt wurden.
Sie haben also zwei Möglichkeiten. Eine besteht darin, die Verwendung von Kernel32 und User32 sowie anderer Bibliotheken zu verbieten, die nicht zum Verzweigen bestimmt sind - einschließlich aller Bibliotheken, die direkt oder indirekt mit Kernel32 oder User32 verknüpft sind, was praktisch alle sind. Dies bedeutet, dass Sie überhaupt nicht mit dem Windows-Desktop interagieren können und in Ihrer eigenen Unixy-Welt stecken bleiben. Dies ist der Ansatz der verschiedenen Unix-Subsysteme für NT.
Die andere Möglichkeit besteht darin, auf einen schrecklichen Hack zurückzugreifen, um 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 Status aus dem alten Prozess und hofft auf das Beste. Es wundert mich, dass dies jemals 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 fehlerhaften Zustand belassen werden. Die Behauptung der derzeit akzeptierten Antwort, dass Cygwin eine "voll ausgestattete Gabel ()" hat, ist ... zweifelhaft.
Zusammenfassung: In einer Interix-ähnlichen Umgebung können Sie Fork durch Aufrufen von Fork () erstellen. Andernfalls versuchen Sie bitte, sich von dem Wunsch zu entwöhnen, dies zu tun. Selbst wenn Sie auf Cygwin abzielen, verwenden Sie fork () nur, wenn Sie dies unbedingt müssen.
Wenn Sie nur einen Unterprozess erstellen und darauf warten möchten, sind möglicherweise _spawn * -APIs in process.h ausreichend. Hier sind weitere Informationen dazu:
https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h