ユーザランド
このドキュメントでは、アプリケーションコードがTockにおいてどのように動作するかを 説明します。これは、各自のアプリケーションを作成するためのガイドではなく、 アプリケーションがどのように機能するかの背後にある設計思想に関するドキュメントです。
Tockにおけるアプリケーションの概要
Tockにおけるアプリケーションはエンドユーザのために何らかのタスクを達成するための ユーザレベルのコードです。デバイスドライバとチップ固有の詳細、一般的な オペレーティングシステムのタスクを処理するカーネルコードとアプリケーションは 区別されます。既存の多くの組み込みオペレーティングシステムとは異なり、Tockでは アプリケーションがカーネルと一緒にはコンパイルされていません。アプリケーションは システムコールを介して カーネルや他のアプリケーションと相互作用する完全に分離されたコードです。
アプリケーションはカーネルの一部ではないので、マイクロコントローラ上で実行可能な コードにコンパイルできる任意の言語で書くことができます。Tockは複数のアプリケーションの 同時実行をサポートします。協調的マルチプログラミングがデフォルトですが、 アプリケーションをタイムスライスすることも可能です。アプリケーションは、 システムコールを介したプロセス間通信(IPC)で相互に通信できます。
アプリケーションはインストールアドレスとロードアドレスをコンパイル時に知ることが できません。現在のTockの設計では、アプリケーションは[位置独立なコード] (https://en.wikipedia.org/wiki/Position-independent_code)(PIC)と してコンパイルされる必要があります。これにより、アプリケーションはロードされた 任意のアドレスから実行できるようになります。TockアプリにおけるPICの使用は基本的な 選択ではありませんので、将来のシステムのバージョンでは、実行時再配置可能なコードを サポートする可能性もあります。
アプリケーションは非特権コードです。メモリのすべての部分にアクセスできるわけは ありません。各自の境界を超えたメモリにアクセスしようとするとフォールトが発生します (Linuxコードにおけるセグメンテーションフォールトと同じです)。ハードウェアを 使用するには、アプリケーションはカーネルを呼び出さなければなりません。
システムコール
システムコール(別名、syscalls)はカーネルへのコマンドの送信に使用されます。 システムコールには、ドライバへのコマンド、コールバックの購読、アプリケーション 関連データを保存できるようにカーネルにメモリを付与すること、他のアプリケーション コードとの通信など多くのものが含まれます。実際には、システムコールはライブラリ コードを通して行われ、アプリケーションが直接対応する必要はありません。
たとえば、GPIOピンをハイに設定する次のようなシステムコールを考えます。
int gpio_set(GPIO_Pin_t pin) {
return command(GPIO_DRIVER_NUM, 2, pin);
}
commandシステムコール自体はARMアセンブリ命令svc(サービスコール)として
実装されています。
int __attribute__((naked))
command(uint32_t driver, uint32_t command, int data) {
asm volatile("svc 2\nbx lr" ::: "memory", "r0");
}
より深い考察はシステムコールドキュメントで見ることことができます。
コールバック
Tockは、コールバック関数を
使用して非同期イベントを処理することが多い、組み込みアプリケーションをサポートする
ように設計されています。たとえば、タイマーコールバックを受け取るためには、
まず、タイマーが発火した際に呼び出してもらいたい関数への関数ポインタを指定して
timer_subscribeを呼び出します。コールバックを実行させたい特定の状態は
ポインタuserdataとして渡すことができます。アプリケーションがタイマーを起動
した後は、yieldを呼び出するとタイマーが発火し、コールバック関数が呼び出されます。
現在のTockの実装では、イベントが処理されるにはyieldが呼び出される必要があることに
注意してください。アプリケーションへのコールバックはイベントが発生した際にキューに
入れられますが、アプリケーションはyieldを呼び出すまでイベントを受け取れません。
これはTockにとって根本的なものではありませんので、将来のバージョンでは、任意の
システムコールに対して、あるいはアプリケーションのタイムスライシングを実行する際に
コールバックを実行するようになるかもしれません。コールバックを受け取り、実行した後、
アプリケーションコードはyield後も継続します。「終了した」アプリケーション
(すなわち、main()から返った)アプリケーションは、カーネルにスケジュール
されないようにループ内でyieldを呼び出す必要があります。
プロセス間通信
IPCにより複数のアプリケーションが共有バッファを介して直接通信を行うことができます。 TockではIPCはサービス・クライアントモデルで実装されています。各アプリはサービスを 1つサポートすることができ、サービスはアプリのTockバイナリフォーマットヘッダに 含まれるパッケージ名により識別されます。アプリは複数のサービスと通信することができ、 検出されたサービスごとに固有のハンドルを取得します。クライアントとサービスは共有 バッファを介して通信します。各クライアントは自身のアプリケーションメモリの一部を サービスと共有し、サービスに通知して共有バッファを解析するように指示することが できます。
サービス
サービスはアプリのTBFヘッダに含まれるパッケージ名により命名されます。サービスを
登録するためにアプリはipc_register_svc()を呼び出してコールバックを設定します。
このコールバックは、クライアントがそのサービスのnotifyを呼び出すごとに呼び出されます。
クライアント
クライアントはまずipc_discover()関数を使って使いたいサービスを発見する必要が
あります。次に、ipc_share()を呼び出してサービスとバッファを共有することが
できます。サービスにバッファを使って何かをするよう指示するためにクライアントは
ipc_notify_svc()を呼び出すことができます。アプリがサービスから通知を得たい
場合は、サービスがipc_notify_client()を呼び出した時にサービスからイベントを
受け取るためにipc_register_client_cb()を呼び出す必要があります。
これらの関数の詳細についてはlibtock-cのipc.hを参照してください。
アプリケーションエントリポイント
アプリケーションはTBFヘッダに変数init_fn_offsetを設定し、カーネルが最初に
呼び出すべき関数を指定します。この関数は次のシグネチャを持つ必要があります。
void _start(void* text_start, void* mem_start, void* memory_len, void* app_heap_break);
Tockカーネルはアプリケーションプロセスのスタックやヒープレイアウトに関しては制限 しないようにしています。そのため、プロセスは非常にミニマムな環境で起動し、初期 スタックはシステムコールのサポートには十分ですが、それ以上のものはありません。 アプリケーションの起動ルーチンはまず、プログラムブレークを動かして 希望のレイアウトに合わせ、実行に追従するようスタックとヒープを設定するべきです。
スタックとヒープ
アプリケーションはTBFヘッダにminimum_ram_size変数を設定することで必要な
メモリ量を指定することができます。Tockカーネルはこれを最小値として扱うことに
注意してください。使用するプラットフォームによってはメモリ量が要求した量より
大きくなることがありますが、小さくなることは絶対にありません。
アプリケーションをロードするためのメモリが不足している場合、カーネルはロード中に 失敗し、メッセージを表示します。
アプリケーションが実行時に割り当てられたメモリを超過した場合、アプリケーションは クラッシュします(その例はデバッグセクションを参照してください)。
デバッグ
アプリケーションがクラッシュした場合、Tockは多くの有用な情報を提供します。 デフォルトでは、アプリケーションがクラッシュすると、Tockはプラットフォームの デフォルトコンソールインターフェイス上にクラッシュダンプを表示します。
アプリケーションはロード時に再配置されるので、アプリケーションが最初にコンパイル
された時に生成されたバイナリとデバッグ用の.lstファイルは、ボードで実際に
実行中のアプリケーションとは一致しないことに注意してください。一致したファイル
(特に一致した.lstファイル)を生成するには、対象となるappディレクトリで
make debugを実行することでアプリケーションが実際に実行されるものにマッチする
適切な.lstファイルを作成することができます。コマンドの呼び出し例については
デバッグプリントの最後を参照してください。
---| Fault Status |---
Data Access Violation: true
Forced Hard Fault: true
Faulting Memory Address: 0x00000000
Fault Status Register (CFSR): 0x00000082
Hard Fault Status Register (HFSR): 0x40000000
---| App Status |---
App: crash_dummy - [Fault]
Events Queued: 0 Syscall Count: 0 Dropped Callback Count: 0
Restart Count: 0
Last Syscall: None
╔═══════════╤══════════════════════════════════════════╗
║ Address │ Region Name Used | Allocated (bytes) ║
╚0x20006000═╪══════════════════════════════════════════╝
│ ▼ Grant 948 | 948
0x20005C4C ┼───────────────────────────────────────────
│ Unused
0x200049F0 ┼───────────────────────────────────────────
│ ▲ Heap 0 | 4700 S
0x200049F0 ┼─────────────────────────────────────────── R
│ Data 496 | 496 A
0x20004800 ┼─────────────────────────────────────────── M
│ ▼ Stack 72 | 2048
0x200047B8 ┼───────────────────────────────────────────
│ Unused
0x20004000 ┴───────────────────────────────────────────
.....
0x00030400 ┬─────────────────────────────────────────── F
│ App Flash 976 L
0x00030030 ┼─────────────────────────────────────────── A
│ Protected 48 S
0x00030000 ┴─────────────────────────────────────────── H
R0 : 0x00000000 R6 : 0x20004894
R1 : 0x00000001 R7 : 0x20004000
R2 : 0x00000000 R8 : 0x00000000
R3 : 0x00000000 R10: 0x00000000
R4 : 0x00000000 R11: 0x00000000
R5 : 0x20004800 R12: 0x12E36C82
R9 : 0x20004800 (Static Base Register)
SP : 0x200047B8 (Process Stack Pointer)
LR : 0x000301B7
PC : 0x000300AA
YPC : 0x000301B6
APSR: N 0 Z 1 C 1 V 0 Q 0
GE 0 0 0 0
EPSR: ICI.IT 0x00
ThumbBit true
Cortex-M MPU
Region 0: base: 0x20004000, length: 8192 bytes; ReadWrite (0x3)
Region 1: base: 0x30000, length: 1024 bytes; ReadOnly (0x6)
Region 2: Unused
Region 3: Unused
Region 4: Unused
Region 5: Unused
Region 6: Unused
Region 7: Unused
To debug, run `make debug RAM_START=0x20004000 FLASH_INIT=0x30059`
in the app's folder and open the .lst file.
アプリケーション
アプリケーションの例については、言語固有のユーザランドリポジトリを参照してください。
- libtock-c: C and C++ apps.
- libtock-rs: Rust apps.