はじめに
前回、表示系を作った。
まずはタスクなどの実行単位を定義していく。
前回のスクリーンショットはこの作業が終わったあとのもの。
sample1では以下の実行単位を使用している。
- タスク
- タスク例外
- 周期ハンドラ
- アラームハンドラ
- CPU例外
それ以外には以下があるが、sample1アプリケーションでは使用していない。
とはいえ、今回紹介する内容さえわかればさして難しくはないはず。
- 割込みハンドラ
- ISR
- 初期化/終了ハンドラ
タスク
並列実行される基本単位。C言語では以下で表される。
void task(intptr_t exinf);
intptr_tはC99で導入された、整数型とポインタを格納できるサイズを持つ型である。
(TOPPERS/ASPのカーネルはC89でも動くが、型はC99のものを使っている※ベースのITRON4.0仕様とは異なる)
この型はRustでも定義されているが、i64
で定義されている。
残念ながらターゲットに依存して変わる定義ではないようなので使えない。
※target_pointer_widthなるfeatureがあることが書いていてわかったのでそのうち対応
よって、タスク引数はCoretx-M4Fのbit長であるi32
にする。
関数名は、C言語とリンクするためにextern "C"
をつける。
またマングリングがあるとNGなので、防止のためのアトリビュートもつける。
main_taskには、起動したことを表示するためにsyslogを入れておく。
#[no_mangle]
#[allow(unreachable_code)]
extern "C" fn task(exinf: i32) {
body;
}
#[no_mangle]
#[allow(unreachable_code)]
extern "C" fn main_task(exinf: i32) {
toppers_syssvc_syslog!(LOG_NOTICE, "Sample program starts (exinf = %d).", exinf as u32);
body;
}
task関数は無限ループが中に存在していることから未到達コードへの警告が検出されるため、#[allow(unreachable_code)]
もつけておく。普通に終了するタスクでは不要である。
#タスク例外
C言語では以下で表される。
typedef uint_t TEXPTN; /* タスク例外要因のビットパターン */
void tex_routine(TEXPTN texptn, intptr_t exinf);
TEXPTNもターゲット依存の型なのだが、カーネル側で定義するものなので別途ファイルを作って(今はstddef.rsとしている)以下のように定義した。命名規則はRustのそれに合わせている。
pub type TexPtn = u32;
intptr_tは先と同じなので、
#[no_mangle]
extern "C" fn tex_routine(texptn: TexPtn, exinf: i32) {
body;
}
となる。
周期ハンドラ、アラームハンドラ
2つとも同じ規則のI/Fであり、C言語では以下の通り。
void cyclic_handler(intptr_t exinf);
void alarm_handler(intptr_t exinf);
sample11のこれらのハンドラでは、exinfは使わないので、未使用であることを示す_
をつける。
#[no_mangle]
extern "C" fn cyclic_handler(_exinf: u32) {
:
}
#[no_mangle]
extern "C" fn alarm_handler(_exinf: u32) {
:
}
CPU例外ハンドラ
C言語では以下の通り。ターゲット依存の情報を扱うため、voidポインタを使う。
void cpuexc_handler(void *p_excinf);
このポインタにはCPU例外発生時のシステム状態が入っており、xsns_xpn/xsns_dpn
に渡して使用する。中の構造はアプリケーションにはわからない。FILE*みたいなイメージ。
voidポインタをRustで使うのはc_void
モジュールを使う。
モジュール宣言をしておく。
use core::ffi::c_void;
c_void = voidなので、引数としてはvoidへの参照の形ととって、以下のようになる。
#[no_mangle]
extern "C" fn cpuexc_handler(p_excinf: &c_void) {
body;
}
TOPPERS/ASPとリンクする
TOPPERS/ASP側のライブラリ化
TOPPERS/ASPはそれなりの規模の大きさなのでいくつかの領域に分割されている。
- カーネル側
- カーネルライブラリであるlibkernel.a
- システムサービス群であるオブジェクトファイル群(*.o)
- アプリケーション側
- アプリ
- コンフィギュレーションファイル(*.cfg -> *.c -> *.o)
上で作ってきたのはアプリであり、その他の部分をリンクする必要がある。
RustはC言語で作成したバイナリをリンクできるが、調べた限り.oを直接Rustコンパイラからリンクする術がわからない。そのため、いったん*.oを.aに
まとめる(Makefileを適当に編集したが、直接タイプしてもいいだろう)。
arm-none-eabi-ar -rcs libsyssvc.a target_serial.o banner.o syslog.o serial.o logtask.o log_output.o vasyslog.o t_perror.o strerror.o
arm-none-eabi-ranlib libsyssvc.a
rm -rf libcfg.a
arm-none-eabi-ar -rc libcfg.a kernel_cfg.o
arm-none-eabi-ranlib libcfg.a
作ったライブラリはRustのディレクトリにコピーしておく。
Rust側でリンクするライブラリを指定する
適当な.rsに以下を記述する。ソース本体とは分けたほうがいいだろう。
以下の記述はldの-l
スイッチにあたる。
#[link(name = "syssvc", kind = "static")]
#[link(name = "kernel", kind = "static")]
#[link(name = "cfg", kind = "static")]
extern "C"{
}
今回はアプリとライブラリ層を階層ワケしているので、build.rsを作ってライブラリのパスを指定した。
カーネルそのものとコンフィギュレーションファイルも別のディレクトリにしておいた。
rustc-link-search
はldコマンドの-l
スイッチに相当する・
use std::env::var;
fn main() {
let manifest_dir = var("CARGO_MANIFEST_DIR").unwrap();
println!("cargo:rustc-link-search={}/src/toppers", manifest_dir);
println!("cargo:rustc-link-search={}/src/toppers_cfg", manifest_dir);
}
リンカスクリプトの指定
C言語環境側から、リンカスクリプトをコピーして、.cargo/config
で指定しておく。
このあたりは同じリンカへのスイッチなのに書き先が違ってややこしい 全部configでいいのに 。
[build]
target = "thumbv7em-none-eabihf"
[target.thumbv7em-none-eabihf]
runner = "hf2 elf"
rustflags = [
"-C", "link-arg=-Tsamd51.ld",
]
まとめ
C言語インターフェース周りは特に難しくない。
苦労したのはリンカスイッチ周りだろうか。
現状ビルド手順が手で操作しているので、動作確認が終わればここの自動化が必要だと思っている。
幸い、cmakeがbuild.rsが呼び出せるとのことなので、どうにかなるだろう。