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

129,005

Solution 1

Cygwin has 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.

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

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.

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

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

Solution 2

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).

A search for ZwCreateProcess() should get you some more details - for example this bit of information from 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().

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

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.

Solution 3

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.

So, I'd have to say:

CreateProcess()/CreateProcessEx()

and

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

Solution 4

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

Taken from: 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;
}

Solution 5

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.

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 here for a good overview of the fork/exec model.

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

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.

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

Share:
129,005
rlbond
Author by

rlbond

I like C++. I also like Python.

Updated on July 08, 2022

Comments

  • rlbond
    rlbond almost 2 years

    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.

  • Foredecker
    Foredecker almost 15 years
    This is the wrong answer. CreateProcess() and CreateThread() are the general equivalents.
  • Foredecker
    Foredecker almost 15 years
    This is a good answer if you want to write a Cygwin app on windows. But in general its not the best thing to do. Fundamentally, the *nix and Windows process and thread models are quite different. CreateProcess() and CreateThread() are the generally equivalent APIs
  • bk1e
    bk1e almost 15 years
    Interix is available in Windows Vista Enterprise/Ultimate as "Subsystem for UNIX Applications": en.wikipedia.org/wiki/Interix
  • Michael Burr
    Michael Burr almost 15 years
    @Foredecker - this may be a wrong answer, but CreateProcess()/CreateThread() might well be wrong, too. It depends on whether one is looking for 'the Win32 way to do things' or 'as close to fork() semantics as possible'. CreateProcess() behaves significantly differently than fork(), which is the reason cygwin needed to do a lot of work to support it.
  • Jonathan Baldwin
    Jonathan Baldwin almost 11 years
    The link is dead as of 2013, is there a mirror or archive somewhere?
  • Michael Burr
    Michael Burr almost 11 years
    @jon: I've tried to fix up the links and copy the relevant text into the answer (so future broken links aren't a problem). However, this answer is from long enough ago that I'm not 100% certain the quote I found today is what I was referring to in 2009.
  • BCran
    BCran over 10 years
    Note that most of the error checking is missing - e.g. ZwCreateThread returns an NTSTATUS value which can be checked using the SUCCEEDED and FAILED macros.
  • Leigh
    Leigh about 10 years
    This doesn't appear to be answering the main question but replying to a few other different answers, and would probably be better replying directly to each for clarity and to make it easier to follow what's going on.
  • Harry Johnston
    Harry Johnston about 10 years
    Developers should keep in mind that this is an unsupported mechanism, and IIRC it is indeed inclined to break whenever some other process on the system is using code injection.
  • leetNightshade
    leetNightshade almost 10 years
    What happens if the fork crashes, does it crash the program, or does the thread just crash? If it crashes the program, then this isn't really forking. Just curious, because I'm looking for a real solution, and hope this might be a decent alternative.
  • leetNightshade
    leetNightshade almost 10 years
    I would like to note there's a bug in the provided code. haveLoadedFunctionsForFork is a global function in the header, but a static function in the c file. They both should be global. And currently fork crashes, adding error checking now.
  • Aaron McDaid
    Aaron McDaid over 9 years
    If people want "fork with immediate exec", then perhaps CreateProcess is a candidate. But fork without exec is often desirable and this is what drivers people to ask for a real fork.
  • PythonNut
    PythonNut over 9 years
    The different implementation link is no longer valid.
  • Laurynas Biveinis
    Laurynas Biveinis over 9 years
    Edited to leave the other answer link only
  • Roman Starkov
    Roman Starkov almost 9 years
    The Sad History of the Microsoft POSIX Subsystem was also quite enlightening about Interix.
  • Pacerier
    Pacerier over 8 years
    @Foredecker, Actually you shouldn't do it even if you are trying to write a "cygwin app". It tries to imitate Unix's fork yet it accomplishes this with a leaky solution and you must be prepared for unexpected situations.
  • Andon M. Coleman
    Andon M. Coleman over 8 years
    Very interesting information regarding Zw* exported symbols, thank you.
  • Jasen
    Jasen almost 8 years
    you seem to be asuming that printf always writes to the console.
  • paxdiablo
    paxdiablo about 6 years
    Well, in fairness, implementing fork was exactly what CygWin did. But, if you ever read up on how they did it, yor "no easy way" is a gross misunderstatement :-)
  • Caesar
    Caesar about 5 years
    So, given WSL, can I use fork in an average, non-WSL application?
  • Paul Stelian
    Paul Stelian about 5 years
    Do note that the Zw* functions from user space still map to Nt* functions in kernel space, for safety. Or at least they should.
  • Paul Stelian
    Paul Stelian about 5 years
    The site is dead and I don't know how I can compile the example on my own system. I assume I am missing some headers or including the wrong ones am I? (the example doesn't show them.)
  • Tarik
    Tarik over 3 years
    @Pacerier Thanks to your answer I found back the leaky abstractions article. :-)