このパートでは、Rustのスキルを向上させ、有用なユーティリティとライブラリをいくつか作成します。 GPIO、UART、組み込みタイマー用のドライバーを作成します。 XMODEMプロトコルを実装します。 これらすべてを使用して、単純なシェルとブートローダーを作成します。 読む前に、 本を読むことを強くお勧めします。 少なくとも最初から最後まで。 怠zyな、しかしもう少し経験を積んだ人には、 これをお勧めできます。 ロシア語では、 ここで掘ることができます 。
もちろん、 ゼロレベルをバイパスすることはまったく価値がありません。 また、この部分の半分はラズベリーを必要としません。
便利な資料
- RustによるBook v2.0 。 途中でロシア語に翻訳。 翻訳者のチームをからかう(そして彼らを助ける)。 この小冊子は、少なくとも読んでいない人には間違いなく読む価値があります。
- Rust標準ライブラリドキュメント 。 そこにはすべてが用意されており、これは標準納品です。
- docs.rs-さまざまなライブラリのドキュメントを読むことができます。
- 登録とSMSなしのマナオンライン無料ダウンロード。 RTFM!
- XMODEMプロトコルに関するWiki記事。 一般的な開発および起源の歴史に興味がある人のために。 実装にはほとんど役立ちません。 ほとんど何もありません。
- 別の XMODEM ドキュメント 。
- BCM2837-これはラズベリーの割合についてです。 そこ、前回。
フェーズ0:はじめに
念のため、コース互換のファームウェアとハードウェアを使用していることをもう一度確認してください。
- 最新の64ビットUnixライクOS:Linux、macOS、またはBSD
- 適切なUSBコネクターがあります(許可されていないものは添付ファイルを使用できます)
さらに、次のプログラムをインストールする必要があります: git
、 wget
、 tar
、 screen
、 make
など、 ゼロレベルに必要なすべてのもの。 このパートでは、 socat
をインストールする必要があります。
前回Windowsで必要なすべてを実行できた場合、今回はすべてが動作するはずです。 しかし、突然サポートがない場合。 私もオリジナルの作者も、Vendaを手に入れることも、掘り下げたいという欲求も持っていません。
コード検索
この部分のコードを次のように複製できます。
git clone https://web.stanford.edu/class/cs140e/assignments/1-shell/skeleton.git 1-shell
自分でリポジトリの内容を自由に探索してください。
ご質問
このラボおよび以下のすべてのラボでは、 質問があります 。 あなたはネタバレを使用してコメントで直接答えることができます。 以下に例を示します。
他のGPIOピンをどのように構成して使用しますか? [assignment0]
前回は、点滅するLEDの名前に16ピンGPIOを使用しました。 レジスタGPFSEL1
、GPSET0
およびGPCLR0
ます。 ピン27を使用する場合、どのレジスタが便利ですか? そして、この27 GPIOピンの物理的な接触は何ですか?
角括弧は、 questions/
ディレクトリ内のファイル名を示します。 コメントで回答する必要があるため、これは私たちにとって特に重要ではありません。 自分が答えたと確信できるまで、他人の答えを読まないでください。 そうでなければ、面白くありません。 ただし、これらのタグはスポイラーのヘッダーとして使用できます。 ただし、これらのファイルに最初に書き込むことをお勧めします。 便宜上。
フェーズ1:観覧車(しゃれ)
(ラスタについての十分な知識が既にある場合、この部分は完全にスキップできます。)
トレーニングのために、わがままな目的のためにRustのプログラムを編集します。 一部は編集後にコンパイルする必要があります。 その他はコンパイルしないでください 。 3番目の場合、テストは正常に完了する必要があります。
ferris-wheel/
ディレクトリの腸では、以下を見つけることができます:
-
compile-fail
しないように壊れる必要があるコードが含まれています -
compile-pass
前に正確に修正する必要があるコードが含まれています -
run-pass
緑になるはずのテストを含むコードが含まれています -
questions
-理論上は質問ですが、コメントにすべて記入できることにすでに同意しています。
test.sh test.sh
はまだありtest.sh
。 彼はタスクの正しさをチェックします。 実行すると、予想どおりではない場所と内容が非常に一般的に説明されます。 次のようなもの:
$ ./test.sh ERROR: compile-pass/borrow-1.rs failed to compile! ERROR: compile-pass/const.rs failed to compile! ... 0 passes, 25 failures
さらに、スクリプトは-v
フラグを受け入れます。 このフラグがスクリプトに渡されると、コンパイラーが吐き出すエラーが表示されます。
$ ./test.sh -v ERROR: compile-pass/borrow-1.rs failed to compile! ---------------------- stderr -------------------------- warning: unused variable: `z` --> /.../ferris-wheel/compile-pass/borrow-1.rs:9:9 | 9 | let z = *y; | ^ | = note: #[warn(unused_variables)] on by default = note: to avoid this warning, consider using `_z` instead error[E0507]: cannot move out of borrowed content --> /.../ferris-wheel/compile-pass/borrow-1.rs:9:13 | 9 | let z = *y; | ^^ | | | cannot move out of borrowed content | help: consider using a reference instead: `&*y` error: aborting due to previous error ... 0 passes, 25 failures
このスクリプトは、文字列をフィルターとしても使用します。 使用可能な場合、このフィルターに一致するファイルパス($ディレクトリ/ $ファイル名)のみがチェックされます。 例:
./test.sh trait ERROR: compile-pass/trait-namespace.rs failed to compile! ERROR: run-pass/trait-impl.rs failed to compile! 0 passes, 2 failures
一方が他方に干渉することはなく、フィルターと-v
組み合わせることができます。 このようなもの: ./test.sh -v filter
。
どのくらい変更できますか?
各ファイルにはコメントが含まれており、コメントがどの程度損なわれる可能性があるかを示します(差分予算)。 つまり プログラムを修正するために行うことができる変更の最大数。 このフレームワークに適合しない決定は、失敗したとみなすことができます。
例として。 ファイルcompile-pass/try.rs
コメントがあります。
// FIXME: Make me compile. Diff budget: 12 line additions and 2 characters.
追加できるコードは12行までです(空の行も考慮されます)。 そして、2文字を変更 (追加/変更/削除)します。 git diff
を使用して、行ごとの変更を確認できます。 そして、 git diff --word-diff-regex=.
同じですが、文字ごとに。
別の例:
// FIXME: Make me compile! Diff budget: 1 line.
kakbeは、コードの1行のみを変更 (追加/変更/拡張)できることを示しています。
一般規則
変更後、プログラムの意図された機能は保持されるはずです。 コンパイルするために特定の関数の本体を変更する必要がある場合、 unimplemented!()
を追加するだけでは不十分だとしましょうunimplemented!()
。 疑わしい場合は、できる限りのことを試してください。 さて、またはコメントでお願いします。
これに加えて、次のダーティメソッドを実行することは完全に推奨されません。
- これらすべての
assert!
変更してくださいassert!
s - 「変更しない」とマークされているすべてを変更します
- 変更可能な範囲と内容に関するコメントの変更
- ファイルの移動、名前変更、追加
すべてのタスクが完了すると、 test.sh
は25 passes, 0 failures
を出力します
ヒント:ファイル名にはソリューションのキーが含まれている場合があります。
ヒント: この居心地の良いチャットルームでは 、Rustに関する質問にすばやく回答できます 。 この記事のコメントよりも速い。
どうした 修理は何でしたか? なぜ機能するのですか? [ファイル名]
この部分の各プログラムについて、ソースコードの何が問題であったかを説明する必要があります。 次に説明するハードコアどのような変更が行われたか、これらの修正がなぜ汚い仕事をするのか。 適切な説明を歓迎します。 あなたはすべてがあなたにとても明白であると思うなら、あなたは書くことができません。 怠lazなら-あなたはまったく何も書くことができません。
フェーズ2:酸化

