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

このパートでは、 Vec



String



HashMap



などの使用をロック解除するために、メモリマネージャーを作成します。 その直後に、FAT32ファイルシステムを実装し、EMMCのドライバー(SDカードと通信するためのもの)を接続します。 最後に、シェルにcd



pwd



cat



ls



の新しいコマンドがいくつか表示されます。







ゼロラボ







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







有用性





フェーズ0:はじめに



まず、最初から環境に何も変わっていないことを確認します。









さらに、これがインストールされます: git



wget



tar



screen



make



および最後のシリーズにあったすべてのもの。 私たちは安定性を確信し、前進します。







必要なコードを取得する



このリポジトリを、 cs140e



という名前のパパ(または、そこにあるもの)にcs140e



します。







 git clone https://web.stanford.edu/class/cs140e/assignments/2-fs/skeleton.git 2-fs
      
      





結局のところ、内部には次のようなものがあるはずです。







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





ここで、コードの一部があるos



リポジトリを、この部分で使用されるコードとマージする必要があります。







 cd os git fetch git checkout 2-fs git merge master
      
      





マージの競合を解決する必要がある場合があります。 次のようなものが表示される場合があります。







 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



を手動で変更する必要があります。 以前のシリーズからのすべての変更を必ず保存してください。 競合を解決するには、 git add



およびgit commit



を使用して固定ファイルを追加する必要があります。 マージの競合の解決に関する一般的な情報、および一般的なgit



に関する情報は、 githowto.comマニュアルにあります 。 このチュートリアルを実行できます。 何でも重宝します。







ファームウェアの更新



2-fs



turnipからmake fetch



を使用してファームウェアを再ダウンロードする必要があります。 チーム自体がすべてをダウンロードして解凍します。 files/



サブディレクトリからMicroSDカードのルートにファイルを配置するだけです。 つまり、ファイルfirmware/bootcode.bin



firmware/config.txt



firmware/fixup.dat



およびfirmware/start.elf



kernel8.img



として前の部分のブートローダーを使用する場合、この行をconfig.txt



に追加する必要があることを忘れないでください:







 kernel_address=0x4000000
      
      





ttywriteをインストールする



これで、 os/kernel



Makefile



追加のinstall



ターゲットが追加されました。 カーネルを収集し、前の部分のttywrite



を使用してttywrite



に直接送信します。 したがって、 kernel8.img



kernel8.img



以前のシリーズで書かれたブートローダーが含まれている場合、このまさにブートローダーはファイルを受け入れてカーネルにロードします。 これを実行make install



は、パパでカーネルコードを使用してmake install



を実行make install



必要があります。







同時に、 Makefile



からのこのターゲットは、名前だけでttywrite



直接呼び出します。 つまり ttywrite



は、 PATH



環境変数が指す場所のいずれかになければなりません。 これらの場所の1つにttywrite



ために、1 1-shell/ttywrite



cargo install



を実行できます。 当社のユーティリティが収集し、適切な場所にコピーします。 すべてが正しい場合、 ttywrite --help



を呼び出すとヘルプが表示されます。







デフォルトでは、 make install



はデバイス名に/dev/tty.SLAB_USBtoUART



を使用します。 Makefile



を編集して、必要なものを示します。 つまり アダプタにはどのデバイスがありますか? PI_TTY



という変数があります。 異なる意味を与えてください。







ALLOCATOR.initialize()



はパニックを引き起こします!


シェルは引き続き機能するはずです。 ただし、 make install



ターゲットをテストしようとすると、シェルが機能していないことがわかります。 ほとんどの場合、問題はALLOCATOR.initialize()



呼び出しにあります。 メモリアロケーターはありません。 警告なしで実行されると、単にパニックになるスタブのみ。 少し後でこの迷惑な事実を修正します。 とりあえず、この行をコメントアウトするだけで、多かれ少なかれそれでも機能します。


フェーズ1:メモリライン





このフェーズでは、2つのメモリアロケーターを実装します。 最も単純なバンプアロケーターとやや複雑なビンアロケーター。 ヒープ上のメモリ割り当てが機能するために必要なのは、実際にはこれだけです。 つまり、 Vec



Box



、およびString



引き続き機能します。 使用可能なメモリの量を判断するために、ARMタグ( ATAGS )を使用します。 さらに、 panic!



呼び出されたときに最終的に呼び出されるpanic_fmt



関数の内部を実装する必要がありpanic!









サブフェーズA:パニック!



