windows对fork()最接近的是什么?

rlb*_*ond 116 c c++ windows fork

我猜这个问题就是这么说的.

我想在Windows上分叉.什么是最相似的操作,我该如何使用它.

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.

编辑:该文档已过时,请参阅此优秀答案以获取更新

  • 如果你想在Windows上编写一个Cygwin应用程序,这是一个很好的答案.但总的来说,这不是最好的事情.从根本上说,*nix和Windows进程和线程模型是完全不同的.CreateProcess()和CreateThread()通常是等效的API (11认同)
  • 开发人员应该记住,这是一个不受支持的机制,而IIRC确实倾向于在系统上的某个其他进程使用代码注入时中断. (2认同)

Mic*_*urr 60

我当然不知道有关这方面的细节因为我从来没有这样做过,但是本机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
Run Code Online (Sandbox Code Playgroud)

这份文件相当陈旧,大约10年左右.虽然我们仍在使用Win32调用来模拟fork,但该方法已经发生了显着变化.特别是,我们不再创建处于暂停状态的子进程,除非特定数据结构在复制到子进程之前需要在父进程中进行特殊处理.在当前的1.5.25版本中,暂停子节点的唯一情况是父节点中的开放套接字.即将推出的1.7.0版本将不会暂停.

不使用ZwCreateProcess的一个原因是,直到1.5.25版本,我们仍然支持Windows 9x用户.但是,在基于NT的系统上使用ZwCreateProcess的两次尝试失败的原因之一.

如果这些东西更好或者完全记录下来会非常好,尤其是几个数据结构以及如何将进程连接到子系统.虽然fork不是Win32概念,但我认为使fork更容易实现并不是一件坏事.

  • @Foredecker - 这可能是一个错误的答案,但CreateProcess()/ CreateThread()也可能是错误的.这取决于是否正在寻找'Win32方式做事'或'尽可能接近fork()语义'.CreateProcess()的行为与fork()的行为明显不同,这也是cygwin需要做大量工作来支持它的原因. (13认同)
  • 如果人们希望"`fork`立即执行`exec`",那么也许CreateProcess是候选者.但是`fork`没有`exec`通常是可取的,这就是驱动人们要求真正的`fork`的原因. (4认同)
  • Interix在Windows Vista Enterprise/Ultimate中作为"UNIX应用程序的子系统"提供:http://en.wikipedia.org/wiki/Interix (2认同)

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)

  • 请注意,大多数错误检查都会丢失 - 例如,ZwCreateThread返回一个NTSTATUS值,可以使用SUCCEEDED和FAILED宏来检查. (4认同)

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


Bra*_*lor 6

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

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


pax*_*blo 6

在Microsoft推出新的"Linux子系统for Windows"选项之前,Windows CreateProcess()是最接近的事情fork(),但Windows要求您指定在该进程中运行的可执行文件.

UNIX进程创建与Windows完全不同.它的fork()调用基本上总是复制当前进程,每个进程都在自己的地址空间中,并继续单独运行它们.虽然流程本身不同,但它们仍在运行相同的程序.请参阅此处以获得该fork/exec模型的良好概述.

回到另一个方面,相当于Windows CreateProcess()是UNIX中的fork()/exec() 对函数.

如果您将软件移植到Windows并且不介意翻译层,那么Cygwin提供了您想要的功能,但它相当笨拙.

当然,的Linux子系统,是最接近Windows有到fork()实际 fork() :-)

  • 那么,鉴于 WSL,我可以在普通的非 WSL 应用程序中使用 `fork` 吗? (2认同)