web-dev-qa-db-ja.com

ウィンドウがfork()に最も近いものは何ですか?

私は質問がそれをすべて言っていると思います。

Windowsでフォークしたい。最も類似した操作は何で、どのように使用しますか。

113
rlbond

Cygwin は、Windows上で完全に機能するfork()を備えています。したがって、Cygwinの使用が許容できる場合は、パフォーマンスが問題でない場合に問題が解決されます。

それ以外の場合は、Cygwinがfork()を実装する方法を確認できます。かなり古いCygwinのアーキテクチャから doc

5.6。プロセスの作成Cygwinのfork呼び出しは、Win32 APIの上にうまくマッピングされないため、特に興味深いものです。これにより、正しく実装することが非常に難しくなります。現在、Cygwinフォークは、UNIXの初期のフレーバーに存在していたものと同様の非コピーオンライト実装です。

親プロセスが子プロセスをフォークするときに最初に起こることは、親が子のCygwinプロセステーブル内のスペースを初期化することです。次に、Win32 CreateProcess呼び出しを使用して、中断された子プロセスを作成します。次に、親プロセスはsetjmpを呼び出して独自のコンテキストを保存し、Cygwin共有メモリ領域(すべてのCygwinタスク間で共有)にこれへのポインターを設定します。次に、自身のアドレススペースから中断された子のアドレススペースにコピーすることにより、子の.dataセクションと.bssセクションを埋めます。子のアドレス空間が初期化された後、親がミューテックスで待機している間に子が実行されます。子は、フォークされたことを発見し、保存されたジャンプバッファーを使用してロングジャンプします。次に、子は親が待機しているミューテックスを設定し、別のミューテックスでブロックします。これは、親がそのスタックとヒープを子にコピーするシグナルです。その後、子が待機しているmutexを解放し、fork呼び出しから戻ります。最後に、子は最後のミューテックスでのブロックから復帰し、共有領域を介して渡されたメモリマップ領域を再作成し、フォーク自体から戻ります。

親プロセスと子プロセスの間のコンテキストスイッチの数を減らすことでフォークの実装を高速化する方法についていくつかのアイデアがありますが、Win32ではフォークはほぼ確実に非効率的です。幸いなことに、ほとんどの場合、Cygwinによって提供される呼び出しのspawnファミリは、わずかな労力でfork/execのペアに置き換えることができます。これらの呼び出しは、Win32 APIの上にきれいにマッピングされます。その結果、それらははるかに効率的です。コンパイラーのドライバープログラムを変更してforkの代わりにspawnを呼び出すことは簡単な変更であり、テストではコンパイル速度が20〜30%向上しました。

ただし、spawnとexecにはそれぞれ独自の問題があります。 Win32では実際のexecを実行する方法がないため、Cygwinは独自のプロセスID(PID)を発明する必要があります。その結果、プロセスが複数のexec呼び出しを実行すると、単一のCygwin PIDに関連付けられた複数のWindows PIDが存在します。場合によっては、これらの各Win32プロセスのスタブが残り、exec'd Cygwinプロセスの終了を待つことがあります。

大変な仕事のようですね。そして、はい、それはslooooowです。

編集:ドキュメントは古くなっています。この素晴らしい answer をご覧ください

79

私はそれをやったことがないので、これに関する詳細を確かに知りませんが、ネイティブのNT APIにはプロセスをフォークする機能があります(WindowsのPOSIXサブシステムにはこの機能が必要です-POSIXサブシステムがもサポートされています)。

ZwCreateProcess()を検索すると、さらに詳細な情報が得られます。たとえば、 Maxim Shatskihからのこの情報のビット

ここで最も重要なパラメーターはSectionHandleです。このパラメーターがNULLの場合、カーネルは現在のプロセスを分岐します。それ以外の場合、このパラメーターは、ZwCreateProcess()を呼び出す前に、EXEファイルで作成されたSEC_IMAGEセクションオブジェクトのハンドルでなければなりません。

Corinna 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呼び出しを使用してforkをエミュレートしていますが、メソッドは顕著に変更されています。特に、特定のデータ構造が子にコピーされる前に親で特別な処理を必要としない限り、子プロセスを一時停止状態で作成しません。現在の1.5.25リリースでは、中断された子の唯一のケースは、親の開いたソケットです。今後の1.7.0リリースはまったく中断されません。

ZwCreateProcessを使用しない理由の1つは、1.5.25リリースまではWindows 9xユーザーをサポートしていることです。ただし、NTベースのシステムでZwCreateProcessを使用しようとする2つの試みは、何らかの理由で失敗しました。

特に、いくつかのデータ構造と、プロセスをサブシステムに接続する方法について、このようなものがより優れているか、まったく文書化されていれば、本当に素晴らしいことです。フォークはWin32の概念ではありませんが、フォークを実装しやすくすることは悪いことではないと思います。

58
Michael Burr

