Rust標準ライブラリ

現行版のRustの標準ライブラリにはSOLIDターゲットのサポートが含まれています。本セクションでは標準ライブラリの各APIの対応状況ならびにそれらがどのSOLID-OS向けにどのようにして実装されているかを説明し、SOLIDターゲット固有の注意点を挙げます。

Rust標準ライブラリのAPIドキュメントはこちらからご覧になれます。

注意

RustはRust Foundationとコミュニティによって活発に開発されているオープンソースソフトウェアです。APIの安定性は保証されていますが、実装は予告なしに変化する可能性があります。実装詳細に関する強い保証が必要な場合、カーネルAPIを直接使用することが推奨されます。

本ドキュメントの情報は nightly-2022-08-12 (SOLID-Rustのデフォルトツールチェーン) に基づきます。過去の変更履歴に関しては Rust更新履歴 をご覧下さい。

主な注意事項

ベアメタル環境でも使用できる core, alloc に関しては特記すべき事項はありません。 std が提供するOS抽象化APIに関しては、特に他のターゲットと比較して以下の点に関して注意を要します。

  • 同期プリミティブスレッド操作APIはカーネル資源を動的生成することで実現されています。標準ライブラリ自体もいくつかの資源を必要とし、また必要になった特に初めて動的生成を行います。資源の動的生成に失敗した場合はパニック、それが不可能な場合はアボートします。

  • Rustの他の多くのターゲットとは異なり、TOPPERSカーネルはRTOSカーネルとしては一般的な優先度ベーススケジューリングを使用しています。あるタスクを実行中しているとき、明示的に他のタスクに実行権を譲らない限りは、実行権が譲られることはありません。

  • RTOSカーネル固有のシステム状態では標準ライブラリの同期プリミティブ等は使用できません

  • サブプロセス等のSOLID-OSに存在しない概念に関係するAPIは使用できず、実行時エラーを返します。 環境変数はlibc関数を使用しますが、これらのlibc関数はexeGCCでサポートされていないため、アプリケーションで定義することが推奨されます。

  • HashMap, HashSet乱数生成APIを使用しますが、これにはBSPによるサポートが必要です。

安全性

Rustのメモリ安全性は外部コードの不正な干渉 (e.g., メモリ破壊、エイリアシングルール違反) がないことを前提としています。これに加え、Rust標準ライブラリはOSの機能を使用して実装されているため、OS経由でこの動作に干渉すると正常な動作が保証できず、メモリ安全性が失われることがあります。

  • スレッド操作APIによるタスクの管理に干渉した場合 (例えば、std::thread::spawn() で起動したタスクをエントリポイントからのリターン以外の方法で終了させたり、完了したタスクを再起動した場合) の動作は未定義です。

  • std::sync の同期プリミティブを使用して待ち状態に入ったタスクを強制終了 (ter_tsk()) した場合、またはそうしたタスクでタスク終了要求 (ras_ter()) が処理された場合の動作は未定義です。(一般に、特別に対策をしていない関数を実行中にタスクを終了させるのは危険です。)

このほかに、システム構成の条件やRTOSカーネル固有のシステム状態によって未定義動作につながる場合があります。

  • カーネル非動作状態で同期プリミティブやスレッド管理APIを使用した場合の動作は未定義です。


スレッド

スレッド操作API (std::thread) はASP3ABI (TOPPERS APIをベースとしたカーネル非依存ABI) の上で実装されています。

スレッドの生成 (std::thread::spawn(), std::thread::Builder::spawn()) は acre_tsk() を使用してタスクを動的生成することで行われます。動的割当て可能なタスクIDが枯渇した場合、 E_ENOID に対応する std::io::Error が返ります。

タスクIDはスレッドが合流 (std::thread::JoinHandle::join()) されたとき、あるいはデタッチ (std::thread::JoinHandle をドロップ) 後にスレッドのエントリポイントが制御を返したときに解放されます。合流もデタッチもされなかった場合、タスクIDが解放される保証はありません。

生成されたタスクは生成元タスクのベース優先度を引き継ぎます。この優先度は chg_pri() により生成後に変更することができます。

TOPPERS/FMP3では生成されたタスクは呼出し元プロセッサと同じプロセッサに割り付けられます。割付け対象プロセッサは mig_tsk() により生成後に変更することができます。

スレッド操作APIによるタスクの管理に干渉した場合 (例えば、タスクをエントリポイントからのリターン以外の方法で終了させたり、完了したタスクを再起動した場合) の動作は未定義です。

std::thread::yield_now()rot_rdq(TPRI_SELF) として実装されています。

