質問がすべてを物語っていると思います。
Windowsでフォークしたい。最も類似した操作とその使用方法を教えてください。
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 呼び出しから戻ります。最後に、子は最後のミューテックスでのブロックから復帰し、共有領域を介して渡されたメモリマップ領域を再作成し、フォーク自体から戻ります。
親プロセスと子プロセス間のコンテキスト スイッチの数を減らすことで fork の実装を高速化する方法についていくつかのアイデアがありますが、Win32 では fork はほぼ常に非効率的です。幸いなことに、ほとんどの場合、Cygwin が提供する呼び出しの spawn ファミリーは、わずかな労力で fork/exec のペアに置き換えることができます。これらの呼び出しは、Win32 API の上にきれいにマップされます。その結果、はるかに効率的です。fork の代わりに spawn を呼び出すようにコンパイラのドライバー プログラムを変更することは些細な変更であり、テストではコンパイル速度が 20% から 30% 向上しました。
ただし、spawn と exec には独自の一連の問題があります。Win32 では実際に exec を実行する方法がないため、Cygwin は独自のプロセス ID (PID) を発明する必要があります。その結果、プロセスが複数の exec 呼び出しを実行すると、複数の Windows PID が単一の Cygwin PID に関連付けられます。場合によっては、これらの各 Win32 プロセスのスタブが残り、実行された Cygwin プロセスが終了するのを待つことがあります。
大変な作業のようですね。そして、はい、それは遅いです。
編集:ドキュメントは古くなっています。更新については、この優れた回答を参照してください
行ったことがないので、これについての詳細は確かにわかりませんが、ネイティブNT APIにはプロセスをフォークする機能があります(WindowsのPOSIXサブシステムにはこの機能が必要です-POSIXサブシステムかどうかはわかりませんもうサポートされています)。
ZwCreateProcess()を検索すると、さらに詳細な情報が得られるはずです。たとえば、MaximShatskihからのこの情報です。
ここで最も重要なパラメータはSectionHandleです。このパラメータがNULLの場合、カーネルは現在のプロセスをフォークします。それ以外の場合、このパラメーターは、ZwCreateProcess()を呼び出す前にEXEファイルで作成されたSEC_IMAGEセクションオブジェクトのハンドルである必要があります。
Corinna Vinschenは、CygwinがZwCreateProcess()を使用して検出したことはまだ信頼できないことを示していることに注意してください。
IkerArizmendiは次のように書いています。
> 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を使用しない理由の1つは、1.5.25リリースまではWindows9xユーザーを引き続きサポートしていることです。ただし、NTベースのシステムでZwCreateProcessを使用する2つの試みは、何らかの理由で失敗しました。
特にいくつかのデータ構造とプロセスをサブシステムに接続する方法について、このようなものがより適切であるか、まったく文書化されていれば、本当に素晴らしいでしょう。フォークはWin32の概念ではありませんが、フォークを実装しやすくすることは悪いことではないと思います。
まあ、Windows にはこれほど似たものはありません。特に、* nix でスレッドまたはプロセスを概念的に作成するために fork を使用できるためです。
だから、私は言わなければならないでしょう:
CreateProcess()
/CreateProcessEx()
と
CreateThread()
(C アプリケーションの場合は優れていると聞いたこと_beginthreadex()
があります)。
人々は 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;
}
次のドキュメントは、UNIX から Win32 へのコードの移植に関する情報を提供します: https://msdn.microsoft.com/en-us/library/y23kc048.aspx
特に、プロセス モデルが 2 つのシステム間で大きく異なることを示しており、fork() のような動作が必要な場合に CreateProcess と CreateThread を検討することをお勧めします。
「ファイル アクセスまたは printf を実行しようとすると、すぐに io が拒否されます」
ケーキを持って食べることはできません... msvcrt.dll では、printf() はコンソール API に基づいており、コンソール サブシステム (csrss.exe) との通信に lpc を使用します。csrss との接続はプロセスの起動時に開始されます。つまり、「途中で」実行を開始するプロセスでは、そのステップがスキップされます。オペレーティング システムのソース コードにアクセスできない限り、手動で csrss に接続しようとしても意味がありません。代わりに、独自のサブシステムを作成し、それに応じて fork() を使用するアプリケーションでコンソール関数を避ける必要があります。
独自のサブシステムを実装したら、子プロセスの親のハンドルもすべて複製することを忘れないでください;-)
「また、カーネル モードでない限り、おそらく Zw* 関数を使用しないでください。代わりに Nt* 関数を使用する必要があります。」
ZwGetContextThread(NtCurrentThread(), &context);
他の回答が述べているように、NT (最新バージョンの Windows の基礎となるカーネル) には、Unix の fork() に相当するものがあります。それは問題ではありません。
問題は、プロセスの状態全体を複製することは、一般的にはまともなことではないということです。これは Windows と同様に Unix の世界でも当てはまりますが、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() を呼び出して fork できます。それ以外の場合は、やりたいという欲求から身を引き離してください。Cygwin をターゲットにしている場合でも、どうしても必要な場合を除き、fork() を使用しないでください。
最良のオプションはCreateProcess()またはCreateThread()です。移植の詳細については、こちらを参照してください。
Windows で fork() をエミュレートする簡単な方法はありません。
代わりにスレッドを使用することをお勧めします。
あなたが言う最も近い...考えさせてください...これはfork()に違いないと思います:)