Risposte:
Cygwin ha fork () completo su Windows. Pertanto, se l'utilizzo di Cygwin è accettabile per te, il problema è risolto nel caso in cui le prestazioni non siano un problema.
Altrimenti puoi dare un'occhiata a come Cygwin implementa fork (). Da un documento di architettura di Cygwin piuttosto vecchio :
5.6. Creazione del processo La chiamata fork in Cygwin è particolarmente interessante perché non si mappa bene sopra l'API Win32. Ciò lo rende molto difficile da implementare correttamente. Attualmente, il fork Cygwin è un'implementazione non-copy-on-write simile a ciò che era presente nelle prime versioni di UNIX.
La prima cosa che accade quando un processo genitore esegue il fork di un processo figlio è che il genitore inizializza uno spazio nella tabella dei processi Cygwin per il figlio. Quindi crea un processo figlio sospeso utilizzando la chiamata Win32 CreateProcess. Successivamente, il processo genitore chiama setjmp per salvare il proprio contesto e imposta un puntatore a questo in un'area di memoria condivisa Cygwin (condivisa tra tutte le attività Cygwin). Quindi riempie le sezioni .data e .bss del figlio copiando dal proprio spazio indirizzo nello spazio indirizzo del figlio sospeso. Dopo che lo spazio degli indirizzi del figlio è stato inizializzato, il figlio viene eseguito mentre il genitore attende un mutex. Il bambino scopre che è stato biforcato e salta in lungo usando il buffer di salto salvato. Il bambino quindi imposta il mutex su cui il genitore sta aspettando e blocca un altro mutex. Questo è il segnale per il genitore di copiare il suo stack e heap nel figlio, dopodiché rilascia il mutex su cui il figlio sta aspettando e ritorna dalla chiamata fork. Infine, il bambino si sveglia dal blocco sull'ultimo mutex, ricrea tutte le aree mappate in memoria che gli sono passate attraverso l'area condivisa e torna dal fork stesso.
Sebbene abbiamo alcune idee su come accelerare l'implementazione del fork riducendo il numero di cambi di contesto tra il processo padre e figlio, il fork quasi certamente sarà sempre inefficiente sotto Win32. Fortunatamente, nella maggior parte dei casi, la famiglia di chiamate spawn fornita da Cygwin può essere sostituita con una coppia fork / exec con un piccolo sforzo. Queste chiamate vengono mappate in modo pulito sopra l'API Win32. Di conseguenza, sono molto più efficienti. Cambiare il programma driver del compilatore per chiamare spawn invece di fork è stato un cambiamento banale e ha aumentato le velocità di compilazione dal 20 al 30 percento nei nostri test.
Tuttavia, spawn ed exec presentano la propria serie di difficoltà. Poiché non è possibile eseguire un vero e proprio exec sotto Win32, Cygwin deve inventare i propri Process ID (PID). Di conseguenza, quando un processo esegue più chiamate exec, ci saranno più PID Windows associati a un singolo PID Cygwin. In alcuni casi, gli stub di ciascuno di questi processi Win32 possono persistere, in attesa che il processo Cygwin eseguito venga chiuso.
Sembra un sacco di lavoro, non è vero? E sì, è slooooow.
EDIT: il documento è obsoleto, vedere questa eccellente risposta per un aggiornamento
fork
ma lo fa con una soluzione che perde e devi essere preparato per situazioni impreviste.
Certamente non conosco i dettagli su questo perché non l'ho mai fatto, ma l'API NT nativa ha la capacità di eseguire il fork di un processo (il sottosistema POSIX su Windows ha bisogno di questa capacità - non sono sicuro che il sottosistema POSIX è persino più supportato).
Una ricerca di ZwCreateProcess () dovrebbe darti qualche dettaglio in più, ad esempio questo bit di informazioni da Maxim Shatskih :
Il parametro più importante qui è SectionHandle. Se questo parametro è NULL, il kernel eseguirà il fork del processo corrente. In caso contrario, questo parametro deve essere un handle dell'oggetto di sezione SEC_IMAGE creato nel file EXE prima di chiamare ZwCreateProcess ().
Tuttavia, nota che Corinna Vinschen indica che Cygwin ha scoperto che l'utilizzo di ZwCreateProcess () è ancora inaffidabile :
Iker Arizmendi ha scritto:
> 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
Questo documento è piuttosto vecchio, 10 anni circa. Mentre stiamo ancora usando le chiamate Win32 per emulare il fork, il metodo è cambiato notevolmente. In particolare, non creiamo più il processo figlio nello stato sospeso, a meno che specifici dati non necessitino di una gestione speciale nel genitore prima di essere copiati nel figlio. Nell'attuale versione 1.5.25 l'unico caso per un figlio sospeso sono i socket aperti nel genitore. La prossima versione 1.7.0 non verrà sospesa affatto.
Un motivo per non usare ZwCreateProcess era che fino alla versione 1.5.25 supportiamo ancora gli utenti di Windows 9x. Tuttavia, due tentativi di utilizzare ZwCreateProcess su sistemi basati su NT non sono riusciti per un motivo o per l'altro.
Sarebbe davvero bello se questa roba fosse migliore o del tutto documentata, specialmente un paio di strutture dati e come connettere un processo a un sottosistema. Sebbene il fork non sia un concetto Win32, non vedo che sarebbe una brutta cosa rendere il fork più facile da implementare.
fork
con immediato exec
", allora forse CreateProcess è un candidato. Ma fork
senza exec
è spesso desiderabile e questo è ciò che le persone chiedono per un vero fork
.
Bene, Windows non ha davvero nulla di simile. Soprattutto perché fork può essere utilizzato per creare concettualmente un thread o un processo in * nix.
Quindi, dovrei dire:
CreateProcess()
/CreateProcessEx()
e
CreateThread()
(Ho sentito che per le applicazioni C, _beginthreadex()
è meglio).
Le persone hanno provato a implementare il fork su Windows. Questa è la cosa più vicina che riesco a trovare:
Tratto da: 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
va in crash, il programma o il thread si blocca? Se si blocca il programma, non si tratta di un vero fork. Sono solo curioso, perché sto cercando una soluzione reale e spero che questa possa essere un'alternativa decente.
Prima che Microsoft introducesse la loro nuova opzione "Sottosistema Linux per Windows", CreateProcess()
era la cosa più vicina a Windowsfork()
, ma Windows richiede di specificare un eseguibile da eseguire in quel processo.
La creazione del processo UNIX è molto diversa da Windows. La sua fork()
chiamata fondamentalmente duplica il processo corrente quasi in totale, ciascuno nel proprio spazio di indirizzi, e continua a eseguirli separatamente. Sebbene i processi stessi siano diversi, eseguono ancora lo stesso programma. Vedi qui per una buona panoramica del fork/exec
modello.
Tornando indietro, l'equivalente di Windows CreateProcess()
è la fork()/exec()
coppia di funzioni in UNIX.
Se stavi eseguendo il porting del software su Windows e non ti dispiace un livello di traduzione, Cygwin ha fornito la capacità che desideri ma era piuttosto complicata.
Naturalmente, con il nuovo sottosistema Linux , la cosa più vicina di Windows deve fork()
è in realtà fork()
:-)
fork
in un'applicazione media non WSL?
Il seguente documento fornisce alcune informazioni sul porting del codice da UNIX a Win32: https://msdn.microsoft.com/en-us/library/y23kc048.aspx
Tra le altre cose, indica che il modello di processo è abbastanza diverso tra i due sistemi e consiglia di considerare CreateProcess e CreateThread dove è richiesto un comportamento simile a fork ().
"non appena vuoi accedere ai file o printf, io vengono rifiutati"
Non puoi avere la tua torta e mangiarla anche tu ... in msvcrt.dll, printf () si basa sull'API della console, che di per sé utilizza lpc per comunicare con il sottosistema della console (csrss.exe). La connessione con csrss viene avviata all'avvio del processo, il che significa che qualsiasi processo che inizia la sua esecuzione "nel mezzo" avrà quel passaggio saltato. A meno che tu non abbia accesso al codice sorgente del sistema operativo, non ha senso provare a connetterti manualmente a csrss. Invece, dovresti creare il tuo sottosistema e di conseguenza evitare le funzioni della console nelle applicazioni che usano fork ().
una volta che hai implementato il tuo sottosistema, non dimenticare di duplicare anche tutti gli handle del genitore per il processo figlio ;-)
"Inoltre, probabilmente non dovresti usare le funzioni Zw * a meno che tu non sia in modalità kernel, dovresti probabilmente usare invece le funzioni Nt *."
ZwGetContextThread (NtCurrentThread (), & context);
La semantica fork () è necessaria laddove il bambino ha bisogno di accedere allo stato di memoria effettivo del genitore nel momento in cui viene chiamato fork (). Ho un software che si basa sul mutex implicito della copia della memoria dal momento in cui viene chiamato fork () istantaneo, il che rende i thread impossibili da usare. (Questo è emulato sulle moderne piattaforme * nix tramite la semantica copy-on-write / update-memory-table.)
Il più vicino che esiste su Windows come syscall è CreateProcess. La cosa migliore che si può fare è che il genitore congeli tutti gli altri thread durante il tempo in cui sta copiando la memoria nello spazio di memoria del nuovo processo, quindi li scongeli. Né la classe Cygwin frok [sic] né il codice Scilab che Eric des Courtis ha pubblicato fa il congelamento dei thread, che posso vedere.
Inoltre, probabilmente non dovresti usare le funzioni Zw * a meno che tu non sia in modalità kernel, dovresti probabilmente usare invece le funzioni Nt *. C'è un ramo extra che controlla se sei in modalità kernel e, in caso contrario, esegue tutti i controlli dei limiti e la verifica dei parametri che Nt * fa sempre. Pertanto, è leggermente meno efficiente chiamarli dalla modalità utente.
Le tue migliori opzioni sono CreateProcess () o CreateThread () . Ulteriori informazioni sul porting sono disponibili qui .
Non esiste un modo semplice per emulare fork () su Windows.
Ti suggerisco di usare invece i thread.
fork
stata esattamente ciò che ha fatto CygWin. Ma, se hai mai letto su come l' hanno fatto, il tuo "no easy way" è un grave fraintendimento :-)
Il più vicino che dici ... fammi pensare ... questo deve essere fork () immagino :)
Per i dettagli, vedere Interix implementa fork ()?
Come altre risposte hanno menzionato, NT (il kernel alla base delle versioni moderne di Windows) ha un equivalente di Unix fork (). Non è questo il problema.
Il problema è che la clonazione dell'intero stato di un processo non è generalmente una cosa sensata da fare. Questo è vero nel mondo Unix come lo è in Windows, ma nel mondo Unix, fork () viene utilizzato tutto il tempo e le librerie sono progettate per gestirlo. Le librerie di Windows non lo sono.
Ad esempio, le DLL di sistema kernel32.dll e user32.dll mantengono una connessione privata al processo server Win32 csrss.exe. Dopo un fork, ci sono due processi all'estremità client di quella connessione, che causeranno problemi. Il processo figlio dovrebbe informare csrss.exe della sua esistenza e stabilire una nuova connessione, ma non esiste un'interfaccia per farlo, perché queste librerie non sono state progettate con fork () in mente.
Quindi hai due scelte. Uno è quello di vietare l'uso di kernel32 e user32 e di altre librerie che non sono progettate per essere forkate, comprese tutte le librerie che si collegano direttamente o indirettamente a kernel32 o user32, che sono praticamente tutte. Ciò significa che non puoi interagire affatto con il desktop di Windows e sei bloccato nel tuo mondo Unixy separato. Questo è l'approccio adottato dai vari sottosistemi Unix per NT.
L'altra opzione è ricorrere a una sorta di orribile hack per cercare di far funzionare librerie inconsapevoli con fork (). Questo è ciò che fa Cygwin. Crea un nuovo processo, lo lascia inizializzare (inclusa la registrazione con csrss.exe), quindi copia la maggior parte dello stato dinamico dal vecchio processo e spera per il meglio. Mi stupisce che funzioni mai . Certamente non funziona in modo affidabile, anche se non si verifica un errore casuale a causa di un conflitto di spazio degli indirizzi, qualsiasi libreria che stai utilizzando potrebbe rimanere silenziosamente in uno stato rotto. L'affermazione dell'attuale risposta accettata che Cygwin ha un "fork () completo di tutte le funzionalità" è ... dubbia.
Riepilogo: in un ambiente simile a Interix, è possibile eseguire il fork chiamando fork (). Altrimenti, per favore cerca di liberarti dal desiderio di farlo. Anche se stai prendendo di mira Cygwin, non usare fork () a meno che non sia assolutamente necessario.
Se ti interessa solo creare un sottoprocesso e aspettarlo, forse le API _spawn * in process.h sono sufficienti. Ecco ulteriori informazioni a riguardo:
https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h