[作って学ぶ]OSのしくみI──メモリ管理、マルチタスク、ハードウェア制御

UEFI 向けのバイナリターゲットもあるのか
$ rustup target list | grep uefi
aarch64-unknown-uefi
i686-unknown-uefi
x86_64-unknown-uefi

DevContainer で環境構築

vscode ➜ /workspace $ qemu-system-x86_64 -bios third_party/ovmf/RELEASEX64_OVMF.fd
gtk initialization failed
UI ツールキットの SDK があってそれが初期化できないみたい

色々試したけど、X11 Forwarding して GUI を表示するのが良さそう
VNC で見る方法もあるみたい

XQuartz
The XQuartz project is an open-source effort to develop a version of the X.Org X Window System that runs on macOS
X Window System は LINUX などの UNIX 系 OS で使用されているウィンドウシステムを提供する表示プロトコル
GUI を構築するのに使われる
現在のバージョンが2012年6月6日にリリースされた X11 で、通称 X11 と呼ばれることもあるみたい
brew install
$ brew install --cask xquartz

"mounts": [
"source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached"
],
"runArgs": [
"--privileged"
],
"containerEnv": {
"DISPLAY": "unix:0"
}
vscode ➜ /workspace $ qemu-system-x86_64 \
-drive file=./third_party/ovmf/RELEASEX64_OVMF.fd,if=virtio \
-net nic,model=virtio \
-vga virtio \
-display gtk,grab-on-hover=on,gl=on \
-boot once=d -no-reboot
qemu-system-x86_64: OpenGL is not supported by the display
上記の .devcontainer.json だと OpenGL is not supported by the display
macOS の場合の指定が違ってそう
"containerEnv": {
"DISPLAY": "docker.for.mac.host.internal:0"
},
vscode ➜ /workspace $ qemu-system-x86_64 \
-drive file=./third_party/ovmf/RELEASEX64_OVMF.fd,if=virtio \
-net nic,model=virtio \
-vga virtio \
-display gtk,grab-on-hover=on,gl=on \
-boot once=d -no-reboot
Couldn't open libEGL.so.1: libEGL.so.1: cannot open shared object file: No such file or directory
[1] 794 IOT instruction qemu-system-x86_64 -drive file=./third_party/ovmf/RELEASEX64_OVMF.fd,if=virti
libEGL 周りのパッケージを入れる
RUN .....
libegl1-mesa \
libglvnd0 \
libgles2

qemu-system-x86_64 \
-drive file=third_party/ovmf/RELEASEX64_OVMF.fd,if=virtio \
-net nic,model=virtio \
-vga virtio \
-display gtk,grab-on-hover=on \
-boot once=d -no-reboot
UEFIファームウェアのファイル (RELEASEX64_OVMF.fd) をディスクとして定義しようとしてるみたい
-pflash
でファームウェアを指定するのが正しそう
-pflash file
Use file as a parallel flash image.
The kernel options were designed to work with Linux kernels although other things (like hypervisors) can be packaged up as a kernel executable image. The exact format of a executable image is usually architecture specific.
The way in which the kernel is started (what address it is loaded at, what if any information is passed to it via CPU registers, the state of the hardware when it is started, and so on) is also architecture specific. Typically it follows the specification laid down by the Linux kernel for how kernels for that architecture must be started.
qemu-system-x86_64 \
-pflash ./third_party/ovmf/RELEASEX64_OVMF.fd \
-net nic,model=virtio \
-vga virtio \
-display gtk,grab-on-hover=on \
-no-reboot
きた

DevContainer 立ち上げ前に xhost +localhost
をホスト側で実行しておく必要あり
X Window System にローカルホストからのアクセスを許可する
恒常的に立ち上げておきたければ、shell 立ち上げ時に実行しておくと良いのかも
Hello World!
これでいけてそう
qemu-system-x86_64 \
-bios ./third_party/ovmf/RELEASEX64_OVMF.fd \
-drive format=raw,file=fat:rw:mnt \
-net nic,model=virtio \
-display gtk,grab-on-hover=on

HLT 命令
x86 CPUの機械語の一つで、CPUを割り込みが来るまで休ませる命令

Rust の話
Rust の trait ってインターフェースみたいなもんなのか
トレイトは、Rustコンパイラに、特定の型に存在し、他の型と共有できる機能について知らせます。 トレイトを使用すると、共通の振る舞いを抽象的に定義できます。トレイト境界を使用すると、 あるジェネリックが、特定の振る舞いをもつあらゆる型になり得ることを指定できます。

x86_64 は 64 bit 幅で、メモリ空間は1ビットあたり2通りだから、2^64 = 約16EB 扱える

UEFI にはメモリの詳細を取得できる API があるみたい

Rust の話
クレートはバイナリクレートとライブラリクレートの2種類ある
- バイナリクレートはsrc/main.rs を頂点とするクレートで、これをもとに実行ファイルが生成される
- cargo run をしたときに最終的に生成するもの
- ライブラリクレートはsrc/lib.rs を頂点とするクレートで、これはほかのクレート(バイナリでもライブラリでも)から利用するためのクレートであり、実行ファイルを直接生成することはない
- これらのクレートはあくまでも別のクレートであり、バイナリクレートをビルドする際には、wasabi というバイナリクレートがwasabi というライブラリクレートに依存する、という形でビルドされる
- src/main.rs から呼び出す場合は、ライブラリクレートwasabi の中の関数を呼ぶた
めwasabi:: から始まるパスを使用 - それ以外のライブラリクレートに属するコードから自分自身のクレートの関数を呼ぶためには、自身のクレートを表すcrate:: から始まるパスを使用

Rust の話
no_std だとテストクレート使えない

Rust の話
GlobalAlloc

