スタックは、成長のためのスペースが無制限ではありません。他のすべてのものと同様に、それらはプロセスの仮想アドレス空間に存在し、拡大できる量は、隣接するマップされたメモリ領域までの距離によって常に制限されます。
動的に成長するスタックについて人々が話すとき、彼らが意味するのは次の 2 つのいずれかです。
- スタックのページは、最初の書き込みが実行されるまでプライベート コピーが作成されないコピー オン ライト ゼロ ページである可能性があります。
- スタック領域の下位部分は、ガード ページがヒットするまで、まだ予約されていない可能性があります (したがって、プロセスのコミット チャージ、つまりカーネルがプロセス用に予約されている物理メモリ/スワップの量にカウントされません)。カーネルがさらにコミットしてガードページを移動するか、コミットするメモリが残っていない場合はプロセスを強制終了します。
MAP_GROWSDOWN
フラグに依存しようとするのは信頼性が低く、危険mmap
です。スタックのすぐ隣に新しいマッピングを作成することを防ぐことができないためです。( http://lwn.net/Articles/294001/を参照) メインスレッドの場合、カーネルは自動的にスタックサイズulimit
分のアドレス空間( memorymmap
ではありません) をスタックの下に確保し、割り当てを防ぎます。(ただし、注意してください! ベンダーがパッチを適用した一部の壊れたカーネルでは、この動作が無効になり、ランダムなメモリ破損が発生します!) 他のスレッドの場合は、スタックを作成するときにスレッドがスタックに必要とする可能性のあるアドレス空間の全範囲を単純に指定する必要があります。 mmap
他に方法はありません。あなたができる最初はほとんどを書き込み不可/読み取り不可にし、障害が発生した場合はそれを変更しますが、シグナルハンドラーが必要になり、アプリケーションのシグナルハンドラーに干渉するため、このソリューションは POSIX スレッドの実装では受け入れられません。(拡張機能として、カーネルは、マッピングへの不正なアクセスの代わりに別のシグナルを配信する特別なフラグを提供できることに注意してください。その後、スレッドの実装は、このシグナルをキャッチして処理することができます。しかし、Linux には現在、そのような機能はありません。 )MAP_
SIGSEGV
最後に、clone
syscall はスタック ポインター引数を必要としないため、引数を取らないことに注意してください。syscall はアセンブリ コードから実行する必要があります。これは、ユーザー空間ラッパーが「子」スレッドのスタック ポインターを目的のスタックを指すように変更し、親のスタックに何も書き込まないようにする必要があるためです。
実際にclone
は、ユーザー空間に戻った後に「子」のスタック ポインターを変更するのを待つのは安全ではないため、スタック ポインター引数を取ります。シグナルがすべてブロックされない限り、シグナル ハンドラーが間違ったスタックですぐに実行される可能性があり、一部のアーキテクチャでは、スタック ポインターが有効であり、常に書き込みが安全な領域を指している必要があります。
C からスタック ポインターを変更することは不可能であるだけでなく、syscall の後、スタック ポインターが変更される前に、コンパイラーが親のスタックを上書きする可能性も回避できませんでした。