Respostas:
O Cygwin possui o fork () completo no Windows. Portanto, se o uso do Cygwin for aceitável para você, o problema será resolvido caso o desempenho não seja um problema.
Caso contrário, você pode dar uma olhada em como o Cygwin implementa o fork (). Do documento de arquitetura de Cygwin bastante antigo :
5.6 Criação de processos A chamada de bifurcação no Cygwin é particularmente interessante porque não é bem mapeada no topo da API do Win32. Isso dificulta a implementação correta. Atualmente, o fork do Cygwin é uma implementação sem cópia na gravação, semelhante à que estava presente nos primeiros sabores do UNIX.
A primeira coisa que acontece quando um processo pai bifurca um processo filho é que o pai inicializa um espaço na tabela de processos Cygwin para o filho. Em seguida, ele cria um processo filho suspenso usando a chamada Win32 CreateProcess. Em seguida, o processo pai chama setjmp para salvar seu próprio contexto e define um ponteiro para isso em uma área de memória compartilhada do Cygwin (compartilhada entre todas as tarefas do Cygwin). Em seguida, preenche as seções .data e .bss da criança, copiando de seu próprio espaço de endereço para o espaço de endereço da criança suspensa. Depois que o espaço de endereço do filho é inicializado, o filho é executado enquanto o pai aguarda um mutex. A criança descobre que foi bifurcada e saltos longos usando o buffer de salto salvo. A criança define o mutex em que o pai está esperando e bloqueia outro mutex. Esse é o sinal para o pai copiar sua pilha e acumular no filho, após o qual libera o mutex em que o filho está esperando e retorna da chamada de bifurcação. Por fim, a criança acorda do bloqueio no último mutex, recria todas as áreas mapeadas na memória passadas para ela através da área compartilhada e retorna do próprio fork.
Embora tenhamos algumas idéias sobre como acelerar a implementação do fork, reduzindo o número de alternâncias de contexto entre o processo pai e filho, o fork quase sempre será sempre ineficiente no Win32. Felizmente, na maioria das circunstâncias, a família de chamadas geradas por Cygwin pode substituir um par de garfo / executivo com apenas um pouco de esforço. Essas chamadas são mapeadas corretamente na API do Win32. Como resultado, eles são muito mais eficientes. Alterar o programa de driver do compilador para chamar spawn em vez de fork foi uma mudança trivial e aumentou as velocidades de compilação em vinte a trinta por cento em nossos testes.
No entanto, spawn e exec apresentam seu próprio conjunto de dificuldades. Como não há como executar um exec real no Win32, o Cygwin precisa inventar seus próprios IDs de processo (PIDs). Como resultado, quando um processo executa várias chamadas executivas, haverá vários PIDs do Windows associados a um único PID Cygwin. Em alguns casos, os stubs de cada um desses processos do Win32 podem demorar, aguardando a saída do processo Cygwin executivo.
Parece muito trabalho, não é? E sim, é slooooow.
EDIT: o documento está desatualizado, consulte esta excelente resposta para uma atualização
fork
mas consegue isso com uma solução com vazamento e você deve estar preparado para situações inesperadas.
Certamente não conheço os detalhes sobre isso porque nunca fiz isso, mas a API nativa do NT tem uma capacidade de bifurcar um processo (o subsistema POSIX no Windows precisa dessa capacidade - não tenho certeza se o subsistema POSIX ainda é suportado).
Uma pesquisa por ZwCreateProcess () deve fornecer mais detalhes - por exemplo, esta informação de Maxim Shatskih :
O parâmetro mais importante aqui é SectionHandle. Se este parâmetro for NULL, o kernel irá bifurcar o processo atual. Caso contrário, esse parâmetro deverá ser um identificador do objeto de seção SEC_IMAGE criado no arquivo EXE antes de chamar ZwCreateProcess ().
Porém, observe que Corinna Vinschen indica que Cygwin encontrou usando ZwCreateProcess () ainda não confiável :
Iker Arizmendi escreveu:
> 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
Este documento é bastante antigo, 10 anos ou mais. Enquanto ainda estamos usando chamadas Win32 para emular fork, o método mudou notavelmente. Especialmente, não criamos mais o processo filho no estado suspenso, a menos que estruturas de dados específicas precisem de um tratamento especial no pai antes de serem copiadas para o filho. Na versão 1.5.25 atual, o único caso de um filho suspenso são os soquetes abertos no pai. A próxima versão 1.7.0 não será suspensa.
Um motivo para não usar o ZwCreateProcess foi que, até a versão 1.5.25, ainda estamos suportando usuários do Windows 9x. No entanto, duas tentativas de usar o ZwCreateProcess em sistemas baseados em NT falharam por um motivo ou outro.
Seria muito bom se esse material fosse melhor ou de todo documentado, especialmente algumas estruturas de dados e como conectar um processo a um subsistema. Embora o fork não seja um conceito do Win32, não acho que seria ruim facilitar a implementação do fork.
fork
com imediato exec
", talvez o CreateProcess seja um candidato. Mas o fork
sem exec
é muitas vezes desejável e é isso que leva as pessoas a pedir um real fork
.
Bem, o Windows realmente não tem nada parecido. Especialmente porque o fork pode ser usado para criar conceitualmente um thread ou um processo no * nix.
Então, eu teria que dizer:
CreateProcess()
/CreateProcessEx()
e
CreateThread()
(Ouvi dizer que, para aplicativos C, _beginthreadex()
é melhor).
As pessoas tentaram implementar o fork no Windows. Esta é a coisa mais próxima que posso encontrar:
Retirado de: 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
falha ocorrer , o programa falhar ou o thread simplesmente falhar? Se travar o programa, isso não é realmente uma bifurcação. Apenas curioso, porque estou procurando uma solução real e espero que essa seja uma alternativa decente.
Antes da Microsoft apresentar sua nova opção "Subsistema Linux para Windows", CreateProcess()
era a coisa mais próxima do Windows fork()
, mas o Windows exige que você especifique um executável para executar nesse processo.
A criação do processo UNIX é bem diferente do Windows. Sua fork()
chamada basicamente duplica quase todo o processo atual, cada um em seu próprio espaço de endereço, e continua executando-os separadamente. Embora os processos em si sejam diferentes, eles ainda estão executando o mesmo programa. Veja aqui uma boa visão geral do fork/exec
modelo.
Voltando ao contrário, o equivalente do Windows CreateProcess()
é o fork()/exec()
par de funções no UNIX.
Se você estava portando software para o Windows e não se importa com uma camada de tradução, o Cygwin forneceu a capacidade que você deseja, mas foi bastante desagradável.
Obviamente, com o novo subsistema Linux , a coisa mais próxima do Windows fork()
é na verdade fork()
:-)
fork
em um aplicativo médio que não seja da WSL?
O documento a seguir fornece algumas informações sobre como transportar código do UNIX para o Win32: https://msdn.microsoft.com/en-us/library/y23kc048.aspx
Entre outras coisas, indica que o modelo de processo é bastante diferente entre os dois sistemas e recomenda a consideração de CreateProcess e CreateThread onde o comportamento do tipo fork () é necessário.
"assim que você desejar acessar ou imprimir arquivos, io será recusado"
Você não pode comer o seu bolo e comê-lo também ... no msvcrt.dll, printf () é baseado na API do console, que por si só usa lpc para se comunicar com o subsistema do console (csrss.exe). A conexão com o csrss é iniciada na inicialização do processo, o que significa que qualquer processo que inicie sua execução "no meio" terá essa etapa ignorada. A menos que você tenha acesso ao código fonte do sistema operacional, não faz sentido tentar se conectar ao csrss manualmente. Em vez disso, você deve criar seu próprio subsistema e, consequentemente, evitar as funções do console em aplicativos que usam fork ().
Depois de implementar seu próprio subsistema, não se esqueça de duplicar todos os identificadores dos pais para o processo filho ;-)
"Além disso, você provavelmente não deve usar as funções Zw *, a menos que esteja no modo kernel, provavelmente deve usar as funções Nt *."
ZwGetContextThread (NtCurrentThread (), e contexto);
a semântica fork () é necessária onde o filho precisa acessar o estado de memória real do pai, a partir do momento em que o fork () é chamado. Eu tenho um software que se baseia no mutex implícito da cópia de memória a partir do momento em que o fork instantâneo () é chamado, o que torna os threads impossíveis de usar. (Isso é emulado nas modernas plataformas * nix por meio da semântica de copiar na gravação / atualizar a memória da tabela.)
O mais próximo que existe no Windows como syscall é CreateProcess. O melhor que pode ser feito é que o pai congele todos os outros encadeamentos durante o tempo em que estiver copiando a memória para o espaço de memória do novo processo e os descongele. Nem a classe Cygwin frok [sic] nem o código Scilab publicado por Eric des Courtis fazem o congelamento de threads, que eu posso ver.
Além disso, você provavelmente não deve usar as funções Zw *, a menos que esteja no modo kernel, provavelmente deve usar as funções Nt *. Há uma ramificação extra que verifica se você está no modo kernel e, se não, executa todas as verificações de limites e verificação de parâmetros que o Nt * sempre faz. Portanto, é um pouco menos eficiente chamá-los do modo de usuário.
Suas melhores opções são CreateProcess () ou CreateThread () . Há mais informações sobre como portar aqui .
Não há uma maneira fácil de emular o fork () no Windows.
Eu sugiro que você use tópicos em seu lugar.
fork
foi exatamente o que CygWin fez. Mas, se você sempre ler sobre como eles fizeram isso, yor "nenhuma maneira fácil" é um misunderstatement bruta :-)
O mais próximo que você diz ... Deixe-me pensar ... Isso deve ser um garfo () eu acho :)
Para detalhes, consulte O Interix implementa fork ()?
Como outras respostas mencionaram, o NT (o kernel subjacente às versões modernas do Windows) possui um equivalente ao fork do Unix (). Esse não é o problema.
O problema é que a clonagem de todo o estado de um processo geralmente não é uma coisa sensata a ser feita. Isso é tão verdadeiro no mundo Unix quanto no Windows, mas no mundo Unix, fork () é usado o tempo todo e as bibliotecas são projetadas para lidar com isso. As bibliotecas do Windows não são.
Por exemplo, as DLLs do sistema kernel32.dll e user32.dll mantêm uma conexão privada com o processo do servidor Win32 csrss.exe. Após uma bifurcação, há dois processos no lado cliente dessa conexão, que causam problemas. O processo filho deve informar o csrss.exe de sua existência e fazer uma nova conexão - mas não há interface para fazer isso, porque essas bibliotecas não foram projetadas com o fork () em mente.
Então você tem duas opções. Uma é proibir o uso do kernel32 e user32 e outras bibliotecas que não foram projetadas para serem bifurcadas - incluindo quaisquer bibliotecas que se vinculam direta ou indiretamente ao kernel32 ou user32, que é praticamente todas elas. Isso significa que você não pode interagir com a área de trabalho do Windows e está preso no seu próprio mundo Unixy. Essa é a abordagem adotada pelos vários subsistemas Unix para NT.
A outra opção é recorrer a algum tipo de truque horrível para tentar fazer com que as bibliotecas inconscientes funcionem com o fork (). Isso é o que Cygwin faz. Ele cria um novo processo, permite inicializar (incluindo o registro com o csrss.exe), depois copia a maior parte do estado dinâmico do processo antigo e espera o melhor. Surpreende-me que isso sempre funcione. Certamente não funciona de maneira confiável - mesmo que não falhe aleatoriamente devido a um conflito no espaço de endereço, qualquer biblioteca que você está usando pode ficar silenciosamente em um estado interrompido. A alegação da resposta atualmente aceita de que Cygwin tem um "fork completo ()" é ... duvidosa.
Resumo: Em um ambiente semelhante ao Interix, você pode bifurcar-se chamando fork (). Caso contrário, tente se afastar do desejo de fazê-lo. Mesmo se você estiver segmentando o Cygwin, não use fork (), a menos que seja absolutamente necessário.
Se você se preocupa apenas em criar um subprocesso e aguardá-lo, talvez as APIs _spawn * em process.h sejam suficientes. Aqui estão mais informações sobre isso:
https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h