注意

TOPPERSカーネルは優先度ベーススケジューリングを使用しています。あるタスクがCPU時間を占拠しているとき、yield_now() を呼び出すか待ち状態に入らない限りは他の同優先度タスクは実行されず、また低優先度タスクは yield_now() を呼び出しても実行されません。

デフォルトのスタックサイズは64KiB (32ビットターゲット)または128KiB (64ビットターゲット)です。RUST_MIN_STACK 環境変数を設定することで変更することができます。


TLS (スレッドローカル格納域)

TLS (std::thread_local!) はELF TLS (.tdata, .tbss セクション) によりサポートされています。

非タスクコンテクストではTLSは使用できません。

注意

非タスクコンテクストでTLSを使用した場合、ゼロ番地周辺のメモリアクセスが発生します。プログラミングエラーを確実に検出してフェイルセーフな形で処理するために、仮想アドレス範囲 0..SOLID_TLS_SIZE を未割当に設定することを推奨します。


システムエラー

システムエラー型 (std::io::Error) はSOLID-OS Core ServiceやRTOSカーネルから返されたITRON、SOLID-OS、およびSOLID Socketsエラーコードを表現するのに使用されます。

動的割当て可能なカーネルオブジェクトIDが枯渇した場合のエラー E_ENOIDErrorKind::OutOfMemory にマッピングされます。


同期プリミティブ

ミューテックス (std::sync::Mutex) はTOPPERSカーネルが提供するミューテックス機能で実装されています。常に優先度継承プロトコル (SOLID-OS拡張) を使用します。初回使用時に acre_mtx() で動的生成され、動的割当て可能なミューテックスIDが枯渇した場合はパニックします。

Readers/Writerロック (std::sync::RwLock) はSOLID-OSカーネル拡張で提供されるReaders/Writerロックを使用して実装されます。初回使用時に rwl_acre_rwl() で動的生成され、動的割当て可能なReaders/WriterロックIDが枯渇した場合はパニックします。

スレッド付属同期機構 (std::thread::park()) は Thread ハンドル毎にカーネルイベントフラグを動的生成することで実現されます。動的割当て可能なイベントフラグIDが枯渇した場合はパニックします。

注意

std はパニック機構や環境変数アクセスの排他制御のためにミューテックスやReaders/Writerロックを内部で使用します。これらは必要になった場合に動的生成され、失敗した場合はアボートします。このため、ミューテックスとReaders/Writerロックは動的割当て可能なIDを余分に確保しておくことが推奨されます。

std が内部で使用するために動的生成する同期オブジェクトを解放する方法はありませんが、生成される数には上限があります。

条件変数 (std::sync::Condvar) はTOPPERSカーネルではサポートされていないため、std 内で待ち行列を実装することで実現しています。待ち行列はタスク優先度順で処理されますが、タスク優先度の変化には対応できません。

注意

std やサードパーティーパッケージで提供される同期プリミティブの多くは std::sync::Condvar の上で実装されています。

std の同期プリミティブを使用して待ち状態に入ったタスクを強制終了 (ter_tsk()) した場合、またはそうしたタスクでタスク終了要求 (ras_ter()) が処理された場合の動作は未定義です。強制待ち解除 (rel_wai()) した場合、あるいはタスク終了を禁止 (dis_ter()) した状態でタスク終了要求フラグをセット (ras_ter()) した場合、対象タスクでパニックまたはアボートが発生する可能性があります。

std の同期プリミティブは起床要求キューイング数を変化させる可能性があります。アプリケーションコードが wup_tsk() を呼び出して余分に起床要求を行うことは許されます。


動的メモリ割り当て

動的メモリ割り当てはlibcの malloc() 関数等を使用して実装されています。


カーネルAPIとのインタラクション

std は汎用システム向けOSのアプリケーションを主なターゲットとしており、CPUロックやディスパッチ禁止状態のようなコンセプトは持っていませんが、SOLIDターゲット向けの内部実装でディスパッチ禁止状態を使用しています。

CPUロック有効状態、ディスパッチ禁止状態、および非タスクコンテキストでは std の同期プリミティブやスレッド管理APIは使用できず、パニックまたはアボートに繋がります。カーネル非動作状態で使用した場合の動作は未定義です。

注意

アプリケーションがプラットフォーム固有のAPIを介して std の動作と干渉した場合の動作は、一般に定義されません。RTOSカーネルに固有のメカニズムを介してタスクの実行に干渉された場合に予測可能な動作が望まれる場合 (例えば、 rel_wai() により待ち操作が中断されるケースを処理したい場合)、 std の同期プリミティブを使用せず、カーネルAPIを直接使用することが必要となります。


