Linuxでは、スタック割り当てはどのように機能しますか?

Linuxでは、スタック割り当てはどのように機能しますか?

オペレーティングシステムは、スタックまたはその他のアイテム用に固定量の有効仮想スペースを予約しますか?大きなローカル変数を使用するだけでスタックオーバーフローを生成できますか?

私は私の仮説をテストするために小さなCプログラムを書いた。 X86-64 CentOS 6.5で動作します。

#include <string.h>
#include <stdio.h>
int main()
{
    int n = 10240 * 1024;
    char a[n];
    memset(a, 'x', n);
    printf("%x\n%x\n", &a[0], &a[n-1]);
    getchar();
    return 0;
}

プログラムを実行する&a[0] = f0ceabe0&a[n-1] = f16eabdf

proc マップはスタックを示しています。7ffff0cea000-7ffff16ec000. (10248 * 1024B)

それでは増やそうとします。n = 11240 * 1024

プログラムを実行する&a[0] = b6b36690&a[n-1] = b763068f

proc マップはスタックを示しています。7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -sマイコンピュータから印刷してください10240

ご覧のとおり、どちらの場合もスタックサイズがulimit -s指定されたサイズより大きいです。そして、ローカル変数が大きくなるとスタックも大きくなります。スタックの上部は3〜5kBほど少ない&a[0](私が知っている限り、赤い領域は128Bです)。

では、このスタックマップはどのように割り当てられますか?

答え1

スタックメモリ制限が割り当てられていないようです(とにかく無限にスタックすることはできません)。https://www.kernel.org/doc/Documentation/vm/overcommit-accounting説明する:

C言語スタックは暗黙のmremapを介して増加します。絶対的な保証を望んでエッジの近くに実行するには、必要と思われる最大サイズにスタックをマッピングする必要があります。一般的なスタック使用の場合、これは重要ではありませんが、実際に興味がある場合は極端なケースです。

ただし、スタックマッピングはコンパイラのターゲットになります(オプションがある場合)。

編集:x84_64 Debianシステムでいくつかのテストを実行した後(Stack Overflowに従ってstrace)、システムコールなしでスタックが大きくなることがわかりました。したがって、これはカーネルがプロセスで明示的mmapに成長せずに自動的に成長することを意味します(上記では「暗黙的に」という意味)。mremap

これを確認する詳細を見つけるのは難しいです。私はお勧めしますLinux仮想メモリマネージャについてメルゴーマ​​ンの地音。私の考えに対する答えはセクション4.6.1にあります。ページエラー処理、例外は、「地域は無効ですが、拡張可能領域(スタックなど)の横にあります。」とそのアクションは、「領域の拡張とページの割り当て」です。 D.5.2も参照してください。拡張スタック

Linuxメモリ管理に関するその他の参考資料(スタックにはほとんどありません):

編集2:この実装には欠点があります。極端な場合、スタックが制限より大きい場合でも、スタックヒープの衝突が検出されない可能性があります。これは、スタック内の変数への書き込みが割り当てられたヒープメモリで終了する可能性があるためです。この場合、ページエラーは発生せず、カーネルはスタックを拡張する必要があることはわかりません。ディスカッションの私の例を参照してください。GNU/Linuxでの自動スタックの競合私はgcc-helpのリストから始めました。これを防ぐには、コンパイラは関数が呼び出されたときにいくつかのコードを追加する必要があります。これはGCCを介して行うことができます-fstack-check(詳細については、Ian Lance Taylorの回答とGCCのマニュアルページを参照)。

答え2

Linuxカーネル4.2

最小テストプログラム

その後、最小NASM 64ビットプログラムを使用してテストできます。

global _start
_start:
    sub rsp, 0x7FF000
    mov [rsp], rax
    mov rax, 60
    mov rdi, 0
    syscall

ASLRをオフにして環境変数を削除します。これらの変数はスタックに格納され、スペースを占有します。

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
env -i ./main.out

制限は私のものよりわずかに低いulimit -s(私の場合は8MiB)。これは、環境に加えて、最初にスタックに配置された追加のSystem V関連データによるものです。アセンブリ内のLinux 64コマンドライン引数スタックオーバーフロー

これを真剣に受け入れればTODO最小限のinitrdイメージを作成するスタックの一番上から書き始めて書き留めた後QEMU + GDBを使用して実行dprintfループに印刷スタックアドレスを配置し、ブレークポイントを配置しますacct_stack_growth。それは素晴らしいでしょう。

関連:

答え3

デフォルトでは、最大スタックサイズはプロセスあたり8 MBで構成されていますが、
次のコマンドを使用して変更できますulimit

デフォルト値の表示(kB):

$ ulimit -s
8192

無制限に設定:

ulimit -s unlimited

現在のシェル、サブシェル、およびそのサブプロセスに影響します。
ulimitシェル組み込みコマンドです)

次のコマンドを使用すると、使用している物理スタックアドレスの範囲を表示できます。
cat /proc/$PID/maps | grep -F '[stack]'
Linuxの場合。

関連情報