最初からオペレーティングシステム。 レベル3(後半)







このラボでは、カスタムプログラムを実行する機能を実装します。 つまり プロセスおよびすべての依存インフラストラクチャ。 最初に、特権コードから切り替える方法、プロセスコンテキストを切り替える方法を理解します。 次に、単純なラウンドロビンスケジューラ、システムコール、および仮想メモリ管理を実装します。 最後に、シェルをカーネル空間からユーザー空間に移動します。







オリジナル







ゼロラボ







最初のラボ: 若い半分古い半分







2番目のラボ: 若い半分古い半分







有用性





フェーズ0:はじめに



前の部分と同様に、保証された作業には以下が必要です。









コード検索



カブ3-spawn



では質問以外の何物もありませんが、誰も邪魔しません:







 git clone https://web.stanford.edu/class/cs140e/assignments/3-spawn/skeleton.git 3-spawn
      
      





その後、役に立たないため、ディレクトリ構造は次のようになります。







 cs140e ├── 0-blinky ├── 1-shell ├── 2-fs ├── 3-spawn └── os
      
      





しかし、 os



rep内では、 3-spawn



ブランチに切り替える必要があり3-spawn









 cd os git fetch git checkout 3-spawn git merge 2-fs
      
      





ほとんどの場合、再びマージの競合が発生します。 このようなもの:







 Auto-merging kernel/src/kmain.rs CONFLICT (content): Merge conflict in kernel/src/kmain.rs Automatic merge failed; fix conflicts and then commit the result.
      
      





マージの競合は、 kmain.rs



ファイルを変更して手動で解決する必要があります。 この場合、Lab 2からすべての変更を保存したことを確認する必要があります。競合を解決したら、 git add



ファイルを追加して、すべてをコミットします。 このトピックに関する詳細情報を取得するには、 githowto.comチュートリアルを参照してください







ARMドキュメント



この課題では、3つの公式のARMドキュメントを常に参照します。 これら3つは:







  1. ARMv8リファレンスマニュアル

    これは、ARMv8アーキテクチャの公式リファレンスガイドです。 アーキテクチャ全体を網羅したワンストップガイド。 ラズベリーのプロセスでこのアーキテクチャを具体的に実装するには、マニュアル2が必要です。 この大きなARMv8マニュアルのセクションは、次の形式の注釈を使用して参照します( ref :C5.2)。 この場合、これは、セクションC5.2のARMv8リファレンスマニュアル参照する必要があることを意味します。
  2. ARM Cortex-A53マニュアル

    これは、ロビンで使用されるARMv8(v8.0-A)の非常に具体的な実装のためのマニュアルです。 このマニュアルでは、フォームの注記( A53 :4.3.30)を参照します。
  3. ARMv8-Aプログラマガイド

    これで、ARMv8-Aをプログラミングするためのかなり高度なマニュアルができました。 フォームのメモでそれを参照します( ガイド :10.1)


これらのマニュアルをディスクにダウンロードすることを強くお勧めします。 そのため、毎回簡単に開くことができます。 特に最初のものは非常に大きいためです。 そういえば。







どうやって読むの? 全体を読む必要はありません。 したがって、初心者にとっては、このマニュアルで何を見つけたいかを知ることが非常に重要です。 このマニュアルは、使いやすい構造になっています。 いくつかの部分に分かれています。 AArch64に興味があり、あまり深く潜ることには興味がありません(プロセッサメーカーではありません)。 ですから、私たちはこの言葉の多くの章に全く興味がありません。 実際、パートA、B、およびCとDの一部の情報で十分ですが、最初の2つのパートでは、アーキテクチャ、特にAArch64に関する一般的な概念を説明します。 パートCでは一連の指示について説明します。 この部分は、最も基本的な命令とレジスタのリファレンスとして使用します(たとえば、SIMDは今は興味がありません)。 パートDでは、AArch64の詳細の一部について説明します。 特に、割り込みとそのすべてについて。







フェーズ1:ARMとレッグ(アームとレッグ)