この段階では、コマンドライン用にいくつかのライブラリと1つのユーティリティを作成します。 サブディレクトリstack-vec
、 volatile
、 ttywrite
およびxmodem
でttywrite
しxmodem
。 また、壊れていない場合に回答できる質問がいくつかあります。 各部分は貨物によって管理されています。 少なくともこれらのコマンドは便利と呼ぶことができます:
-
cargo build
-プログラムまたはライブラリをビルドします -
cargo test
-cargo test
実行 -
cargo run
-アプリケーションの起動 -
cargo run -- $
-この方法で、アプリケーションの起動時にフラグを渡すことができます
Cargoについては、別の小冊子Cargo Bookがあります。 そこから、すべてが詳細に機能する方法について必要な情報を学ぶことができます。
サブフェーズA:StackVec
オペレーティングシステムが扱う最も重要な機能の1つは、メモリの割り当てです。 C、Rust、Java、Python、またはほぼすべてのアプリケーションが一般的にmalloc()
呼び出す場合、十分なメモリがない場合、システムコールが最終的に使用され、オペレーティングシステムに追加のメモリが要求されます。 オペレーティングシステムは、まだメモリが誰も占有していないかどうかを判断します。 その場合、このメモリからOSがプロセッサに少し振りかけます。
メモリ割り当て- 非陰茎
すべてのlinupsなどの最新のOSには、メモリ管理に関連する多くのトリックが含まれています。 たとえば、最適化松葉杖の順序で、一定量のメモリが要求されると、仮想的に割り当てられます。 この場合、アプリケーションがこのメモリを使用しようとするまで、物理メモリは割り当てられません。 一方、単純化された配布の錯覚は、アプリケーションに対して作成されます。 オペレーティングシステムは見事に嘘をつくことができます()
内部のVec
、 String
およびBox
などの構造体は、 malloc()
を使用して独自のニーズに合わせてメモリを割り当てます。 つまり、これらの構造にはオペレーティングシステムのサポートが必要です。 特に、OSがメモリを割り当てる方法を知っている必要があります。 このパートはまだ始まっていません(次のシリーズを参照)ので、メモリ管理は一切ありません。 したがって、これらのすべてのVec
(まだ)使用することはできません。
これはVec
集中的な混乱であり、あらゆる点で適切な抽象化です! これにより、あらゆる種類の微妙な点を覚える必要なく、 .push()
および.pop()
観点から考えることができます。 完全なメモリアロケータなしでVec
似たものを取得できますか?

