マルチプロセッサ用リアルタイムOSの解説

第09回 TOPPERS/FMPカーネルの実装設計方針/データ構造の設計

今回は,TOPERS/FMPカーネル(以下,FMPカーネル)の実装設計方針と,データ構造について解説します.

FMPカーネルは,TOPPERS新世代カーネルの基盤となるTOPPERS/ASPカーネル(以下,ASPカーネル)をマルチプロセッサ拡張したリアルタイムカーネルです.
FMPカーネルの実装設計を行うにあたり,ASPカーネルで定めた方針に加えて,次の方針が設定されました.

(1) 様々なアーキテクチャのマルチプロセッサシステムのサポート
マルチプロセッサシステムのアーキテクチャは多種多様であり,その上で動作するOSは,それらのアーキテクチャを有効に用いて動作する必要があります.FMPカーネルでサポートする,マルチプロセッサシステムのアーキテクチャのバリエーション例を以下に示します.

FMPカーネルでサポートするアーキテクチャバリエーション
■プロセッサ数
・2プロセッサ
・3プロセッサ以上

■プロセッサ間の排他制御機構
・排他制御命令
・専用ハードウェア
  ・同時に作成可能な排他制御区間(ロック)の個数

■メモリ
・プライベートメモリ
  特定のプロセッサのみアクセス可能なメモリ
・ローカルメモリ 
  全プロセッサからアクセス可能であるが,特定のプロセッサからのアクセスが高速なメモリ
・グローバルメモリ 
  全プロセッサから同じ速度でアクセス可能なメモリ

■タイマ
・プロセッサ毎の個別のタイマ
・システムに単一のタイマ

これらのアーキテクチャバリエーションに対して効率のよい実装となるように,FMPカーネルでは,カーネルの機能や実装を以下のバリエーションの組み合わせ(コンフィギュレーション項目)から選択可能にしています.

選択できる項目について,以下に示します.

OS内部のロック単位
システムコールを実行する際にプロセッサ間の排他実行が必要な場合に,排他制御するためのロックの単位です.TOPPERS/FMPカーネルでは以下の3つのロック単位を用意しています.ロックの単位が粗いと,取得・解放のオーバヘッドは小さくなりますが,実行の並列性が低下します.反対に,ロックの粒度を細かくすると,複数のロックを取得・解放するため,実行オーバヘッドは大きくなるが,実行の並列性は向上します.プロセッサ数が少ない場合(2個程度)は,ロック単位が粗いほうが有利であり,プロセッサが多くなるにつれて,粒度の細かいロックのほうが有利となります.
どのロックアーキテクチャを選択するかは,ターゲット依存部で指定します.


・ジャイアントロック方式
ジャイアントロックは,システムで1つのロックを使用する方法です(図1).すべてのシステムコールはシーケンシャルに実行されます(図2).



【図1 ジャイアントロック方式】



【図2 すべてのシステムコールはシーケンシャルに実行】


・プロセッサロック方式
プロセッサ毎にタスクロックとオブジェクトロックを用いる方式です(図3).よって,プロセッサ数 x 2 個のロックが必要となります.プロセッサロック方式を採用すると,異なるプロセッサのロックに関連付けられたオブジェクトに対するシステムコールはお互いに影響を与えず並列に実行可能となります.



【図3 プロセッサロック方式】


・細粒度ロック方式
プロセッサ毎にタスクロック,オブジェクト毎にオブジェクトロックを用いる方式です(図4).ロックの数に上限がない場合に採用できます.細粒度ロック方式を採用すると,タスクに関しては,各プロセッサが自プロセッサに割り付けられたタスクに対して,オブジェクトに関しては,異なるオブジェクトに対してシステムコールを実行する限りは,お互いに影響を与えず並列に実行可能となります.




【図4 細粒度ロック方式】


