Threadのスタック

Linuxでpthread_create()をすると、生成されたスレッドにはそれ固有のスタック領域が割り当てられる。具体的にどのように割り当てられるのかを調査。


stack smash 対策を考える1場合、スタックの割り付け方を知っていると多少有益かもしれないので[securiy]カテゴリにも入れてみました。なお、exec-shield などの、address space randomize系パッチとの相互作用は未調査です。調べないとなぁ…。SELinuxで遊ぶ計画も延び延びだ。

NPTLのstack allocation手法


NPTLで、pthrad_create()時にそのスレッド用のスタックがどう割り当てられるか調査した。調査といっても nptl のソースコードを軽く読んだだけですが。


(1) スタックサイズ


nptl/allocatestack.c を見るに、pthread_setstacksize() あるいは pthread_setstack() が呼ばれているならそのサイズ、呼ばれていないなら __default_stacksize となる。__default_stacksize は、nptl/init.c を見るに

  if (getrlimit (RLIMIT_STACK, &limit) != 0
      || limit.rlim_cur == RLIM_INFINITY)
    /* The system limit is not usable.  Use an architecture-specific
       default.  */
    limit.rlim_cur = ARCH_STACK_DEFAULT_SIZE;
#ifdef NEED_SEPARATE_REGISTER_STACK
  __default_stacksize = MAX (limit.rlim_cur / 2, PTHREAD_STACK_MIN);
#else
  __default_stacksize = MAX (limit.rlim_cur, PTHREAD_STACK_MIN);
#endif

です。各スレッドに割り当てられるスタックサイズは、プロセスのスタックサイズのソフトリミットと同じになるわけですね。


ulimit(1)などで、スタックサイズをunlimited(RLIM_INFINITY)にしとくと、強制的に2MB制限になってしまうことに注意が必要ですねぇ。NEED_SEPARATE_REGISTER_STACK については不明。ここでしか使われていない定数だし。

$ grep -r ARCH_STACK_DEFAULT_SIZE ..
../nptl/sysdeps/i386/pthreaddef.h:
 #define ARCH_STACK_DEFAULT_SIZE       (2 * 1024 * 1024)


(2) スタックアドレス


pthread_setstackaddr() あるいは pthread_setstack() が呼ばれているならそのアドレス、呼ばれていないならMAP_ANONYMOUSでmmapされて確保される。明快。

          mem = mmap (NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

確保した領域をキャッシュして再利用してたり、なんの目的なのかmprotect(2)を呼んだりする箇所があるのだけれど、追っていない。

LinuxThreadsのstack allocation手法


NPTLの場合とほぼ一緒でしょう。ソースコードは面倒なんで見ないで想像。

FAQに

By default, each thread reserves 2M of virtual memory space for its stack. This space is just reserved; actual memory is allocated for the stack on demand. But still, on a 32-bit processor, the total virtual memory space available for the stacks is on the order of 1G, meaning that more than 500 threads will have a hard time fitting in. You can overcome this limitation by moving to a 64-bit platform, or by allocating smaller stacks yourself using the setstackaddr attribute.

って書いてあるので、デフォルトのスタックサイズはgetrlimit(2)しないで2MB固定なのかな。2MB固定の問題は、有名な(有名だった?) C10K problemのページでも言及されてますね。あと

The default stack allocation strategy for LinuxThreads is nearly optimal: stacks start small (4k) and automatically grow on demand to a fairly large limit (2M).

って書いてあるけど、単にmalloc(or MAP_ANONYMOUSでmmap)すりゃそうなるよなー。


上記を調べていて気になったのは、いくつかのweb pageで登場する "floating stack" というキーワード。これがenableなglibcだとちょっと前のJVMは動かなかったりいろいろあったとかなかったとか。


この件は、こちらの「スタックオーバーフローのハンドリング」というページに、floating stack について書いてありました。

  • floating stack だとサイズは自由に設定できる
  • そうでない場合は2MB固定

なのですね。上記URI、他にもいろいろと参考になる記述がたくさんあります。感謝。