TOPPERS/ASP3カーネル 設計メモ 対応バージョン: Release 3.B.0 最終更新: 2016年1月3日 このドキュメントは,TOPPERS/ASP3カーネルの設計メモである.作成途中のも のであり,網羅的ではない. ---------------------------------------------------------------------- TOPPERS/ASP Kernel Toyohashi Open Platform for Embedded Real-Time Systems/ Advanced Standard Profile Kernel Copyright (C) 2005-2016 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 480 2016-01-03 07:37:12Z ertl-hiro $ ---------------------------------------------------------------------- ○目次 ・TOPPERS/ASP3カーネルの実装設計方針 ・システム状態とコンテキストの実装 - カーネル動作状態と非動作状態 - タスクコンテキストと非タスクコンテキスト - 全割込みロック状態と全割込みロック解除状態 - CPUロック状態とCPUロック解除状態 - 割込み優先度マスク - ディスパッチ禁止状態とディスパッチ許可状態 - ディスパッチ保留状態 ・タスク状態の管理とスケジューリング - タスク状態の管理 - タスクスケジューラ ・タスクディスパッチ処理の実装 - タスクディスパッチ処理の必要なタイミング - タスクディスパッチャの構造 - タスクの終了時のタスクディスパッチ - ディスパッチャ本体とアイドル処理 ・xsns_dpnの実装 - カーネル管理外の状態に関する考察 - xsns_dpnの実装 ・エラーのチェック順序 - エラーの3分類 - 静的エラーのチェック順序 - 準静的エラーのチェック順序 ・CHECKマクロとgoto文の使用 - CHECKマクロの定義とその使用法 - 設計意図 - CHECKマクロを使用してよい条件 - 問題を生じることがない根拠 ・カーネルのデータ構造に対するvolatile宣言について ・型キャストに伴う警告メッセージ ・kernel_cfg.hへの依存関係の扱い ○TOPPERS/ASP3カーネルの実装設計方針 TOPPERS/ASP3カーネル(以下,ASP3カーネル)は,TOPPERS第3世代カーネル (ITRON系)の基盤となるリアルタイムカーネルである.TOPPERS第3世代カーネ ル仕様(ITRON系)の設計方針と,ASP3カーネルの適用対象領域と設計方針につ いては,TOPPERS第3世代カーネル(ITRON系)統合仕様書に述べられている. 以下では,ASP3カーネルの実装設計方針について述べるが,仕様設計方針とも 関連しており,明確に分離できない部分もある. TOPPERS/ASP3カーネルの実装設計を行うにあたり,次の方針を設定する. (1) ソースコードの読みやすさ・改造しやすさを重視する【ASPR0001】 ソースコードが読みやすいことは,オープンソースソフトウェアの品質を向上 させる上で最も重要な特性である.ソースコードを理解している技術者が増え ることで,問題を早期に発見することができ,サポート体制も充実させること ができる.また,ソースコードが読みやすいことは,シンプルな設計がされて いることも意味しており,信頼性向上にもつながる.さらに,技術者教育のた めの教材とする観点からも,ソースコードが読みやすいことは重要となる. 改造しやすいことは,システム毎の要求にあわせたチューニングが行いやすい ことを意味しており,すり合わせ型の開発を支援する性質である.また,ASP3 カーネルを基盤として一連のTOPPERS第3世代カーネル(ITRON系)を開発してい く上でも,改造しやすいことは必須の条件である. (2) 新しいターゲットシステムへのポーティングが容易な構造とする 組込みシステムには多様なハードウェアが用いられるため,それらに容易にポー ティングできることは重要な性質である.そのために,実行性能に配慮しつつ ハードウェアを抽象化し,ターゲットシステムに依存する部分(ターゲット依 存部)と依存しない部分(ターゲット非依存部)を明確に分離する【ASPR0002】. また,開発環境(コンパイラなど)に依存する部分も明確に分離する【ASPR0003】. (3) 検証が容易な構造とする 信頼性を確保するために,検証が容易な構造とする. 具体的には,サービスコールのほとんど全体を割込み禁止で実行することとし, サービスコールの処理途中で割込みを許可しない【ASPR0004】.この構造は, 読みやすく改造しやすいソースコードにするためにも有効である.これにより 割込み応答性が犠牲になるが,それによって問題を生じるアプリケーションは 少数であり,やむをえないものと考える. また,条件コンパイル等によりコンフィギュレーションできる箇所を増やすと, 細かな最適化ができる一方で,検証すべき組合せが増えることから,コンフィ ギュレーションできる箇所は必要最低限とする【ASPR0005】. (4) 実行性能とメモリ使用量に配慮する 上記の方針を満たした上で,高い実行性能と小さいメモリ使用量を達成できる ような実装を行う【ASPR0006】.実行性能を向上させる際には,平均性能の向 上よりも,最悪時性能の向上を重視する. ソースコードの読みやすさを重視すると言っても,実行性能の悪いアルゴリズ ムを安易に採用することはせず,高い実行性能を達成できるアルゴリズムを用 いる【ASPR0007】.ただし,新しいターゲットシステムへのポーティングを容 易にするために大部分をC言語で実装しており,すべてをアセンブリ言語で記述 した場合に比べて実行性能が落ちるのはやむをえない. メモリ使用量については,RAMの使用量を削減することに重点を置いた設計を行 うが,上記の方針および実行性能とのトレードオフを考慮し,ぎりぎりまでの 削減は行わない【ASPR0008】. (5) スケーラビリティに配慮する 様々な規模のシステムに適用できるスケーラビリティをもった構造とする.特 に,小規模なシステムに適用する際に,使用しない機能をカーネルが持ってい ることによるメモリ使用量の増加が最小限になるように配慮する. 具体的には,アプリケーションとカーネルを1つのロードモジュールにリンクす る方法(1リンクモデル)を想定し,カーネルを関数単位でライブラリ化して, 使用する関数のみをリンクできる構造とする【ASPR0009】.これは一種のコン フィギュレーションであるが,この方法は,条件コンパイルによるコンフィギュ レーションとは違い,検証工数に与える影響が小さい. また,固定的に使用するRAM領域を減らし,スタックに置ける情報はできる限り スタック上に置く【ASPR0010】. (6) タイムティックを用いない時間管理(ティックレスカーネル) 消費エネルギー削減のため,不必要なタイミングでプロセッサを動作させない ことが求められる.具体的には,処理すべきタイムイベントがない時に,タイ マ割込みがかからないような実装とする【ASPR0011】. ○システム状態とコンテキストの実装 この章では,「TOPPERS第3世代カーネル(ITRON系)統合仕様書」の「2.5 シス テム状態とコンテキスト」の節に規定されているシステム状態とコンテキスト の実装方法について記述する. ●カーネル動作状態と非動作状態[NGKI0143] カーネルの動作状態を管理するために,カーネル動作状態フラグ(kerflg)を 用意する.kerflgは,スタートアップモジュールでfalse(=0)に初期化する. また,カーネル動作の開始時にtrueにし,カーネル動作の終了時にfalseにする. ---------------------------------------- bool_t kerflg = false; ---------------------------------------- kerflgは,sns_kerで参照する.カーネル非動作状態でsns_ker以外のサービス コールを呼び出した場合の動作は保証する必要がないため,他のサービスコー ルではkerflgを参照しない[NGKI0146]. ●タスクコンテキストと非タスクコンテキスト[NGKI0148] タスクコンテキストと非タスクコンテキストの切換えは,ターゲットハードウェ アおよびターゲット依存部に委ねる.また,どちらのコンテキストで実行中で あるかを判別する関数(sense_context)も,ターゲット依存部で用意すること とする. ●全割込みロック状態と全割込みロック解除状態 全割込みロックフラグの管理はターゲット依存部に委ね,全割込みロック状態 に遷移させるマクロ(SIL_LOC_INT)と,元の状態に戻すマクロ(SIL_UNL_INT) は,SILのターゲット依存部で用意することとする.全割込みロック状態である か否かを判別する機能は,必要がないために用意していない. 全割込みロック状態でsns_kerとext_ker以外のサービスコールを呼び出した場 合の動作は保証する必要がないため,サービスコール中で全割込みロック状態 であることを判別する必要はない. ●CPUロック状態とCPUロック解除状態 CPUロックフラグの管理はターゲット依存部に委ね,CPUロック状態に遷移させ る関数(lock_cpu)と,CPUロック解除状態に遷移させる関数(unlock_cpu)は, ターゲット依存部で用意することとする.また,CPUロック状態であるか否かを 判別する関数(sense_lock)も,ターゲット依存部で用意することとする. ●割込み優先度マスク 割込み優先度マスクの管理はターゲット依存部に委ね,タスクコンテキストか ら割込み優先度マスクを設定する関数(t_set_ipm)と,それを参照する関数 (t_get_ipm)は,ターゲット依存部で用意することとする. ●ディスパッチ禁止状態とディスパッチ許可状態[NGKI0177] ディスパッチ禁止フラグを管理するために,それを反転したフラグ(ensdsp) を用意する.enadspは,カーネルの初期化時にtrueに初期化する. ---------------------------------------- bool_t enadsp; ---------------------------------------- ●ディスパッチ保留状態 ディスパッチ保留状態は,非タスクコンテキストの実行中,CPUロック状態,割 込み優先度マスクが全解除でない状態,ディスパッチ禁止状態のいずれか(ま たは,それらが重なった状態)である. ディスパッチ保留状態でないこと(つまり,ディスパッチできる状態であるこ と)を効率的に判別するために,割込み優先度マスク全解除状態であり,ディ スパッチ許可状態である(ディスパッチ禁止状態でない)ことを示すフラグ (dspflg)を用意する.すなわち,常に以下の関係が成り立つようにする. dspflg == ((t_get_ipm() == TIPM_ENAALL) && enadsp) dspflgは,カーネルの初期化時にtrueに初期化する.また,タスクコンテキス トにおいて割込み優先度マスクの値が変更されるか,割込み禁止フラグが変更 される度に更新する. ---------------------------------------- bool_t dspflg; ---------------------------------------- ○タスク状態の管理とスケジューリング ●タスク状態の管理 タスク管理ブロック(TCB)中のタスク状態を管理するフィールド(tstat)で は,タスク状態が次のいずれであるかを管理する.  ・実行できる状態  ・休止状態  ・(狭義の)待ち状態  ・強制待ち状態  ・二重待ち状態 また,タスクが(狭義の)待ち状態と二重待ち状態の時には,同じフィールド で待ち要因も管理する. タスクが実行できる状態の時に,実行状態であるか実行可能状態であるかは, このフィールドでは管理せず,実行状態のタスクのTCBを指すポインタ変数 (p_runtsk)によって判別する.実行状態のタスクがない場合は,p_runtskは NULLにする. ---------------------------------------- TCB *p_runtsk; ---------------------------------------- p_runtskは,カーネルの初期化時にNULLに初期化し,ディスパッチャにおいて 更新する.サービスコールの処理の中で自タスクに関する情報を参照する場合 は,p_runtskを用いる. ●タスクスケジューラ タスクスケジューラは,実行できる状態のタスクの中から,実行すべきタスク を決定し,そのタスクのTCBを指すポインタ変数(p_schedtsk)を設定する.実 行できる状態のタスクがない場合は,p_schedtskはNULLにする. ---------------------------------------- TCB *p_schedtsk; ---------------------------------------- p_runtskは,通常はp_schedtskと一致しているが,非タスクコンテキスト実行 中は,一致しているとは限らない.割込み優先度マスク全解除でない状態の間 とディスパッチ禁止状態の間(すなわち,dspflgがtrueである間)は, 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)を呼び出す.この処理では,実行状態のタスクを参 照せず,コンテキストの保存も行わずに,次に実行するタスクの選択処理を行 うため,タスクディスパッチ処理がスキップされることはない. ena_terおよびchg_ipmによるタスクの終了時も,これと同様である. なお,他タスクからタスクを終了させるサービスコール(ras_ter,ter_tsk) では,実行状態のタスクを終了させることはできないため,このような状況は 起こらない. ●ディスパッチャ本体とアイドル処理 タスクディスパッチャでは,コンテキストの保存処理を行った後,ディスパッ チャ本体へ分岐する. ディスパッチャ本体では,まず,p_schedtskをp_runtskに代入する.p_runtsk がNULLでない場合は,p_runtsk(ディスパッチ先のタスク)のTCBからスタック ポインタを復帰し,TCBに保存されている実行再開番地に分岐する.分岐先にお いて,コンテキストの復帰処理を行う. p_runtskがNULLになるのは,実行できる状態のタスクがなくなった場合である. 実行できる状態のタスクがなくなるのは,実行状態のタスクが実行できる状態 でなくなり,他に実行できる状態のタスクがない場合に限られる.この場合, スケジューラがp_schedtskをNULLに設定し,タスクディスパッチャが実行され る. 実行できる状態のタスクがなくなった場合の処理を,アイドル処理と呼ぶ.ア イドル処理では,CPUロック状態を解除して,割込みの発生を待つ.割込みが発 生し,いずれかのタスクが実行できる状態になった場合には,割込みハンドラ の出口処理でタスクディスパッチが行われ,ディスパッチャ本体には戻らない. 割込みによっていずれのタスクも実行できる状態にならなかった場合には,ディ スパッチャ本体に戻り,再度割込みの発生を待つ. アイドル処理をこのように実装すると,割込みハンドラの出口処理(ret_int) からタスクディスパッチャが呼び出された場合に,p_runtskがNULLになってい る可能性がある.そのため,割込みハンドラの出口で呼び出された場合のコン テキストの保存処理の際に,p_runtskがNULLであるかチェックし,NULLの場合 にはコンテキストの保存を行わないようにする必要がある. アイドル処理は,最後に実行状態であったタスクのスタック領域を用いて実行 される.ここで,以下の2つの課題が生じる. 1つめの課題は,カーネルの動作開始時には,「最後に実行状態であったタスク」 が存在しないことである.カーネルの初期化処理が,それ専用のスタック領域 を用いて実行されていた場合には,アイドル処理をそのまま実行しても差し支 えないが,非タスクコンテキスト用のスタック領域を用いて実行されていた場 合には,他のスタック領域に切り換える必要がある(アイドル処理を非タスク コンテキスト用のスタック領域で実行すると,アイドル処理中に受け付けた割 込み処理で,スタックが破壊される可能性がある).そこで,そのような場合 には,カーネルの動作開始時の処理(start_dispatch)において,スタックを, IDが1のタスクのスタック領域に切り換えることにする.カーネルの動作開始直 後にアイドル処理が実行される時には,すべてのタスクが休止状態になってい るため,どのタスクのスタック領域も使われておらず,アイドル処理で使用し て差し支えない.ASP3カーネルでは,少なくとも1つのタスクを生成する必要が あることから,IDが1のタスクは必ず存在する. もう1つの課題は,「最後に実行状態であったタスクのスタック領域」が解放さ れる可能性である.タスクのスタック領域が解放されるのは,タスクが削除さ れた場合であるため,ASP3カーネルで問題になるのは,動的生成機能拡張パッ ケージを用いた場合のみである.ASP3カーネルでは,割込みハンドラからタス クを削除するサービスコール(del_tsk)を呼び出すことができず,タスクの終 了と同時にタスクを削除するサービスコール(exd_tsk)もサポートしていない ことから,タスクを削除するのは他のタスクからであり,削除されたタスクが 「最後に実行状態であったタスク」になっていることはない.以上より,「最 後に実行状態であったタスクのスタック領域」が解放されている可能性はない. 上のアイドル処理中で割込みの発生を待っている間は,タスクコンテキストで CPUロック解除状態であるにもかかわらず,p_runtskがNULLになっている.この 状況は,通常はアプリケーションから観測できないが,ここで(ハードウェア 故障などの理由で)CPU例外が発生すると,アプリケーションから観測されてし まう.具体的には,xsns_dpnがfalseを返すにもかかわらず,get_tidが TSK_NONEが返ってしまう.この状況を防ぐために,p_runtskがNULLの場合には, xsns_dpnがtrueを返すようにする. このような措置を行うと,CPU例外ハンドラが規則通りに実装されていれば, CPU例外ハンドラの出口処理ではp_runtskがNULLになる状況はないはずであるが, 安全のために,p_runtskがNULLの場合にはコンテキストの保存を行わないよう にする. ○xsns_dpnの実装設計 ●カーネル管理外の状態に関する考察 カーネル管理外の状態とは,以下の3状態の総称である.  (1) 全割込みロック状態  (2) カーネル管理外の割込みハンドラ実行中  (3) カーネル管理外のCPU例外ハンドラ実行中 カーネル管理外のCPU例外ハンドラとは,以下のいずれかで発生したCPU例外に よって起動されたCPU例外ハンドラのことを言う.  (3-1) カーネル非動作状態  (3-2) カーネル内のクリティカルセクションの実行中  (3-3) 全割込みロック状態  (3-4) CPUロック状態  (3-5) カーネル管理外の割込みハンドラ実行中  (3-6) カーネル管理外のCPU例外ハンドラ実行中 統合仕様書には,カーネル管理外の状態では,少なくとも,カーネル管理の割 込みはマスクされている[NGKI0545]としているが,これは次の理由による. まず,(1)については,全割込みロック状態ではNMIを除くすべての割込みがマ スクされるため[NGKI0160],カーネル管理の割込みがマスクされているのは 明らかである. (2)については,カーネル管理の割込みの優先度がTMIN_INTPRIと同じかそれよ りも低いのに対して,カーネル管理外の割込みの優先度はTMIN_INTPRIよりも高 いため,カーネル管理外の割込みハンドラ実行中は,カーネル管理の割込みは マスクされていることになる. (3)については,CPU例外ハンドラの起動によって割込みのマスク状態は変化し ないため,CPU例外が発生した時の状態について考察すればよい. カーネル非動作状態(3-1)では,原則として,NMIを除くすべての割込みがマス クされる[NGKI0144]ため,カーネル管理の割込みはマスクされている. カーネル内のクリティカルセクション(3-2)は,カーネル管理の割込みのマスク により実現していることから,明らかである. CPUロック状態(3-4)では,すべてのカーネル管理の割込みがマスクされる [NGKI0167]ことから,条件を満たしている. 残る(3-3)(3-5)(3-6)については,カーネル管理外の状態で発生したCPU例外に より起動されたCPU例外ハンドラは,カーネル管理外の状態であることを意味し ており,帰納的に説明することができる. 以上により,カーネル管理外の状態では,少なくとも,カーネル管理の割込み はマスクされている[NGKI0545]ことがわかる.ただし,(3-1)については, 「原則として」というフレーズがあるため,この原則が守られない場合は例外 となる. ●xsns_dpnの実装 xsns_dpnは,実行中のCPU例外ハンドラの起動原因となったCPU例外が,以下の 3条件を満たす場合にfalse,そうでない場合にtrueを返す[NGKI3151].  (a) カーネル管理外のCPU例外でない  (b) タスクコンテキストで発生  (c) CPU例外が発生した時の状態がディスパッチ保留状態でない さらに,「アイドル処理」の節で述べた通り,p_runtskがNULLの場合には, xsns_dpnがtrueを返すようにする必要がある.つまり,上の3条件に次の条件を 加えた4条件が満たす場合にfalseを返す.  (d) p_runtskがNULLでない カーネル管理外のCPU例外とは,以下のいずれかで発生したCPU例外のことを言 う.  (a-1) カーネル非動作状態  (a-2) カーネル内のクリティカルセクションの実行中(カーネル実行中)  (a-3) 全割込みロック状態  (a-4) CPUロック状態  (a-5) カーネル管理外の割込みハンドラ実行中  (a-6) カーネル管理外のCPU例外ハンドラ実行中 また,ディスパッチ保留状態とは,以下のいずれかの場合を言う.  (c-1) 非タスクコンテキストの実行中  (c-2) CPUロック状態  (c-3) 割込み優先度マスクが全解除でない状態  (c-4) ディスパッチ禁止状態 xsns_dpnでは,これらの条件をすべてチェックする必要がある. これらの条件の中で,まず,(b)と(c-1),(a-4)と(c-2)は重複している.また, (a-1)はkerflg,(c-4)はenadsp,(d)はp_runtskを参照することで,ターゲット 非依存部で判定することができる.残りの条件は,ターゲット依存部で用意す る関数exc_sense_intmaskにより判定する. すなわち,exc_sense_intmaskは,CPU例外が発生した時のシステム状態が,以 下の条件をすべて満たす場合にtrue,そうでない場合にfalseを返すものとする.  (a-2) カーネル内のクリティカルセクションの実行中でない  (a-3) 全割込みロック状態でない  (a-4) CPUロック状態でない  (a-5) カーネル管理外の割込みハンドラ実行中でない  (a-6) カーネル管理外のCPU例外ハンドラ実行中でない  (b) タスクコンテキスト  (c-3) 割込み優先度マスクが全解除 上で考察した通り,(a-2)〜(a-6)では,(少なくとも)カーネル管理の割込み がマスクされている.そこで,多くの場合,カーネル管理の割込みがすべてマ スクされていることをチェックすることで,(a-2)〜(a-6)の判定を一括して行 うことができる.また,割込み優先度マスクを実現しているハードウェアレジ スタをチェックすることで,(a-2)〜(a-6)と(c-3)の判定を一括して行える場合 も多い. なお,カーネル管理の割込みがすべてマスクされていることをチェックするこ とで,(a-1)の条件も判定できるため,ターゲット非依存部でkerflgをチェック する必要はないことになるが,カーネル非動作状態でNMIを除くすべての割込み がマスクされるのは「原則として」であるため,kerflgもチェックした方が安 全である. また,カーネル管理の割込みがすべてマスクされていることをチェックすると, dis_intにより,すべてのカーネル管理の割込みを個別にマスクした場合にも xsns_dpnがtrueを返してしまう可能性がある.このようにならないことが望ま しいが,カーネル管理の割込みすべてをdis_intで個別にマスクする状況は稀で あると考えられるため,このような状況でxsns_dpnがtrueを返すこと(つまり, リカバリが行えないこと)を,ターゲット定義の制限としても差し支えない. ○エラーのチェック順序 サービスコール内におけるエラーチェックは,以下の順序で行うことを原則と する.なお,この節には,保護機能対応カーネルに関する記述が含まれている が,それらの記述は将来的にはより適切なドキュメントに移動する予定である. ●エラーの3分類 サービスコールのエラーは,大きく以下の3つに分類することができる. (a) 静的エラー 対象のカーネルオブジェクトが登録されているか否かや,その状態に依存せず に,チェックすることができるエラー. (b) 準静的エラー 対象のカーネルオブジェクトが登録されていれば,その状態に依存せずにチェッ クすることができるエラー. (c) 動的エラー 対象のカーネルオブジェクトの状態に依存するエラー. ASPカーネルにおいては,(a)と(b)のエラーはクリティカルセクションの外側で, この順序でチェックし,(c)のエラーはクリティカルセクションの内側でチェッ クする.ただし,動的生成機能拡張パッケージでは,(b)のエラー(の一部)は クリティカルセクションの内側で実施する必要がある. ●静的エラーのチェック順序 静的エラーには,実行コンテキストのエラー(ディスパッチ保留状態からの呼 出しエラーも含む),パラメータの範囲等のエラー(対象のカーネルオブジェ クトに依存せずにチェックできるもの)が含まれる. サービスコール中では,最初に実行コンテキストのエラーをチェックし,その 後,パラメータの並び順に,範囲等のエラーをチェックする. 保護機能対応カーネルでは,パラメータがポインタである場合に,ポインタの 指すメモリ領域がアクセス可能であるかをチェックする必要があるが,メモリ 領域の設定が静的である場合には,このエラーチェックもここで実施する.た だし,メモリ領域の設定が静的でない場合には,このエラーチェックはクリティ カルセクションの内側で行う必要がある. ●准静的エラーのチェック順序 准静的エラーのチェックの前に,対象のカーネルオブジェクトの管理ブロック の先頭番地を,ローカル変数に代入する. 保護機能対応カーネルでは,この次に,対象のカーネルオブジェクトがアクセ ス可能であるかをチェックする処理を行う.当該サービスコールの呼出しが, システム状態に対するアクセス許可ベクタで保護されている場合にも,この段 階でチェックを実施する. その後で,パラメータの範囲等のエラーの中で,対象のカーネルオブジェクト の登録情報(初期化ブロックに含まれている情報)に依存してチェックすべき もののチェックを,パラメータの並び順で実施する. ○CHECKマクロとgoto文の使用 ASP3カーネルの実装においては,サービスコールの静的なエラーをチェックする ために,名称が"CHECK_"で始まる一連のマクロ(これらを,CHECKマクロと総称 する)を用いている. CHECKマクロの定義中にはgoto文を含んでいるが,MISRA-Cなどのコーディング ルールではgoto文の使用を禁止しており,goto文を使うべきではないという意 見も多い.また,マクロの定義中にgoto文を使用することが問題であるという 意見もある. ここでは,定義中にgoto文を含むCHECKマクロを用いる設計意図とそれを使用し てよい条件,CHECKマクロの使用によりソフトウェアの信頼性に問題が生じるこ とがないことを論証する. なお,ASP3カーネルのカーネル本体の実装では,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マ クロにより問題を生じることはない. ○カーネルのデータ構造に対するvolatile宣言について(クリティカルセクショ ンの出入処理の実現に関する制約) カーネル内のデータ構造は,並行実行される他の処理単位(割込みハンドラや タスク)からもアクセスされる可能性があるため,volatile宣言が必要ではな いかと考えられる.実際,クリティカルセクション内でカーネル変数を読むコー ドが,コンパイラの最適化によりクリティカルセクション外に移動され,それ が原因となった問題事例も報告されている. カーネル内のすべてのデータ構造にvolatile宣言をつける方法は,安全ではあ るが,最適化が抑止されるために,カーネルのサイズや性能には悪影響を与え る.そこでASP3カーネルでは,次の方法でvolatile宣言の必要性をなくすことと する. ASP3カーネルにおいては,並行実行される他の処理単位から書き換えられる可能 性のあるデータ構造は,すべて,CPUロック状態または全割込みロック状態によ るクリティカルセクション内でアクセスしている.クリティカルセクション内 でのデータ構造のアクセスが,コンパイラの最適化によりクリティカルセクショ ン外に移動されないようにするには,コンパイラに対して,クリティカルセク ションの出入処理により,メモリ上のデータ構造が書き変わる可能性があるこ とを知らせればよい. 具体的には,クリティカルセクションの出入処理を関数によって実現すれば, このような最適化を抑止することができる.しかし,ASP3カーネルの多くのター ゲット依存部において,クリティカルセクションの出入処理はマクロやインラ イン関数により実装されており,上のような最適化を抑止できない. そこで,クリティカルセクションの出入処理を実現する場合には,メモリ上の データ構造が書き変わる可能性があることを,何らかの方法でコンパイラに知 らせなければならないという制約を設ける.GNU開発環境では,次のいずれかの 方法でこの制約を満たすことができる. (a) クリティカルセクションの出入処理の全体または出入処理の本質的な部分 (具体的には,割込み禁止/許可する処理)を(インラインでない)通常 の関数により実現する. (b) クリティカルセクションの出入処理の本質的な部分をインラインアセンブ ラによって実現している場合には,そのインラインアセンブラのclobber変 数リストに"memory"を追加する. (c) クリティカルセクションの出入処理の本質的な部分が,マクロやインライ ン関数呼出しで実現している場合には,クリティカルセクションに入る処 理の最後と出る処理の先頭に,Asm("":::"memory")という記述を入れる. なお,この制約が適用されるクリティカルセクションの出入処理は,以下のも のである. SIL_LOC_INT SIL_UNL_INT lock_cpu 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を 前提とした最適化は行われなくなる. ASP3カーネルに実装においては,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 *)型のポインタ経由で,オーバラップするメモリ領域をアクセスする 代入文を使用してはならない. ASP3カーネルの実装においては,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)中の警告メッセージについても,そのまま当てはまる. ○kernel_cfg.hへの依存関係の扱い システムコンフィギュレーションファイルからインクルードされたヘッダファ イルが変更された場合,単純に依存関係をたどるとコンフィギュレータの再実 行が必要になり,kernel_cfg.hが更新される.その結果,kernel_cfg.hに依存 するソースファイルの再コンパイルが行われる.しかし,ヘッダファイルが変 更されても,kernel_cfg.hの内容は更新されない場合も多い.このような場合, 再コンパイルは不要なはずである. この問題を解決する一般的な手法として,move-if-changeスクリプトを使い, kernel_cfg.hの内容が変わった場合のみ,そのタイムスタンプを更新する方法 がある.この場合,kernel_cfg.hを生成するターゲットは,タイムスタンプファ イルにし,生成後にタイムスタンプファイルをtouchする必要がある(そのよう にしないと,kernel_cfg.hは,変更されたヘッダファイルに依存するため,次 回以降のmakeでも必ずkernel_cfg.hの生成手順が実行されてしまう). ただし,タイムスタンプファイルをtouchする方法には,過去に,Windowsと Cygwinのシステム時刻差異に伴う問題が指摘されている(下のURL参照). http://dev.toppers.jp/trac/asp/ticket/286 この問題を回避するために,コンフィギュレータによってタイムスタンプファ イルを生成する方法をとる. これと同じ問題は,offset.hにも存在するため,同様に対処する. なお,kernel_cfg.cに同じ方法を適用してもよいが,再実行を防げる可能性が ある処理がkernel_cfg.cのコンパイルだけで,それも再実行が必要な場合が多 いため,メリットが小さいと考えて適用しないことにする. なお,ASPカーネルにおける対応は,move-if-changeを用いていなかったために, あまり意味がなかった.これは,コンフィギュレータが内容が更新されていな いファイルのタイプスタンプを書き換えないことを想定していたためである (その想定下でも,"touch -r"を用いた対応は不適切だった).コンフィギュ レータをそのように改造することも難しくないが,コンフィギュレータによっ てタイムスタンプを生成する方法は使えなくなる. 以上