スピンロックアーキテクチャ
スピンロックは,マルチプロセッサ対応カーネルにおいて,割込みのマスクとプロセッサ間ロックの取得により,排他制御を行うためのオブジェクトです.FMPカーネルで新たに導入された機能です.この機能は,異なるプロセッサで実行されている処理間の排他制御に使用することを想定しています.このスピンロックは,「タスク同士」,「タスク⇔割込みハンドラ」,「割込みハンドラ同士」で使用可能です.なお,「タスク同士」は一般的にセマフォで排他制御を実現しますが,短い処理(エリア更新のみなど)区間で使用することは認められます.スピンロックの必要性については,第4回を参照してください.この機能は,上記OS内部のロックとは使用方法が異なり,アプリケーション内で排他制御が必要となった場合に使用するためのロックです.つまり,アプリケーションエンジニアが,使用方法を決定します.
このスピンロックの実現方式としては,2種類をサポートしています.

・ネイティブ方式 
ターゲットプロセッサが持つ排他制御機能(CAS,Test&Set命令,LL/SC,Mutex回路)を用いる.スピンロックの数だけ,排他制御機能が必要となる.

・エミュレーション方式
他のシステムコールと同様にOS内部のロックを用いて実現する方法.

タイマアーキテクチャ
システム時刻の管理方式として,プロセッサ毎にシステム時刻を持つローカルタイマ方式と,システム全体で1つのシステム時刻を持つグローバルタイマ方式の2つの方式があります.どちらの方式を用いることができるかは,ターゲット定義です.

・ローカルタイマ方式
ローカルタイマ方式では,プロセッサ毎のシステム時刻は,それぞれのプロセッサが更新します.異なるプロセッサのシステム時刻を同期させる機能は,カーネルでは用意しません.

・グローバルタイマ方式
グローバルタイマ方式では,システム中の1つのプロセッサがシステム時刻を更新します.これを,システム時刻管理プロセッサと呼びます.どのプロセッサをシステム時刻管理プロセッサとするかは,ターゲット定義になります.


(2) ROMデータの配置場所の詳細な制御は行わない
ROMデータの配置は細かく指定しないことにしています.これは,全てのプロセッサがプライベートメモリにコピーを持つことを許容できるからです.また,マルチプロセッサシステムにおいて,RAMデータに対してキャッシュを有効とするためには,高価なコヒーレントキャッシュが必須なのに対して,ROMデータのキャッシュは通常のキャッシュで対応できます.そのため,多くのターゲットにおいてROMデータのキャッシュを持つことを前提としています.
そのため,動的に内容が変化しないオブジェクトの初期化ブロックは配列で宣言しています.すべてのプロセッサでその配列を保持しても問題がないためです.一方,動的に変化する管理ブロック(RAMデータ)は,個別の変数で宣言し,保持する場所を最適化できるようにしています.

TOPPERS/FMPカーネルは次のアーキテクチャのマルチプロセッサシステムで動作します.

(a)各プロセッサで,プログラムやROMデータに対して,同一アドレスでアクセス可能であること.それぞれのプロセッサがアクセスする物理的なメモリが異なっていてもよい.
(b)全プロセッサから,同一のアドレスでアクセス可能なRAMがあること.
(c)全てのプロセッサから,任意のプロセッサに対して割込み(プロセッサ間割込み)を発生可能であること
(d)プロセッサ間での排他制御のための機構を持つこと
  例 : test & set 命令, Mutex回路
(e)プロセッサ間の排他制御機構を用いてロックを最低1個作成可能であること
  ・1個のみの場合はジャイアントロック方式のみサポート可能
  ・プロセッサ毎に2個+1個作成可能であるとプロセッサロック方式をサポート可能
  ・ロックの作成個数に上限がないと細粒度ロック方式もサポート可能
(f)各プロセッサがユニークなIDを持ち,ソフトウェアから自プロセッサを判 別可能であること.

データ構造の設計
TOPPERS/FMPカーネルでは,メモリアーキテクチャによって実装の最適化が可能なようにデータ構造が設計されています.

(1)プロセッサーコントロールブロック(PCB)
TOPPERS/ASPカーネルではシステム全体で1つ,p_runtsk,p_schedtskという変数を用意して,カーネル内のスケジューリングを行ってきました.TOPPERS/FMPカーネルでは,プロセッサごとにスケジューリングを行いますので,これらの変数はプロセッサごとに必要となります.このように,プロセッサごとに持つ必要のある変数を,PCBという構造体で保持しています.

PCBのメンバ
/kernel/mp.hに定義があります.

【表1 TOPPERS/FMPカーネルのPCBメンバ】

  メンバ  意味
