13

Linux の clone() システム コールは、新しく作成されたスレッドが使用するスタックを指すパラメータを取ります。これを行う明白な方法は、単純にいくつかのスペースを malloc して渡すことですが、そのスレッドがこれまでに使用する (予測するのが難しい) ほど多くのスタックスペースを malloc したことを確認する必要があります。

pthreads を使用するときはこれを行う必要がなかったことを思い出したので、代わりに何をするのか興味がありました。このサイトに出くわしました。「Linux pthreads 実装で使用される最善の解決策は、mmap を使用してメモリを割り当てることです。フラグは、使用時に割り当てられるメモリの領域を指定します。この方法では、メモリが割り当てられます。システムが追加のメモリを割り当てることができない場合、セグメンテーション違反が発生します。」

mmap が使用されているのを聞いたことがある唯一のコンテキストは、ファイルをメモリにマッピングするためのものであり、実際に mmap のマニュアルページを読むにはファイル記述子が必要です。clone() に与える動的な長さのスタックを割り当てるために、これをどのように使用できますか? そのサイトはただのクレイジーですか?;)

どちらの場合でも、カーネルは、ユーザーが新しいプロセスを起動するときに常に実行しなければならないことなので、新しいスタック用の空きメモリの束を見つける方法を知る必要はありませんか? カーネルがすでにこれを把握できるのに、そもそもスタック ポインターを指定する必要があるのはなぜですか?

4

7 に答える 7

7

スタックは、成長のためのスペースが無制限ではありません。他のすべてのものと同様に、それらはプロセスの仮想アドレス空間に存在し、拡大できる量は、隣接するマップされたメモリ領域までの距離によって常に制限されます。

動的に成長するスタックについて人々が話すとき、彼らが意味するのは次の 2 つのいずれかです。

  • スタックのページは、最初の書き込みが実行されるまでプライベート コピーが作成されないコピー オン ライト ゼロ ページである可能性があります。
  • スタック領域の下位部分は、ガード ページがヒットするまで、まだ予約されていない可能性があります (したがって、プロセスのコミット チャージ、つまりカーネルがプロセス用に予約されている物理メモリ/スワップの量にカウントされません)。カーネルがさらにコミットしてガードページを移動するか、コミットするメモリが残っていない場合はプロセスを強制終了します。

