Windows 最接近于 fork()的东西是什么?

我想问题说明了一切。

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

140725 次浏览

窗户上可没有这样的东西。特别是因为 fork 可以用于在 * nix 中概念性地创建线程或进程。

所以,我不得不说:

CreateProcess() /CreateProcessEx()

还有

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

您的最佳选择是 创建进程()CreateThread ()。有更多关于移植 给你的信息。

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

我建议你用线代替。

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

除了其他方面之外,它指出这两个系统之间的流程模型是非常不同的,并建议考虑 CreateProcess 和 CreateThread,其中需要类似 fork ()的行为。

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

UNIX 进程的创建与 Windows 非常不同。它的 fork()调用基本上几乎完全复制了当前进程,每个进程都有自己的地址空间,并继续分别运行它们。虽然进程本身是不同的,但它们仍然运行相同的 程序。,请参阅 给你以获得对 fork/exec模型的良好概述。

反过来说,与 WindowsCreateProcess()等价的是 UNIX 中函数的 fork()/exec()一对

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

当然,是新的 Linux 子系统,Windows 最接近 fork()的东西是 事实上 fork(): -)

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

否则你可以看看 Cygwin 是如何实现 fork ()的:

5.6程序创建 Cygwin 的 fork call 尤其有趣 因为它不能很好地映射到 Win32API。这使得它非常 难以正确实施。 目前,Cygwin fork 是一个 非书面复制实现 类似于早期 UNIX 的风格。

时发生的第一件事 父进程分叉子进程 是父级初始化一个空格 对象的 Cygwin 过程表中的 然后创建一个暂停 使用 Win32的子进程 CreateProcess 调用 Process 调用 setjmp 保存自己的 中的上下文并设置指向此 Cygwin 共享内存区(共享 在所有 Cygwin 任务中)。然后填充 在子的。 data 和。 bss 部分中 通过从自己的地址空间复制 进入被停学儿童的地址 空间。在孩子的地址空间之后 初始化时,子元素运行 父等待互斥对象 发现它已经被分叉 使用保存的跳转缓冲区进行长跳。 然后,该子元素将互斥量设置为 父母在等待,阻止 另一个互斥对象。这是 复制其堆栈和堆的父级 进入孩子体内,然后 释放子互斥体 等着从岔路口回来 终于,孩子从 阻塞最后一个互斥对象,重新创建 任何传递给它的内存映射区域 通过共享区域,并从 叉子自己。

虽然我们有一些关于如何 加速我们的 fork 实现 减少上下文的数量 在父代和子代之间切换 过程中,叉几乎肯定会 在 Win32下总是效率低下。 幸运的是,在大多数情况下 产卵的家庭提供的呼叫 Cygwin 可以替代 a 只有少量的 fork/exec 对 努力。这些呼叫清楚地映射在顶部 的应用程式编程接口 更有效率 要调用的编译器驱动程序 而不是叉子是微不足道的 更改和增加编译 以百分之二十到百分之三十的速度 我们的测试。

然而,刷新和执行提出了他们的 自己的一套困难。因为有 不是一个真正的执行官 Cygwin 必须发明自己的 Win32 进程 ID (PID) 一个进程执行多个 exec 调用时,将会有多个 Windows 与单个 Cygwin 关联的 PID 在某些情况下,每个 这些 Win32进程可能会逗留, 等待他们被处决的 Cygwin 退出程序退出程序退出程序。

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

编辑: 文档已经过时,请查看这个优秀的 回答更新

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

搜索 ZwCreateProcess ()应该可以得到更多细节,例如 这是马克西姆 · 沙茨基提供的信息:

这里最重要的参数是 SectionHandle 如果为 NULL,则内核将分叉当前进程 上创建的 SEC _ IMAGE 节对象的句柄 在调用 ZwCreateProcess ()之前调用 EXE 文件。

但请注意,Corinna Vinchen 指出 Cygwin 发现使用 ZwCreateProcess ()仍然不可靠:

Iker Arizona mendi 写道:

> 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 我们仍然支持 Windows9x 用户 在基于 NT 的系统上使用 ZwCreateProcess 的尝试失败了一次 不管是什么原因。

如果这些东西能更好一点就好了 文档,特别是一对数据结构和如何连接 虽然 fork 不是 Win32的概念,但我不是 让 fork 更容易实现将是一件坏事。

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

详情请参阅 Interix 实现 fork ()吗?

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

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

在 Windows 上,最接近系统调用的是 CreateProcess。最好的办法是父进程在将内存复制到新进程的内存空间期间冻结所有其他线程,然后解冻它们。我可以看到,无论是 Cygwin Frok 类还是 Eric des Courtis 发布的 Scilab 代码都没有实现线程冻结。

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

“只要你想访问文件或者打印输出,那么我就会被拒绝”

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

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

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

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

ZwGetContextThread (NtCurrentThread () ,& context) ;

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

如果您只关心创建一个子进程并等待它,那么 process.h 中的 _ spawn * API 就足够了。下面是更多的相关信息:

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

正如其他答案所提到的,NT (Windows 现代版本的内核基础)有一个相当于 Unix fork ()的东西。这不是问题所在。

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

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

所以你有两个选择。一个是禁止使用 kernel32和 user32以及其他没有设计成可以分叉的库——包括任何直接或间接链接到 kernel32或 user32的库,这实际上就是所有这些库。这意味着你根本无法与 Windows 桌面交互,只能呆在你自己的 Unixy 世界里。这是 NT 的各种 Unix 子系统采用的方法。

另一种选择是诉诸某种可怕的黑客手段,尝试让不知情的库使用 fork ()。Cygwin 就是这么做的。它创建一个新进程,让它进行初始化(包括使用 csrss.exe 注册自己) ,然后从旧进程复制大部分动态状态,并期待最好的结果。令我惊讶的是,这个 永远不会的工作。它当然不能可靠地工作——即使它不会因为地址空间冲突而随机失败,您正在使用的任何库都可能处于破碎状态。目前公认的关于 Cygwin 拥有一个“功能齐全的 fork ()”的说法是... 令人怀疑的。

简介: 在类 Interix 的环境中,可以通过调用 fork ()来进行 fork。否则,请试着让自己摆脱做这件事的欲望。即使您的目标是 Cygwin,也不要使用 fork () ,除非万不得已。

大多数粗制滥造的解决方案已经过时了。Winnie the fuzzer 有一个版本的 fork,可以在当前版本的 Windows10上运行(尽管这需要系统特定的偏移量,而且很容易中断)。

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