													TOPPERS Confidential
		TOPPERSプロジェクト 設計メモ
		TOPPERS/HRP2カーネル設計メモ（作成中）

		作成者: 高田広章（名古屋大学）
		最終更新: 2015年4月22日

○メモの位置付け

このメモは，TOPPERS/HRPカーネルRelease 2（以下，HRP2カーネルと呼ぶ）の
設計記録である．

○目次

・HRPカーネルで問題と思われる箇所
・HRP2カーネルの仕様
	- CRE_TSKのパラメータの追加
	- アクセス許可ベクタを設定する静的API
	- タスク例外処理マスク状態と待ち禁止状態（統合仕様書より）
	- メモリオブジェクト管理機能
	- サービスコールの呼出し方法
・データ型，定数，マクロの定義
	- アクセス許可パターンとアクセス許可ベクタ
	- 保護ドメインID
・ユーザタスクの実現
	- ユーザタスクとシステムタスクの区別
	- タスク初期化ブロックの修正
	- システムスタック領域の生成とエラーチェック
	- ユーザスタック領域の生成とエラーチェック
	- ユーザスタックポインタの保存と復帰
	- ユーザタスクの起動
	- ユーザスタック領域のアクセス保護
・保護ドメインの管理
	- 保護ドメイン初期化ブロック
	- オブジェクトのアクセス許可ベクタの管理
	- オブジェクトの属する保護ドメインの管理
	- 実行中の保護ドメインの管理
	- svclevelの導入
	- rundomの設定方法
	- オブジェクトに対するアクセス権チェックの追加
	- メモリ領域のアクセス保護
	- 実行状態のタスクの属する保護ドメインIDの参照
	- 拡張サービスコールを呼び出した保護ドメインの参照
・ユーザタスクが特権モードを実行しているか否かの判別
	- ターゲット非依存に判別する方法の難しさ
	- 特権モードを実行しているか否かを判別する関数
・サービスコールの呼出し処理
	- サービスコール処理関数のリネーム
	- 関数呼出しによるサービスコール呼出し
	- ソフトウェア割込みによるサービスコール呼出し
	- ソフトウェア割込みの出入口処理
	- 拡張サービスコールの管理と出入口処理
・待ち禁止状態の実装
	- 待ち禁止状態を示すフラグ
	- 待ち禁止状態への遷移
	- 待ち禁止状態の解除
	- 待ち禁止の実現
・タスク例外処理機能の実装
	- タスク例外処理ルーチンの実行開始条件とシステム状態（仕様の確認）
	- システムタスクに対するタスク例外処理ルーチンの呼出し処理
	- ユーザタスクに対するタスク例外処理ルーチンの呼出し処理
	- タスク例外処理ルーチンの実行開始が必要なタイミング
	- タスク例外処理ルーチンの実行開始処理
	- ユーザタスクのタスク例外処理ルーチンからのリターン
・カーネルがユーザスタックを操作する場合のチェック
	- タスク例外処理ルーチン起動時のユーザスタックのチェック
	- タスク例外処理ルーチンからのリターン時のユーザスタックのチェック
	- タスク例外起動時／リターン時スタック不正例外ハンドラの定義と呼出し
	- VALID_USTACKの実装
・CPU例外発生時の状態参照機能の修正
・保護ドメインのメモリ管理
	- メモリオブジェクトの管理
	- メモリオブジェクト初期化ブロック
	- メモリオブジェクト初期化ブロックの探索
	- メモリアクセス権のチェック
	- メモリアクセス権のチェックマクロ
	- メモリアクセス権チェックの追加
	- prb_memの実装
	- メモリアクセス違反ハンドラの実装
・メモリオブジェクトの登録と情報の生成
	- 登録すべきメモリオブジェクト
	- セクションのリンク方法
	- タスクのユーザスタック領域のリンク方法
	- 固定長メモリプール領域のリンク方法
	- メモリオブジェクト，セクション，メモリ保護単位
	- メモリオブジェクトの配置の原則
	- メモリオブジェクトの配置順序の決定（★未完成）
	- レッドゾーン方式におけるユーザスタック領域の配置順序の決定（★未完成）
・カーネル構築手順
・システムサービスの拡張サービスコール化
	- 命名規則
	- ヘッダファイルの内容
	- 機能番号の割付け
・無効になった検討

○HRPカーネルで問題と思われる箇所

ソフトウェア割込みの実行開始とsvclevelフィールドの更新がアトミックに行
えないターゲットプロセッサにおいて，拡張サービスコールのためのソフトウェ
ア割込みの実行開始後，svclevelがインクリメントされる前に割込みが受け付
けられ，その割込みハンドラ（または，そこで切り換わった後のタスク）から
rel_wai/irel_waiが発行されると，待ち禁止状態にならない。

○HRP2カーネルの仕様

HRP2カーネルは，TOPPERS/ASPカーネルに対して，メモリ保護機能とオブジェク
トアクセス保護機能，拡張サービスコール機能，ミューテックス機能，オーバ
ランハンドラ機能を追加し，メールボックス機能を削除したものである．ただ
し，優先度継承ミューテックスを実装するかについては，別途検討する．

HRP2カーネルの仕様は，TOPPERS新世代カーネル統合仕様に記述されている．詳
細な仕様については，TOPPERS新世代カーネル統合仕様書を参照すること．

●CRE_TSKのパラメータの追加

タスクを生成する静的APIに渡すパラメータに，システムスタック領域のサイズ
と先頭番地を追加する．

	CRE_TSK(ID tskid, { ATR tskatr, intptr_t exinf, TASK task,
			PRI itskpri, SIZE stksz, STK_T *stk, SIZE sstksz, STK_T *sstk })

ASPカーネルとの互換性のために，sstkszおよびsstkの記述は省略できるものと
する．

【HRPカーネルからの変更】

JSPカーネルでは，stkにNULLのみを指定できることとしていたため，HRPカーネ
ルでは，ユーザタスクのstk以外はNULLのみを指定できることとした．ASPカー
ネルにはそのような制限がないため，HRP2カーネルでも，スタック領域をユー
ザ側で指定できる仕様とした．

●アクセス許可ベクタを設定する静的API

各カーネルオブジェクトおよびシステム状態のアクセス許可ベクタを設定する
ための静的APIとして，以下を追加する．

	SAC_TSK(ID tskid, { ACPTN acptn1, ACPTN acptn2,
								ACPTN acptn3, ACPTN acptn4 })
	SAC_SEM(ID semid, { ACPTN acptn1, ACPTN acptn2,
								ACPTN acptn3, ACPTN acptn4 })
	SAC_FLG(ID flgid, { ACPTN acptn1, ACPTN acptn2,
								ACPTN acptn3, ACPTN acptn4 })
	SAC_DTQ(ID dtqid, { ACPTN acptn1, ACPTN acptn2,
								ACPTN acptn3, ACPTN acptn4 })
	SAC_PDQ(ID pdqid, { ACPTN acptn1, ACPTN acptn2,
								ACPTN acptn3, ACPTN acptn4 })
	SAC_MTX(ID mtxid, { ACPTN acptn1, ACPTN acptn2,
								ACPTN acptn3, ACPTN acptn4 })
	SAC_MPF(ID mpfid, { ACPTN acptn1, ACPTN acptn2,
								ACPTN acptn3, ACPTN acptn4 })
	SAC_CYC(ID cycid, { ACPTN acptn1, ACPTN acptn2,
								ACPTN acptn3, ACPTN acptn4 })
	SAC_ALM(ID almid, { ACPTN acptn1, ACPTN acptn2,
								ACPTN acptn3, ACPTN acptn4 })
	SAC_SYS({ ACPTN acptn1, ACPTN acptn2, ACPTN acptn3, ACPTN acptn4 })

HRP2カーネルでは，割込みサービスルーチンを操作するサービスコールをサポー
トしないため，SAC_ISRはサポートしない．

【HRPカーネルからの変更】

ID番号で識別するオブジェクトのアクセス許可ベクタをデフォルト以外に設定
する場合には，オブジェクトを生成した後に設定することとし，アクセス許可
ベクタを設定する静的API（SAC_YYY）を新設した．逆に，アクセス許可ベクタ
を指定してオブジェクトを生成する静的API（CRA_YYY）は廃止した．

ASPカーネルにおいて，システム時刻を設定するサービスコール（set_tim）を
廃止したことから，システム時刻のアクセス許可ベクタは廃止し，システム状
態のアクセス許可ベクタで代替することとした．そのため，システム時刻のア
クセス許可ベクタを設定する静的API（SAC_TIM）は廃止した．

●タスク例外処理マスク状態と待ち禁止状態（統合仕様書 2.6.5節より）

保護機能対応カーネルにおいて，ユーザタスクについては特権モードで実行し
ている間（特権モードを実行している間に，実行可能状態や広義の待ち状態に
なっている場合を含む．また，サービスコールを呼び出して，実行可能状態や
広義の待ち状態になっている場合も含む．タスクの実行開始前は含まない），
システムタスクについては拡張サービスコールを実行している間（拡張サービ
スコールを実行している間に，実行可能状態や広義の待ち状態になっている場
合を含む）は，タスク例外処理ルーチンの実行は開始されない．これらの状態
を，タスク例外処理マスク状態と呼ぶ．

タスクは，タスク例外処理マスク状態である時に，基本的なタスク状態と重複
して，待ち禁止状態になることができる．

待ち禁止状態とは，タスクが待ち状態に入ることが一時的に禁止された状態で
ある．待ち禁止状態にあるタスクが，サービスコールを呼び出して待ち状態に
遷移しようとした場合，サービスコールはE_RLWAIエラーとなる．

タスクを待ち禁止状態に遷移させるサービスコールは，対象タスクがタスク例
外処理マスク状態である場合に，対象タスクを待ち禁止状態に遷移させる．そ
の後，タスクがタスク例外処理マスク状態でなくなる時点（ユーザタスクにつ
いては特権モードから戻る時点，システムタスクについて拡張サービスコール
からリターンする時点）で，待ち禁止状態が解除される．また，タスクの待ち
禁止状態を解除するサービスコールによっても，待ち禁止状態を解除すること
ができる．

【HRPカーネルからの変更】

タスク例外処理マスク状態という概念を導入し，タスクが待ち禁止状態になる
ことができる状態を明確化した（★要整理）．また，タスクを待ち禁止にする
サービスコール（dis_wai／idis_wai）と，待ち禁止を解除するサービスコール
（ena_wai／iena_wai）を新設した．

●メモリオブジェクト管理機能

メモリリージョンを登録する機能として，以下のAPIを追加する．

	ATT_REG("メモリリージョン名", { ATR regatr, void *base, SIZE size })

メモリオブジェクトを操作するための機能として，以下のAPIを追加する．

	ATT_SEC("セクション名", { ATR mematr, "メモリリージョン名" })
	ATA_SEC("セクション名", { ATR mematr, "メモリリージョン名" },
				{ ACPTN acptn1, ACPTN acptn2, ACPTN acptn3, ACPTN acptn4 })

	ATT_MOD("オブジェクトモジュール名")
	ATA_MOD("オブジェクトモジュール名",
				{ ACPTN acptn1, ACPTN acptn2, ACPTN acptn3, ACPTN acptn4 })

	ATT_MEM({ ATR mematr, void *base, SIZE size })
	ATA_MEM({ ATR mematr, void *base, SIZE size },
				{ ACPTN acptn1, ACPTN acptn2, ACPTN acptn3, ACPTN acptn4 })

	ATT_PMA({ ATR mematr, void *base, SIZE size, void *paddr })
	ATA_PMA({ ATR mematr, void *base, SIZE size, void *paddr  },
				{ ACPTN acptn1, ACPTN acptn2, ACPTN acptn3, ACPTN acptn4 })

	ER ercd = prb_mem(const void *base, SIZE size, ID tskid, MODE pmmode)

ただし，ATT_PMA／ATA_PMAは，MMUを持つターゲットシステムにおいて，ターゲッ
ト定義でサポートされる機能である．

また，セクションを配置する（リンクする）が，メモリオブジェクトとして登
録しないAPIとして以下を追加する．

	LNK_SEC("セクション名", { "メモリリージョン名" })

【HRPカーネルからの変更】

ATT_REG，ATT_SEC／ATA_SEC，ATT_MOD／ATA_MOD，ATT_PMA／ATA_PMAを新設した．
prb_memのdomidパラメータを削除した．

●サービスコールの呼出し方法

サービスコールは，ソフトウェア割込みによって呼び出すことを基本とする．
このドキュメントで単にソフトウェア割込みと言った場合には，サービスコー
ルを呼び出すためのソフトウェア割込みを指す．

ただし，オーバヘッド削減のために，カーネルドメインに属する処理単位がカー
ネルのサービスコールを呼び出す場合には，関数呼出し（サブルーチンコール）
によって直接呼び出すことも可能とする．

具体的には，TOPPERS_SVC_CALLをマクロ定義してコンパイルした場合に，関数
呼出しによりサービスコールを呼び出すようにする．ユーザドメインに属する
処理単位が実行するコードを，この方法でコンパイルしてはならない．

また，サービスコール名をSVC_CALLマクロで囲んで記述することで，関数呼出
しによりサービスコールを呼び出すようにする．この方法は，カーネルドメイ
ンに属する処理単位が実行するコードと，ユーザドメインに属する処理単位が
実行するコードが，同じファイル中に含まれている場合に用いることを想定し
ている．

拡張サービスコールを，関数呼出しによって呼び出す方法は用意しない．カー
ネルドメインに属する処理単位が，関数呼出しによって拡張サービスコールと
して登録した関数を呼び出すことはできてしまうが，その場合には，処理単位
から呼び出された通常の関数であるとみなし，拡張サービスコールルーチンで
あるとはみなさない．つまり，拡張サービスコールの本体を実行中でも，タス
ク例外処理ルーチンが起動される．

これは，拡張サービスコールの処理関数を直接関数呼出しによって呼び出した
場合には，カーネルが拡張サービスコールルーチン実行中であると判断するこ
とができず，タスク例外処理ルーチンの起動条件を正確にチェックできないた
めである．

これに対して，カーネルのサービスコールを関数呼出しによって呼び出した場
合には，サービスコール処理中でタスク例外処理ルーチンが起動されることに
なるが，カーネルのサービスコールは不可分実行と見なせるように実装されて
おり，起動条件に従っていると見なせる．

【HRPカーネルからの変更】

CALSVC_TRAP（ソフトウェア割込みによりサービスコールを呼び出す）の意味を
逆にして，TOPPERS_SVC_CALLとした．SVC_CALLマクロを追加した．

○データ型，定数，マクロの定義

●アクセス許可パターンとアクセス許可ベクタ

HRP2カーネルでは，アクセス許可パターンは32ビット固定とし，ACPTN型は
uint_32型に定義する．アクセス許可ベクタは，actptn1〜actptn4の4つの要素
で構成する（t_stddef.h）．

----------------------------------------
typedef	uint32_t		ACPTN;		/* アクセス許可パターン */
typedef	struct acvct {				/* アクセス許可ベクタ */
	ACPTN		acptn1;				/* 通常操作1のアクセス許可パターン */
	ACPTN		acptn2;				/* 通常操作2のアクセス許可パターン */
	ACPTN		acptn3;				/* 管理操作のアクセス許可パターン */
	ACPTN		acptn4;				/* 参照操作のアクセス許可パターン */
} ACVCT;
----------------------------------------

アクセス許可パターンを記述するためのマクロとして，次のマクロを用意する
（t_stddef.h）．

----------------------------------------
#define TACP_KERNEL		(0U)	/* カーネルドメインだけにアクセスを許可 */
#define TACP_SHARED		(~0U)	/* すべてのドメインからアクセスを許可 */
----------------------------------------
#define TACP(domid)		(1U << ((domid) - 1))	/* domidだけにアクセスを許可 */
----------------------------------------

これらの定義は，ASPカーネルのt_stddef.hにも入れる（t_stddef.hはカーネル
によらず共通にする）．

【HRPカーネルからの変更】

HRPカーネルでは，アクセス権チェックの効率化のために，最上位ビットでカー
ネルドメインからアクセスできるかを表すアクセス許可パターンのカーネル内
部表現を用いていたが，HRP2カーネルでは，この方法は採用しない．これは，
次の理由による．

(1) アクセス許可パターンは，拡張サービスコールにより呼び出すシステムサー
ビスの保護を実現するためにも用いることを想定して，システムサービスの実
装者（カーネルのユーザ）にも解放することにした．そのため，カーネルの内
部表現と外部表現が一致している方がよいと考えられること．

(2) 保護ドメインがカーネルドメインかを判別するオーバヘッドは極めて小さ
いこと．

(3) TOPPERSカーネルの実装では，番兵方式などの類似技法を用いておらず，実
装方針が整合していないこと．

●保護ドメインID

保護ドメインIDを，以下のように定義する（kernel.h）．

----------------------------------------
#define TDOM_SELF		0				/* 自タスクの属する保護ドメイン */
#define TDOM_KERNEL		(-1)			/* カーネルドメイン */
#define TDOM_NONE		(-2)			/* 無所属（保護ドメインに属さない）*/
----------------------------------------

○ユーザタスクの実現

●ユーザタスクとシステムタスクの区別

TCBまたはそこからリンクされたデータ構造を参照することで，ユーザタスク
とシステムタスクを区別できることが必要である．

ユーザタスクであるかシステムタスクであるかのフラグをTCBに入れる方法も考
えられるが，現時点では，保護ドメイン初期化ブロック中の情報で区別する方
法をとる．保護ドメイン初期化ブロックについては後で検討することとし，こ
の段階では，タスクがシステムタスクか否かの判定ができる方法が用意されて
いるものとしておく．

●タスク初期化ブロックの修正

タスク初期化ブロック中に持っているスタック領域のサイズと先頭番地を，シ
ステムスタック領域のサイズと先頭番地と，ユーザスタック領域のサイズと先
頭番地に分離する（task.h）．

----------------------------------------
typedef struct task_initialization_block {
	………
#ifdef USE_TSKINICTXB
	TSKINICTXB	tskinictxb;		/* タスク初期化コンテキストブロック */
#else /* USE_TSKINICTXB */
!	SIZE	sstksz;				/* システムスタック領域のサイズ（丸めた値） */
!	void	*sstk;				/* システムスタック領域の先頭番地 */
!	SIZE	ustksz;				/* ユーザスタック領域のサイズ（丸めた値） */
!	void	*ustk;				/* ユーザスタック領域の先頭番地 */
#endif /* USE_TSKINICTXB */
	………
} TINIB;
----------------------------------------

システムタスクの場合には，sstkszとsstkを用い，ustkszとustkは用いない．
これは，CRE_TSKのパラメータの扱いとは異なるが，システムタスクとユーザタ
スクで，カーネルが用いるスタック領域を同じフィールドから初期化できる方
が見透しがよいと考えたためである．コンフィギュレータは，システムタスク
に対しては，ustkszに0，ustkにNULLを設定するものとする．

上記のスタック領域のサイズと先頭番地の持ち方は，ターゲットプロセッサに
依存しない汎用的な方法であるが，多くのターゲットプロセッサにおいて，よ
り効率的な持ち方があると思われる．また，ターゲット依存の情報をタスク初
期化ブロックに入れたい場合もある．そこで，このような場合には，ターゲッ
ト依存部（target_config.h）で，USE_TSKINICTXBをマクロ定義し，TINIB中に
持つことが必要なデータを入れるためのタスク初期化コンテキスブロック
（TSKINICTXB型）を定義することとしている．

タスク初期化ブロックのテーブルは，コンフィギュレータで生成する．タスク
初期化コンテキストブロックの初期化記述は，ターゲット依存部のテンプレー
トファイルで，テンプレートファイル関数GENERATE_TSKINICTXBに定義する．
USE_TSKINICTXBを定義しない場合には，タスク初期化コンテキストブロックを
生成せず，汎用のタスク初期化ブロックを生成する．GENERATE_TSKINICTXBには，
タスクIDがパラメータとして渡される．

●システムスタック領域の生成とエラーチェック

コンフィギュレータのパス2では，システムスタック領域の生成とエラーチェッ
クを，次の手順で行う．

(1) システムタスクの場合

	sstkが省略されておらず，NULLでない場合 → E_PAR
	stkszが0以下の場合 → E_PAR
	stkがNULLの場合
		sstkszが省略されていない場合
			stksz+sstkszがターゲット定義の最小値より小さい場合 → E_PAR
			stksz+sstkszのサイズのシステムスタック領域を確保
			→ タスク初期化ブロックのsstksz，sstkに設定
		sstkszが省略されている場合
			stkszがターゲット定義の最小値より小さい場合 → E_PAR
			stkszのサイズのシステムスタック領域を確保
			→ タスク初期化ブロックのsstksz，sstkに設定
	stkがNULLでない場合
		stkszがターゲット定義の最小値より小さい場合 → E_PAR
		stkszがスタック領域のサイズの制約を満たしていない場合 → E_PAR
		sstkszが省略されておらず，0でない場合 → E_PAR
		→ タスク初期化ブロックのsstksz，sstkに設定
	 〔後のパスでチェック〕
		stkがスタック領域の先頭番地の制約を満たしていない場合 → E_PAR
		システムスタック領域がカーネル専用のメモリオブジェクトに含まれて
		いない場合 → E_PAR

(2) ユーザタスクの場合

	sstkが省略されているか，NULLの場合
		sstkszが省略されていない場合
			sstkszが0以下か，ターゲット定義の最小値よりも小さい場合 → E_PAR
			sstkszのサイズのシステムスタック領域を確保
			→ タスク初期化ブロックのsstksz，sstkに設定
		sstkszが省略されている場合
			DEFAULT_SSTKSZのサイズのシステムスタック領域を確保
			→ タスク初期化ブロックのsstksz，sstkに設定
	sstkが省略されておらず，NULLでない場合
		sstkszが省略されている場合 → この状況はない
		sstkszが0以下か，ターゲット定義の最小値よりも小さい場合 → E_PAR
		sstkszがスタック領域のサイズの制約を満たしていない場合 → E_PAR
		→ タスク初期化ブロックのsstksz，sstkに設定
	 〔後のパスでチェック〕
		システムスタック領域がカーネル専用のメモリオブジェクトに含まれて
		いない場合 → E_PAR
		sstkがスタック領域の先頭番地の制約を満たしていない場合 → E_PAR

ここで，ユーザタスクのシステムスタック領域のサイズの記述が省略された場
合のデフォルトのサイズは，ターゲット依存部（target_config.hまたはそこか
らインクルードされるファイル）でDEFAULT_SSTKSZに定義する．

システムスタック領域のサイズのターゲット定義の最小値は，テンプレートファ
イルのターゲット依存部（target.tf）で，TARGET_MIN_SSTKSZに定義する．
TARGET_MIN_SSTKSZを定義しない場合，システムスタック領域のサイズが0以下
の場合にのみE_PARエラーとなる．つまり，TARGET_MIN_SSTKSZを1に定義したの
と同等となる．

システムスタック領域のサイズが，スタック領域のサイズの制約を満たしてい
るかのチェックを行うには，スタック領域のサイズのアライン単位を，テンプ
レートファイルのターゲット依存部（target.tf）で，CHECK_STKSZ_ALIGNに定
義する．

コンフィギュレータのパス3では，システムスタック領域が，カーネル専用のメ
モリオブジェクトに含まれているかをチェックする．含まれていない場合には，
E_PARエラーとなる．

コンフィギュレータのパス4では，システムスタック領域の先頭番地が，スタッ
ク領域の先頭番地の制約を満たしているかをチェックする．このチェックを行
うには，スタック領域の先頭番地のアライン単位を，パス4のテンプレートファ
イルのターゲット依存部（target_check.tf）で，CHECK_STACK_ALIGNに定義す
る．制約を満たしていない場合には，E_PARエラーとなる．

●ユーザスタック領域の生成とエラーチェック

コンフィギュレータは，システムタスクに対しては，タスク初期化ブロックの
ustkszには0，ustkにはNULLを設定を設定する．

コンフィギュレータのパス2では，ユーザタスクのユーザスタック領域の生成と
エラーチェックを，次の手順で行う．

	stkszが0以下か，ターゲット定義の最小値よりも小さい場合 → E_PAR
	stkがNULLの場合
		stkszのサイズのユーザスタック領域を確保（ALLOC_USTACKを呼ぶ）
		→ タスク初期化ブロックのustksz，ustkに設定
	stkがNULLでない場合
		stkszがスタック領域のサイズの制約を満たしていない場合 → E_PAR
		→ タスク初期化ブロックのustksz，ustkに設定
	 〔後のパスでチェック〕
		stkがスタック領域の先頭番地の制約を満たしていない場合 → E_PAR
		ユーザスタック領域が他のメモリオブジェクトとオーバラップしている
		場合 → E_OBJ