もちろん。 最初に思い浮かぶのは、メモリの予備割り当てと、その上に必要な抽象化を実装する特定の構造へのその後の転送です。 バイナリファイルに直接、またはスタックのどこかにメモリを静的に割り当てることができます。 どちらの場合も、そのようなメモリは、事前に決められた固定サイズでなければなりません。
このサブフェーズでは、 StackVec
を実装しStackVec
。これは、 Vec
が標準ライブラリから提供するAPIと同様のAPIを提供します。 ただし、事前に割り当てられたメモリを使用します。 この同じStackVec
、コマンドラインを実装するときに役立ちます(フェーズ3)。 stack-vec
サブディレクトリで作業しstack-vec
。 その中には、すでに次のものがあります。
-
Cargo.toml
-Cargoの構成ファイル -
src/lib.rs
必要なコードを追加します -
src/tests.rs
cargo test
開始時に実行されるcargo test
-
questions/
-questions/
ファイルの空白(あまり興味がない)
StackVec
インターフェース
StackVec<T>
は、 StackVec::new()
呼び出すことによって作成されます。 f-ii new
引数として、タイプT
スライスT
ます。 StackVec<T>
は、 Vec
類似のメソッドとほぼ同じ方法で使用される多くのメソッドを実装します。 たとえば、 StackVec<u8>
ます。
let mut storage = [0u8; 1024]; let mut vec = StackVec::new(&mut storage); for i in 0..10 { vec.push(i * i).expect("can push 1024 times"); } for (i, v) in vec.iter().enumerate() { assert_eq!(*v, (i * i) as u8); } let last_element = vec.pop().expect("has elements"); assert_eq!(last_element, 9 * 9);
StackVec
型StackVec
既に次のように宣言されStackVec
います。
pub struct StackVec<'a, T: 'a> { storage: &'a mut [T], len: usize, }
StackVec
理解StackVec
StackVec
デバイスに関する質問がいくつかあります。
push
がResult
返すのはなぜですか? [プッシュ失敗]
標準ライブラリにあるVec
のpush
メソッドには、戻り値がありません。 ただし、StackVec
からのpush
はStackVec
ようなものがあります。何らかのエラーがある可能性があることを示す結果を返します。Vec
と違ってStackVec::push()
失敗するのはなぜですか?
T
生涯に制限する必要があるのはなぜですか? [寿命]
コンパイラは、このStackVec
を拒否しStackVec
。
struct StackVec<'a, T> { buffer: &'a mut [T], len: usize }
型T
に制約を追加すると、すべてが機能します。
struct StackVec<'a, T: 'a> { buffer: &'a mut [T], len: usize }
なぜこの制限が必要なのですか? Rustがこの制限に従わないとどうなりますか?
StackVec
pop
メソッドにT: Clone
必要なのStackVec
なぜですか? [clone-for-pop]
Vec<T>
標準ライブラリのpop
メソッドはT
実装されますが、StackVec
pop
メソッドはT
がClone
プロパティを実装する場合にのみ実装されます。 なぜそうなのでしょうか? この制限を克服するとどうなりますか?
StackVec
実装
stack-vec/src/lib.rs
すべてのunimplemented!()
メソッドをStackVec
しstack-vec/src/lib.rs
。 各メソッドにはすでにドキュメントがあります(たとえば、そこから何が必要かは明確です)。 これに加えて、 src/tests.rs
ファイルには、実装が正しいことを確認するのに役立つテストがあります。 cargo test
コマンドを使用してテストを実行できます。 さらに、 StackVec
クラスのIntoIterator
、 DerefMut
およびIntoIterator
を実装する必要があります。 そして&StackVec
IntoIterator IntoIterator
。 これらの特性を実装しないと、テストは失敗します。 実装が正しいことを確認し、質問に答えられるようになったらすぐに、次のサブフェーズに進みます。
どのテストにDeref
実装が必要ですか? [deref-in-tests]
str/tests.rs
からすべてのテストコードを読み取りstr/tests.rs
。Deref
実装がない場合、どのテストをコンパイルしたくないのですか?DerefMut
どうですか? なんで?
実際、テストは完了していません
提供される単体テストは基本的な機能をカバーしていますが、くしゃみをすべてテストするわけではありません。 そのようなスペースを探し、偉大な正義の名の下に、テストの神にさらにテストを追加してください。
ヒント:ゼロフェーズliftime
ジョブからのソリューションが役立つ場合があります。
サブフェーズB: volatile

このパートでは、揮発性メモリアクセスについて説明し、 volatile/
サブディレクトリのコードを読み取ります。 独自のコードを書くことはしませんが、セルフテストには疑問があります。
典型的なオペレーティングシステムと同様に、コンパイラは非常に巧妙なトリックを巧みに実行します。 最適化の名において、彼らはあなたが意図したもののように見える何かをしています。 実際、内部には非常に強力な魔術があります。 そのような魔術の良い例は、デッドコードの削除です。 コンパイラがコードが実行に影響を与えないことを証明できる場合、デッドコードは迅速かつ断定的に切り取られます。 そのようなコードがあるとしましょう:
fn f() { let mut x = 0; let y = &mut x; *y = 10; }
コンパイラーは、記録後に*y
読み取られないという少し合理的な理由を考えるかもしれません。 このため、コンパイラは結果のバイナリファイルからこの部分を単純に除外できます。 このように議論を続けると、コンパイラはy
自体の宣言を切断してからx
を切断することが適切であると判断します。 最後に、 f()
への呼び出しはナイフの下に行きます。
この種の最適化は非常に便利で価値があります。 それらのおかげで、プログラムは結果に影響を与えることなく加速されます。 確かに、場合によっては、そのような詐欺は予期せぬ結果をもたらす可能性があります。 たとえば、 y
は書き込みのみ可能なレジスタを指します。 この場合、 *y
への書き込みは、 *y
を読み取らなくても、かなり観察可能な効果があります。 コンパイラがこれを知らない場合、最適化段階でこの部分を取得するだけで、プログラムは期待どおりに動作しません。
このようなものの読み取り/書き込みが、居心地の良い世界自体に影響を与えることをコンパイラーにどのように納得させることができますか? これはまさに、揮発性メモリアクセスの意味です。 コンパイラは、そのようなサイトへのアクセスを最適化しないことを誓います。
さびたvolatile
Rustでは、 write_volatile
とwrite_volatile
を使用して、生のポインターを読み書きできます。
これらはどのような生のポインタですか?
これまで、リンクを密接に知ることができました(これは&T
および&mut T
)。 Rustのrawポインター(*const T
および*mut T
)は、ボローチェッカーの有効期間を追跡しない基本的に同じリンクです。 これらの生のポインターを使用した読み取り/書き込みは、CおよびC ++の愛好家でよく見られる同じ足の負傷につながる可能性があります。 Rustでは、このような操作は安全ではないと考えています。 したがって、このすべてをunsafe
タグでマークすることが必須です。 ドキュメントで生のポインタの詳細を読んでください。
write_volatile
とwrite_volatile
毎回書くことは十分に悲しいです(うつ病によって引き起こされる迷惑なエラーにつながる可能性があるという事実に加えて)。 幸いなことに、Rustは私たちの生活をより簡単で安全にする機会を提供してくれます。 一方では、単純にvolatile
ラッパーを作成し(素敵なCのvolatile
キーワードにほとんど似ています)、すべての読み取り/書き込みがコード内に残るようにします。 ボーナスとして、読み取り専用または書き込み専用のラッパーを定義できます(ラッパーはありません。必要に応じてトランクとスピンを提供します)。
Volatile
、 ReadVolatile
、 WriteVolatile
およびUniqueVolatile
volatile/
ディレクトリ内のvolatile
(誰が考えたでしょうか?)これら4つのタイプを実装します。これらは、その名前から明らかなことを行います。 詳細はドキュメントをご覧ください。 このドキュメントを便利な形式で実際に読むには、 volatile/
ディレクトリで直接cargo doc --open
呼び出してください。
UniqueVolatile
があるのはなぜですか? [ユニーク揮発性]
Volatile
とUniqueVolatile
使用すると、揮発性メモリアクセスをUniqueVolatile
できます。 ドキュメントに基づいて、2つのタイプの違いは何ですか?
src/lib.rs
。 自分のスキルの精神でコードを読んでください。 その後(コードを読んで)、次の2つの質問に答えます。 終了方法-次のサブフェーズに進むことができます。
読み書きの制限はどのように整理されていますか? [強制]
WriteVolatile
とWriteVolatile
は、それぞれポインターの読み取りと書き込みを不可能にします。 これはどのように行われますか?
通常の方法の代わりに特性を使用する利点は何ですか? [特性]
注意深く調べると、各タイプが独自のnew
メソッドを1つだけ実装していることを置き換えることができます。 他のすべてのメソッドは、Writeable
方法でReadable
、Writeable
およびReadableWriteable
実装に関連しています。 このすべての利益は何ですか? このアプローチの少なくとも2つの利点を説明してください。
read
とwrite
安全でnew
安全でないのはなぜですか? [安全]
read
とwrite
が安全であると見なされるように、new
に関して何が真実である必要がありますか? 対照的に、read
とwrite
安全write
はありませんwrite
代わりにnew
を安全とマークするのは安全でしょうか?
ヒント:これらすべてのメソッドのドキュメントを読んでください。なぜ
new
の使用を強制するのですか? [pub-constructor]
タイプ
Volatile
が次のように宣言された場合:
struct Volatile<T>(pub *mut T);
次に、
new
を呼び出す代わりにVolatile(ptr)
を使用してVolatile(ptr)
型の値を作成できます。 静的呼び出しnew
ラッパーを作成する使用法は何ですか?
ヒント:両方のオプションの安全性ステートメントの意味を考慮してください。
マクロは何をしますか? [マクロ]
readable!
マクロは何をしますかreadable!
writeable!
そしてreadable_writeable!
?
サブフェーズC: xmodem
このサブフェーズでは、XMODEMファイル転送プロトコル( xmodem/
サブディレクトリ)を実装します。 主な作業はxmodem/src/lib.rs
。
XMODEMは、1977年に開発された単純なファイル転送プロトコルです。 パケットチェックサム、送信キャンセル、およびエラーが発生した場合に送信を自動的に再試行する機能があります。 UARTなどのシリアルインターフェイスを介して情報を送信するために広く使用されています。 プロトコルの主要な機能はシンプルです。 wikiで詳細を読んでください: XMODEM (誰でも記事をロシア語に翻訳できます)。
プロトコル
プロトコル自体については、テキストファイル「X-Modem File Transfer Protocolの理解」で詳細に説明されています。 ここでいくつかの説明を繰り返します。
ウィキペディアからの説明に基づいて実装を行わないでください!
教育学からの説明は高レベルで有用ですが、多くの詳細はここで実装するものとは異なります。 教育学はプロトコルの概要としてのみ使用してください。
XMODEMは非常にバイナリプロトコルです。生のバイトが送受信されます。 さらに、プロトコルは半二重です。送信者または受信者はいつでもデータを送信しますが、両方を同時に送信することはありません。 最後に、これはパケットプロトコルです。データは128バイトのブロック(パケット)に分割されます。 プロトコルは、どのバイトを送信するか、いつ送信するか、何を示すか、そして後でそれらを読み取る方法を決定します。
まず、いくつかの定数を定義しましょう。
const SOH: u8 = 0x01; const EOT: u8 = 0x04; const ACK: u8 = 0x06; const NAK: u8 = 0x15; const CAN: u8 = 0x18;
, NAK
, NAK
. , NAK
, . NAK
, .
, , . 1. (.. 255), 0.
, :
-
SOH
- (
255 - $_
) -
- 256
- :
-
NAK
, ( 10 ) -
ACK
,
-
, :
-
SOH
EOT
- ,
-
EOT
—
-
- —
-
- ,
- (128 )
-
- つまり 256
-
- ,
NAK
- ,
ACK
- ,
, , , CAN
. CAN
— .
, :
-
EOT
-
NAK
( — ) -
EOT
-
ACK
( — )
, ( EOT
):
-
NAK
-
EOT
( , ) -
ACK
XMODEM
XMODEM . , . expect_byte
, expect_byte_or_cancel
, read_packet
write_packet
src/lib.rs
. Xmodem
: packet
started
. , .
expect_byte
expect_byte_or_cancel
. ( read_byte
write_byte
) read_packet
write_packet
. , , transmit
receive
. / . , . cargo test
. , — .
std.
std::io
.std
.
:
{read, write}_packet
33 .
io::Read io::Write (, , ).
?
。
, .
D: ttywrite
ttywrite
. XMODEM. xmodem
. ttywrite/src/main.rs
. test.sh
. - socat
.
?
, . . , . UART, .
TTY?
TTY — (TeltTYpe writer). , . ( ) ./dev/
, tty.
ttywrite
- . structopt , clap . , , Cargo.toml
. structopt . , , structopt .
, , --help
. , cargo run
--
. : cargo run -- --help
. . main.rs
. Opt
. .
, ? [invalid]
.-f
idk
.structopt
, ?
, . . , .
main
serial::open . open
serial , . open
TTYPort , / / ( io::Read
io::Write
). ( SerialDevice
).
ttywrite
. , , opt
main
. , stdin
. . . -r
, . , xmodem
. ( ).
XMODEM Xmodem::transfer
Xmodem::transmit_with_progress
. transmit_with_progress
. :
fn progress_fn(progress: Progress) { println!("Progress: {:?}", progress); } Xmodem::transmit_with_progress(data, to, progress_fn)
test.sh
ttywrite
. :
Opening PTYs... Running test 1/10. wrote 333 bytes to input ... Running test 10/10. wrote 232 bytes to input SUCCESS
stdin
io::stdin() .
io::copy() .
main()
35 .
TTYPort .
test.sh
-r
? [bad-test]
-r
. XMODEM. , ? XMODEM ? ?