C++ Windows 最接近 fork() 的是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/985281/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 18:15:25  来源:igfitidea点击:

What is the closest thing Windows has to fork()?

c++cwindowsfork

提问by rlbond

I guess the question says it all.

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

I want to fork on Windows. What is the most similar operation and how do I use it.

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

采纳答案by Laurynas Biveinis

Cygwinhas fully featured fork() on Windows. Thus if using Cygwin is acceptable for you, then the problem is solved in the case performance is not an issue.

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

Otherwise you can take a look at how Cygwin implements fork(). From a quite old Cygwin's architecture doc:

否则你可以看看 Cygwin 如何实现 fork()。来自相当古老的 Cygwin 的架构文档

5.6. Process Creation The fork call in Cygwin is particularly interesting because it does not map well on top of the Win32 API. This makes it very difficult to implement correctly. Currently, the Cygwin fork is a non-copy-on-write implementation similar to what was present in early flavors of UNIX.

The first thing that happens when a parent process forks a child process is that the parent initializes a space in the Cygwin process table for the child. It then creates a suspended child process using the Win32 CreateProcess call. Next, the parent process calls setjmp to save its own context and sets a pointer to this in a Cygwin shared memory area (shared among all Cygwin tasks). It then fills in the child's .data and .bss sections by copying from its own address space into the suspended child's address space. After the child's address space is initialized, the child is run while the parent waits on a mutex. The child discovers it has been forked and longjumps using the saved jump buffer. The child then sets the mutex the parent is waiting on and blocks on another mutex. This is the signal for the parent to copy its stack and heap into the child, after which it releases the mutex the child is waiting on and returns from the fork call. Finally, the child wakes from blocking on the last mutex, recreates any memory-mapped areas passed to it via the shared area, and returns from fork itself.

While we have some ideas as to how to speed up our fork implementation by reducing the number of context switches between the parent and child process, fork will almost certainly always be inefficient under Win32. Fortunately, in most circumstances the spawn family of calls provided by Cygwin can be substituted for a fork/exec pair with only a little effort. These calls map cleanly on top of the Win32 API. As a result, they are much more efficient. Changing the compiler's driver program to call spawn instead of fork was a trivial change and increased compilation speeds by twenty to thirty percent in our tests.

However, spawn and exec present their own set of difficulties. Because there is no way to do an actual exec under Win32, Cygwin has to invent its own Process IDs (PIDs). As a result, when a process performs multiple exec calls, there will be multiple Windows PIDs associated with a single Cygwin PID. In some cases, stubs of each of these Win32 processes may linger, waiting for their exec'd Cygwin process to exit.

5.6. 进程创建 Cygwin 中的 fork 调用特别有趣,因为它不能很好地映射到 Win32 API 之上。这使得正确实施非常困难。目前,Cygwin fork 是一种非写时复制实现,类似于早期版本的 UNIX 中存在的实现。

当父进程派生子进程时发生的第一件事是父进程在 Cygwin 进程表中为子进程初始化一个空间。然后它使用 Win32 CreateProcess 调用创建一个挂起的子进程。接下来,父进程调用 setjmp 来保存自己的上下文,并在 Cygwin 共享内存区域(在所有 Cygwin 任务之间共享)中设置指向 this 的指针。然后它通过从它自己的地址空间复制到挂起的孩子的地址空间来填充孩子的 .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 进程退出。

Sounds like a lot of work, doesn't it? And yes, it is slooooow.

听起来工作量很大,不是吗?是的,它很慢。

EDIT: the doc is outdated, please see this excellent answerfor an update

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

回答by Michael Burr

