Zephyr×Rustのインテグレーションにチャレンジ!⑦~システムコール調査~
はじめに
ZephyrとRustのインテグレーションに挑戦しています。
これまでで、アプリケーションをRustで書いてきました。 ここからは、Driverを書く方法を調査していきます。
前回までのあらすじ
Rustで書いたDriverの初期化関数から、文字が出力できました。
前回のDriverでは、ユーザーアプリケーションから、Driver APIを使う方法がありません。 そこで、ユーザーアプリケーションからDriver APIを利用できるように、システムコールを実装します。 実装する前には、調査が必要です。
Zephyrでどのようにシステムコールが実装されているか、見ていきます。
System Calls
少し古いドキュメントですが、下記にシステムコールについて書かれています (1.13以降はZephyr Kernel Primerの章が削除されています…)。
Components
__syscall
プレフィックスのついたプロトタイプ宣言。ビルドプロセス内でgen_syscall.py
によって処理されます。- 実装関数。実際のシステムコール実装です。
- ハンドラ関数。引数を検証してから、実装関数に渡すためのラッパーです。
プロトタイプ宣言
システムコールにする関数は、__syscall
を付けて宣言されています。
__syscall void k_sem_init(struct k_sem *sem, unsigned int initial_count, unsigned int limit);
__syscall
属性は、非常に特殊です。コンパイラは、単純にstatic inline
に展開します。
post-buildで実行されるparse_syscalls.py
は、__syscall
属性がついたAPIを解析します。細かい制限はありますが、一旦置いておきます。
呼び出しコンテキスト
システムコールを呼び出しているソースコードが、ユーザーモードで実行されるか、スーパーバイザモードで実行されるか、でシステムコールの呼び出し方を変更します。 基本、スーパーバイザモードからのシステムコール呼び出しは、単純に実装関数を呼び出す簡易の実装になります。
Cソースファイルごとに実行モードを判定するために、ビルドプロセスで、__ZEPHYR_SUPERVISOR__
か__ZEPHYR_USER__
というマクロをコンパイラフラグとして追加します。
CONFIG_USERSPACE
が有効になっていない場合、全てのシステムコールAPIは、直接、実装関数を呼び出します。__ZEPHYR_SUPERVISOR__
が定義されていると、直接、実装関数を呼び出します。__ZEPHYR_USER__
が定義されていると、システムコールが無条件に発行されます。
実装の詳細
__syscall
を使ってAPIを宣言すると、scripts/gen_syscalls.py
によって、Cソースファイルとヘッダファイルが生成されます。
生成されたファイルは、include/generated
下にあります。
- システムコールはシステムコールID型で番号付けされます。
include/generated/syscall_list.h
にK_SYSCALL_
から始まるAPIとして表現されます。 include/generated/syscall_list.h
にハンドラ関数のプロトタイプが生成されます。include/generated/syscall_dispatch.c
のシステムコール発行テーブル (_k_sycall_table
) にエントリが生成されます。- ハンドラ関数は、未実装のシステムコールハンドラのエイリアスである、weakハンドラ関数として宣言されます。
k_sem_init()
は、build/zephyr/include/generated/syscalls/kernel.h
で次のようになっています。
K_SYSCALL_DECLARE3_VOID(K_SYSCALL_K_SEM_INIT, k_sem_init, struct k_sem *, sem, unsigned int, initial_count, unsigned int, limit);
この場合のマクロは、次のように実装されています。上述した通り、システムコールの呼び出しコンテキストによって実装が変わります。
#if !defined(CONFIG_USERSPACE) || defined(__ZEPHYR_SUPERVISOR__) #define K_SYSCALL_DECLARE3_VOID(id, name, t0, p0, t1, p1, t2, p2) \ extern void _impl_##name(t0 p0, t1 p1, t2 p2); \ static inline void name(t0 p0, t1 p1, t2 p2) \ { \ _impl_##name(p0, p1, p2); \ } #elif defined(__ZEPHYR_USER__) #define K_SYSCALL_DECLARE3_VOID(id, name, t0, p0, t1, p1, t2, p2) \ static inline void name(t0 p0, t1 p1, t2 p2) \ { \ _arch_syscall_invoke3((u32_t)p0, (u32_t)p1, (u32_t)p2, id); \ } #else /* mixed kernel/user macros */ #define K_SYSCALL_DECLARE3_VOID(id, name, t0, p0, t1, p1, t2, p2) \ extern void _impl_##name(t0 p0, t1 p1, t2 p2); \ static inline void name(t0 p0, t1 p1, t2 p2) \ { \ if (_is_user_context()) { \ _arch_syscall_invoke3((u32_t)p0, (u32_t)p1, (u32_t)p2, id); \ } else { \ compiler_barrier(); \ _impl_##name(p0, p1, p2); \ } \ } #endif
あー、やっと、アーキテクチャ固有のシステムコール呼び出しが見えました。
だいぶ、長くなってきたので、次回に続きます。