1 ID prcid  プロセッサID ※FMP独自
2 bool_t  kerflg  カーネル動作状態フラグ ※FMP独自
3 LOCK  tsk_lock  ジャイアントロック以外の場合
タスクロック ※FMP独自
4 LOCK  obj_lock  プロセッサロックの場合
オブジェクトロック ※FMP独自
5 LOCK *p_firstlock  1段目の取得中のロック ※FMP独自
6 LOCK *p_secondlock  2段目の取得中のロック ※FMP独自
7 TCB  *p_runtsk  実行状態のタスク
8 TCB  *p_schedtsk  最高優先順位のタスク
9 bool_t  reqflg  ディスパッチ/タスク例外処理ルーチン起動要求フラグ
10 bool_t  disdsp  ディスパッチ禁止状態であることを示すフラグ
11 bool_t  dspflg タスクディスパッチ可能状態を表すフラグ
12 ID locspnid  取得スピンロックID ※FMP独自
13 QUEUE ready_queue[TNUM_TPRI]  レディキュー
14 uint16_t ready_primap  レディキューサーチのためのビットマップ
15 TEVTCB   *p_tevtcb  タイムイベントコントロールブロックへのポインタ 
※FMP独自
16 TPCB    target_pcb  ターゲット依存のプロセッサコントロールブロック
※FMP独自


PCBの実体は,kernel_cfg.cに記述されています.kernel_cfg.cは,アプリケーションエンジニアが記述したコンフィギュレーションファイルを元に,コンフィギュレータが自動生成するファイルです.そのkernel_cfg.cに,PCBの実体はプロセッサ毎に異なる変数として生成されます.さらに,各プロセッサのPCBにアクセスするため,PCBのポインタの配列(p_pcb_table[])も生成しています.
 
PCBの実体
    PCB _kernel_prc1_pcb;
    PCB _kernel_prc2_pcb;

PCBへのアクセス用配列
    PCB* const p_pcb_table[TNUM_PRC] = {
      &_kernel_prc1_pcb,
      &_kernel_prc2_pcb
    };
 
カーネル内で,自プロセッサのPCB及び,PCB内の要素にアクセスするために,インライン関数が用意されています.

\kernel\pcb.h
get_my_p_pcb() / get_mp_p_pcb(prcid)

システムコールでは,上記インライン関数で取得したPCBへのポインタをローカル変数 my_p_pcb へ保存して,この変数を経由してプロセッサ固有のデータへアクセスしています.
たとえば,自プロセッサのPCB内のメンバ p_runtskにアクセスするのは,以下のように行います.

PCB *my_p_pcb = get_my_p_pcb();
my_p_pcb->p_runtsk = ・・・・

タスク管理機能の1つである,タスクを起動するシステムコール(act_tsk)では,対象タスクの所属するPCBのレディキューを操作する必要があります.このような場合には,どのようにPCBのメンバであるレディキューにアクセスすればよいのでしょうか?対象タスクが,現在どのプロセッサに所属しているかは,タスクごとにTCBのメンバに登録されています.TOPPERS/FMPカーネルのタスクコントロールブロック(TCB)は,TOPERS/ASPカーネルのTCBに対して,いくつかメンバが追加されています(表2).



【表2 TOPPERS/FMPカーネルのTCBメンバ】

  メンバ   意味
1 QUEUE task_queue タスクキュー
2 const TINIB *p_tinib   初期化ブロックへのポインタ
3 uint8_t tstat   タスクの状態
4 BIT_FIELD_UINT priority   現在の優先度
5 BIT_FIELD_BOOL actque   起動要求キューイング
6 BIT_FIELD_BOOL wupque 起床要求キューイング
7 BIT_FIELD_BOOL enatex   タスク例外処理許可状態
8 TEXPTN texptn   保留例外要因
9 PCB *p_pcb   動作するPCB  ※FMPで追加
10 CTXB tskctxb   タスクコンテキストブロック
11 ID actprc   次回起動時割り付けプロセッサ  ※FMPで追加
12 ER wercd   待ち解除時のエラーコード  
※ASPでは分離し,スタック上に確保
13 TMEVTB tmevtb   タイムイベントブロック  
※ASPでは分離し,スタック上に確保
14 WOBJCB *p_wobjcb   待ちオブジェクトの管理ブロック  
※ASPでは分離し,スタック上に確保
15 WINFO_OBJ winfo_obj   オブジェクト毎の待ち情報ブロック  
※ASPでは分離し,スタック上に確保