ここで，ユーザスタック領域のサイズのターゲット定義の最小値は，テンプレー
トファイルのターゲット依存部（target.tf）で，TARGET_MIN_USTKSZに定義す
る．TARGET_MIN_USTKSZを定義しない場合，ユーザスタック領域のサイズが0以
下の場合にのみE_PARエラーとなる．つまり，TARGET_MIN_USTKSZを1に定義した
のと同等となる．

ユーザスタック領域のサイズが，スタック領域のサイズの制約を満たしている
かのチェックを行うには，スタック領域のサイズのアライン単位を，テンプレー
トファイルのターゲット依存部（target.tf）で，CHECK_USTKSZ_ALIGNに定義す
る．

ユーザスタック領域をコンフィギュレータによって確保する方法はターゲット
依存になるため，ユーザスタック領域を確保するための記述を生成するための
テンプレートファイル関数ALLOC_USTACKをターゲット依存部で定義し，ターゲッ
ト非依存部から呼び出す．

コンフィギュレータのパス3では，ユーザスタック領域が，他のメモリオブジェ
クトとオーバラップしていないかをチェックする．オーバラップしている場合
には，E_OBJエラーとなる．ただし，同じユーザドメインに属するタスクのユー
ザスタック領域については，ターゲット定義でオーバラップを許す方法がある．

コンフィギュレータのパス4では，ユーザスタック領域の先頭番地が，ユーザス
タック領域の先頭番地の制約を満たしているかをチェックする．このチェック
を行うには，ユーザスタック領域の先頭番地のアライン単位を，パス4のテンプ
レートファイルのターゲット依存部（target_check.tf）で，CHECK_USTACK_ALIGN
に定義する．制約を満たしていない場合には，E_PARエラーとなる．

●ユーザスタックポインタの保存と復帰

システムスタックとユーザスタックを分離したことに伴い，ユーザスタックポ
インタの保存/復帰を行う必要がある．

ユーザスタックポインタの保存/復帰を行う方法として，次の2つの方法がある．

(a) タスクディスパッチャにおいて保存/復帰する．
(b) 特権モードに遷移する箇所で保存し，非特権モードに戻る箇所で復帰する．

特権モード用のスタックと非特権モード用のスタックを別々に持つターゲット
プロセッサでは(a)の方法が，別々に持たないターゲットプロセッサでは(b)の
方法が素直である．(b)の方法を採用する場合には，ユーザスタックポインタを
TSKCTXB中またはシステムスタック上に保存する．

以下では，(a)の方法を仮定して設計を進める．以下で疑似コード中で単に「ス
タック」と記述した場合には，その時点で使用しているスタックを指すものと
する．

また，システムスタックとユーザスタックを分離したことにより，割込みと
CPU例外の出入口で，スタックの切換えを行う必要がある．ターゲットプロセッ
サによっては，この切換え処理はハードウェアによって行われるため，ソフト
ウェアで行う必要はない．

----------------------------------------
void
dispatch(void)
{
#ifdef TOPPERS_SUPPORT_OVRHDR
	ovrtimer_stop();					/* オーバランタイマの停止 */
#endif /* TOPPERS_SUPPORT_OVRHDR */
	スクラッチレジスタを除くすべてのレジスタをスタックに保存する
+	if (自タスクがユーザタスク) {
+		ユーザスタックポインタをスタックに保存する
+	}
	スタックポインタを自タスク（p_runtsk）のTCBに保存する
	dispatch_rを，実行再開番地として自タスクのTCBに保存する
	dispatcherに分岐する

  dispatch_r:
+	if (自タスクがユーザタスク) {
+		ユーザスタックポインタをスタックから復帰する
+	}
	スクラッチレジスタを除くすべてのレジスタをスタックから復帰する
#ifdef TOPPERS_SUPPORT_OVRHDR
	ovrtimer_start();					/* オーバランタイマの動作開始 */
#endif /* TOPPERS_SUPPORT_OVRHDR */
	calltex();
}
----------------------------------------
void
<割込みの出入口処理>(void)
{
+	if (非特権モードで割込み発生) {
+		スタックを実行状態のタスクのシステムスタックに切り換える
+	}
	割込み優先度マスクを，受け付けた割込み要求の割込優先度に設定する
	少なくともカーネル管理の割込みを禁止した状態にする
	スクラッチレジスタをスタックに保存する

	………

  ret_int:
	if (タスクコンテキストで割込み発生) {
		（少なくとも）カーネル管理の割込みを禁止した状態にする
		スタックをタスク用のスタックに戻し，タスクコンテキストに切り換える
		if (reqflg) {
			reqflg = false;
			CPUロック状態にする
			割込み優先度マスクを，全解除状態（TIPM_ENAALL）に設定する
			if (dspflg && p_runtsk != p_schedtsk) {
				スクラッチレジスタを除くすべてのレジスタをスタックに保存する
+				if (自タスクがユーザタスク) {
+					ユーザスタックポインタをスタックに保存する
+				}
				スタックポインタを自タスク（p_runtsk）のTCBに保存する
				ret_int_rを，実行再開番地として自タスクのTCBに保存する
				dispatcherに分岐する

			  ret_int_r:
+				if (自タスクがユーザタスク) {
+					ユーザスタックポインタをスタックから復帰する
+				}
				スクラッチレジスタを除くすべてのレジスタをスタックから復帰する
			}
#ifdef TOPPERS_SUPPORT_OVRHDR
			ovrtimer_start();			/* オーバランタイマの動作開始 */
#endif /* TOPPERS_SUPPORT_OVRHDR */
			calltex();
		}
#ifdef TOPPERS_SUPPORT_OVRHDR
		else {
			ovrtimer_start();			/* オーバランタイマの動作開始 */
		}
#endif /* TOPPERS_SUPPORT_OVRHDR */
	}
	割込み処理からのリターン後に，CPUロック解除状態に戻るように準備する
	スクラッチレジスタをスタックから復帰する
+	if (戻り先が非特権モード) {
+		スタックを実行状態のタスクのユーザスタックに戻す
+	}
	割込み処理からのリターン
}
----------------------------------------
void
<CPU例外の出入口処理>(void)
{
+	if (非特権モードでCPU例外発生) {
+		スタックを実行状態のタスクのシステムスタックに切り換える
+	}
	スクラッチレジスタをスタックに保存する

	………

	  ret_exc:
		if (タスクコンテキストでCPU例外発生) {
			（少なくとも）カーネル管理の割込みを禁止した状態にする
			スタックをタスク用のスタックに戻し，タスクコンテキストに切り換える
			if (reqflg) {
				reqflg = false;
				CPUロック状態にする
				if (dspflg && p_runtsk != p_schedtsk) {
					スクラッチレジスタを除くすべてのレジスタをスタックに保存する
+					if (自タスクがユーザタスク) {
+						ユーザスタックポインタをスタックに保存する
+					}
					スタックポインタを自タスク（p_runtsk）のTCBに保存する
					ret_exc_rを，実行再開番地として自タスクのTCBに保存する
					dispatcherに分岐する

				  ret_exc_r:
+					if (自タスクがユーザタスク) {
+						ユーザスタックポインタをスタックから復帰する
+					}
					スクラッチレジスタを除くすべてのレジスタを
												スタックから復帰する
				}
#ifdef TOPPERS_SUPPORT_OVRHDR
				ovrtimer_start();		/* オーバランタイマの動作開始 */
#endif /* TOPPERS_SUPPORT_OVRHDR */
				calltex();
			}
#ifdef TOPPERS_SUPPORT_OVRHDR
			else {
				ovrtimer_start();		/* オーバランタイマの動作開始 */
			}
#endif /* TOPPERS_SUPPORT_OVRHDR */
		}
		CPU例外処理からのリターン後に，CPUロック解除状態に戻るように準備する
	}
	スクラッチレジスタをスタックから復帰する
+	if (戻り先が非特権モード) {
+		スタックを実行状態のタスクのユーザスタックに戻す
+	}
	CPU例外処理からのリターン
}
----------------------------------------

上記のコードでは，ユーザタスクに対してのみユーザスタックポインタの保存
/復帰を行っているが，ユーザタスクであることの判定が面倒な場合には，すべ
てのタスクに対して一律ユーザスタックポインタの保存/復帰を行っても差し支
えない（ターゲットによっては，これが許されない場合があるかもしれない）．

また，上記のコードでは，レジスタを保存した後にユーザスタックポインタを
保存し，その逆順で復帰しているが，これらの順序は変更しても差し支えない．

ユーザスタックポインタの保存/復帰をディスパッチャ本体（dispatcher）で行
う方法は，カーネル起動時，タスクの実行開始時，自タスクの終了時が例外と
なるため，採用しなかった．

●ユーザタスクの起動

タスクの実行開始時の処理内容は，システムタスクとユーザタスクで大きく異
なる．そこで，システムタスク用のstart_stask_rと，ユーザタスク用の
start_utask_rを用意して，どちらを使うかをタスクの起動時に設定する．

----------------------------------------
void
activate_context(TCB *p_tcb)
{
	指定されたタスク（p_tcb）のTCB中のスタックポインタを初期化する
+	if (指定されたタスクがユーザタスク) {
+		start_utask_rを，実行再開番地として自タスクのTCBに保存する
+	}
+	else {
!		start_stask_rを，実行再開番地として自タスクのTCBに保存する
+	}
	return;

! start_stask_r:
#ifdef TOPPERS_SUPPORT_OVRHDR
	ovrtimer_start();					/* オーバランタイマの動作開始 */
#endif /* TOPPERS_SUPPORT_OVRHDR */
	CPUロック解除状態にする
	自タスク（p_runtsk）の起動番地を，拡張情報をパラメータとして呼び出す
	ext_tskに分岐する

+ start_utask_r:
+	自タスクのユーザスタックポインタを初期化する
+#ifdef TOPPERS_SUPPORT_OVRHDR
+	ovrtimer_start();					/* オーバランタイマの動作開始 */
+#endif /* TOPPERS_SUPPORT_OVRHDR */
+	例外/割込みからのリターンによって，
+			自タスクの起動番地へ分岐するように準備する … (*1)
+	例外/割込みからのリターンによって，非特権モード・CPUロック解除状態・
+			割込み優先度マスク全解除状態になるように準備する … (*2)
+	自タスクの起動番地へ分岐する際に，
+			自タスクの拡張情報がパラメータとして渡るように準備する … (*3)
+	自タスクの起動番地からのリターンにより，
+			call_ext_tskの番地へ分岐するように準備する … (*4)
+	例外/割込みからのリターン
}
----------------------------------------

システムタスク用のstart_stask_rは，名前を変えただけで，ASPカーネルの
start_rと同じ内容である．

自タスクのユーザスタックポインタの初期値は，USE_TSKINICTXBを定義しない
場合には，ターゲットプロセッサにも依存するが，多くの場合に次の式で求め
ることができる．

	((char *)(p_runtsk->p_tinib->ustk) + p_runtsk->p_tinib->ustksz)

(*1)は，例外/割込みからのリターン先の番地を保持する領域（スタック上や専
用レジスタ）に，自タスクの起動番地を格納することで行う．(*2)は，例外/割
込みからのリターン後のシステム状態を保持する領域（スタック上や専用レジ
スタ）に，非特権モード・CPU ロック解除状態・割込み優先度マスク全解除状
態を示す値を設定することで行う．また，これらのシステム状態の管理に変数
を用いている場合には，それらの変数の値を設定する．

(*3)は，例外/割込みからのリターン後に実行される関数のパラメータを置く領
域（ユーザスタック上や汎用レジスタ）に，自タスクの拡張情報を格納するこ
とで行う．(*4)は，例外/割込みからのリターン後に実行される関数からのリター
ン先番地を置く領域（ユーザスタック上や専用レジスタ）に，call_ext_tskの
番地を格納することで行う．

(*1)〜(*4)は，ターゲットプロセッサに都合のよい順序に並べ換えてよい．

call_ext_tskは，以下の内容の関数で，すべての保護ドメインから呼び出せる
共有ライブラリとして（よって，ext_tskとext_kerを，ソフトウェア割込みを
用いて呼び出すコードとしなければならない），ターゲット依存部で用意する．

----------------------------------------
void
call_ext_tsk(void)
{
	(void) ext_tsk();
	(void) ext_ker();
	assert(0);
}
----------------------------------------

ここで，ext_tskの後にext_kerを呼び出すのは，call_ext_tskが非タスクコン
テキストから呼ばれた場合の対策である．非タスクコンテキストはカーネルド
メインなので，ext_kerがエラーになることはない（はずである）．

●ユーザスタック領域のアクセス保護

※この節の内容は，メモリ保護の方式を決定した後に実施すべきである．

タスクのユーザスタック領域を同じ保護ドメインに属する他のタスクから保護
するために，タスク切換えの際に，切換え前のタスクのユーザスタック領域を
アクセスできない状態とし，切換え後のタスクのユーザスタック領域をアクセ
スできる状態とする必要がある．

切換え前のタスクのユーザスタック領域をアクセスできない状態とする処理と，
切換え後のタスクのユーザスタック領域をアクセスできる状態とする処理は，
ディスパッチャ本体に組み込む．

----------------------------------------
void
dispatcher(void)
{
#ifdef LOG_DSP_ENTER
	log_dsp_enter(p_runtsk);
#endif /* LOG_DSP_ENTER */
+	if (自タスクがユーザタスク) {
+		自タスクのユーザスタック領域をアクセスできない状態とする
+	}
  dispatcher_0:

	………

	自タスク（p_runtsk）のTCBからスタックポインタを復帰する
+	if (自タスクがユーザタスク) {
+		自タスクのユーザスタック領域をアクセスできる状態とする
+	}
#ifdef LOG_DSP_LEAVE
	log_dsp_leave(p_runtsk);
#endif /* LOG_DSP_LEAVE */
	自タスクのTCBから実行再開番地を復帰し，そこへ分岐する
}
----------------------------------------

メモリプロテクションユニット（MPU）を持つターゲットプロセッサでは，切換
え後のタスクのユーザスタック領域をアクセスできる状態とすれば，切換え前
のタスクのユーザスタック領域はアクセスできなくなる場合が多い．この場合
には，アクセスできない状態とする処理は省略することができる．また，処理
内容を工夫することで，自タスクがユーザタスクか否かの判定は省ける場合が
ある．

ここで，自タスクのユーザスタック領域をアクセスできない状態とする処理は，
実行状態のタスクが終了する時（exit_and_dispatchからディスパッチャ本体に
入る場合）には行う必要があるが，カーネル起動時（start_dispatchからディ
スパッチャ本体に入る場合）には行う必要がない．exit_and_dispatchからは
dispatcherに分岐し，start_dispatchからはdispatcher_0に分岐するため，上
記のコードでこれが実現されている．

メモリ管理ユニット（MMU）を持つターゲットプロセッサでは，タスク毎にユー
ザスタック領域のアクセス権を切り換えると，効率が極めて低下する場合が多
い．その場合には，タスク毎にアクセス権を切り換えるのをやめ（統合仕様書
2.11.6節で明示的に許されている），レッドゾーン方式のユーザスタック領域
保護を採用する．

○保護ドメインの管理

●保護ドメイン初期化ブロック

HRP2カーネルでは，保護ドメインを管理するための動的な情報は必要ないため，
保護ドメインコントロールブロックは設けず，保護ドメイン初期化ブロックの
みを設ける．

保護ドメイン初期化ブロックには，保護ドメインのビットパターン（ACPTN型，
カーネルドメインの場合は0）と，（必要な場合には）ターゲット依存の情報を
入れる．

ターゲット依存の情報を入れる場合には，ターゲット依存部（target_config.h）
で，USE_DOMINICTXBをマクロ定義し，ターゲット依存の情報を入れるための保
護ドメイン初期化コンテキストブロック（DOMINICTXB型）を定義する．

----------------------------------------
typedef struct domain_initialization_block {
	ACPTN		domptn;			/* 保護ドメインのビットパターン */
#ifdef USE_DOMINICTXB
	DOMINICTXB	dominictxb;		/* 保護ドメイン初期化コンテキストブロック */
#endif /* USE_DOMINICTXB */
} DOMINIB;
----------------------------------------

ユーザドメインの保護ドメイン初期化ブロックのテーブルは，コンフィギュレー
タで生成する．ユーザドメインの保護ドメイン初期化コンテキストブロックの
初期化記述は，ターゲット依存部のテンプレートファイルで，テンプレートファ
イル関数GENERATE_DOMINICTXBに定義する．GENERATE_DOMINICTXBには，ドメイ
ンIDがパラメータとして渡される．

カーネルドメインの保護ドメイン初期化ブロックは，内容が変わらないためカー
ネル内で静的に定義する方法もあるが，そのためだけにdomain.cを用意するこ
とになることから，コンフィギュレータで生成することとする．カーネルドメ
インの保護ドメイン初期化コンテキストブロックの初期化記述は，ターゲット
依存部のテンプレートファイルで，テンプレートファイル変数
DOMINICTXB_KERNELに設定する．

具体的には，kernel_cfg.cに以下の記述を生成する．

----------------------------------------
const DOMINIB _kernel_dominib_table[TNUM_DOMID] = {
	{ TACP(<ドメインIDが1のドメイン名>), $GENERATE_DOMINICTXB(1)$ },
	{ TACP(<ドメインIDが2のドメイン名>), $GENERATE_DOMINICTXB(2)$ },
	………
	{ TACP(<ドメインIDがnのドメイン名>), $GENERATE_DOMINICTXB(n)$ }
};

const DOMINIB	dominib_kernel = { TACP_KERNEL, $DOMINICTXB_KERNEL$ };
----------------------------------------

ここで，$GENERATE_DOMINICTXB(i)$は，テンプレートファイル関数
GENERATE_DOMINICTXBをiをパラメータとして呼び出した時に生成される文字列
を示す．また，$DOMINICTXB_KERNEL$は，テンプレートファイル変数
DOMINICTXB_KERNELに設定された文字列を示す．

●オブジェクトのアクセス許可ベクタの管理

保護の対象となるカーネルオブジェクトの初期化ブロックに，アクセス許可ベ
クタを追加する．

----------------------------------------
typedef struct task_initialization_block {
	………
	ACVCT		acvct;			/* アクセス許可ベクタ */
} TINIB;
----------------------------------------
typedef struct semaphore_initialization_block {
	………
	ACVCT		acvct;			/* アクセス許可ベクタ */
} SEMINIB;
----------------------------------------
※ 他の初期化ブロック（FLGINIB，DTQINIB，PDQINIB，MPFINIB，CYCINIB，
ALMINIB）も同様．
----------------------------------------

また，システム状態に対するアクセス許可ベクタを格納する変数を新設する
（kernel_impl.h）．

----------------------------------------
/*
 *  システム状態に対するアクセス許可ベクタ（kernel_cfg.c）
 */
extern const ACVCT	sysstat_acvct;
----------------------------------------

システム状態に対するアクセス許可ベクタ（sysstat_acvct）の定義は，コンフィ
ギュレータで生成する．

●オブジェクトの属する保護ドメインの管理

HRP2カーネルでは，タスク以外のオブジェクトに関しては，各カーネルオブジェ
クトに対してアクセス許可ベクタを管理すれば，それが属する保護ドメインを
取り出す必要がない．そのため，オブジェクト（タスク以外）の属する保護ド
メインに関する情報は管理しないことにする．

それに対して，タスクに関しては，それが属する保護ドメインに関する情報を
取り出す状況が多い．そこで，タスク初期化ブロックに，そのタスクが属する
保護ドメイン初期化ブロックへのポインタを保持するフィールド（p_dominib）
を追加する．このポインタは，コンフィギュレータが設定する．

----------------------------------------
typedef struct task_initialization_block {
	const DOMINIB *p_dominib;	/* 保護ドメイン初期化ブロック */
	………
} TINIB;
----------------------------------------

【HRPカーネルからの変更】

HRPカーネルでは，保護ドメイン初期化ブロックへのポインタを保持するフィー
ルド（p_dominib）を，タスクコントロールブロック（TCB）に追加した．オー
バヘッド削減のためにはこのアプローチも有力であるが，このフィールドを初
期化するために，タスク初期化ブロックの中にどの保護ドメインに属するかの
情報を持つ必要があり，情報を二重管理することになるため，HRP2カーネルで
は採用していない．

●実行中の保護ドメインの管理

アクセス権のチェックを行う際には，実行中の処理単位の属する保護ドメイン
（以下，単に実行中の保護ドメインという）のビットパターンを取り出すこと
が必要になる．実行中の保護ドメインは，次の方法で求めることができる．

タスクが拡張サービスコールを実行していない時には，実行中の保護ドメイン
は，そのタスクの属する保護ドメイン（ビットパターンは，
p_runtsk->p_tinib->p_dominib->domptn）である．非タスクコンテキスト実行
中と，タスクが拡張サービスコールを実行している時は，実行中の保護ドメイ
ンは，カーネルドメイン（ビットパターンは，TACP_KERNEL）である．

実行中の保護ドメインのビットパターンを管理する方法として，次の3つが考え
られる．

(1) 特別な変数等を設けず，毎回上記の方法によって求める．実行中のタスク
が，拡張サービスコールを実行しているか否かを判別する方法が必要である．

(2) 実行中の保護ドメインのビットパターンを持つ変数としてrundomを設ける．

rundomはタスクコンテキストでのみ有効な変数とする．これは，HRP2カーネル
では，非タスクコンテキストはすべてカーネルドメインで実行されるため，非
タスクコンテキストに対するrundomは必要ないためである．

rundomは，拡張サービスコールを呼び出す時に，カーネルドメインのビットパ
ターン（TACP_KERNEL）に設定し，リターンする時に元の値に戻す．

タスク切換え時にrundomを更新するための方法として，次の2つの方針が考えら
れる．

(2-1) rundomは，タスクのコンテキストの一部と扱う．タスクの実行開始時に
初期化し，ディスパッチャにおいて保存／復帰する．

(2-2) タスク切換えの度に，上記の方法によってrundomを求めて設定する．実
行中のタスクが，拡張サービスコールを実行しているか否かを判別する方法が
必要である．

(3) タスクが実行中の保護ドメインのビットパターンを持つTCB中のフィールド
として，rundomを設ける．rundomは，タスク実行開始時までに（具体的には，
make_dormantで）初期化し，拡張サービスコールを呼び出す時に，カーネルド
メインのビットパターン（TACP_KERNEL）に設定し，リターンする時に元の値に
戻す．

この中で(1)の方法は，アクセス権チェックの度（TLBミス時にも，アクセス権
チェックをすることに注意）に，ポインタを3回たどることが必要になり，オー
バヘッドが大きいという問題がある．

HRPカーネルでは，(2-1)の方法を採用していたが，ディスパッチャでrundomを
保存／復帰するのは，他の状態の扱いと異なっている点が気になる．

以上より，(2-2)の方法と(3)の方法が有力であるが，メモリ使用量を小さくで
き，rundomを効率よくアクセスできる(2-2)の方法を採用する．ただし，後で
(3)の方法に変更する可能性もある．

----------------------------------------
/*
 *  実行中の保護ドメインのビットパターン
 */
ACPTN	rundom;
----------------------------------------

●svclevelの導入

タスクが拡張サービスコール実行しているか否かを判別するために，拡張サー
ビスコールのネストレベルを管理する．拡張サービスコールの中でもディスパッ
チは起こるため，拡張サービスコールのネストレベルはタスク毎に管理する必
要がある．そこで，タスク毎の拡張サービスコールのネストレベルを管理する
ために，TCBの中にuint8_t型（uint8_t型がない場合には，8ビット幅のビット
フィールドとする）のsvclevelフィールドを用意する．

----------------------------------------
typedef struct task_control_block {
	………
#ifdef UINT8_MAX
	uint8_t			svclevel;		/* 拡張サービスコールのネストレベル */
#else /* UINT8_MAX */
	BIT_FIELD_UINT	svclevel : 8;	/* 拡張サービスコールのネストレベル */
#endif /* UINT8_MAX */
	………
} TCB;
----------------------------------------

