# 第6章: メモリ保護
MPU(Memory Protection Unit:アクセスしてよいメモリの範囲を CPU が見張る仕組み)を使ってタスク同士のメモリを守り、暴走を検出します。
:::{admonition} この章で分かること: アプリが落ちても、OS が落ちないのはなぜ?
:class: tip
PC では、アプリが 1 つ落ちても、OS やほかのアプリは動き続けます。
OS が、おかしなことをしたプログラムだけを終わらせているからです。
この章では、ゼロ除算や、存在しないアドレスへの書き込みで CPU が起こす例外
(フォールト)を OS が受け止めて、起こしたタスクだけを止めます。
ほかのタスクは何事もなかったように動き続けます。
:::
## サンプルコード
- ディレクトリ: `docs/os-on-arduino/code/06_memory_protection`
- ビルドと書き込み:
```bash
uv run pio run -d 06_memory_protection -t upload
uv run pio device monitor -b 115200
```
コードを見る: [docs/os-on-arduino/code/06_memory_protection](https://github.com/iory/learning-os-from-arduino/tree/main/docs/os-on-arduino/code/06_memory_protection)
## ソースコード
この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。
:::{dropdown} `src/main.cpp` — 141 行
:icon: code
[GitHub で開く](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/06_memory_protection/src/main.cpp)
```{literalinclude} ../../../../code/06_memory_protection/src/main.cpp
:language: cpp
:linenos:
```
:::
:::{dropdown} `include/os_fault.h` — 46 行
:icon: code
[GitHub で開く](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/06_memory_protection/include/os_fault.h)
```{literalinclude} ../../../../code/06_memory_protection/include/os_fault.h
:language: cpp
:linenos:
```
:::
:::{dropdown} `include/os_kernel.h` — 74 行
:icon: code
[GitHub で開く](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/06_memory_protection/include/os_kernel.h)
```{literalinclude} ../../../../code/06_memory_protection/include/os_kernel.h
:language: cpp
:linenos:
```
:::
:::{dropdown} `src/os_fault.cpp` — 170 行
:icon: code
[GitHub で開く](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/06_memory_protection/src/os_fault.cpp)
```{literalinclude} ../../../../code/06_memory_protection/src/os_fault.cpp
:language: cpp
:linenos:
```
:::
:::{dropdown} `src/os_kernel.cpp` — 219 行
:icon: code
[GitHub で開く](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/06_memory_protection/src/os_kernel.cpp)
```{literalinclude} ../../../../code/06_memory_protection/src/os_kernel.cpp
:language: cpp
:linenos:
```
:::
## 実機での出力
Arduino UNO R4 WiFi の実機で実際に取得した出力です(macOS / Windows の
両方で同じ結果を確認しています)。手元の出力と見比べてください。
`d` を送ってゼロ除算タスクを作ると、そのタスクだけが `TERMINATED` になり、`Heartbeat` は動き続けます。
コマンドは 1 文字です。`d` はゼロ除算を起こすタスク(`Crash_Div`)を、`m` は 5 秒後に
不正なアドレス(`0xFFFFFFFF`)へ書き込むタスク(`Crash_Mem`)を作ります。`p` はタスクの
一覧を表示します。
```{figure} ../_static/ch06_fault.gif
:name: fig-ch06-fault
:width: 100%
`p` でタスクを見て、`d` でゼロ除算を起こしたところ。fault のあとも
`Heartbeat` が途切れず、`Crash_Div` だけが `TERMINATED` になります
```
```text
ID NAME STATE
0 idle READY
1 LED BLOCKED
2 Heartbeat BLOCKED
3 Shell RUNNING
[Heartbeat] 2
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
[Heartbeat] 3
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!! FAULT DETECTED !!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Task 4 (Crash_Div) crashed!
Reason: Divide by zero
PC at fault: 0x41BA
Task terminated. Other tasks continue.
[Heartbeat] 4
[Heartbeat] 5
ID NAME STATE
0 idle READY
1 LED BLOCKED
2 Heartbeat BLOCKED
3 Shell RUNNING
4 Crash_Div TERMINATED
[Heartbeat] 6
[Heartbeat] 7
[Heartbeat] 8
[Heartbeat] 9
```
`PC at fault` の PC(プログラムカウンタ)は、落ちた命令のアドレスです。
### シミュレータで見る
実機が無くても、同じファームウェアを {doc}`シミュレータ <../getting-started/simulator>` で動かすとこう見えます。
```{figure} ../_static/sim_ch06_fault.gif
:name: fig-ch06-sim
:width: 80%
`d` を送ると `Crash_Div` タスクがゼロ除算でフォールトを起こし、`FAULT DETECTED` と原因が出ます。そのあとも L(Heartbeat)の点滅と `[Heartbeat]` の出力は続きます。(シミュレータで撮影)
```
(ch06-break)=
## 壊してみる
**予想してから**答えを開いてください。1 つめはコードを変えずに、
ブラウザ版の第6章で
そのまま試せます。2 つめはコードを変えてビルドし、`firmware.elf` を
{ref}`ブラウザ版にドロップ ` します。
:::{note}
答えに載せた出力は、ブラウザ版と同じ QEMU で実際に動かして取ったものです。
実機ではまだ確かめていません。
:::
### 1. 続けて 5 回落とす
`d` を 5 回続けて送り、しばらく待ってから `p` でタスクの一覧を出します。
**予想:** `FAULT DETECTED` は何回出るでしょう。
:::{dropdown} 答え
**4 回しか出ません。** 5 回目も `Creating div-by-zero crash task...` とは表示されますが、
そのあとの `[Crash] Dividing by zero in 3 seconds...` が出ません。
```text
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
[Heartbeat] 1
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
Creating div-by-zero crash task...
[Heartbeat] 2
(FAULT DETECTED が 4 回)
ID NAME STATE
0 idle READY
1 LED BLOCKED
2 Heartbeat BLOCKED
3 Shell RUNNING
4 Crash_Div TERMINATED
5 Crash_Div TERMINATED
6 Crash_Div TERMINATED
7 Crash_Div TERMINATED
```
タスクを入れておく表 `g_tasks` は `MAX_TASKS` = 8 個ぶんしかありません。
起動時の 4 つ(idle・LED・Heartbeat・Shell)で半分を使い、残りの 4 つを
`Crash_Div` が埋めます。`TERMINATED` になったタスクの場所は再利用されないので、
5 回目の `os_create_task()` は -1 を返して何もしません。シェルは戻り値を
見ていないので、「作ります」とだけ言って、失敗に気づきません。
タスクが終わるたびに場所が減っていくので、この OS は、落ちたタスクを作り直し続けるような
使い方には向きません。
:::
### 2. ゼロ除算の見張りを外す
`src/os_fault.cpp` の `fault_init()` で、ゼロ除算を例外にする設定をコメントにします。
```diff
- SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
+ // SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
```
**予想:** `d` を送ると、`100 / 0` はどうなるでしょう。
:::{dropdown} 答え
**`FAULT DETECTED` が出ません。** それなのに、`p` で見ると `Crash_Div` は
`TERMINATED` になっています。
```text
Creating div-by-zero crash task...
[Crash] Dividing by zero in 3 seconds...
[Heartbeat] 1
[Heartbeat] 2
[Heartbeat] 3
ID NAME STATE
0 idle READY
1 LED BLOCKED
2 Heartbeat BLOCKED
3 Shell RUNNING
4 Crash_Div TERMINATED
```
Cortex-M の割り算命令は、この設定(`CCR.DIV_0_TRP`)が無いと、0 で割っても
例外を起こさず、答えを 0 にして先へ進みます
([Arm の解説「Divide and Conquer」](https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/divide-and-conquer))。
`c` が 0 になったまま `task_crash_divzero()` は最後まで走って return し、
タスクが return したときの受け皿 `task_exit()` が `TERMINATED` にしています。
エラーを出さずに、間違った答えで先へ進んでしまうわけです。
`fault_init()` がわざわざこの設定を入れているのは、このためです。
:::
## つまずきやすいポイント
(読者からの質問に応じて随時追記します。質問は
[GitHub Issues](https://github.com/iory/build-your-own-arduino-rtos/issues)
へどうぞ)