development

Windows가 fork ()해야 할 가장 가까운 것은 무엇입니까?

big-blog 2020. 7. 24. 07:16
반응형

Windows가 fork ()해야 할 가장 가까운 것은 무엇입니까?


나는 그 질문에 모든 것이 있다고 생각합니다.

Windows에서 포크하고 싶습니다. 가장 유사한 작업은 무엇이며 어떻게 사용합니까?


Cygwin 은 Windows에서 fork () 기능을 완전히 갖추고 있습니다. 따라서 Cygwin을 사용할 수 있으면 성능에 문제가없는 경우 문제가 해결됩니다.

그렇지 않으면 Cygwin이 fork ()를 구현하는 방법을 살펴볼 수 있습니다. 아주 오래된 Cygwin의 아키텍처 문서에서 :

5.6. 프로세스 생성 Cygwin의 포크 호출은 Win32 API 위에 잘 매핑되지 않기 때문에 특히 흥미 롭습니다. 이로 인해 올바르게 구현하기가 매우 어렵습니다. 현재 Cygwin 포크는 UNIX의 초기 버전에 존재했던 것과 유사한 비 복사시 구현입니다.

부모 프로세스가 자식 프로세스를 분기 할 때 가장 먼저 일어나는 일은 부모가 자식에 대한 Cygwin 프로세스 테이블에서 공간을 초기화한다는 것입니다. 그런 다음 Win32 CreateProcess 호출을 사용하여 일시 중단 된 자식 프로세스를 만듭니다. 다음으로, 상위 프로세스는 setjmp를 호출하여 자체 컨텍스트를 저장하고 Cygwin 공유 메모리 영역 (모든 Cygwin 태스크간에 공유 됨)에서 이에 대한 포인터를 설정합니다. 그런 다음 자체 주소 공간에서 일시 중단 된 자식 주소 공간으로 복사하여 자식 .data 및 .bss 섹션을 채 웁니다. 자식의 주소 공간이 초기화되면 부모가 뮤텍스를 기다리는 동안 자식이 실행됩니다. 아이는 저장된 점프 버퍼를 사용하여 포크와 롱 점프를 발견했습니다. 그러면 자식은 부모가 기다리는 뮤텍스를 설정하고 다른 뮤텍스를 차단합니다. 이것은 부모가 스택과 힙을 자식으로 복사하라는 신호이며, 그 후에 자식이 기다리고있는 뮤텍스를 해제하고 포크 호출에서 돌아옵니다. 마지막으로 자식은 마지막 뮤텍스를 차단하고 공유 영역을 통해 전달 된 메모리 매핑 영역을 다시 만든 다음 포크 자체에서 돌아옵니다.

부모 프로세스와 자식 프로세스 사이의 컨텍스트 전환 수를 줄여 포크 구현 속도를 높이는 방법에 대한 아이디어가 있지만 Win32에서는 포크가 항상 비효율적입니다. 다행히도 대부분의 상황에서 Cygwin이 제공하는 스폰 패밀리는 약간의 노력만으로 포크 / 엑서스 페어로 대체 될 수 있습니다. 이러한 호출은 Win32 API 위에 깔끔하게 매핑됩니다. 결과적으로 훨씬 효율적입니다. 포크 대신 스폰을 호출하도록 컴파일러의 드라이버 프로그램을 변경하는 것은 사소한 변화였으며 테스트에서 컴파일 속도가 20 ~ 30 % 증가했습니다.

그러나 스폰과 exec는 고유의 어려움을 제시합니다. Win32에서는 실제 실행 파일을 수행 할 방법이 없으므로 Cygwin은 자체 프로세스 ID (PID)를 발명해야합니다. 결과적으로 프로세스가 여러 exec 호출을 수행 할 때 단일 Cygwin PID와 연관된 여러 Windows PID가 있습니다. 경우에 따라 이러한 각 Win32 프로세스의 스텁이 실행되어 실행 된 Cygwin 프로세스가 종료 될 때까지 대기 할 수 있습니다.

많은 일처럼 들리지 않습니까? 그리고 그렇습니다.

편집 : 문서가 오래 되었습니다. 업데이트에 대한 훌륭한 답변참조하십시오


나는 그것을하지 않았기 때문에 확실히 이것에 대한 세부 사항을 모른다. 그러나 네이티브 NT API는 프로세스를 포크하는 기능을 가지고있다 (Windows의 POSIX 하위 시스템에는이 기능이 필요하다-POSIX 하위 시스템이 있는지 확실하지 않다) 더 이상 지원됩니다).

ZwCreateProcess ()를 검색하면 Maxim Shatskih의 정보와 같은 자세한 정보를 얻을 수 있습니다 .

여기서 가장 중요한 매개 변수는 SectionHandle입니다. 이 매개 변수가 NULL이면 커널은 현재 프로세스를 분기합니다. 그렇지 않으면이 매개 변수는 ZwCreateProcess ()를 호출하기 전에 EXE 파일에 작성된 SEC_IMAGE 섹션 오브젝트의 핸들이어야합니다.

참고 비록 코리나 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

이 문서는 10 년 정도 된 오래된 문서입니다. 우리는 여전히 포크를 에뮬레이트하기 위해 Win32 호출을 사용하고 있지만 그 방법은 눈에 띄게 변경되었습니다. 특히 특정 데이터 구조가 자식에 복사되기 전에 부모에서 특별한 처리가 필요하지 않으면 더 이상 일시 중단 상태에서 자식 프로세스를 만들지 않습니다. 현재 1.5.25 릴리스에서 일시 중단 된 자식에 대한 유일한 경우는 부모의 열린 소켓입니다. 다음 1.7.0 릴리스는 전혀 중단되지 않습니다.

ZwCreateProcess를 사용하지 않는 한 가지 이유는 여전히 Windows 9x 사용자를 지원하는 1.5.25 릴리스까지였습니다. 그러나 NT 기반 시스템에서 ZwCreateProcess를 사용하려는 두 가지 시도는 어떤 이유로 실패했습니다.

이 자료가 더 좋거나 문서화되어 있다면, 특히 몇 가지 데이터 구조와 프로세스를 서브 시스템에 연결하는 방법이 정말 좋을 것입니다. 포크는 Win32 개념은 아니지만 포크를 더 쉽게 구현하는 것은 나쁜 일이 아닙니다.


글쎄, 창문에는 실제로 비슷한 것이 없습니다. 특히 포크는 * nix에서 개념적으로 스레드 또는 프로세스를 작성하는 데 사용될 수 있기 때문입니다.

그래서 나는 말해야 할 것입니다.

CreateProcess()/CreateProcessEx()

CreateThread()(C 응용 프로그램의 경우 _beginthreadex()더 좋습니다).


사람들은 Windows에서 포크를 구현하려고 시도했습니다. 이것은 내가 찾을 수있는 가장 가까운 것입니다.

출처 : 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;
}

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() :-)


The following document provides some information on porting code from UNIX to 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.


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

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.

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.


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

  • 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;-)

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

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

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.

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


There is no easy way to emulate fork() on Windows.

I suggest you to use threads instead.


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

For details see Does Interix implement fork()?


If you only care about creating a subprocess and waiting for it, perhaps _spawn* API's in process.h are sufficient. Here's more information about that:

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

참고URL : https://stackoverflow.com/questions/985281/what-is-the-closest-thing-windows-has-to-fork

반응형