svclevelは，タスク実行開始時までに（具体的には，make_dormantで）0に初期
化し，ソフトウェア割込みによる拡張サービスコールの入口処理でインクリメ
ントし，出口処理でデクリメントする．ただし，非タスクコンテキストに対す
る拡張サービスコールのネストレベルは管理しないため，呼出し元が非タスク
コンテキストの場合には，svclevelをインクリメント／デクリメントしない．

通常のアプリケーション設計で，拡張サービスコールのネストレベルが
UINT8_MAX（＝255）を超えることは考えられないため，svclevelがオーバフロー
した場合には，システム異常とみなしてE_SYSエラーを返す．

サブルーチンコールによって拡張サービスコールの本体を呼び出した場合は，
拡張サービスコールとは見なさないこととしたため，サブルーチンコールによ
る拡張サービスコールの本体の呼出しでは，svclevelを操作しない．

----------------------------------------
void
make_dormant(TCB *p_tcb)
{
	………
	p_tcb->svclevel = 0U;
	………
}
----------------------------------------

svclevelは，あくまでも拡張サービスコールを実行しているか否かを判別する
ための変数であり，ユーザタスクが特権モードを実行しているか否かの判別に
使用することはできない．

●rundomの設定方法

上で導入したsvclevelを用いて，rundomに，実行状態のタスクの属する保護ド
メインのビットパターンを設定する方法は次の通り．

----------------------------------------
void
dispatcher(void)
{
!	DOMINIB	*p_dominib;

	………

	自タスク（p_runtsk）のTCBからスタックポインタを復帰する
+	p_dominib = p_runtsk->p_tinib->p_dominib;
+	rundom = (p_runtsk->svclevel > 0U) ? TACP_KERNEL : p_dominib->domptn;
	if (自タスクがユーザタスク) {
		自タスクのユーザスタック領域をアクセスできる状態とする
	}
#ifdef LOG_DSP_LEAVE
	log_dsp_leave(p_runtsk);
#endif /* LOG_DSP_LEAVE */
	自タスクのTCBから実行再開番地を復帰し，そこへ分岐する
}
----------------------------------------

p_runtsk->p_tinib->p_dominibを一旦p_dominibに格納するのは，後の修正の都
合である．

●オブジェクトに対するアクセス権チェックの追加

check.hに次のチェックマクロを追加し，保護の必要のあるすべてのサービスコー
ルの入口で，このマクロを用いてアクセス権のチェックを行う（このマクロは，
タスクコンテキストでのみ使うことができる）．

----------------------------------------
/*
 *  オブジェクトアクセス権のチェック（E_OACV）
 */
#define CHECK_ACPTN(acptn) {									\
	if (rundom != TACP_KERNEL && (rundom & (acptn)) == 0U) {	\
		ercd = E_OACV;											\
		goto error_exit;										\
	}															\
}
----------------------------------------

例えば，act_tskの場合には，次のようなコードとなる．

