きっかけ・読んでいく本
うさねこらーじの Discord サーバーで、いろいろな先輩たちに技術のことを教えてもらっています。
https://discord.gg/RMq7e5qbQj
このなかで、著者の garasubo さんにサポートしてもらいながら以下の本の内容を実装しているのですが、 Rust の知識も OS の知識もない私にとっては内容がかなり高度なので、補助のためにこのブログを作成しています。
Rustで始める自作組込みOS入門
https://amzn.asia/d/ebuOUM1
ぜひこの本を読みながらこのブログを参照していただけると嬉しいです。
第 2 章 ベアメタルで Hello World
単語のかみくだいた説明
ベアメタルプログラミング
OSのない環境で実行するためのプログラムを書くことです。この環境下では CPU ごとに決まった動作(たとえば、決まったアドレスにあるプログラムを実行する)が行われます。
例外ハンドラ
例外ハンドラは、例外が発生したときにジャンプする命令のアドレスを記した配列のことです。
ベクターテーブル
Armv7-M のマニュアル https://developer.arm.com/documentation/ddi0403/latest/ によると以下のようになっています。
スタックポインタや、例外ハンドラのエントリーポイントのアドレスを含んでいます。
スタックポインタの初期値と例外ハンドラを含めることで、リセット時やエラー時の挙動を定義しています。
どこに置かれているかはチップの実装によります。
リンカ・リンカスクリプト
リンカはコンパイルにより生成された機械語の断片たちを適切な場所に配置していく作業を行います。イメージとしては、高水準言語のなかで関数名による呼び出しなどを行っていたところを、機械語の断片がある実際のアドレスに置き換えていくという感じです。リンカスクリプトは、ベアメタルプログラミングをする際にメモリのレイアウトやプログラムをそこにどうやって配置するかを定義することで、リンカが関数のポインタなどを正しく使えるようにしているイメージです。今回は link.ld
がそれに当たります。
マングリング
Rust はコンパイラされるときに関数名を別の名前(シンボル)に変更します。これをマングリングといいます。そのため、 Rust の外にエクスポートする関数名はこの処理を行わないようにコンパイラに支持する必要があります。
Rust の記法のうちコードを読むときに必要なものの紹介
- 関数の返り値が
!
で示されているとき、その関数は終了することがありません(発散します)
link.id
の説明
SECTIONS
{
.vector_table ORIGIN(FLASH) :
{
LONG(ORIGIN(RAM) + LENGTH(RAM)); //(i)
KEEP(*(.vector_table.reset_vector)); //(ii)
} > FLASH
(i)...RAM 領域の末尾を指定して、スタックポインタの初期値を配置しています。スタックポインタは末尾から先頭に向かって伸びていくので、このように RAM 領域の先頭ではなく末尾から始めています。
(ii)...KEEP() はリンカによるリンカ省略を防ぐための関数です。リンカ省略は、使っていない部分のコードをプログラムに含めないようにすることです。使っている関数をちょうど木のように (これを dependency tree といいます) 辿っていって、到達できるところのみをプログラムに含めます。 KEEP() は、中に入れた関数をこの tree の根本としてマークするようなイメージです。
第 3 章 割り込み制御
単語のかみくだいた説明
ベリフェラル
直訳では周辺機器となりますが、マイコンにおいては内蔵された様々な装置を意味します。LCDや無線モジュールなど。
疑問解決
スタック領域に保存されないレジスタの値ってどうなるの?
例外が発生するとき、LR, R12, R3 - R0 がハードウェアにより自動的にスタック領域に退避されますが、残りの R4 - R11 はそうされません。これらの範囲は、Arm の関数の呼び出し規約により、関数を呼び出したら関数側で値を戻す必要があります。
(なんでそうなっているのか知りたい)
第 4 章 プロセス切り替え
単語のかみくだいた説明
スレッドモード/ハンドラーモード and カーネルモード
スレッドモードは通常のプログラムが実行されている状態で非特権状態になることが可能、ハンドラーモードが例外が発生して例外ハンドラに移動している状態で特権状態になる。
カーネルモードは2つとは全く別で、通常よりも権限の強い状態で CPU 上で動くこと(第一章)。本CPUでは触れなくて良い。
2 種類のスタックポインタ
Armv7-M ではメインとプロセスの 2 種類のスタックポインタを使い分けることができます。
Two stack pointer (SP) registers, SP_main and SP_process. These are banked versions of SP, also described as R13.
複雑なポインタと参照の操作部分
let context_frame: &mut ContextFrame = &mut *(ptr as *mut ContextFrame);
ここでは、ptr の位置のデータを ContextFrame 型として取り出しています。
まず、ptr as *mut ContextFrame
でポインタ ptr の位置を ContextFrame 型のポインタとして認識して、それに*
をつけることで中の値を取り出し、それに対して参照を生成してそれを context_frame に束縛しています。つまり、ポインタを参照に変換しています。
Rust: 'a
ってなに?
これを読めばわかります。
https://doc.rust-jp.rs/book-ja/ch10-03-lifetime-syntax.html
Rust: Deref ってなに?
これを読めばわかります。
https://doc.rust-jp.rs/book-ja/ch15-02-deref.html