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

时间:2009-06-12 06:36:22

标签: c++ c windows fork

我想这个问题就是这么说的。

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

14 个答案:

答案 0 :(得分:78)

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

否则你可以看看Cygwin如何实现fork()。从一个相当古老的Cygwin的架构doc

  

5.6。流程创建   Cygwin中的fork调用特别有趣   因为它不能很好地映射到   Win32 API。这非常有用   难以正确实施。   目前,Cygwin fork是一个   非写时复制实现   类似于早期的现状   UNIX的风格。

     

当a时发生的第一件事   父进程分叉子进程   是父级初始化空格   在Cygwin进程表中   儿童。然后它创建一个暂停   使用Win32的子进程   CreateProcess调用。接下来,父母   进程调用setjmp来保存自己的   上下文并设置指向此的指针   一个Cygwin共享内存区域(共享   在所有Cygwin任务中)。它然后填补   在孩子的.data和.bss部分   通过从自己的地址空间复制   进入暂停的孩子的地址   空间。孩子的地址空间之后   初始化,孩子在运行时   父母等待互斥。孩子   发现它已经分叉了   longjumps使用保存的跳转缓冲区。   然后孩子设置互斥锁   父母正在等待并阻止   另一个互斥体。这是信号   父级复制其堆栈和堆   进入孩子,之后呢   释放孩子的互斥锁   等待并从叉子返回   呼叫。最后,孩子从中醒来   在最后一个互斥锁上阻塞,重新创建   传递给它的任何内存映射区域   通过共享区域,并从中返回   叉子本身。

     

虽然我们对如何做一些想法   加快我们的fork实现   减少上下文的数量   在父母和孩子之间切换   过程中,fork几乎可以肯定   在Win32下总是效率低下。   幸运的是,在大多数情况下   产生的一系列电话   Cygwin可以替代a   fork / exec配对只有一点点   努力。这些调用清晰地映射在顶部   Win32 API。结果,他们   效率更高。改变了   编译器的驱动程序来调用   spawn而不是fork是一件小事   改变和增加编译   速度提高20%到30%   我们的测试。

     

然而,spawn和exec呈现他们的   自己的一系列困难。因为那里   没办法做一个真正的高手   Win32,Cygwin必须发明自己的   进程ID(PID)。结果,什么时候   一个进程执行多个exec   电话,会有多个Windows   与单个Cygwin相关的PID   PID。在某些情况下,每个的存根   这些Win32进程可能会延续,   等待他们的执行官Cygwin   退出的过程。

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

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

答案 1 :(得分:57)

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

搜索ZwCreateProcess()可以获得更多详细信息 - 例如this bit of information from Maxim Shatskih

  

这里最重要的参数是SectionHandle。如果是这个参数   为NULL,内核将fork当前进程。否则,这个   参数必须是创建的SEC_IMAGE节对象的句柄   调用ZwCreateProcess()之前的EXE文件。

虽然注意Corinna Vinschen indicates that Cygwin found using ZwCreateProcess() still unreliable

  

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更容易实现会是一件坏事。

答案 2 :(得分:35)

嗯,Windows并没有真正的东西。特别是因为fork可用于在概念上创建* nix中的线程或进程。

所以,我不得不说:

CreateProcess() / CreateProcessEx()

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

答案 3 :(得分:15)

人们试图在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;
}

答案 4 :(得分:6)

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

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

答案 5 :(得分:6)

在Microsoft推出新的“Linux子系统for Windows”选项之前,CreateProcess()是Windows与fork()最接近的事情,但Windows要求您指定在该过程中运行的可执行文件。 / p>

UNIX进程创建与Windows完全不同。它的fork()调用基本上完全复制了当前进程,每个进程都在自己的地址空间中,并继续单独运行它们。虽然流程本身不同,但它们仍在运行相同的程序。有关fork/exec模型的详细概述,请参阅here

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

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

当然, 新的Linux subsystem,Windows与fork()最接近的是 fork() :-)