このフェーズでは、ARMv8アーキテクチャを検討し、特権レベルを下げて、プロセッサ例外ベクトルを構成し、タイマー割り込みとブレークポイント割り込みを処理します。 ARMアーキテクチャの例外レベルを調べてみましょう。 私たちは、これらのまさに例外と中断をキャッチする方法に主に興味があります。







サブフェーズA:ARMv8レビュー



このサブフェーズでは、ARMv8のアーキテクチャを検討します。 ここではコードを記述しませんが、セルフテストには質問があります。







ARM(Acron RISC Machine)は、30年以上の歴史を持つマイクロプロセッサアーキテクチャです。 現在、このアーキテクチャには8つのバージョンがあります。 最新のARMv8は2011年に導入されました。 BroadcomのBCM2837チップには、ARMv8.0ベースのコアであるARM Cortex-A53コアが含まれています。 Cortex-A53(など)はアーキテクチャの実装です 。 そして、これがこのすべての部分で学習する実装です。







ARMマイクロプロセッサがモバイル市場を支配しています。



ARMは、世界のスマートフォン市場の約95%、主力スマートフォンの100%です。 Apple iPhoneまたはGoogle Pixelを含む。

これまでのところ、プロセッサアーキテクチャを回避しようとしています。 Rustがすべてを行ってくれました。 ユーザー空間でプロセスを運用するには、ある程度の作業を低レベルで行う必要があります。 プロセスのプログラミングには、このアーキテクチャのアセンブラーとそれに関連するすべての関連概念に精通する必要があります。 アーキテクチャのレビューから始め、最も基本的なアセンブリ手順を扱います。







登録



ARMv8アーキテクチャには、次のレジスタがあります( ref :D1.2.1):









さらに多くの特別な目的のレジスタがあります。 それらについては少し後で説明します。







プステート



任意の時点で、パーセントARMv8を使用すると、PSTATEという名前の擬似レジスター( ref :D1.7)を介してプログラムの状態にアクセスできます。 これは通常のレジスタではありません。 直接読み書きすることはできません。 代わりに、PSTATE擬似レジスタの一部を操作するために使用できるいくつかの専用レジスタがあります。 ARMv8.0では、これは次のとおりです。









このようなレジスタは、システムまたは特殊レジスタのクラスに属します( 参照 :C5.2)。 通常のレジスタは、 ldr



を使用してRAMからldr



か、 str



を使用してメモリに書き込むことができstr



。 システムレジスタをそのように使用することはできません。 代わりに、特別なコマンドmsr



およびmsr



ref :C6.2.162-C6.2.164)が必要です。 たとえば、 x1



NZCV



を読み取るには、次のレコードを使用する必要があります。







 mrs x1, NZCV
      
      





実行状況



常に、ARMv8パーセントが特定の実行状態で実行されます。 合計で、このような状態は正確に2つあります。 AArch32-32ビットARMv7との互換モード。 そして、AArch64-64ビットARMv8モード( ガイド :3.1)。 AArch64でのみ動作します。







セーフモード



ある時点で、特定のセキュリティ状態でパーセントが実行されます(ガイド:3)。 このガベージは、セキュリティモードまたはセキュリティワールドでも検索できます。 セキュア セキュアの 2つの状態のみ。 つまり 安全かつ正常。 完全に通常モードで動作します。







例外レベル



これに加えて、例外レベルもあります( ガイド :3)。 各例外レベルは、特定の特権レベルに対応しています。 例外レベルが高いほど、そのレベルで実行されているプログラムが受け取る特権が多くなります。 合計で4つのレベルがあります。









Raspberry PiプロセッサがEL3で起動します。 この時点で、Raspberry Pi Foundationが提供するファームウェアが起動します。 ファームウェアはプロセッサーをEL2に切り替え、kernel8.img kernel8.img



を起動しkernel8.img



。 したがって、カーネルはEL2レベルから始まります。 少し後、EL2からEL1に切り替えて、カーネルが適切なレベルの例外で動作するようにします。







ELxレジスタ



ELR



SPSR



SP



などの多くのシステムレジスタは、例外レベルごとに複製されます。 同時に、接尾辞_ELn



が名前に付けられますn



は、このレジスタが属する例外のレベルです。 たとえば、 ELR_EL1



はEL1レベルの例外参照レジスタであり、 ELR_EL2



