Windowsがfork()に最も近いものは何ですか?


124

質問はそれをすべて言うと思います。

Windowsでフォークしたい。最も類似した操作とは何ですか?

回答:


86

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

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

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

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

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

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

大変な作業ですね。そして、はい、それはすごいです。

編集:ドキュメントは古くなっています。更新については、この優れた回答をご覧ください


11
これは、WindowsでCygwinアプリを作成する場合に適しています。しかし、一般的にそれを行うのは最善ではありません。基本的に、* nixとWindowsプロセスおよびスレッドモデルはまったく異なります。CreateProcess()とCreateThread()は一般的に同等のAPIです
Foredecker

2
開発者はこれがサポートされていないメカニズムであり、IIRCはシステム上の他のプロセスがコードインジェクションを使用しているときはいつでも壊れる傾向があることを覚えておく必要があります。
ハリージョンストン

1
別の実装リンクは無効になりました。
PythonNut 2014年

他の回答リンクのみを残すように編集
Laurynas Biveinis 2014年

@Foredecker、「cygwinアプリ」を書こうとしても、実際にはそうすべきではありません。Unixを模倣しようとしますforkが、これはリークの多いソリューションでこれを実現し、予期しない状況に備える必要があります。
Pacerier 2015

66

私はそれを行ったことがないので、確かにこれの詳細はわかりませんが、ネイティブ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の概念ではありませんが、フォークを実装しやすくすることは悪いことだとは思いません。


これは間違った答えです。CreateProcess()とCreateThread()は一般的に同等です。
フォレデッカー2009年

2
Interixには、「UNIXのアプリケーション用サブシステム」としてWindows Vistaのエンタープライズ/アルティメットで利用可能です:en.wikipedia.org/wiki/Interix
bk1e

15
@Foredecker-これは間違った答えかもしれませんが、CreateProcess()/ CreateThread()も間違っている可能性があります。それは、「Win32で物事を行う方法」を探しているのか、「fork()セマンティクスにできるだけ近い」を探しているのかによって異なります。CreateProcess()の動作はfork()とは大きく異なります。これが、cygwinがサポートするために多くの作業を行う必要があった理由です。
マイケル・バー、

1
@jon:リンクを修正し、関連するテキストを回答にコピーしようとしました(そのため、リンク切れの問題は問題になりません)。しかし、この答えは十分な長さ前に、私は100%ではないんだということから、ある特定の私が今日見つけ引用は、私が2009年にまで言及していたものです
マイケル・バリ

4
人々が「fork即時exec」を必要とする場合、おそらくCreateProcessが候補です。しかし、forkないexecことが望ましい場合が多く、これはドライバーが実際に求めているものforkです。
アーロン・マクデイド2014年

37

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

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

CreateProcess()/CreateProcessEx()

そして

CreateThread()(Cアプリケーションの_beginthreadex()方が優れていると聞きました)。


17

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

4
エラーチェックのほとんどが欠落していることに注意してください。たとえば、ZwCreateThreadは、SUCCEEDEDおよびFAILEDマクロを使用してチェックできるNTSTATUS値を返します。
BCran 2013年

1
forkクラッシュした場合はどうなりますか?プログラムがクラッシュしたり、スレッドがクラッシュしたりしますか?プログラムがクラッシュした場合、これは実際にはフォークではありません。私は本当の解決策を探しているので、好奇心旺盛です。そして、これがまともな代替手段になることを願っています。
leetNightshade 14

1
提供されたコードにバグがあることに注意したいと思います。haveLoadedFunctionsForForkはヘッダーのグローバル関数ですが、cファイルの静的関数です。どちらもグローバルでなければなりません。そして現在、フォーククラッシュが発生し、エラーチェックが追加されています。
leetNightshade 14

サイトは死んでいて、自分のシステムでサンプルをコンパイルする方法がわかりません。一部のヘッダーが欠落しているか、間違ったヘッダーが含まれていると思いますか?(例ではそれらを示していません。)
Paul Stelian

