Answers:
Cygwin在Windows上具有全功能的fork()。因此,如果您可以接受使用Cygwin,那么在性能不成问题的情况下解决了该问题。
否则,您可以看看Cygwin如何实现fork()。从一个相当老的Cygwin的体系结构文档中可以得出:
5.6。流程创建Cygwin中的fork调用特别有趣,因为它无法在Win32 API上很好地映射。这使得正确实施非常困难。当前,Cygwin分支是一种非写时复制实现,类似于UNIX早期版本中提供的实现。
父进程派生子进程时发生的第一件事是,父进程为子进程初始化了Cygwin进程表中的空间。然后,它使用Win32 CreateProcess调用创建一个暂停的子进程。接下来,父进程调用setjmp保存其自己的上下文,并在Cygwin共享内存区域(在所有Cygwin任务之间共享)中设置指向此上下文的指针。然后,通过从其自己的地址空间复制到暂挂的子代的地址空间中,来填充子代的.data和.bss节。初始化子级的地址空间后,在父级等待互斥时运行子级。孩子发现使用已保存的跳转缓冲区已将其分叉并进行了长跳。然后,孩子设置父母正在等待的互斥锁,并阻止另一个互斥锁。这是父级将其堆栈和堆复制到子级中的信号,此后它释放子级正在等待的互斥量并从fork调用中返回。最后,子进程从最后一个互斥锁的阻塞中醒来,重新创建通过共享区传递给它的所有内存映射区域,然后从fork本身返回。
尽管我们对如何通过减少父进程和子进程之间的上下文切换次数来加快fork的实现有一些想法,但是在Win32下,fork几乎总是效率低下。幸运的是,在大多数情况下,由Cygwin提供的派生调用系列可以轻松完成,以替代fork / exec对。这些调用完全在Win32 API上映射。结果,它们效率更高。在我们的测试中,将编译器的驱动程序更改为调用spawn而不是fork是一个微不足道的更改,并且将编译速度提高了20%到30%。
但是,spawn和exec会遇到一系列困难。由于无法在Win32下执行实际的exec,因此Cygwin必须发明自己的进程ID(PID)。结果,当一个进程执行多个exec调用时,将有多个与单个Cygwin PID关联的Windows PID。在某些情况下,每个Win32进程的存根可能会徘徊,等待其执行的Cygwin进程退出。
听起来需要做很多工作,不是吗?是的,它是slooooow。
编辑:该文档已过时,请查看此出色的答案以进行更新
fork
但是它通过泄漏的解决方案来实现这一点,因此您必须为意外情况做好准备。
我当然不知道细节,因为我从未做过,但是本机NT API具有分叉进程的功能(Windows上的POSIX子系统需要此功能-我不确定POSIX子系统是否甚至不再受支持)。
搜索ZwCreateProcess()应该会为您提供更多详细信息-例如,Maxim Shatskih提供的以下信息:
这里最重要的参数是SectionHandle。如果此参数为NULL,则内核将派生当前进程。否则,此参数必须是在调用ZwCreateProcess()之前在EXE文件上创建的SEC_IMAGE节对象的句柄。
尽管请注意Corinna Vinschen指出Cygwin使用ZwCreateProcess()发现仍然不可靠:
艾克·阿里兹门迪(Iker Arizmendi)写道:
> 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
这份文件比较旧,已有10年左右的历史了。虽然我们仍在使用Win32调用来模拟fork,但是方法已发生了显着变化。特别是,我们不再在挂起状态下创建子进程,除非特定的数据结构在复制到子级之前需要在父级中进行特殊处理。在当前的1.5.25版本中,挂起的子级的唯一情况是父级中的开放套接字。即将发布的1.7.0版本将不会暂停。
不使用ZwCreateProcess的原因之一是,直到1.5.25版本,我们仍然支持Windows 9x用户。但是,由于某种原因或其他原因,两次在基于NT的系统上使用ZwCreateProcess的尝试均失败了。
如果这些东西会更好或者全部记录下来,那将是非常好的,尤其是几个数据结构以及如何将流程连接到子系统。尽管fork不是Win32概念,但我认为使fork易于实现并不是一件坏事。
fork
立即exec
”,那么也许CreateProcess是一个候选者。但是常常fork
没有exec
希望,这是促使人们要求真实的东西fork
。
好吧,窗户真的没有任何类似的东西。尤其是因为fork可以在概念上在* nix中创建线程或进程。
所以,我不得不说:
CreateProcess()
/CreateProcessEx()
和
CreateThread()
(我听说对于C应用程序_beginthreadex()
来说更好)。
人们试图在Windows上实现fork。这是我能找到的最接近的东西:
摘自: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
崩溃,该程序崩溃还是该线程刚刚崩溃怎么办?如果它使程序崩溃,那么这并不是真正的分叉。只是好奇,因为我正在寻找一个真正的解决方案,希望这可能是一个不错的选择。
在Microsoft引入其新的“ Windows的Linux子系统”选项之前,Windows CreateProcess()
是最接近的fork()
,但是Windows要求您指定一个可执行文件才能在该进程中运行。
UNIX进程的创建与Windows完全不同。它的fork()
调用基本上将当前过程几乎全部复制,每个过程都在各自的地址空间中进行,然后继续单独运行它们。尽管进程本身不同,但它们仍在运行同一程序。有关此fork/exec
模型的详细概述,请参见此处。
换一种说法,Windows相当于UNIX中CreateProcess()
的fork()/exec()
一对函数。
如果您要将软件移植到Windows而又不介意翻译层,Cygwin提供了您想要的功能,但是这很麻烦。
当然,与新的Linux子系统,Windows有到最近的事情fork()
是实际 fork()
:-)
fork
在普通的非WSL应用程序中使用吗?
以下文档提供了有关将代码从UNIX移植到Win32的一些信息:https : //msdn.microsoft.com/zh-cn/library/y23kc048.aspx
其中,这表明两个系统之间的过程模型完全不同,并建议考虑需要类似fork()行为的CreateProcess和CreateThread。
“只要您想进行文件访问或printf,那么io就会被拒绝”
您不能再吃蛋糕了……在msvcrt.dll中,printf()基于控制台API,它本身使用lpc与控制台子系统(csrss.exe)进行通信。与csrss的连接在进程启动时启动,这意味着“在中间”开始执行的任何进程都将跳过该步骤。除非您有权访问操作系统的源代码,否则尝试手动连接到csrss是没有意义的。相反,您应该创建自己的子系统,并因此避免使用fork()的应用程序中的控制台功能。
一旦实现了自己的子系统,请不要忘记为子进程也复制父级的所有句柄;-)
“此外,除非处于内核模式,否则可能不应该使用Zw *函数,而应该使用Nt *函数。”
ZwGetContextThread(NtCurrentThread(),&context);
当子级需要调用即时fork()时需要访问父级的实际内存状态时,fork()语义是必需的。我有一个软件依赖于调用fork()时内存复制的隐式互斥,这使线程无法使用。(这是在现代* nix平台上通过写时复制/更新内存表语义来模拟的。)
在Windows上,最接近系统调用的是CreateProcess。最好的办法是让父进程在将内存复制到新进程的内存空间时冻结所有其他线程,然后解冻它们。我可以看到,Cygwin frok [sic]类和Eric des Courtis发布的Scilab代码都没有进行线程冻结。
另外,除非您处于内核模式,否则可能不应该使用Zw *函数,而应该使用Nt *函数。还有一个额外的分支,用于检查您是否处于内核模式,如果不是,则执行Nt *始终执行的所有边界检查和参数验证。因此,从用户模式调用它们的效率略低。
最好的选择是CreateProcess()或CreateThread()。在此处有更多有关移植的信息。
您说的最接近的...让我想想...这一定是fork()我猜:)
有关详细信息,请参见Interix是否实现fork()?
正如其他答案所提到的,NT(Windows的现代基础内核)与Unix fork()等效。那不是问题。
问题在于,克隆进程的整个状态通常不是一件明智的事情。在Unix世界中,这与Windows中一样,但是在Unix世界中,一直使用fork(),并且设计了库来处理它。Windows库不是。
例如,系统DLLs kernel32.dll和user32.dll维护与Win32服务器进程csrss.exe的专用连接。分叉之后,该连接的客户端上有两个进程,这将引起问题。子进程应将其存在通知csrss.exe并建立新连接-但是没有接口可以这样做,因为这些库的设计没有考虑到fork()。
因此,您有两种选择。一种是禁止使用kernel32和user32以及其他并非旨在分叉的库–包括直接或间接链接到kernel32或user32的所有库,实际上它们都是库。这意味着您根本无法与Windows桌面进行交互,而被困在自己的单独Unixy世界中。这是各种Unix NT子系统所采用的方法。
另一种选择是求助于某种可怕的黑客,以尝试使不知道的库与fork()一起使用。那就是Cygwin所做的。它创建一个新进程,使其进行初始化(包括使用csrss.exe注册自身),然后从旧进程中复制大部分动态状态,并希望达到最佳状态。令我惊讶的是,它曾经奏效。它当然不能可靠地工作-即使它不会由于地址空间冲突而随机失败,您正在使用的任何库也可能会默默地处于损坏状态。目前公认的答案称Cygwin具有“功能齐全的fork()”是令人怀疑的。
简介:在类似Interix的环境中,可以通过调用fork()进行fork。否则,请尝试使自己摆脱这样做的欲望。即使您的目标是Cygwin,除非绝对必要,也不要使用fork()。
如果您只关心创建子进程并等待它,那么_spawn * API在process.h中就足够了。有关此的更多信息:
https://docs.microsoft.com/zh-cn/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h