最初からオペレーティングシステム。 レベル1(古い半分)

次のパートの時が来ました。 これは、翻訳ラボ番号1の後半です。 この問題では、周辺機器ドライバー(タイマー、GPIO、UART)を記述し、XMODEMプロトコルと1つのユーティリティを実装します。 これらすべてを使用して、カーネルのコマンドシェルと、microSDカードを前後に動かさないようにするブートローダーを作成します。







下半分

読書ゼロラボで始まります







フェーズ3:貝殻ではない





今回は、統合された周辺機器用のドライバーをいくつか作成します。 組み込みタイマー、GPIO、およびUARTに興味があります。 それらは組み込みのコマンドラインを書くのに十分であり、少し後にブートローダーを作成するのに便利になります(これにより、さらに作業がいくらか簡単になります)。







ドライバーとは何ですか?



ドライバーまたはデバイスドライバーという用語は、特定のハードウェアデバイスと直接対話し、それを管理するソフトウェアなどです。 ドライバーは、制御するデバイスに高レベルのインターフェイスを提供します。 オペレーティングシステムは、デバイスドライバーと対話して、それらの上にさらに高度な抽象化を構築します(もちろん、便宜上)。 たとえば、Linuxカーネルは、ドライバーと対話するオーディオ用のAPIであるALSA(Advanced Linux Sound Architecture)を提供し、ドライバーはサウンドカードと直接通信します。


サブフェーズA:はじめに



割り当ての残りの部分では、 os



カブの内部で作業します。これは、この部分だけでなく、コースの残りの部分でも使用されます。 最終的にオペレーティングシステムになるのは、このリポジトリです。







このコースを参照して、ラボなどすべてに次のディレクトリ構造をお勧めします。







 cs140e ├── 0-blinky │ ├── Makefile │ ├── phase3 │ └── phase4 ├── 1-shell │ ├── ferris-wheel │ ├── getting-started │ ├── stack-vec │ ├── ttywrite │ ├── volatile │ └── xmodem └── os ├── Makefile ├── bootloader ├── kernel ├── pi ├── std └── volatile
      
      





快適で端正な。 0-blinky



および1-shell



は、以前のラボと現在のラボを指し、 os



は次のように取得できます。







 git clone https://web.stanford.edu/class/cs140e/os.git os git checkout master
      
      





すべてが正しく配置されていることを確認し、 os/kernel



内でmake



を実行しos/kernel



。 すべてが正常であれば、コマンドは正常に実行されます。







プロジェクト構造



os



ディレクトリには、次の一連のサブディレクトリが含まれます。









すべてのドライバーコードはpi



ライブラリにあります。 pi



volatile



ライブラリと(オプションで) std



ます。 kernel



bootloader



pi



を使用してデバイスと通信します。 そして、これに加えて、それらはstd



に依存していstd



volatile



は何にも依存しません。 グラフィカルに、この関係は次のようになります。









ファームウェア



続行する前に、ラズベリーファームウェアを更新する必要があります。 これらはすべて、 os



ディレクトリからmake fetch



コマンドでダウンロードできます。 必要な素材をfiles/



ロードしfiles/



firmware/bootcode.bin



firmware/config.txt



およびfirmware/start.elf



をmicroSDカードのルートにコピーしfirmware/bootcode.bin



。 前の部分からact-led-blink.bin



コピーし、名前をkernel8.img



に変更kernel8.img



ます。 したがって、すべてが機能することを確認できます。 そこで、ラズベリー自体の緑色のLEDが点滅します。







更新されたvolatile





os



フォルダーにあるこのライブラリーは、フェーズ2で検討したコードとは少し異なります。変更により、デバイスドライバーの作成のコンテキストでこれを少し簡単に使用できるようになります。 主な違いは次のとおりです。







  1. UniqueVolatile



    Unique<Volatile>



    置き換えられました
  2. Reserved



    タイプを追加しました。これは絶対に何もできず、スタブとして使用されます


もう1つ、より重要な違いがあります。 ライブラリのすべての型は、 *mut T



T



ではなくT



ラップします*mut T



これにより、ラッピングせずにあらゆる種類の生のアドレスを使用し、次のようにキャストできます0x1000 as *mut Volatile<T>



。 これに加えて、 Volatile