I certainly don't know the details on this because I've never done it it, but the native NT API has a capability to fork a process (the POSIX subsystem on Windows needs this capability - I'm not sure if the POSIX subsystem is even supported anymore).

我当然不知道这方面的细节,因为我从来没有这样做过,但是原生 NT API 具有 fork 进程的能力(Windows 上的 POSIX 子系统需要这种能力 - 我不确定 POSIX 子系统是否甚至不再支持)。

A search for ZwCreateProcess() should get you some more details - for example this bit of information from Maxim Shatskih:

搜索 ZwCreateProcess() 应该会为您提供更多详细信息 - 例如来自 Maxim Shatskih 的以下信息

The most important parameter here is SectionHandle. If this parameter is NULL, the kernel will fork the current process. Otherwise, this parameter must be a handle of the SEC_IMAGE section object created on the EXE file before calling ZwCreateProcess().

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

Though note that Corinna Vinschen indicates that Cygwin found using ZwCreateProcess() still unreliable:

尽管请注意Corinna Vinschen 表示 Cygwin 发现使用 ZwCreateProcess() 仍然不可靠

Iker Arizmendi wrote:

> 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

This document is rather old, 10 years or so. While we're still using Win32 calls to emulate fork, the method has changed noticably. Especially, we don't create the child process in the suspended state anymore, unless specific datastructes need a special handling in the parent before they get copied to the child. In the current 1.5.25 release the only case for a suspended child are open sockets in the parent. The upcoming 1.7.0 release will not suspend at all.

One reason not to use ZwCreateProcess was that up to the 1.5.25 release we're still supporting Windows 9x users. However, two attempts to use ZwCreateProcess on NT-based systems failed for one reason or another.

It would be really nice if this stuff would be better or at all documented, especially a couple of datastructures and how to connect a process to a subsystem. While fork is not a Win32 concept, I don't see that it would be a bad thing to make fork easier to implement.

伊克尔·阿里兹门迪写道:

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

回答by Evan Teran

Well, windows doesn't really have anything quite like it. Especially since fork can be used to conceptually create a thread or a process in *nix.

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

So, I'd have to say:

所以,我不得不说:

CreateProcess()/CreateProcessEx()

CreateProcess()/CreateProcessEx()

and

CreateThread()(I've heard that for C applications, _beginthreadex()is better).

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

回答by Eric des Courtis

People have tried to implement fork on Windows. This is the closest thing to it I can find:

人们曾尝试在 Windows 上实现 fork。这是我能找到的最接近它的东西:

Taken from: http://doxygen.scilab.org/5.3/d0/d8f/forkWindows_8c_source.html#l00216

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

回答by Brandon E Taylor

The following document provides some information on porting code from UNIX to Win32: https://msdn.microsoft.com/en-us/library/y23kc048.aspx

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

Among other things, it indicates that the process model is quite different between the two systems and recommends consideration of CreateProcess and CreateThread where fork()-like behavior is required.

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

回答by paxdiablo

Prior to Microsoft introducing their new "Linux subsystem for Windows" option, CreateProcess()was the closest thing Windows has to fork(), but Windows requires you to specify an executable to run in that process.

在 Microsoft 推出新的“Windows 的 Linux 子系统”选项之前,这CreateProcess()是 Windows 最接近的选项fork(),但 Windows 要求您指定一个可执行文件以在该进程中运行。

The UNIX process creation is quite different to Windows. Its fork()call basically duplicates the current process almost in total, each in their own address space, and continues running them separately. While the processes themselves are different, they are still running the same program.See herefor a good overview of the fork/execmodel.

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

Going back the other way, the equivalent of the Windows CreateProcess()is the fork()/exec()pairof functions in UNIX.

反过来说,Windows 的等价物CreateProcess()是UNIX 中的fork()/exec()对函数。

If you were porting software to Windows and you don't mind a translation layer, Cygwin provided the capability that you want but it was rather kludgey.

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

Of course, withthe new Linux subsystem, the closest thing Windows has to fork()is actuallyfork():-)

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

回答by sjcaged

fork() semantics are necessary where the child needs access to the actual memory state of the parent as of the instant fork() is called. I have a piece of software which relies on the implicit mutex of memory copying as of the instant fork() is called, which makes threads impossible to use. (This is emulated on modern *nix platforms via copy-on-write/update-memory-table semantics.)

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

The closest that exists on Windows as a syscall is CreateProcess. The best that can be done is for the parent to freeze all other threads during the time that it is copying memory over to the new process's memory space, then thaw them. Neither the Cygwin frok [sic] class nor the Scilab code that Eric des Courtis posted does the thread-freezing, that I can see.

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

Also, you probably shouldn't use the Zw* functions unless you're in kernel mode, you should probably use the Nt* functions instead. There's an extra branch that checks whether you're in kernel mode and, if not, performs all of the bounds checking and parameter verification that Nt* always do. Thus, it's very slightly less efficient to call them from user mode.

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

回答by user3502619

"as soon as you want to do file access or printf then io are refused"

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

  • You cannot have your cake and eat it too... in msvcrt.dll, printf() is based on the Console API, which in itself uses lpc to communicate with the console subsystem (csrss.exe). Connection with csrss is initiated at process start-up, which means that any process that begins its execution "in the middle" will have that step skipped. Unless you have access to the source code of the operating system, then there is no point in trying to connect to csrss manually. Instead, you should create your own subsystem, and accordingly avoid the console functions in applications that use fork().

  • once you have implemented your own subsystem, don't forget to also duplicate all of the parent's handles for the child process;-)

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

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

"Also, you probably shouldn't use the Zw* functions unless you're in kernel mode, you should probably use the Nt* functions instead."

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

  • This is incorrect. When accessed in user mode, there is absolutely no difference between Zw*** Nt***; these are merely two different (ntdll.dll) exported names that refer to the same (relative) virtual address.
  • 这是不正确的。在用户态访问时,Zw*** Nt*** 绝对没有区别;这些只是引用相同(相对)虚拟地址的两个不同(ntdll.dll)导出名称。

ZwGetContextThread(NtCurrentThread(), &context);

ZwGetContextThread(NtCurrentThread(), &context);

  • obtaining the context of the current (running) thread by calling ZwGetContextThread is wrong, is likely to crash, and (due to the extra system call) is also not the fastest way to accomplishing the task.
  • 通过调用 ZwGetContextThread 获取当前(正在运行)线程的上下文是错误的,很可能会崩溃,并且(由于额外的系统调用)也不是完成任务的最快方法。

回答by Piotr Dobrogost

The closest you say... Let me think... This must be fork() I guess :)

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

For details see Does Interix implement fork()?

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

回答by John T

Your best options are CreateProcess()or CreateThread(). There is more information on porting here.

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