は同じですが、EL2レベルです。







例外x



ターゲットレベルからレジスタを参照する必要がある場合は、サフィックスx



(たとえばELR_ELx



)を使用します。 ターゲット例外レベルは、例外ベクトルが開始されたときにCPUが(必要に応じて)切り替える例外レベルです。







例外s



初期レベルでレジスタを参照する必要がある場合、たとえばSP_ELs



で接尾辞s



を使用します。例外s



初期レベルは、例外が発生する前にCPUが実行された例外のレベルです。







例外レベルを切り替える



除外レベルを上げるメカニズムは1つ、除外レベルを下げるメカニズムは1つです。







上位レベルから下位レベルに切り替えるには(特権の削減)、実行中のプログラムは、 eret



ref :D1.11)を使用してこのレベルの例外から復帰する必要があります。 ELx



レベルのプロセッサでeret



を実行する場合:









レジスタSPSR_ELx



ref :C5.2.18)は、とりわけ、あなたが行かなければならない例外のレベルを含んでいます。 さらに、例外レベルを変更すると、次の追加の結果に注意する価値があります。









下位レベルから上位レベルへの移行は、除外の結果としてのみ発生します( ガイド :10)。 特に設定しない限り、パーセントは次のレベルの例外をキャッチします。 たとえば、EL0での動作中に割り込みを受信した場合、パーセントは例外を処理するためにEL1に切り替わります。 ELx



パーセントに切り替えると、次のことが行われます。









例外シンドロームレジスタは、 同期例外に対してのみ有効であることに注意してください。 すべての汎用レジスタとSIMD / FPレジスタには、例外が発生したときに持っていた値が含まれます。







例外ベクトル



例外が発生すると、CPUは例外ベクトルがある場所に制御を移します( ref :D1.10.2)。 例外には4つのタイプがあり、それぞれに4つの例外のソースが含まれています。 つまり 合計16の例外ベクトル。 以下に4つのタイプの例外を示します。









以下に、割り込みの4つのソースを示します。









マニュアルの説明から( ガイド :10.4):







例外が発生すると、プロセッサは例外に一致するハンドラコードを実行する必要があります。 [例外]ハンドラーが格納されるメモリ内の場所は、例外ベクトルと呼ばれます。 ARMアーキテクチャでは、例外ベクトルは例外ベクトルテーブルと呼ばれるテーブルに格納されます。 各例外レベルには、EL3、EL2、およびEL1ごとに独自のベクターテーブルがあります。 テーブルには、[x86のように]アドレスのセットではなく、実行のための命令が含まれています。 ベクターテーブルの各エントリのサイズは16命令です。 個々の例外のベクトルは、テーブルの先頭からの固定オフセットで配置されます。 各テーブルの仮想アドレスは、[特別]ベクトルアドレスレジスタVBAR_EL3



VBAR_EL2



、およびVBAR_EL1



ます。

これらのベクトルは、次のようにメモリ内に物理的に配置されます。







SP = SP_EL0



現在の例外レベル







VBAR_ELx



からのオフセット
例外
0x000 同期例外
0x080 IRQ
0x100 FIQ
0x180 セロロール


SP = SP_ELx



現在の例外レベル







VBAR_ELx



からのオフセット
例外
0x200 同期例外
0x280 IRQ
0x300 FIQ
0x380 セロロール


AArch64が実行される低い例外レベル







VBAR_ELx



からのオフセット
例外
0x400 同期例外
0x480 IRQ
0x500 FIQ
0x580 セロロール


AArch32が実行される低い例外レベル







VBAR_ELx



からのオフセット
例外
0x600 同期例外
0x680 IRQ
0x700 FIQ
0x780 セロロール


まとめ



今のところ、ARMv8アーキテクチャについて知る必要があるのはこれだけです。 続行する前に、これらの質問に答えてみてください。 自己テスト用。







x30



エイリアスとは何ですか?
[arm-x30]



0xFFFF



x30



レジスタに書き込むと、この値を抽出するためにこのレジスタの他の2つの名前を使用できますか?



PC値を特定のアドレスに変更するにはどうすればよいですか? [arm-pc]



ret



ステートメントを使用してアドレスA