----------------------------------------
ER
act_tsk(ID tskid)
{
	TCB		*p_tcb;
	ER		ercd;

	LOG_ACT_TSK_ENTER(tskid);
	CHECK_TSKCTX_UNL();
	CHECK_TSKID_SELF(tskid);
	p_tcb = get_tcb_self(tskid);
	CHECK_ACPTN(p_tcb->p_tinib->acvct.acptn1);

	………
----------------------------------------

【HRPカーネルからの変更】

オブジェクトに対するアクセス権チェックを，次のように変更した．

　get_pri：タスクに対する通常操作1 → 参照操作
　get_tim：システム時刻に対する通常操作2 → システム状態に対する参照操作
　get_utm：システム時刻に対する通常操作2 → 常に呼び出せる
　get_ipm：システム状態に対する通常操作2 → 参照操作
　ext_ker：システム状態に対する管理操作 → カーネルドメインのみ

●メモリ領域のアクセス保護

ユーザタスクを実行中は，メモリ管理ユニット（MMU）またはメモリプロテクショ
ンユニット（MPU）を，そのタスクが属する保護ドメインからアクセス可能なメ
モリ領域にのみアクセスできるように設定する．ここで，MMU/MPUに設定する情
報の形式は，MMU/MPUの機能や仕様に大きく依存する．将来的には，MMU/MPUを
いくつかに類型化して，その類型毎に標準的な形式を決めたいが，現時点では
ターゲット毎に形式を決定するものとする．また，その生成も，ターゲット依
存に行うこととする．

MMU/MPUに，どの保護ドメイン（ユーザドメインに限られる）に関する情報が設
定されているかを示す変数として，p_ctxdomを設ける．p_ctxdomは，MMU/MPUに
設定されている保護ドメインの保護ドメイン初期化ブロックへのポインタを設
定する．p_ctxdomは，カーネルの初期化処理でNULLに初期化する．

----------------------------------------
/*
 *  MMU/MPUに設定されている保護ドメイン
 */
DOMINIB	*p_ctxdom;
----------------------------------------
void
initialize_task(void)
{
	………
	p_ctxdom = NULL;
	………
}
----------------------------------------

ディスパッチャ本体においては，切換え後のタスクが属する保護ドメインがユー
ザドメインであり，p_ctxdomに一致していない場合に，MMU/MPUに設定されてい
る保護ドメインを切り換える．

----------------------------------------
void
dispatcher(void)
{
	………

	自タスク（p_runtsk）のTCBからスタックポインタを復帰する
	p_dominib = p_runtsk->p_tinib->p_dominib;
	rundom = (p_runtsk->svclevel > 0U) ? TACP_KERNEL : p_dominib->domptn;
	if (自タスクがユーザタスク) {
		自タスクのユーザスタック領域をアクセスできる状態とする
+		if (p_ctxdom != p_dominib) {
+			p_ctxdom = p_dominib;
+			p_dominibに関するアクセス保護情報をMMU/MPUに設定する
+		}
	}
#ifdef LOG_DSP_LEAVE
	log_dsp_leave(p_runtsk);
#endif /* LOG_DSP_LEAVE */
	自タスクのTCBから実行再開番地を復帰し，そこへ分岐する
}
----------------------------------------

上のコードでは，p_dominibに関するアクセス保護情報をMMU/MPUに設定すれば，
それまでにMMU/MPUに設定されていた（切換え前の保護ドメインに関する）情報
は上書きされて設定解除されるものと想定している．この想定が成り立たない
ハードウェアでは，切換え前の保護ドメインに関する情報を，MMU/MPUから設定
解除する必要がある．

●実行状態のタスクの属する保護ドメインIDの参照

実行状態のタスクの属する保護ドメインIDを参照するget_didサービスコールは，
p_runtskのタスク初期化ブロック中の保護ドメイン初期化ブロックへのポイン
タから，所属保護ドメインIDを求める方法で実装する．

----------------------------------------
/*
 *  実行状態のタスクが属する保護ドメインIDの参照
 */
#ifdef TOPPERS_get_did

ER
get_did(ID *p_domid)
{
	ER		ercd;

	LOG_GET_DID_ENTER(p_domid);
	CHECK_TSKCTX_UNL();

	t_lock_cpu();
	*p_domid = DOMID(p_runtsk->p_tinib->p_dominib);
	ercd = E_OK;
	t_unlock_cpu();

  error_exit:
	LOG_GET_DID_LEAVE(ercd, *p_domid);
	return(ercd);
}

#endif /* TOPPERS_get_did */
----------------------------------------

●拡張サービスコールを呼び出した保護ドメインの参照

拡張サービスコールが，アクセス権を持った保護ドメインから呼び出されたこ
とをチェックするためには，拡張サービスコールを呼び出した保護ドメインが
参照できる必要がある．

get_didは，実行状態のタスクが属する保護ドメインを調べるためのものである．
タスク自身のコンテキストから拡張サービスコールが呼び出される場合（言い
換えると，拡張サービスコールがネストしない場合）には，get_didをこの目的
に使用できるが，拡張サービスコールから拡張サービスコールが呼び出された
場合には，この目的で使用できない．具体的には，

	タスク → 拡張サービスコール1 → 拡張サービスコール2

という呼出し関係の場合に，拡張サービスコール2の中でget_didを呼び出すと，
タスクが属する保護ドメインが返される．ここでは本来，拡張サービスコール1
の保護ドメイン，すなわちカーネルドメインを参照したい．

これを実現するために，拡張サービスコールに対して，拡張サービスコールを
呼び出した処理単位が属する保護ドメインのID番号を渡すことにする．具体的
には，拡張サービスコールの記述形式を次のようにする．cdmidは，Calling
DoMain IDを表す．

	ER_UINT extended_svc(intptr_t par1, intptr_t par2, intptr_t par3,
								intptr_t par4, intptr_t par5, ID cdmid)
	{
		拡張サービスコール本体
	}

【HRPカーネルからの変更】

HRPカーネルでは，拡張サービスコールを呼び出した保護ドメインを参照する方
法を用意していなかった．

○ユーザタスクが特権モードを実行しているか否かの判別

●ターゲット非依存に判別する方法の難しさ

HRP2カーネルでは，次の状況において，ユーザタスクがタスク例外処理マスク
状態か否か，すなわち特権モードを実行しているか否かを判別することが必要
になる．

(a) タスク例外処理ルーチンの起動条件をチェックするため
(b) 待ち禁止状態への遷移条件をチェックするため

ユーザタスクが特権モードを実行しているか否かを判別するために，特権モー
ドを実行していることを示す変数を設け，特権モードに入った時にセットし，
特権モードを抜ける時にクリアする方法が考えられる．しかし，この方法には，
次の問題がある．

ソフトウェア割込みによって割込みが禁止されないプロセッサ（例えば，
M68040）においては，ソフトウェア割込みが受け付けられて特権モードになっ
ているにもかかわらず，その変数がセットされておらず，一貫性が崩れた状態
になることが避けられない．

そのため，このようなプロセッサでは，指定したタスクが特権モードを実行し
ているか否かや，非タスクコンテキストから実行状態のタスクが特権モードを
実行しているか否かを，このような変数を使って判別することができず，ター
ゲット依存の方法で対応せざるを得ない．

●特権モードを実行しているか否かを判別する関数

HRP2カーネルでは，次の関数をターゲット依存部で用意するものとする．

・t_sense_priv(p_tcb)
・i_sense_priv(p_tcb)

p_tcbで指定される実行状態でないユーザタスクが，特権モードを実行している
場合にtrue，そうでない場合にfalseを返す関数．この関数が，p_tcbに実行状
態のタスク（t_sense_privの場合は自タスク）を指定して呼び出されることや，
p_tcbにシステムタスクを指定して呼び出されることはない．

・i_sense_priv_runtsk(void)

実行状態のユーザタスクが，特権モードを実行している場合にtrue，そうでな
い場合にfalseを返す関数．実行状態のタスクがない場合（p_runtskがNULLの場
合）や，実行状態のタスクがシステムタスクの場合には，この関数が呼び出さ
れることはない．

なお，タスクコンテキストから，実行状態のユーザタスク（つまり，自タスク）
が特権モードを実行しているか否かを判別する関数（t_sense_priv_runtsk）は，
この関数がサービスコール中から呼び出すものであることを考えると，必ず
trueを返す関数となるため，用意する必要がない．

これらの関数は，ソフトウェア割込みによって割込みが禁止されるプロセッサ
では，特権モードを実行していることを示すboot_t型の変数をTSKCTXB中のフィー
ルドとして設け，特権モードに入った時にセットし，特権モードを抜ける時に
クリアする方法で実現することができる．

特権モードを実行していることを示す変数を設けない場合には，次のように実
装することができる．

・t_sense_priv(p_tcb)
・i_sense_priv(p_tcb)

この関数が呼ばれる時点では，p_tcbによって指定されるタスク（対象タスク）
のレジスタ等は，ディスパッチャによって，TCBとシステムスタックに保存され
た状態にある．タスクが実行していたのが特権モードか否かの取り出し方は，
対象タスクのTCB中の実行再開番地毎に，次の通りである．

　dispatch_rの時：dispatchが呼び出されるのはサービスコールの中であるた
	め，タスクが実行していたのは特権モードである．

　ret_int_rかret_exc_rの時：対象タスクのレジスタ等は，割込みハンドラま
	たはCPU例外ハンドラの出入口処理によって，タスクのシステムスタックの
	先頭（スタックトップ）に積まれている．その中から，タスクが実行して
	いたのが特権モードか否かを取り出すことができる．この方法で取り出し
	にくい場合には，システムスタックに積む情報を増やしてもよい．

　start_utask_rの時：タスクは実行開始しておらず，タスクが実行していたの
	は特権モードではない．基本的には，タスクの実行開始前は，タスクが実
	行していたのが特権モードと判断しても，そうでないと判断しても差し支
	えないが，待ち禁止状態には遷移しない方が素直であるため，統合仕様書
	ではタスク例外処理マスク状態ではないと定義している．

・i_sense_priv_runtsk(void)

この関数が呼ばれる時点では，実行状態のタスクのレジスタ等は，割込みハン
ドラまたはCPU例外ハンドラの実行開始時に，その出入口処理（またはプロセッ
サ）によって，タスクのシステムスタックの先頭（スタックトップ）か非タス
クコンテキスト用のスタックの底（スタックボトム）に積まれている．その中
から，タスクが実行していたのが特権モードか否かを取り出すことができる．
この方法で取り出しにくい場合には，システムスタックに積む情報を増やして
もよい．

○サービスコールの呼出し処理

●サービスコール処理関数のリネーム

アプリケーションとカーネルを1つのロードモジュールにリンクすることから，
名称の衝突を避けるために，カーネル内のサービスコール処理関数は，先頭に
_kernel_を付加した名称にリネームすることとする．例えば，task_manage.c中
のact_tsk関数は，_kernel_act_tskにリネームする．

リネームするための記述は，次に述べる理由でアプリケーションからも用いる
ため，include/svc_call.hに含める．

●関数呼出しによるサービスコール呼出し

カーネルドメインに属する処理単位から，関数呼出しによってサービスコール
を呼び出すために，TOPPERS_SVC_CALLをマクロ定義した場合には，先頭
に_kernel_を付加した名称の関数を直接呼び出すようにする．つまり，サービ
スコール関数の名称を，先頭に_kernel_を付加した名称にリネームする．また，
SVC_CALLマクロは，先頭に_kernel_を付加した名称を作るマクロとする．

このことから，先頭に_kernel_を付加した名称にリネームするための記述は，
アプリケーションからも使用することになるため，include/svc_call.hに含め，
kernel.hからインクルードする．svc_call.hには，サービスコール処理関数の
プロトタイプ宣言も含め，リネーム記述は，TOPPERS_SVC_CALLがマクロ定義さ
れている場合にのみ有効とする．svc_call.hはツール（gensvc）によって生成
する．

svc_call.hの一部分（act_tskに関連する部分）を下に示す．

----------------------------------------
extern ER _kernel_act_tsk(ID tskid) throw();

#ifdef TOPPERS_SVC_CALL
#define act_tsk _kernel_act_tsk
#ifdef TOPPERS_LABEL_ASM
#define _act_tsk __kernel_act_tsk
#endif /* TOPPERS_LABEL_ASM */
#endif /* TOPPERS_SVC_CALL */
----------------------------------------

カーネル内では先頭に_kernel_を付加した名称を使用するため，
kernel_impl.hからkernel.hをインクルードする前に，TOPPERS_SVC_CALLをマク
ロ定義する．

●ソフトウェア割込みによるサービスコール呼出し

TOPPERS_SVC_CALLをマクロ定義しない場合には，サービスコールを呼び出す関
数をターゲット依存部で実現するものとする．そのために，ターゲット依存の
ヘッダファイル（target_svc.h）を，kernel.hからインクルードする．

なお，拡張サービスコールを呼び出すためのcal_svcサービスコールは，
TOPPERS_SVC_CALLのマクロ定義の有無に関わらず，ソフトウェア割込みによっ
て呼び出すこととする．そのため，cal_svcに関するプロトタイプ宣言やリネー
ム記述はsvc_call.hには含めず，cal_svcサービスコールを呼び出す処理を，ター
ゲット依存部（target_svc.h）で実現するものとする．後述のCAL_SVC_5Mを用
いて，cal_svcサービスコールを呼び出す処理を記述すると，下のようになる．

----------------------------------------
Inline ER_UINT
cal_svc(FN fncd, intptr_t par1, intptr_t par2,
							intptr_t par3, intptr_t par4, intptr_t par5)
{
	if (fncd > 0) {
		CAL_SVC_5M(ER_UINT, fncd, intptr_t, par1, intptr_t, par2,
							intptr_t, par3, intptr_t, par4, intptr_t, par5);
	}
	else {
		return(E_RSFN);
	}
}
----------------------------------------

上記のコードでは，fncdが正であることのチェックをユーザモードで行ってい
る．これは，カーネルのサービスコールと拡張サービスコールで同一のソフト
ウェア割込みを用いることを標準としているため，fncdが負の場合にはカーネ
ルのサービスコールを呼び出してしまうためである．fncdに0または負の値を与
えても，保護が破れないようになっており，保護の観点からもこれで問題ない．

ソフトウェア割込みによってサービスコールを呼び出す場合，機能コードとパ
ラメータはレジスタに置くことを原則とする．標準のコーリングコンベンショ
ンがレジスタ渡しのプロセッサでは，パラメータを標準のコーリングコンベン
ションに従ってレジスタに置き，機能コードはその邪魔にならないレジスタに
置くのがよい．

パラメータを置くレジスタをコントロールするために，サービスコールの呼出
しルーチンはインラインアセンブラやpragma等を用いて記述する．具体的な方
法は，開発環境（コンパイラ）に依存する．

GNU開発環境においては，サービスコールの呼出しルーチンを，インラインアセ
ンブラを含むインライン関数で記述する．すべてのサービスコールに対して呼
出しルーチンを記述するのは手間がかかるため，ターゲット依存部でソフトウェ
ア割込みによってサービスコールを呼び出すためのマクロを定義した後，ツー
ル（gensvc）によって生成したarch/gcc/tool_svc.hをインクルードすることで，
各サービスコールの呼出しルーチンが定義されるようにする．

ターゲット依存部で定義すべきソフトウェア割込みによってサービスコールを
呼び出すためのマクロは次の通り．

・CAL_SVC_<n>M（<n>は0〜5までの数字）

n個の引数を持ったサービスコールをソフトウェア割込みによって呼び出すため
のマクロ．このマクロに対する引数は，返値の型，機能コード，各引数の型お
よび引数名である．

例えば，M68040におけるCAL_SVC_1Mの定義を下に示す．

----------------------------------------
#define CAL_SVC_1M(TYPE, FNCD, TYPE1, PAR1)						\
	register FN d0 asm("d0") = (FN)(FNCD);						\
	register TYPE1 d1 asm("d1") = (TYPE1)(PAR1);				\
	Asm("trap #1"												\
	  : "=g"(d0)												\
	  : "0"(d0), "g"(d1)										\
	  : "d1", "a0" "a1", memory");								\
	return((TYPE) d0)
----------------------------------------

このマクロは，機能コードをレジスタD0に，第1引数をレジスタD1に入れて，
trap #1を実行し，D0を返値とするものである．また，返値を入れるレジスタ
（D0）以外のスクラッチレジスタ（D1, A0, A1）とメモリ（memory）を，
clobber指定している．これにより，ソフトウェア割込みの中で，スクラッチレ
ジスタを保存する必要がなくなる．また，サービスコールを超えた最適化が抑
止される．

これを用いてact_tskを呼び出すインライン関数（tool_svc.hの一部分で，ツー
ルにより生成される）は，下のようになる．

----------------------------------------
Inline ER
act_tsk(ID tskid)
{
	CAL_SVC_1M(ER, TFN_ACT_TSK, ID, tskid);
}
----------------------------------------

【HRPカーネルからの変更】

HRPカーネルでは，メモリをclobber指定しないCAL_SVC_<n>（<n>は0〜5までの
数字）を用意し，サービスコールで，n個の引数を持ったサービスコールの中で，
メモリを書き換える副作用を持たないものを呼び出す場合には，こちらを用い
ていた．

この方法は，次のようなコードにおいて，変数xへの代入文がサービスコール呼
出しを超えて移動されてしまう可能性があり（変数xにvolatile指定があれば移
動されないと思われる），最適化され過ぎになるという問題がある．

	wai_sem(SEM_FOR_X);
	x = 1;
	sig_sem(SEM_FOR_X);

●ソフトウェア割込みの出入口処理

ここでは，ソフトウェア割込みによるサービスコールの出入口処理の基本形に
ついて検討する．

ソフトウェア割込みの入口処理では，必要な場合，レジスタに置かれたパラメー
タをスタックに積み，機能コードに従ってサービスコール処理ルーチンを呼び
出す．カーネルコールについては，機能コードを符号反転し1を減じた値をイン
デックスとするカーネルコールの処理ルーチンの番地のテーブル（svc_table）
を用意し，そこから取り出した処理ルーチンを呼び出す．このテーブルのソー
スファイル（svc_table.c）は，ツール（gensvc）によって生成する．

ソフトウェア割込みによるサービスコールの出入口処理の処理内容は次の通り．

----------------------------------------
ER_UINT
svc_entry(FN fncd, パラメータリスト)
{
	ER_UINT		ercd;

	if (非特権モードからの呼び出し) {
		スタックを実行状態のタスクのシステムスタックに切り換える
	}
	if (fncd <= 0) {
		/*
		 *  カーネルのサービスコールの場合
		 */
		if (fncd < TMIN_FNCD) {
			ercd = E_RSFN;
			goto error_exit;
		}
		ソフトウェア割込みによりシステム状態が変化する場合には，
							ソフトウェア割込み前のシステム状態に戻す
		ercd = (*(svc_table[-fncd]))(パラメータリスト);
		ソフトウェア割込みからリターン後のシステム状態が，
							この時点でのシステム状態になるよう準備する
	}
	else {
		/*
		 *  拡張サービスコールの場合 → 次に検討
		 */
	}

  error_exit:
	if (非特権モードからの呼び出し) {
		スタックを実行状態のタスクのユーザスタックに戻す
	}
	return(ercd);
}
----------------------------------------

ここで操作対象とする必要のある「システム状態」は，全割込みロックフラグ，
CPUロックフラグ，割込み優先度マスクである．ディスパッチ禁止フラグとタス
ク例外禁止フラグは，ターゲット非依存に管理されているため，ここでの操作
対象とする必要はない．

ターゲットプロセッサによっては，スタックの切換え処理はハードウェアによっ
て行われるため，ソフトウェアで行う必要はない．

なお，ここでは，カーネルのサービスコールと拡張サービスコールを同一の入
口としているが，異なるソフトウェア割込みを用いて区別する方法もある．

●拡張サービスコールの管理と出入口処理

拡張サービスコールの呼出し処理のために，機能コードをインデックスとする
拡張サービスコール初期化ブロックのテーブル（svcinib_table）を用意し，そ
こから取り出した処理ルーチンを呼び出す．このテーブルは，コンフィギュレー
タによって生成する．

コンフィギュレータが生成する拡張サービスコール初期化ブロックのテーブル
は，次のように定義する．また，コンフィギュレータは，拡張サービスコール
の機能コードの最大値を入れた定数tmax_fncdを生成する．

----------------------------------------
/*
 *  拡張サービスコールルーチンのデータ型
 */
typedef ER_UINT	(*EXTSVC)(intptr_t par1, intptr_t par2, intptr_t par3,
								intptr_t par4, intptr_t par5, ID cdmid);
----------------------------------------
/*
 *  拡張サービスコール初期化ブロック
 */
typedef struct extended_service_call_initialization_block {
	EXTSVC		svcrtn;		/* 拡張サービスコールの先頭番地 */
	SIZE		stksz;		/* 拡張サービスコールで使用するスタックサイズ */
} SVCINIB;

/*
 *  拡張サービスコールの機能コードの最大値（kernel_cfg.c）
 */
extern const FN	tmax_fncd;

/*
 *  拡張サービスコール初期化ブロックのエリア（kernel_cfg.c）
 */
extern const SVCINIB	svcinib_table[];
----------------------------------------

このテーブルを用いて拡張サービスコールへ分岐する処理（svc_entryの一部分）
は次の通り．前述の通り，タスクコンテキストから呼ばれた場合には，拡張サー
ビスコールルーチンを呼び出す前にsvclevelをインクリメントし，rundomをカー
ネルドメインのビットパターン（TACP_KERNEL）に設定する．拡張サービスコー
ルルーチンからリターンしてきたら，svclevelとrundomを元の値に戻す．

----------------------------------------
	else {
		/*
		 *  拡張サービスコールの場合
		 */
		const SVCINIB	*p_svcinib;
		ID				cdmid;

		if (fncd > tmax_fncd) {
			ercd = E_RSFN;
			goto error_exit;
		}
		p_svcinib = &svcinib_table[fncd - 1];
		if (p_svcinib->svcrtn == NULL) {
			ercd = E_RSFN;
			goto error_exit;
		}

		if (非タスクコンテキストからの呼出し) {
			ソフトウェア割込みによりシステム状態が変化する場合には，
							ソフトウェア割込み前のシステム状態に戻す
			LOG_EXTSVC_ENTER(fncd, パラメータリスト, TDOM_KERNEL);
			ercd = (*(p_svcinib->svcrtn))(パラメータリスト, TDOM_KERNEL);
			LOG_EXTSVC_LEAVE(fncd, ercd);
			ソフトウェア割込みからリターン後のシステム状態が，
							この時点でのシステム状態になるよう準備する
		}
		else {
			（少なくとも）カーネル管理の割込みを禁止した状態にする
			if (p_runtsk->svclevel == UINT8_MAX) {
				ercd = E_SYS;
				ソフトウェア割込みからリターン後のシステム状態が，
						ソフトウェア割込み前のシステム状態になるよう準備する
			}
			else if (システムスタック領域の残りサイズ < p_svcinib->stksz) {
				ercd = E_NOMEM;
				ソフトウェア割込みからリターン後のシステム状態が，
						ソフトウェア割込み前のシステム状態になるよう準備する
			}
			else {
				if (rundom == TACP_KERNEL) {
					cdmid = TDOM_KERNEL;
				}
				else {
					cdmid = DOMID(p_runtsk->p_tinib->p_dominib);
				}
				p_runtsk->svclevel++;
				rundom = TACP_KERNEL;
				ソフトウェア割込み前のシステム状態に戻す
				LOG_EXTSVC_ENTER(fncd, パラメータリスト, cdmid);
				ercd = (*(p_svcinib->svcrtn))(パラメータリスト, cdmid);
				LOG_EXTSVC_LEAVE(fncd, ercd);
				ソフトウェア割込みからリターン後のシステム状態が，
							この時点でのシステム状態になるよう準備する
				（少なくとも）カーネル管理の割込みを禁止した状態にする
				if (--p_runtsk->svclevel == 0U) {
					rundom = p_runtsk->p_tinib->p_dominib->domptn;
				}
			}
		}
	}
----------------------------------------

タスクコンテキストからの拡張サービスコール呼出しの前後に，（少なくとも）
カーネル管理の割込みを禁止するのは，ディスパッチャにおいてsvclevelを参
照してrundomを値を決定するために，svclevelとrundomの更新をアトミックに
行う方が安全であるためである．

○待ち禁止状態の実装

●待ち禁止状態を示すフラグ

待ち禁止状態を実現するために，TCBにwaifbdフィールドを追加する．waifbd
フィールドは，make_dormantの中でFALSEに初期化する．

----------------------------------------
typedef struct task_control_block {
	………
	BIT_FIELD_BOOL	waifbd : 1;		/* 待ち禁止状態 */
	………
} TCB;
----------------------------------------
void
make_dormant(TCB *p_tcb)
{
	………
	p_tcb->waifbd = false;
	………
}
----------------------------------------

●待ち禁止状態への遷移

タスクを待ち禁止状態へ遷移させるサービスコール（dis_wai/idis_wai）は，
対象タスクがタスク例外処理マスク状態である場合にのみ，対象タスクを待ち
禁止状態に遷移させる．すなわち，対象タスクのwaifbdフィールドをtrueに設
定する．

対象タスクがユーザタスクの場合には，特権モードを実行している間がタスク
例外処理マスク状態であり，特権モードを実行しているか否かを判別する関数
を用いて，タスク例外処理マスク状態であることを判別できる．

対象タスクがシステムタスクの場合には，拡張サービスコールを実行している
間がタスク例外処理マスク状態であり，TCB中のsvclevelフィールドを用いて，
タスク例外処理マスク状態であることを判別できる．

タスクコンテキストで実行する場合（dis_wai）
----------------------------------------
	if (p_tcb->p_tinib->p_dominib == &dominib_kernel) {
		/* 対象タスクがシステムタスク */
		if (p_tcb->svclevel > 0) {
			p_tcb->waifbd = true;
		}
	}
	else if (p_tcb == p_runtsk) {
		/* 対象タスクが自タスク */
		p_tcb->waifbd = true;
	}
	else {
		/* 対象タスクが他タスク */
		if (t_sense_priv(p_tcb)) {
			p_tcb->waifbd = true;
		}
	}
----------------------------------------

非タスクコンテキストで実行する場合（idis_wai）
----------------------------------------
	if (p_tcb->p_tinib->p_dominib == &dominib_kernel) {
		/* 対象タスクがシステムタスク */
		if (p_tcb->svclevel > 0) {
			p_tcb->waifbd = true;
		}
	}
	else if (p_tcb == p_runtsk) {
		/* 対象タスクが実行状態のタスク */
		if (i_sense_priv_runtsk()) {
			p_tcb->waifbd = true;
		}
	}
	else {
		/* 対象タスクが実行状態でないタスク */
		if (i_sense_priv(p_tcb)) {
			p_tcb->waifbd = true;
		}
	}
----------------------------------------

●待ち禁止状態の解除

ソフトウェア割込みの出口で，svclevelが0である場合には，waifbdフィールド
をfalseにする．svclevelのディクリメントと，waifbdフィールドをfalseにす
る処理は，アトミックに行う．

システムタスクが，ソフトウェア割込みによってカーネルのサービスコールを
呼び出した場合には，タスク例外処理マスク状態とはならないために，
waifbdフィールドをfalseにする必要はないが，falseにしても支障がないため，
条件判断を省略できる．

以上を実現するコードは以下の通り．

----------------------------------------
ER_UINT
svc_entry(FN fncd, パラメータリスト)
{
	ER_UINT		ercd;

	if (fncd <= 0) {
		/*
		 *  カーネルのサービスコールの場合
		 */
		………
+		（少なくとも）カーネル管理の割込みを禁止した状態にする
+		if (タスクコンテキストからの呼出し && p_runtsk->svclevel == 0U) {
+			p_runtsk->waifbd = false;
+		}
	}
	else {
		/*
		 *  拡張サービスコールの場合
		 */
		………
		if (非タスクコンテキストからの呼出し) {
			………
		}
		else {
			………
			else {
				………
				（少なくとも）カーネル管理の割込みを禁止した状態にする
				if (--p_runtsk->svclevel == 0U) {
					rundom = p_runtsk->p_tinib->p_dominib->domptn;
+					p_runtsk->waifbd = false;
				}
			}
		}
	}

  error_exit:
	return(ercd);
}

----------------------------------------

waifbdをfalseにする前に，（少なくとも）カーネル管理の割込みを禁止するの
は，waifbdをfalseにした後に割込みとタスク切換えが発生し，切り換わった後
のタスクから元のタスクを待ち禁止状態へ遷移させると，waifbdがtrueになっ
てしまい，waifbdがtrueのまま非特権モードに戻ってしまうためである．

●待ち禁止の実現

待ち状態に入るすべてのサービスコールで，waifbdフィールドを参照し，true
であれば，待ち状態に入らずに，E_RLWAIエラーですぐにリターンするように修
正する．waifbdフィールドの参照は，待ち状態に入る直前（tmoutがTMO_POLで
ある場合をチェックした後）に行う．例えば，tslp_tskであれば，次の通りに
修正する．

----------------------------------------
ER
tslp_tsk(void)
{
	………
	if (p_runtsk->wupque) {
		p_runtsk->wupque = false;
		ercd = E_OK;
	}
	else if (tmout == TMO_POL) {
		ercd = E_TMOUT;
	}
+	else if (p_runtsk->waifbd) {
+		ercd = E_RLWAI;
+	}
	else {
		p_runtsk->tstat = (TS_WAITING | TS_WAIT_SLP);
		make_wait_tmout(&winfo, &tmevtb, tmout);
		LOG_TSKSTAT(p_runtsk);
		dispatch();
		ercd = winfo.wercd;
	}
	………
}
----------------------------------------

○タスク例外処理機能の実装

●タスク例外処理ルーチンの実行開始条件とシステム状態（仕様の確認）

タスク例外処理ルーチンは，次の7つの条件が揃った場合に実行が開始される．

　・タスク例外処理許可状態である
　・保留例外要因が0でない
　・タスクが実行状態である
　・タスクコンテキストが実行されている
　・割込み優先度マスク全解除状態である
　・CPUロック状態でない
　・タスク例外処理マスク状態でない

また，タスク例外処理ルーチンの起動／リターン時のシステム状態に関する仕
様は次の通りである．

				CPUロック		割込み優先度	ディスパッチ
				フラグ			マスク			禁止フラグ
------------------------------------------------------------
【タスク例外処理ルーチン】
実行開始条件	解除			全解除			任意
実行開始時処理	そのまま		そのまま		そのまま
リターン前		原則解除(*1)	原則全解除(*1)	元に戻す
リターン時処理	解除する		全解除する		元に戻す(*4)
------------------------------------------------------------

タスク例外処理ルーチンからのリターン時にカーネルによって行われるシステ
ム状態を元に戻す処理(*4)は，タスクにそれぞれの状態変更を許可している場
合にのみ行われる．ここでカーネルは，元のシステム状態に戻す処理を実現す
るために，元のシステム状態をユーザスタック上に保存する．アプリケーショ
ンがユーザスタック上に保存されたシステム状態を書き換えた場合，タスク例
外処理ルーチンからのリターン時に，書き換えた後のシステム状態に変更され
る（すなわち，元に戻されるとは限らない）．

また，タスクに状態変更を許可していない場合で，タスク例外処理ルーチン中
で拡張サービスコールを用いて状態を変更した場合，カーネルは元の状態に戻
さない．このことから，タスク例外処理ルーチンからの終了前に，割込み優先
度マスクとディスパッチ禁止フラグを元の状態に戻すのは，アプリケーション
の責任とする．

●システムタスクに対するタスク例外処理ルーチンの呼出し処理

システムタスクに対するタスク例外処理ルーチンの実行開始は，基本的には，
ASPカーネルと同じ処理となる．そのため，ASPカーネルのcall_texrtnおよび
calltexはそのまま使えるが，システムタスクに対する処理であることを明確に
するために，それぞれcall_texrtn_staskとcalltex_stackに名称変更する．

●ユーザタスクに対するタスク例外処理ルーチンの呼出し処理

ユーザタスクが非特権モードを実行中にタスク例外処理ルーチンの実行開始条
件が揃うことはないため，ユーザタスクに対するタスク例外処理ルーチンの実
行開始は，特権モードから非特権モードにリターンする際に行う．具体的には，
特権モードからのリターンにより，タスク例外処理ルーチンの実行が開始する
ように，リターン前に準備する．

タスク例外処理ルーチンの実行を開始するための準備処理の内容は次の通り．

　・次の情報をユーザスタックに積む．
	　- タスク例外処理ルーチンからの戻り番地（＝特権モードからリターン
		する際の戻り番地）
	　- 非特権モードでのスクラッチレジスタとフラグの値（スクラッチレジ
		スタ以外のレジスタは，タスク例外処理ルーチンにより破壊されない
		ため，保存する必要がない）
	　- ディスパッチ禁止フラグ
　・タスクの保留例外要因と拡張情報が，タスク例外処理ルーチンへのパラメー
	タとして渡るように準備する．
　・タスク例外処理ルーチンからのリターンにより，call_ret_texの番地へ分
	岐するように準備する．
　・特権モードからのリターンによって，タスク例外処理ルーチンへ分岐する
	ように準備する．

これらの準備処理と関連する処理を行うために，ターゲット依存部で，次のよ
うな内容のprepare_texrtn_utask関数を用意する．

----------------------------------------
void
prepare_texrtn_utask()
{
	TEXPTN	texptn = p_runtsk->texptn;

	ユーザスタックに，タスク例外処理ルーチンからの戻り番地を積む
	ユーザスタックに，非特権モードでのスクラッチレジスタとフラグの値を積む
	ユーザスタックに，disdspの値を積む
	タスクの保留例外要因（p_runtsk->texptn）と
			拡張情報（p_runtsk->p_tinib->exinf）が，
			タスク例外処理ルーチンへのパラメータとして渡るように準備する
	タスク例外処理ルーチンからのリターンにより，
			call_ret_texの番地へ分岐するように準備する … (*b)
	特権モードからのリターンによって，タスク例外処理ルーチン
			（p_runtsk->p_tinib->texrtn）へ分岐するように準備する
	p_runtsk->enatex = false;
	p_runtsk->texptn = 0U;
	LOG_TEX_ENTER(p_runtsk, texptn);
}
----------------------------------------

このコードでは省略しているが，タスク例外処理ルーチンからの戻り番地や非
特権モードでのスクラッチレジスタとフラグの値を取り出すために，特権モー
ドからリターンする直前のシステムスタックポインタの値を，
prepare_texrtn_utask関数のパラメータとして渡す必要がある．

関数からのリターン時に，リターン先の番地などの情報をユーザスタックから
取り出すターゲットの場合には，(*b)の処理においてそれらの情報をユーザス
タックに積むことになる．

call_ret_texは，以下の内容の関数で，ターゲット依存部で用意する．

----------------------------------------
void
call_ret_tex(void)
{
	(void) ret_tex();
	(void) ext_tsk();
	(void) ext_ker();
	assert(0);
}
----------------------------------------

call_ret_texは，すべての保護ドメインから呼び出せる共有ライブラリとする．
そのため，ret_tex，ext_tsk，ext_kerを，ソフトウェア割込みを用いて呼び出
すコードとしなければならない．ret_texはサービスコールでないため，呼出し
方法には注意が必要である．ret_texについては後述する

また，ret_texでユーザスタックを操作するため，ret_texを呼び出す前にスタッ
クを使わないように実装するのが望ましい．そのため，ret_texを呼び出す部分
までは，アセンブリ言語で実装すべきである（それ以降はC言語で実装してもよ
い）．ret_texを呼び出す前にスタックを使わないようにできない場合には，
ret_texの中でその分を補正する必要がある．

なお，ret_texの後にext_tskを呼び出すのは，ret_texでエラーが発生した場合
の対策である（ret_texは正常に実行された場合にはリターンしない）．さらに，
ext_tskの後にext_kerを呼び出すのは，call_ret_tex が非タスクコンテキスト
から呼ばれた場合の対策である．非タスクコンテキストはカーネルドメインな
ので，ext_kerがエラーになることはない（はずである）．

●タスク例外処理ルーチンの実行開始が必要なタイミング

タスク例外処理ルーチンは，前述の7つの条件が揃った場合に実行開始すべきで
あるため，7つの条件のいずれかを新たに満たすようになる可能性のあるタイミ
ングで，タスク例外処理ルーチンの実行開始処理が必要になる．

以下では，7つの条件のいずれかを新たに満たすようになるタイミングについて
検討する．

(1) タスク例外処理許可状態である

タスク例外処理の許可（ena_tex）によって，タスク例外処理許可状態になる．
また，タスク例外処理ルーチンからのリターンによっても，タスク例外処理許
可状態になる．

(2) 保留例外要因が0でない

タスク例外処理の要求（ras_tex，iras_tex）によって，タスクの保留例外要因
が0でなくなる．ただし，非タスクコンテキストからのタスク例外処理の要求
（iras_tex）では，(4)の条件が満たされないため，タスク例外処理ルーチンの
実行開始は必要ない．

(3) タスクが実行状態である

タスクディスパッチャにより，切換え後のタスクが実行状態になる．

タスクディスパッチの中で，切換え後のタスクを実行状態にする処理は，
dispatch_r，ret_int_r，ret_exc_r，start_stask_r，start_utask_rの5つある．
この中でstart_stask_rとstart_utask_rに関しては，タスクの実行開始直後は
タスク例外処理禁止状態であり（自タスクがena_texするまでは，タスク例外が
許可されない），(1)の条件が満たされないため，タスク例外処理ルーチンの実
行開始は必要ない．

(4) タスクコンテキストが実行されている

割込みハンドラおよびCPU例外ハンドラからのリターンによって，タスクコンテ
キストに戻る場合がある．

(5) 割込み優先度マスク全解除状態である

割込み優先度マスクの変更（chg_ipm）によって，割込み優先度マスクが全解除
になる場合がある．

また，タスクの終了およびタスク例外処理ルーチンからのリターンによって，
割込み優先度マスクが全解除される．さらに，割込みハンドラからのリターン
およびCPU例外ハンドラからのリターンによっても，割込み優先度マスクが全解
除される場合がある．

ただし，タスクの終了時については，タスクの終了後は別のタスクへ切り換わ
るため，切り換わった後のタスクに対するタスク例外処理ルーチンの実行開始
を，タスクディスパッチ後の処理で行えばよい．

(6) CPUロック状態でない

CPUロック状態の解除（unl_cpu，iunl_cpu）によって，CPUロック状態でなくな
る．ただし，非タスクコンテキストでのCPUロック状態の解除（iunl_cpu）では，
(4)の条件が満たされないため，タスク例外処理ルーチンの実行開始は必要ない．

また，タスクの終了，タスク例外処理ルーチンからのリターン，割込みハンド
ラからのリターン，割込みサービスルーチンからのリターン，タイムイベント
ハンドラからのリターンによって，CPUロック状態でなくなる．さらに，CPU例
外ハンドラからのリターンによって，CPUロック状態でなくなる場合もある．

ただし，割込みサービスルーチンおよびタイムイベントハンドラからのリター
ンについては，リターン後も非タスクコンテキストの実行が続き，(4)の条件が
満たされないため，タスク例外処理ルーチンの実行開始は必要ない．

タスクの終了時については，タスクの終了後は別のタスクへ切り換わるため，
切り換わった後のタスクに対するタスク例外処理ルーチンの実行開始を，ディ
スパッチ後の処理で行えばよい．

(7) タスク例外処理マスク状態でない

(7-1) システムタスクの場合：拡張サービスコールを実行していない

拡張サービスコールからのリターンによって，この条件を満たす場合がある．

(7-2) ユーザタスクの場合：特権モードで実行していない

ソフトウェア割込み（サービスコールおよび拡張サービスコールの呼出し）か
らのリターンによって，この条件を満たす場合がある．

以上より，重複するケースを考慮すると，タスク例外処理ルーチンの実行開始
が必要になる可能性があるのは，以下の処理である．

(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
(e) 割込みハンドラの出口処理 … (4)(5)(6)
(f) CPU例外ハンドラの出口処理 … (4)(5)(6)
(g) 割込み優先度マスクの変更（chg_ipm）… (5)
(h) CPUロック状態の解除（unl_cpu）… (6)
(i) 拡張サービスコールの出口処理 … (7-1)(7-2)
(j) サービスコールの出口処理（ユーザタスクの場合）… (7-2)

(h)に関しては，ASPカーネルと異なり，タスク例外処理ルーチンの実行開始が
必要になる．

●タスク例外処理ルーチンの実行開始処理

ここでは，前の節で検討したタスク例外処理ルーチンの実行開始が必要なタイ
ミングのそれぞれについて，実行開始処理の実装方法について述べる．

(a) タスク例外処理の許可（ena_tex）

ena_texがユーザタスクから呼び出された場合には，サービスコールの出口処理
でタスク例外処理ルーチンの実行を開始するため，ena_texではタスク例外処理
許可状態にするだけでよい．

ena_texがシステムタスクから呼び出された場合には，自タスクに対して「タス
クが実行状態である」「タスクコンテキストが実行されている」の2条件は満た
されており，「タスク例外処理許可状態である」の条件はena_texの処理により
満たされる．また，「CPUロック状態でない」の条件はena_texの入口でチェッ
クしている．そのため，自タスクがシステムタスクであり，「保留例外要因が
0でない」「割込み優先度マスク全解除状態である」「タスク例外処理マスク状
態でない」の3条件が満たされている場合には，タスク例外処理ルーチンを呼び
出す．

ena_texの本体の処理（エラー処理を除く）は次の通り．

----------------------------------------
		p_runtsk->enatex = true;
!		if (p_runtsk->texptn != 0U && ipmflg
+				 && rundom == TACP_KERNEL && p_runtsk->svclevel == 0U) {
!			call_texrtn_stask();
		}
----------------------------------------

ここで，(rundom == TACP_KERNEL && p_runtsk->svclevel == 0U)は，システム
タスクから呼び出された場合にのみ成立する．この条件式で，呼び出したタス
クがシステムタスクであり，タスク例外処理マスク状態でないことをチェック
している．

(b) タスク例外処理ルーチンの出口処理

タスク例外処理ルーチンの出口処理では，タスク例外処理許可状態になり，割
込み優先度マスクが全解除され，CPUロック状態が解除される．そのため，他の
条件が満たされている場合には，タスク例外処理ルーチンの実行を開始する必
要がある．

システムタスクの場合，タスク例外処理ルーチンの出口処理
（call_texrtn_staskの後半）では，自タスクに対して「タスクが実行状態であ
る」「タスクコンテキストが実行されている」「タスク例外処理マスク状態で
ない」の3条件は満たされており，「タスク例外処理許可状態である」「割込み
優先度マスク全解除状態である」「CPUロック状態でない」の3条件が出口処理
で満たされる．そのため，「保留例外要因が0でない」が満たされている場合，
タスク例外処理ルーチンを呼び出す必要がある．

call_texrtn_stask（ASPカーネルのcall_texrtn）の中には，「保留例外要因が
0でない」が満たされれば再度タスク例外処理ルーチンの実行を開始する処理が
含まれており，新たなコードは必要ない．

ユーザタスクの場合には，ret_texに同様の処理を含める必要がある．これにつ
いては，後で検討する．

(c) タスク例外処理の要求（ras_tex）

ras_texがユーザタスクから呼び出された場合には，サービスコールの出口処理
でタスク例外処理ルーチンの実行を開始するため，ras_texではtexptnを更新す
るだけでよい．

ras_texがシステムタスクから呼び出された場合で，対象タスクが自タスクの場
合には，自タスクに対して「タスクが実行状態である」「タスクコンテキスト
が実行されている」の2条件は満たされており，「保留例外要因が0でない」の
条件はras_texの処理により満たされる（ras_texのパラメータrasptnが0の場合
はエラーとなるため）．また，「CPUロック状態でない」の条件はras_texの入
口でチェックしている．そのため，対象タスクが自タスクであり，自タスクが
システムタスクであり，「タスク例外処理許可状態である」「割込み優先度マ
スク全解除状態である」「タスク例外処理マスク状態でない」の3条件が満たさ
れている場合には，タスク例外処理ルーチンを呼び出す．

ras_texの本体の処理（エラー処理を除く）は次の通り．

----------------------------------------
		p_tcb->texptn |= rasptn;
!		if (p_tcb == p_runtsk && p_runtsk->enatex && ipmflg
+					&& rundom == TACP_KERNEL && p_runtsk->svclevel == 0U) {
!			call_texrtn_stask();
		}
----------------------------------------

ここで，(rundom == TACP_KERNEL && p_runtsk->svclevel == 0U)は，システム
タスクから呼び出された場合にのみ成立する．この条件式で，呼び出したタス
クがシステムタスクであり，タスク例外処理マスク状態でないことをチェック
している．

(d-1) dispatch_r

dispatch_rからのリターン先のサービスコールが，ユーザタスクから呼び出さ
れたものである場合，言い換えると，ディスパッチ後のタスクがユーザタスク
である場合には，サービスコールの出口処理でタスク例外処理ルーチンの実行
を開始するため，dispatch_rではタスク例外処理ルーチンの実行を開始しない．

それに対して，ディスパッチ後のタスクがシステムタスクである場合には，
dispatch_rでタスク例外処理ルーチンの実行を開始する必要がある．

dispatch_rにおいては，dispatch_rからのリターン先のタスクに対して，「タ
スクが実行状態である」「タスクコンテキストが実行されている」の2条件は満
たされている．また， CPUロック状態でdispatch_rに来ることはないため，
「CPUロック状態でない」の条件も成立している．そのため，ディスパッチ後の
タスクがシステムタスクであり，「タスク例外処理許可状態である」「保留例
外要因が0でない」「割込み優先度マスク全解除状態である」「タスク例外処理
マスク状態でない」の4条件が満たされている場合には，タスク例外処理ルーチ
ンを呼び出す必要がある．

dispatch_rからタスク例外処理ルーチンを実行開始する処理は，次のように実
装する（calltex_staskを展開してある）．

----------------------------------------
void
dispatch(void)
{
	………

  dispatch_r:
	if (自タスクがユーザタスク) {
		ユーザスタックポインタをスタックから復帰する
	}
	スクラッチレジスタを除くすべてのレジスタをスタックから復帰する
#ifdef TOPPERS_SUPPORT_OVRHDR
	ovrtimer_start();					/* オーバランタイマの動作開始 */
#endif /* TOPPERS_SUPPORT_OVRHDR */
!	if (p_runtsk->enatex && p_runtsk->texptn != 0U && ipmflg
+					&& rundom == TACP_KERNEL && p_runtsk->svclevel == 0U) {
!		call_texrtn_stask();
	}
}
----------------------------------------

ここで，(rundom == TACP_KERNEL && p_runtsk->svclevel == 0U)は，
dispatchを呼び出したサービスコールが，システムタスクから呼び出された場
合にのみ成立する．この条件式で，呼び出したタスクがシステムタスクであり，
タスク例外処理マスク状態でないことをチェックしている．

(d-2) ret_int_r
(e) 割込みハンドラの出口処理

ASPカーネルでは，ret_int_rにおけるタスク例外処理ルーチンの実行開始処理
と，割込みハンドラの出口処理におけるタスク例外処理ルーチンの実行開始処
理は，1つのコードで実現している．そこで，この2つをまとめて検討する．

ret_int_rは，割込みハンドラの出口処理でディスパッチを行ったタスクが，実
行を再開する際の処理である．そのため，ret_int_rからのリターン先のタスク
に対して，「タスクが実行状態である」「タスクコンテキストが実行されてい
る」の2条件は満たされている．また， CPUロック状態でret_int_rに来ること
はないため，「CPUロック状態でない」の条件も成立している．そのため，「タ
スク例外処理許可状態である」「保留例外要因が0でない」「（割込みハンドラ
からのリターン後に）割込み優先度マスク全解除状態である」「（割込みハン
ドラからのリターン後に）タスク例外処理マスク状態でない」の4条件が満たさ
れている場合には，タスク例外処理ルーチンを呼び出す必要がある．

割込みハンドラからタスクへリターンする場合には，リターン先のタスクに対
して，「タスクが実行状態である」「タスクコンテキストが実行されている」
の2条件は満たされている．また，CPUロック状態で割込みハンドラが実行され
ることはないため，「CPUロック状態でない」の条件も成立している．そのため，
「タスク例外処理許可状態である」「保留例外要因が0でない」「（割込みハン
ドラからのリターン後に）割込み優先度マスク全解除状態である」「（割込み
ハンドラからのリターン後に）タスク例外処理マスク状態でない」の4条件が満
たされている場合には，タスク例外処理ルーチンを呼び出す必要がある．

割込みハンドラの呼出し前と呼出し後でこれらの条件が変化するのは，実行状
態のタスクが変化した時と，割込みハンドラ中でiras_texが呼び出された場合
である．割込みハンドラからはena_texとchg_ipmは呼び出せないため，「タス
ク例外処理許可状態である」と「（割込みハンドラからのリターン後に）割込
み優先度マスク全解除状態である」の2条件が変化することはない．

この2つの状況の内，実行状態のタスクが変化するケースは，ret_int_rで考慮
済みである．割込みハンドラ中でiras_texが呼び出された場合には，reqflgが
trueになるため，タスク例外処理ルーチンの実行開始処理は，reqflgがtrueの
時にのみ行えばよい．

以上の検討より，割込みハンドラの出口処理でタスク例外処理ルーチンを実行
開始する処理は，次のように実装する（calltex_staskを展開してある）．

----------------------------------------
void
<割込みの出入口処理>(void)
{
	………

	if (タスクコンテキストで割込み発生) {
		………
		if (reqflg) {
			………

			  ret_int_r:
				if (自タスクがユーザタスク) {
					ユーザスタックポインタをスタックから復帰する
				}
				スクラッチレジスタを除くすべてのレジスタをスタックから復帰する
			}
#ifdef TOPPERS_SUPPORT_OVRHDR
			ovrtimer_start();			/* オーバランタイマの動作開始 */
#endif /* TOPPERS_SUPPORT_OVRHDR */
			if (p_runtsk->enatex && p_runtsk->texptn != 0U && ipmflg) {
+				if (rundom == TACP_KERNEL) {
+					if (p_runtsk->svclevel == 0U) {
!						call_texrtn_stask();
+					}
+				}
+				else {
+					if (!i_sense_priv_runtsk()) {
+						prepare_texrtn_utask();
+					}
+				}
			}
		}
#ifdef TOPPERS_SUPPORT_OVRHDR
		else {
			ovrtimer_start();			/* オーバランタイマの動作開始 */
		}
#endif /* TOPPERS_SUPPORT_OVRHDR */
	}
	割込み処理からのリターン後に，CPUロック解除状態に戻るように準備する
	スクラッチレジスタをスタックから復帰する
	割込み処理からのリターン
}
----------------------------------------

ここで，(rundom == TACP_KERNEL)は，ユーザタスクが拡張サービスコールを実
行している時も成立するが，その後の(p_runtsk->svclevel == 0U)が成立する
のは，リターン先のタスクがシステムタスクの場合のみである．一方，リター
ン先のタスクがユーザタスクで，(rundom == TACP_KERNEL)の時は，タスク例外
処理マスク状態であり，タスク例外処理ルーチンの実行開始は必要ない．

(d-3) ret_exc_r
(f) CPU例外ハンドラの出口処理

カーネル管理のCPU例外ハンドラの出口処理は，割込みハンドラの出口処理と同
様である．

カーネル管理外のCPU例外ハンドラの中では，実行状態のタスクが変化すること
はなく，iras_texやena_texを呼び出すこともできないため，タスク例外処理ルー
チンの実行開始条件が新たに満たされることはなく，タスク例外処理ルーチン
の実行開始は必要ない．

このことから，CPU例外ハンドラの出入口処理についても，割込みハンドラの出
入口処理と同様に，次のように修正すればよい．

----------------------------------------
void
<CPU例外の出入口処理>(void)
{
	………

				  ret_exc_r:
					if (自タスクがユーザタスク) {
						ユーザスタックポインタをスタックから復帰する
					}
					スクラッチレジスタを除くすべてのレジスタを
												スタックから復帰する
				}
#ifdef TOPPERS_SUPPORT_OVRHDR
				ovrtimer_start();		/* オーバランタイマの動作開始 */
#endif /* TOPPERS_SUPPORT_OVRHDR */
				if (p_runtsk->enatex && p_runtsk->texptn != 0U && ipmflg) {
+					if (rundom == TACP_KERNEL) {
+						if (p_runtsk->svclevel == 0U) {
!							call_texrtn_stask();
+						}
+					}
+					else {
+						if (!i_sense_priv_runtsk()) {
+							prepare_texrtn_utask();
+						}
+					}
				}
			}
#ifdef TOPPERS_SUPPORT_OVRHDR
			else {
				ovrtimer_start();		/* オーバランタイマの動作開始 */
			}
#endif /* TOPPERS_SUPPORT_OVRHDR */
		}
		CPU例外処理からのリターン後に，CPUロック解除状態に戻るように準備する
	}
	スクラッチレジスタをスタックから復帰する
	CPU例外処理からのリターン
}
----------------------------------------

