什么是最接近的东西窗户fork()?

我想这个问题说明了一切。

我想在窗户上分叉。 什么是最相似的操作,我如何使用它。

Cygwin在Windows上具有全function的fork()。 因此,如果使用Cygwin对您来说是可以接受的,那么在性能不是问题的情况下就可以解决问题。

否则,你可以看看Cygwin如何实现fork()。 从一个相当古老的Cygwin的build筑文档 :

5.6。 进程创buildCygwin中的fork调用特别有趣,因为它不能很好地映射到Win32 API之上。 这使得正确实施非常困难。 目前,Cygwin的fork是一个非copy-on-write的实现,类似于早期版本的UNIX。

父进程分叉subprocess时发生的第一件事是父进程为subprocess初始化Cygwin进程表中的空间。 然后使用Win32 CreateProcess调用创build一个暂停的subprocess。 接下来,父进程调用setjmp来保存自己的上下文,并在Cygwin共享内存区域(在所有Cygwin任务中共享)中设置一个指针。 然后通过将自己的地址空间复制到挂起的孩子的地址空间来填充孩子的.data和.bss段。 在孩子的地址空间被初始化之后,孩子在父母等待互斥体时运行。 孩子发现它已经分叉,并使用保存的跳转缓冲区跳转。 然后,孩子设置父母正在等待的互斥体,并阻止另一个互斥体。 这是父级将堆栈和堆复制到子级的信号,之后释放子级正在等待的并从叉子调用返回的互斥量。 最后,子从最后一个互斥锁中唤醒,重新创build通过共享区域传递给它的任何内存映射区域,并从fork本身返回。

虽然我们对如何通过减less父进程和subprocess之间的上下文切换次数来加速我们的fork实现有一些想法,但在Win32下,fork几乎肯定总是低效的。 幸运的是,在大多数情况下,Cygwin提供的spawn系列调用可以用一点努力代替fork / exec对。 这些调用映射干净的Win32 API之上。 因此,他们更有效率。 改变编译器的驱动程序来调用spawn而不是fork是一个微不足道的变化,在我们的testing中编译速度提高了20%到30%。

然而,产卵和执行会出现他们自己的困难。 因为Win32下无法执行实际的exec,所以Cygwin必须创build自己的进程ID(PID)。 因此,当进程执行多个exec调用时,将会有多个Windows PID与一个Cygwin PID相关联。 在某些情况下,这些Win32进程中的每一个进程的存根可能会持续,等待其执行的Cygwin进程退出。

听起来很多工作,不是吗? 是的,这是slooooow。

编辑:文档已过时,请参阅此优秀答案的更新

我当然不知道这个细节,因为我从来没有这样做过,但是原生的NT API有能力分叉一个进程(Windows上的POSIX子系统需要这个function – 我不确定POSIX子系统甚至被支持)。

searchZwCreateProcess()应该会得到更多的细节,例如Maxim Shatskih提供的这些信息 :

这里最重要的参数是SectionHandle。 如果此参数为NULL,则内核将分叉当前进程。 否则,在调用ZwCreateProcess()之前,此参数必须是在EXE文件上创build的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,但方法已经明显改变。 尤其是,我们不再创build处于挂起状态的subprocess,除非在父进程复制到subprocess之前,特定的数据结构需要在父进程中进行特殊处理。 在目前的1.5.25版本中,暂停的孩子的唯一情况是父母的开放式套接字。 即将到来的1.7.0版本将不会暂停。

不使用ZwCreateProcess的一个原因是,在1.5.25版本之前,我们仍然支持Windows 9x用户。 但是,在基于NT的系统上使用ZwCreateProcess的两次尝试都因为某种原因失败了。

如果这些东西会更好或者完全logging下来,尤其是一些数据结构以及如何将一个进程连接到一个子系统,那将是非常好的。 虽然fork不是Win32的概念,但我没有看到让fork更容易实现会是一件坏事。

那么,窗户并没有什么特别的。 特别是因为fork可以用来在概念上创build一个线程或一个进程在* nix中。

所以,我不得不说:

CreateProcess() / CreateProcessEx()

CreateThread() (我听说,对于C应用程序, _beginthreadex()更好)。

人们试图在Windows上实现fork。 这是我能find的最接近的东西:

取自: 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; } 

以下文档提供了有关将代码从UNIX移植到Win32的一些信息: http : //msdn.microsoft.com/en-us/library/y23kc048(vs.71).aspx

除此之外,它表明两个系统之间的过程模型是完全不同的,并build议考虑CreateProcess和CreateThread,其中fork()类似的行为是必需的。

CreateProcess()是Windows最接近的fork()但是Windows需要你指定一个可执行文件来运行这个过程。

UNIX进程创build完全不同。 它基本上复制了当前的进程,几乎总是在各自的地址空间中,并分别开始运行它们。

CreateProcess()的实际等价物是UNIX的fork()/exec()对。 exec()没有必要发生,因为进程本身可能有不同的父代和子代的代码path。

如果你要将软件移植到Windows上,并且不介意翻译层,Cygwin可能会提供你想要的function(我从来没有需要在Cygwin下testingfork() ,所以我不能肯定地说) 。

你最好的select是CreateProcess()或CreateThread() 。 这里有更多关于移植的信息 。

有没有简单的方法来模拟Windows上的fork()。

我build议你使用线程来代替。

最接近你说…让我想…这一定是叉()我猜:)

有关详细信息,请参阅Interix是否实现fork()?

“只要你想做文件访问或printf然后io被拒绝”

  • 在msvcrt.dll中,printf()基于Console API,它本身使用lpc与控制台子系统(csrss.exe)进行通信。 与csrss的连接在进程启动时启动,这意味着任何开始执行“中间”的进程都会跳过该步骤。 除非您有权访问操作系统的源代码,否则尝试手动连接到csrs没有任何意义。 相反,您应该创build自己的子系统,并相应地避免使用fork()的应用程序中的控制台function。

  • 一旦你已经实现了你自己的子系统,不要忘了也复制父进程的所有subprocess;-)

另外,除非你处于内核模式,否则你可能不应该使用Zw *函数,而应该使用Nt *函数。“

  • 这是不正确的。 在用户模式下访问时,Zw *** Nt ***绝对没有区别; 这些只是两个不同的(ntdll.dll)导出名称,指向相同(相对)的虚拟地址。

ZwGetContextThread(NtCurrentThread(),&context);

  • 通过调用ZwGetContextThread获得当前(运行)线程的上下文是错误的,很可能会崩溃,并且(由于额外的系统调用)也不是完成任务的最快方式。

在fork()被调用的时候,fork()语义是需要访问父对象的实际内存状态的。 我有一个软件依赖于即时fork()被调用时的内存复制的隐式互斥,这使得线程无法使用。 (这是通过写时复制/更新内存表语义在现代* nix平台上模拟的。)

作为系统调用,在Windows上最接近的是CreateProcess。 可以做的最好的事情是让父母在将内存复制到新进程的内存空间期间冻结所有其他线程,然后解冻它们。 Cygwin frok [sic]类和Eric des Courtis发布的Scilab代码都不会冻结线程,我可以看到。

另外,除非你处于内核模式,否则你可能不应该使用Zw *函数,而应该使用Nt *函数。 有一个额外的分支检查你是否处于内核模式,如果不是,执行Nt *总是做的所有边界检查和参数validation。 因此,从用户模式调用它们的效率会稍低一些。

如果你只关心创build一个subprocess并等待它,那么process.h中的_spawn * API就足够了。 这里有更多关于这方面的信息:

https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h