答案 6 :(得分:3)

fork()语义是必要的,其中当调用fork()时,子进程需要访问父实际的内存状态。我有一个软件,它依赖于内存复制的隐式互斥,因为调用了fork(),这使得线程无法使用。 (这是通过copy-on-write / update-memory-table语义在现代* nix平台上模拟的。)

Windows上作为系统调用存在的最接近的是CreateProcess。可以做的最好的事情是父进程在将内存复制到新进程的内存空间时冻结所有其他线程,然后解冻它们。无论是Cygwin frok [原文如此]类还是Eric des Courtis发布的Scilab代码都没有线程冻结,我可以看到。

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

答案 7 :(得分:3)

“只要您想要进行文件访问或printf,那么io就会被拒绝”

  • 你不能吃蛋糕而且吃得太多......在msvcrt.dll中,printf()基于Console API,它本身使用lpc与控制台子系统(csrss.exe)进行通信。在进程启动时启动与csrss的连接,这意味着任何“在中间”开始执行的进程将跳过该步骤。除非您可以访问操作系统的源代码,否则尝试手动连接到csrss是没有意义的。相反,您应该创建自己的子系统,并相应地避免使用fork()的应用程序中的控制台功能。

  • 一旦实现了自己的子系统,不要忘记复制子进程的所有父句柄; - )

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

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

ZwGetContextThread(NtCurrentThread(),& context);

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

答案 8 :(得分:3)

正如其他答案所提到的,NT(Windows现代版本的基础内核)与Unix fork()等效。那不是问题。

问题在于,克隆进程的整个状态通常不是一件明智的事情。在Unix世界和Windows中都是如此,但是在Unix世界中,一直使用fork(),并且设计了库来处理它。 Windows库不是。

例如,系统dll kernel32.dll和user32.dll维护与Win32服务器进程csrss.exe的专用连接。分叉之后,该连接的客户端上有两个进程,这将导致问题。子进程应该将其存在告知csrss.exe并建立新的连接-但是没有接口可以这样做,因为这些库在设计时并未考虑到fork()。

因此,您有两种选择。一种是禁止使用kernel32和user32以及其他并非旨在进行分叉的库,包括直接或间接链接到kernel32或user32的所有库,实际上它们都是库。这意味着您根本无法与Windows桌面进行交互,而被困在自己独立的Unixy世界中。这是各种Unix NT子系统所采用的方法。

另一种选择是求助于某种可怕的黑客手段,以使不知道的库可以与fork()一起使用。那就是Cygwin所做的。它创建一个新进程,使其进行初始化(包括使用csrss.exe注册自身),然后从旧进程中复制大部分动态状态,并希望达到最佳状态。这个 ever 的作品使我感到惊讶。它当然不能可靠地工作-即使它不会由于地址空间冲突而随机失败,您正在使用的任何库也可能会默默地处于损坏状态。目前公认的答案称Cygwin具有“功能齐全的fork()”是令人怀疑的。

摘要:在类似Interix的环境中,可以通过调用fork()进行fork。否则,请尝试使自己脱离这样做的欲望。即使您的目标是Cygwin,除非绝对必要,也不要使用fork()。

答案 9 :(得分:2)

您最好的选择是CreateProcess()CreateThread()。有关移植here的更多信息。

答案 10 :(得分:2)

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

我建议您改用线程。

答案 11 :(得分:2)

你说的最接近......让我想想......这一定是fork()我想:)

有关详细信息,请参阅Does Interix implement fork()?

答案 12 :(得分:1)

如果你只关心创建一个子进程并等待它,那么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

答案 13 :(得分:0)

大多数 hacky 解决方案已经过时。 Winnie the fuzzer 有一个 fork 版本,可以在当前版本的 Windows 10 上运行(但这需要系统特定的偏移量,并且也很容易损坏)。

https://github.com/sslab-gatech/winnie/tree/master/forklib