さて、Windowsには実際にそのようなものは何もありません。特にフォークは、* nixでスレッドまたはプロセスを概念的に作成するために使用できるためです。

だから、私は言わなければならないでしょう:

CreateProcess() /CreateProcessEx()

そして

CreateThread() (Cアプリケーションの場合、 _beginthreadex() の方が良いと聞きました)。

35
Evan Teran

人々は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;
}
15

Microsoftが新しい「Windows用Linuxサブシステム」オプションを導入する前は、CreateProcess()がWindowsがfork()に最も近いものでしたが、そのプロセスで実行する実行可能ファイルを指定する必要があります。

UNIXプロセスの作成は、Windowsとはまったく異なります。そのfork()呼び出しは、基本的に現在のプロセスをほぼ合計で、それぞれ独自のアドレス空間で複製し、別々に実行し続けます。プロセス自体は異なりますが、それらは同じプログラムを実行しています。fork/execモデルの概要については here を参照してください。 。

逆に言えば、WindowsのCreateProcess()に相当するものは、UNIXの関数のfork()/exec()pairです。 。

ソフトウェアをWindowsに移植していて、翻訳レイヤーを気にしない場合、Cygwinは必要な機能を提供しますが、かなり面倒です。

もちろん、with新しい Linuxサブシステム 、Windowsがfork()に最も近いものは実際にfork() :-)

6
paxdiablo

次のドキュメントは、UNIXからWin32へのコードの移植に関する情報を提供します。 https://msdn.Microsoft.com/en-us/library/y23kc048.aspx

特に、プロセスモデルは2つのシステム間でかなり異なることを示しており、fork()のような動作が必要なCreateProcessとCreateThreadの検討を推奨しています。

6

「ファイルアクセスまたはprintfを実行するとすぐにioは拒否されます」

  • ケーキを持って食べることもできません... msvcrt.dllでは、printf()はコンソールAPIに基づいており、それ自体はlpcを使用してコンソールサブシステム(csrss.exe)と通信します。 csrssとの接続は、プロセスの起動時に開始されます。つまり、「中間」で実行を開始するプロセスでは、そのステップがスキップされます。オペレーティングシステムのソースコードにアクセスできない限り、手動でcsrssに接続しようとしても意味がありません。代わりに、独自のサブシステムを作成し、それに応じてfork()を使用するアプリケーションのコンソール機能を回避する必要があります。

  • 独自のサブシステムを実装したら、子プロセスの親のハンドルもすべて複製することを忘れないでください;-)

「また、カーネルモードでない限り、おそらくZw *関数を使用しないでください。代わりにNt *関数を使用する必要があります。」

  • これは間違っています。ユーザーモードでアクセスした場合、Zw *** Nt ***にはまったく違いはありません。これらは、同じ(相対)仮想アドレスを参照する2つの異なる(ntdll.dll)エクスポート名にすぎません。

ZwGetContextThread(NtCurrentThread()、&context);

  • zwGetContextThreadを呼び出して現在の(実行中の)スレッドのコンテキストを取得するのは間違っており、クラッシュする可能性が高く、また(余分なシステムコールのために)タスクを達成する最速の方法でもありません。
3
user3502619

fork()が呼び出された時点で、子が親の実際のメモリ状態にアクセスする必要がある場合、fork()セマンティクスが必要です。 fork()が呼び出された時点で、メモリコピーの暗黙的なミューテックスに依存するソフトウェアを使用しているため、スレッドを使用できません。 (これは、copy-on-write/update-memory-tableセマンティクスを介して、最新の* nixプラットフォームでエミュレートされます。)

Windowsでsyscallとして存在する最も近いものはCreateProcessです。できる最善の方法は、親がメモリを新しいプロセスのメモリスペースにコピーしている間に他のすべてのスレッドをフリーズし、それらを解凍することです。 Cygwin frok [sic]クラスも、Eric des Courtisが投稿したScilabコードも、スレッドフリーズを行いません。

また、カーネルモードでない限り、Zw *関数を使用しないでください。代わりにNt *関数を使用する必要があります。カーネルモードになっているかどうかをチェックする追加のブランチがあり、そうでない場合は、Nt *が常に行う境界チェックとパラメーター検証のすべてを実行します。したがって、ユーザーモードから呼び出すのは非常にわずかに効率的です。

3
sjcaged

あなたが言う最も近い...考えさせてください...これはfork()でなければなりません:)

詳細については、 Interixはfork()を実装していますか? を参照してください

2
Piotr Dobrogost

最適なオプションは CreateProcess() または CreateThread() です。移植に関する詳細情報があります here

2
John T

Windowsでfork()をエミュレートする簡単な方法はありません。

代わりにスレッドを使用することをお勧めします。

2
VVS

サブプロセスの作成とその待機のみを必要とする場合、おそらくprocess.hの_spawn * APIで十分です。詳細は次のとおりです。

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

1
solstice333