本からUnix環境の高度なプログラミングUnixファミリーシステムのスレッドについては、以下を読みました。
プロセス内のすべてのスレッドは、同じアドレス空間、ファイル記述子、スタック、およびプロセス関連の属性を共有します。同じメモリにアクセスできるため、不整合を防ぐために、スレッド間で共有データへのアクセスを同期する必要があります。
作家はstacks
ここで何を表現したいですか?私はJavaプログラミングの分野で働いており、各スレッドには独自のスタックがあることを知っています。だから私は混乱しています。共有 stacks
コンセプトはここにあります。
答え1
UnixまたはLinuxプロセスの文脈では、「スタック」という語句は2つのことを意味できます。
まず、「スタック」は、制御フローへの呼び出し順序の後入れ先出しレコードを表すことができます。プロセスが実行されると、main()
まず呼び出されます。main()
に電話できますprintf()
。コンパイラによって生成されたコードは、フォーマット文字列のアドレスと他のパラメータをprintf()
いくつかのメモリ位置に書き込みます。その後、コードはprintf()
完了後に制御フローを返す必要があるアドレスに書き込みます。その後、コードは呼び出しの先頭にジャンプまたは分岐しますprintf()
。各スレッドには、これらの関数アクティブ化レコードスタックの1つがあります。多くのCPUにはスタックを設定および保守するためのハードウェアガイドラインがありますが、他のCPU(IBM 360または他の名前)は実際にはアドレス空間全体に分散できる接続のリストを使用しています。
第二に、「スタック」は、CPUが関数に引数を書き込むメモリ位置と、呼び出された関数が返されるべきアドレスを示します。 「スタック」は、プロセスアドレス空間の連続した部分を表します。
Unix、Linux、または* BSDプロセスのメモリは、約0x400000で始まり、約0x7fffffffffffff(x86_64 CPU)で終わる長い行です。スタックアドレス空間は、最大の数値アドレスから始まります。関数が呼び出されるたびに、関数アクティブ化レコードスタックは「成長」します。プロセスコードは、関数パラメータと戻りアドレスをアクティブなレコードスタックに配置し、プロセスの現在を追跡する特別なCPUレジスタであるスタックポインタを減らします。変数の値はスタックのアドレス空間にあります。
各スレッドには、独自の使用のために「スタック」(スタックアドレス空間)フラグメントが提供され、スタックを関数アクティブとして記録します。 0x7ffffffffffと下位アドレスの間の各スレッドには、関数呼び出し用に予約されたメモリ領域があります。通常、これはハードウェアではなく慣例に従って実施されます。したがって、スレッドがネストされた関数の後に関数を呼び出すと、そのスレッドスタックの一番下が他のスレッドスタックの一番上を上書きする可能性があります。
したがって、各スレッドには「スタック」メモリ領域があり、「共有スタック」という用語が由来しています。これは、プロセスアドレス空間を単一の線形メモリブロックとして使用し、「スタック」という用語を使用した結果である。一部の古いJVM(非常に古い)には、実際には1つのスレッドしかないと確信しています。 Javaコードのすべてのスレッドは、実際には単一の物理スレッドによって実行されます。最新のJVM(Javaスレッドを実行するために実際のスレッドを呼び出すこと)は、前述のものと同じ「共有スタック」を持ちます。 LinuxとPlan 9には、アドレス空間の一部と異なるスタックアドレス空間を共有するプロセスを設定できるプロセス起動システムコール(Linuxではclone()、Plan 9ではrfork())がありますが、このスタイルスレッドは絶対に本当に捕らえられましたええと。
答え2
著者がここでスタックとはどういう意味ですか?私はJavaプログラミングの分野で働いており、各スレッドには独自のスタックがあることを知っています。だから私はここで共有スタックの概念について混乱しています。
複数形を使うのは少し変で誤解を招くようです。おそらく要点は、マルチスレッドプログラムの複数のスタックが同じアドレス空間を共有することです。
Bruce Edigerが説明するように、「スタック」は、データが後入先出し方式で配置される連続アドレス空間の単一領域を表します。しかし、ネイティブスレッドごとプロセスには連続した領域があります。プロセス間に分割された領域はありません。スレッドの作成追加のスタックを割り当てる必要があります。同じサイズ(固定数、たとえばLinuxのデフォルト値は8 MB)のため、多すぎるマルチスレッドアプリケーションはあまりにも多くのメモリを消費します。
Javaは基本スレッドを使用してJavaスレッドを実装しますが、カーネルに関係なく各Javaスレッド自体の「スタック」を管理します。これは、この目的のためにいくつかのグローバルメモリを別々に設定することを意味します。ここで最高の答えの3番目の部分をご覧ください。
https://stackoverflow.com/questions/5483047/why-is-creating-a-thread-said-to-be-expense
もちろん、これは特定の実装(openjdk)を参照しますが、おそらくすべてこれを行う必要があります(内部使用のために「スレッドスタック」としてヒープをローカルに割り当てます)。
各基本スレッドで使用される個々のスタックはカーネルによって管理されるため、これがプロセス全体に属する一部のスタック全体の一部であるという Bruce の暗示に同意しません。同様に、単一(スレッドではない)プロセスには1つのスタックしかありません。しかし、マルチスレッドプロセスのメインスレッド確かに他のスレッドとスタックスペースを共有します。
「共有スタック」とは、すべてのデータが単一のアドレスから開始して保存されるスタックを意味します。これは、同じスタックを共有するネストされた関数呼び出しを意味します。ただし、以下のBruceが述べたサンプルプログラムの適応バージョンをLinuxバージョンで使用しています。pthread_create のマニュアルページマニュアルページ:
./a.out one two three
In main() stack starts near: 0x7fff17b80f98
Thread 1: top of stack near 0x7f11ac6d3e78; argv_string=one
Thread 2: top of stack near 0x7f11abed2e78; argv_string=two
Thread 3: top of stack near 0x7f11ab6d1e78; argv_string=three
プログラムはスレッド関数からローカル変数のアドレスを取得します。私が追加した調整は、main()
スレッドを作成する前に同じことを行うことです。最近の64ビットLinuxシステムで実行している場合、スレッドの開始アドレスは正確に8MB離れており、実際にはデフォルトのスレッドスタックの一番上から離れています。次のようにネストされた関数呼び出しと対比してみてください。
#include <stdio.h>
void eg (int n) {
char *p;
printf("#%d first variable at %p\n", n, &p);
if (n < 3) eg(n+1);
}
int main(int argc, const char *argv[]) {
char *p;
printf("main() first variable at %p\n", &p);
eg(1);
return 0;
}
例を実行してください:
main() first variable at 0x7fffef0aaf68
#1 first variable at 0x7fffef0aaf38
#2 first variable at 0x7fffef0aaf08
#3 first variable at 0x7fffef0aaed8
それらはわずか48バイト離れています。つまり、それらは間に未使用のスペースなしで互いに連続して配置されます(他の少量の実際のデータと共に)。ネストされた再帰なしでマルチスレッドプログラムでこれを使用しますが、eg()
各スレッドで一度呼び出される場合:
Thread 1: top of stack near 0x7f4bd5061e78; argv_string=one
Thread 2: top of stack near 0x7f4bd4860e78; argv_string=two
Thread 3: top of stack near 0x7f4bd405fe78; argv_string=three
#3 first variable at 0x7f4bd405fe48
#1 first variable at 0x7f4bd5061e48
#2 first variable at 0x7f4bd4860e48
各変数は、3つの別々のスタックの上部付近に配置されます。
これらはすべて「スタックスペース」と呼ばれる上位アドレス領域にありますが、マルチスレッドプログラムでは次のように分割されます。マルチスタック;大きなLIFO構造として扱われません。