(g) 割込み優先度マスクの変更

chg_ipmがユーザタスクから呼び出された場合には，サービスコールの出口処理
でタスク例外処理ルーチンの実行を開始するため，chg_ipmでは割込み優先度マ
スクを変更するだけでよい．

chg_ipmがシステムタスクから呼び出された場合には，自タスクに対して「タス
クが実行状態である」「タスクコンテキストが実行されている」の2条件は満た
されている．また，「CPUロック状態でない」の条件はchg_ipmの入口でチェッ
クしている．そのため，自タスクがシステムタスクであり，パラメータintpri
がTIPM_ENAALLであり，「タスク例外処理許可状態である」「保留例外要因が0
でない」「タスク例外処理マスク状態でない」の3条件が満たされている場合に
は，タスク例外処理ルーチンを呼び出す．

chg_ipmの関連部分の処理は次の通り．

----------------------------------------
	t_set_ipm(intpri);
	if (intpri == TIPM_ENAALL) {
		ipmflg = true;
		ここにタスク切換え処理が入る
!		if (p_runtsk->enatex && p_runtsk->texptn != 0U
+				&& rundom == TACP_KERNEL && p_runtsk->svclevel == 0U) {
!			call_texrtn_stask();
		}
	}
----------------------------------------

ここで，(rundom == TACP_KERNEL && p_runtsk->svclevel == 0U)は，システム
タスクから呼び出された場合にのみ成立する．この条件式で，呼び出したタス
クがシステムタスクであり，タスク例外処理マスク状態でないことをチェック
している．

(h) CPUロックの解除（unl_cpu）

unl_cpuがユーザタスクから呼び出された場合には，サービスコールの出口処理
でタスク例外処理ルーチンの実行を開始するため，unl_cpuではCPUロック状態
を解除するだけでよい．

unl_cpuがシステムタスクから呼び出された場合には，unl_cpuでタスク例外処
理ルーチンの実行を開始する必要がある．これは，ASPカーネルにはなかった処
理である．

unl_cpuでタスク例外処理ルーチンの実行が開始される状況は次のような場合に
起こる．拡張サービスコールルーチン中で，ras_texにより自タスクに対してタ
スク例外を要求した後，loc_cpuでCPUロック状態にしてから拡張サービスコー
ルから戻ると，拡張サービスコールの出口処理ではタスク例外処理ルーチンの
実行は開始されない．その後のunl_cpuの発行により，7つの条件が揃う場合が
ある．

整理すると，unl_cpuがシステムタスクから呼び出された場合には，自タスクに
対して「タスクが実行状態である」「タスクコンテキストが実行されている」
の2条件は満たされており，「CPUロック状態でない」の条件はunl_cpuの処理に
より満たされる．そのため，自タスクがシステムタスクであり，「タスク例外
処理許可状態である」「保留例外要因が0でない」「割込み優先度マスク全解除
状態である」「タスク例外処理マスク状態でない」の4条件が満たされている場
合には，タスク例外処理ルーチンを呼び出す必要がある．

----------------------------------------
SYSCALL ER
unl_cpu(void)
{
	………
	if (t_sense_lock()) {
+		if (p_runtsk->enatex && p_runtsk->texptn != 0U && ipmflg
+					&& rundom == TACP_KERNEL && p_runtsk->svclevel == 0U) {
+			call_texrtn_stask();
+		}
		t_unlock_cpu();
	}
	………
}
----------------------------------------

ここで，(rundom == TACP_KERNEL && p_runtsk->svclevel == 0U)は，システム
タスクから呼び出された場合にのみ成立する．この条件式で，呼び出したタス
クがシステムタスクであり，タスク例外処理マスク状態でないことをチェック
している．

(i) 拡張サービスコールの出口処理

タスクから呼び出された拡張サービスコールの出口処理においては，リターン
先のタスクに対して，「タスクが実行状態である」「タスクコンテキストが実
行されている」の2条件は満たされている．

「タスク例外処理マスク状態でない」ことは，svclevelが0になることでチェッ
クできる．よって，svclevelが0になり，残りの「タスク例外処理許可状態であ
る」「保留例外要因が0でない」「割込み優先度マスク全解除状態である」
「CPUロック状態でない」の4条件が成立していれば，タスク例外処理ルーチン
を実行開始する必要がある．

----------------------------------------
	else {
		/*
		 *  拡張サービスコールの場合
		 */
		………

			else {
				if (rundom == TACP_KERNEL) {
					cdmid = TDOM_KERNEL;
				}
				else {
					cdmid = DOMID(p_runtsk->p_tinib->p_dominib);
				}
				p_runtsk->svclevel++;
				rundom = TACP_KERNEL;
				ソフトウェア割込み前のシステム状態に戻す
				LOG_EXTSVC_ENTER(fncd, パラメータリスト, cdmid);
				ercd = (*(p_svcinib->svcrtn))(パラメータリスト, cdmid);
				LOG_EXTSVC_LEAVE(fncd, ercd);
				ソフトウェア割込みからリターン後のシステム状態が，
							この時点でのシステム状態になるよう準備する
				（少なくとも）カーネル管理の割込みを禁止した状態にする
				if (--p_runtsk->svclevel == 0U) {
					rundom = p_runtsk->p_tinib->p_dominib->domptn;
					p_runtsk->waifbd = false;
+
+					/*
+					 *  タスク例外処理ルーチンの実行開始処理
+					 */
+					if (p_runtsk->enatex && p_runtsk->texptn != 0U && ipmflg
+								&& 拡張サービスコールからのリターン後の
+									システム状態がCPUロック状態でない) {
+						if (rundom == TACP_KERNEL) {
+							この時点でのシステム状態を，拡張サービスコール
+									からリターン後のシステム状態に対して，
+									CPUロック状態にしたシステム状態に変更する
+							call_texrtn_stask();
+							ソフトウェア割込みからリターン後のシステム状態が，
+									CPUロック解除状態，割込み優先度マスク
+									全解除状態になるよう準備する … (*1)
+						}
+						else {
+							prepare_texrtn_utask();
+						}
+					}
				}
			}
		}
	}
----------------------------------------

(*1)において，ソフトウェア割込みリターン後のシステム状態を割込み優先度
マスク全解除状態にする処理は，ほとんどの場合に不要である．なぜなら，
call_texrtn_staskを呼ぶ前の時点で，ソフトウェア割込みリターン後のシステ
ム状態は割込み優先度マスク全解除状態になっており（ipmflgがtrueの時しか
call_texrtn_staskを呼ばないため），call_texrtn_staskから戻る時点では割
込み優先度マスク全解除状態になっているためである．

(j) サービスコールの出口処理（ユーザタスクの場合）

ユーザタスクから呼び出されたサービスコールの出口処理においては，戻り先
のタスクに対して，「タスクが実行状態である」「タスクコンテキストが実行
されている」の2条件は満たされている．

「タスク例外処理マスク状態でない」の条件は，svclevelが0であれば，リター
ン後に非特権モードに戻ることから，svclevelが0であることでチェックできる．
よって，svclevelが0であり，残りの「タスク例外処理許可状態である」「保留
例外要因が0でない」「割込み優先度マスク全解除状態である」「CPUロック状
態でない」の4条件が成立していれば，タスク例外処理ルーチンを実行開始する
必要がある．

----------------------------------------
ER_UINT
svc_entry(FN fncd, パラメータリスト)
{
	ER_UINT		ercd;

	if (fncd <= 0) {
		/*
		 *  カーネルのサービスコールの場合
		 */
		if (fncd < TMIN_FNCD) {
			ercd = E_RSFN;
			goto error_exit;
		}
		ソフトウェア割込みによりシステム状態が変化する場合には，
							ソフトウェア割込み前のシステム状態に戻す
		ercd = (*(svc_table[-fncd]))(パラメータリスト);
		ソフトウェア割込みからリターン後のシステム状態が，
							この時点でのシステム状態になるよう準備する
		（少なくとも）カーネル管理の割込みを禁止した状態にする
		if (タスクコンテキストからの呼出し && p_runtsk->svclevel == 0U) {
			p_runtsk->waifbd = false;
+
+			/*
+			 *  タスク例外処理ルーチンの実行開始処理
+			 */
+			if (p_runtsk->enatex && p_runtsk->texptn != 0U && ipmflg
+								&& サービスコールからのリターン後の
+									システム状態がCPUロック状態でない) {
+				assert(rundom != TACP_KERNEL);
+				prepare_texrtn_utask();
+			}
		}
	}
	………
}
----------------------------------------

なお，システムタスクから呼び出されたサービスコールの出口処理において，
(p_runtsk->texptn != 0U && p_runtsk->enatex && CPUロック状態でない)の条
件が満たされることはない．なぜなら，システムタスクでこれらの条件が満た
されれば，満たされた時点でタスク例外処理ルーチンが実行開始するためであ
る．念のため，システムタスクでないことを，assertにより確認している．

●ユーザタスクのタスク例外処理ルーチンからのリターン

ユーザタスクのタスク例外処理ルーチンからリターンすると，call_ret_texへ
処理が移る．call_ret_texは，ソフトウェア割込みによりret_texを呼び出す．

ret_texは，サービスコールとは振舞いが大きく異なるため，ソフトウェア割込
みの種類の多いプロセッサでは，ret_texを呼び出すために専用のソフトウェア
割込みを割り当てるのが望ましい．ソフトウェア割込みの種類の少ないプロセッ
サでは，サービスコールを呼び出すためのソフトウェア割込みを共用する．サー
ビスコールを呼び出すためのソフトウェア割込みを共用する場合のために，
ret_texを呼び出すための機能コードとして，-1をリザーブする（svc.defにコ
メントで記述してある）．

ret_texの処理内容は，次の通り．

----------------------------------------
ER
ret_tex(void)
{
	if (呼び出し元が非特権モードでない) {
		返値をE_CTXとして，ソフトウェア割込みからリターン
	}
	スタックを実行状態のタスクのシステムスタックに切り換える

	LOG_TEX_LEAVE(p_runtsk);
	（少なくとも）カーネル管理の割込みを禁止した状態にする
	ソフトウェア割込みからリターン後のシステム状態が，
			CPUロック解除状態，割込み優先度マスク全解除状態になるよう準備する
	if ((rundom & sysstat_acvct.acptn1) != 0U) {
		disdspを，ユーザスタックに保存した値に戻す
	}
	dspflg = !disdsp;
	if (p_runtsk != p_schedtsk && dspflg) {
		CPUロック状態にする
		dispatch();
	}

	if (p_runtsk->texptn != 0U) {
		/*
		 *  タスク例外処理ルーチンの再度の実行開始
		 */
		TEXPTN	texptn = p_runtsk->texptn;

		タスクの保留例外要因（p_runtsk->texptn）と
				拡張情報（p_runtsk->p_tinib->exinf）が，
				タスク例外処理ルーチンへのパラメータとして渡るように準備する
		タスク例外処理ルーチンからのリターンにより，
				call_ret_texの番地へ分岐するように準備する … (*b)
		特権モードからのリターンによって，タスク例外処理ルーチン
				（p_runtsk->p_tinib->texrtn）へ分岐するように準備する
		p_runtsk->texptn = 0U;
		LOG_TEX_ENTER(p_runtsk, texptn);
	}
	else {
		/*
		 *  タスク例外処理ルーチンからのリターン
		 */
		ユーザスタック上の，disdspの値を捨てる（使用済み）
		ユーザスタックから，非特権モードでの
				スクラッチレジスタとフラグの値を取り出す
		ソフトウェア割込みからのリターンによって，
				スクラッチレジスタとフラグが取り出した値に戻るように準備する
		ユーザスタックから，タスク例外処理ルーチンからの戻り番地を取り出す
		ソフトウェア割込みからのリターンによって，
				タスク例外処理ルーチンからの戻り番地へ分岐するように準備する
		p_runtsk->enatex = true;
	}
	スタックを実行状態のタスクのユーザスタックに戻す
	ソフトウェア割込みからリターン
}
----------------------------------------

ここで，タスク例外処理ルーチンを再度実行開始するための処理では，
prepare_texrtn_utaskで行ったのと同様の処理を行うが，すでにユーザスタッ
クに積まれている情報はそのまま利用できる．具体的には，以下の情報は，タ
スク例外処理ルーチンの初回の実行開始時から変更されないので，再度積み直
す必要はない．

　・タスク例外処理ルーチンからの戻り番地
　・非特権モードでのスクラッチレジスタとフラグの値
　・ディスパッチ禁止フラグ

〔保護の観点での留意点〕

ret_texの実装にあたって，アプリケーションがユーザスタック上に保存した情
報を破壊／改変した場合に，保護の観点で問題がないように注意する必要があ
る．特に，ユーザスタック上の情報が破壊された場合でも，ret_texのコードが
誤動作しないように注意しなければならない．

ユーザスタック上に保存されたディスパッチ禁止フラグが破壊された場合，タ
スクのディスパッチ禁止フラグが正しく復帰されなくなる．しかし，ret_texで
これらを復帰するのは，あくまでもタスクにそれぞれの状態変更を許可してい
る場合にのみであり，ret_texによらずにタスクの誤動作でも同じ問題が起こる．
よって，保護の観点では問題がないと言える．

同様に，ユーザスタック上に保存された非特権モードでのスクラッチレジスタ
とフラグ（ステータスレジスタではないことに注意）の値が破壊された場合，
スクラッチレジスタとフラグが正しく復帰されなくなるが，タスクの誤動作で
も同じ問題が起こるため，保護の観点では問題がない．

ここで復帰するのは，アプリケーションレベルの状態を含むフラグだけであっ
て，システムレベルの状態を含むステータスレジスタ全体でないことに注意が
必要である．なぜなら，ステータスレジスタ全体をユーザスタック上に保存し，
ret_texでそれをそのまま復帰すると，ユーザスタック上に保存したものが破壊
された場合に，ユーザタスクを特権モードで実行することや，割込み禁止状態
で実行することができてしまうためである．

ユーザスタック上に保存されたタスク例外処理ルーチンからの戻り番地が破壊
された場合，ソフトウェア割込みから不正なアドレスを戻り番地としてリター
ンする命令を発行することになる．この時にプロセッサがどのように振る舞う
かが問題になる．一度ソフトウェア割込みからリターンした後に，例外を受け
付けるようようになっていれば，タスクの誤動作により起こりうる現象なので
問題ない．それに対して，リターンする前の特権モードのままで例外が発生す
ると，カーネルの中で例外が発生したように見え，リカバリーの余地を減らす
ので望ましくない．

〔ret_texとタスク例外処理保留状態に関する留意点〕

ret_texの処理は特権モードで行うため，定義により，タスク例外処理保留状態
である．ソフトウェア割込みによって割込みが禁止されるプロセッサでは，
ret_texの処理全体がカーネル管理の割込みを禁止した状態で実行され，
ret_texの処理途中にユーザの記述した処理単位が実行されることはないため，
このことは特に問題にならない．

それに対してソフトウェア割込みによって割込みが禁止されないプロセッサで
は，ret_texが呼ばれてから，カーネル管理の割込みを禁止するまでの間に，カー
ネル管理の割込みが発生する可能性がある．このようなプロセッサにおいては，
この間に発生した割込みの処理が，タスク例外処理保留状態で実行されること
から，以下に説明する追加処理が必要になる．

この割込み処理においてタスク例外処理ルーチンの起動が要求された場合，一
般には，タスク例外処理保留状態の解除時に，タスク例外処理ルーチンの実行
開始が必要になる．ただしこのケースでは，ret_tex内の処理でタスク例外処
理ルーチンの再度の実行開始を行うため，特に追加処理は必要ない．

また，この割込み処理において待ち禁止状態に遷移した場合，ユーザタスクに
戻る前に，それを解除する処理を追加する必要がある．実際には，ユーザタス
クに戻る前に，待ち禁止状態を無条件に解除してよい．

----------------------------------------
ER
ret_tex(void)
{
	………

+	p_runtsk->waifbd = false;
	スタックを実行状態のタスクのユーザスタックに戻す
	ソフトウェア割込みからリターン
}
----------------------------------------

〔相互再帰呼出しの問題に関する注記〕

システムタスクに対するタスク例外処理ルーチンからのリターン後の処理
（call_texrtn_stask内）では，相互再帰呼出しの問題を回避するために，
dispatchを呼び出す前にp_runtsk->enatexをfalseにしているが，この処理はユー
ザタスクに対しては必要ない．これは，dispatchの中では，システムタスクに
対するタスク例外処理ルーチンの実行開始処理をするのに対して，ユーザタス
クに対してはこれを行わない（言い換えると，相互再帰呼出しにはなっていな
い）ためである．

○カーネルがユーザスタックを操作する場合のチェック

●タスク例外処理ルーチン起動時のユーザスタックのチェック

ユーザスタックの残りが少ない状態でタスク例外処理ルーチンを起動すると，
ユーザスタックの続きの領域を，カーネル内で破壊する可能性がある．また，
ユーザスタックポインタを不正な値にした状態でタスク例外処理ルーチンを起
動すると，アクセスできてはならないメモリ領域を破壊できてしまう．

この問題を防ぐために，タスク例外処理ルーチン起動時に，ユーザスタックポ
インタの妥当性をチェックし，妥当でない場合には，エラー処理のためのハン
ドラを呼び出す．このハンドラをタスク例外実行開始時スタック不正例外ハン
ドラと呼ぶ．