ラップされたフィールドを含む構造を指定できます。 このようなもの:







 #[repr(C)] struct Registers { REG_A: Volatile<u32>, REG_B: Volatile<u8> } //    .  `Registers`   `0x4000`  . //     `u32`     `u8`      // (. ). let x: *mut Registers = 0x4000 as *mut Registers; //       `unsafe`. //   Rust        unsafe { //   Rust      (*x).REG_A.write(434); let val: u8 = (*x).REG_B.read(); }
      
      





#[repr(C)]



とは何ですか?




追記#[repr(C)]



は、RustがSmallと同じ方法でメモリ内に構造を形成するように強制します。 これがなければ、Rustにはフィールドの順序とフィールド間のインデントを最適化する権利があります。 生のポインタを使用する場合、ほとんどの場合、メモリ内の特定の構造を意味します。 したがって、 #[repr(C)]



使用すると、Rustが想定どおりに構造をメモリに配置することをアサートできます。


コア



os/kernel



ディレクトリには、OSのカーネルコードの準備が含まれています。 このディレクトリ内のmake



呼び出しは、カーネルを収集します。 アセンブリの結果はbuild/



サブディレクトリにあります。 このビジネスを開始するには、 build/kernel.bin



kernel8.img



という名前でmicroSDカードのルートにコピーbuild/kernel.bin



必要があります。 現在、カーネルは何もしていません。 このフェーズの終わりまでに、カーネルにはチャット可能なテキストベースの対話型シェルが含まれます。







kernel



ラックはpi



ラックに依存します。 extern crate pi;



を見ることができますextern crate pi;



kernel/src/kmain.rs



に記述しCargo.toml



。 つまり pi



宣言されたすべての型と構造を非選択的に使用できます。







ドキュメント



ドライバーを作成するとき、 BCM2837ペリフェラルマニュアルは非常に役立ちます。







サブフェーズB:システムタイマー





このサブフェーズでは、組み込みタイマーのドライバーを作成します。 主な作業は、ファイルos/pi/src/timer.rs



およびos/kernel/src/kmain.rs



ます。 タイマーは、 BCM2837周辺機器マニュアルの 172ページ(セクション12)に記載されています。







最初に、すでにos/pi/src/timer.rs



あるコードを見てos/pi/src/timer.rs



。 少なくともこれらの部分:







 const TIMER_REG_BASE: usize = IO_BASE + 0x3000; #[repr(C)] struct Registers { CS: Volatile<u32>, CLO: ReadVolatile<u32>, CHI: ReadVolatile<u32>, COMPARE: [Volatile<u32>; 4] } pub struct Timer { registers: &'static mut Registers } impl Timer { pub fn new() -> Timer { Timer { registers: unsafe { &mut *(TIMER_REG_BASE as *mut Registers) }, } } }
      
      





最初に注意する必要があるunsafe



unsafe



コードの1行があります。 *mut Registers



のアドレスTIMER_REG_BASE



はこの行にキャストされ、その後すぐに&'static mut Registers



に変わります。 実際、 TIMER_REG_BASE



の構造への静的リンクが必要であることをTIMER_REG_BASE



ています。







TIMER_REG_BASE



には正確に何がありますか? マニュアルの 172ページで、 0x3000



がタイマーの周辺の先頭からのオフセットであることがわかります。 つまり TIMER_REG_BASE



は、このタイマーのレジスタが始まるアドレスです。 unsafe



unsafe



1行の後に、 registers



フィールドを使用して、これらすべてに完全に安全にアクセスできます。 たとえば、 self.registers.CLO.read()



self.registers.CLO.read()



CLO



レジスタをself.registers.CLO.read()



か、または

self.registers.CS.write()



を使用するCS









なぜCLO



およびCHI



レジスタに書き込めないのですか?
[制限付き読み取り]



BCM2837のドキュメントには、 CLO



およびCHI



レジスタが読み取り専用であると記載されています。 コードはこのプロパティを提供します。 どうやって? 何がCLO



CHI



書くことを妨げますか?





安全ではないものは何ですか?



要するに、 unsafe



はRustコンパイラーのマーカーであり、ユーザーはメモリーのセキュリティを制御していると言います。 コンパイラーは、これらのコードのメモリー問題からユーザーを保護しません。 コードのunsafe