環境変数

環境変数を扱うAPI (std::env::var() 等) はlibc の getenv() 等を使用して実装されています。 std 含む様々なライブラリが環境変数を使用しますが、これらのlibc関数はexeGCCではサポートされていません (ライブラリの仕様(ARM) および ライブラリの仕様(AArch64) を参照)。このため、必要な場合はアプリケーションで getenv() を定義することを推奨します。

use std::os::raw::c_char;

#[no_mangle]
unsafe extern "C" fn getenv(name: *const c_char) -> *const c_char {
    let name = std::ffi::CStr::from_ptr(name);
    let value = match name.to_bytes() {
        // `std::thread` のデフォルトスタックサイズを減らす
        b"RUST_MIN_STACK" => b"32768\0",
        _ => return std::ptr::null(),
    };
    value.as_ptr() as _
}

std::collections::{HashMap, HashSet}

HashMapHashSet のデフォルト Hasher 実装である std::collections::hash_map::RandomState はHash DoS耐性を確保するために乱数生成器を使用して内部状態を乱数化します。乱数を生成するために SOLID_RNG_SampleRandomBytes() 関数を使用します。この関数が失敗した場合はパニックします。

注釈

SOLID_RNG_SampleRandomBytes() による乱数生成にはBSPまたはアプリケーションによるエントロピー源デバイスドライバの実装が必要です。


時刻

実時刻 (std::time::SystemTime) はlibcの time_t のラッパーです。現在時刻の取得はSOLID-OS RTC APIを使用して行われます。タイムゾーンはサポートされておらず、 SOLID_RTC_ReadTime() から返された SOLID_RTC_TIME はUTCとして解釈されて time_t に変換されます。

計測用の時刻 (std::time::Instant) はTOPPERS APIの get_tim() を使用して実装されています。


標準入出力

標準出力と標準エラー出力は SOLID_LOG_write() に接続されています。標準入力は実装されておらず、 read メソッドは常に Ok(0) を返します。

注釈

std::io::Stdout 経由での出力は内部のミューテックスによって排他制御されます。このため、出力が行えるシステム状態は同期プリミティブの仕様による制限を受けます。


ファイルシステム

ファイルシステムAPI (std::fs) はSOLID-OSファイルシステムAPIの上で実装されています。パスAPI (std::path) はSOLID-OSファイルシステムで使用するために \ (ASCII: バックスラッシュ, Windows-932: 円記号) をパス区切り文字として使用します。

以下のAPIは対応するSOLID-OSファイルシステムの機能が無いため実装されておらず、 ErrorKind::Unsupported エラーを返します。

SOLID-OSファイルシステムAPIはそのままではスレッド安全ではないため、パスにプレフィックス \TS\ を付加することによりスレッド安全性ラッパーを経由してファイルシステム操作を行います。

注釈

nightly-2022-02-20 より以前のバージョンではスレッド安全性ラッパーを使用していませんでした。

UNIXターゲットと同様にパスおよびOS文字列 (std::ffi::OsStr) は未解釈のバイト列として表現されます。 str への変換はバイト列をUTF-8として解釈することで行われます。

注釈

OS文字列とバイト列の間の無損失変換は std::os::solid::ffi::{OsStrExt, OsStringExt} を使用することで実現できます。このAPIはUNIXターゲット向けのもの (std::os::unix::ffi::OsStrExt など) と同等です。


ネットワーク

ネットワークAPI (std::net) はSOLID Sockets APIの上で実装されています。サポートされる機能はSOLID Sockets実装によります。例えば、lwIPソケットは try_clone() 操作 (SOLID_NET_Dup()) をサポートしていません。


パニック

パニックはサポートされています。

注意

パニック時のアンワインド (ローカル変数のデストラクタの実行と std::panic::catch_unwind() による捕捉) はC++例外処理機構を使用して実装されています。正しく動作させるためには、 __register_frame_info() の呼出しによってC++例外ハンドラ情報が正しく登録されること必要です。


その他

アボート (std::process::abort()) は abort() グローバル関数を呼び出すことによって行われます。


サブプロセス

サポートされていません。


バックトレース

サポートされていません。


スタートアップコード

Rustコンパイラから実行可能ファイルを直接生成する場合、 std に含まれるスタートアップコードが main 関数の前に実行されます。SOLIDターゲットでは実行可能ファイルを直接生成することはサポートしていないため、スタートアップコードが使用されることはありません。