TOPPERS APIを使用する (方法1)

C/C++コードとの相互運用を実現する仕組みをFFIと言います。本セクションでは手動でFFI関数・型宣言を行うことにより、TOPPERSカーネルのAPIを直接利用する方法を説明します。

  1. 使用したいカーネルAPIを選びます。ここでは例として以下のAPIを選ぶことにします。

    t_stddef.hkernel.h から抜粋
    typedef signed int      int_t;      /* 自然なサイズの符号付き整数 */
    typedef unsigned int    uint_t;     /* 自然なサイズの符号無し整数 */
    
    typedef int_t           ER;         /* エラーコード */
    typedef int_t           ID;         /* オブジェクトのID番号 */
    typedef uint_t          ATR;        /* オブジェクトの属性 */
    typedef int_t           ER_ID;      /* エラーコードまたはID番号 */
    
    typedef struct t_csem {
        ATR     sematr;     /* セマフォ属性 */
        uint_t  isemcnt;    /* セマフォの初期資源数 */
        uint_t  maxsem;     /* セマフォの最大資源数 */
    } T_CSEM;
    
    typedef struct t_rsem {
        ID      wtskid;     /* セマフォの待ち行列の先頭のタスクのID番号 */
        uint_t  semcnt;     /* セマフォの現在の資源数 */
    } T_RSEM;
    
    extern ER_ID    acre_sem(const T_CSEM *pk_csem) throw();
    extern ER       sig_sem(ID semid) throw();
    extern ER       wai_sem(ID semid) throw();
    extern ER       ref_sem(ID semid, T_RSEM *pk_rsem) throw();
    

  1. 型定義をRustで記述します。

    #[allow(non_camel_case_types)]
    mod ffi {
        pub type int_t = i32;
        pub type uint_t = u32;
        pub type ER = int_t;
        pub type ID = int_t;
        pub type ATR = uint_t;
        pub type ER_ID = int_t;
    
        #[repr(C)]
        #[derive(Debug, Copy, Clone)]
        pub struct T_CSEM {
            pub sematr: ATR,
            pub isemcnt: uint_t,
            pub maxsem: uint_t,
        }
    
        #[repr(C)]
        #[derive(Debug, Copy, Clone)]
        pub struct T_RSEM {
            pub wtskid: ID,
            pub semcnt: uint_t,
        }
    }
    

    Note

    mod ffiffi という名前の子モジュールを定義します。通常はソースファイル全体がモジュールに対応しますが、このようにソースファイル内に子モジュールの内容を埋め込むこともできます。 lib.rs で記述した場合、この子モジュールの完全パスは crate::ffi となります。

    pub 指定により ffi モジュールの外からでもアクセス可能にします。

    #[repr(C)] [Rustリファレンス] を指定することで構造体レイアウトがC言語と互換になるようにします。

    #[derive(Debug, Copy, Clone)] [The Book] は型に各種トレイトを自動的に実装します。

    • Debug は値のデバッグ用文字列表現を生成する共通のインタフェースを表します。

    • Copy はユーザ定義のコードを必要とせずに値を複製できることを表します。

    • Clone は値を複製するため共通のインタフェースを表します。 derive で生成される実装は個別のフィールドに対して再帰的に Clone::clone() メソッドを呼び出します。ここでは derive を使用していますが、 derive を使わずに手動で実装するのも参照カウントを行うスマートポインタなどの実装に有用です。

    #[allow(non_camel_case_types)] [Rustリファレンス] はRustの命名規則に従わない型名に対する警告を抑止します。Rust外で定義された項目に対してこれを使用するのは一般的です。


  1. 関数宣言をRustで記述します。

    mod ffi {
        /* ... */
    
        extern "C" {
            pub fn acre_sem(pk_csem: *const T_CSEM) -> ER_ID;
            pub fn sig_sem(semid: ID) -> ER;
            pub fn wai_sem(semid: ID) -> ER;
            pub fn ref_sem(semid: ID, pk_rsem: *mut T_RSEM) -> ER;
        }
    }
    

    Note

    extern "C" { ... } ブロックはC言語ABIに準拠した外部関数をこのモジュールに追加します。


  1. 以上で記述したFFI宣言を利用したコードを記述します。(root_taskRustタスクを追加する で追加した関数です。)

    #[no_mangle]
    extern "C" fn root_task(_: usize) {
        unsafe {
            // SAFETY: 渡している `*const T_CSEM` の参照先は読み込み可能
            let sem = check_er(ffi::acre_sem(&ffi::T_CSEM {
                sematr: 0,
                isemcnt: 1,
                maxsem: 2,
            }));
    
            // SAFETY: `sem` の所有権を持っているので、他のコードの動作を妨げることはない
            check_er(ffi::sig_sem(sem));
    
            // SAFETY: `sem` の所有権を持っているので、他のコードの動作を妨げることはない
            check_er(ffi::wai_sem(sem));
    
            let info = {
                let mut info = std::mem::MaybeUninit::uninit();
    
                // SAFETY: 渡している `*mut T_RSEM` の参照先は書き込み可能
                check_er(ffi::ref_sem(sem, info.as_mut_ptr()));
    
                // SAFETY: `ref_sem` が成功したので、 `info` は初期化されている
                info.assume_init()
            };
    
            println!("Semaphore state = {info:?}");
            assert_eq!(info.semcnt, 1);
        }
    }
    
    #[inline]
    #[track_caller]
    fn check_er(x: ffi::int_t) -> ffi::int_t {
        assert!(x >= 0, "kernel service call failed: {}", x);
        x
    }
    

    注意

    この例ではセマフォを動的生成するので、kernel_cfg.c (カーネル起動パラメータ) を編集して TNUM_AID_SEM の値を 1 以上に設定する必要があります。

    Note

    FFIで宣言した関数の呼び出しは unsafe { ... } ブロック内でしか行えません。これはRustの言語機能による安全性の保証は外部関数には及ばないためです。 unsafe { ... } ブロックを使う際は // SAFETY: コメントで安全性の根拠を簡潔に説明するのが慣習です。

    MaybeUninit は「未初期化かもしれないメモリ」を表します。通常はRustでは未初期化の変数のアクセスはできません。しかし「含まれる T_RSEM が未初期化である」状態は MaybeUninit<T_RSEM> の初期化済み状態として有効です。 MaybeUninit::uninit によりこの状態の MaybeUninit を生成します。 ref_sem() の呼出しによって有効な T_RSEM が代入された後に、 MaybeUninit::assume_init によってこの T_RSEM を取り出します。

    assert! は指定された条件が満たされていない場合にパニックします。パニックメッセージを println! と同じ書式で指定できます。

    #[track_caller] [Rustリファレンス] はパニック時などに呼出し元の行番号を伝搬する属性です。

    println! の書式で使用している {:?}T_RSEMDebug トレイト実装を使用して文字列表現を生成することを表します。


  1. 実行すると、 ref_sem() から返された T_RSEM の内容がログ出力先で見えるはずです。

../../_images/itron-sem-debug-output1.png

さらに詳しく

手動でFFI記述を作成する以外の方法

  • bindgen (CヘッダファイルからFFI記述を生成するツール)

  • CXX (C++コードとRustコード間で安全に連携を行うためのライブラリ)