panic_fmt



の実装から始めましょう。 kernel/src/lang_items.rs









言語アイテム



Rustコンパイラが、オペレーティングシステム(Raspberry Piなど)を使用しない目的でプログラムをコンパイルするように構成されている場合、コンパイラはいくつかの機能を要求します。 私たちは自分の手でそれらを書く必要があります。 そのようなものは言語項目と呼ばれます。 これらの要素は、特定の条件下でコンパイラが呼び出しを置き換える関数です。 このような関数は、あらゆる基本言語に対して定義できます。 これを行うには、そのような関数に#[lang_item]



属性で注釈を付ける必要があります。







Rustでは現在、これらの関数のうち2つだけが必要です。









これらの関数は両方ともkernel/src/lang_items.rs



ですでに宣言されていkernel/src/lang_items.rs



。 関数panic_fmt



を追加して、有用なものを表示する必要があります。







panic_fmt



実装



次に、この関数を追加します。 実装では、送信された情報をコンソールに出力してから、無限ループloop



。 ところで。 fmt::Arguments



Display



トレイトを実装します。 したがって、 kprintln!("{}", fmt)



)を使用できます。 個人的に好きなように結論を出します。 たとえば、Linuxカーネルのパニックからインスピレーションを受けることができます







  ( ( ) ) ) ( ( ( ` .-""^"""^""^"""^""-. (//\\//\\//\\//\\//\\//) ~\^^^^^^^^^^^^^^^^^^/~ `================` The pi is overdone. ---------- PANIC ---------- FILE: src/kmain.rs LINE: 40 COL: 5 index out of bounds: the len is 3 but the index is 4
      
      





次に、ある種のカーネルパニックでpanic_fmt



実装panic_fmt



テストします。 make install



を使用してコンパイルし、ブートローダーを介して実行できることを思い出してください。 はい。 ALLOCATOR.initialize()



panic!



どこか中。 したがって、実行すると、エラーメッセージも表示されるはずです。







これに加えて、多くの方法でパニックを引き起こすようにしてください。 Using panic!()



、他の同様のマクロを使用します。 そして何か他のもの。 実装が自分で機能していることが確実な場合は、次のフェーズに進みます。







サブフェーズB:ATAGS



このサブフェーズでは、raspberryファームウェアが提供するARMタグ(ATAGS)に対してイテレーターを実行します。 反復子を使用して、使用可能なメモリの量を決定します。 主な作業は、 pi/src/atags



kernel/src/allocator/mod.rs









ARMタグ



ATAGSまたはARMタグは、さまざまな情報をオペレーティングシステムのカーネルに転送するために、ARMブートローダーとファームウェアで使用されるメカニズムです。 Linuxは、ATAGSを使用するように構成することもできます。







Malinkaは、アドレス0x100



から始まるATAG構造体の配列を配置します。 この場合、各タグは8ビットのヘッダーで始まります。







 struct AtagHeader { dwords: u32, tag: u32, }
      
      





dwords



フィールドには、タイトル自体を含むタグ全体のサイズが含まれます。 ダブルワード(ダブルワード、つまり32ビットワード)のサイズ。 最小サイズは2



(ヘッダーのサイズ)です。 tag



フィールドには、タイプATAGが含まれます。 ATAGSヘルプにはこれらの約12があります。 Malinkaはそのうちの4つだけを使用します。 彼らは私たちにとって興味深いです:







タイプ( tag



大きさ 説明
コア 0x54410001 5または2(空のリストの場合) スターターとして使用
なし 0x00000000 2 終わりを意味する
思い出 0x54410002 4 物理メモリの一部を説明します
CMDLINE 0x54410009 違う カーネルに引数を渡す


タグのタイプによって、ヘッダーの後のデータの解釈方法が決まります。 名前に関連付けられているリンクをクリックすると、詳細を確認できます。 たとえば、 MEM



タグには次のようなものが含まれます。







 struct Mem { size: u32, start: u32 }
      
      





タグは、それらの間を埋めることなく連続してメモリ内にあります。 このリストは、 CORE



タグで始まります。 そして、このリストの最後のタグはNONE



です。 他のすべては任意の順序にすることができます。 次のタグがどこから始まるのかをdwords



は、 dwords



の内容を調べる必要があります。 グラフィカルには、すべて次のようになります。







ATAGS







組合と安全





ATAGタグの生の構造は、 pi/src/atags/raw.rs



ファイルで宣言されています。 同時に、そこでunion



使用されます。 Rustの結合は、Neat Cの結合とほとんど同じです。 一部のフィールドがメモリを共有する構造を定義します。







 pub struct Atag { dwords: u32, tag: u32, kind: Kind } pub union Kind { core: Core, mem: Mem, cmd: Cmd }
      
      





つまり、関連付けにより、正しい選択を考慮せずに、任意の構造をメモリに書き込むことができます。 Rustでは、特定のユニオンフィールドへのアクセスは安全ではないことがわかりました。







多くの場所に既にunsafe



ラッパーがあります。 少なくとも、自分で協会に連絡する方法について心配する必要はありません。 ただし、ライブラリのエンドユーザーに関連付けを渡すことはお勧めできません。 このため、ファイルpi/src/atags/atag.rs



には別のAtag



構造があります。 この構造は、アクセスに対して完全に安全です。 外部コードに渡すのは私たちです。 atag



モジュールを追加すると、内部表現とこの構造の間の変換を記述します。







関連付けをエンドユーザーに渡すのはなぜ悪い考えですか? [エンドユーザー安全]



安全でない構造物の安全なインターフェイスを作成するために多くの努力をしました。 これは、Rustで複数回表示されます。 標準ライブラリもこの好例です。 安全なレイヤーを作成する用途は何ですか? このアプローチをNeat Cなどの言語に翻訳できますか?


コマンドライン引数



CMDLINE



タグには特に注意が必要です。 このタグは次のように宣言されます。







 struct Cmd { /// The first byte of the command line string. cmd: u8 }
      
      





コメントによると、 cmd



フィールドには文字列の最初のバイトが含まれています。 言い換えると、 &cmd



はCに似た文字列へのポインターであり、最終的にはゼロバイトで終了します。 このタグの安全なバージョンはCmd(&'static str)



です。 raw



バージョンから安全なバージョンに変換する場合、この行のサイズを決定する必要があります。 つまり nullターミネータ(コード0



文字)を見つけます。 その後、アドレスとサイズを使用して、これをslice::from_raw_parts()



を使用してスライスに変換できます。 次に、このスライスは、 str::from_utf8()



またはstr::from_utf8_unchecked()



を使用して文字列に変換できます。 既にラボ1で使用しています。







atags



実装



まあ。 これで、 pi/src/atags



あるatags



モジュールを実装するために必要なものがすべてpi/src/atags



atags/raw.rs



からraw::Atag::next()



atags/raw.rs



からatags/raw.rs



このメソッドは、現在のATAGの次のATAGのアドレスを決定します。 ここでは、 unsafe



コードに頼らなければなりunsafe



。 次に、 raw



構造から安全な構造に変換するためのヘルパーメソッドとプロパティを実装します。これはatags/atag.rs



From<&'a raw::Cmd> for Atag



する場合、少しunsafe



unsafe



コードを使用するFrom<&'a raw::Cmd> for Atag



From<&'a raw::Cmd> for Atag



ます。 Atags



atags/mod.rs



にあるatags/mod.rs



Iterator



traitを実装しatags/mod.rs



ここでは、少しunsafe



コードも必要になる場合があります。







ヒント:



3行のコードでAtags::next()



メソッドを実装できます(少なくとも試してみてください!)。



x: &T



から*const u32



変換できますx as *const T as *const u32



です。



x: *const T



から&T



への逆変換は、 &*x



実行できます。



add() sub()またはoffsetを使用して、生のポインターで算術演算を実行できます。


atags



テスト



ATAGSの独自の実装をテストします。 イテレータを介してすべての値を取得し、すべてをコンソールに出力してみてください。 kernel/src/kmain.rs



直接移動しkernel/src/kmain.rs



NONE



以外の3つのタグのうち少なくとも1つが表示されるはずです。 同時に、各ATAGの実装が期待どおりであることを確認してください。 実装が完了したら、次のサブフェーズに進みます。







ヒント :構造のより美しい出力をコンソールに出力するには、 `{:#?}を使用します。





タイプCMDLINE



タグには何がCMDLINE



ますか?
[atag-cmdline]



これらのタグに値はありますか? ファームウェアからどのような議論がありましたか? どこから来たと思いますか、どのように使用できますか?





MEM



タグに従って使用可能なメモリ量はどれくらいですか?
[atag-mem]



MEM



タグで報告される使用可能なメモリの正確な開始アドレスとサイズは何ですか? これはすべて、ラズベリーが持っている1ギガバイトのメモリにどれくらい近いですか?


サブフェーズC:ウォームアップ



このサブフェーズでは、2つのメモリアロケータの後続の書き込みに必要なすべてを準備します。 アドレスを2のべき乗に揃える関数align_up



およびalign_down



を実装します。 さらに、システムメモリから開始アドレスと終了アドレスを返すmemory_map



関数を実装します。 この関数は両方のアロケーターによって使用され、割り当てに使用可能なメモリー量を決定します。







アライメント



メモリ内のアドレスは、Nで完全に分割されると、Nバイトでアラインされたと呼ばれます N



つまり、アドレスk



k % n == 0



が真になります。 通常、記憶を調整することを心配する必要はありません。 ただし、現在はシステムプログラマです。 このトピックへの注目が高まっているのは、ハードウェア、プロトコル、およびその他すべてが、アライメントに関して非常に特定のプロパティを規定しているという事実に関連しています。 たとえば、32ビットARMアーキテクチャでは、スタックポインターが8バイトにアライメントされている必要があります。 AArch64アーキテクチャ(この場合)では、スタックポインターに16バイトのアライメントが必要です。 x86-64でもほぼ同じことが必要です。 メモリのページのアドレスは、4キロバイトに揃える必要があります。 さらに多くの異なる例がありますが、それなしではできないことはすでにわかっています。







Cute Cでは、libCからアロケータによって返されるメモリアドレスは、32ビットシステムでは8バイト、64ビットシステムでは16バイトであることが保証されます。 さらに、呼び出し元は、返されたメモリアドレスのアライメントを明確に制御することはできず、自分で処理する必要があります。 このために、たとえば、POSIX標準のposix_memalign



関数があります。







Neat Cでこのようなアライメントプロパティが選択されるのはなぜですか? [libc-align]



malloc



システム関数のアライメントに8バイトまたは16バイトの保証を選択しても、根拠はありません。 標準Cライブラリでこのような保証が選択されたのはなぜですか?

Cute Cでは、 malloc()



およびfree()



宣言は次のようになります。







 void *malloc(size_t size); void free(void *pointer);
      
      





Rustは、低安全レベルでalloc



dealloc



を使用します。これは次のようになります。







 // `layout.size()` is the requested size, `layout.align()` the requested alignment unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr>; // `layout` should be the same as was used for the call that returned `ptr` unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout);
      
      





呼び出しコードは整列を示す場合があることに注意してください。 その結果、呼び出し元のコードではなくアロケーターが、整列されたポインターを返すように注意する必要があります。 次のサブフェーズでメモリアロケータを実装する場合、戻りアドレスが正しく整列されていることを確認する必要があります。







さらに、 dealloc



とは異なりdeallocでは、呼び出し側がalloc



とまったく同じLayout



渡す必要があることに注意してdealloc



。 したがって、外部コードは、特定の割り当てられたメモリのサイズとアラインメントの格納に注意する必要があります。







このようにRustが責任をアロケーターと呼び出しコードとに分けていると思うのはなぜですか? [onus]



キュートなアロケータでは、返すことができるメモリアドレスのアライメントに関する制限が少なくなります。 ただし、同時に、割り当てられたスペースのサイズを保持する必要があります。 Rustでは、まったく逆です。 なぜRustは反対の道を選んだと思いますか? これはアロケーターと呼び出しコードにどのような利点がありますか?


align_up



align_up



およびalign_down





次のサブフェーズでアロケーターを実装する場合、次または前のアライメントされたアドレスを判別するのに役立ちます。 つまり アドレスu



次の>=



または<=



u



、2の累乗で整列されます。 align_up



およびalign_down



が既に(もちろん実現されていalign_up



)ありalign_down



。 これらはkernel/src/allocator/util.rs



。 これらはおおよそ次のように宣言されます:







 /// Align `addr` downwards to the nearest multiple of `align`. /// Panics if `align` is not a power of 2. fn align_down(addr: usize, align: usize) -> usize; /// Align `addr` upwards to the nearest multiple of `align`. /// Panics if `align` is not a power of 2. fn align_up(addr: usize, align: usize) -> usize;
      
      





これらの機能を今すぐリリースしてください。 kernel



ディレクトリからmake test



またはcargo test



呼び出すmake test



により、このプロセスの正当性を確認できkernel



。 テスト自体はkernel/src/allocator/tests.rs



。 この部分のすべてが正しい場合、 align_util



すべてのテストにalign_util



はずです。







注意kprint{ln}!



テスト中kprint{ln}!



print{ln}!



呼び出しになりますprint{ln}!



すべてが機能するはずです。



ヒント:



実装は1行または2行を占有します。



align_up



align_down



は互いに非常に似ています。


スレッドセーフ



libCのmalloc()



および実装する2つのメモリアロケータはグローバルです。 つまり いつでもどこでも呼び出すことができます。 任意のストリームに含める。 したがって、アロケーターはスレッドセーフでなければなりません。 Rustはスレッドの安全性を非常に重視しています。 このため、システムに並行性を処理するメカニズム(スレッドなど)がまだない場合でも、アロケーターを実装することは困難です。







スレッドセーフメモリアロケーターのトピックは非常に広範囲です。 このトピックについては、多くの研究が見つかります。 現時点では、このトピックについては触れません。 Mutex



メモリMutex



をラップするだけです。 このラッパーは既にkernel/src/allocator/mod.rs



今すぐこのコードをすべて読んでください。 Alloc特性の実装がそこにあることに注意してください。 これにより、Rustはこれが非常に有効なアロケーターであることを知ることができます。 , #[global_allocator]



( kmain.rs



). #[global_allocator]



— , , Rust- Vec



, String



Box



. つまり alloc()



dealloc()



, .









Alloc



Allocator



kernel/src/allocator/mod.rs



imp::Allocator



, mutex



. imp



. . #[path = "bump.rs"]



, Rust-, . #[path]



. bump- bump.rs



. bin- bin.rs



.







: memory_map





kernel/src/allocator/mod.rs



memory_map



. Allocator::initialize()



, kmain()



. initialize()



imp::Allocator



.







memory_map



. , . つまり , ATAGS. , . binary_end



. , _end



( layout.ld



).







memory_map



, Atags



B binary_end



. , . - String::from("Hi!")



. . , panic!()



bump-. memory_map



, bump- — . , .







D: Bump-





. Bump-. kernel/src/allocator/bump.rs



.







Bump- . alloc



current



. . current



( ). , . dealloc



.







, , , 1 , 512 . , .







バンプ







, kernel/src/allocator/bump.rs



. つまり new()



, alloc()



dealloc()



bump::Allocator



. align_up



align_down



, . -. make test



cargo test



kernel



. allocator::bump_*



.







. , , !



saturating_add saturating_sub .

- , kmain()



. :







 let mut v = vec![]; for i in 0..1000 { v.push(i); kprintln!("{:?}", v); }
      
      





— .







alloc



?
[bump-chain]



bump::Allocator::alloc()



. ? : , v.push(i)



bump::Allocator::alloc()



.


E: Bin-





: bin-. kernel/src/allocator/bin.rs



.







Bin- , . ( ) , . . — . , . .







, , . k - 2



2^n



n



, 3



k



( 2^3



, 2^4



2^k



). , 2^3



, 2^3



. 2^3



2^4



2^4



:











(intrusive) . kernel/src/allocator/linked_list.rs



. kernel/src/allocator/bin.rs



LinkedList



, .







?



next



( previous



) . . .

LinkedList::new()



. push()



. ( ), pop()



. peek()



. :







 let mut list = LinkedList::new(); unsafe { list.push(address_1); list.push(address_2); } assert_eq!(list.peek(), Some(address_2)); assert_eq!(list.pop(), Some(address_2)); assert_eq!(list.pop(), Some(address_1)); assert_eq!(list.pop(), None);
      
      





LinkedList



. iter()



. . iter_mut()



. Node



, . value()



pop()



.







 let mut list = LinkedList::new(); unsafe { list.push(address_1); list.push(address_2); list.push(address_3); } for node in list.iter_mut() { if node.value() == address_2 { node.pop(); } } assert_eq!(list.pop(), Some(address_3)); assert_eq!(list.pop(), Some(address_1)); assert_eq!(list.pop(), None);
      
      





LinkedList



. , push()



. LinkedList



.







? [ll-alloc]



— . , , ?




, , . , . . , . . . .







:









, .







実装



bin- kernel/src/allocator/bin.rs



. ( , bin- ) . . . - . make test



cargo test



. bump.rs



bin.rs



#[path]



kernel/src/allocator/mod.rs



. まあつまり bump-, bin, .







bin- — .







? [bin-about]



. :

  • () ?
  • ?
  • ?







? [bin-frag]



! , ? ( ) .







UPD :








All Articles