Tockの概要

Tock は、Cortex-M、および、RISC-Vマイクロコントローラ用のセキュアな組み込み オペレーティングシステムです。Tockは、ハードウェアがメモリ保護ユニット(MPU)を 持っていることを前提としています。MPUを持たないシステムでは複数の信頼できない プロセスを同時にサポートすることも、Tockの安全性とセキュリティな特性の維持をする こともできないからです。Tockカーネルとその拡張機能(カプセルと呼ばれる)は Rustで書かれています。

Tockは、任意の言語で書かれた複数の独立した信頼できないプロセスを実行することができます。 Tockが同時にサポートできるプロセスの数は、MCUのフラッシュとRAMにより制限されます。 Tockはさまざまなスケジュールアルゴリズムの使用を設定できますが、Tockのデフォルト スケジューラはプリエンプティブであり、ラウンドロビン方式を使用します。Tockはマイクロ カーネルアーキテクチャを採用しています。複雑なドライバとサービスの多くは信頼できない プロセスとして実装されており、アプリケーションなどの他のプロセスをプロセス間通信(IPC)を 介して呼び出すことができます。

このドキュメントでは、Tockのアーキテクチャの概要、Tockにおける異なるクラスのコード、 Tockが使用する保護機構、そしてこの構造がソフトウェアのディレクトリ構造にどのように 反映されているかについて説明します。

Tockのアーキテクチャ

Tock architecture

上の図は、Tockのアーキテクチャを示しています。コードは3つのカテゴリ、コアカーネルカプセルプロセスのいずれかに分類されます。

コアカーネルとカプセルはRustで書かれています。Rustは型安全なシステム言語です。この 言語とそのカーネル設計への影響については他のドキュメントで詳しく説明されていますが、 鍵となるのは、Rustのコードはメモリを意図したものと異なる使用ができない(たとえば、 バッファオーバーフロー、偽ポインタ、デッドスタックフレームポインタの保持など)ことです。 これらの制約は、OSカーネルがしなければならない多くのこと(たとえば、データシートで 指定されているメモリアドレスに存在するペリフェラルのアクセスなど)を妨げるため、非常に 小さなコアカーネルは「安全でない」Rustコードを使用することでこれらの制約を破る ことが許されています。しかし、カプセルは安全でない機能を使用することはできません。 これは、コアカーネルのコードは非常に小さく慎重に書かれているが、カーネルに追加される 新たなカプセルは安全なコードであり、信頼される必要はないということを意味します。

プロセスは任意の言語で書くことができます。カーネルは、ハードウェアメモリ保護ユニット (MPU)を使用して、自分自身と他のプロセスを不正なプロセスコードから保護します。 プロセスが許可されていないメモリにアクセスしようとすると、MPUは例外を発生させます。 カーネルはこの例外を処理してプロセスを終了させます。

カーネルは4つの主要なシステムコールを提供します。

  • command: プロセスからカーネルのコールを行います。
  • subscribe: カーネルからコールされるプロセスのコールバックを登録します。
  • allow: プロセス内のメモリにカーネルがアクセスできるようにします。
  • yield: コールバックが呼び出されるまでプロセスを一時停止します。

yield以外のすべてのシステムコールはノンブロッキングです。長時間かかる可能性のある コマンド(UART経由のメッセージ送信など)はすぐに戻り、処理が完了するとコールバックが 発行されます。yieldシステムコールは、コールバックが呼び出されるまでプロセスをブロック します。通常、ユーザーランドのコードは、コマンドを実行し、yieldを使用してコールバックが 完了するまで待機するというブロック関数を実装します。

command、subscribe, allowの各システムコールはすべて、最初の引数としてドライバIDを 受け取ります。これは、システムコールがカーネル内のどのドライバを対象としているかを 示します。ドライバはシステムコールを実装したカプセルです。

Tockのディレクトリ構造

Tockにはいくつかの主要なコードディレクトリがあります。

  • arch: アーキテクチャ固有のコードを格納します。つまり、Cortex-M0やCortex-M4 固有のコードです。これには、コンテキストスイッチの実行やシステムコール(ユーザーコード からカーネルコードへのトラップ)を行うコードが含まれます。

  • boards: imix、Hail、nrf52dkなどの特定のTockプラットフォーム用のコードを 格納します。通常、これはカーネルが持つすべてのカプセル、MCUのIOピンを適切な状態に 設定するコード、カーネルを初期化するコード、プロセスをロードするコードなどを定義する 構造体です。このディレクトリで最も重要なファイルはmain.rsであり、その最も重要な 初期化関数は(MCUがリセットされたときに実行される)reset_handlerです。ボード コードでは、システムコールデバイス識別子をカプセルにマッピングする方法も with_driver関数の中で定義しています。

  • capsules: 特定のペリフェラルのチップ固有実装の上に構築できるMCUに依存しない カーネル拡張を格納します。システムコールを提供するカプセルもあります。たとえば、 この ディレクトリにあるspiモジュールは、チップのSPI実装を使って、そのシステム コールを提供する実装を構築しています。

  • chips: SPI、I2C、GPIO、UARTの実装やその他のマイクロコントローラ固有のコード を格納します。chipsとboardsの区別はマイクロコントローラとフルプラットフォームの 違いです。たとえば、多くのマイクロコントローラは複数のUARTを持ちます。どのUARTが Tockと通信するのにはどのUARTが主に使われるのか、また、別のチップを制御するには どのUARTが使用されるのかは、ボード上にチップがどのように配置され、どのピンが公開 されているかにより定義されます。したがって、チップはUARTの実装を提供し、ボードは どのUARTが何に使われるかを定義します。

  • doc: 内部インターフェースの仕様やチュートリアルを含むTockのドキュメントを 格納します。

  • kernel: スケジューラ、プロセス、メモリ管理など、マイクロコントローラに依存しない カーネルコードを格納します。このディレクトリとarchがすべてのコアカーネルコードを 格納する場所です。

  • libraries: 内部で使用したり、外部と共有するライブラリを格納します。いくつかの プリミティブがTock用に作成されていますが、他のプロジェクトにも有用でないかと考えて います。ここは各クレートを置く場所です。

  • tools: コードフォーマットチェック、バイナリ変換、ビルドスクリプトなど、 コンパイルやコードメンテナンスに役立つ関連ツールを格納します。

  • vagrant: 仮想マシン環境でTockを動かすための情報を格納します。