これに対応するために，まず，prepare_texrtn_utaskの返値をbool_t型とし，
ユーザスタックポインタが妥当でない場合には，trueを返すように修正する．

----------------------------------------
bool_t
prepare_texrtn_utask(void *ssp)
{
	TEXPTN	texptn = p_runtsk->texptn;

+	if (!VALID_USTACK(ユーザスタックに情報を積む領域の先頭番地, サイズ) {
+		return(true);
+	}
	ユーザスタックに，タスク例外処理ルーチンからの戻り番地を積む

	………

	LOG_TEX_ENTER(p_runtsk, texptn);
+	return(false);
}
----------------------------------------

ここでVALID_USTACKは，メモリ領域の先頭番地とサイズから，先頭番地がスタッ
クポインタとして正しくアラインされており，メモリ領域全体が現在実行中の
タスクのユーザスタック領域に含まれているかどうかをチェックするマクロで
ある．その定義については後述する．

さらに，prepare_texrtn_utaskを呼び出すすべての処理に，trueが返った場合
にタスク例外起動時スタック不正例外ハンドラを呼び出す処理を追加する必要
がある．具体的には，割込みの出口処理，CPU例外の出口処理，拡張サービスコー
ルの出口処理，サービスコールの出口処理の4箇所を修正する．

----------------------------------------
void
<割込みの出入口処理>(void)
{
	………

					if (!i_sense_priv_runtsk()) {
!						if (prepare_texrtn_utask()) {
+							タスク例外実行開始時スタック不正例外ハンドラを
+																	呼び出す
+						}
					}

	………
}
----------------------------------------
void
<CPU例外の出入口処理>(void)
{
	………

						if (!i_sense_priv_runtsk()) {
!							if (prepare_texrtn_utask()) {
+								タスク例外実行開始時スタック不正例外ハンドラを
+																	呼び出す
+							}
						}

	………
}
----------------------------------------
ER_UINT
svc_entry(FN fncd, パラメータリスト)
{
	ER_UINT		ercd;

	if (fncd <= 0) {
		………

			if (p_runtsk->enatex && p_runtsk->texptn != 0U && ipmflg
								&& サービスコールからのリターン後の
									システム状態がCPUロック状態でない) {
				assert(rundom != TACP_KERNEL);
!				if (prepare_texrtn_utask()) {
+					タスク例外実行開始時スタック不正例外ハンドラを呼び出す
+				}
			}

		………
	}
	else {
		/*
		 *  拡張サービスコールの場合
		 */
		………

						else {
!							if (prepare_texrtn_utask()) {
+								タスク例外実行開始時スタック不正例外ハンドラを
+																	呼び出す
+							}
						}

		………
		}
	}
	………
}
----------------------------------------

●タスク例外処理ルーチンからのリターン時のユーザスタックのチェック

同様の問題は，タスク例外処理ルーチンからのリターン時にも発生する．そこ
で，タスク例外処理ルーチンからのリターン時にも，ユーザスタックポインタ
の妥当性をチェックし，妥当でない場合には，エラー処理のためのハンドラを
呼び出す．このハンドラをタスク例外リターン時スタック不正例外ハンドラと
呼ぶ．

これに対応するために，ret_texを次のように修正する．

----------------------------------------
ER
ret_tex(void)
{
	if (呼び出し元が非特権モードでない) {
		返値をE_CTXとして，ソフトウェア割込みからリターン
	}
	スタックを実行状態のタスクのシステムスタックに切り換える

+	if (!VALID_USTACK(prepare_texrtn_utaskで
+						ユーザスタックに情報を積んだ領域の先頭番地, サイズ) {
+		タスク例外リターン時スタック不正例外ハンドラを呼び出す
+	}

	LOG_TEX_LEAVE(p_runtsk);
	（少なくとも）カーネル管理の割込みを禁止した状態にする
	………
}
----------------------------------------

ここで，prepare_texrtn_utaskでユーザスタックに情報を積んだ領域の先頭番
地は，この時点でのユーザスタックポインタから求めることができる．具体的
には，この時点でのユーザスタックポインタから，タスク例外処理ルーチンか
らのリターンによりcall_ret_texの番地へ分岐するように準備する処理(*b)で
ユーザスタックに積む情報のサイズを引いた値が，prepare_texrtn_utaskでユー
ザスタックに情報を積んだ領域の先頭番地となる．

●タスク例外実行開始時／リターン時スタック不正例外ハンドラの定義と呼出し

タスク例外実行開始時／リターン時スタック不正例外ハンドラは，DEF_EXCによ
り定義することとする．これらに対するCPU例外ハンドラ番号はターゲット依存
とするが，CPU例外ハンドラ番号をマクロ定義するためのシンボルを次のように
決める．

	EXCNO_CALTEX_ILLSTK		タスク例外実行開始時スタック不正例外ハンドラ
	EXCNO_RETTEX_ILLSTK		タスク例外リターン時スタック不正例外ハンドラ

タスク例外実行開始時／リターン時スタック不正例外ハンドラは，エミュレー
トされたCPU例外ハンドラである．つまり，タスク実行開始起動時／リターン時
スタック不正例外ハンドラは，タスク例外処理ルーチンを実行するタスクのコ
ンテキストにおいて発生したCPU例外に対するCPU例外ハンドラと同じに扱える
ようにするのが原則である．それが実現できない場合には，ターゲット依存の
制約を設けることとする．

具体的には，タスク例外実行開始時／リターン時スタック不正例外ハンドラの
呼出し処理では，カーネル管理で，タスクコンテキストで発生したCPU例外によっ
て起動されたCPU例外ハンドラの出入口処理と同じ処理を実装しなければならな
い．ただし，以下の点に留意が必要である．

・「CPU例外発生時の状態」は，タスク例外実行開始時スタック不正例外ハンド
　ラにおいては「タスク例外処理ルーチンが実行開始される直前のタスクの状
　態」，タスク例外リターン時スタック不正例外ハンドラにおいては「タスク
　例外処理ルーチンからリターンする直前のタスクの状態」と解釈する．

・CPU例外ハンドラに渡すパラメータ（p_excinf）は，それをパラメータとして
　exc_sense_intmaskを呼び出した場合に，仕様通り動作するものでなければな
　らない．

・タスク例外実行開始時／リターン時スタック不正例外ハンドラからのリター
　ン後のタスクディスパッチ処理，タスク例外処理ルーチンの呼出し処理，オー
　バランタイマの停止／動作開始，ハンドラの呼出し前後のログ記録処理も，
　CPU例外ハンドラからのリターン後と同様に行わなければならない．

最後のことから，CPU例外ハンドラの出入口処理の大部分（少なくとも，
ret_exc以降の処理）を共用するのが良いと考えられる．

●VALID_USTACKの実装

VALID_USTACKは，メモリ領域の先頭番地とサイズから，先頭番地がスタックポ
インタとして正しくアラインされており，メモリ領域全体が現在実行中のタス
クのユーザスタック領域に含まれているかどうかをチェックするマクロである．

VALID_USTACKのデフォルトの定義は，スタックポインタがSTK_T型と同様にアラ
インされているかをチェックする．VALID_USTACKの定義は，ターゲット依存部
で定義することにより，置き換えることができるようにする．

----------------------------------------
#ifndef VALID_USTACK
#define	VALID_USTACK(sp, size) \
				(ALIGN_TYPE(sp, STK_T) && within_ustack(sp, size, p_runtsk))
#endif /* VALID_USTACK */
----------------------------------------

VALID_USTACKはターゲット依存部のみから使用される．VALID_USTACKをアセン
ブリ言語のコードから呼び出す場合には，これと同等の関数をターゲット依存
部で実装すればよい．

上記のVALID_USTACK中で用いているwithin_ustackは，メモリ領域がユーザスタッ
ク領域に含まれているかをチェックする関数で，次のように実装する．

----------------------------------------
/*
 *  メモリ領域がメモリオブジェクトに含まれているかのチェック
 *
 *  先頭番地がbaseでサイズがsizeのメモリ領域が，先頭番地がmobaseでサイ
 *  ズがmosizeのメモリオブジェクトに含まれている場合にtrue，そうでない
 *  場合にfalseを返す．
 */
Inline bool_t
within_memobj(const void *base, SIZE size, void *mobase, SIZE mosize)
{
	return((SIZE)(mobase) <= (SIZE)(base) && size <= mosize
			&& (SIZE)((char *)(base) - (char *)(mobase)) <= mosize - size);
}

/*
 *  メモリ領域がユーザスタック領域に含まれているかのチェック
 *
 *  先頭番地がbaseでサイズがsizeのメモリ領域が，p_tcbで指定されるタスク
 *  のユーザスタック領域に含まれている場合にtrue，そうでない場合に
 *  falseを返す．
 */
#ifndef USE_TSKINICTXB

Inline bool_t
within_ustack(const void *base, SIZE size, TCB *p_tcb)
{
	return(within_memobj(base, size,
					p_tcb->p_tinib->ustk, p_tcb->p_tinib->ustksz));
}

#else /* USE_TSKINICTXB */

extern bool_t	within_ustack(const void *base, SIZE size, TCB *p_tcb);

#endif /* USE_TSKINICTXB */
----------------------------------------

within_memobjのreturn文中の条件判定式中の3つめの条件は，比較演算子の左
右辺の減算とも（符号なしの数として）オーバフローしないことが，その前の
2つの条件から保証される．そのため，オーバフローを気にせずに比較すること
ができる．

within_memobjでは，(base + size)がオーバフローする場合と(mobase +
mosize)が0になる場合は考慮する必要があるが，(mobase + mosize)が0を超え
てオーバフローする場合は考慮する必要がないが，上記のwithin_memobjの実装
は，この場合も考慮できている．

上記のwithin_ustackの実装は，汎用のタスク初期化ブロックを用いている場合
を想定している．汎用のタスク初期化ブロックを用いていない場合は，
within_ustackをターゲット依存部で実装する必要がある．この場合にも，上の
コード中でwithin_ustackのプロトタイプ宣言を行っているのは，
target_config.hがtask.hよりも先にインクルードされるために
target_config.hの中ではTCBを参照することができず，このプロトタイプ宣言
が行えないためである．

○CPU例外発生時の状態参照機能の修正

CPU例外発生時の状態参照を行うサービスコール（xsns_dpn，xsns_xpn）は，
CPU例外ハンドラから呼び出すべきサービスコールであり，タスクコンテキスト
から呼び出した場合の振舞いは，ASPカーネルでは保証されない．HRP2カーネル
では，ユーザドメインから呼び出した場合に無保証になるのは避けたいため，
タスクコンテキストから呼び出した場合（ユーザドメインからの呼び出しは，
必ずタスクコンテキストである）にはtrueを返す仕様となっている．

----------------------------------------
bool_t
xsns_dpn(void *p_excinf)
{
	bool_t	state;

	LOG_XSNS_DPN_ENTER(p_excinf);
!	state = (sense_context() && exc_sense_intmask(p_excinf)
									&& !disdsp) ? false : true;
	LOG_XSNS_DPN_LEAVE(state);
	return(state);
}
----------------------------------------

xsns_xpnに関しては，タスク例外処理ルーチンの起動条件が変わったことに伴っ
て，trueを返す条件を変更する必要がある．具体的には，タスク例外処理マス
ク状態の場合には，xsns_xpnはtrueを返すようにする．さらに，CPU例外がユー
ザタスクで発生した場合で，ユーザスタックの残りが少ないためにタスク例外
処理ルーチンが起動できない場合にも，trueを返すようにする．

----------------------------------------
bool_t
xsns_xpn(void *p_excinf)
{
!	bool_t	state = true;

	LOG_XSNS_XPN_ENTER(p_excinf);
!	if (sense_context() && exc_sense_intmask(p_excinf) && p_runtsk->enatex) {
+		if (p_runtsk->p_tinib->p_dominib == &dominib_kernel) {
+			if (p_runtsk->svclevel == 0U) {
+				state = false;
+			}
+		}
+		else {
+			if (!i_sense_priv_runtsk() && !i_check_tex_runtsk()) {
+				state = false;
+			}
+		}
+	}
	LOG_XSNS_XPN_LEAVE(state);
	return(state);
}
----------------------------------------

ここで，i_check_tex_runtsk()は，実行状態のタスクのユーザスタックの残り
が少ないためにタスク例外処理ルーチンが起動できない場合にtrue，そうでな
い場合にfalseを返す関数で，ターゲット依存部で用意する．

----------------------------------------
bool_t
i_check_tex_runtsk()
{
	if (!VALID_USTACK(prepare_texrtn_utaskで
						ユーザスタックに情報を積む領域の先頭番地, サイズ) {
		return(true);
	}
	return(false);
}
----------------------------------------

ここで，prepare_texrtn_utaskでユーザスタックに情報を積む領域の先頭番地
は，この時点でのユーザスタックポインタから，ユーザスタックに情報を積む
領域のサイズを引いた番地になる．

○保護ドメインのメモリ管理

●メモリオブジェクトの管理

メモリオブジェクトとは，カーネルがアクセス保護の対象とする一連のメモリ
領域である．

HRP2カーネルにおいては，メモリオブジェクトは以下のいずれかである．

　・ATT_SEC／ATA_SECで登録されたセクション（コード領域やデータ領域）
　・ATT_MOD／ATA_MODで登録されたセクション（コード領域やデータ領域）
　・ATT_MEM／ATA_MEMで登録されたメモリオブジェクト
　・タスクのユーザスタック領域
　・固定長メモリプール領域（コンフィギュレータが割り付けた場合）

タスクのシステムスタック領域やデータキュー管理領域，非タスクコンテキス
ト用のスタック領域など，カーネルの用いる管理領域は，kernel_cfg.cに含ま
れていて，ATT_MODで登録した領域とみなすこととする．

固定長メモリプール領域をアプリケーションで確保する場合には，いずれかの
メモリオブジェクトに含まれていなければならず，単独でメモリオブジェクト
とはならない．

メモリオブジェクトに関する情報は，メモリに対するアクセス保護を行うため
にMMU/MPUに設定するのに加えて，サービスコールに渡されたポインタが指すメ
モリ領域に対して，サービスコールを呼び出した処理単位がアクセス権を持つ
かどうかをチェックする（これを，メモリアクセス権のチェックと呼ぶ）ため
にも使用する．MMU/MPUに設定した情報がこの目的にも使える場合には，新たな
データ構造は必要ないが，レアケースと思われる．

MMU/MPUに設定した情報がメモリアクセス権のチェックに使えない場合や，
MMU/MPUに設定する情報を動的に生成する場合には，メモリオブジェクトに関す
る情報を管理するデータ構造が必要になる．そこで，メモリオブジェクトに関
する情報を管理する標準的なデータ構造として，メモリオブジェクト初期化ブ
ロックを定義する．

●メモリオブジェクト初期化ブロック

ここでは，標準的なメモリオブジェクト初期化ブロックについて述べる．メモ
リオブジェクト初期化ブロックを用いない場合や，これと同等の情報をターゲッ
ト依存のデータ構造で管理する場合には，OMIT_STANDARD_MEMINIBをマクロ定義
し，以下で述べるのと同様の機能をターゲット依存部で実現する．

メモリオブジェクト初期化ブロックは，メモリオブジェクト毎に，その属性や
アクセス権に関する情報を管理するためのデータ構造で，コンフィギュレータ
で生成する．HRP2カーネルにおいては，いずれのメモリオブジェクトも，属性，
所属ドメイン，アクセス許可ベクタがコンフィギュレータによって静的に決定
されるため，メモリオブジェクト初期化ブロックは，（その名称の通り）定数
データ領域に置くことができる．

メモリオブジェクト初期化ブロックは，概念的には，メモリオブジェクトの先
頭番地，属性，アクセス許可情報からなる構造体である．指定された番地がど
のメモリオブジェクトに含まれるかを効率的に検索できるように，メモリオブ
ジェクトの先頭番地の小さい順に配列に格納する．指定された番地がどのメモ
リオブジェクトに含まれるかを調べる時には，その配列をバイナリサーチする．
メモリオブジェクトの置かれていない未使用のメモリ領域にも，メモリオブジェ
クト初期化ブロックを割り当てることで，メモリオブジェクトの先頭番地のみ
を持てばよいことになる．メモリオブジェクトを先頭番地の小さい順に並べる
処理は，コンフィギュレータのパス3で行う．

実際には，バイナリサーチを効率化するために，先頭番地のみを格納した配列
と，残りの情報を格納した配列に分割する．便宜上，残りの情報を入れる構造
体をメモリオブジェクト初期化ブロックと呼ぶことにする．

また，HRP2カーネルは，メモリオブジェクトに対する管理操作に該当するサー
ビスコールをサポートしないため，アクセス許可ベクタに含まれるアクセス許
可パターンの内，管理操作のアクセス許可パターン（acptn3）は必要ない．そ
こで，メモリオブジェクト初期化ブロックには，アクセス許可ベクタの代わり
に，3つのアクセス許可パターンを格納することにする．

タスクのユーザスタック領域に対する書込みアクセスと読出しアクセスは，保
護ドメイン単位でなく，タスク単位で保護されるため，アクセス許可パターン
では表現できない．そこで，タスクのユーザスタック領域として使用するメモ
リオブジェクトに対しては，通常操作1と通常操作2のアクセス許可パターン
（acptn1，acptn2）は0にする．参照操作のアクセス許可パターン（acptn4）は，
タスクのユーザスタック領域に対しても有効なので，コンフィギュレータによっ
て正しく設定しなければならない．

----------------------------------------
#ifndef OMIT_STANDARD_MEMINIB

/*
 *  メモリオブジェクト初期化ブロック
 */
typedef struct memory_initialization_block {
	ATR		mematr;			/* メモリオブジェクト属性 */
	ACPTN	acptn4;			/* 参照操作のアクセス許可パターン */
	ACPTN	acptn1;			/* 通常操作1のアクセス許可パターン */
	ACPTN	acptn2;			/* 通常操作2のアクセス許可パターン */
} MEMINIB;

/*
 *  メモリオブジェクト初期化ブロックのエントリ数（kernel_cfg.c）
 */
extern const uint_t		tnum_meminib;

/*
 *  メモリオブジェクトの先頭番地の領域（kernel_cfg.c）
 */
extern void *const		memtop_table[];

/*
 *  メモリオブジェクト初期化ブロックの領域（kernel_cfg.c）
 */
extern const MEMINIB	meminib_table[];

#endif /* OMIT_STANDARD_MEMINIB */
----------------------------------------

メモリオブジェクト初期化ブロック中のメモリオブジェクト属性（mematrフィー
ルド）には，メモリオブジェクトの種別と属性を示す以下の値の論理和を設定
する．

まず，メモリオブジェクトの種別に対応して，以下のメモリオブジェクト属性
を設定する．また，未使用領域に対しては，TA_NULLを設定する．

	TOPPERS_ATTSEC	0x0100		ATT_SEC／ATA_SECまたはATT_MOD／ATA_MODで登録
								されたメモリオブジェクト
	TOPPERS_ATTMEM	0x0200		ATT_MEM／ATA_MEMで登録されたメモリオブジェクト
	TOPPERS_USTACK	0x0400		タスクのユーザスタック領域

この中で，HRP2カーネルで実際に参照しているのは，TOPPERS_USTACKのみであ
る．その他の2つは，カーネル内では区別して扱っていない．

ATT_SEC／ATA_SECで登録されたセクションと，ATT_MOD／ATA_MODで登録された
モジュール（の標準セクション）は，1つのメモリオブジェクトにまとめて登録
されるため，メモリオブジェクトの種別では区別できない．

固定長メモリプール領域をコンフィギュレータで割り付ける場合には，固定長
メモリプール領域のみを含むセクションを作り，そのセクションをATT_SEC／
ATA_SECした扱いとするため，ATT_SEC／ATA_SECで登録したメモリオブジェクト
であるものと扱う．

メモリオブジェクトの属性に対応して，以下のメモリオブジェクト属性を設定
する（これらの属性値は，ユーザも使用するものである）．

	TA_NOWRITE	0x01	書込みアクセス禁止
	TA_NOREAD	0x02	読出しアクセス禁止
	TA_EXEC		0x04	実行アクセス許可
	TA_MEMINI	0x08	メモリの初期化を行う
	TA_MEMPRSV	0x10	メモリの初期化を行わない
	TA_SDATA	0x20	ショートデータ領域に配置
	TA_UNCACHE	0x40	キャッシュ禁止
	TA_IODEV	0x80	周辺デバイスの領域

この属性値は，ターゲットプロセッサに依存して拡張（または制限）してもよ
い．

メモリアクセス権のチェックを効率化するために，未使用領域（TA_NULL）に対
してはすべてのアクセス許可パターン（acptn1，acptn2，acptn4）を0にする．
また，書込みアクセス禁止（TA_NOWRITE）の領域に対しては，通常操作1（書込
みアクセス）のアクセス許可パターン（acptn1）を0にする．

それに対して，読出しアクセス禁止（TA_NOREAD）の領域や実行アクセス禁止
（TA_NOEXEC）の領域に対しては，通常操作2（読出し・実行アクセス）のアク
セス許可パターン（acptn2）を0にしない．読出しアクセスと実行アクセスの両
方が禁止されている領域に対して0にする手もあるが，チェックを省略できず，
効率化に貢献しないため，採用していない．

メモリオブジェクト初期化ブロックのエントリ数，メモリオブジェクトの先頭
番地の領域，メモリオブジェクト初期化ブロックの領域の定義は，コンフィギュ
レータがkernel_mem.c中に生成する．この時，メモリオブジェクトの先頭番地
の領域とメモリオブジェクト初期化ブロックの領域の要素数は，コンフィギュ
レータのパス3でしかわからないが，パス2とパス3の生成する領域のサイズが変
わるのは不都合であるため，パス2の時点でわかる最悪の場合の要素数（すなわ
ち，メモリオブジェクトの数×2＋1）としておく．

〔以前のバージョンでの設計〕

HRP2カーネルの以前のバージョンでは，以下の設計を採用していた．

タスクのユーザスタック領域として使用するメモリオブジェクトに対しては，
通常操作1のアクセス許可パターンを入れるフィールドに（acptn1）に，そのタ
スクのTCBへのポインタを格納することにする．

メモリオブジェクト初期化ブロック内のフィールドをmematr，acptn4，acptn1，
acptn2の順序にし，タスクのTCBへのポインタをacptn1に格納することにしたの
は，ポインタが64ビットの場合を考慮したためである．ただしこの方法でも，
ポインタが64ビットの場合には，うまく初期化するのは簡単ではない（コンフィ
ギュレータが頑張れば不可能ではないはず）．

なお，TCBへのポインタを格納するために共用体（union）を用いることも検討
したが，共用体は初期化ができないために，採用しないことにした．

●メモリオブジェクト初期化ブロックの探索

メモリオブジェクト初期化ブロックを探索するための関数は次の通り．

----------------------------------------
/*
 *  メモリオブジェクト初期化ブロックの検索
 */
#ifndef OMIT_STANDARD_MEMINIB

int_t
search_meminib(const void *addr)
{
	int_t	left, right, i;

	left = 0;
	right = tnum_meminib - 1;
	while (left < right) {
		i = (left + right + 1) / 2;
		if ((SIZE)(addr) < (SIZE)(memtop_table[i])) {
			right = i - 1;
		}
		else {
			left = i;
		}
	}
	return(left);
}

#endif /* OMIT_STANDARD_MEMINIB */
----------------------------------------

※以下の文中では，ポインタの大小比較は符号なしであるものとする．

search_meminibは，

	memtop_table[i] <= addr < memtop_table[i + 1] ... (1)
	ただし，memtop_table[tnum_meminib] == ∞ とする

を満たすiを探す．このことを以下で証明する．

----------------------------------------
証明にあたり，プログラムを通じて，leftはiが取りうる範囲の最小値，right
はiが取りうる範囲の最大値であることを利用する．

[1] 最初に，プログラムを通じて，次の式が成立していることを示す．

	memtop_table[left] <= addr < memtop_table[right + 1] ... (2)
	ただし，memtop_table[tnum_meminib] == ∞ とする

まず，leftとrightの初期値は，left == 0，right == tnum_meminib - 1なの
で，ループに入る前には(2)は成立している．

次に，ループを回ることにより，(2)の関係式が維持されることを示す．ルー
プ中のif文が成立した時，すなわち「addr < memtop_table[i]」の場合， 
rightにi - 1が代入される．その結果，「i == right + 1」となるので，これ
を上の条件式に代入すると「addr < memtop_table[right + 1]」となり，(2)
の右半分が成り立つことがわかる．leftは変化しないので，左半分は明らかに
維持される．

ループ中のif文が成立しなかった時，すなわち「addr >= memtop_table[i]」
の場合，leftにiが代入される．その結果，「i == left」となるので，これを
上の条件式に代入すると「memtop_table[lett] <= addr」となり，(2)の左半
分が成り立つことがわかる．rightは変化しないので，右半分は明らかに維持
される．

以上より，プログラムを通じて常に(2)が成立していることが証明された．

[2] 次に，このプログラムが必ず終了することを示す．

「i = (left + right + 1) / 2」によって定まるiが，「left < i」かつ「i -
1 < right」を満たすことが示せれば

このことを，ループを回ることにより，iの取りうる範囲の幅が単調に狭まる，
すなわち「right - left」が単調に小さくなることを示すことによって示す．

ループ中のiの値は，

	i = (left + right + 1) / 2 ... (3)

によって定まる．まず，このiが次の2つの条件式を満たすことを示す．

	left < i ... (4)
	i - 1 < right ... (5)

ループを回るのは，「left < right」言い換えると「left + 1 <= right」の
場合である．そこで，「right >= left + 1」を(3)に代入すると，

	i >= (left + (left + 1) + 1) / 2
	  = left + 1
	  > left

より，(4)が満たされることが示せる．次に，「left <= right - 1」を(3)に
代入すると，

	i <= ((right - 1) + right + 1) / 2
	  = right
	  < right - 1

より，「i + 1 < right」が求まり，(5)が満たされることが示せる．

ループ中のiが(4)と(5)の条件式を満たすと，ループ文中のif文が成立した時
には，rightにi - 1が代入されるが，(5)よりrightが小さくなることがわかる．
if文が成立しなかった時には，leftにiが代入されるが，(4)よりleftが大きく
なることがわかる．以上より，いずれの場合にも，「right - left」は単調に
小さくなる．

[3] 最後に，ループから抜けた時点では，「left >= right」と(2)が成り立つ
ことから，leftが(1)のiの条件を満たす値になっていることがわかる．

証明終わり
----------------------------------------

ここで，search_meminibにどのようなアドレスを渡しても正しい結果を返すた
めには，メモリオブジェクト初期化ブロックの先頭のエントリの先頭番地が0で
なければならない．

●メモリアクセス権のチェック

サービスコールに渡されたポインタが指すメモリ領域に対して，サービスコー
ルを呼び出した保護ドメインがアクセス権を持つかどうかをチェックするため
に，probe_mem_writeとprobe_mem_readの2つの関数を用意する．これらの関数
は，実行中の保護ドメインがユーザドメインである時に使用するものとし，カー
ネルドメインの時には呼んではならないものとする．

これらの関数の実装では，メモリオブジェクト初期化ブロックをサーチして，
指定されたメモリ領域のアクセス許可情報を取り出し，アクセス権があるかど
うかをチェックする．

----------------------------------------
/*
 *  メモリへの書込み権のチェック
 */
#ifndef OMIT_PROBE_MEM_WRITE

bool_t
probe_mem_write(const void *base, SIZE size)
{
	int_t	meminib;
	ATR		mematr;
	SIZE	memsize;

	meminib = search_meminib(base);
	mematr = meminib_table[meminib].mematr;
	memsize = ((char *)((meminib + 1 < tnum_meminib) ?
					memtop_table[meminib + 1] : 0)) - ((char *) base);

	if (mematr == TA_NULL) {
		return(false);
	}
	else if (size > memsize) {
		/*
		 *  指定されたメモリ領域が，複数のメモリオブジェクトにまたがっ
		 *  ている場合
		 */
		return(false);
	}
	else if ((mematr & TOPPERS_USTACK) == 0U) {
		/*
		 *  ((mematr & TA_NOWRITE) != 0U)の時は，acptn1を0にしているた
		 *  め，後者のチェックのみを行えばよい．
		 */
		if ((rundom & meminib_table[meminib].acptn1) == 0U) {
			return(false);
		}
	}
	else {
		return(within_ustack(base, size, p_runtsk));
	}
	return(true);
}

#endif /* OMIT_PROBE_MEM_WRITE */

/*
 *  メモリからの読出し権のチェック
 */
#ifndef OMIT_PROBE_MEM_READ

bool_t
probe_mem_read(const void *base, SIZE size)
{
	int_t	meminib;
	ATR		mematr;
	SIZE	memsize;

	meminib = search_meminib(base);
	mematr = meminib_table[meminib].mematr;
	memsize = ((char *)((meminib + 1 < tnum_meminib) ?
					memtop_table[meminib + 1] : 0)) - ((char *) base);

	if (mematr == TA_NULL) {
		return(false);
	}
	else if (size > memsize) {
		/*
		 *  指定されたメモリ領域が，複数のメモリオブジェクトにまたがっ
		 *  ている場合
		 */
		return(false);
	}
	else if ((mematr & TOPPERS_USTACK) == 0U) {
		if ((mematr & TA_NOREAD) != 0U
					|| (rundom & meminib_table[meminib].acptn2) == 0U) {
			return(false);
		}
	}
	else {
		return(within_ustack(base, size, p_runtsk));
	}
	return(true);
}

#endif /* OMIT_PROBE_MEM_READ */
----------------------------------------

ここでmemsizeは，baseから，baseを含むメモリオブジェクトの上限までのサイ
ズであり，(size > memsize)は，baseとsizeで指定されたメモリ領域が，複数
のメモリオブジェクトにまたがっていることを判定している．この方法で判定
することで，base+sizeが0のまたぐ場合にも対応できている．

このprobe_mem_writeとprobe_mem_readの実装は，標準のメモリオブジェクト初
期化ブロックを用いている場合を想定している．標準のメモリオブジェクト初
期化ブロックを用いていない場合は，OMIT_PROBE_MEM_WRITEと
OMIT_PROBE_MEM_READをマクロ定義し，probe_mem_writeとprobe_mem_readをター
ゲット依存部で実装する必要がある．

また，プロセッサによっては，probe_mem_writeとprobe_mem_readを，プロセッ
サが持つMMU/MPU命令を使って実装した方が効率がよい場合がある．その場合に
は，ターゲット依存部でMMU/MPU命令を使ってprobe_mem_writeと
probe_mem_readを実現し，それぞれOMIT_PROBE_MEM_WRITEと
OMIT_PROBE_MEM_READをマクロ定義すればよい．

●メモリアクセス権のチェックマクロ

メモリアクセス権とアラインメントの両方をチェックするマクロとして
PROBE_MEM_WRITEとPROBE_MEM_READ，それらを用いてをエラーチェックを行うマ
クロとしてCHECK_MACV_WRITEとCHECK_MACV_READを用意する．

これらの定義は以下の通り．

----------------------------------------
/*
 *  メモリアクセス権のチェックのためのマクロ
 *
 *  以下の定義は，アラインメントの単位が2の巾乗であることを仮定している．
 */
#ifndef PROBE_MEM_WRITE
#define PROBE_MEM_WRITE(p_var, type) \
				(ALIGN_TYPE(p_var, type) && (rundom == TACP_KERNEL \
					|| probe_mem_write((void *)(p_var), sizeof(*(p_var)))))
#endif /* PROBE_MEM_WRITE */

#ifndef PROBE_MEM_READ
#define PROBE_MEM_READ(p_var, type) \
				(ALIGN_TYPE(p_var, type) && (rundom == TACP_KERNEL \
					|| probe_mem_read((void *)(p_var), sizeof(*(p_var)))))
#endif /* PROBE_MEM_READ */
----------------------------------------
/*
 *  メモリアクセス権のチェック（E_MACV）
 */
#define CHECK_MACV_WRITE(p_var, type) {						\
	if (!PROBE_MEM_WRITE(p_var, type)) {					\
		ercd = E_MACV;										\
		goto error_exit;									\
	}														\
}

#define CHECK_MACV_READ(p_var, type) {						\
	if (!PROBE_MEM_READ(p_var, type)) {						\
		ercd = E_MACV;										\
		goto error_exit;									\
	}														\
}
----------------------------------------

