「自動スタック拡張」とは何ですか?


13

getrlimit(2)のマニュアルページには次の定義があります。

RLIMIT_AS プロセスの仮想メモリ(アドレス空間)の最大サイズ(バイト単位)。この制限はbrk(2)、mmap(2)およびmremap(2)の呼び出しに影響し、この制限を超えるとエラーENOMEMで失敗します。また、自動スタック拡張は失敗します(また、sigaltstack(2)で代替スタックが使用可能にされていない場合、プロセスを強制終了するSIGSEGVを生成します)。値は長いため、32ビット長のマシンでは、この制限は最大2 GiBであるか、このリソースは無制限です。

ここで「自動スタック拡張」とはどういう意味ですか?Linux / UNIX環境のスタックは必要に応じて成長しますか?はいの場合、正確なメカニズムは何ですか?

回答:


1

はい、スタックは動的に成長します。スタックは、メモリの最上部にあり、ヒープに向かって下方に成長しています。

--------------
| Stack      |
--------------
| Free memory|
--------------
| Heap       |
--------------
     .
     .

ヒープは(mallocを実行するたびに)上向きに成長し、新しい関数が呼び出されるとスタックが下向きに成長します。ヒープは、プログラムのBSSセクションのすぐ上にあります。つまり、プログラムのサイズとヒープ内のメモリの割り当て方法は、そのプロセスの最大スタックサイズにも影響します。通常、スタックサイズは無制限です(ヒープ領域とスタック領域が満たされるか、上書きされると、スタックオーバーフローとSIGSEGVが発生します:-)

これはユーザープロセス専用です。カーネルスタックは常に固定です(通常8KB)


「通常、スタックサイズは無制限です」、いいえ、通常は8Mb(ulimit -s)に制限されています。
Eddy_Em

はい、あなたはほとんどのシステムで正しいです。シェルのulimitコマンドをチェックしている場合は、スタックサイズに無制限の厳しい制限があります(ulimit -Hs)。とにかく、そのポイントは、スタックとヒープが反対方向に成長することを強調することでした。
サントシュ

それでは、「自動展開」と「スタックへの要素のプッシュ」とはどう違うのでしょうか?あなたの説明から、私はそれらが同じであると感じます。また、スタックとヒープの開始点は8MBを超えているため、スタックは必要なだけ大きくなる(またはヒープに達する)ことがあります。本当?はいの場合、オペレーティングシステムは、ヒープとスタックを配置する場所をどのように決定しましたか?
ラウドアンドクリア

それらは同じですが、rlimitを使用してサイズを厳密に制限しない限り、スタックのサイズは固定されません。スタックは仮想メモリ領域の最後に配置され、ヒープはすぐに実行可能ファイルのデータセグメントになります。
サントシュ

わかりました、ありがとう。ただし、「スタックのサイズが固定されていない」という部分はないと思います。その場合、8Mbのソフト制限は何ですか?
ラウドアンドクリア

8

正確なメカニズムは、Linuxでここに示されています。匿名マッピングでのページフォールトの処理では、スタックのように拡張する必要があるのが「成長した割り当て」であるかどうか確認します。VM領域レコードに必要があると表示されている場合は、開始アドレスを調整してスタックを拡張します。

ページフォールトが発生すると、アドレスに応じて、スタック拡張を介してサービスが提供されます(およびフォールトが打ち消されます)。仮想メモリのこの「フォールトで下向きに成長する」動作はMAP_GROWSDOWNmmapsyscall に渡されるフラグを使用して、任意のユーザープログラムで要求できます。

ユーザープログラムでもこのメカニズムをいじることができます:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

int main() {
        long page_size = sysconf(_SC_PAGE_SIZE);
        void *mem = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_GROWSDOWN|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        if (MAP_FAILED == mem) {
                perror("failed to create growsdown mapping");
                return EXIT_FAILURE;
        }

        volatile char *tos = (char *) mem + page_size;

        int i;
        for (i = 1; i < 10 * page_size; ++i)
                tos[-i] = 42;

        fprintf(stderr, "inspect mappping for originally page-sized %p in /proc... press any key to continue...\n", mem);
        (void) getchar();

        if (munmap(mem, page_size))
                perror("failed munmap");

        return EXIT_SUCCESS;
}

プロンプトが表示されたら、プログラムのPIDを見つけ(を介してps/proc/$THAT_PID/maps、元の領域がどのように成長したかを確認します。


メモリ領域がMAP_GROWSDOWNを介して成長した場合でも、元のmemおよびpage_sizeに対してmunmapを呼び出しても問題ありませんか?それ以外の場合は使用することは非常に複雑なAPIになるので、私は、そう推測しますが、ドキュメントがこの問題について、明示的に何も言っていません
i.petruk

2
MAP_GROWSDOWNは使用すべきではなく、glibcから削除されました(理由についてはlwn.net/Articles/294001を参照してください)。
コリン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.