TCBは,そのタスクを実行するプロセッサのPCBへのポインタをメンバとして持っています.よって,対象タスクの所属するPCBのレディキューにアクセスする場合には,TCBメンバである,PCBへのポインタをさすp_pcbを使用して以下のように記述します.
p_tcb->p_pcb->ready_queue

TOPPERS/ASPカーネルでは,待ち情報管理ブロックWINFO(WINFO_WOBJ)やタイムイベントブロックTMEVTB は,TCBから分離して,タスクスタック上に確保していました(図5).これらの情報は,タスクが待ち状態でないときは必要のない情報ですので,TCBから切り離して,待ち状態となって必要な時だけタスクのスタックに領域を確保するという方法をとっていました.このようにTOPPERS/ASPカーネルではできるだけ,RAM使用量を抑えるという設計方針で設計されています.




【図5 ASPカーネルにおけるオブジェクト待ち時のデータ構造】

しかし,この方法はマルチプロセッサ用RTOSには適用できません.なぜなら,タスクスタックをタスクが所属するプロセッサのプライベートメモリに配置した場合,他のプロセッサからアクセスすることができません.例えば,待ち解除時のエラーコードは,解除した側のシステムコール内で格納されますので,他のプロセッサから格納する可能性もあります.その時,wercd(待ち解除時のエラーコード)が,他のプロセッサからアクセスできないプライベートメモリに配置されているとアクセスできません.そのため,FMPカーネルでは,待ち情報管理ブロックやタイムイベントブロックをスタック上ではなく,常にTCB内に確保しています(図6).




【図6 FMPカーネルにおけるオブジェクト待ち時のデータ構造】

(2)タイムイベントコントロールブロック(TEVTCB)
タイムイベントコントロールブロックは,タイムイベントに関連したデータ構造(current_time)等を含む構造体です.TOPPERS/ASPカーネルでは,これらの情報をシステム全体で一つの変数で管理していました.TOPPERS/FMPカーネルではローカルタイマ方式を選択した場合は,プロセッサごとにこれらを管理する必要があるため,タイムイベントコントロールブロックは,プロセッサごとに生成されます.グローバルタイマ方式を選択した場合は,システム全体で1つタイムイベントコントロールブロックが生成されます.

TEVTCBのメンバ
/kernel/time_event.hに定義があります.

【表3 TOPPERS/FMPカーネルのTEVTCBメンバ】

  メンバ  意味
1 EVTTIM current_time  現在のシステム時刻(単位: ミリ秒)
2 EVTTIM next_time  次のタイムティックのシステム時刻(単位: ミリ秒)
3 uint_t  next_subtime  システム時刻積算用変数
4 bool_t pend_update  システム時刻が未更新であることを示すフラグ
5 uint_t last_index  タイムイベントヒープの最後の使用領域のインデックス
6 TMEVTN  *p_tmevt_heap  タイムイベントヒープへのポインタ



TEVTCBの実体は,kernel_cfg.cに記述されています.kernel_cfg.cは,アプリケーションエンジニアが記述したコンフィギュレーションファイルを元に,コンフィギュレータが自動生成するファイルです.そのkernel_cfg.cに,TEVTCBの実体はプロセッサ毎に異なる変数として生成されます.

TEVTCBの実体
TEVTCB _kernel_prc1_tevtcb;
TEVTCB _kernel_prc2_tevtcb;

TEVTCBへのアクセス用配列
TEVTCB* const _kernel_p_tevtcb_table[TNUM_PRC] = {
&_kernel_prc1_tevtcb,
&_kernel_prc2_tevtcb
};

PCBには,そのプロセッサで使用する TEVTCB へのポインタを持っていますので,PCBを経由して,TEVTCBにアクセスできます.TEVTCBへのポインタをpcb経由とするのは,ローカルタイマ方式とグローバルタイマ方式でのTEVTCBの持ち方をの違いを吸収するためです.