実行中の保護ドメインがカーネルドメインの時は，probe_mem_writeと
probe_mem_readを呼び出せないため，PROBE_MEM_WRITEとPROBE_MEM_READの中で
対処している．

●メモリアクセス権チェックの追加

パラメータでポインタを受け取るサービスコール内では，上記のマクロを用い
て，メモリアクセス違反エラーをチェックする．例えば，int_t型へのポインタ
変数p_xxxxxに対して書込みができるかどうかを調べるプログラムは，次のよう
になる．これにより，p_xxxxxの番地から，このポインタ変数が指す型（この場
合はint_t型）のサイズ分のメモリ領域に書き込むことができるかをチェックす
る．

----------------------------------------
	CHECK_MACV_WRITE(p_xxxxx, int_t);
----------------------------------------

HRP2カーネルでは，すべてのメモリオブジェクトの保護情報は静的に決定され
るため，上記のチェックはクリティカルセクションの外で行ってよい．

●prb_memの実装

アプリケーション（通常は拡張サービスコール）でメモリアクセス権のチェッ
クを行えるように，prb_memサービスコールを用意する．

----------------------------------------
/*
 *  メモリアクセス権のチェック（prb_memの本体）
 */
#ifndef OMIT_PROBE_MEMORY

Inline ER
probe_memory(const void *base, SIZE size, TCB *p_tcb, MODE pmmode)
{
	ACPTN	domptn;
	int_t	meminib;
	ATR		mematr;
	SIZE	memsize;
	ER		ercd;

	domptn = p_tcb->p_tinib->p_dominib->domptn;
	meminib = search_meminib(base);
	mematr = meminib_table[meminib].mematr;
	memsize = ((char *)((meminib + 1 < tnum_meminib) ?
					memtop_table[meminib + 1] : 0)) - ((char *) base);

	if (mematr == TA_NULL) {
		ercd = E_NOEXS;
	}
	else if (size > memsize) {
		ercd = E_OBJ;
	}
	else if (rundom != TACP_KERNEL
				&& (rundom & meminib_table[meminib].acptn4) == 0U) {
		ercd = E_OACV;
	}
	else if (domptn == TACP_KERNEL) {
		/*
		 *  対象タスクがカーネルドメインに属する場合
		 */
		ercd = E_OK;
	}
	else if ((mematr & TOPPERS_USTACK) == 0U) {
		/*
		 *  通常のメモリオブジェクト（タスクのユーザスタック領域以外）
		 *  の場合
		 *
		 *  メモリオブジェクト属性がTA_NOWRITEの場合には，acptn1が0に
		 *  なっているので，メモリオブジェクト属性のTA_NOWRITEはチェッ
		 *  クしていない．
		 */
		if ((pmmode & TPM_WRITE) != 0U
					&& (domptn & meminib_table[meminib].acptn1) == 0U) {
			ercd = E_MACV;
		}
		else if ((pmmode & TPM_READ) != 0U && ((mematr & TA_NOREAD) != 0U
						|| (domptn & meminib_table[meminib].acptn2) == 0U)) {
			ercd = E_MACV;
		}
		else if ((pmmode & TPM_EXEC) != 0U && ((mematr & TA_EXEC) == 0U
						|| (domptn & meminib_table[meminib].acptn2) == 0U)) {
			ercd = E_MACV;
		}
		else {
			ercd = E_OK;
		}
	}
	else {
		/*
		 *  タスクのユーザスタック領域の場合
		 *
		 *  ユーザスタック領域が読出し禁止や書込み禁止になっていること
		 *  はあり得ないため，メモリオブジェクト属性のTA_NOWRITEと
		 *  TA_NOREADはチェックしていない．
		 */
		if (!within_ustack(base, size, p_tcb)) {
			ercd = E_MACV;
		}
		else if ((pmmode & TPM_EXEC) != 0U && ((mematr & TA_EXEC) == 0U)) {
			ercd = E_MACV;
		}
		else {
			ercd = E_OK;
		}
	}
	return(ercd);
}

#endif /* OMIT_PROBE_MEMORY */

/*
 *  メモリアクセス権のチェック
 *
 *  このサービスコールでは，静的なデータ構造しか参照しないため，クリティ
 *  カルセクションを作る必要がない．
 */
#ifdef TOPPERS_prb_mem

ER
prb_mem(const void *base, SIZE size, ID tskid, MODE pmmode)
{
	ER		ercd;

	LOG_PRB_MEM_ENTER(base, size, tskid, pmmode);
	CHECK_TSKCTX_UNL();
	CHECK_TSKID_SELF(tskid);
	CHECK_PAR(size > 0U);
	CHECK_PAR((pmmode & ~(TPM_READ|TPM_WRITE|TPM_EXEC)) == 0U);
	CHECK_PAR(pmmode != 0U);

	ercd = probe_memory(base, size, get_tcb_self(tskid), pmmode);

  error_exit:
	LOG_PRB_MEM_LEAVE(ercd);
	return(ercd);
}
----------------------------------------

このprobe_memoryの実装は，標準のメモリオブジェクト初期化ブロックを用い
ている場合を想定している．標準のメモリオブジェクト初期化ブロックを用い
ていない場合は，OMIT_PROBE_MEMORYをマクロ定義し，probe_memoryをターゲッ
ト依存部で実装する必要がある．

また，プロセッサによっては，probe_memoryを，プロセッサが持つMMU/MPU命令
を使って実装した方が効率がよい場合がある．その場合には，ターゲット依存
部でMMU/MPU命令を使ってprobe_memoryを実現し，OMIT_PROBE_MEMORYをマクロ
定義すればよい．

なお，メモリオブジェクト境界を越えていることのチェックは，μITRON4.0/PX
仕様では必須とはしていないが，TOPPERS新世代カーネル仕様では必ずチェック
することとしている．

●メモリアクセス違反ハンドラの実装

メモリアクセス違反ハンドラは，CPU例外ハンドラの一種であると扱うこととし，
メモリアクセス違反が発生したコンテキストにおいてCPU例外が発生した場合と
同等のコンテキストで実行することを原則とする．ただし，メモリアクセス違
反ハンドラの起動方法によりそれが難しい場合には例外扱いとする．

メモリアクセス違反ハンドラは，DEF_EXCにより定義することとする．これに対
するCPU例外ハンドラ番号はターゲット依存とするが，メモリアクセス違反ハン
ドラをソフトウェアでエミュレートする場合で，メモリアクセス違反ハンドラ
の種類が1つの場合のために，CPU例外ハンドラ番号をマクロ定義するためのシ
ンボルを次のように決める．

	EXCNO_MEM_ACV		メモリアクセス違反ハンドラ

○メモリオブジェクトの登録と情報の生成

●登録すべきメモリオブジェクト

一般的なターゲットシステムにおいては，カーネルに登録すべきメモリオブジェ
クトには次のものがある（括弧内のセクション名は，GNU開発環境における標準
的な名称である）．

(a) ATT_SEC／ATA_SECで登録されたメモリオブジェクト
(b) ATT_MOD／ATA_MODで登録されたメモリオブジェクト
(b-1) コードセクション（text）
(b-2) 定数データセクション（rodata）
(b-3) 初期化データセクション（data）
(b-4) 非初期化データセクション（bss）
(c) ATT_MEM／ATA_MEMで登録されたメモリオブジェクト
(d) タスクのユーザスタック領域
(d-1) コンフィギュレータが割り付けた場合
(d-2) アプリケーションで指定した場合
(e) 固定長メモリプール領域（コンフィギュレータが割り付けた場合）
	 ※ アプリケーションで指定した固定長メモリプール領域は，いずれかの
		メモリオブジェクトに含まれていなければならず，単独のメモリオブ
		ジェクトとしては登録しない．

ターゲットによっては，(b-1)と(b-2)が分離できない場合がある．また，ショー
トデータセクションを持つターゲットでは，次の2つが追加される．

(b-5) ショート定数データセクション（rosdata）
(b-6) ショート初期化データセクション（sdata）
(b-7) ショート非初期化データセクション（sbss）

これらのメモリオブジェクトの中で，(a)，(b-1)〜(b-7)，(d-1)，(e)は，コン
フィギュレータがメモリに配置する．具体的には，コンフィギュレータがリン
カスクリプトを生成し，リンカがメモリへの配置を決定する．他のメモリオブ
ジェクト（(c)，(d-2)）は，どの番地に置くかをアプリケーションが指定する．

※ さらにターゲットによっては，ショートデータセクションよりもさらに小さ
いデータセクション（例えば，タイニーデータセクション）を持つ場合がある．
これらへの対応は，ターゲット依存で実施する（ターゲット非依存部の修正が
必要な場合が出てくる可能性もある）．

●セクションのリンク方法

ここでは，上の(a)と(b-1)〜(b-7)のメモリオブジェクトを，どのようにリンク
するかについて述べる．

ATT_SEC／ATA_SECで登録されるセクションは，配置先のメモリリージョン，属
する保護ドメイン（または無所属），メモリオブジェクト属性，アクセス許可
ベクタによって，リンク先のセクションを決める．ただし，アクセス許可ベク
タの中で，管理操作のアクセス許可パターン（acptn3）は無視する．また，配
置先のメモリリージョンがTA_NOWRITE属性の場合には，通常操作1のアクセス許
可パターン（acptn1）も無視する．さらに，参照操作のアクセス許可パターン
（acptn4）が異なっていても同じセクションにリンクするが，セクション内で
はacptn4が同じものを連続して配置する．

ATT_MOD／ATA_MODで登録されるオブジェクトモジュールは，その中に含まれる
標準のセクションが，それぞれATT_SEC／ATA_SECで登録されたのと同様に扱う．
その際に，配置先のメモリリージョンとメモリオブジェクト属性は，セクショ
ンの種類毎に下の表のように定める（表中のメモリオブジェクト属性は，ター
ゲット依存部で設定する）．

------------------------------------------------------------------------
セクションの種類			メモリリージョン	メモリオブジェクト属性
------------------------------------------------------------------------
コードセクション			標準ROMリージョン	TA_NOWRITE|TA_EXEC
定数データセクション		標準ROMリージョン	TA_NOWRITE
初期化データセクション		標準RAMリージョン	TA_MEMINI
非初期化データセクション	標準RAMリージョン	TA_NULL
ショート定数データセクシ...	標準RAMリージョン	TA_NOWRITE|TA_MEMINI|TA_SDATA
ショート初期化データセク...	標準RAMリージョン	TA_MEMINI|TA_SDATA
ショート非初期化データセ...	標準RAMリージョン	TA_SDATA
------------------------------------------------------------------------

ショート定数データセクションを，標準ROMリージョンではなく，標準RAMリー
ジョンに置くのは，ショートデータをアクセスするためのグローバルポインタ
が1つしかないのが通常で，すべてのショートデータセクションをまとめて配置
する必要があるためである．

※ グローバルポインタ複数あり，ショート定数データセクションを標準ROMリー
ジョンに置く場合には，ターゲット依存で対応する（ターゲット非依存部の修
正が必要な場合が出てくる可能性もある）．

GNU開発環境の場合，リンク先のセクションの名称は，次の6つの要素を"_"で接
続したものとする．要素を省略する場合には，それをつなぐ"_"も省略する．

(1) メモリリージョン名

セクションの配置先のメモリリージョンの名称．標準ROMリージョンか標準RAM
リージョンの場合には，この要素は省略する．

(2) セクション種別

次の8つの文字列のいずれか．この要素を省略することはない．

	"text"：配置先のメモリリージョンがTA_NOWRITE属性で，メモリオブジェ
			クト属性にTA_EXECが設定されている場合
	"rodata"：配置先のメモリリージョンがTA_NOWRITE属性で，メモリオブジェ
			　クト属性にTA_EXECが設定されていない場合
	"data"：配置先のメモリリージョンがTA_NOWRITE属性でなく，メモリオブ
			ジェクト属性にTA_MEMINIが設定されている場合
	"prsv"：配置先のメモリリージョンがTA_NOWRITE属性でなく，メモリオブ
			ジェクト属性にTA_MEMPRSVが設定されている場合
	"bss"： 配置先のメモリリージョンがTA_NOWRITE属性でなく，メモリオブ
			ジェクト属性にTA_MEMINIもTA_MEMPRSVも設定されていない場合
	"sdata"：配置先のメモリリージョンがTA_NOWRITE属性でなく，メモリオブ
			 ジェクト属性にTA_MEMINIとTA_SDATAが設定されている場合
	"sprsv"：配置先のメモリリージョンがTA_NOWRITE属性でなく，メモリオブ
			 ジェクト属性にTA_MEMPRSVとTA_SDATAが設定されている場合
	"sbss"：配置先のメモリリージョンがTA_NOWRITE属性でなく，メモリオブ
			ジェクト属性にTA_MEMINIもTA_MEMPRSVも設定されておらず，
			TA_SDATAが設定されている場合

なお，ショート定数データセクション（rosdata）は，"sdata"となる．

(3) 保護ドメイン名

セクションが属する保護ドメインの名称．カーネルドメインの場合は"kernel"，
無所属の場合は"shared"とする．この要素を省略することはない．

(4) 通常操作1のアクセス許可パターン

通常操作1のアクセス許可パターン（acptn1）の値を16進数で表現したもの．配
置先のメモリリージョンがTA_NOWRITE属性の場合には，この要素は省略する．
また，メモリオブジェクト属性が標準的なもので，通常操作1のアクセス許可パ
ターンと通常操作2のアクセス許可パターンが標準的なものである場合にも，こ
の要素は省略する．

(5) 通常操作2のアクセス許可パターン

通常操作2のアクセス許可パターン（acptn1）の値を16進数で表現したもの．メ
モリオブジェクト属性が標準的なもので，通常操作1のアクセス許可パターンと
通常操作2のアクセス許可パターンが標準的なものである場合には，この要素は
省略する．

(6) メモリオブジェクト属性

メモリオブジェクト属性の値を16進数で表現したもの．メモリオブジェクト属
性が標準的なものである場合には，この要素は省略する．

以上の規則より，リンク先のセクション名と，そこにリンクされるセクション
の例を挙げる．

例1）.text_kernel：カーネルドメインにATT_MODで登録したオブジェクトモ
ジュールのコードセクション

例2）.rodata_kernel_1：カーネルドメインに，通常操作2のアクセス許可パター
ンに0x01を指定してATA_MODで登録したオブジェクトモジュールの定数データセ
クション

例3）.data_kernel_1_0：カーネルドメインに，通常操作1のアクセス許可パター
ンに0x01を，通常操作2のアクセス許可パターンにTACP_KERNELを指定して
ATA_MODで登録したオブジェクトモジュールの初期化データセクション

例4）.bss_DOM1_1_ffffffff：保護ドメインDOM1に，通常操作1のアクセス許可
パターンに0x01を，通常操作2のアクセス許可パターンにTACP_SHAREDを指定し
てATA_MODで登録したオブジェクトモジュールの非初期化データセクション

例5）.prsv_shared_50_ffffffff_ffffffff：無所属のメモリオブジェクトとし
て，配置先のメモリリージョンに標準RAMリージョンを，メモリオブジェクト属
性に(TA_UNCACHE|TA_MEMPRSV)を指定し，ATT_SECで登録したセクション

例6）.ROM2_text_kernel_2：カーネルドメインに，配置先のメモリリージョン
に"ROM2"（ROM2はTA_NOWRITE属性のメモリリージョンであることを想定）を，
メモリオブジェクト属性にTA_EXECを，通常操作2のアクセス許可パターンに
0x02を指定して，ATA_SECで登録したセクション

●タスクのユーザスタック領域のリンク方法

タスクのユーザスタック領域をコンフィギュレータで割り付ける場合（前記の
(d-1)），ユーザスタック領域のみを含むセクションを作成する．

作成するセクションは，配置先のメモリリージョンを標準RAMリージョンに，メ
モリオブジェクト属性をTARGET_MEMATR_USTACK（ターゲット依存部で，
TA_MEMPRSVに設定）に，属する保護ドメインをタスクと同じに設定する．ユー
ザスタック領域に対しては，通常操作1のアクセス許可パターンと通常操作2の
アクセス許可パターンは意味を持たないが［NGKI0439］，レッドゾーン方式の
スタック保護を行う場合との整合性を考えて，いずれも，タスクが属する保護
ドメインのみからアクセス可能な値に設定する．参照操作に対するアクセス許
可パターンをどう設定されるか，統合仕様書では規定されていないが，HRP2カー
ネルではタスクと同じに設定する．

