Lau*_*nis 80
Cygwin在Windows上具有全功能的fork()功能.因此,如果使用Cygwin是可以接受的,那么在性能不是问题的情况下问题就解决了.
否则你可以看看Cygwin如何实现fork().从一个相当古老的Cygwin的架构文档:
5.6.进程创建Cygwin中的fork调用特别有趣,因为它不能很好地映射到Win32 API之上.这使得很难正确实施.目前,Cygwin fork是一种非写时复制实现,类似于早期版本的UNIX.
父进程分叉子进程时发生的第一件事是父进程初始化子进程的Cygwin进程表中的空格.然后,它使用Win32 CreateProcess调用创建一个挂起的子进程.接下来,父进程调用setjmp来保存自己的上下文,并在Cygwin共享内存区域中设置指向此的指针(在所有Cygwin任务中共享).然后它通过从其自己的地址空间复制到暂停的子地址空间来填充子的.data和.bss部分.初始化子地址空间后,子进程在父进程等待互斥锁时运行.孩子发现它已被分叉并使用保存的跳转缓冲区进行长时间跳转.然后,子进程设置父进程等待的互斥锁,并阻塞另一个互斥进程.这是父进程将其堆栈和堆复制到子进程中的信号,之后它释放子进程等待的互斥锁并从fork调用返回.最后,孩子从上一个互斥锁上的阻塞中醒来,重新创建通过共享区域传递给它的任何内存映射区域,并从fork本身返回.
虽然我们有一些关于如何通过减少父进程和子进程之间的上下文切换次数来加速fork实现的想法,但在Win32下,fork几乎肯定总是效率低下.幸运的是,在大多数情况下,Cygwin提供的spawn系列调用只需要很少的努力即可替换fork/exec对.这些调用完全映射到Win32 API之上.结果,它们更有效率.更改编译器的驱动程序以调用spawn而不是fork是一个微不足道的变化,并且在我们的测试中将编译速度提高了20%到30%.
然而,spawn和exec提出了他们自己的一系列困难.因为无法在Win32下执行实际的exec,所以Cygwin必须创建自己的进程ID(PID).因此,当进程执行多个exec调用时,将有多个Windows PID与单个Cygwin PID关联.在某些情况下,每个Win32进程的存根可能会延续,等待其执行的Cygwin进程退出.
听起来很多工作,不是吗?是的,它是slooooow.
编辑:该文档已过时,请参阅此优秀答案以获取更新
Mic*_*urr 60
我当然不知道有关这方面的细节因为我从来没有这样做过,但是本机NT API有能力分叉一个进程(Windows上的POSIX子系统需要这个功能 - 我不确定POSIX子系统甚至支持了).
搜索ZwCreateProcess()应该可以获得更多细节 - 例如Maxim Shatskih提供的这些信息:
这里最重要的参数是SectionHandle.如果此参数为NULL,则内核将分叉当前进程.否则,在调用ZwCreateProcess()之前,此参数必须是在EXE文件上创建的SEC_IMAGE节对象的句柄.
虽然注意到Corinna Vinschen指出Cygwin发现使用ZwCreateProcess()仍然不可靠:
Iker Arizmendi写道:
Run Code Online (Sandbox Code Playgroud)> 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更容易实现并不是一件坏事.
Eva*_*ran 35
好吧,Windows并没有真正有类似的东西.特别是因为fork可用于在概念上创建*nix中的线程或进程.
所以,我不得不说:
CreateProcess()/CreateProcessEx()
和
CreateThread()(我听说对于C应用程序,_beginthreadex()更好).
Eri*_*tis 16
人们试图在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;
}
Run Code Online (Sandbox Code Playgroud)
ben*_*nrg 14
正如其他答案所提到的,NT(现代 Windows 版本的底层内核)具有相当于 Unix fork() 的功能。那不是问题。
\n问题在于,克隆进程的整个状态通常不是一件明智的事情。这在 Unix 世界和 Windows 中都是如此,但在 Unix 世界中,一直使用 fork(),并且库被设计来处理它。Windows 库则不然。
\n例如,系统 DLL kernel32.dll 和 user32.dll 维护与 Win32 服务器进程 csrss.exe 的专用连接。分叉后,该连接的客户端有两个进程,这会导致问题。子进程应该通知 csrss.exe 它的存在并建立一个新连接 \xe2\x80\x93 但没有接口可以做到这一点,因为这些库在设计时没有考虑 fork() 。
\n所以你有两个选择。一是禁止使用 kernel32 和 user32 以及其他未设计为 fork \xe2\x80\x93 的库,包括任何直接或间接链接到 kernel32 或 user32 的库,这实际上是所有库。这意味着您根本无法与 Windows 桌面交互,并且被困在您自己的独立 Unixy 世界中。这是 NT 的各种 Unix 子系统所采用的方法。
\n另一种选择是采取某种可怕的黑客手段来尝试让不知情的库使用 fork()。这就是 Cygwin 所做的。它创建一个新进程,让它初始化(包括向 csrss.exe 注册自身),然后从旧进程复制大部分动态状态,并希望得到最好的结果。令我惊讶的是,这曾经有效。它当然不能可靠地工作 \xe2\x80\x93 即使它不会由于地址空间冲突而随机失败,您正在使用的任何库都可能会默默地处于损坏状态。当前接受的答案声称 Cygwin 具有“功能齐全的 fork()”,这是......可疑的。
\n简介: 在类似Interix 的环境中,您可以通过调用fork() 来进行分叉。否则,请尝试戒掉这样做的欲望。即使您的目标是 Cygwin,也不要使用 fork(),除非绝对必要。
\n以下文档提供了有关将代码从UNIX移植到Win32的一些信息:https: //msdn.microsoft.com/en-us/library/y23kc048.aspx
除此之外,它表明两个系统之间的流程模型完全不同,并建议考虑CreateProcess和CreateThread,其中需要类似fork()的行为.
在Microsoft推出新的"Linux子系统for Windows"选项之前,Windows CreateProcess()是最接近的事情fork(),但Windows要求您指定在该进程中运行的可执行文件.
UNIX进程创建与Windows完全不同.它的fork()调用基本上总是复制当前进程,每个进程都在自己的地址空间中,并继续单独运行它们.虽然流程本身不同,但它们仍在运行相同的程序.请参阅此处以获得该fork/exec模型的良好概述.
回到另一个方面,相当于Windows CreateProcess()是UNIX中的fork()/exec() 一对函数.
如果您将软件移植到Windows并且不介意翻译层,那么Cygwin提供了您想要的功能,但它相当笨拙.
当然,与新的Linux子系统,是最接近Windows有到fork()是实际 fork() :-)
| 归档时间: |
|
| 查看次数: |
96123 次 |
| 最近记录: |