6

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

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

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

ソフトウェアをWindowsに移植していて、翻訳レイヤーを気にしていない場合、Cygwinは必要な機能を提供していましたが、それはかなり扱いにくいものでした。

もちろん、新しいLinuxのサブシステム、Windowsがに持っている最も近いものがfork()あり、実際に fork() :-)


2
では、WSLを前提forkとして、平均的な非WSLアプリケーションで使用できますか?
シーザー


4

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

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

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

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

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

ZwGetContextThread(NtCurrentThread()、&context);

  • ZwGetContextThreadを呼び出して現在の(実行中の)スレッドのコンテキストを取得することは間違っており、クラッシュする可能性が高く、(追加のシステムコールのため)タスクを完了するための最速の方法でもありません。

2
これはメインの質問に答えているようには見えませんが、他のいくつかの異なる答えに返信しているので、明確にするために、また進行中の状況を理解しやすくするために、それぞれに直接返信する方が良いでしょう。
リー

printfは常にコンソールに書き込むと想定しているようです。
Jasen 2016年

3

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

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

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


Zw *エクスポートされたシンボルに関する非常に興味深い情報、ありがとうございます。
Andon M. Coleman

安全のために、ユーザー空間のZw *関数は引き続きカーネル空間のNt *関数にマップされることに注意してください。あるいは、少なくともそうすべきです。
Paul Stelian


2

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

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


まあ、公平に言うと、実装forkはCygWinが行ったとおりです。しかし、あなたが彼らがそれをどのようにしたかについて読んだことがあるなら、あなたの「簡単な方法はない」は
ひどい


2

他の答えが述べたように、NT(Windowsの最新バージョンの基礎となるカーネル)にはUnix fork()と同等の機能があります。それは問題ではありません。

問題は、プロセス全体の状態のクローンを作成することは、通常、行うべき健全なことではないということです。これは、Unixの世界でもWindowsと同じですが、Unixの世界では常にfork()が使用され、ライブラリはそれに対応するように設計されています。Windowsライブラリはそうではありません。

たとえば、システムDLLであるkernel32.dllおよびuser32.dllは、Win32サーバープロセスcsrss.exeへのプライベート接続を維持します。分岐後、その接続のクライアント側には2つのプロセスがあり、問題が発生します。子プロセスはcsrss.exeにその存在を通知し、新しい接続を作成する必要があります。ただし、これらのライブラリはfork()を考慮して設計されていないため、そのためのインターフェイスはありません。

したがって、2つの選択肢があります。1つは、kernel32とuser32、およびフォークするように設計されていないその他のライブラリー(直接または間接的にkernel32またはuser32にリンクしているライブラリーを含む)の使用を禁止することです。つまり、Windowsデスクトップをまったく操作できず、独自のUnixyの世界で立ち往生しています。これは、NTのさまざまなUnixサブシステムで採用されているアプローチです。

もう1つのオプションは、何らかの恐ろしいハックに頼って、知らないライブラリをfork()で動作させることです。それがCygwinが行うことです。新しいプロセスを作成し、初期化(csrss.exeへの自身の登録を含む)を実行してから、動的プロセスのほとんどを古いプロセスからコピーし、最善を望みます。これはことを私に驚かせる今までに動作します。それは確かに確実に機能しません。アドレス空間の競合が原因でランダムに失敗しない場合でも、使用しているライブラリは何も表示されずに壊れた状態のままになる可能性があります。Cygwinが「フル機能のfork()」を持っているという現在受け入れられている回答の主張は...疑わしいです。

概要:Interixのような環境では、fork()を呼び出すことでフォークできます。それ以外の場合は、それをしたいという欲求から自分自身を離してみてください。Cygwinをターゲットにしている場合でも、どうしても必要な場合以外は、fork()を使用しないでください。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.