部分では、Rustを使用して、Neat Cで実行できるすべての操作を実行できます。 できる コラバンを奪う あるタイプを別のタイプにキャストし、生のポインターで遊んで、寿命を作成するだけで十分です。



ただし、 unsafe



ブロック内のコードは非常に危険です。 安全でないセクションで行うことは、実際に安全であることを確認する必要があります。 これは一見思われるよりも複雑です。 特に、Rustのセキュリティ概念は他の言語よりも厳格であるためです。 unsafe



でないものを使用しunsafe



してください。 もちろん、可能な範囲で。 オペレーティングシステムなどの場合、機器と直接通信する場合は、 unsafe



を使用する必要があります。 ただし、この使用はできる限り制限します。



unsafe



モアレを読みたい場合は、Nomiconの第1章をご覧ください 。 さらに、この小さな本を通して、Rustのさまざまな強力な魔術師に役立つ多くのことを学ぶことができます。


ドライバーの実装



os/pi/src/timer.rs



Timer::read()



os/pi/src/timer.rs



からTimer::read()



実装しos/pi/src/timer.rs



。 次に、メソッドcurrent_time()



spin_sleep_us()



spin_sleep_ms()



が近くにあります。 これらの関数のコメントと名前は、期待される機能を示しています。 Timer::read()



を実装するには、適切なセクションのBCM2837ドキュメントを読む必要がありTimer::read()



。 少なくとも、64ビットタイマー値全体を取得するために読み取る必要があるレジスタを理解する必要があります。 cargo build



して、パイクレートを組み立てることができます。 ただし、 cargo check



を使用して記述された内容の正確性を単純にチェックする方が高速です。







ドライバーテスト



spin_sleep_ms()



関数が正しく実装されていることを確認することはspin_sleep_ms()



です。 これを行うには、 kernel/src/kmain.rs



適切なコードを記述しkernel/src/kmain.rs









ゼロラボのフェーズ4からLEDの点滅コードをコピーします。 ループ内でスピンするだけのスリープ関数の代わりに、 spin_sleep_ms()



関数を使用して、点滅間の休止を作成する必要があります。 カーネルを再コンパイルし、 kernel8.img



というメモリカードにロードします。 すべてを実行して、計画した頻度でLEDが点滅することを確認します。 別の遅延サイズを設定して、すべてが機能することを確認してください。 はい、microsdカードを常に前後に突くのは非常に面倒です。 このパートの終わりまでに、この問題を解決するブートローダーができます。







タイマーのドライバー実装が機能する場合、次のサブフェーズに進むことができます。







サブフェーズC:GPIO



このサブフェーズでは、特定のピン番号に関係なく、汎用GPIOドライバーを作成します。 主な作業は、ファイルos/pi/src/gpio.rs



およびos/kernel/src/kmain.rs



ます。 GPIOのドキュメントは、 BCM2837周辺機器マニュアルの 89ページ(セクション6)にあります







ステートマシン





すべてのハードウェアデバイスは、本質的にステートマシンeng )と見なすことができます。 それらはある状態で初期化され、他の状態に明示的またはそうではありません。 同時に、デバイスは現在の状態に応じてさまざまな機能を提供します。 つまり、特定の特定の状態では、他の状態への特定の遷移セットのみが動作可能です。







ほとんどのプログラミング言語では、有限状態マシンのセマンティクスを正確に追跡することは不可能です。 しかし、もちろんこれはRustには当てはまりません。 Rustを使用すると、このセマンティクスを非常に明確に追跡できます。 これを使用して、より安全なGPIOドライバーを実装します。 ドライバーは、すべてのGPIOピンが常に正しく使用されるようにします。 コンパイル段階。







* ある種の研究のように見える...



あなたは私を捕まえた。 これは本質的に、現時点での私の研究分野です。 - セルジオ

以下に、GPIOステートマシンプロパティのサブセット(1ピン)の状態図を示します。









私たちの目標は、これらすべてをRustで実装することです。 そもそも、sobsnaは次の図を教えてくれます。









ラボ0ではどのようなトランジションを使用しましたか? [まばたき状態]



Lab 0からフェーズ4のコードを記述したとき、本質的にステートマシンのサブセットを暗黙的に実装しました。 この場合、状態間のどのような遷移が実現しましたか?

Rustタイプシステムを使用して、ピンがOUTPUT



状態にある場合にのみSET



およびCLEAR



INPUT