作成したセクションは，単独のメモリオブジェクトとして登録する必要がある
（言い換えると，他のセクションとまとめて1つのメモリオブジェクトにしては
ならない）ため，リンク先のセクションも単独のものとする．つまり，次に説
明する固定長メモリプール領域の場合と異なり，ATA_SECしたのと同様には扱わ
ない．

GNU開発環境の場合，リンク前のセクションの名称と，リンク先のセクションの
名称を一致させ，.ustack_<タスク名>とする．例えば，タスク名がTASK1のタス
クのユーザスタック領域は，リンク前後とも，.ustack_TASK1という名称のセク
ション内に配置される．

●固定長メモリプール領域のリンク方法

固定長メモリプール領域をコンフィギュレータで割り付ける場合（前記の(e)）
には，固定長メモリプール領域のみを含むセクションを作成し，そのセクショ
ンをATA_SECしたのと同様に扱う．

作成するセクションは，配置先のメモリリージョンを標準RAMリージョンに，メ
モリオブジェクト属性をTARGET_MEMATR_MPFAREA（ターゲット依存部で，
TA_MEMPRSVに設定）に，属する保護ドメインとアクセス許可パターンを固定長
メモリプールと同じに設定する．

●メモリオブジェクト，セクション，メモリ保護単位

以上で述べた通り，コンフィギュレータで割り付けたユーザスタック領域を除
いては，配置先のメモリリージョン，属する保護ドメイン，メモリオブジェク
ト属性，通常操作1のアクセス許可パターン（必要ない場合は無視する），通常
操作2のアクセス許可パターンのすべてが一致するメモリオブジェクトは，1つ
のセクションにリンクする（つまり，連続して配置する）．

ここで，1つのセクションの中に，参照操作のアクセス許可パターン（acptn4）
が異なるメモリオブジェクトが含まれる場合には，acptn4が同じものを連続し
て配置する．

統合仕様書では，ATT_SEC／ATA_SECおよびATT_MOD／ATA_MODで登録するメモリ
オブジェクトは，同じ保護ドメインに属し，メモリオブジェクト属性とアクセ
ス許可ベクタがすべて一致するメモリオブジェクトと，1つにまとめて登録され
る場合があるとしている．つまり，1つのメモリオブジェクトにまとめることが
できるのは，1つのセクションの中で，acptn4も一致するものだけである．

また，セクションの中で，メモリオブジェクト属性のTA_MEMINIおよび
TA_MEMPRSVの有無のみが異なるものは，初期化方法の違いのみで，メモリ保護
の観点では違いがない．そこで，これらのセクションは，メモリ保護の際には
1つにまとめて扱うこととし，この単位を，メモリ保護単位と呼ぶことにする．

以上より，1つのメモリ保護単位には一般には複数のセクションが含まれ，1つ
のセクションには一般には複数のメモリオブジェクトが含まれるという関係に
なる．

アプリケーションが配置を決めるメモリオブジェクト（前記の(c)と(d-2)）は，
メモリオブジェクトをまとめる処理を行わず，メモリオブジェクト毎にメモリ
保護単位とする．

●メモリオブジェクトの配置順序

ここでは，コンフィギュレータが，メモリオブジェクトをメモリに配置する順
序について検討する．すなわち，前記の(a)，(b-1)〜(b-7)，(d-1)，(e)のメモ
リオブジェクトがここでの検討対象である．

ここまでで述べた通り，メモリオブジェクトはセクションに，セクションはメ
モリ保護単位にまとめて配置する（つまり，連続して配置する）．そこで，ま
ずはメモリ保護単位の配置順序を検討し，次にメモリ保護単位内でのメモリオ
ブジェクトの配置順序を検討する．

ROMリージョン（TA_NOWRITE属性のメモリリージョン）に配置するメモリ保護単
位については，次のことを考慮して配置する．

・GNU開発環境では，ATT_MODで指定されなかった標準ライブラリ（少なくとも
　libgcc.a）をリンクするためにワイルドカードを使う必要があることから，
　標準的なメモリオブジェクト属性を持つ共有リードオンリー領域を最後に配
　置する．

・言語依存（具体的にはC++用）のセクションを出力するためにも，標準的なメ
　モリオブジェクト属性を持つ共有リードオンリー領域を最後に配置するのが
　都合がよい．

・ターゲットによっては，ベクタテーブルやカーネルの起動番地が先頭に置か
　れている必要があることから，カーネルドメインの標準的なメモリオブジェ
　クト属性を持つ専有リードオンリー領域を先頭に配置する．

この制約を満たすために，メモリ保護単位の配置順序を以下のようにする．

	カーネルドメインの標準的なメモリオブジェクト属性の専有リードオンリー領域
	カーネルドメインのその他のROMに配置すべき領域
	ユーザドメイン1のROMに配置すべき領域
	……
	ユーザドメインnのROMに配置すべき領域
	無所属のその他のROMに配置すべき領域
	無所属の標準的なメモリオブジェクト属性の共有リードオンリー領域

これ以外にはメモリ保護単位を配置する上での考慮事項はなく，どのような順
序で配置しても差し支えないが，ターゲット非依存部では，次の順序で配置す
ることにする．

カーネルドメインのROMに配置すべき領域の中では，標準的なメモリオブジェク
ト属性の専有リードオンリー領域を先頭に配置する必要があることから，メモ
リオブジェクト属性と通常操作2のアクセス許可パターン（acptn2）のいずれも
標準的な領域を最初に配置し，次にメモリオブジェクト属性が標準的でアクセ
ス許可パターンが標準的でない領域を，最後にいずれも標準的でない領域を配
置する．

各ユーザドメインのROMに配置すべき領域の中も，カーネルドメインと同様の順
序で配置する．

無所属のROMに配置すべき領域の中は，標準的なメモリオブジェクト属性の共有
リードオンリー領域を最後に配置する必要があることから，カーネルドメイン
とは逆の順序で配置する．

それぞれの領域の中では，TA_EXEC属性があるメモリ保護単位（コードセクショ
ンなど）を先に配置し，TA_EXEC属性がないメモリ保護単位（定数データセクショ
ンなど）を後に配置する．

メモリ保護単位の中では，acptn4が標準のメモリオブジェクトを最初に配置す
る．ただし，無所属のメモリ保護単位では，acptn4が標準のメモリオブジェク
トを最後に配置する．

RAMリージョン（TA_NOWRITE属性でないメモリリージョン）に配置するメモリオ
ブジェクトについては，次のことを考慮して配置する．

・GNU開発環境では，ATT_MODで指定されなかった標準ライブラリ（少なくとも
　libgcc.a）をリンクするためにワイルドカードを使う必要があることから，
　標準的なメモリオブジェクト属性を持つ共有リードライト領域を最後に配置
　する（標準ライブラリが静的データを持つのは，メモリ保護の観点からは問
　題がある）．

・ユーザスタックのオーバフローが確実に検出できることが望ましい．すなわ
　ち，ユーザタスクのスタック領域をスタックの進行方向にはみ出したアドレ
　スには，そのタスクがアクセスできないメモリ領域が配置されていること．
　さらに，スタックの進行方向の逆方向にはみ出したアドレスについても同様
　になっていることが望ましい．

・MPUを持つターゲットプロセッサにおいて，保護の必要なメモリ領域数を最小
　限にするため，すべての保護ドメインの共有リード専有ライト領域を，連続
　して配置する（Release 2.1.0までは，カーネルドメインの共有リード専有ラ
　イト領域を考え落としていた）．

・すべてのショートデータ領域を連続して配置する．さらに，MPUのメモリ領域
　数を最小限にするため，共有リードライト領域と，（各保護ドメインの）共
　有リード専有ライト領域は，ショートデータ領域と一般の領域を連続して配
　置する（Release 2.1.0までは考慮していなかった）．

・標準ROMリージョンの配置順序と，なるべく同じ方針で配置するのが望ましい．

これを踏まえて，ターゲット非依存部では，次の順序で配置することにする．

	カーネルドメインのその他のRAMに配置すべき領域
	カーネルドメインの専有リードライト領域 … (*1)
	各ユーザドメインに属する各タスクのユーザスタック領域
	ユーザドメイン1のRAMに配置すべき領域
	……
	ユーザドメインnのRAMに配置すべき領域
	無所属のその他のRAMに配置すべき領域
	カーネルドメインの共有リード専有ライト領域
	ユーザドメイン1の共有リード専有ライト領域
	……
	ユーザドメインnの共有リード専有ライト領域
	カーネルドメインの共有リード専有ライト領域（ショートデータ領域）
	すべての共有リード・ライト不可領域（ショートデータ領域）… (*2)
	ユーザドメイン1の共有リード専有ライト領域（ショートデータ領域）
	……
	ユーザドメインnの共有リード専有ライト領域（ショートデータ領域）
	カーネルドメインのその他のRAMに配置すべき領域（ショートデータ領域）
	カーネルドメインの専有リードライト領域（ショートデータ領域）
	ユーザドメイン1のRAMに配置すべき領域（ショートデータ領域）
	……
	ユーザドメインnのRAMに配置すべき領域（ショートデータ領域）
	無所属のその他のRAMに配置すべき領域（ショートデータ領域）
	無所属の標準的なメモリオブジェクト属性の共有リードライト領域（ショート
																データ領域）
	無所属の標準的なメモリオブジェクト属性の共有リードライト領域

(*1)において，カーネルドメインのRAMに配置すべき領域の中で，専有リードラ
イト領域を最後に配置するのは，ユーザスタックのオーバフローを確実に検出
するためである．

(*2)において，共有リード・ライト不可のショートデータ領域を特別扱いして
いるのは，共有リード専有ライトにする意図でATT_MODしたモジュールにショー
ト定数データセクション（rosdata）が含まれていた場合に，このような領域が
作られるためである．

各ユーザドメインのRAMに配置すべき領域の中では，ROMへの配置順序となるべ
く同じ方針とするために，メモリオブジェクト属性と通常操作1および2のアク
セス許可パターン（acptn1およびacptn2）のいずれも標準的なメモリ保護単位
を最初に配置し，次にメモリオブジェクト属性が標準的でアクセス許可パター
ンが標準的でないメモリ保護単位を，最後にいずれも標準的でないメモリ保護
単位を配置する．

カーネルドメインと無所属のRAMに配置すべき領域の中は，それぞれ，標準的な
メモリオブジェクト属性の専有リードライト領域と共有リードライト領域を最
後に配置する必要があることから，ユーザドメインとは逆の順序で配置する．

メモリ保護単位の中では，TA_MEMINI属性があるセクション（初期化データセク
ションなど）を最初に配置し，次にTA_MEMINI属性・TA_MEMPRSV属性ともないセ
クション（非初期化データセクションなど）を，最後にTA_MEMPRSV属性がある
セクションを配置する．

セクションの中では，acptn4が標準のメモリオブジェクトを最初に配置する．
ただし，無所属のメモリ保護単位では，acptn4が標準のメモリオブジェクトを
最後に配置する．

ここで，ユーザドメインが1つしかない場合には，ユーザドメイン1に属するタ
スクのユーザスタックの直後に，ユーザドメイン1の専有リードライト領域が配
置されてしまうが，スタックの進行方向の逆方向にはみ出した側なので，許容
することにする．また，ユーザドメインが複数ある場合でも，ユーザドメイン
の専有リードライト領域が空の場合には，後続のユーザドメインで同様のこと
が起こる可能性がある．

なお，MMUを持ったターゲットシステムで，レッドゾーン方式のスタック保護を
行う場合には，同じユーザドメインに属するタスクのユーザスタック領域が隣
接しないように配置する．必要な場合（全体の半分を越えるユーザタスクが1つ
のユーザドメインに属する場合）には，ダミーのスタック領域を追加してスタッ
ク保護を行う．この配置アルゴリズムについては，下で述べる．

●メモリオブジェクトの配置順序の実装

前述のRAMリージョンの配置順序を実現するため，配置順にソートするために用
いる「配置タイプ」を導入する．配置タイプは，メモリオブジェクトが属する
保護ドメイン等により，以下の通りに定める．

----------------------------------------
【配置タイプ0】
	カーネルドメインのその他のRAMに配置すべき領域
	カーネルドメインの専有リードライト領域
【配置タイプ1】
	各ユーザドメインに属する各タスクのユーザスタック領域
【配置タイプ2】
	ユーザドメイン1のRAMに配置すべき領域
	……
	ユーザドメインnのRAMに配置すべき領域
	無所属のその他のRAMに配置すべき領域
【配置タイプ3】
	カーネルドメインの共有リード専有ライト領域
	ユーザドメイン1の共有リード専有ライト領域
	……
	ユーザドメインnの共有リード専有ライト領域
【配置タイプ4】
	カーネルドメインの共有リード専有ライト領域（ショートデータ領域）
	ユーザドメイン1の共有リード・ライト不可領域（ショートデータ領域）
	……
	ユーザドメインnの共有リード・ライト不可領域（ショートデータ領域）
	無所属の共有リード・ライト不可領域（ショートデータ領域）
【配置タイプ5】
	ユーザドメイン1の共有リード専有ライト領域（ショートデータ領域）
	……
	ユーザドメインnの共有リード専有ライト領域（ショートデータ領域）
【配置タイプ6】
	カーネルドメインのその他のRAMに配置すべき領域（ショートデータ領域）
	カーネルドメインの専有リードライト領域（ショートデータ領域）
	ユーザドメイン1のRAMに配置すべき領域（ショートデータ領域）
	……
	ユーザドメインnのRAMに配置すべき領域（ショートデータ領域）
	無所属のその他のRAMに配置すべき領域（ショートデータ領域）
【配置タイプ7】
	無所属の標準的なメモリオブジェクト属性の
							共有リードライト領域（ショートデータ領域）
【配置タイプ8】
	無所属の標準的なメモリオブジェクト属性の共有リードライト領域
----------------------------------------

各配置タイプの中では，保護ドメイン順（カーネルドメインは最初，無所属は
最後）に配置する．

★未完成★

●レッドゾーン方式におけるユーザスタック領域の配置順序の決定

★未完成★

そこに属するユーザスタック領域の数が最大であるドメインを，DOM_Mとする．

ユーザスタック領域の数の合計が偶数で，そのちょうど半分が，DOM_Mに属する
場合は，次のように配置すればよい．

	DOM_1, DOM_M, DOM_2, DOM_M, DOM_3, DOM_M, DOM_4, DOM_M

ユーザスタック領域の数の合計が奇数で，その半分（切り上げ）が，DOM_Mに属
する場合は，次のように配置すればよい．

	DOM_M, DOM_1, DOM_M, DOM_2, DOM_M, DOM_3, DOM_M, DOM_4, DOM_M

DOM_Mに属するユーザスタック領域数がこれより多いと，ダミースタック領域を
追加する必要がある．このことから，必要となるダミースタック領域の数は，
以下の式で求めることができる．

	DOM_Mに属するユーザスタック領域数 × 2 - 1 - ユーザスタック領域の数の合計

○カーネル構築手順

HRP2カーネルにおけるコンフィギュレータの処理手順は，統合仕様書 2.12.5
節に記載されている通りである．

メモリ保護のための設定情報を，kernel_cfg.cに生成せず，kernel_mem.cに生
成するのは，kernel_cfg.cにすべてのコンフィギュレーション情報を生成する
と，パス3およびパス4において，kernel_cfg.c全体を再度生成する必要があり，
効率が悪いためである．

なお，コンフィギュレーション手順の各パスにおいて，メモリ保護のための設
定情報がどのように決定されているかについては，「メモリオブジェクトのコ
ンフィギュレーション手順（mem_cfg_design.txt）」を参照すること．

○システムサービスの拡張サービスコール化

暫定的な修正として，以下のシステムサービスを，拡張サービスコールにより
呼び出せるように修正する．

・システムログ機能
・シリアルインタフェースドライバ
・システムログタスク

●命名規則

アプリケーションから拡張サービスコールを呼び出す場合の名称：
		zzz_xxx_yyy

拡張サービスコールとして登録する関数（EXTSVC型）
		extsvc_zzz_xxx_yyy
		→ _zzz_zzz_xxx_yyyを呼び出すだけ

拡張サービスコールの処理を行う本体関数
		_zzz_zzz_xxx_yyy

機能番号
		TFN_ZZZ_XXX_YYY

●ヘッダファイルの内容

extsvc_fncode.h
----------------------------------------
#define TFN_ZZZ_XXX_YYY		NNN
----------------------------------------

zzz.h（システムログ機能は例外とする）
----------------------------------------
/*
 *  必要なデータ型や定数の定義
 */

/*
 *  アプリケーションから拡張サービスコールを呼び出すための定義
 */
#ifndef TOPPERS_SVC_CALL

Inline ER_XXXX
zzz_xxx_yyy(....)
{
	return((ER_XXXX) cal_svc(TFN_ZZZ_XXX_YYY, ....));
}

#endif /* TOPPERS_SVC_CALL */

/*
 *  カーネルドメインから関数呼出しで呼び出すための定義
 */
extern ER_XXXX _zzz_zzz_xxx_yyy(.....) throw();

#ifdef TOPPERS_SVC_CALL
#define zzz_xxx_yyy _zzz_zzz_xxx_yyy
#endif /* TOPPERS_SVC_CALL */

/*
 *  拡張サービスコールとして登録するための定義
 */
extern ER_XXXX	extsvc_zzz_xxx_yyy(..., ID cdmid) throw();
----------------------------------------

●機能番号の割付け

	名称				機能番号
	syslog_wri_log		1
	syslog_fwri_log		2
	syslog_rea_log		3
	syslog_msk_log		4
	syslog_ref_log		5
	予約				6
	serial_opn_por		7
	serial_cls_por		8
	serial_rea_dat		9
	serial_wri_dat		10
	serial_ctl_por		11
	serial_ref_por		12
	予約				13
	logtask_flush		14
	予約				15
	check_point			16
	check_assert_error	17
	check_ercd_error	18
	set_bit_func		19
	syslog_flush		20
	test_finish			21

以上

======================================================================
○無効になった検討

●ソフトウェア割込みのネストレベルか拡張サービスコールのネストレベルか？

svclevelに保持する値を，ソフトウェア割込みによってサービスコールを呼び
出したネストレベルとするか，拡張サービスコールのネストレベルとするかに
ついて検討する．

(a) タスク例外処理ルーチンの起動条件のチェックは，どちらの方法でも実現
可能である．ただし，どちらの方法を採るかによって，カーネルドメインのタ
スクがソフトウェア割込みによってras_tex等を呼び出して，タスク例外処理ルー
チンの起動条件が揃った場合の，タスク例外処理ルーチンの実行開始タイミン
グが異なる．

svclevelを，ソフトウェア割込みによってサービスコールを呼び出したネスト
レベルとした場合，自タスクに対するras_tex等でタスク例外処理ルーチンを呼
び出す条件として，svclevel==0のみチェックすればよい．これは，svclevel==0
をチェックすることで，そのタスクがカーネルドメインに属することもチェッ
クしたことになるためである．カーネルドメインのタスクがソフトウェア割込
みでシステムコールを呼び出していた場合にはsvclevel>0となるが，この時は，
ソフトウェア割込みの出口処理でタスク例外処理ルーチンを呼び出せばよい．

一方，svclevelを，拡張サービスコールのネストレベルとした場合，自タスク
に対するras_tex等でタスク例外処理ルーチンを呼び出す条件は，svclevel==0
に加えて，当該タスクがカーネルドメインのタスクであることをチェックする
必要がある．そのため，このチェックがオーバヘッドとなる．ここで，カーネ
ルドメインのタスクがソフトウェア割込みでシステムコールを呼び出していた
場合，サービスコール処理関数の中からタスク例外処理ルーチンが呼び出され
ることになり，タスク例外処理ルーチンの起動タイミングは上の方法とは異な
ることになる．

以上より，どちらの方法でも実現可能ではあるが，オーバヘッドの観点で，前
者の方法がやや都合が良いということが言える．

(b) 待ち禁止状態へ移行させる処理については，拡張サービスコールのネスト
レベルが必要である．

dis_wai／idis_waiにおいて待ち禁止状態に遷移させる条件は，対象タスクが拡
張サービスコールを実行していることである．そのため，前述のsvclevelの使
用に関する制限がない場合には，svclevelを拡張サービスコールのネストレベ
ルとすることで，svclevel>0でこれが判定できる．

これを，ソフトウェア割込みによってサービスコールを呼び出したネストレベ
ルで代用しようとした場合，拡張サービスコールを実行していないにもかかわ
らず，dis_wai／idis_waiにおいて待ち禁止状態を示すフラグをセットしてしま
うことになり，仕様を満たせなくなる．

以上より，拡張サービスコールのネストレベルを保持することが必要である．

(c) 実行中の保護ドメインの管理を(1)または(2-2)の方法で実現するためには，
拡張サービスコールのネストレベルを保持することが必要である．

ただし，実行中の保護ドメインの管理には，(2-1)や(3)の実装方法もあるため，
これが決定的な理由とはならない．

以上の(a)〜(c)の検討の結果，HRP2カーネルでは，svclevelを，拡張サービス
コールのネストレベルとすることとした．

なお，HRPカーネルでは，svclevelを，ソフトウェア割込みによってサービスコー
ルを呼び出したネストレベルとしている．これは，HRPカーネルでは，(c)の実
現においては(2-1)の実装方法を採用し，(b)の実現においては，rel_wai/irel_wai
によって待ち禁止状態に遷移するため，通常のカーネルのサービスコールの実
行中に待ち禁止状態を示すフラグが立っていても，待ち解除されてE_RLWAIが返
るという結果が変わらず，支障がないためである．HRP2カーネルにおいては，
rel_wai／irel_waiとdis_wai／idis_waiを独立させたため，この方法が適用で
きない．

●svclevelの導入

HRP2カーネルでは，次の状況において，タスクが特権モードを実行しているか
否かを判別することが必要になる．

(a) タスク例外処理ルーチンの起動条件をチェックするため
	(a-1) 自タスクに対するras_tex, ena_tex, unl_cpu（カーネルドメインの
		  タスクに対してのみ）
	(a-2) dispatch_r
	(a-3) ret_int_r
	(a-4) ソフトウェア割込みの出口
(b) 待ち禁止状態への遷移条件をチェックするため

(a-1)において，カーネルドメインのタスクに対してのみ判別が必要になるのは，
ユーザタスクの場合には，ソフトウェア割込みの出口でタスク例外処理ルーチ
ンを起動するためである．

●svclevelの使用に関する制限

上述のように，svclevelは，ソフトウェア割込みの入口処理でインクリメント
する．しかし，この方法には次の制限がある．

ソフトウェア割込みによって割込みが禁止されないプロセッサ（例えば，
M68040）においては，ソフトウェア割込みが受け付けられ，特権モードになっ
ているにもかかわらず，svclevelフィールドが0になっている（一貫性が崩れた）
状態になることが避けられない．

そのため，このようなプロセッサでは，他タスクが特権モードを実行している
か否かや，非タスクコンテキストから実行状態のタスクが特権モードを実行し
ているか否かを，svclevelを使って判別することができない．それに対して，
自タスクが特権モードを実行しているか否かは，svclevelにより判別すること
ができる．

この制限は，svclevelを，(a-3)および(b)の目的に使用する場合に，問題とな
る．解決策については，個別に検討する必要がある．

●ターゲット非依存に行う方法の難しさ

この判別を，ターゲット非依存に行う方法について検討したが，以下の理由で
難しいという結論に達した．

dis_wai/idis_waiを，svclevelを使わずに実装するためには，対象タスクが拡
張サービスコールを実行中であるか否かにかかわらず待ち禁止状態を示すフラ
グをセットし，そのフラグがセットされておりかつ拡張サービスコールが実行
中の時のみ待ち禁止状態であると判断する方法が考えられる．しかしこの方法
では，待ち禁止状態を示すフラグを適切なタイミングでクリアすることができ
ない．

具体的には，拡張サービスコールへの入口では，アトミックにクリアすること
ができない（それができれば，svclevelもアトミックに更新できる）．出口で
クリアする方法では，拡張サービスコールの外を実行中に発行されたdis_wai/
idis_waiが，次に発行された拡張サービスコール中に引き継がれてしまい，仕
様を満たさない．

●メモリオブジェクト初期化情報

それに対してメモリオブジェクト初期化情報は，コンフィギュレータによる生
成しやすさを考慮して設計したデータ構造である．そのため，このデータ構造
を用いて，指定された番地がどのメモリオブジェクトに含まれるかを検索する
のは現実的でない．そこで，メモリオブジェクト初期化情報からメモリオブジェ
クト初期化ブロックを動的に生成するコードを用意する．
======================================================================
