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

第08回 TOPPERS/FMPカーネルのマルチプロセッサのための仕様

※本内容は,TOPPERS/FMP Kernel(Release 1.0.1)に対応しています.

TOPPERS/FMPカーネルは,新世代カーネルの基盤であるTOPPERS/ASPカーネルのマルチプロセッサ対応版です.TOPPERS/ASPカーネルで提供しているAPIは,TOPPERS/FMPカーネルでもそのまま提供されます.さらに,マルチプロセッサ用リアルタイムOSとして機能するために,TOPPERS/ASPカーネルにはない機能を提供しています.今回はTOPPERS/FMPカーネルが持つ,マルチプロセッサ用リアルタイムOSのための以下の仕様の概要について解説します.

(1)タスクマイグレーション
(2)スピンロック機能
(3)クラス
タスクがプロセッサ間を移動することを,マイグレーションと言います.
TOPPERS/FMPカーネルでは,負荷変動への対応が可能なようにタスクを移動させるAPIを追加しています.カーネル自身が自動的に負荷分散のためにタスクのマイグレーションを行うと,TOPPERS/SMPカーネルのデメリットもあった,リアルタイム性の保証が困難となってしまいます(第7回参照).そこで,TOPPERS/FMPカーネルではカーネル自身が負荷分散を行うことはしないが,タスクをマイグレーションするサービスコールを用意することで,ユーザがミドルウェアとして負荷分散を実現することができる仕組みを提供しています.
TOPPRES/FMPカーネルでは,タスクのマイグレーションを以下のサービスコールで実現します.

mig_tsk(ID tskid,ID prcid)
tskidで指定したタスクをprcidで指定したプロセッサに移動させる.

システムコールの最悪実行時間,平均実行時間を抑えるために,マイグレーション可能な条件を以下のように定めています.
・タスクから自プロセッサに割り付けられている他のタスクに対して
・自タスクに対して

つまり言い換えると,mig_tskサービスコールの発行には,以下のような制限があります.
・非タスクコンテキストからは発行できない
・他プロセッサに割り付けられているタスクには発行できない

mig_tskサービスコールの詳細については,後日解説します.

mact_tsk(ID tskid,ID prcid)
tskidで指定したタスクをprcidで指定したプロセッサで起動する.

このサービスコールは,タスクを起動するサービスコール(act_tsk)を,マルチプロセッサ用に拡張したものです.mig_tskとは異なり,指定できるタスクに制限はありません.
loc_spn(ID spinid)/iloc_spn(ID spinid),unl_spn(ID spinid)/inul_spn(ID spinid)

タスクと割込みハンドラ間の排他制御を行う機能

第4回で解説したように,マルチプロセッサでは一般に,あるプロセッサで割り込みを禁止しても,他のプロセッサの割込みまでは禁止することはできません.たとえ,全プロセッサで割込みを禁止したとしても,その時点で動作している割込みハンドラやタスクの処理までは止めることができません.また,あるプロセッサでCPUロック状態としても,他のプロセッサに対してはロックされず、ディスパッチも発生します。したがって,CPUロック状態を使って、他のプロセッサとの排他制御は実現することができません.そこで,スピンロックを使ってプロセッサ間の排他制御を実現しています。そこで, TOPPERS/FMPカーネルでは上記のサービスコールを提供し,アプリケーションでもCPU間の排他制御の実現を可能にしています.
アプリケーションで排他制御をおこなう場合は,タスク間の排他制御はセマフォ等を使用し,割込みハンドラ間,割込みハンドラとタスク間の排他制御はスピンロック機能を使用するというように使い分ける必要があります.
クラスとは,マルチプロセッサに対応するために用いるカーネルオブジェクトの集合のことです.たとえば,タスクの初期割り付けプロセッサや,カーネルデータの配置場所の指定をするために,クラスという概念を導入しています.

・システムには複数のクラスが定義されている.
・オブジェクトはいずれかのクラスに属する必要がある.
・オブジェクトが属するクラスは静的に指定する(動的に変更できない).