CPU はメモリバスという信号線を介してメモリ上のデータを読み書き
光の速度で信号をやり取りできたとして、1ナノ秒でおよそ30cm程度
光の速度は 299792458 m/s(約30万km毎秒) ≒ 0.3Gm/s
1Gは10^9、1ナノ秒は10^-9なので、0.3 * 10^9 m / 10^-9 * ns => 0.3 m / ns
情報伝達速度の限界

CPU はクロックという周期的な信号に合わせて計算
CPU のクロックは近年3GHzが多くなってきてるので、1秒あたり3 * 10^9 = 30億命令サイクルくらい回せる。
命令サイクル => CPU がメモリから命令をフェッチ・デコード・実行する一連のサイクル
大体のコンピュータの時間感覚を掴むための gist
CPU 内部で計算するよりメモリとやりとりする方が圧倒的に時間がかかる。
それを効率化するためにキャッシュメモリかなと思ってたけど、メモリバスで1度に転送できるデータ量を増やすというのも確かに。
現代のコンピュータのメモリバスの幅は64bit幅が多い
キャッシュに記憶されるメモリ上の領域の最小単位のことをキャッシュラインと言い、その大きさのことはキャッシュラインサイズと呼ぶ

方式 | 仕組み | 利点 | 欠点 |
---|---|---|---|
フルアソシエイティブ | 要求されたアドレスを、キャッシュ内の全てのラインと比較する。 | 格納場所に制限がなく、キャッシュヒット率が最も高くなる可能性がある。 | 比較回路が非常に大規模で高コスト。消費電力も大きい。 |
ダイレクトマッピング | メモリアドレスから格納先のキャッシュラインを一意に決定する。 | 回路が単純で高速。低コストで実装できる。 | 違うデータでも格納先が同じだと衝突し、上書きが頻発することがある。 |
セットアソシエイティブ | アドレスから**グループ(セット)を特定し、その中の複数のライン(ウェイ)**から格納場所を探す。 | 性能とコストのバランスが良い。ダイレクトマッピングの衝突問題を緩和できる。 | ダイレクトマッピングよりは回路が複雑になる。 |
この画像分かりやすい
CPU に近い順から、L1、L2、L3

キャッシュラインを跨ぐケース
struct Data {
long long b; // 8バイト
};
みたいなデータがあったとして、b のデータがキャッシュライン0と1にそれぞれ格納されているケースを考える。
b のデータを扱うためにキャッシュライン0と1と参照しないといけない。キャッシュライン0に格納されていれば、それだけ見れれば良い
読みたいバイトが同一のキャッシュラインに収まるようにすることをアラインメント
2バイトなら2の倍数のアドレスに、3バイトか4バイトなら4の倍数のアドレスに、5から8バイトなら8の倍数のアドレスに……というように、一まとめにアクセスしたいバイト数より大きい、最小の2のべきのバイト数の倍数になるようアドレスを調節してあげることが、CPUにとってやさしいデータ配置になる

シリアルポートというのは、比較的古くからコンピューターに存在する入出力インタフェースの一つです。ほかの入出力に比べて簡単に扱うことができるため、現在でもデバッグ用に広く用いられています

Non-volatile DIMM(NVDIMM)の不揮発メモリについて

ページ管理
そもそもページとは?
メモリの管理単位のこと、x86_64 では4KB
ページテーブルにおける変換処理
x86_64 では、一つのページテーブル構造体は4KBの大きさとアライメントを持つ。
CPUがアドレス変換を行う際に起点として参照するページテーブルでは、仮想アドレス空間全体を大雑把に分割して管理。
アドレスは64ビット、つまり8バイトなので、一つのページテーブルは次の段階へのポイ
ンタを512個書き並べたものになっている
そして、分割された各部分の管理は、さらに次の段階のページテーブルによってもう少し細かく管理。これを何回か繰り返して、最終的に管理する領域の範囲がページサイズになっ
たら、そこで変換は終了
Huge Page について
仮想アドレスを物理アドレスに変換してメモリアクセスするが、そのマッピングテーブルをページテーブルと呼ぶ。Memory Management Unit が変換処理を行う
ページテーブルからメモリを読み込んで変換するのは時間がかかる => 最近使った変換情報を TLB(Translation Lookaside Buffer) にキャッシュとして保存して使う
大量のメモリが必要な情報を扱おうとすると、TLB でキャッシュヒットしない場合がある => ページテーブル参照のコスト
これを解決するのが Huge Page => 基本的には4KBのページサイズを増やして、キャッシュヒット率上げようぜということ
4KB/ページの場合、1GB のメモリを管理するには、1GB/4KB 大体 25万個のページエントリ(1つのページに対応するデータ)が必要
2MB/ページの場合、1GB/2MB = 312 ページエントリになる => TLB キャッシュヒット率が向上
hugetlbpage support
標準的な Huge Page で、事前にページサイズを上げておく方法
THP: Transparent Huge Pages
事前にページサイズを上げなくても動的にページサイズを確保する方法

ページテーブルを見る
...
entry[ 63] = L3Entry @ 0x00000000bfc021f8 { 0x00000000BFC42003 PWS }
}
Some(L2Table @ 0x00000000bfc03000 {
entry[ 0] = L2Entry @ 0x00000000bfc03000 { 0x00000000000000E3 PWS }
...
entry[511] = L2Entry @ 0x00000000bfc03ff8 { 0x000000003FE00083 PWS }
}
)
None
各 Entry[0] は次のページテーブルへのアドレスが格納
L2Entry[0] の 0x00000000 から 物理アドレス 0x00000000から始まる 2MiB の領域にマッ
ピングされている

割り込みについて
TSS(Task State Segment)
IST
GDT