回答:
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プロセスが終了するのを待つことがあります。
大変な作業ですね。そして、はい、それはすごいです。
編集:ドキュメントは古くなっています。更新については、この優れた回答をご覧ください
fork
が、これはリークの多いソリューションでこれを実現し、予期しない状況に備える必要があります。
私はそれを行ったことがないので、確かにこれの詳細はわかりませんが、ネイティブ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の概念ではありませんが、フォークを実装しやすくすることは悪いことだとは思いません。
fork
即時exec
」を必要とする場合、おそらくCreateProcessが候補です。しかし、fork
ないexec
ことが望ましい場合が多く、これはドライバーが実際に求めているものfork
です。
まあ、窓は本当にそのようなものは何もありません。特にフォークは* 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;
}
fork
クラッシュした場合はどうなりますか?プログラムがクラッシュしたり、スレッドがクラッシュしたりしますか?プログラムがクラッシュした場合、これは実際にはフォークではありません。私は本当の解決策を探しているので、好奇心旺盛です。そして、これがまともな代替手段になることを願っています。
Microsoftが新しい「Linuxサブシステムfor Windows」オプションを導入する前CreateProcess()
は、Windowsに最も近いものfork()
が必要でしたが、Windowsでは、そのプロセスで実行する実行可能ファイルを指定する必要があります。
UNIXプロセスの作成は、Windowsとはかなり異なります。そのfork()
呼び出しは、基本的に現在のプロセスをほぼ独自にそれぞれのアドレス空間に複製し、個別に実行し続けます。プロセス自体は異なりますが、同じプログラムを実行しています。モデルの概要については、こちらをご覧くださいfork/exec
。
逆に言えば、Windowsに相当するのはUNIXの関数CreateProcess()
のfork()/exec()
ペアです。
ソフトウェアをWindowsに移植していて、翻訳レイヤーを気にしていない場合、Cygwinは必要な機能を提供していましたが、それはかなり扱いにくいものでした。
もちろん、で新しいLinuxのサブシステム、Windowsがに持っている最も近いものがfork()
あり、実際に fork()
:-)
fork
として、平均的な非WSLアプリケーションで使用できますか?
次のドキュメントは、UNIXからWin32へのコードの移植に関するいくつかの情報を提供します。https: //msdn.microsoft.com/en-us/library/y23kc048.aspx
特に、2つのシステム間でプロセスモデルがかなり異なることを示しており、fork()のような動作が必要なCreateProcessとCreateThreadの検討を推奨しています。
「ファイルアクセスまたはprintfを実行するとすぐに、ioは拒否されます」
msvcrt.dllでは、printf()はそれ自体がlpcを使用してコンソールサブシステム(csrss.exe)と通信するコンソールAPIに基づいています。csrssとの接続は、プロセスの起動時に開始されます。つまり、「中間」で実行を開始するプロセスは、そのステップをスキップします。オペレーティングシステムのソースコードにアクセスできない場合は、csrssに手動で接続しようとしても意味がありません。代わりに、独自のサブシステムを作成して、fork()を使用するアプリケーションのコンソール関数を回避する必要があります。
独自のサブシステムを実装したら、子プロセスの親のハンドルもすべて複製することを忘れないでください;-)
「また、カーネルモードでない限り、おそらくZw *関数を使用しないでください。代わりにNt *関数を使用する必要があります。」
ZwGetContextThread(NtCurrentThread()、&context);
fork()セマンティクスは、fork()が呼び出された時点で子が親の実際のメモリ状態にアクセスする必要がある場合に必要です。私は、fork()が呼び出された時点でメモリコピーの暗黙的なミューテックスに依存するソフトウェアを使用しています。これにより、スレッドを使用できなくなります。(これは、copy-on-write / update-memory-tableセマンティクスにより、最新の* nixプラットフォームでエミュレートされます。)
syscallとしてWindowsに存在する最も近いものはCreateProcessです。実行できる最善の方法は、親がメモリを新しいプロセスのメモリ空間にコピーしている間、親が他のすべてのスレッドを凍結してから、それらを解凍することです。Cygwin frok [sic]クラスも、Eric des Courtisが投稿したScilabコードも、スレッドフリーズを実行していません。
また、カーネルモードでない限り、おそらくZw *関数を使用しないでください。代わりに、Nt *関数を使用する必要があります。カーネルモードになっているかどうかをチェックし、そうでない場合は、Nt *が常に行うすべての境界チェックとパラメーター検証を実行する追加のブランチがあります。したがって、ユーザーモードから呼び出すと、効率が少し低下します。
最適なオプションはCreateProcess()またはCreateThread()です。移植の詳細については、こちらをご覧ください。
最も近いあなたが言う...私に考えさせてください...これはfork()であるに違いないと思います:)
詳細については、Interixはfork()を実装していますか?を参照してください。
他の答えが述べたように、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()を使用しないでください。
サブプロセスを作成してそれを待つだけの場合は、おそらくprocess.hの_spawn * APIで十分です。その詳細は次のとおりです。
https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h