※カーネルオブジェクトとは
カーネルが管理対象とするソフトウェア資源のこと.例えば,タスク,セマフォ,イベントフラグなど

クラスが初期状態で所属できるプロセッサは1つです.クラスに指定する属性の1つに初期割り付けプロセッサがあります.また,クラスにはそのクラスに所属するオブジェクト(タスクやセマフォなど)を指定します.同じクラスを指定したオブジェクトは,そのクラスの定義にしたがって振舞います.オブジェクトは1つのクラスに属し,クラス間を移動できません.
図1に示すように,オブジェクトはいずれかのクラスに属します.クラスの初期割り付けプロセッサ属性で,そのクラスが初期状態でどのプロセッサに所属するかを決定します.



【図1 クラスの初期割り付けプロセッサ属性】

クラスの初期割り付けプロセッサ属性に指定したプロセッサに,クラスとオブジェクトを配置した例を図2に示します.



【図2 クラスとオブジェクトの配置例】

クラスに定義する内容

1.初期割付けプロセッサ (処理単位)
2.マイグレーション可能なプロセッサのマスク指定(処理単位)
3.タスクコントロールブロックのセクション指定
4.オブジェクトコントロールブロックのセクション指定
5.タスクスタックのセクション指定
6.オブジェクトロック(処理単位以外)

※処理単位とは
対応するアプリケーションプログラムを持つオブジェクトのこと.例えば,タスクや割込みハンドラは,対応する関数をアプリケーション側で用意して,それをカーネルが実行しています.具体的には,タスク,割込みハンドラ,タイムイベントハンドラ,CPU例外ハンドラなどが該当します.

1.初期割り付けプロセッサ
処理単位はいずれかのプロセッサに割り付けられています.その処理単位が実行されると,割り付けられたプロセッサで実行されます.処理単位の登録時にその処理単位が割付けられたプロセッサを初期割付けプロセッサと呼びます.図2において,クラス2の初期割り付けプロセッサはプロセッサ2です.よってクラス2に所属する処理単位であるタスク2_1の初期割り付けプロセッサはプロセッサ2となります.act_tsk(タスク2_1)とすると,タスク2_1はプロセッサ2で起動します.

2. マイグレーション可能なプロセッサのマスク指定
このマスク指定により,処理単位がマイグレーションすることができるプロセッサを指定することができます.マイグレーションするサービスコールの中で,その処理単位が所属するクラスのマスク指定以外のプロセッサへのマイグレーションが要求されていた場合は,エラーを返しています.
たとえば,クラス2のマイグレーション可能なプロセッサのマスク指定は,1,2,3と定義されているとします.つまりどのプロセッサに対しても,マイグレーションが可能ですので,クラス2に所属するタスク2_1はプロセッサ1とプロセッサ3にマイグレーション可能です(図3).



【図3 クラス2に所属するオブジェクトのマイグレーション】

一方,クラス3のマイグレーション可能なプロセッサのマスク指定は,1,3と定義されているとします.つまり,プロセッサ2に対して,マイグレーションができません.クラス3に所属するタスク3_2は,プロセッサ1へはマイグレーション可能となりますが,プロセッサ2へはマイグレーションできません.



【図4 クラス3に所属するオブジェクトのマイグレーション】

3.タスクコントロールブロックのセクション指定
4.オブジェクトコントロールブロックのセクション指定
5.タスクスタックのセクション指定

TOPPERS/FMPカーネルは,多彩なアーキテクチャに対応しています(第7回で解説).ターゲットハードウェアのメモリアーキテクチャは,共有型であれば,対象共有メモリ型(UMA),分散共有メモリ型(NUMA)どちらにも対応できます.このどちらにも対応できるように,タスクコントロールブロック,オブジェクトコントロールブロック,タスクのスタックの配置場所をターゲットハードウェアのメモリアーキテクチャに最適となるように,クラスの定義で指定することができます.