状態にある場合にLEVEL



のみが可能であることを保証します。 pi/src/gpio.rs



GPIO



構造宣言を見てください:







 pub struct Gpio<State> { pin: u8, registers: &'static mut Registers, _state: PhantomData<State> }
      
      





構造体には、 State



という一般的な引数が1つあります。 PhantomData



のみが使用し、他のPhantomData



は使用しません。 実際、そのようなPhantomDataのために、構造が何らかの形で一般化された引数を使用していることをRustに納得させるために存在します。 Gpio



状態のマーカーとしてState



を使用Gpio



ます。 同時に、このパラメーターの特定の値を作成できないようにする必要があります。







マクロstate!



そこにあると思われる型を生成しますが、作成することはできません。 この場合、 Gpio



の状態のリストが生成さGpio



ます。







 states! { Uninitialized, Input, Output, Alt } //        -  : enum Input { }
      
      





変に見えます 。 可能な値なしで列挙を作成する必要があるのはなぜですか? 彼らは1つの快適なプロパティがあります。 作成できません。 ただし、マーカーとして使用できます。 Input



型の値は作成できないため、だれも渡すことができません。 それらはタイプのレベルでのみ存在し、他のどこにも存在しません。







次に、適切な遷移セットを使用して各状態のメソッドを実装できます。







 impl Gpio<Output> { ///   pub fn set(&mut self) { ... } ///   pub fn clear(&mut self) { ... } } impl Gpio<Input> { ///      pub fn level(&mut self) -> bool { ... } }
      
      





これは、 Gpio



が状態に応じて厳密に定義された方法でのみGpio



できるという保証のように見えます。 悪くないでしょ? しかし、これらの状態をどのように達成するのでしょうか? これを行うには、 Gpio::transition()



メソッドがあります。







 impl<T> Gpio<T> { fn transition<S>(self) -> Gpio<S> { Gpio { pin: self.pin, registers: self.registers, _state: PhantomData } } }
      
      





この方法を使用すると、 Gpio



をある状態から別の状態に簡単かつ自由に転送できます。 状態T



Gpio



を取得し、状態S



Gpio



Gpio



S



すべてのS



およびT



で機能することに注意してくださいT



このメソッドは非常に慎重に使用する必要があります。 このすべてに間違いを犯した場合、ドライバーは誤って記述されていると見なされます。







transition()



を使用するには、 Gpio<S>



タイプS



を指定する必要があります。 Rustに十分な情報を提供して、Rustがすべて自分で取得できるようにします。 たとえば、 into_output



メソッドの実装:







 pub fn into_output(self) -> Gpio<Output> { self.into_alt(Function::Output).transition() }
      
      





このメソッドでは、戻り値の型がGpio<Output>



ことが必要です。 Rust型システムはtransition()



呼び出しを調べると、 Gpio::transition()



メソッドをGpio<Output>



ます。このメソッドはGpio<Output>



を返します。 彼は、任意のS



存在するGpio<S>



を返すメソッドを見つけますS



したがって、 S



代わりにS



Output



を安全に置き換えることができます。 その結果、 Gpio<Alt>



into_alt



関数から)をGpio<Output>



に変換します。







クライアントが任意の状態を渡すことができる場合はどうなりますか? [偽の状態]



ユーザーコードがGpio



構造の初期状態を自由に選択できるようにするとどうなるかを考えてください。 何がおかしいのでしょうか?





なぜこれがすべてRustでしかできないのですか?



into_



遷移は移動のセマンティクスを使用するという小さな事実に注意してください。 これは、 Gpio



が別の状態になるとすぐに、以前の状態ではアクセスできなくなることを意味します。 型がClone



Copy



およびその他の複製メソッドを実装するまで、逆遷移は使用できません。 他の言語ではそれができません。 C++



でも。 すべての保証付きのコンパイル中のそのような魔術師はここだけです。 (プラスや他の何かの達人は、この声明に挑戦しようとすることができます)


ドライバーの実装



unimplemented!()



代わりに必要なコードをすべて書いてunimplemented!()



ファイルpi/src/gpio.rs



これらすべての方法のコメントと署名から、理解できます 控除を使用して 期待される機能。 ドキュメント(89ページ、 BCM2837マニュアルのセクション6)を参照することは不要です。 cargo check



の有用性を忘れないでcargo check









ヒント:中括弧{ ... }



を使用して任意の字句スコープを作成できることを忘れないでください。


ドライバーテスト



明らかに、ドライバーをテストするには、 kernel/src/kmain.rs



いくつかのコードを書く必要がありkernel/src/kmain.rs









今回は、レジスタ自体を直接読み書きする代わりに、LEDを点滅させるためにドライバーを使用します。 GPIOピン番号16をオン/オフすることにより、同時に、コード全体がよりクリーンでエレガントになります。 カーネルをコンパイルし、 kernel8.img



というカードにロードして、 kernel8.img



ラズベリーを実行します。 LEDは以前とまったく同じように点滅するはずです。







これで、より多くのLEDを接続できます。 5、6、13、19、26の番号が付けられたGPIOピンを使用します。ゼロラボのピンの番号付きの図を参照して、物理的な位置を確認します。 あなたが望むように多くのLEDでコアを点滅させてください!







どの点滅パターンを選択しましたか? [LEDパターン]



LEDをオン/オフすることにしたスキームは何ですか? 好みに合わせて多くのオプションから選択できます。 ただし、選択が厳しい場合は、サークルでオンとオフを切り替えることができます。 画像

GPIOドライバーが完全に動作可能になったら、次のサブフェーズに進むことができます。







サブフェーズD:UART



このサブフェーズでは、ミニUARTデバイスドライバーを作成します。これは、ラズベリーの割合に組み込まれています。 ほとんどの作業は、ファイルos/pi/src/uart.rs



およびos/kernel/src/kmain.rs



ます。 Mini UARTは、 BCM2837マニュアルの 8ページと10ページ(セクション2.1と2.2)に記載されています







UART:ユニバーサル非同期RX / TX



UARTruまたはUniversal Synchronous Reception Transmitterは、腺を2本のワイヤで通信するためのデバイスおよびシリアルプロトコルです。 これらは、CP2102 USBモジュールのUARTデバイスをラズベリーのUARTデバイスに接続するために、ゼロラボのフェーズ1で使用されたものと同じ2つの配線(rx / tx)です。 UARTを介して任意のデータを送信できます。テキスト、バイナリファイル、シール付きの写真など、ファンタジーに十分なものです。 例として、次のサブフェーズで、ラズベリーのUARTから読み取り、CP2102のUARTに書き込む対話型シェルを作成します。 フェーズ4では、ほぼ同じ方法でバイナリ情報を送信します。







UARTプロトコルにはいくつかの構成パラメーターがあります。 すべてが機能するためには、受信機と送信機の両方を同一に構成する必要があります。 これらは次のパラメーターです。









Mini UARTはパリティビットをサポートせず、1つのストップビットのみをサポートします。 したがって、ボーレートとフレーム長を設定するだけです。 UART通信の基本 (翻訳が必要ですか?)というドキュメントで、UART自体についてもう少し読むことができます。







ドライバーの実装



この段階では、各ステップをペイントすることなくデバイスドライバーを作成するために必要なすべてのツールが用意されています。 おめでとうございます!







タスクは、必要なすべてをファイルpi/src/uart.rs



に実装することです。 Registers



構造のコンテンツを追加する必要があります。 この場合、各レジスタに最低限必要な機能のセットを備えた[ Volatile



タイプ]オプションを使用します。 これらの読み取り専用レジスタはReadVolatile



を使用する必要があります。 WriteVolatile



のみWriteVolatile



許可されている場合、 WriteVolatile



。 予約スペースにはReserved



ます。 new()



115200 ( 270) 8 . unimplemented!()



, . fmt::Write



, io::Read



io::Write



MiniUart



.







: LCR



, BAUD



CNTL



new



/



: GPIO .




, ( kernel/src/kmain.rs



), . :







 loop { write_byte(read_byte()) }
      
      





screen /dev/<_> 115200



UART. screen



TTY . , . つまり . :







 loop { write_byte(read_byte()) write_str("<-") }
      
      





, — .







E: The Shell



UART . os/kernel/src/console.rs



, os/kernel/src/shell.rs



os/kernel/src/kmain.rs



.







Console







, , - / . Unix stdin



stdout



. Console



. Console



kprint!



kprintln!



。 , print!



println!



。 . Console



, .







os/kernel/src/console.rs



. Console



. - MiniUart



. . . MiniUart



MiniUart



Console



.









— , . Rust. Rust . , ? , unsafe



. : Rust'y, , .. , "" . , . Rust . :







 //      ! fn make_mut<T>(value: &T) -> &mut T { unsafe { /*      */ } }
      
      





. . , , unsafe



Rust. , . " ". つまり . .







, . , . , ( &



). , ( &T -> &mut



).







. , , :







 fn lock<T>(value: &T) -> Locked<&mut T> { unsafe { lock(value); cast value to Locked<&mut T> } } impl Drop for Locked<&mut T> { fn drop(&mut self) { unlock(self.value) } }
      
      





Mutex . — , :







 fn get_mut<T>(value: &T) -> Mut<&mut T> { unsafe { if ref_count(value) != 0 { panic!() } ref_count(value) += 1; cast value to Mut<&mut T> } } impl Drop for Mut<&mut T> { fn drop(&mut self) { ref_count(value) -= 1; } }
      
      





, RefCell::borrow_mut() . — , :







 fn get_mut<T>(value: &T) -> Option<Mut<&mut T>> { unsafe { if ref_count(value) != 0 { None } else { ref_count(value) += 1; Some(cast value to Mut<&mut T>) } } } impl Drop for Mut<&mut T> { fn drop(&mut self) { ref_count(value) -= 1; } }
      
      





RefCell::try_borrow_mut() . " ": . Console



Mutex



. std::Mutex



— . . kernel/src/mutex.rs



. , , , Rust. Mutex



, , .







だから。 CONSOLE



kernel/src/console.rs



. kprint!



kprintln!



, . , Console



Console



. CONSOLE



Console



.







Rust Sync



.




T



static



, T



Sync



. , Rust . , Rust , . Send



Sync



, Rust .





** &mut T



? [drop-container]



, , Drop



. , &mut T



?





write_fmt



?
[write-fmt]



_print



write_fmt



MutexGuard



( Mutex<Console>::lock()



. write_fmt



?


Console





, unimplemented!()



kernel/src/console.rs



. kprint!



kprintln!



, kernel/src/kmain.rs



, , . , print!



println!



screen /dev/<-> 115200



.







...



println!



— Rust. printf



. Rust , , . . , ? .





: Console



: .




完成品







. kernel/src/shell.rs



. Command



. Command::parse()



Command



. parse



args



StackVec



, buf



. Command::path()



.







( Command



, StackVec



, Console



CONSOLE



, kprint!



, kprintln!



, ) shell



. prefix



, . "> "



. , . ad-infinitum . . echo



.







, :









shell



. kernel/src/kmain.rs



. SOS, , . , . — . .







:



b'a'



u8



'a'







\u{b}



ASCII b







\r



\n







, backspace, , backspace



StackVec







std::str::from_utf8





std



!




, std



. . , , xargo doc --open



os/std



.





? [shell-lookback]



. , .


4:



, , Raspberry Pi. os/bootloader/src/kmain.rs









, MicroSD- . , , . , .







— "", , XMODEM UART. , . ttywrite



. :











Raspberyy Pi 3 kernel8.img



0x80000



. , , kernel8.img



0x80000



ARM' (program counter) 0x80000



. , . , 0x80000



.







(linker, ). . : , . , . os/kernel/ext/layout.ld



( ). , 0x80000



. 0x80000



.







, , 0x80000



. . 0x80000



. つまり ! . . . . どうやって?









. os/bootloader/ext/layout.ld



, , 0x4000000



. , 0x80000



. kernel_address



config.txt



. bootloader/ext/config.txt



. , . つまり MicroSD-.







0x80000



0x4000000



"" .







63.5 ? [small-kernels]



, , , . — . , . ?



, . macOS - /System/Library/Kernels/kernel



. /mach_kernel



. Linux - /boot/



vmlinuz



, vmlinux



bzImage



. ? 63.5 ?




bootloader/src/kmain.rs



. , , . const



. jump_to



, addr



. . pi



xmodem



UART, , . , .







, XMODEM, ( 750 ). . — . , — . os/kernel/build/kernel.bin



ttywrite



. — , screen



.







? [bootloader-timeout]



. ?





config.txt



, !



:



kmain()



15 .



std::slice::from_raw_parts_mut .



&mut [u8]



io::Write



.

UPD








All Articles