TOPPERS/ASPカーネル 設計メモ 対応バージョン: Release 1.9.2 最終更新: 2014年4月15日(作成中) このドキュメントは,TOPPERS/ASPカーネルの設計メモである.作成途中のもの であり,網羅的ではない. ---------------------------------------------------------------------- TOPPERS/ASP Kernel Toyohashi Open Platform for Embedded Real-Time Systems/ Advanced Standard Profile Kernel Copyright (C) 2005-2014 by Embedded and Real-Time Systems Laboratory Graduate School of Information Science, Nagoya Univ., JAPAN 上記著作権者は,以下の(1)~(4)の条件を満たす場合に限り,本ソフトウェ ア(本ソフトウェアを改変したものを含む.以下同じ)を使用・複製・改 変・再配布(以下,利用と呼ぶ)することを無償で許諾する. (1) 本ソフトウェアをソースコードの形で利用する場合には,上記の著作 権表示,この利用条件および下記の無保証規定が,そのままの形でソー スコード中に含まれていること. (2) 本ソフトウェアを,ライブラリ形式など,他のソフトウェア開発に使 用できる形で再配布する場合には,再配布に伴うドキュメント(利用 者マニュアルなど)に,上記の著作権表示,この利用条件および下記 の無保証規定を掲載すること. (3) 本ソフトウェアを,機器に組み込むなど,他のソフトウェア開発に使 用できない形で再配布する場合には,次のいずれかの条件を満たすこ と. (a) 再配布に伴うドキュメント(利用者マニュアルなど)に,上記の著 作権表示,この利用条件および下記の無保証規定を掲載すること. (b) 再配布の形態を,別に定める方法によって,TOPPERSプロジェクトに 報告すること. (4) 本ソフトウェアの利用により直接的または間接的に生じるいかなる損 害からも,上記著作権者およびTOPPERSプロジェクトを免責すること. また,本ソフトウェアのユーザまたはエンドユーザからのいかなる理 由に基づく請求からも,上記著作権者およびTOPPERSプロジェクトを 免責すること. 本ソフトウェアは,無保証で提供されているものである.上記著作権者お よびTOPPERSプロジェクトは,本ソフトウェアに関して,特定の使用目的 に対する適合性も含めて,いかなる保証も行わない.また,本ソフトウェ アの利用により直接的または間接的に生じたいかなる損害に関しても,そ の責任を負わない. $Id: design.txt 2676 2015-05-26 15:16:46Z ertl-hiro $ ---------------------------------------------------------------------- ○目次 ・TOPPERS/ASPカーネルの実装設計方針 ・システム状態とコンテキストの実装 - カーネル動作状態と非動作状態 - タスクコンテキストと非タスクコンテキスト - 全割込みロック状態と全割込みロック解除状態 - CPUロック状態とCPUロック解除状態 - 割込み優先度マスク - ディスパッチ禁止状態とディスパッチ許可状態 - ディスパッチ保留状態 ・タスク状態の管理とスケジューリング - タスク状態の管理 - タスクスケジューラ ・タスクディスパッチ処理の実装 - タスクディスパッチ処理の必要なタイミング - タスクディスパッチャの構造 - タスクの終了時のタスクディスパッチ - reqflgの導入理由 ・タスク例外処理機能の実装 - タスク例外処理ルーチンの実行開始条件とシステム状態(仕様の確認) - タスク例外処理ルーチンの呼出し処理 - タスク例外処理ルーチンの実行開始が必要なタイミング - タスク例外処理ルーチンの実行開始処理 - call_texrtnからdispatchを呼び出す処理について ・エラーのチェック順序 - エラーの3分類 - 静的エラーのチェック順序 - 準静的エラーのチェック順序 ・CHECKマクロとgoto文の使用 - CHECKマクロの定義とその使用法 - 設計意図 - CHECKマクロを使用してよい条件 - 問題を生じることがない根拠 ・ext_tsk,ext_kerの返り値 ・カーネルのデータ構造に対するvolatile宣言について ・型キャストに伴う警告メッセージ ・性能評価用システム時刻参照機能 - 必要性と使途 - API仕様 - 実装 ・タスク例外処理禁止フラグをenatexで実装している理由 ○TOPPERS/ASPカーネルの実装設計方針 TOPPERS/ASPカーネル(以下,ASPカーネル)は,TOPPERS新世代カーネルの出発 点となるリアルタイムカーネルである.TOPPERS新世代カーネル仕様の設計方針 と,ASPカーネルの適用対象領域と設計方針については,TOPPERS新世代カーネ ル統合仕様書に述べられている. 以下では,ASPカーネルの実装設計方針について述べるが,仕様設計方針とも関 連しており,明確に分離できない部分もある. TOPPERS/ASPカーネルの実装設計を行うにあたり,次の方針を設定する. (1) ソースコードの読みやすさ・改造しやすさを重視する ソースコードが読みやすいことは,オープンソースソフトウェアの品質を向上 させる上で最も重要な特性である.ソースコードを理解している技術者が増え ることで,問題を早期に発見することができ,サポート体制も充実させること ができる.また,ソースコードが読みやすいことは,シンプルな設計がされて いることも意味しており,信頼性向上にもつながる.さらに,技術者教育のた めの教材とする観点からも,ソースコードが読みやすいことは重要となる. 改造しやすいことは,システム毎の要求にあわせたチューニングが行いやすい ことを意味しており,擦り合わせ型の開発を支援する性質である.また,ASPカー ネルを基盤としてTOPPERS新世代カーネルシリーズを開発していく上でも,改造 しやすいことは必須の条件である. (2) 新しいターゲットシステムへのポーティングが容易な構造とする 組込みシステムには多様なハードウェアが用いられるため,それらに容易にポー ティングできることは重要な性質である.そのために,実行性能に配慮しつつ ハードウェアを抽象化し,ターゲットシステムに依存する部分(ターゲット依 存部)と依存しない部分(ターゲット非依存部)を明確に分離する.また,開 発環境(コンパイラなど)に依存する部分も明確に分離する. (3) 検証が容易な構造とする 信頼性を確保するために,検証が容易な構造とする. 具体的には,サービスコールのほとんど全体を割込み禁止で実行することとし, サービスコールの処理途中で割込みを許可しない.この構造は,読みやすく改 造しやすいソースコードにするためにも有効である.これにより割込み応答性 が犠牲になるが,それによって問題を生じるアプリケーションは少数であり, やむをえないものと考える. また,条件コンパイル等によりコンフィギュレーションできる箇所を増やすと, 細かな最適化ができる一方で,検証すべき組合せが増えることから,コンフィ ギュレーションできる箇所は必要最低限とする. (4) 実行性能とメモリ使用量に配慮する 上記の方針を満たした上で,高い実行性能と小さいメモリ使用量を達成できる ような実装を行う.実行性能を向上させる際には,平均性能の向上よりも,最 悪時性能の向上を重視する. ソースコードの読みやすさを重視すると言っても,実行性能の悪いアルゴリズ ムを安易に採用することはせず,高い実行性能を達成できるアルゴリズムを用 いる.ただし,新しいターゲットシステムへのポーティングを容易にするため に大部分をC言語で実装しており,すべてをアセンブリ言語で記述した場合に比 べて実行性能が落ちるのはやむをえない. メモリ使用量については,RAMの使用量を削減することに重点を置いた設計を行 うが,上記の方針および実行性能とのトレードオフを考慮し,ぎりぎりまでの 削減は行わない. (5) スケーラビリティに配慮する 様々な規模のシステムに適用できるスケーラビリティをもった構造とする.特 に,小規模なシステムに適用する際に,使用しない機能をカーネルが持ってい ることによるメモリ使用量の増加が最小限になるように配慮する. 具体的には,アプリケーションとカーネルを1つのロードモジュールにリンクす る方法(1リンクモデル)を想定し,カーネルを関数単位でライブラリ化して, 使用する関数のみをリンクできる構造とする.これは一種のコンフィギュレー ションであるが,この方法は,条件コンパイルによるコンフィギュレーション とは違い,検証工数に与える影響が小さい. また,固定的に使用するRAM領域を減らし,スタックに置ける情報はできる限り スタック上に置く. ○システム状態とコンテキストの実装 この章では,「TOPPERS新世代カーネル統合仕様書」の「2.5 システム状態とコ ンテキスト」の節に規定されているシステム状態とコンテキストの実装方法に ついて記述する. ●カーネル動作状態と非動作状態 カーネルの動作状態を管理するために,カーネル動作状態フラグ(kerflg)を 用意する.kerflgは,スタートアップモジュールでfalse(=0)に初期化する. また,カーネル動作の開始時にtrueにし,カーネル動作の終了時にfalseにする. ---------------------------------------- bool_t kerflg = false; ---------------------------------------- kerflgは,sns_kerで参照する.カーネル非動作状態でsns_ker以外のサービス コールを呼び出した場合の動作は保証する必要がないため,他のサービスコー ルではkerflgを参照しない. ●タスクコンテキストと非タスクコンテキスト タスクコンテキストと非タスクコンテキストの切換えは,ターゲットハードウェ アおよびターゲット依存部に委ねる.また,どちらのコンテキストで実行中で あるかを判別する関数(sense_context)も,ターゲット依存部で用意すること とする. ●全割込みロック状態と全割込みロック解除状態 全割込みロックフラグの管理はターゲット依存部に委ね,全割込みロック状態 に遷移させるマクロ(SIL_LOC_INT)と,元の状態に戻すマクロ(SIL_UNL_INT) は,SILのターゲット依存部で用意することとする.全割込みロック状態である か否かを判別する機能は,必要がないために用意していない. 全割込みロック状態でsns_kerとext_ker以外のサービスコールを呼び出した場 合の動作は保証する必要がないため,サービスコール中で全割込みロック状態 であることを判別する必要はない. ●CPUロック状態とCPUロック解除状態 CPUロックフラグの管理はターゲット依存部に委ね,CPUロック状態に遷移させ る関数(t_lock_cpu/i_lock_cpu/x_lock_cpu)と,CPUロック解除状態に遷移 させる関数(t_unlock_cpu/i_unlock_cpu/x_unlock_cpu)は,ターゲット依 存部で用意することとする.また,CPUロック状態であるか否かを判別する関数 (t_sense_lock/i_sense_lock/x_sense_lock)も,ターゲット依存部で用意 することとする. ●割込み優先度マスク 割込み優先度マスクの管理はターゲット依存部に委ね,割込み優先度マスクを 設定する関数(t_set_ipm/i_set_ipm/x_set_ipm)と,それを参照する関数 (t_get_ipm/i_get_ipm/x_get_ipm)は,ターゲット依存部で用意することと する. ただし,タスクコンテキストの実行中に,割込み優先度マスク全解除状態であ ることを効率的に判断するために,割込み優先度マスク全解除状態であること を示すフラグ(ipmflg)を用意する. ipmflgは,カーネルの初期化時にfalseに初期化する.非タスクコンテキストで は,割込み優先度マスク全解除状態になることはないため,このフラグを用い る必要はなく,フラグの更新も行わない. ---------------------------------------- bool_t ipmflg; ---------------------------------------- ●ディスパッチ禁止状態とディスパッチ許可状態 ディスパッチ禁止フラグを管理するために,フラグ(disdsp)を用意する. disdspは,カーネルの初期化時にfalseに初期化する. ---------------------------------------- bool_t disdsp; ---------------------------------------- ※ disdspではなく,それを論理反転したenadspを用意した方が,ipmflgや dspflgとの整合性の観点からは良かった. ●ディスパッチ保留状態 ディスパッチ保留状態は,非タスクコンテキストの実行中,CPUロック状態,割 込み優先度マスクが全解除でない状態,ディスパッチ禁止状態のいずれか(ま たは,それらが重なった状態)である. タスクコンテキストの実行中に,ディスパッチ保留状態でないこと(つまり, ディスパッチできる状態であること)を効率的に判別するために,割込み優先 度マスク全解除状態であり,ディスパッチ許可状態である(ディスパッチ禁止 状態でない)ことを示すフラグ(dspflg)を用意する.すなわち,常に 「dspflg == (ipmflg && !disdsp)」に設定する. dspflgは,カーネルの初期化時にtrueに初期化する.また,タスクコンテキス トにおいて割込み優先度マスクの値が変更されるか,割込み禁止フラグが変更 される度に更新する.非タスクコンテキストでは,常にディスパッチ保留状態 であるため,このフラグを用いる必要はなく,フラグの更新も行わない. ---------------------------------------- bool_t dspflg; ---------------------------------------- ○タスク状態の管理とスケジューリング ●タスク状態の管理 タスク管理ブロック(TCB)中のタスク状態を管理するフィールド(tstat)で は,タスク状態が次のいずれであるかを管理する.  ・実行できる状態  ・休止状態  ・(狭義の)待ち状態  ・強制待ち状態  ・二重待ち状態 タスクが実行できる状態の時に,実行状態であるか実行可能状態であるかは, このフィールドでは管理せず,実行状態のタスクのTCBを指すポインタ変数 (p_runtsk)によって判別する.実行状態のタスクがない場合は,p_runtskは NULLにする. ---------------------------------------- TCB *p_runtsk; ---------------------------------------- p_runtskは,カーネルの初期化時にNULLに初期化し,ディスパッチャにおいて 更新する.サービスコールの処理の中で自タスクに関する情報を参照する場合 は,p_runtskを用いる. ●タスクスケジューラ タスクスケジューラは,実行できる状態のタスクの中から,最も優先順位が高 いタスク(これを,最高優先順位のタスクと呼ぶ)を決定し,そのタスクの TCBを指すポインタ変数(p_schedtsk)を設定する. ---------------------------------------- TCB *p_schedtsk; ---------------------------------------- タスクスケジューラに対しては,どのタスクが実行できる状態であるかを知ら せる必要がある.そのため,タスクスケジューラは,次の2つの関数を用意する.  ・タスクが実行できる状態に遷移したことを知らせる関数(make_runnable)  ・タスクが実行できる状態から他の状態へ遷移したことを知らせる関数 (make_non_runnable) また,処理の効率化のために,上の2つの関数を用いずにレディキューを直接操 作してタスクスケジュールを行う関数として,次の2つの関数を用意する.  ・タスクの優先度の変更(change_priority)  ・レディキューの回転(rotate_ready_queue) ○タスクディスパッチ処理の実装 ●タスクディスパッチ処理の必要なタイミング タスクディスパッチは,実行状態のタスク(p_runtsk)と最高優先順位のタス ク(p_schedtsk)が一致しておらず,ディスパッチ保留状態でない場合に行う. このことから,タスクディスパッチ処理を行う必要があるのは,次の3つの場合 である. (1) 実行状態のタスクが実行できる状態でなくなる 自タスクを広義の待ち状態に遷移させるサービスコールや,自タスクを終了さ せるサービスコールにおいて,タスクディスパッチ処理を行う必要がある. (2) 最高優先順位のタスクが変化する タスクの起動,タスクの待ち解除,タスクの強制待ちからの再開,タスクの優 先度の変更,タスクの優先順位の回転を行うサービスコールにおいて,最高優 先順位のタスクが変化し,ディスパッチ保留状態でない場合には,タスクディ スパッチ処理を行う必要がある. (3) ディスパッチ保留状態が解除される ディスパッチ保留状態とは,非タスクコンテキストの実行中,CPUロック状態, 割込み優先度マスクが全解除でない状態,ディスパッチ禁止状態の総称である ため,これらの状態のいずれかが遷移するタイミングで,タスクディスパッチ 処理を行う必要がある.具体的には,次のタイミングが該当する. (3-1) 非タスクコンテキストからタスクコンテキストに遷移する 割込みハンドラまたはCPU例外ハンドラからタスクにリターンする際に,タスク ディスパッチ処理を行う必要がある. (3-2) CPUロック状態が解除される CPUロック状態においては,上記の(1)や(2)の状況を作り出すサービスコールを 呼び出すことができない.そのため,CPUロック状態の解除時には,タスクディ スパッチ処理を行う必要がない. (3-3) 割込み優先度マスクが全解除される 割込み優先度マスクの変更(chg_ipm)により割込み優先度マスクが全解除され る場合に,タスクディスパッチ処理を行う必要がある. また,タスクの終了,タスク例外処理ルーチンからのリターン,割込みハンド ラからのリターン,割込みサービスルーチンからのリターン,タイムイベント ハンドラからのリターン,CPU例外ハンドラからのリターンによって,割込み優 先度マスクが全解除される場合があり,その場合には,タスクディスパッチ処 理を行う必要がある.ただし,割込みサービスルーチンおよびタイムイベント ハンドラからのリターンについては,リターン後も非タスクコンテキストであ り,タスクディスパッチ保留状態が継続することから,タスクディスパッチ処 理を行う必要がない. (3-4) ディスパッチ許可状態になる ディスパッチの許可(ena_dsp)において,タスクディスパッチ処理を行う必要 がある. また,タスクの終了とタスク例外処理ルーチンからのリターンによって,ディ スパッチ許可状態になる場合があり,その場合には,タスクディスパッチ処理 を行う必要がある. 以上に加えて,カーネルの動作開始時にも,タスクディスパッチ処理を呼び出 す. ●タスクディスパッチャの構造 タスクディスパッチャの主な機能は,切換え前のタスクのコンテキスト(プロ セッサの汎用レジスタ等)をメモリ上に保存し,切換え後のタスクのコンテキ ストをメモリ上から復帰することである.ここで,保存/復帰しなければなら ないレジスタは,タスクディスパッチャが実行される状況によって,次のよう な違いがある. ・タスクが割込み(または,CPU例外)によりプリエンプトされる場合には,す  べてのレジスタを保存しなければならない.また,その状態から実行再開す  る場合には,すべてのレジスタを復帰しなければならない. ・タスクが自発的にタスクディスパッチャを呼び出す場合には,スクラッチレ  ジスタ(caller saved register)以外のレジスタを保存すればよい.また,  その状態から実行再開する場合には,スクラッチレジスタ以外のレジスタを  復帰すればよい. ・タスクが終了する場合には,どのレジスタも保存する必要がない. ・タスクが実行開始する場合には,どのレジスタも復帰する必要がない. そこで,それぞれの状況で必要最低限のレジスタのみを保存/復帰するために, タスクディスパッチ処理を,(a) コンテキストの保存処理,(b) 実行するタス クの選択処理,(c) コンテキストの復帰処理の3つのステップで構成し,(a)と (c)のステップについては,タスクディスパッチャが実行される状況毎に用意す る.具体的には,次の各処理を行うルーチンを用意する. (a) コンテキストの保存処理 (a-1) タスクが自発的に呼び出した場合の保存処理(dispatch) (a-2) 割込みハンドラの出口で呼び出された場合の保存処理(ret_int) (a-3) CPU例外ハンドラの出口で呼び出された場合の保存処理(ret_exc) (a-4) タスクの終了時の処理(exit_and_dispatch) (a-5) カーネルの動作開始時の処理(start_dispatch) (b) 実行するタスクの選択(ディスパッチャ本体,dispatcher) (c) コンテキストの復帰処理 (c-1) タスクが自発的に呼び出した場合の復帰処理(dispatch_r) (c-2) 割込みハンドラの出口で呼び出された場合の復帰処理(ret_int_r) (c-3) CPU例外ハンドラの出口で呼び出された場合の復帰処理(ret_exc_r) (c-4) タスクの実行開始時の処理(start_r) ●タスクの終了時のタスクディスパッチ ext_tskによるタスクの終了時に,起動要求がキューイングされていると,同じ タスクがすぐに起動される.この場合,タスクディスパッチャにとっては,同 じタスクへの切換えに見え,タスクディスパッチ処理をスキップ可能に思える が,実際には,同じタスクの異なるインスタンスへの切換えであるため,タス クディスパッチ処理をスキップしてはならない. 上述のタスクディスパッチャの構造により,タスクの終了時には,それ専用の 処理(exit_and_dispatch)を呼び出す.この処理では,実行状態のタスクを参 照せず,コンテキストの保存も行わずに,次に事項するタスクの選択処理を行 うため,タスクディスパッチ処理がスキップされることはない. なお,ter_tskによって実行状態のタスクを終了させることはできないため, ter_tskではこのような状況は起こらない. ●reqflgの導入理由 割込みハンドラ/CPU例外ハンドラの出口処理に,タスクディスパッチまたはタ スク例外処理ルーチンの実行開始を要求することを示すフラグとして,reqflg を用意している.非タスクコンテキストにおいて,タスクディスパッチが必要 になった場合や,タスク例外処理ルーチンの実行開始が必要になった場合には, このフラグをセットする. reqflgを導入した理由は,割込みハンドラ/CPU例外ハンドラの出口処理の典型 的なケース(タスクディスパッチもタスク例外処理ルーチンの実行開始も必要 ない場合)を高速化するためである.また,ディスパッチャ本体(dispatcher) のアイドル処理も高速化できる. しかし,割込みハンドラ/CPU例外ハンドラの出口処理全体のオーバヘッドを考 えると,reqflgによる高速化の効果はそれほど大きくないものと思われる. ○タスク例外処理機能の実装 ●タスク例外処理ルーチンの実行開始条件とシステム状態(仕様の確認) タスク例外処理ルーチンは,次の6つの条件が揃った場合に実行が開始される.  ・タスク例外処理許可状態である  ・保留例外要因が0でない  ・タスクが実行状態である  ・タスクコンテキストが実行されている  ・割込み優先度マスク全解除状態である  ・CPUロック状態でない また,タスク例外処理ルーチンの実行開始/リターン時のシステム状態に関す る仕様は次の通りである. CPUロック 割込み優先度 ディスパッチ フラグ マスク 禁止フラグ ------------------------------------------------------------ 【タスク例外処理ルーチン】 実行開始条件 解除 全解除 任意 実行開始時処理 そのまま そのまま そのまま リターン前 原則解除(*1) 原則全解除(*1) 元に戻す リターン時処理 解除する 全解除する 元に戻す(*4) ------------------------------------------------------------ ●タスク例外処理ルーチンの呼出し処理 タスク例外処理ルーチンを呼び出す処理は,次の流れとなる.この関数は, CPUロック状態で呼び出すことを想定している. ---------------------------------------- void call_texrtn(void) { TEXPTN texptn; texptn = p_runtsk->texptn; p_runtsk->texptn = 0U; タスク例外処理禁止状態にする ディスパッチ禁止フラグを保存する CPUロック解除状態にする タスク例外処理ルーチンを呼び出す CPUロック状態にする 割込み優先度マスク全解除状態にする ディスパッチ禁止フラグを元に戻す タスク例外処理禁止状態にする … (*2) 必要な場合にはディスパッチを行う … (*1) タスク例外処理許可状態にする } ---------------------------------------- (*1)において,必要な場合にタスク切換えを行うのは,割込み優先度マスクを 全解除にし,ディスパッチ禁止フラグを元に戻した結果,ディスパッチ保留状 態が解除され,ディスパッチが必要になる場合があるためである. また,ディスパッチを行う前にタスク例外処理禁止状態にする(*2)理由につい ては,「call_texrtnからdispatchを呼び出す処理について」の節を参照するこ と. ●タスク例外処理ルーチンの実行開始が必要なタイミング タスク例外処理ルーチンは,前述の6つの条件が揃った場合に実行開始すべきで あるため,6つの条件のいずれかを新たに満たすようになる可能性のあるタイミ ングで,タスク例外処理ルーチンの実行開始が必要になる. 以下では,6つの条件のいずれかを新たに満たすようになるタイミングについて 検討する. (1) タスク例外処理許可状態である タスク例外処理の許可(ena_tex)によって,タスク例外処理許可状態になる. また,タスク例外処理ルーチンからのリターンによっても,タスク例外処理許 可状態になる. (2) 保留例外要因が0でない タスク例外処理の要求(ras_tex,iras_tex)によって,タスクの保留例外要因 が0でなくなる.ただし,非タスクコンテキストからのタスク例外処理の要求 (iras_tex)では,(4)の条件が満たされないため,タスク例外処理ルーチンの 実行開始は必要ない. (3) タスクが実行状態である タスクディスパッチャにより,切換え後のタスクが実行状態になる. (4) タスクコンテキストが実行されている 割込みハンドラおよびCPU例外ハンドラからのリターンによって,タスクコンテ キストに戻る場合がある. (5) 割込み優先度マスク全解除状態である 割込み優先度マスクの変更(chg_ipm)によって,割込み優先度マスクが全解除 になる. また,タスクの終了,タスク例外処理ルーチンからのリターン,割込みハンド ラからのリターン,CPU例外ハンドラからのリターンによっても,割込み優先度 マスクが全解除される場合もある. ただし,タスクの終了時については,タスクの終了後は別のタスクへ切り換わ るため,切り換わった後のタスクに対するタスク例外処理ルーチンの実行開始 を,タスクディスパッチ後の処理で行えばよい. (6) CPUロック状態でない CPUロック状態の解除(unl_cpu,iunl_cpu)によって,CPUロック状態でなくな る.ただし,非タスクコンテキストでのCPUロック状態の解除(iunl_cpu)では, (4)の条件が満たされないため,タスク例外処理ルーチンの実行開始は必要ない. また,タスクの終了,タスク例外処理ルーチンからのリターン,割込みハンド ラからのリターン,割込みサービスルーチンからのリターン,タイムイベント ハンドラからのリターン,CPU例外ハンドラからのリターンによって,CPUロッ ク状態でなくなる場合もある. ただし,割込みサービスルーチンおよびタイムイベントハンドラからのリター ンについては,リターン後も非タスクコンテキストの実行が続き,(4)の条件が 満たされないため,タスク例外処理ルーチンの実行開始は必要ない. タスクの終了時については,タスクの終了後は別のタスクへ切り換わるため, 切り換わった後のタスクに対するタスク例外処理ルーチンの実行開始を,タス クディスパッチ後の処理で行えばよい. 以上より,重複するケースを考慮すると,タスク例外処理ルーチンの実行開始 が必要になる可能性があるのは,以下の処理である. (a) タスク例外処理の許可(ena_tex)… (1) (b) タスク例外処理ルーチンの出口処理 … (1)(5)(6) (c) タスク例外処理の要求(ras_tex)… (2) (d) タスクディスパッチ後の処理 … (3)(5)(6) (d-1) dispatch_r (d-2) ret_int_r (d-3) ret_exc_r (d-4) start_r (e) 割込みハンドラの出口処理 … (4)(5)(6) (f) CPU例外ハンドラの出口処理 … (4)(5)(6) (g) 割込み優先度マスクの変更(chg_ipm)… (5) (h) CPUロック状態の解除(unl_cpu)… (6) この中で(d-4)に関しては,タスクの実行開始直後はタスク例外処理禁止状態で あり(自タスクがena_texするまでは,タスク例外が許可されない),(1)の条 件が満たされないため,タスク例外処理ルーチンの実行開始は必要ない. また(h)に関しては,次に述べる理由により,CPUロック状態が継続している間 に他の5つの条件が新たに満たされることはないため,タスク例外処理ルーチン の実行開始は必要ない. CPUロック状態では,まず,タスク例外処理の許可(ena_tex),タスク例外処 理の要求(ras_tex,iras_tex),割込み優先度マスクの変更(chg_ipm)を行 うことはできず,タスクディスパッチも起こらない.CPUロック状態でタスク例 外処理ルーチンからリターンすることはできるが,この場合はCPUロック状態も 解除され,CPUロック状態が継続しない.CPUロック状態で割込みハンドラから リターンした場合も,これと同様である. 最後に,CPUロック状態でCPU例外ハンドラからリターンした場合が問題になる. これについては,CPU例外が発生した時に,CPUロック状態であった場合と, CPUロック解除状態であった場合に分けて考える.CPUロック解除状態であった 場合には,CPU例外ハンドラからのリターンにより,CPUロック状態が解除され るため,割込みハンドラからリターンした場合と同様である.CPUロック状態で あった場合には,起動されるCPU例外ハンドラはカーネル管理外のCPU例外ハン ドラであるため,その中で(1)~(3)と(5)の条件が新たに満たされることはない. CPU例外ハンドラの実行前後で(4)の条件は保存されることから,CPU例外ハンド ラからのリターンによって,(1)~(5)の条件はCPU例外の発生前に戻り,新たに 満たされることはない. ●タスク例外処理ルーチンの実行開始処理 ここでは,前の節で検討したタスク例外処理ルーチンの実行開始が必要なタイ ミングのそれぞれについて,実行開始処理の実装方法について述べる. (a) タスク例外処理の許可(ena_tex) ena_texがタスクから呼び出された場合には,自タスクに対して「タスクが実行 状態である」「タスクコンテキストが実行されている」の2条件は満たされてお り,「タスク例外処理許可状態である」の条件はena_texの処理により満たされ る.また,「CPUロック状態でない」の条件はena_texの入口でチェックしてい る.そのため,「保留例外要因が0でない」と「割込み優先度マスク全解除状態 である」の2条件が満たされている場合には,タスク例外処理ルーチンを呼び出 す. ena_texの本体の処理(エラー処理を除く)は次の通り. ---------------------------------------- p_runtsk->enatex = true; if (p_runtsk->texptn != 0U && ipmflg) { call_texrtn(); } ---------------------------------------- (b) タスク例外処理ルーチンの出口処理 タスク例外処理ルーチンの出口処理(call_texrtnの後半)では,自タスクに対 して「タスクが実行状態である」「タスクコンテキストが実行されている」の 2条件は満たされており,「タスク例外処理許可状態である」「割込み優先度マ スク全解除状態である」「CPUロック状態でない」の3条件が出口処理で満たさ れる.そのため,「保留例外要因が0でない」が満たされている場合には,タス ク例外処理ルーチンを呼び出す必要がある. ただし,タスク例外処理ルーチンの出口処理で単純にcall_texrtnを呼び出すと, call_texrtnの中からcall_texrtnを呼び出すことになり,タスク例外処理が繰 り返し要求された場合に,スタックの使用量に上限がなくなる.そこで,タス ク例外処理ルーチンの出口処理で「保留例外要因が0でない」場合には, call_texrtnの中でループさせる. 修正したcall_texrtnの流れは次の通り. ---------------------------------------- void call_texrtn(void) { TEXPTN texptn; ディスパッチ禁止フラグを保存する … (*3) タスク例外処理禁止状態にする … (*6) do { texptn = p_runtsk->texptn; p_runtsk->texptn = 0U; CPUロック解除状態にする タスク例外処理ルーチンを呼び出す CPUロック状態にする 割込み優先度マスク全解除状態にする ディスパッチ禁止フラグを元に戻す … (*4) タスク例外処理禁止状態にする … (*2) 必要な場合にはディスパッチを行う … (*1) } while (p_runtsk->texptn != 0U); タスク例外処理許可状態にする … (*5) } ---------------------------------------- タスク例外処理ルーチンの呼出し前にディスパッチ禁止フラグを保存する処理 は,(*4)において元の状態に戻すことから,ループの外(*3)で行うのが効率が よい. タスク例外処理ルーチンの呼出し前にタスク例外処理禁止状態にする処理は, (*2)においてタスク例外処理禁止状態にすることから,ループ外(*6)で行うの が効率がよい.また,タスク例外処理ルーチンの呼出し後にタスク例外処理許 可状態にする処理も,ループの外(*5)で行う方が効率がよい. (c) タスク例外処理の要求(ras_tex) ras_texがタスクから呼び出された場合で,対象タスクが自タスクの場合には, 自タスクに対して「タスクが実行状態である」「タスクコンテキストが実行さ れている」の2条件は満たされており,「保留例外要因が0でない」の条件は ras_texの処理により満たされる(ras_texのパラメータrasptnが0の場合はエラー となるため).また,「CPUロック状態でない」の条件はras_texの入口でチェッ クしている.そのため,対象タスクが自タスクであり,「タスク例外処理許可 状態である」と「割込み優先度マスク全解除状態である」の2条件が満たされて いる場合には,タスク例外処理ルーチンを呼び出す. ras_texの本体の処理(エラー処理を除く)は次の通り. ---------------------------------------- p_tcb->texptn |= rasptn; if (p_tcb == p_runtsk && p_runtsk->enatex && ipmflg) { call_texrtn(); } ---------------------------------------- (d-1) dispatch_r dispatch_rにおいては,dispatch_rからのリターン先のタスクに対して,「タ スクが実行状態である」「タスクコンテキストが実行されている」の2条件は満 たされている.また,CPUロック状態でdispatch_rに来ることはないため, 「CPUロック状態でない」の条件も成立している.そのため,「タスク例外処理 許可状態である」「保留例外要因が0でない」「割込み優先度マスク全解除状態 である」の3条件が満たされている場合には,タスク例外処理ルーチンを呼び出 す必要がある. これを実現するために,タスクコンテキストからのディスパッチ処理を次のよ うに修正する. ---------------------------------------- void dispatch(void) { ……… dispatch_r: スクラッチレジスタを除くすべてのレジスタをスタックから復帰する calltex(); } ---------------------------------------- ここでcalltexは,「タスク例外処理許可状態である」「保留例外要因が0でな い」「割込み優先度マスク全解除状態である」の3条件が満たされている場合に call_texrtnを呼び出す関数である. ---------------------------------------- void calltex(void) { if (p_runtsk->enatex && p_runtsk->texptn != 0U && ipmflg) { call_texrtn(); } } ---------------------------------------- (d-2) ret_int_r ret_int_rは,割込みハンドラの出口処理でディスパッチを行ったタスクが,実 行を再開する際の処理である.そのため,ret_int_rからのリターン先のタスク に対して,「タスクが実行状態である」「タスクコンテキストが実行されてい る」の2条件は満たされている.また,CPUロック状態でret_int_rに来ることは ないため,「CPUロック状態でない」の条件も成立している.そのため,「タス ク例外処理許可状態である」「保留例外要因が0でない」「(割込みハンドラか らのリターン後に)割込み優先度マスク全解除状態である」の3条件が満たされ ている場合には,タスク例外処理ルーチンを呼び出す必要がある. これを実現するために,割込みハンドラの出入口処理を次のように修正する. ---------------------------------------- void <割込みの出入口処理>(void) { ……… ret_int_r: スクラッチレジスタを除くすべてのレジスタをスタックから復帰する calltex(); } ……… } ---------------------------------------- (d-3) ret_exc_r ret_exc_rは,CPU例外ハンドラの出口処理でディスパッチを行ったタスクが, 実行を再開する際の処理である. カーネル管理のCPU例外ハンドラの出口処理(ret_exc)は,割込みハンドラの 出口処理(ret_int)と同様である.これは,CPUロック状態でCPU例外が発生し た場合には,カーネル管理外のCPU例外ハンドラとなり,CPU例外ハンドラの出 口処理でディスパッチを行うことはなく,ret_excにも来ないためである.その ため,ret_exc_rからのリターン先のタスクに対して,「CPUロック状態でない」 の条件が成立している. このことから,CPU例外ハンドラの出入口処理についても,割込みハンドラの出 入口処理と同様に,次のように修正すればよい. ---------------------------------------- void (void) { ……… ret_exc_r: スクラッチレジスタを除くすべてのレジスタを スタックから復帰する calltex(); } ……… } ---------------------------------------- (e) 割込みハンドラの出口処理 割込みハンドラからタスクへリターンする場合には,リターン先のタスクに対 して,「タスクが実行状態である」「タスクコンテキストが実行されている」 の2条件は満たされている.また,CPUロック状態で割込みハンドラが実行され ることはないため,「CPUロック状態でない」の条件も成立している.そのため, 「タスク例外処理許可状態である」「保留例外要因が0でない」「(割込みハン ドラからのリターン後に)割込み優先度マスク全解除状態である」の3条件が満 たされている場合には,タスク例外処理ルーチンを呼び出す必要がある. 割込みハンドラの呼出し前と呼出し後でこれらの条件が変化するのは,実行状 態のタスクが変化した時と,割込みハンドラ中でiras_texが呼び出された場合 である.割込みハンドラからはena_texとchg_ipmは呼び出せないため,「タス ク例外処理許可状態である」と「(割込みハンドラからのリターン後に)割込 み優先度マスク全解除状態である」の2条件が変化することはない. この2つの状況の内,実行状態のタスクが変化するケースは,ret_int_rで考慮 済みである.割込みハンドラ中でiras_texが呼び出された場合には,reqflgが trueになるため,タスク例外処理ルーチンの実行開始処理は,reqflgがtrueの 時にのみ行えばよい. これを実現するために,上で修正した割込みハンドラの出入口処理を,さらに 次のように修正する. ---------------------------------------- void <割込みの出入口処理>(void) { ……… if (タスクコンテキストで割込み発生) { ……… if (reqflg) { ……… ret_int_r: スクラッチレジスタを除くすべてのレジスタをスタックから復帰する } calltex(); } } ……… } ---------------------------------------- (f) CPU例外ハンドラの出口処理 カーネル管理のCPU例外ハンドラの出口処理は,割込みハンドラの出口処理と同 様である. カーネル管理外のCPU例外ハンドラの中では,実行状態のタスクが変化すること はなく,iras_texやena_texを呼び出すこともできないため,タスク例外処理ルー チンの実行開始条件が新たに満たされることはなく,タスク例外処理ルーチン の実行開始は必要ない. このことから,CPU例外ハンドラの出入口処理についても,割込みハンドラの出 入口処理と同様に,次のように修正すればよい. ---------------------------------------- void (void) { ……… if (タスクコンテキストでCPU例外発生) { ……… if (reqflg) { ret_exc_r: スクラッチレジスタを除くすべてのレジスタを スタックから復帰する } calltex(); } } ……… } ---------------------------------------- (g) 割込み優先度マスクの変更 chg_ipmがタスクから呼び出された場合には,自タスクに対して「タスクが実行 状態である」「タスクコンテキストが実行されている」の2条件は満たされてい る.また,「CPUロック状態でない」の条件はchg_ipmの入口でチェックしてい る.そのため,パラメータintpriがTIPM_ENAALLであり,「タスク例外処理許可 状態である」「保留例外要因が0でない」の2条件が満たされている場合には, タスク例外処理ルーチンを呼び出す. chg_ipmの関連部分の処理は次の通り. ---------------------------------------- t_set_ipm(intpri); if (intpri == TIPM_ENAALL) { ipmflg = true; ここにタスク切換え処理が入る if (p_runtsk->enatex && p_runtsk->texptn != 0U) { call_texrtn(); } } ---------------------------------------- dispatchを呼び出してタスク切換えを行う場合には,dispatchの出口でタスク 例外処理ルーチンを呼び出すためここで呼出し処理を行う必要はないが,コー ドが複雑になるため,dispatchを呼び出した場合もここでタスク例外処理ルー チンの呼出し処理を行っている. ●call_texrtnからdispatchを呼び出す処理について dispatch_rから(ターゲットによってはcalltexを経由して)call_texrtnを呼 び出し,call_texrtnからdispatchを呼び出すため,この2つの関数は相互再帰 呼出しをしている.ここでは,この実装で支障のない理由を説明する. call_texrtnからdispatchを呼び出すのは,ディスパッチが保留されていない状 態で呼び出されたタスク例外処理ルーチンが,その実行中にディスパッチ保留 状態に遷移し,さらにタスクディスパッチを必要とする処理を行い,ディスパッ チ保留状態を解除しないままリターンした場合である.この場合,call_texrtn の中でディスパッチ保留状態を解除した後に,dispatchを呼び出してタスクディ スパッチを行う.つまり,call_texrtnからdispatchを呼び出す処理は,タスク 例外処理ルーチンの中でディスパッチ保留状態を解除すべきであったのを,解 除せずにリターンした場合を救済するためのものである.以下,この振舞いを 「救済ケース」と呼ぶ. そこで,比較のために,タスク例外処理ルーチンの最後で,正しくディスパッ チ保留状態を解除してからリターンした場合の振舞いを考える.この場合には, ディスパッチ保留状態を解除するサービスコール(ena_dsp,chg_ipm)の中で, dispatchが呼び出されてタスクディスパッチが起こる.以下,この振舞いを 「正常ケース」と呼ぶ. 正常ケースと救済ケースを比較すると,call_texrtn→タスク例外処理ルーチン →サービスコール→dispatchの順でdispatchが呼び出されるか,call_texrtnか ら直接dispatchが呼び出されるかの違いということになり,救済ケースの方が スタックの使用量は少ない.つまり,正常ケースの動きを想定してスタック領 域が用意してあれば,救済ケースでも問題なく動作することになる. ここで,正常ケースと救済ケースで,上記以外に違いがないことが重要である. 具体的には,call_texrtnの中で,タスク例外処理許可状態にする(p_runtsk ->enatexをtrueにする)前に,dispatchを呼び出すことが重要である.タスク 例外処理許可状態にした後にdispatchを呼び出すと,出口のdispatch_rで,再 びタスク例外処理ルーチンを実行してしまう可能性があり,救済ケースの方が スタックの使用量が増えてしまう. タスク例外処理ルーチンの中で,タスク例外処理許可状態にしたままリターン した場合には,call_texrtnから呼び出したdispatchの出口のdispatch_rで再び タスク例外処理ルーチンを実行してしまう可能性がある.この場合も,タスク 例外処理ルーチンの最後で,ディスパッチ保留状態を解除してからリターンし た場合よりはスタックの使用量が少ないが,タスク例外処理禁止状態にした後 にディスパッチ保留状態を解除してリターンした場合よりは,スタックの使用 量が増える場合がある.そこで,このような状況が起こらないように, dispatchを呼ぶ前にp_runtsk->enatexをfalseにする. ○エラーのチェック順序 サービスコール内におけるエラーチェックは,以下の順序で行うことを原則と する.なお,この節には,保護機能対応カーネルに関する記述が含まれている が,それらの記述は将来的にはより適切なドキュメントに移動する予定である. ●エラーの3分類 サービスコールのエラーは,大きく以下の3つに分類することができる. (a) 静的エラー 対象のカーネルオブジェクトが登録されているか否かや,その状態に依存せず に,チェックすることができるエラー. (b) 準静的エラー 対象のカーネルオブジェクトが登録されていれば,その状態に依存せずにチェッ クすることができるエラー. (c) 動的エラー 対象のカーネルオブジェクトの状態に依存するエラー. ASPカーネルにおいては,(a)と(b)のエラーはクリティカルセクションの外側で, この順序でチェックし,(c)のエラーはクリティカルセクションの内側でチェッ クする.ただし,動的生成機能拡張パッケージでは,(b)のエラー(の一部)は クリティカルセクションの内側で実施する必要がある. ●静的エラーのチェック順序 静的エラーには,実行コンテキストのエラー(ディスパッチ保留状態からの呼 出しエラーも含む),パラメータの範囲等のエラー(対象のカーネルオブジェ クトに依存せずにチェックできるもの)が含まれる. サービスコール中では,最初に実行コンテキストのエラーをチェックし,その 後,パラメータの並び順に,範囲等のエラーをチェックする. 保護機能対応カーネルでは,パラメータがポインタである場合に,ポインタの 指すメモリ領域がアクセス可能であるかをチェックする必要があるが,メモリ 領域の設定が静的である場合には,このエラーチェックもここで実施する.た だし,メモリ領域の設定が静的でない場合には,このエラーチェックはクリティ カルセクションの内側で行う必要がある. ●准静的エラーのチェック順序 准静的エラーのチェックの前に,対象のカーネルオブジェクトの管理ブロック の先頭番地を,ローカル変数に代入する. 保護機能対応カーネルでは,この次に,対象のカーネルオブジェクトがアクセ ス可能であるかをチェックする処理を行う.当該サービスコールの呼出しが, システム状態に対するアクセス許可ベクタで保護されている場合にも,この段 階でチェックを実施する. その後で,パラメータの範囲等のエラーの中で,対象のカーネルオブジェクト の登録情報(初期化ブロックに含まれている情報)に依存してチェックすべき もののチェックを,パラメータの並び順で実施する. ○CHECKマクロとgoto文の使用 ASPカーネルの実装においては,サービスコールの静的なエラーをチェックする ために,名称が"CHECK_"で始まる一連のマクロ(これらを,CHECKマクロと総称 する)を用いている. CHECKマクロの定義中にはgoto文を含んでいるが,MISRA-Cなどのコーディング ルールではgoto文の使用を禁止しており,goto文を使うべきではないという意 見も多い.また,マクロの定義中にgoto文を使用することが問題であるという 意見もある. ここでは,定義中にgoto文を含むCHECKマクロを用いる設計意図とそれを使用し てよい条件,CHECKマクロの使用によりソフトウェアの信頼性に問題が生じるこ とがないことを論証する. なお,ASPカーネルのカーネル本体の実装では,CHECKマクロ以外にgoto文を用 いている箇所はない(一部のシステムサービスでは,これ以外の方法でgoto文 を用いている). ●CHECKマクロの定義とその使用法 kernel/check.hには,25個のCHECKマクロが定義されているが,いずれも次のパ ターンで定義されている.ここでXXXXXには,チェックしたいエラーの種類を表 す文字列が入る. ---------------------------------------- #define CHECK_XXXXX(<……>) { \ if (<エラー条件>) { \ ercd = <エラーコード>; \ goto error_exit; \ } \ } ---------------------------------------- これらのCHECKマクロは,多くのサービスコールの処理関数中で,次のように使 用されている. ---------------------------------------- ER <サービスコール名>(……) { <ローカル変数の宣言> ER ercd; LOG_XXX_YYY_ENTER(……); CHECK_XXXXX(……); CHECK_YYYYY(……); <サービスコール処理本体> error_exit: LOG_XXX_YYY_LEAVE(……); return(ercd); } ---------------------------------------- この例では,CHECKマクロを2つ使用しているが,1つのみ使用している場合もあ れば,3つ以上使用している場合もある.また,複数のCHECKマクロの間に,ロー カル変数への代入文が入る場合もある(例えば,ter_tskの処理関数). ●設計意図 CHECKマクロを使用する意図は,ほとんどのサービスコールで必要な静的エラー のチェックコードをパターン化し,ソースコードの簡潔さを保つことで読みや すさを向上させるとともに,記述ミスの可能性を減らすことである. ちなみに,CHECKマクロを使用しない場合,上に示したサービスコールの処理関 数は,次のように記述することになる. ---------------------------------------- ER <サービスコール名>(……) { <ローカル変数の宣言> ER ercd; LOG_XXX_YYY_ENTER(……); if () { ercd = ; } else { if () { ercd = ; } else { <サービスコール処理本体> } } LOG_XXX_YYY_LEAVE(……); return(ercd); } ---------------------------------------- CHECKマクロの内容を知っているという前提の下では,元のソースコードの方が 読みやすいのは明らかである. なお,このようなCHECKマクロの定義には,goto文の使用が不可避である. ●CHECKマクロを使用してよい条件 CHECKマクロは,次の条件を満たすように使用しなければならない. (1) CHECKマクロは,サービスコール処理関数の先頭部分で,サービスコール入   口のログを出した後,クリティカルセクションに入る前までに,処理関数   のトップレベルで用いる. (2) サービスコール処理関数の末尾部分で,クリティカルセクションを抜けた   後,サービスコール出口のログを出す前に,処理関数のトップレベルに,   error_exitラベルを置く. ●問題を生じることがない根拠 このような方法でgoto文を使用しても,ソフトウェアの信頼性に問題が生じる ことがないことを主張するには,そもそも,MISRA-Cなどのコーディングルール でgoto文の使用を禁止している根拠,言い換えると,goto文の使用によりソフ トウェアの信頼性に問題が生じる可能性のある理由を明らかにする必要がある. MISRA-Cのドキュメントでは,goto文の使用を禁止する根拠についてあまり明確 になっておらず,ここではその根拠を次のように推測する. ・処理の流れが複雑になり,プログラムの意図が読みにくくなる(いわゆる,  スパゲッティプログラムになる). CHECKマクロの場合には,goto文をエラー発生時の強制脱出に用いており,ラベ ルもerror_exitとするなど,プログラムの意図は明らかである.一方,このよ うにgoto文をエラー時の強制脱出に用いる場合には,次の点もgoto文の使用を 禁止する根拠になりうる. ・goto文で強制脱出することにより,脱出時に行わなければならない後処理が  飛ばされるおそれがある. ただしこの点についても,CHECKマクロを上記の使用条件を満たして使う限りは, 脱出時に行わなければならない後処理はなく,CHECKマクロの使用によりソフト ウェアの信頼性に問題が生じることはない. 問題を生じることがないことを論証するもう1つ方法は,コーディングルールに 合致しないプログラムが,コーディングルールに合致したプログラムと等価で あることを示す方法である. すでに「設計意図」の節で述べたように,CHECKマクロを使用したプログラムは, goto文を使用しない等価なプログラムに書き換えることができるが,より一般 的には,次のことが言える. else部を持たないif文のthen部の最後に,if文より下(前方)にあり,if文と 同じブロック内の同じ階層のラベルへ分岐するgoto文がある場合には,if文と ラベルの間の文をelse部にすることにより,goto文を使わない等価なプログラ ムに書き換えることができる. 例として,goto文を使った次のプログラムを考える. ---------------------------------------- { /* if文の前の文 */ if (....) { /* then部の文 */ goto <ラベル>; } /* if文とラベルの間の文 */ <ラベル>: /* ラベルより後ろの文 */ } ---------------------------------------- このプログラムは,goto文を使わない次のプログラムと等価である. ---------------------------------------- { /* if文の前の文 */ if (....) { /* then部の文 */ } else { /* if文とラベルの間の文 */ } /* ラベルより後ろの文 */ } ---------------------------------------- この条件に合致するgoto文が複数ある場合には,下にあるgoto文から順に上記 の方法によって書き換えることで,goto文を使わない等価なプログラムに書き 換えることができる. CHECKマクロの使用方法は,上記のgoto文の使用方法に合致するため,goto文を 使用しない等価なプログラムに書き換えることができる.よって,このCHECKマ クロにより問題を生じることはない. ○ext_tsk,ext_kerの返り値 μITRON4.0仕様では,ext_tskはリターンすることのないサービスコールとなっ ているが,TOPPERS新世代カーネルにおいては,このサービスコールの返り値を ER型に変更し,非タスクコンテキストから呼ばれた場合には,E_CTXエラーを返 り値としてリターンすることとする. この仕様に対するいくつかの対案を検討したが,以下の理由で採用しない. ・JSPカーネルのように,危険の可能性を検出しながら実行を継続する方法は,  信頼性・安全性を重視するシステムでは望ましくない.信頼性・安全性を重  視するシステムでは,危険の可能性を検出したら,早期にリカバリをすべき  である. ・ASPカーネルの当初案のカーネルをダウンさせる方法は,アプリケーション側  で回復する余地をなくすという意味で望ましくない. ・過去の互換性を保つために,型をvoidとしたまま,非タスクコンテキストか  ら呼ばれた場合にはリターンするとする方法は,カーネルの仕様変更に気づ  きにくくなるという意味で望ましくない.また,他のエラーコード(例えば  E_NOSPT)を返す余地をなくしている. ・一種のCPU例外を呼び出すことにする方法は,カーネル仕様全体の整合性を考  えて採用しなかった.ここで一種のCPU例外を導入するよりも,サービスコー  ルがエラーを返した場合に呼ばれるOSEK/VDX OS仕様のエラーフックに相当す  る機能を導入した方が有用性が高いと思われるためである. この変更により,さらに,CPUロック状態やディスパッチ禁止状態でext_tskが 呼ばれた場合にもエラーリターンする方法が考えられるが,タスクのメインルー チンからのリターンとext_tskが等価にならないため,採用しない.また,他の 処理単位からのリターン方法と整合させる意味もある(例えば,割込みハンド ラからCPUロック状態のままリターンした場合の扱い). これにあわせて,ext_kerについても,返り値をER型に変更する.ASPカーネル では,ext_kerがエラーを返すことはないが,HRPカーネルでは,E_OACVエラー を返す場合がある. なお,μITRON4.0仕様では,exd_tskもリターンすることのないサービスコール となっているが,TOPPERS新世代カーネルでは,exd_tskはサポートしていない. ○カーネルのデータ構造に対するvolatile宣言について(クリティカルセクショ ンの出入処理の実現に関する制約) カーネル内のデータ構造は,並行実行される他の処理単位(割込みハンドラや タスク)からもアクセスされる可能性があるため,volatile宣言が必要ではな いかと考えられる.実際,クリティカルセクション内でカーネル変数を読むコー ドが,コンパイラの最適化によりクリティカルセクション外に移動され,それ が原因となった問題事例も報告されている. カーネル内のすべてのデータ構造にvolatile宣言をつける方法は,安全ではあ るが,最適化が抑止されるために,カーネルのサイズや性能には悪影響を与え る.そこでASPカーネルでは,次の方法でvolatile宣言の必要性をなくすことと する. ASPカーネルにおいては,並行実行される他の処理単位から書き換えられる可能 性のあるデータ構造は,すべて,CPUロック状態または全割込みロック状態によ るクリティカルセクション内でアクセスしている.クリティカルセクション内 でのデータ構造のアクセスが,コンパイラの最適化によりクリティカルセクショ ン外に移動されないようにするには,コンパイラに対して,クリティカルセク ションの出入処理により,メモリ上のデータ構造が書き変わる可能性があるこ とを知らせればよい. 具体的には,クリティカルセクションの出入処理を関数によって実現すれば, このような最適化を抑止することができる.しかし,ASPカーネルの多くのター ゲット依存部において,クリティカルセクションの出入処理はマクロやインラ イン関数により実装されており,上のような最適化を抑止できない. そこで,クリティカルセクションの出入処理を実現する場合には,メモリ上の データ構造が書き変わる可能性があることを,何らかの方法でコンパイラに知 らせなければならないという制約を設ける.GNU開発環境では,次のいずれかの 方法でこの制約を満たすことができる. (a) クリティカルセクションの出入処理の全体または出入処理の本質的な部分 (具体的には,割込み禁止/許可する処理)を(インラインでない)通常 の関数により実現する. (b) クリティカルセクションの出入処理の本質的な部分をインラインアセンブ ラによって実現している場合には,そのインラインアセンブラのclobber変 数リストに"memory"を追加する. (c) クリティカルセクションの出入処理の本質的な部分が,マクロやインライ ン関数呼出しで実現している場合には,クリティカルセクションに入る処 理の最後と出る処理の先頭に,Asm("":::"memory")という記述を入れる. なお,この制約が適用されるクリティカルセクションの出入処理は,以下のも のである. SIL_LOC_INT SIL_UNL_INT t_lock_cpu, i_lock_cpu, x_lock_cpu t_unlock_cpu, i_unlock_cpu, x_unlock_cpu ○型キャストに伴う警告メッセージ GCCで-O2オプションをつけてコンパイルした場合に,カーネルのソースコード 中の数箇所で,次の警告メッセージが出る(GCCのバージョンにもよる). warning: dereferencing type-punned pointer will break strict-aliasing rules これは,GCCに-O2オプションをつけると,コンパイラがC言語のstrict aliasing ruleを前提とするためである.コンパイラにstrict aliasing ruleを 適用させないためには,GCCのオプションに-fno-strict-aliasingを指定すれば よい.これにより,警告メッセージは抑止されるが,strict aliasing ruleを 前提とした最適化は行われなくなる. ASPカーネルに実装においては,strict aliasing ruleを前提とした最適化を行っ てもよく,この警告メッセージを無視しても差し支えない.以下では,この警 告メッセージを無視しても差し支えない理由を述べる. 警告メッセージが出る例として,semaphore.c中の次の行について検討する. wobj_make_wait((WOBJCB *) p_semcb, (WINFO_WOBJ *) &winfo_sem); この警告メッセージの原因は,直接的には,&winfo_semを(WINFO_WOBJ *)にキャ ストしていることであるが,本質的な原因は,このコードがC言語のstrict aliasing ruleに従わない代入文の原因になる可能性があり,このルールに依存 した最適化が誤った結果を引き起こす可能性があることである. C言語のstrict aliasing ruleは,互換性のない異なる型を通して,オーバラッ プするメモリ領域をアクセスする代入文を使用してはならないというものであ る(使用した場合の振舞いは未定義になる).このケースでは,(WINFO_SEM *)型のポインタ経由と(WINFO_WOBJ *)型のポインタ経由で,オーバラップする メモリ領域をアクセスする代入文を使用してはならないことになる.また,警 告メッセージの原因になってはいないが,(SEMCB *)型のポインタ経由と (WOBJCB *)型のポインタ経由で,オーバラップするメモリ領域をアクセスする 代入文を使用してはならない. ASPカーネルの実装においては,semaphore.c中の関数においては,(SEMCB *)型 および(WINFO_SEM *)型のポインタを使用しており,(WOBJCB *)型および (WINFO_WOBJ *)型のポインタ経由でメモリ領域をアクセスすることはない.一 方,そこから呼び出されるwait.c中の関数においては,(WOBJCB *)型および (WINFO_WOBJ *)型のポインタを使用しており,(SEMCB *)型および(WINFO_SEM *)型のポインタ経由でメモリ領域をアクセスすることはない. strict aliasing ruleに従わない代入文の問題点は,このルールに依存した最 適化が誤った結果を引き起こす可能性があることであるが,異なるコンパイル 単位をまたいで最適化が行われることはないため,これにより問題が起こるこ とはないと言うことができる. 同じ議論は,他のソースファイル(eventflag.c,dataqueue.c,pridataq.c, mailbox.c,mempfix.c)中の警告メッセージについても,そのまま当てはまる. ○性能評価用システム時刻参照機能 ●必要性と使途 ASPカーネルには,ASPカーネル上で動作するタスクやASPカーネル自身の性能を 計測するために,システム時刻より精度の高い性能評価用システム時刻を読み 出す機能をサポートする.性能評価用システム時刻は,マイクロ秒単位で表現 されるが,実際の精度はターゲット依存である. この機能を用いてあるプログラムの実行時間を計測するには,その実行直前と 実行直後に性能評価用システム時刻を読み出し,その差を求める.そのため, 性能評価用システム時刻は常に相対値を使用し,性能評価用システム時刻の絶 対値を使用することは想定していない. ●API仕様 性能評価用システム時刻参照機能では,次のデータ型を用いる. SYSUTM 性能評価用システム時刻(符号無し整数,単位はマイクロ秒, 32ビット以上) SYSUTM型は,ターゲット非依存部においてulong_t型(すなわち,unsigned long型)に定義されており,そのサイズは,32ビット以上で,ターゲット定義 である. 性能評価用システム時刻がSYSUTM型で表現できる範囲を超えた(つまり,オー バフローした)場合,性能評価用システム時刻は0に戻る.評価対象プログラム の実行前後の性能評価用システム時刻の差を求める場合には,計測する時間が SYSUTM型で表現できる範囲である限り,0に戻ることを特別に考慮する必要はな い. SYSUTM型が32ビットの場合,性能評価用システム時刻は約71分でオーバフロー する.そのため,この機能を71分を越える時間の測定に使った場合の動作は保 証されない. 性能評価用システム時刻参照機能のためのサービスコールの仕様については, 「TOPPERS新世代カーネル統合仕様書」の「4.6.1 システム時刻管理」の節を参 照すること. ●実装 時刻をマイクロ秒単位で取得するために,周期的なタイムティックを供給する タイマの現在値(タイマはカウントアップするものと仮定する)を読み出し, それをマイクロ秒単位に換算した値に,現在のシステム時刻(ミリ秒単位で表 現される)を1000倍した値を加えたものを性能評価用システム時刻とする.現 在のシステム時刻を1000倍する際に,オーバフローが発生する可能性があるが, 無視してかまわない. ただし,システム時刻の現在値とタイマの現在値を一貫した状態で読み出すの は容易ではない.両方の値を順に読み出すと,読出しの間にタイマがオーバフ ローして割込み要求が発生した場合に,片方はオーバフロー前の値,もう片方 はオーバフロー後の値を読んでしまい,誤った性能評価用システム時刻を取得 してしまう. この問題を解決する方法はいくつか考えられるが,どの方法を採用するの決定 にあたり,次の要求事項を設定した. (1) 多くのターゲットシステムで実現できること. (2) サービスコールの実行時間が可能な限り一定となること.言い換えると, 条件によってサービスコールの実行時間が大きく変動しないこと. (3) サービスコール中の可能な限り同じタイミングの時刻を返すこと.言い換 えると,条件によって時刻を読み取るタイミングが変動しないこと. (4) 調整する必要のあるパラメータを最小限とすること. これらの要求事項を満たす方法として,次の方法を用いることにした. まず,NMIを除くすべての割込みを禁止した状態で,システム時刻の現在値,タ イマの現在値(1回目),タイマ割込み要求の有無,タイマの現在値(2回目) を,この順で読み出す.割込みを禁止しているため,この間にシステム時刻の 現在値が変化することはなく,システム時刻の現在値を読み出す順番はどこで もよい.また,タイマの現在値の2回目の読出しは,タイマ割込み要求があった 場合にのみ必要となるが,(2)の要求から,タイマ割込み要求の有無によらず読 み出すこととする. これらの値を読み出した後,割込み禁止を解除し,次の処理を行う.まず,タ イマ割込み要求がなかった場合には,システム時刻の現在値と,1回目に読んだ タイマの現在値は一貫した値であることが保証できるため,これらの値から性 能評価用システム時刻の現在値を求める. 次にタイマ割込み要求があった場合には,1回目に読んだタイマの現在値が,タ イマ割込み要求発生前の値(オーバフロー前の値)である場合と,発生後の値 (オーバフロー後の値)である場合の両方の可能性が考えられる.このどちら の場合であったかを,2回目に読んだタイマの現在値を使って,次のように決定 する.2回目の値は,タイマ割込み要求発生後の値(オーバフロー後の値)であ ることが保証できるため,1回目の値が2回目の値よりも大きい場合には,その 間にオーバフローがあったものと推測できる.つまり,1回目の値はオーバフロー 前の値ということになり,システム時刻の現在値と一貫した値であるとして性 能評価用システム時刻の現在値を求める.逆に,1回目の値が2回目の値と同じ かそれより小さい場合には,1回目の値はオーバフロー後の値であると推測でき る.この場合には,次のタイムティックのシステム時刻を求め,その値と1回目 の値が一貫した値であるとして性能評価用システム時刻の現在値を求める. ここで,タイマ割込み要求があった場合には,2回目に読んだタイマの現在値を 用いる方法が考えられるが,(3)の要求を満たさなくなるために採用しなかった. また,JSPカーネルと同様の方法は,(4)の要求を満たさないために採用しなかっ た. 上で「推測できる」としたのは,この推測が成り立たなくなるケースがあるた めである.この推測が成り立たなくなるケースは,次の2つの場合に分けて分析 することができる. (a) 1回目の値がオーバフロー後の値であるにもかかわらず,1回目の値が2回目 の値よりも大きくなる場合 このようなケースは,タイマ割込みが要求されているにもかかわらずサービス されない状態が長時間続くか,タイマの現在値を1回目に読んでから2回目に読 むまでの間に長い時間がかかった結果,その間に(再度)オーバフローが発生 した場合に起こる.つまり,タイマ割込みがサービスされない時間が,タイム ティックの周期よりも長くなった場合である.このような場合には,システム 時刻の更新も正しく行われなくなる. (b) 1回目の値がオーバフロー前の値であるにもかかわらず,1回目の値が2回目 の値と同じかそれよりも小さくなる場合 このようなケースは,タイマの現在値を1回目に読んでから2回目に読むまでの 間に,タイマがほぼ1周分カウントアップした場合に起こる.この場合も,タイ マ割込みが禁止されている時間が,タイムティックの周期よりも長かったこと になり,システム時刻の更新が正しく行えなくなる. いずれのケースも,タイマ割込みが長時間禁止されている,タイマ割込みより も優先度の高い割込み処理が長時間続けて実行された,シミュレーション環境 においてシミュレータのプロセスが長時間スケジュールされなかったなどの理 由で,システム時刻の更新が正しく行えない状況に相当する.そこでこの状況 を,サービスコール使用上の注意事項に盛り込む. 実際のコードにおいては,システム時刻の現在値は変数に保持されていないた め(上位桁はcurrent_timeに保持されているが,下位桁を保持する変数がな い),次のタイムティックのシステム時刻を用いて計算している.そのため, タイマの現在値がオーバフロー後の値であると判断した場合を除いては,タイ ムティックの周期時間を,求めた性能評価用システム時刻から減算する.この 処理により,サービスコールの実行時間が変動することになるが,if文の内容 が(コンパイラの最適化を仮定すると)定数値の減算1回なので,変動はわずか である. このサービスコールは,任意の状態から呼び出すことができるため,SILの全割 込みロック機能を用いて,サービスコール内部のクリティカルセクションを実 現する. ○タスク例外処理禁止フラグをenatexで実装している理由 タスク例外処理禁止フラグは,TCB中のenatexフィールド(タスク例外処理許可 状態であることを示す)の形で保持している.このフィールドをdistexとせず enatexとしたのは,JSPカーネルにおいてタスクディスパッチ禁止フラグを enadspの形で保持したのと整合させたためである. ASPカーネルでは,enadspはdisdspに変更になったことから,enatexもdistexに 変更した方が良かったと思われる. 以上