たとえば,プロセッサ1,2,3ともに,全プロセッサからアクセス可能なローカルメモリをそれぞれ持ち,かつグローバルメモリも持つというNUMA型のアーキテクチャの場合で考えてみます.クラス4に所属するオブジェクトは,プロセッサ3で閉じた処理を行うために使用するものと仮定すると,クラス4のタスクコントロールブロック,オブジェクトコントロールブロック,タスクスタックはプロセッサ3のローカルメモリに配置するのが適当と言えます.クラス1,2,3に所属するオブジェクトは,プロセッサ間を跨ぐ処理を行うために使用すると仮定すると,クラス1,2,3のタスクコントロールブロック,オブジェクトコントロールブロック,タスクスタックはグローバルメモリに配置するのが適当と言えます(図5).



【図5 コントロールブロックなどの配置例】

6.オブジェクトロック
FMPカーネルでは,3種類のロックの粒度をサポートしています.

(1) ジャイアントロック方式 
  システムで1つのロックを持つ.
(2) プロセッサロック方式
  プロセッサごとにタスクのためのロックと,プロセッサごとにオブジェクトのためのロックを持つ.
(3) 細粒度ロック方式
  プロセッサごとにタスクのためのロックと,オブジェクトごとにオブジェクトのためのロックを持つ,

クラスで指定するオブジェクトロックは,プロセッサロック方式を選択した場合に,どのプロセッサのロックでプロセッサロックを行うか.ということを指定するためのものになります.
FMPのロック方式については,後日解説をいたします.

◎クラスの定義の仕方
ここまで,クラスに定義する内容について解説してきました.それでは,クラスの定義はどのように行えばいいのでしょうか?
クラスに定義する内容は,ターゲットハードウェアのアーキテクチャに依存します.そこで,このクラスの定義は,OSのターゲット依存部の実装者がそのアーキテクチャに最適な組み合わせをあらかじめ用意しておく方法が現実的であると判断しています.
そこで,クラスの定義はOSのターゲット依存部の実装者が,OSのターゲット依存部の中にあらかじめ記載しています.
\fmp\target\ターゲット名¥target.tf

つまりアプリケーションプログラム内で定義するものではありませんので,基本的にはアプリケーションエンジニアが毎回定義する必要はありません.基本的には,カーネルのソースコード内にある,OS実装者が用意したクラスの定義を使用することになります.もちろん,この部分をアプリケーションエンジニアが変更することも可能です.その場合には,アプリケーションエンジニアがターゲットハードウェアのアーキテクチャを熟知している必要があります.

\fmp\target\ターゲット名¥target.tf
 
$ クラス TCL_1(クラスID 1)に関する指定
$
$ 初期割付けプロセッサ(ID指定)
$CLASS_AFFINITY_INI[1] = 1$
$ マイグレーション可能なプロセッサのマスク指定
$CLASS_AFFINITY_MASK[1] = 0x0000000f$
$ オブジェクトロック
& P_LOCKの場合に使用するオブジェクトロックを持つプロセッサのIDを指定
$CLASS_OBJ_LOCK[1] = 1$
$ タスクスタックのセクション指定
$CLASS_SECTION_TSKSTK[1]= "__attribute__((section(\"._kernel_prc1s_tstack\"),nocommon))"$
$ タスクコントロールブロックのセクション指定
$CLASS_SECTION_TSKCB[1]= "__attribute__((section(\"._kernel_prc1s_tskcb\"),nocommon))"$
$ オブジェクトコントロールブロックのセクション指定
$CLASS_SECTION_OBJCB[1]= "__attribute__((section(\"._kernel_prc1s_objcb\"),nocommon))"$


◎オブジェクトの定義の仕方
OSのターゲット依存部の実装者が用意したクラスに,それぞれどんなオブジェクトを配置するかを決定するのは,アプリケーションエンジニアの仕事になります.
アプリケーションエンジニアが記載するコンフィギュレーションファイルの,クラスの囲みに,そのクラスに属するオブジェクト生成の静的APIを記述します.

プログラム名.cfg
CLASS(TCL_1){
CRE_TSK(TASK1, .....);
CRE_SEM(SEM1, ....);
}


上記をコンフィギュレーションファイルに記載することで,クラス1にタスク(TASK1)とセマフォ(SEM1)が配置されます.