マイクロプロセッサ試験の準備をしています。プログラムカウンタの使用が次の命令のアドレスを保持することである場合、スタックポインタの使用は何ですか?
9 に答える
スタッ現在の関数)。
これには以下が含まれますが、これらに限定されません。
- 返品先住所。
- 戻り値の場所。
- 渡されたパラメーター。
- ローカル変数。
アイテムをスタックにプッシュしてポップします。マイクロプロセッサでは、スタックはユーザー データ (ローカル変数や渡されたパラメーターなど)とCPU データ (サブルーチン呼び出し時の戻りアドレスなど) の両方に使用できます。
スタックの実際の実装は、マイクロプロセッサのアーキテクチャによって異なります。メモリ内で拡大または縮小でき、プッシュ/ポップ操作の前または後に移動できます。
通常、スタックに影響を与える操作は次のとおりです。
- サブルーチンの呼び出しと戻り。
- コールとリターンを中断します。
- エントリを明示的にプッシュおよびポップするコード。
- SP レジスタの直接操作。
私の(架空の)アセンブリ言語で次のプログラムを検討してください。
Addr Opcodes Instructions ; Comments
---- -------- -------------- ----------
; 1: pc<-0000, sp<-8000
0000 01 00 07 load r0,7 ; 2: pc<-0003, r0<-7
0003 02 00 push r0 ; 3: pc<-0005, sp<-7ffe, (sp:7ffe)<-0007
0005 03 00 00 call 000b ; 4: pc<-000b, sp<-7ffc, (sp:7ffc)<-0008
0008 04 00 pop r0 ; 7: pc<-000a, r0<-(sp:7ffe[0007]), sp<-8000
000a 05 halt ; 8: pc<-000a
000b 06 01 02 load r1,[sp+2] ; 5: pc<-000e, r1<-(sp+2:7ffe[0007])
000e 07 ret ; 6: pc<-(sp:7ffc[0008]), sp<-7ffe
それでは、上記のコメントに示されている手順を説明しながら、実行を追跡しましょう。
- これは、プログラム カウンターが 0 で、スタック ポインターが 8000 (これらの数値はすべて 16 進数) である開始状態です。
- これは単純にレジスタ r0 に即時値 7 をロードし、次のステップに移動します (特に指定がない限り、デフォルトの動作は次のステップに移動することを理解していると仮定します)。
- これにより、スタック ポインターが 2 だけ減らされ、r0 がスタックにプッシュされ、レジスタの値がその場所に格納されます。
- これはサブルーチンを呼び出します。プログラム カウンターは、前のステップの r0と同様の方法でスタックにプッシュされ、プログラム カウンターは新しい値に設定されます。これは、システム レベルの処理として行われているという点を除けば、ユーザー レベルのプッシュと何ら変わりはありません。
- これは、スタック ポインタから計算されたメモリ位置から r1 をロードします。関数にパラメータを渡す方法を示しています。
- return ステートメントは、スタック ポインターが指す場所から値を抽出し、それをプログラム カウンターにロードし、同時にスタック ポインターを調整します。これは、システム レベルの pop のようなものです (次の手順を参照)。
- スタックから r0 を取り出すには、スタック ポインターが指す場所から値を抽出し、そのスタック ポインターを上に調整する必要があります。
- Halt 命令は、単にプログラム カウンターをその場所に残すだけで、ある種の無限ループになります。
うまくいけば、その説明から、それが明らかになるでしょう。要するに、スタックは LIFO 方式で状態を保存するのに便利であり、これは通常、ほとんどのマイクロプロセッサがサブルーチン呼び出しを行う方法に理想的です。
もちろん、あなたがSPARCでない限り、その場合はスタックに循環バッファを使用します :-)
更新:上記の例で値をプッシュおよびポップするときに実行される手順を明確にするために(明示的または呼び出し/リターンによる)、次の例を参照してください。
LOAD R0,7
PUSH R0
Adjust sp Store val
sp-> +--------+ +--------+ +--------+
| xxxx | sp->| xxxx | sp->| 0007 |
| | | | | |
| | | | | |
| | | | | |
+--------+ +--------+ +--------+
POP R0
Get value Adjust sp
+--------+ +--------+ sp->+--------+
sp-> | 0007 | sp->| 0007 | | 0007 |
| | | | | |
| | | | | |
| | | | | |
+--------+ +--------+ +--------+
スタック ポインターは、スタックにプッシュされた最新のエントリのアドレスを格納します。
値をスタックにプッシュするには、スタック ポインターをインクリメントして次の物理メモリ アドレスを指すようにし、新しい値をメモリ内のそのアドレスにコピーします。
スタックから値をポップするには、スタック ポインターのアドレスから値をコピーし、スタック ポインターをデクリメントして、スタック内の次の使用可能なアイテムをポイントします。
ハードウェア スタックの最も一般的な用途は、サブルーチン呼び出しの戻りアドレスを格納することです。サブルーチンの実行が終了すると、戻りアドレスがスタックの一番上からポップされてプログラム カウンター レジスタに配置され、サブルーチンの呼び出しに続く次の命令でプロセッサが実行を再開します。
http://en.wikipedia.org/wiki/Stack_%28data_structure%29#Hardware_stacks
[試験の] 準備をする必要があります ;-)
スタック ポインタは、スタック上で次に利用可能なスポットのアドレスを保持するレジスタです。
スタックは、スタックを格納するために予約されているメモリ内の領域です。これは、LIFO (Last In First Out) タイプのコンテナーであり、ローカル変数と戻りアドレスを格納し、関数呼び出しのネストを簡単に管理できるようにします。典型的なプログラム。
スタック管理の基本的な説明については、このウィキペディアの記事を参照してください。
8085 の場合: スタック ポインタは、マイクロプロセッサ内の特別な目的の 16 ビット レジスタであり、スタックの最上部のアドレスを保持します。
コンピューターのスタック ポインター レジスターは、割り込みハンドラーよりも低い特権レベルで実行されるプログラムによって、汎用目的で使用できるようになります。このようなプログラムの命令セットは、スタック操作を除き、オペランドなどのスタックポインタ以外のデータをスタックポインタレジスタに格納する。割り込みで割り込みハンドラに実行を切り替えると、現在実行中のプログラムの戻りアドレス データが、割り込みハンドラの特権レベルでスタックにプッシュされます。したがって、スタック ポインタ レジスタに他のデータを格納しても、スタックが破損することはありません。また、これらの命令は、現在のスタック ポインターを超えたスタック セグメントのスクラッチ部分にデータを格納できます。
詳細については、これをお読みください。
スタックは、一時データを保持するためのメモリ領域です。スタックは、プロシージャの戻りアドレスを保持するために CALL 命令によって使用されます。戻り RET 命令は、スタックからこの値を取得し、そのオフセットに戻ります。INT 命令が割り込みを呼び出すときも同じことが起こります。フラグ レジスタ、コード セグメント、およびオフセットをスタックに格納します。IRET 命令は、割り込み呼び出しからの復帰に使用されます。
スタックは後入れ先出し (LIFO) メモリです。データは PUSH 命令でスタックに配置され、POP 命令で削除されます。スタック メモリは、スタック ポインタ (SP) とスタック セグメント (SS) レジスタの 2 つのレジスタによって維持されます。データのワードがスタックにプッシュされると、上位 8 ビット バイトがロケーション SP-1 に配置され、下位 8 ビット バイトがロケーション SP-2 に配置されます。次に、SP が 2 減分されます。SP は (SS x 10H) レジスタに加算され、物理スタック メモリ アドレスが形成されます。データがスタックからポップされると、逆のシーケンスが発生します。データのワードがスタックからポップされると、上位 8 ビット バイトが場所 SP-1 で取得され、下位 8 ビット バイトが場所 SP-2 で取得されます。その後、SP が 2 増加します。
スタック ポインタは、スタックの先頭へのアドレスを保持します。スタックを使用すると、関数はスタックに格納された引数を相互に渡したり、スコープ付き変数を作成したりできます。このコンテキストでのスコープとは、スタック フレームがなくなるとき、および/または関数が戻るときに、変数がスタックからポップされることを意味します。スタックがなければ、すべてに対して明示的なメモリ アドレスを使用する必要があります。そうなると、そのアーキテクチャー用の高水準プログラミング言語を設計することは不可能 (または少なくとも非常に困難) になります。また、通常、各 CPU モードには独自のバンク スタック ポインターがあります。そのため、例外が発生した場合 (割り込みなど)、例外ハンドラー ルーチンは、ユーザー プロセスを破壊することなく独自のスタックを使用できます。
より深い理解が必要な場合は、イントロとしてPatterson and Hennessyを、中級から上級のテキストとしてHennessy and Pattersonを心からお勧めします。それらは高価ですが、本当に並外れたものです。私が修士号を取得し、チップ、システム、およびそれらのシステム ソフトウェアの一部を設計する労働力になったときに、どちらかまたは両方が利用可能であったことを願っています (しかし、残念ながら、それはかなり前のことです;-)。スタック ポインタは非常に重要です (そして、マイクロプロセッサと他の種類の CPU との区別は、このコンテキストでは非常に意味があります... または、さらに言えば、過去数十年間の他のコンテキストでも...!-)ゼロからの完全な復習が役立つことは間違いありません!-)
一部の CPU では、スタック専用のレジスタ セットがあります。呼び出し命令が実行されると、1 つのレジスターにプログラム カウンターがロードされ、同時に 2 つ目のレジスターに最初の内容がロードされ、3 つ目のレジスターに 2 つ目の内容がロードされ、4 つ目のレジスターに 3 つ目の内容がロードされます。リターン命令が実行されると、プログラム カウンタは最初のスタック レジスタの内容でラッチされ、同時にそのレジスタが 2 番目のスタック レジスタからラッチされます。2 番目のレジスタは 3 番目のレジスタからロードされるなどです。このようなハードウェア スタックはかなり小さい傾向があることに注意してください (たとえば、小型の PIC シリーズ マイクロプロセッサの多くは 2 レベルのスタックを持っています)。
ハードウェア スタックにはいくつかの利点がありますが (たとえば、プッシュとポップは呼び出し/リターンに時間を追加しません)、2 つのソースでロードできるレジスタを使用すると、コストが追加されます。スタックが非常に大きくなる場合は、プッシュプル レジスタをアドレス指定可能なメモリに置き換える方が安くなります。これに小さな専用メモリを使用したとしても、それぞれ 2 つの入力を持つ 32 個のレジスタを使用するよりも、32 個のアドレス指定可能なレジスタとインクリメント/デクリメント ロジックを備えた 5 ビットのポインタ レジスタを使用する方が安価です。アプリケーションが CPU に簡単に収まるよりも多くのスタックを必要とする可能性がある場合は、ロジックと共にスタック ポインターを使用して、メイン RAM からスタック データを格納/フェッチすることができます。