MAP_GROWSDOWNフラグに依存しようとするのは信頼性が低く、危険mmapです。スタックのすぐ隣に新しいマッピングを作成することを防ぐことができないためです。( http://lwn.net/Articles/294001/を参照) メインスレッドの場合、カーネルは自動的にスタックサイズulimit分のアドレス空間( memorymmapではありません) をスタックの下に確保し、割り当てを防ぎます。(ただし、注意してください! ベンダーがパッチを適用した一部の壊れたカーネルでは、この動作が無効になり、ランダムなメモリ破損が発生します!) 他のスレッドの場合は、スタックを作成するときにスレッドがスタックに必要とする可能性のあるアドレス空間の全範囲を単純に指定する必要があります。 mmap他に方法はありません。あなたができる最初はほとんどを書き込み不可/読み取り不可にし、障害が発生した場合はそれを変更しますが、シグナルハンドラーが必要になり、アプリケーションのシグナルハンドラーに干渉するため、このソリューションは POSIX スレッドの実装では受け入れられません。(拡張機能として、カーネルは、マッピングへの不正なアクセスの代わりに別のシグナルを配信する特別なフラグを提供できることに注意してください。その後、スレッドの実装は、このシグナルをキャッチして処理することができます。しかし、Linux には現在、そのような機能はありません。 )MAP_SIGSEGV

最後に、clonesyscall はスタック ポインター引数を必要としないため、引数を取らないことに注意してください。syscall はアセンブリ コードから実行する必要があります。これは、ユーザー空間ラッパーが「子」スレッドのスタック ポインターを目的のスタックを指すように変更し、親のスタックに何も書き込まないようにする必要があるためです。

実際にcloneは、ユーザー空間に戻った後に「子」のスタック ポインターを変更するのを待つのは安全ではないため、スタック ポインター引数を取ります。シグナルがすべてブロックされない限り、シグナル ハンドラーが間違ったスタックですぐに実行される可能性があり、一部のアーキテクチャでは、スタック ポインターが有効であり、常に書き込みが安全な領域を指している必要があります。

C からスタック ポインターを変更することは不可能であるだけでなく、syscall の後、スタック ポインターが変更される前に、コンパイラーが親のスタックを上書きする可能性も回避できませんでした。

于 2011-03-20T14:20:27.773 に答える
5

mmap には MAP_ANONYMOUS フラグが必要です。そして、スタックとして使用したいので、MAP_GROWSDOWN。

何かのようなもの:

void *stack = mmap(NULL,initial_stacksize,PROT_WRITE|PROT_READ,MAP_PRIVATE|MAP_GROWSDOWN|MAP_ANONYMOUS,-1,0);

詳細については、mmap の man ページを参照してください。そして、クローンは低レベルの概念であり、それが提供するものが本当に必要でない限り使用することを意図していないことを覚えておいてください. また、独自のスタックを設定するなど、多くの制御を提供します (関連するすべてのプロセスでスタックにアクセスできるようにするなど)。clone を使用する非常に正当な理由がない限り、fork または pthread を使用してください。

于 2009-07-04T23:46:41.083 に答える
2

これは、スタック領域を mmap し、クローン システム コールにこの領域をスタックとして使用するように指示するコードです。

#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <sched.h>

int execute_clone(void *arg)
{
    printf("\nclone function Executed....Sleeping\n");
    fflush(stdout);
    return 0;
}

int main()
{
    void *ptr;
    int rc;
    void *start =(void *) 0x0000010000000000;
    size_t len = 0x0000000000200000;

    ptr = mmap(start, len, PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED|MAP_GROWSDOWN, 0, 0);
    if(ptr == (void *)-1) 
    {
        perror("\nmmap failed");
    }

    rc = clone(&execute_clone, ptr + len, CLONE_VM, NULL);

    if(rc <= 0) 
    {
        perror("\nClone() failed");
    }
}
于 2010-04-21T23:23:16.570 に答える
2

ジョセフ、あなたの最後の質問に答えて:

ユーザーが「通常の」新しいプロセスを作成すると、それは fork() によって行われます。この場合、新しいプロセスはスタックに至るまで古いプロセスの完全な複製であるため、カーネルは新しいスタックの作成についてまったく心配する必要はありません。

ユーザーが exec() を使用して現在実行中のプロセスを置き換える場合、カーネルは新しいスタックを作成する必要がありますが、この場合は白紙の状態から開始できるため簡単です。exec() はプロセスのメモリ空間を一掃して再初期化するため、カーネルは「exec() の後、スタックは常にここにある」と言うようになります。

ただし、clone() を使用すると、新しいプロセスが古いプロセス (CLONE_VM) とメモリ空間を共有すると言えます。この状況では、カーネルは ( fork() がそうするように) 呼び出しプロセスにあったようにスタックを離れることはできません。カーネルは、デフォルトの場所 (exec() のように) に配置することもできません。その場所は、このメモリ空間で既に使用されているためです。唯一の解決策は、呼び出しプロセスがその場所を見つけられるようにすることです。

于 2009-07-09T15:02:01.603 に答える
0

mmap は、ファイルをメモリにマッピングするだけではありません。実際、malloc の実装の中には、大規模な割り当てに mmap を使用するものがあります。詳細なマニュアル ページを読むと、MAP_ANONYMOUS フラグが表示され、ファイル記述子を指定する必要がまったくないことがわかります。

カーネルが「大量の空きメモリを見つける」ことができない理由については、誰かにその作業を任せたい場合は、代わりに fork を使用するか、pthreads を使用してください。

于 2009-07-04T23:43:54.257 に答える
0

スタックは成長できなくなるまで下向きに成長すると思います。たとえば、以前に割り当てられたメモリに成長した場合、障害が通知される可能性があります。冗長なスペースがある場合、デフォルトは使用可能な最小スタック サイズであることがわかります。スタックがいっぱいになると、スタックが下向きに成長する可能性があります。それ以外の場合、システムは障害を通知する場合があります。

于 2012-02-13T07:34:17.757 に答える
0

cloneシステム コールは、スタックの場所の引数を取らないことに注意してください。実際には と同じように機能しforkます。その引数を取るのは、glibc ラッパーだけです。

于 2009-07-06T21:18:01.800 に答える