はじめに
実行単位の定義ができたので、本格的にASPカーネルを使う部分に入っていく。
とはいえ、既にできあがっているものをラッピングするだけなので難しいことはない。
データ型の定義
ASPカーネルのサービスコールで使う引数や戻り値は専用の型を持っていることが多い。
最たるものはER型だろう。ERはERror codeの略称であり、ほとんどのシステムサービスがこのER型を返す。
元の定義は以下の通り。
typedef int_t ER; /* エラーコード */
int_tはさらにtypedefされており、
typedef signed int int_t; /* 自然なサイズの符号付き整数 */
基本的にはそのデータ型が表したい範囲より広い幅で、最も性能がよい型が使われると考えてよい。
##int_t、uint_tの定義
基本となる2つをまずは定義する。
ではこの2つはRustのどの型がいいだろうか?32bitだろうか、64bitだろうか。
答えは使うマイコンによるである。32bit CPUならu32になるし、64bitならu64にする。
厳密に言えばどんなマイコンでどんなコンパイラでどんなオプションを付けたらで異なってくるが、多くの場合は対象マイコンの自然な演算長を使えばよい。
RustではコンパイラがCPUの語長を教えてくれる。target_pointer_width
を参照して以下のように書ける。
IntやUIntでもいいが、プレフィックスを付けておいた。
(16bit/8bitではRustやRTOSの出番がない気がするので省略)
#[cfg(target_pointer_width = "32")]
pub type ToppersUInt = u32;
#[cfg(target_pointer_width = "32")]
pub type ToppersInt = i32;
#[cfg(target_pointer_width = "64")]
pub type ToppersUInt = u64;
#[cfg(target_pointer_width = "64")]
pub type ToppersInt = i64;
int_t、uint_tを使った定義
先のER型など、ほとんどのデータ型は整数長である。
pub type Fc = ToppersInt; // fnがキーワードなのでfunction codeのfcとした
pub type Er = ToppersInt;
pub type Id = ToppersInt;
pub type Atr = ToppersUInt;
pub type Stat = ToppersUInt;
pub type Mode = ToppersUInt;
pub type Pri = ToppersInt;
pub type Size = ToppersUInt;
pub type Tmo = ToppersInt;
pub type Reltim = ToppersUInt;
pub type Systim = ToppersUInt;
pub type Sysutm = ToppersUInt;
pub type Fp = fn();
pub type ErBool = ToppersInt;
pub type ErUint = ToppersInt;
pub type ErIf = ToppersInt;
Fpはvoid関数の関数ポインタである。ASPの範囲では使わないが、Rustでも関数ポインタは使える。
その他のデータ型
いくつかのデータ型は語長が決まっていて、符号なし32bitである。これは説明不要だが、以下の通り。
細かく言うともともとt_stddef.hで定義されているかkernel.hで定義されているかで位置付けは違うのだが、アバウトに同じファイルで定義している。
// ビットパターンやオブジェクト番号(kernel.hより)
pub type TexPtn = u32;
pub type FlgPtn = u32;
pub type IntNo = u32;
pub type InhNo = u32;
pub type ExcNo = u32;
定数の定義
型によっては特定の値に意味があるものがある。これもまたER型の話だが、
/*
* メインエラーコード
*/
#define E_SYS (-5) /* システムエラー */
#define E_NOSPT (-9) /* 未サポート機能 */
#define E_RSFN (-10) /* 予約機能コード */
#define E_RSATR (-11) /* 予約属性 */
/* 続く */
のようにある値であることに意味があるものは、定数値として定義する。
Rustにはマクロがあり型付けをしないこともできるが、強い型付けは安全性に寄与すると思っているので、定数にする。
const E_OK: Er = 0;
pub const E_SYS: Er = -5; // システムエラー
pub const E_NOSPT: Er = -9; // 未サポート機能
pub const E_RSFN: Er = -10; // 予約機能コード
pub const E_RSATR: Er = -11; // 予約属性
pub const E_PAR: Er = -17; // パラメータエラー
// 続く
基本的に写経である。
唯一悩ましいのは一般定数NULL
の扱い。
NULLは使いやすい反面、危険も多い(とりあえずNULLに設定していて0番地書き込みするとか)。
RustではNULLのような不正な参照はコンパイラで検出されて扱いづらいし、値がないことを示すにはOptionやResultみたいな便利型があるので、不要かもしれない。
サービスコール
データ型が使えるようになったので、サービスコールのラッパを作っていく。
基本的にはそのまま呼び出せばいいのだが、ポインタはref_xxx系を除いて隠蔽する。
どういうことかと言うと、ASPのサービスコールでポインタを渡すのはデータの格納先を渡している箇所である。
戻り値は処理結果を示すER型で使われているから、C言語ではこの方法しかない。
しかし、Rustはタプルをサポートしている。複数の戻り値が欲しいならそのまま書ける。
例えばget_tidはこうなる。
extern ER get_tid(ID *p_tskid) throw();
pub fn get_tid() -> (Er, Id);
ラッパでポインタを正しく使っておけば、アプリケーション側でポインタの誤用を防ぐことができる。
ASPカーネルをRustから呼ぶ
さて、本題である。
C言語で書かれた関数をRustから呼ぶ場合、まずはextern "C"
を付けばブロック内でI/Fを書く。
オリジナルの定義をRustの書き方、用意したデータ型に合わせればよい。
extern "C" {
fn act_tsk(tskid: Id) -> Er;
}
extern ER act_tsk(ID tskid) throw();
これを全サービスコールに適用していく(今日現在全てはかけていないが、時間の問題)。
ラッパ本体を書く
たいていの場合
先に書いたI/Fをunsafe
で囲って呼ぶ。
呼び出した関数の戻り値がそのまま返るので、returnもセミコロンもいらない。
pub fn act_tsk(tskid: Id) -> Er {
unsafe { act_tsk(tskid) }
}
ポインタを隠蔽する場合
一度ローカル変数で受けて、タプルで返す。
参照で渡す引数は予め初期化しておかないとコンパイルエラーとなる。
extern ER get_tid(ID *p_tskid) throw();
extern "C"{
fn get_tid(p_tskid : *mut Id) -> Er;
}
pub fn get_tid() -> (Er, Id){
let mut tskid : Id = 0;
let mut ercd : Er;
unsafe{ ercd = get_tid(&mut tskid) }
(ercd, tskid)
}
ref_xxx系
オブジェクト状態の参照は戻り値が多いため、タプル化には向かないと判断し、そのままの形とした。
ref_texの場合を示す。
#[repr(C)]
pub struct TRtex {
texstat: Stat, // タスク例外処理の状
pndptn: TexPtn, // 保留例外要因
}
fn ref_tsk(tskid: Id, pk_rtsk: *mut TRtsk) -> Er;
fn ref_tex(tskid : Id, pk_rtex: *mut TRtex) -> Er;
pub fn ref_tex(tskid : Id, pk_rtex : &mut TRtex) -> Er{
unsafe{ ref_tex(tskid, pk_rtex) }
}
オブジェクト状態を格納する構造体には#[repr(C)]
をつける。これで構造体のメンバ並びがC言語と同じになる。
終わりに
ほとんどが書き写しの作業で疲れた、というか面倒すぎて全部は終わっていない。
sample1で使わない範囲は徐々にやっていこうと思う。テストできないものを作ると後々怖いことにもなりかねない。
初回でも書いたが、コンパイラが最大の敵であり、味方である。
安全に動かない型変換はほとんどエラーとなるので途中は辛いが、通ってしまえば動く。
以上