PCを設定するにはどうすればよいですか? eret



命令を使用してPCをアドレスA



に設定する方法は? これを達成するために変更するレジスタを指定します。



例外の現在のレベルを確認するにはどうすればよいですか? [arm-el]



現在の除外レベルを判断するには、どのような具体的な指示に従いますか?



スタックポインターをどのように変更して例外をスローしますか? [arm-sp-el]



実行中のプログラムのスタックポインターは、例外が発生した時点でA



です。 例外を処理した後、プログラムが実行されていた場所に戻りたいが、スタックポインターをB



に変更したい どうやってやるの?



低いELからのシステムコールに使用されるベクトルはどれですか? [arm-svc]



ユーザープロセスはEL0で実行されます。 このプロセスはsvc



呼び出します。 経営陣はどの住所に移転されますか?



下位ELからの割り込みに使用されるベクトルはどれですか? [arm-int]



ユーザープロセスはEL0で実行されます。 この時点で、タイマー割り込みが発生します。 経営陣はどの住所に移転されますか?



IRQ例外処理を有効にするにはどうすればよいですか? [アームマスク]



IRQ割り込みのロックを解除するには、どのレジスタにどの値を書き込む必要がありますか?



eret



を使用してAArch32モードを有効にするにはどうしますか?
[arm-aarch32]



例外ソースはAArch64です。 この例外のハンドラーもAArch64にあります。 どのレジスタのどの値を変更すると、 eret



を介して例外から戻るときeret



パーセントが実行モードAArch32に切り替わりますか?

ヒント :ウォッチ( ガイド :10.1)


サブフェーズB:アセンブラー命令





このサブフェーズでは、ARMv8コマンドセットから最も基本的なコマンドを学習します。 今はコードを書きませんが、セルフテストにはいくつかの質問があります。







メモリアクセス



ARMv8は、RISC(命令セットが削減されたコンピューター)をロード/保存するための命令セットです。 この一連の命令の特徴は、明確に定義された命令によってのみメモリアクセスが実現できるという小さな事実です。 特に、メモリは、読み込み命令でレジスタに読み込むことによってのみ読み込むことができ、保存命令によってのみ書き込むことができます。







さまざまなバリエーションのロード/アンロード(ロード/ストア)のための多くの指示があります(ほとんどの場合、同じタイプです)。 最も単純なフォームから始めましょう。









レジスタ<rb>



ベースレジスタと呼ばます。 たとえば、 r3 = 0x1234



場合:







 ldr r0, [r3] // r0 = *r3 ( , r0 = *(0x1234)) str r0, [r3] // *r3 = r0 ( , *(0x1234) = r0)
      
      





さらに、ギャップ[-256, 255]



からオフセットを追加できます。







 ldr r0, [r3, #64] // r0 = *(r3 + 64) str r0, [r3, #-12] // *(r3 - 12) = r0
      
      





ロードまたは保存を適用した 、基本ケースの値を変更するポストインデックスを指定することもできます。







 ldr r0, [r3], #30 // r0 = *r3; r3 += 30 str r0, [r3], #-12 // *r3 = r0; r3 -= 12
      
      





または、ロードまたは保存を適用する前にベースレジスタの値を変更する事前インデックス:







 ldr r0, [r3, #30]! // r3 += 30; r0 = *r3 str r0, [r3, #-12]! // r3 -= 12; *r3 = r0
      
      





オフセット、ポストインデックス、プリインデックスは、 アドレッシングモードとして知られています







さらに、2つのレジスタを一度にロード/アンロードできるチームもあります。 命令ldp



およびstp



ldp



、ストアペア)。 これらの命令は、 ldr



およびstr



と同じアドレス指定モードで使用できstr









 //  `x0`  `x1`  .     : // // |------| <x ( SP) // | x1 | // |------| // | x0 | // |------| <- SP // stp x0, x1, [SP, #-16]! //  `x0`  `x1`  .     : // // |------| <- SP // | x1 | // |------| // | x0 | // |------| <x (original SP) // ldp x0, x1, [SP], #16 //       ,     sub SP, SP, #16 stp x0, x1, [SP] ldp x0, x1, [SP] add SP, SP, #16 //   ,      x0, x1, x2,  x3. sub SP, SP, #32 stp x0, x1, [SP] stp x2, x3, [SP, #16] ldp x0, x1, [SP] ldp x2, x3, [SP, #16] add SP, SP, #32
      
      





値の直接読み込み



即値は、値が計算なしで既知の整数の別の名前です。 (たとえば)16ビットのイミディエイトをレジスタにロードし、オプションで特定のビット数を左にシフトするには、 mov



(移動)コマンドが必要です。 シフトで同じ16ビットをロードするために、残りのビットを置き換えることなく、 movk



(move / keep)が必要movk



。 これをすべて使用する例を次に示します。







 mov x0, #0xABCD, LSL #32 // x0 = 0xABCD00000000 mov x0, #0x1234, LSL #16 // x0 = 0x12340000 mov x1, #0xBEEF // x1 = 0xBEEF movk x1, #0xDEAD, LSL #16 // x1 = 0xDEADBEEF movk x1, #0xF00D, LSL #32 // x1 = 0xF00DDEADBEEF movk x1, #0xFEED, LSL #48 // x1 = 0xFEEDF00DDEADBEEF
      
      





ロードされた値自体には接頭辞#



が付いていることに注意してください。 同時にLSL



は左へのシフトを意味します。







オプションのオフセットを持つ16ビットのみをレジスタにロードできます。 ところで、多くの場合、アセンブラは必要なシフト自体を決定できます。 mov x12, #(1 << 21)



mov x12, 0x20, LSL #16



.









<label>:



:







 add_30: add x1, x1, #10 add x1, x1, #20
      
      





, , adr



ldr



:







 adr x0, add_30 // x0 =     add_30 ldr x0, =add_30 // x0 =     add_30
      
      





ldr



. adr



.









, , mov



:







 mov x13, #23 // x13 = 23 mov sp, x13 // sp = 23, x13 = 23
      
      







ELR_EL1



/ mrs



msr



.







, - msr



:







 msr ELR_EL1, x1 // ELR_EL1 = x1
      
      





- mrs



:







 mrs x0, CurrentEL // x0 = CurrentEL
      
      







add



sub



:







 add <dest> <a> <b> // dest = a + b sub <dest> <a> <b> // dest = a - b
      
      





例:







 mov x2, #24 mov x3, #36 add x1, x2, x3 // x1 = 24 + 36 = 60 sub x4, x3, x2 // x4 = 36 - 24 = 12
      
      





<b>



:







 sub sp, sp, #120 // sp -= 120 add x3, x1, #120 // x3 = x1 + 120 add x3, x3, #88 // x3 += 88
      
      







and



orr



AND



OR



. add



sub



:







 mov x1, 0b11001 mov x2, 0b10101 and x3, x1, x2 // x3 = x1 & x2 = 0b10001 orr x3, x1, x2 // x3 = x1 | x2 = 0b11101 orr x1, x1, x2 // x1 |= x2 and x2, x2, x1 // x2 &= x1 and x1, x1, #0b110 // x1 &= 0b110 orr x1, x1, #0b101 // x1 |= 0b101
      
      







(Branching) — . PC . , b



:







 b label // jump to label
      
      





( lr



), bl



. ret



lr



:







 my_function: add x0, x0, x1 ret mov x0, #4 mov x1, #30 bl my_function // lr =   `mov x3, x0` mov x3, x0 // x3 = x0 = 4 + 30 = 34
      
      





br



blr



b



bl



, , :







 ldr x0, =label blr x0 //  bl label br x0 //  b label
      
      







cmp



. , bne



(branch not equal), beq



(branch if equal), blt



(branch if less than) .. ( ref : C1.2.4)







 //  1  x0   ,      x1, //   `function_when_eq`,   not_equal: add x0, x0, #1 cmp x0, x1 bne not_equal bl function_when_eq exit: ... //   x0 == x1 function_when_eq: ret
      
      





:







 cmp x1, #0 beq x1_is_eq_to_zero
      
      





: , .









ARMv8 . , . ( ref : C1.2.4). . ISA- Griffin Dietz. , :







memcpy



ARMv8?
[arm-memcpy]



, x0



, , x1



, x2



( 8 ). memcpy



? , ret





: 6-7 .



0xABCDE



ELR_EL1



?
[arm-movk]



, EL1



, 0xABCDE



ELR_EL1



ARMv8?

: .



cbz



?
[arm-cbz]



cbz



( ref : C6.2.36). ? ?



init.S



?
[asm-init]



os/kernel/ext/init.S



— , . _start



0x80000



. , EL1 .



os/kernel/ext/init.S



context_save



. , , - , , . (“read cpu affinity”, “core affinity != 0”) - :



MPIDR_EL1



( ref : D7.2.74) ( Aff0



), , . — setup



. wfe



.

: / , .


C: EL1



EL2 EL1. os/kernel/ext/init.S



os/kernel/src/kmain.rs



. , .









aarch64



( os/kernel/src/aarch64.rs



), . sp()



. current_el()



, . , EL2 . , kmain()



. , current_el()



unsafe



. , , EL1.









, EL1. os/kernel/ext/init.S



:







 // FIXME: Return to EL1 at `set_stack`.
      
      





:







 mov x2, #0x3c5 msr SPSR_EL2, x2
      
      





, . , , SPSR_EL2



eret



.







, FIXME



. , EL1 CPU set_stack



, . . , — eret



. , current_el()



1



.







: PC ?


D:



. , . , , brk #n



. kernel/ext/init.S



kernel/src/traps



.







復習



, 16 , 16 . init.S



_vectors



. , 16 , handle_exception



Rust kernel/src/traps/mod.rs



. handle_exception



. , , .









handle_exception



, Rust, , . , , info



, esr



tf



, .







, ( 2 C Rust). , , , . — , :









AArch64 ( guide : 9) procedure call standard .

Rust- handle_exception



, , .







Rust , ?



, . Rust . , Rust , extern



. handle_exception



extern



, Rust .




, , HANDLER(source, kind)



, . HANDLER(a, b)



"", , #define



. つまり :







 _vectors: HANDLER(32, 39)
      
      





:







 _vectors: .align 7 stp lr, x0, [SP, #-16]! mov x0, #32 movk x0, #39, LSL #16 bl context_save ldp lr, x0, [SP], #16 eret
      
      





lr



x0



x0



32- 16 source



16 kind



. context_save



, _vectors



. , , lr



x0



.







context_save



. ret



context_restore



. context_save



, Rust.







Syndrome



(, ), ( ESR_ELx



) ( ref : D1.10.4). kernel/src/traps/syndrome.rs



. Syndrome



-. , ESR_ELx



Rust esr



. Sydnrome::from(esr)



, , .







Info



handle_exception



Info



. 16 : source



kind



. , 32- , HANDLE



x0



. , HANDLE



- , Info



.







実装



. , — brk



, .. . , , .







brk



kmain



. :







 unsafe { asm!("brk 2" :::: "volatile"); }
      
      





:







  1. _vectors



    HANDLE



    .
    , Info



    .
  2. handle_exception



    context_save



    .

    , / caller-saved . 5 9 . 0



    tf



    . .

    注意してください。 AArch64 , SP



    16 , /. , .
  3. VBAR



    , :


     // FIXME: load `_vectors` addr into appropriate register (guide: 10.4)
          
          



  4. handle_exception



    , .


    handle_exception



    info



    esr



    , , . . , , , aarch64::nop()



    . , . .
  5. Syndrome::from()



    Fault::from()



    .


    . ( ref : D1.10.4, ref : Table D1-8) , . “ISS encoding description” , , . , brk 12



    Syndrome::Brk(12)



    , svc 77



    Syndrome::Svc(77)



    . , 32- , , .
  6. brk



    .


    Syndrome::from()



    handle_exception



    , brk



    . , . . , Syndrome::from()



    . ESR_ELx



    .

    exit



    . exit



    , . brk



    . shell()



    kmain



    loop { }



    , .


, brk 2



kmain



Brk(2)



, source, CurrentSpElx



kind Synchronous



. . exit



, .







, , . , , svc 3



. , .







すべてが期待どおりに機能したら、次のステップに進む準備ができています。









UPD次の部分








All Articles