拡張方法

Home

モジュールドライバを書く

モジュールドライバは,ADC や I/O レジスタなどのデータ収集モジュールへの,Kinoko からのインターフェースです.全てのモジュールに共通するインターフェースとして,抽象クラス TRoomModule が定義されており,この中で Read() や WaitData() など多くのモジュールに共通する操作が宣言されています.各モジュールに対応するモジュールドライバは,これらの操作のうち必要なものをオーバライドすることにより,Kinoko からのインターフェースを実装します.

VME および CAMAC のモジュール用に,TRoomVmeModule と TRoomCamacModule クラスがそれぞれ TRoomModule クラスから派生されています.これらは VME および CAMAC のコントローラドライバのポインタを保持しており,これを介して VME や CAMAC にアクセスする基本的な機能を提供します.各モジュールに対応するモジュールドライバでは,これらの基本機能を利用してインターフェースを実装することになります.

クラス TRoomModule は src/kernel/lib-common/room/RoomModule.hh に定義されています.以下は,このクラスの定義の抜粋です.
class TRoomModule: public TRoomServiceRequester { protected: TRoomModule(const std::string& ModuleType, const std::string& ModelName); public: virtual ~TRoomModule(); // コントロール関係 // virtual bool Probe(void) throw(THardwareException); virtual int Initialize(int InitialState = 0) throw(THardwareException); virtual int Finalize(int FinalState = 0) throw(THardwareException); virtual int Enable(int Address = -1) throw(THardwareException); virtual int Disable(int Address = -1) throw(THardwareException); virtual int IsEnabled(int Address = -1) throw(THardwareException); virtual int IsBusy(int Address = -1) throw(THardwareException); virtual int Clear(int Address = -1) throw(THardwareException); // データ入出力関係 // /* シングル入出力 */ virtual int Read(int Address, int &Data) throw(THardwareException); virtual int Write(int Address, int Data) throw(THardwareException); /* 連続入出力 */ virtual int SequentialRead(int Address, int Data[], int MaxSize) throw(THardwareException); virtual int SequentialWrite(int Address, int Data[], int Size) throw(THardwareException); virtual int NextNumberOfDataElements(int Address = -1) throw(THardwareException); /* ブロック入出力 */ virtual int BlockRead(int Address, void *Data, int MaxSize) throw(THardwareException); virtual int BlockWrite(int Address, const void *Data, int Size) throw(THardwareException); virtual int NextDataBlockSize(int Address = -1) throw(THardwareException); // 割り込み処理関係 // virtual int EnableInterrupt(int SignalId = 0) throw(THardwareException); virtual int DisableInterrupt(void) throw(THardwareException); virtual int ClearInterrupt(void) throw(THardwareException); // イントロスペクション関係 // virtual int NumberOfChannels(void) throw(THardwareException); virtual int AddressBitLength(void); virtual int DataBitLength(void); // その他 // virtual int ReadRegister(int Address, int& Data) throw(THardwareException); virtual int WriteRegister(int Address, int Data) throw(THardwareException); virtual bool HasData(int Address = -1) throw(THardwareException); virtual bool WaitData(unsigned TimeOut_sec = 1) throw(THardwareException); };
各メソッドの意味を以下に示します.モジュールドライバでは,このうち必要なものだけをオーバーライドすればいいことに注意してください.オーバーライドされていない操作が呼び出された場合は,デフォルトの動作がもしあればそれが実行され,そうでない場合は例外 THardwareException が投げられます.
= = = = = [コントロール関係] = = = = =

bool Probe(void) throw(THardwareException)
モジュールが接続されていて,正常に応答するなら true を返す.通常は Identification レジスタなどをチェックする.モジュールがこの機能を持っていない場合は無条件に true を返してよい(オーバーライドしない場合の振舞い).

int Initialize(int InitialState = 0) throw(THardwareException)
モジュールを初期化する.オーバーライドしなければなにも行なわない.

int Finalize(int FinalState = 0) throw(THardwareException)
モジュールの終了処理をする.オーバーライドしなければなにも行なわない.

int Enable(int Address = -1) throw(THardwareException)
指定されたチャンネル(負の場合は全チャンネル)をイネーブルする.FADC のスタートやスケーラのカウント開始など.

int Disable(int Address = -1) throw(THardwareException)
指定されたチャンネル(負の場合は全チャンネル)をディスエーブルする.スケーラのカウント停止など.

int IsEnabled(int Address = -1) throw(THardwareException)
指定されたチャンネル(負の場合は全チャンネル)がイネーブルされていたら 1 を,そうでなければ 0 を返す.

int IsBusy(int Address = -1) throw(THardwareException)
指定されたチャンネル(負の場合は全チャンネル)がビジーならば 1 を,そうでなければ 0 を返す.

int Clear(int Address = -1) throw(THardwareException)
指定されたチャンネル(負の場合は全チャンネル)をクリアする.

= = = = = [データ入出力関係] = = = = =

[シングル入出力]

int Read(int Address, int &Data) throw(THardwareException)
指定されたアドレス(チャンネル)からデータを1ワード読み出す.読み出しに成功したら 1 を,データが存在しない場合 0 を返す.FADC や マルチヒット TDC などでは,呼ばれるたびにそのチャンネルの次のデータワードを返し,全てのデータワードを呼んだら 0 をリターンするようにする.エラーの場合は THardwareException を投げる.

int Write(int Address, int Data) throw(THardwareException)
指定されたアドレス(チャンネル)にデータを1ワード書き込む.成功では 1 を返す.エラーの場合は THardwareException を投げる.

[連続入出力]

int SequentialRead(int Address, int Data[], int MaxSize) throw(THardwareException)
MaxSize に指定された数を上限として,指定されたアドレス(チャンネル)にデータがある間データをくり返し読み出して Data[] に記録し,読み出したデータワード数を返す.このメソッドをオーバーライドしない場合,Read() をループする実装が使われる.

int SequentialWrite(int Address, int Data[], int Size) throw(THardwareException)
Data と Size に渡されたデータワード列を指定されたアドレス(チャンネル)に書き込み,書き込んだデータワード数を返す.このメソッドをオーバーライドしない場合,Write() をループする実装が使われる.

int NextNumberOfDataElements(int Address = -1) throw(THardwareException)
次回の SequencialRead() で読み出されるデータワード数(またはその上限)を返す.FADC のワード数や,マルチヒット TDC の最大ヒット数など.この値は次回 SequencialRead() 呼び出しのためのバッファの確保と MaxSize 引数の指定に使われる.実際の読み出しワード数は SequencialRead() の呼び出しにより返されることに注意.

[ブロック入出力]

int BlockRead(int Address, void *Data, int MaxSize) throw(THardwareException)
MaxSize を上限として,指定されたアドレス(チャンネル)からデータブロックを読み出し,Data で指し示される領域に記録して,読み出したサイズを返す.Address 引数の解釈はモジュールによる.チャンネルを示す場合もあれば,ベースアドレスからのオフセットを意味することもある.また,多くの場合,Address 引数は単に無視される.

int BlockWrite(int Address, const void *Data, int Size) throw(THardwareException)
Data と Size で指定されたデータブロックを指定されたアドレス(チャンネル)に書き込み,書き込んだサイズを返す.モジュールの仕様によっては Address 引数の意味はモジュール依存(無視されることが多い).

int NextDataBlockSize(int Address = -1) throw(THardwareException)
次回の BlockRead() で読み出されるデータブロックのサイズ(またはその上限)を返す.この値は次回 BlockRead() 呼び出しのためのバッファの確保と MaxSize の指定に使われる.実際の読み出しサイズは BlockRead() の呼び出しにより返されることに注意.

= = = = = [割り込み処理関係] = = = = =

int EnableInterrupt(int SignalId = 0) throw(THardwareException)
割り込みを許可する.引数の SignalId が 0 でない場合,割り込みでシグナルを発行するようにする.

int DisableInterrupt(void) throw(THardwareException)
割り込みを禁止する.

int ClearInterrupt(void) throw(THardwareException)
割り込みをクリアする.

= = = = = [イントロスペクション関係] = = = = =

int NumberOfChannels(void) throw(THardwareException)
モジュールのチャンネル数を返す.

int AddressBitLength(void)
Read()/SequencialRead() で使用するアドレス(チャンネル)の値を表現するのに必要なビット数を返す.16チャンネル なら 4 など.この値はデータフォーマットの決定に使われる.

int DataBitLength(void)
Read()/SequencialRead() で返されるデータワードのビット数を返す.この値はデータフォーマットの決定に使われる.

= = = = = [その他] = = = = =

int ReadRegister(int Address, int& Data) throw(THardwareException)
Address 引数で指定されたアドレスから値を読み,Data 引数に格納する.

int WriteRegister(int Address, int Data) throw(THardwareException)
Address 引数で指定されたアドレスにData 引数の値を書き込む.

bool HasData(int Address = -1) throw(THardwareException)
指定されたアドレス(チャンネル)にすぐに読めるデータがあるなら true を返す.

bool WaitData(unsigned TimeOut_sec = 1) throw(THardwareException)
引数に指定された時間を上限にデータを待つ.データが来たら true を,タイムアウトの場合は false を返す.

CAMAC モジュールドライバ

CAMAC モジュールドライバを作る際のベースになるクラスは RoomCamacAccess.hh に定義されている TRoomCamacModuleクラスです.このクラスはモジュールインターフェースを規定した抽象クラス TRoomModule から派生されており,CAMAC コントローラドライバのポインタを持ち,CAMAC にアクセスする基本的な機能を実装しています.さらに,多くの CAMAC モジュールで振舞いが共通となる一部のモジュールインターフェースについては,デフォルトの実装が提供されています(もちろん各モジュールドライバでこれらをオーバーライドしても構いません).

TRoomCamacModule のクラス定義の一部を以下に示します.

class TRoomCamacModule: public TRoomModule { public: TRoomCamacModule(const std::string& ModuleType, const std::string& ModelName); virtual ~TRoomCamacModule(); virtual TRoomCamacModule* Clone(void) = 0; public: // CAMAC デバイスにアクセスする機能を提供するメソッド // int Transact(int Function, int Address, int &Data, int &Q, int &X); int Transact(int Function, int Address, int &Data) throw(THardwareException); int Transact(int Function, int Address) throw(THardwareException); int EnableLam(void) throw(THardwareException); int DisableLam(void) throw(THardwareException); int ClearLam(void) throw(THardwareException); bool WaitLam(int TimeOut_sec) throw(THardwareException); bool IsRequestingLam(void); public: // モジュールドライバインターフェースの標準的な実装 // virtual int Read(int Address, int& Data) throw(THardwareException); virtual int Write(int Address, int Data) throw(THardwareException); virtual int ReadRegister(int Address, int& Data) throw(THardwareException); virtual int WriteRegister(int Address, int Data) throw(THardwareException); virtual int Clear(int Address = -1) throw(THardwareException); virtual bool HasData(int Address = -1) throw(THardwareException); virtual bool WaitData(unsigned TimeOut_sec = 1) throw(THardwareException); virtual int EnableInterrupt(int SignalId = 0) throw(THardwareException); virtual int DisableInterrupt(void) throw(THardwareException); virtual int ClearInterrupt(void) throw(THardwareException); virtual int AddressBitLength(void); virtual int DataBitLength(void); protected: // CAMAC で共通に使われる定数 // enum TCamacFunctionTable { fnRead = 0, fnTestLam = 8, fnClear = 9, fnClearLam = 10, fnWrite = 16, fnDisable = 24, fnEnable = 26, _NumberOfCamacFunctions }; enum TCamacAddressTable { adAny = 0, _NumberOfCamacAddresses }; };
モジュールドライバでは,最低限,コンストラクタと Clone() メソッドを実装する必要があります.TRoomCamacModule のコンストラクタは,モジュールの種類と型番の文字列を引数に取ります.また,Clone() メソッドは,自分自身のインスタンスを1つ作成して,そのポインタを返すメソッドです.

以下は,林栄精器の 16ch 12bit 電荷積分型 ADC (RPC-022) の例です.クラス名は原則として,T{種類名}_{ベンダ名}_{型番} とします.また,ファイル名も module-{ベンダ名}_{型番}.hh/cc とします.

// module-Rinei_RPC022.hh // #include "RoomCamacAccess.hh" class TCamacQADC_Rinei_RPC022: public TRoomCamacModule { public: TCamacQADC_Rinei_RPC022(void); virtual ~TCamacQADC_Rinei_RPC022(); virtual TRoomCamacModule* Clone(void); // その他のインターフェースの実装 // // ... };
// module-Rinei_RPC022.cc // #include "RoomCamacAccess.hh" #include "module-Rinei_RPC022.hh" TCamacQADC_Rinei_RPC022::TCamacQADC_Rinei_RPC022(void) : TRoomCamacModule("CamacQADC", "RPC022") { } TCamacQADC_Rinei_RPC022::~TCamacQADC_Rinei_RPC022() { } TRoomCamacModule* TCamacQADC_Rinei_RPC022::Clone(void) { return new TCamacQADC_Rinei_RPC022(); } // その他のインターフェースの実装 // // ...
以下は,TRoomCamacModule が個々のモジュールドライバのために提供する CAMAC デバイスアクセスのためのメソッドです.モジュールドライバでは,これらを使用して,Read() などのインターフェースを実装していくことになります.
int Transact(int Function, int Address, int &Data, int &Q, int &X)
引数に指定されたファンクションとアドレスで CAMAC サイクルを実行する.書き込みサイクルなら Data 引数の値がデータ値に使われ,読み出しサイクルならデータ値が Data 引数に設定される.CAMAC サイクルの Q, X レスポンスがそれぞれ引数の Q, X に返される.戻り値は Q レスポンス.

int Transact(int Function, int Address, int &Data) throw(THardwareException)
引数に指定されたファンクションとアドレスで CAMAC サイクルを実行する.書き込みサイクルなら Data 引数の値がデータ値に使われ,読み出しサイクルならデータ値が Data 引数に設定される.戻り値に Q レスポンスが返される.X レスポンスがない場合には例外 THardwareException が投げられる.上記 Transact() メソッドの簡略版.

int Transact(int Function, int Address) throw(THardwareException)
引数に指定されたファンクションとアドレスでデータを伴わない CAMAC サイクルを実行する.戻り値に Q レスポンスが返される.X レスポンスがない場合には例外 THardwareException が投げられる.上記 Transact() メソッドの簡略版.

int EnableLam(void) throw(THardwareException)
F26 を送って LAM をイネーブルする.

int DisableLam(void) throw(THardwareException)
F24 を送って LAM をディスエーブルする.

int ClearLam(void) throw(THardwareException)
F10 を送って LAM をクリアする.

bool WaitLam(int TimeOut_sec) throw(THardwareException)
引数に指定された時間を上限として LAM を待つ.LAM があれば true を,タイムアウトまたは他の理由で中断された場合は false を返す.

bool IsRequestingLam(void)
F8 を送って LAM を出しているか調べる.
振舞いが多くのモジュールで共通なインターフェースは,TRoomCamacAccess で実装されています.以下は実装済のメソッドとデフォルトの動作のリストです.これらのデフォルトの動作が不都合のときは,各モジュールドライバでオーバーライドすることができます.
int Read(int Address, int& Data) throw(THardwareException);
F0 でデータを読み,Q レスポンスを返す.

int Write(int Address, int Data) throw(THardwareException);
F16 でデータを書き込み,Q レスポンスを返す.

int ReadRegister(int Address, int& Data) throw(THardwareException);
Read() と同じ.

int WriteRegister(int Address, int Data) throw(THardwareException);
Write() と同じ.

int Clear(int Address = -1) throw(THardwareException);
F9 でデータをクリアする.アドレス引数負の場合は 0 にセットされる.(多くのモジュールで,F9 ではアドレスは無視されると思う)

bool HasData(int Address = -1) throw(THardwareException);
IsRequestingLam() を呼ぶ.

bool WaitData(unsigned TimeOut_sec = 1) throw(THardwareException);
WaitLam() を呼ぶ.

int EnableInterrupt(int SignalId = 0) throw(THardwareException);
EnableLam() を呼ぶ.

int DisableInterrupt(void) throw(THardwareException);
DisableLam() を呼ぶ.

int ClearInterrupt(void) throw(THardwareException);
ClearLam() を呼ぶ.

int AddressBitLength(void);
8 を返す.この値は CAMAC サイクルのアドレス幅.

int DataBitLength(void);
24 を返す.この値は CAMAC サイクルのデータワード長.

ほとんどの CAMAC モジュールに対して,これらのデフォルトの実装はうまく働きます.つまり,これらのインターフェースを独自に実装する必要は(CAMAC の場合は)ほとんどありません.

作成したモジュールドライバをシステムに登録するには,RoomDeviceFactory.hh に定義されている TRoomCamacModuleCreator を使用します.若干トリッキーですが,以下のようにモジュールドライバの .cc ファイル内で Creator オブジェクトを static に作成し,そのコンストラクタ引数にモジュールドライバ名とモジュールドライバのインスタンスを渡します.これにより,ドライバのコードがロードされたときに自動でモジュールドライバのインスタンスが作成され,システムに登録されるようになります.

/* module-Rinei_RPC022.cc */ #include "RoomCamacAccess.hh" #include "RoomDeviceFactory.hh" #include "module-Rinei_RPC022.hh" static TRoomCamacModuleCreator Creator( "Rinei_RPC022", new TCamacModule_Rinei_RPC022() ); //...

作成したモジュールドライバの .hh/.cc ファイルを kinoko/src/kernel/lib-common/room に置いて Kinoko をコンパイルしなおせば,Kinoko からこれを利用できるようになります.Makefile を編集する必要はありません.

VME モジュールドライバ

VME モジュールドライバを作る際のベースになるクラスは RoomVmeAccess.hh に定義されている TRoomVmeModule クラスです.このクラスはモジュールインターフェースを規定した抽象クラス TRoomModule から派生されており,VME コントローラドライバのポインタを持ち,VME にアクセスする基本的な機能を実装しています.さらに,多くの VME モジュールで振舞いが共通となる一部のモジュールインターフェースについては,デフォルトの実装が提供されています(もちろん各モジュールドライバでこれらをオーバーライドしても構いません).

TRoomVmeModule のクラス定義の一部を以下に示します.

class TRoomVmeModule: public TRoomModule { public: TRoomVmeModule( const std::string& ModuleType, const std::string& ModelName, TRoomVmeTransferMode TransferMode, size_t MapSize = 0, off_t MapOffset = 0 ); virtual ~TRoomVmeModule(); virtual TRoomVmeModule* Clone(void) = 0; public: // VME デバイスにアクセスする機能を提供するメソッド // inline volatile Word* WordPointer(off_t OffsetAddress = 0); inline volatile Word& WordAt(off_t OffsetAddress); inline volatile DoubleWord* DoubleWordPointer(off_t OffsetAddress = 0); inline volatile DoubleWord& DoubleWordAt(off_t OffsetAddress); long PioRead(off_t Address, void* Buffer, int Size) throw(THardwareException); long PioWrite(off_t Address, const void* Buffer, int Size) throw(THardwareException); long DmaRead(off_t Address, void* Buffer, int Size) throw(THardwareException); long DmaWrite(off_t Address, const void* Buffer, int Size) throw(THardwareException); public: // モジュールドライバインターフェースの標準的な実装 // virtual int BlockRead(int Address, void* Data, int MaxSize) throw(THardwareException) virtual int BlockWrite(int Address, const void* Data, int Size) throw(THardwareException) virtual int ReadRegister(int Address, int& Data) throw(THardwareException) virtual int WriteRegister(int Address, int Data) throw(THardwareException) virtual int EnableInterrupt(int SignalId = 0) throw(THardwareException) virtual int DisableInterrupt(void) throw(THardwareException) virtual int ClearInterrupt(void) throw(THardwareException) virtual bool WaitData(unsigned TimeOut_sec) throw(THardwareException) };
モジュールドライバでは,最低限,コンストラクタと Clone() メソッドを実装する必要があります.TRoomVmeModule のコンストラクタは,モジュールの種類と型番の文字列,および転送モードやメモリマップに関するいくつかのパラメータを引数に取ります.また,Clone() メソッドは,自分自身のインスタンスを1つ作成して,そのポインタを返すメソッドです.

以下は,林栄精器のフラッシュ ADC (RPV-160) の例です.このモジュールの転送モードはアドレス 32bit データ 16bit で,メモリマップでアクセスしたいレジスタ群はオフセット 0x30000 から 0x31000 の範囲(サイズ 0x1000)に配置されています.

// module-Rinei_RPV160.hh // #include "RoomVmeAccess.hh" class TVmeFADC_Rinei_RPV160: public TRoomVmeModule { public: TVmeFADC_Rinei_RPV160(void); virtual ~TVmeFADC_Rinei_RPV160(); virtual TRoomVmeModule* Clone(void); // その他のインターフェースの実装 // // ... };
// module-Rinei_RPV160.cc // #include "RoomVmeAccess.hh" #include "module-Rinei_RPV160.hh" static const TVmeTransferMode TransferMode = VmeTransferMode_A32D16; static const off_t MapOffset = 0x30000; static const size_t MapSize = 0x1000; TVmeFADC_Rinei_RPV160::TVmeFADC_Rinei_RPV160(void) : TRoomVmeModule("VmeFADC", "Rinei_RPV160", TransferMode, MapSize, MapOffset) { } TVmeFADC_Rinei_RPV160::~TVmeFADC_Rinei_RPV160() { } TRoomVmeModule* TVmeFADC_Rinei_RPV160::Clone(void) { return new TVmeFADC_Rinei_RPV160(); } // その他のインターフェースの実装 // // ...
以下は,TRoomVmeModule が個々のモジュールドライバのために提供する VME デバイスアクセスのためのメソッドです.モジュールドライバでは,これらを使用して,Read() などのインターフェースを実装していくことになります.
volatile Word* WordPointer(off_t OffsetAddress = 0)
メモリマップされた領域の先頭からで引数で指定されたオフセットの位置にあるワード(16bit)のポインタを返す.

volatile Word& WordAt(off_t OffsetAddress)
メモリマップされた領域の先頭からで引数で指定されたオフセットの位置にあるワード(16bit)の参照を返す.

volatile DoubleWord* DoubleWordPointer(off_t OffsetAddress = 0)
メモリマップされた領域の先頭からで引数で指定されたオフセットの位置にあるダブルワード(32bit)のポインタを返す.

volatile DoubleWord& DoubleWordAt(off_t OffsetAddress)
メモリマップされた領域の先頭からで引数で指定されたオフセットの位置にあるダブルワード(32bit)の参照を返す.

long PioRead(off_t Address, void* Buffer, int Size) throw(THardwareException)
引数で指定されたアドレス(ベースアドレスからのオフセット)から,指定されたサイズのデータを PIO (Programmed I/O) で読み込み,Buffer 引数で指される領域に格納し,実際に読み出したサイズを返す.

long PioWrite(off_t Address, const void* Buffer, int Size) throw(THardwareException)
引数で指定されたアドレス(ベースアドレスからのオフセット)に,引数の Buffer と Size で示されるデータを PIO (Programmed I/O) で書き込み,実際に書き込んだサイズを返す.

long DmaRead(off_t Address, void* Buffer, int Size) throw(THardwareException)
引数で指定されたアドレス(ベースアドレスからのオフセット)から,指定されたサイズのデータを DMA (Direct Memory Access) で読み込み,Buffer 引数で指される領域に格納し,実際に読み出したサイズを返す.

long DmaWrite(off_t Address, const void* Buffer, int Size) throw(THardwareException)
引数で指定されたアドレス(ベースアドレスからのオフセット)に,引数の Buffer と Size で示されるデータを DMA (Direct Memory Access) で書き込み,実際に書き込んだサイズを返す.

振舞いが多くのモジュールで共通なインターフェースは,TRoomVmeAccess で実装されています.以下は実装済のメソッドのリストです.これらのデフォルトの振舞いが不都合のときは,各モジュールドライバでオーバーライドすることができます.
int BlockRead(int Address, void* Data, int MaxSize) throw(THardwareException)
DmaRead(Address, Data, MaxSize) を呼び出す.

int BlockWrite(int Address, const void* Data, int Size) throw(THardwareException)
DmaWrite(Address, Data, Size) を呼び出す.

int ReadRegister(int Address, int& Data) throw(THardwareException)
PioRead(Address, Data, {転送モードで指定されたワードサイズ}) を呼び出す.

int WriteRegister(int Address, int Data) throw(THardwareException)
PioWrite(Address, Data, {転送モードで指定されたワードサイズ}) を呼び出す.

int EnableInterrupt(int SignalId = 0) throw(THardwareException)
VMEコントローラの割り込みの設定と割り込み許可を行なう.通常はモジュール上の割り込み関連レジスタの設定も必要なので,このメソッドをオーバーライドして処理を追加することになる.

int DisableInterrupt(void) throw(THardwareException)
VMEコントローラに対し割り込みの禁止を設定する.通常はモジュール上の割り込み関連レジスタの設定も必要なので,このメソッドをオーバーライドして処理を追加することになる.

int ClearInterrupt(void) throw(THardwareException)
何もしない.通常はモジュール上の割り込み関連レジスタの設定も必要なので,このメソッドをオーバーライドして処理を追加することになる.

bool WaitData(unsigned TimeOut_sec) throw(THardwareException)
EnableInterrupt() を呼んでからコントローラの WaitForInterrupt() で割り込みを待つ.

CAMAC の場合と異なり,VME はこれらのデフォルトの動作はあまり役に立ちません.特に,1ワードごとの入出力を行なう Read()/Write() が含まれていないことに注意してください.割り込み周りも,モジュールに対する操作は何もしていません.また,デフォルト実装における BlockRead()/BlockWrite() の Address 引数はベースアドレスからのオフセットを意味していますが,これは多くの場合において適切な振舞いではありません.さらに,ReadRegister()/WriteRegister() は PioRead()/PioWrite() を使用していますが,メモリマッピングを使用している場合,マップされた領域をポインタでアクセスする方が,効率がずっと良くなります.

作成したモジュールドライバをシステムに登録するには,RoomDeviceFactory.hh に定義されている TRoomVmeModuleCreator を使用します.若干トリッキーですが,以下のようにモジュールドライバの .cc ファイル内で Creator オブジェクトを static に作成し,そのコンストラクタ引数にモジュールドライバ名とモジュールドライバのインスタンスを渡します.これにより,ドライバのコードがロードされたときに自動でモジュールドライバのインスタンスが作成され,システムに登録されるようになります.

/* module-Rinei_RPV160.cc */ #include "RoomVmeAccess.hh" #include "RoomDeviceFactory.hh" #include "module-Rinei_RPV160.hh" static TRoomVmeModuleCreator Creator( "Rinei_RPV160", new TCamacModule_Rinei_RPV160() ); //...

作成したモジュールドライバの .hh/.cc ファイルを kinoko/src/kernel/lib-common/room に置いて Kinoko をコンパイルしなおせば,Kinoko からこれを利用できるようになります.Makefile を編集する必要はありません.

MiscControl インターフェース

TRoomModule に規定されているインターフェースで,多くのモジュールの多くの機能を利用することができますが,ここに含まれていないインターフェースが必要になることがあります.標準インターフェースに含まれないモジュールの機能を Kinoko から利用できるようにするための特殊なインターフェースとして,MiscControl インターフェースがあります.

MiscControl は操作名を文字列で与え,パラメータやデータの受け渡しを整数の可変長配列で行ないます. 以下に MiscControl で使用するメソッドを示します.

int MiscControlIdOf(const std::string& CommandName) throw(THardwareException)
引数の操作名に対応するユニークなID(非負の整数)を返す.操作名が不正の場合は負の値を返す.

int MiscControl(int ControlId, int* ArgumentList = 0, int NumberOfArguments = 0) throw (THardwareException)
引数の ID で示される操作を実行し,成功したら 1 を,失敗なら 0 を返す.パラメータは ArgumentList で受け渡す.値を返したい場合は ArgumentList の要素に直接代入する.逆に,値を返さない場合は,ArgumentList の値を変更してはいけない.

MiscControl を利用した例を以下に示します.ここでは,海津製作所の CAMAC アウトプットレジスタ KC3471 を,outputLevel と outputPulse という操作名で使用できるようにしています.
class TCamacOutputRegister_Kaizu_KC3471: public TRoomCamacModule { public: virtual int MiscControlIdOf(const std::string& CommandName) throw (THardwareException); virtual int MiscControl(int ControlId, int* ArgumentList = 0, int NumberOfArguments = 0) throw (THardwareException); public: // Control ID の定義 enum TControlId { ControlId_OutputLevel, ControlId_OutputPulse, _NumberOfControls }; };
// 操作名から Control ID への変換 // int TCamacOutputRegister_Kaizu_KC3471::MiscControlIdOf(const std::string& CommandName) throw (THardwareException) { int ControlId = -1; if (CommandName == "outputLevel") { ControlId = ControlId_OutputLevel; } else if (CommandName == "outputPulse") { ControlId = ControlId_OutputPulse; } else { throw THardwareException( _ModelName, "unknown command: " + CommandName ); } return ControlId; } // 操作を実行 // int TCamacOutputRegister_Kaizu_KC3471::MiscControl(int ControlId, int* ArgumentList, int NumberOfArguments) throw (THardwareException) { int Result = 0; if (ControlId == ControlId_OutputLevel) { if (NumberOfArguments < 1) { throw THardwareException( "TCamacOutputRegister_Kaizu_KC3471::OutputLevel(int Data)", "too few argument[s]" ); } // ArgumentList の値を変更しないように中間変数を経由する int Data = ArgumentList[0]; Transact(fnOutputLevel, adOutput, Data); Result = 1; } else if (ControlId == ControlId_OutputPulse) { if (NumberOfArguments < 1) { throw THardwareException( "TCamacOutputRegister_Kaizu_KC3471::OutputPulse(int Data)", "too few argument[s]" ); } int Data = ArgumentList[0]; Transact(fnOutputPulse, adOutput, Data); Result = 1; } return Result; }

サービスリクエスタ

サービスリクエスタは,モジュールからの読みだし要求などをシステムに伝達するためのインターフェースです.能動的に読みだし要求を出すようなモジュール(割り込みを要求するモジュールなど)はこのインターフェースを実装しなれけばなりません.TRoomModule が規定するインターフェースにも WaitData() や HasData() などの読みだし要求を処理するインターフェースはありましたが,サービスリクエスタは,複数のモジュールからの読みだし要求(サービス要求)を最適な方法で同時に処理するというような,より高度な構造を持っています.

サービスリクエスタインターフェースは,抽象クラス TRoomServiceRequester として,RoomServiceRequester.hh ファイルに定義されています.このクラスは標準モジュールインターフェースを規定する抽象クラス TRoomModule の親クラスにもなっています.

class TRoomServiceRequester { public: TRoomServiceRequester(void); virtual ~TRoomServiceRequester(); // Basic Interface // virtual bool WaitForServiceRequest(int TimeOut_sec) throw(THardwareException); virtual int RequestingServiceNumber(void) throw(THardwareException); // Polling Interface // virtual void EnableServiceRequest(void) throw(THardwareException); virtual void DisableServiceRequest(void) throw(THardwareException); virtual void ClearServiceRequest(void) throw(THardwareException); virtual bool IsRequestingService(void) throw(THardwareException); // Signal on Service Request // virtual bool IsSignalOnServiceRequestAvailable(void); virtual void EnableSignalOnServiceRequest(int SignalId) throw(THardwareException); virtual void DisableSignalOnServiceRequest(void) throw(THardwareException); };
各メソッドの意味は以下のとおりです.
bool WaitForServiceRequest(int TimeOut_sec) throw(THardwareException)
引数に指定された時間を上限として,サービス要求を待つ.サービス要求が来た場合は true を,来なかった場合または何らかの理由で中断された場合は false を返す.

int RequestingServiceNumber(void) throw(THardwareException)
サービス要求がパラメータを持っている場合,そのパラメータ値を返す.

void EnableServiceRequest(void) throw(THardwareException)
サービス要求を許可する.

void DisableServiceRequest(void) throw(THardwareException)
サービス要求を禁止する.

void ClearServiceRequest(void) throw(THardwareException)
サービス要求をクリアする.

bool IsRequestingService(void) throw(THardwareException)
サービス要求出していれば true を,そうでなければ false を返す.

bool IsSignalOnServiceRequestAvailable(void)
サービス要求でシグナルを発行する機能があれば true を,そうでなければ false を返す.

void EnableSignalOnServiceRequest(int SignalId) throw(THardwareException)
サービス要求の際にシグナルを発行するように設定する.

void DisableSignalOnServiceRequest(void) throw(THardwareException)
サービス要求でシグナルを発行するようにした設定を解除する.

TRoomCamacModule および TRoomVmeModule では,これらの一部が共通の振舞いで実装されています.

TRoomCamacModule のサービスリクエスタの実装
bool WaitForServiceRequest(int TimeOut_sec) throw(THardwareException)
WaitData(TimeOut_sec) を呼び出す.WaitData() は WaitLam() を呼ぶ.

int RequestingServiceNumber(void) throw(THardwareException)
0 を返す.

void EnableServiceRequest(void) throw(THardwareException)
EnableLam() を呼び出す.

void DisableServiceRequest(void) throw(THardwareException)
DisableLam() を呼び出す.

void ClearServiceRequest(void) throw(THardwareException)
ClearLam() を呼び出す.

bool IsRequestingService(void) throw(THardwareException)
HasData() を呼び出す.

bool IsSignalOnServiceRequestAvailable(void)
コントローラドライバの IsSignalOnInterruptAvailable() を呼び出す.

void EnableSignalOnServiceRequest(int SignalId) throw(THardwareException)
何もしない.

void DisableSignalOnServiceRequest(void) throw(THardwareException)
何もしない.
多くの CAMAC モジュールに対して,これらのデフォルトの実装で十分です.
TRoomVmeModule のサービスリクエスタの実装
bool WaitForServiceRequest(int TimeOut_sec) throw(THardwareException)
WaitData(TimeOut_sec) を呼び出す.WaitData() は EnableInterrupt() をして割り込みを待つ.

int RequestingServiceNumber(void) throw(THardwareException)
0 を返す.

void EnableServiceRequest(void) throw(THardwareException)
何もしない.おそらくオーバライドして実装する必要がある.

void DisableServiceRequest(void) throw(THardwareException)
何もしない.おそらくオーバライドして実装する必要がある.

void ClearServiceRequest(void) throw(THardwareException)
例外 THardwareException を投げる.オーバライドして実装する必要がある.

bool IsRequestingService(void) throw(THardwareException)
HasData() を呼び出す.HasData() は TRoomVmeModule では実装されていないので,すくなくとも HasData() をオーバーライドして実装する必要がある.

bool IsSignalOnServiceRequestAvailable(void)
コントローラドライバの IsSignalOnInterruptAvailable() を呼び出す.

void EnableSignalOnServiceRequest(int SignalId) throw(THardwareException)
EnableInterrupt(SignalId) を呼び出す.

void DisableSignalOnServiceRequest(void) throw(THardwareException)
DisableInterrupt() を呼び出す.
ここでも,CAMAC の場合と違ってこれらの標準の実装はとても貧弱です.EnableServiceRequest() / DisableServiceRequest() / ClearServiceRequest() をはじめとして,かなりのメソッドをモジュールの仕様に合わせて実装する必要があります.

コントローラドライバを書く

コントローラドライバは,Kinoko から UNIX のデバイスドライバへのインターフェースコードです.Kinoko によって規定されたインターフェースでデバイスドライバへのアクセスを実装することにより,Kinoko に対しデバイスドライバの詳細を隠蔽し,Kinoko の他の部分(モジュールドライバなど)に対して統一されたインターフェースを提供します.

CAMAC コントローラドライバ

CAMAC コントローラドライバは,CAMAC コントローラ 用 UNIX デバイスドライバへのインターフェースコードです.Kinoko には,全ての CAMAC コントローラに対する共通のインターフェースとして抽象クラス TRoomCamacController が定義されています.個々のデバイスに対するコントローラドライバはこのクラスを継承し,いくつかのメソッドを実装することにより作成します.

TRoomCanacController クラスは,kinoko/src/kernel/lib-common/room/RoomCamacAccess.hh で定義されています.

class TRoomCamacController: public TRoomController { public: TRoomCamacController(void); virtual ~TRoomCamacController(); virtual TRoomCamacController* Clone(void) = 0; public: virtual void Open(void) throw(THardwareException) = 0; virtual void Close(void); virtual int Transact(int StationNumber, int Function, int Address, int &Data, int &Q, int &X) throw(THardwareException) = 0; virtual int Initialize(void) throw(THardwareException) = 0; virtual int Clear(void) throw(THardwareException) = 0; virtual int SetInhibition(void) throw(THardwareException) = 0; virtual int ReleaseInhibition(void) throw(THardwareException) = 0; virtual int EnableInterruptNotification(void) throw(THardwareException) = 0; virtual int DisableInterruptNotification(void) throw(THardwareException) = 0; virtual bool IsSignalOnInterruptAvailable(void); virtual int ReadLam(void) throw(THardwareException) = 0; virtual int WaitLam(int TimeOut_sec) throw(THardwareException) = 0; };
ほとんどのメソッドが,純粋仮想関数となっており,これらを全て子クラスで実装する必要があります.純粋仮想関数でないメソッドも,必要に応じてオーバーライドします.

以下に各メソッドの役割を順に解説します.

TRoomCamacController* Clone(void)
自分自身のインスタンスを一つ作成してそのポインタを返す.

void Open(void) throw(THardwareException)
オープン.他の全てのメソッドに先立って1度だけ呼ばれる.普通はここでドライバをオープンする.

void Close(void)
クローズ.処理の最後に1度だけ呼ばれる.普通はここでドライバをクローズする.

int Transact(int StationNumber, int Function, int Address, int &Data, int &Q, int &X) throw(THardwareException)
与えられた引数に従い CAMAC アクションを1回実行する.READ アクションの場合はデータ値を引数 Data に返す.Q, X レスポンスをそれぞれ引数 Q, X に返す.

int Initialize(void) throw(THardwareException)
Z (Initialize) を発行する.

int Clear(void) throw(THardwareException)
C (Clear) を発行する.

int SetInhibition(void) throw(THardwareException)
I (Inhibit) を設定する.

int ReleaseInhibition(void) throw(THardwareException)
I (Inhibit) の設定を解除する.

int EnableInterruptNotification(void) throw(THardwareException)
コントローラデバイスに対し割り込み許可を設定する.

int DisableInterruptNotification(void) throw(THardwareException)
コントローラデバイスに対し割り込み許可を解除する.

bool IsSignalOnInterruptAvailable(void)
デバイスドライバに割り込みでシグナルを発行する機能があるなら true を返す.

int ReadLam(void) throw(THardwareException)
LAM のパターンをビット列で返す.ドライバがこの機能を持っていない場合,例外を投げても良い.

int WaitLam(int TimeOut_sec) throw(THardwareException)
引数の TimeOut_sec に指定された時間を上限として LAM を待つ.LAM があった場合は LAM のパターンをビット配列にした値を返す.タイムアウトでは負の値を返す.

コントローラドライバは,"controller-ベンダ名_デバイス名.hh/cc" という名前のファイルで作成します.これを ROOM のディレクトリ (kinoko/src/kernel/lib-common/room) に配置し,これをコンパイルするように Makefile.in および Makefile を編集します.

Makefile.in/Makefile の all: の前に以下の太文字の行を追加してください(以下の例では,ベンダ名を CamacInternational, デバイス名を Camac2000 としています).

# 以下の2行を追加 DEVOBJS += controller-CamacInternational_Camac2000.o HAS_DEVICE=yes all: $(ARC) $(DEVOBJS) $(BINS) @if [ $(HAS_DEVICE) = yes ]; then \ cd samples; $(MAKE); \ fi

作成したコントローラドライバオブジェクトをシステムに登録するには,RoomDeviceFactory.hh に定義されている TRoomCamacControllerCreator を使用します.若干トリッキーですが,以下のようにコントローラドライバの .cc ファイル内で Creator オブジェクトを static に作成し,そのコンストラクタ引数にコントローラドライバ名とコントローラドライバのインスタンスを渡します.これにより,ドライバのコードがロードされたときに自動でコントローラドライバのインスタンスが作成され,システムに登録されるようになります.

/* controller-CamacInternational_Camac2000.cc */ #include "RoomDeviceFactory.hh" #include "controller-CamacInternational_Camac2000.hh" static TRoomCamacControllerCreator Creator( "CamacInternational_Camac2000", new TCamacController_CamacInternational_Camac2000() ); //...

VME コントローラドライバ

VME コントローラドライバも,基本的には CAMAC コントローラドライバと同じですが,VME の多様な機能に対応するため,若干複雑な構造になっています.

VME のモジュールは,ベースアドレスやアクセスモードなど,アクセスに関連するパラメータを数多く持っているため,各モジュールごとにデバイスドライバをオープンして,アクセスモードの設定やメモリマッピングなどを行うのが一般的です.したがって,VME コントローラドライバは,接続される各モジュールごとに何度もオープンされることになります.

Kinoko の VME コントローラドライバは,オープンにより,VmeAccessProperty オブジェクトを作成して,これを返します.VmeAccessProperty には,ドライバのデバイスデスクリプタやアクセスモードなどのパラメータが記録されており,オープン以降の全ての VME へのアクセスはこの VmeAccessProperty を指定して行ないます.原則として,VmeAccessProperty 内の情報は,VME コントローラドライバのみによって使われます.

以下に VmeAccessProperty の構造を示します.

class TRoomVmeAccessProperty { public: off_t VmeAddress; // モジュールの VME ベースアドレス TRoomVmeTransferMode TransferMode; // 転送モード (アドレス幅,データワード長) int DeviceDescriptor; // open() により返されるデバイスデスクリプタ caddr_t MappedAddress; // mmap() によりマッピングされたアドレス size_t MapSize; // mmap() に渡すマップサイズ off_t MapOffset; // mmap() に渡すアドレスのベースアドレスからのオフセット int DmaDeviceDescriptor; // DMA 転送用に open() した場合のデバイスデスクリプタ int InterruptNumber; // 割り込みの IRQ (または割り込み番号) int InterruptVector; // 割り込みベクタ int InterruptSignalId; // 割り込みでシグナルを発行する場合のシグナルID void* DeviceSpecificData; // コントローラドライバが自由に使用できる領域 };
各フィールドの意味は以下の通りです.
off_t VmeAddress
モジュールの VME オフセットアドレス.通常モジュール上のジャンパで設定する値.

TRoomVmeTransferMode TransferMode
転送のアドレス幅やデータワード長を表す enum 値.以下の値が定義されている.
enum TRoomVmeTransferMode {
    VmeTransferMode_A16D16, 
    VmeTransferMode_A16D32, 
    VmeTransferMode_A24D16, 
    VmeTransferMode_A24D32, 
    VmeTransferMode_A32D16, 
    VmeTransferMode_A32D32,
    VmeTransferMode_Unknown
};

int DeviceDescriptor
open() システムコールにより返されるデバイスデスクリプタ.

caddr_t MappedAddress
mmap() システムコールにより設定されるプロセスのアドレス空間側のアドレス.

size_t MapSize
mmap() システムコールに渡されるマップサイズ.

off_t MapOffset
mmap() システムコールに渡される VME アドレス空間側のアドレスで,ベースアドレス(VmeAddress) からのオフセット.ベースアドレスからマップする場合は 0 になる.

int DmaDeviceDescriptor
DMA 転送用に別のデバイスエントリを open() した場合の,デバイスデスクリプタ.

int InterruptNumber
割り込みを使用する場合の,割り込み番号.一般には IRQ 番号.デバイスドライバによっては,割り込みテーブルのエントリ番号など,意味が変わる場合もある.

int InterruptVector
割り込みベクタ.

int InterruptSignalId
割り込みでシグナルを発行する場合の,シグナルID.シグナルIDは <signal.h> に定義されている値.

void* DeviceSpecificData
コントローラドライバが自由に使用できる領域.
VME コントローラドライバの作成は,CAMAC の場合と同様に,抽象クラス TRoomVmeController を継承し,必要なメソッドをオーバーライドすることにより行ないます.TRoomVmeController クラスは,RoomVmeAccess.hh に定義されています.
class TRoomVmeController: public TRoomController { public: TRoomVmeController(void); virtual ~TRoomVmeController(); virtual TRoomVmeController* Clone(void) = 0; public: virtual TRoomVmeAccessProperty* Open(TRoomVmeTransferMode TransferMode) throw(THardwareException); virtual void Close(TRoomVmeAccessProperty* Property); virtual long Read(TRoomVmeAccessProperty* Property, void* Buffer, int Size) throw(THardwareException); virtual long Write(TRoomVmeAccessProperty* Property, const void* Buffer, int Size) throw(THardwareException); virtual off_t Seek(TRoomVmeAccessProperty* Property, off_t OffsetAddress) throw(THardwareException); virtual caddr_t Map(TRoomVmeAccessProperty* Property, off_t VmeAddress, size_t MapSize, off_t MapOffset) throw(THardwareException); virtual void Unmap(TRoomVmeAccessProperty* Property) throw(THardwareException); virtual void RegisterInterruptNotification(TRoomVmeAccessProperty* Property, int SignalId = 0) throw(THardwareException) = 0; virtual void UnregisterInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException) = 0; virtual void EnableInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException) = 0; virtual void DisableInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException) = 0; virtual bool WaitForInterruptNotification(TRoomVmeAccessProperty* Property, unsigned TimeOut_sec) throw(THardwareException) = 0; virtual long DmaRead(TRoomVmeAccessProperty* Property, void* Buffer, int Size) throw(THardwareException); virtual long DmaWrite(TRoomVmeAccessProperty* Property, const void* Buffer, int Size) throw(THardwareException); virtual off_t DmaSeek(TRoomVmeAccessProperty* Property, off_t OffsetAddress) throw(THardwareException); public: virtual bool IsSignalOnInterruptAvailable(void); protected: virtual const char* DeviceFileName(TRoomVmeTransferMode TransferMode) = 0; virtual const char* DmaDeviceFileName(TRoomVmeTransferMode TransferMode) = 0; virtual void DmaOpen(TRoomVmeAccessProperty* Property) throw(THardwareException); virtual void DmaClose(TRoomVmeAccessProperty* Property); };
以下に各メソッドの詳細を示します.CAMAC の場合と異なり,Open() / Close() / Read() / Write( ) / Seek() / Map() / Unmap() など,一部の共通のメソッドはすでに実装されており,特に必要が無ければ子クラスでこれらをオーバーライドする必要はありません(それ以外の純粋仮想関数は子クラスで実装する必要があります).
TRoomVmeController* Clone(void)
自分自身のインスタンスを一つ作成してそのポインタを返す.

const char* DeviceFileName(TRoomVmeTransferMode TransferMode)
引数の転送モードに対応した UNIX デバイスドライバのエントリ名(/dev のファイル名)を返す.

const char* DmaDeviceFileName(TRoomVmeTransferMode TransferMode)
引数の転送モードでの DMA 転送に対応した UNIX デバイスドライバのエントリ名(/dev のファイル名)を返す.デバイスドライバが PIO と DMA で同じエントリの場合でも,そのエントリ名を返す.DMA がサポートされていない場合は,PIO のエントリ名を返す.

TRoomVmeAccessProperty* Open(TRoomVmeTransferMode TransferMode) throw(THardwareException)
デバイスドライバを PIO 用にオープンし,AccessProperty オブジェクトを作成してそれを返す.通常はオーバーライドする必要はない.

void Close(TRoomVmeAccessProperty* Property)
リソースを開放し,デバイスドライバをクローズする.通常はオーバーライドする必要はない.

long Read(TRoomVmeAccessProperty* Property, void* Buffer, int Size) throw(THardwareException)
デバイスからデータを読み出す.通常はオーバーライドする必要はない.

long Write(TRoomVmeAccessProperty* Property, const void* Buffer, int Size) throw(THardwareException)
デバイスにデータを書き込む.通常はオーバーライドする必要はない.

off_t Seek(TRoomVmeAccessProperty* Property, off_t OffsetAddress) throw(THardwareException)
Read()/Write() 用のファイルポインタを移動させる(lseek()).通常はオーバーライドする必要はない.

caddr_t Map(TRoomVmeAccessProperty* Property, off_t VmeAddress, size_t MapSize, off_t MapOffset) throw(THardwareException)
引数に指定された VME のアドレス空間をプロセスのアドレス空間にマップし(mmap()),マップされたプロセス側のアドレスを返す.通常はオーバーライドする必要はない.

void Unmap(TRoomVmeAccessProperty* Property) throw(THardwareException)
Map() によりマップされた領域を開放する.通常はオーバーライドする必要はない.

void RegisterInterruptNotification(TRoomVmeAccessProperty* Property, int SignalId = 0) throw(THardwareException)
割り込みを登録し,取得する準備をする.引数の SignalId が 0 でない場合,割り込みでシグナルを発行するようにする.

void UnregisterInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException)
登録してある割り込みを解除し,リソースを開放する.

void EnableInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException)
割り込みを許可する.

void DisableInterruptNotification(TRoomVmeAccessProperty* Property) throw(THardwareException)
割り込みを禁止する.

bool WaitForInterruptNotification(TRoomVmeAccessProperty* Property, unsigned TimeOut_sec) throw(THardwareException)
引数の TimeOut_sec に指定された時間を上限として割り込みを待つ.割り込みがあった場合は true を,タイムアウトでは false を返す.

void DmaOpen(TRoomVmeAccessProperty* Property) throw(THardwareException)
デバイスドライバを DMA 用にオープンする.通常はオーバーロードする必要はない.

void DmaClose(TRoomVmeAccessProperty* Property)
DMA 用にオープンしたデバイスドライバをクローズする.通常はオーバーロードする必要はない.

long DmaRead(TRoomVmeAccessProperty* Property, void* Buffer, int Size) throw(THardwareException)
デバイスから DMA モードでデータを読み出す.通常はオーバーロードする必要はない.

long DmaWrite(TRoomVmeAccessProperty* Property, const void* Buffer, int Size) throw(THardwareException)
デバイスに DMA モードでデータを書き込む.通常はオーバーロードする必要はない.

off_t DmaSeek(TRoomVmeAccessProperty* Property, off_t OffsetAddress) throw(THardwareException)
DmaRead()/DmaWrite() 用のファイルポインタを移動させる(lseek()).通常はオーバーロードする必要はない.

bool IsSignalOnInterruptAvailable(void)
デバイスドライバに割り込みでシグナルを発行する機能があるなら true を返す.
CAMAC コントローラドライバと同様に,VME コントローラドライバのファイルは "controller-ベンダ名_デバイス名.hh/cc" という名前でなければなりません.このファイルを ROOM のディレクトリ (kinoko/src/kernel/lib-common/room) に配置し,これをコンパイルするように Makefile.in および Makefile を編集します.
# 以下の2行を追加 DEVOBJS += controller-VmeInternational_Vme2000.o HAS_DEVICE=yes all: $(ARC) $(DEVOBJS) $(BINS) @if [ $(HAS_DEVICE) = yes ]; then \ cd samples; $(MAKE); \ fi

やはり CAMAC コントローラドライバと同様に,作成したコントローラドライバオブジェクトをシステムに登録するために TRoomVmeControllerCreator を使用します.

/* controller-VmeInternational_Vme2000.cc */ #include "RoomDeviceFactory.hh" #include "controller-VmeInternational_Vme2000.hh" static TRoomVmeControllerCreator Creator( "VmeInternational_Vme2000", new TVmeController_VmeInternational_Vme2000() ); //...

カスタムコンポーネントを作成する

実験固有のオンラインデータ解析などを C++ で直接記述したい場合や,特定の外部ライブラリやデータストレージシステムにインターフェースしたい場合など,既存のコンポーネントの機能やスクリプティングでは対処しきれない機能を実装したいときは,独自のカスタムコンポーネントを作成することになります. 独自のコンポーネントイベントを発生させるだけのコントローラ的なコンポーネントを作成するなら,単純に KinokoComponents の抽象コンポーネントクラスから自分のコンポーネントを派生させて,必要なメソッドを追加・登録するだけです.一方,データストリームに対して独自の解析などを行うデータストリームコンポーネントを作成する場合は,Kinoko が提供しているアナリシスフレームワークを利用すると,コンポーネントなどの実装を気にせずに計算部分のみを実装するだけで簡単に作成することができます.データストリームとコンポーネントイベントが相互作用するようなコンポーネントでも,適当なクラスを継承することにより,比較的少ない手順で実装することが可能です.

ここでは,簡単なカスタムコンポーネントの例として,以下の3つのコンポーネントの作成を行います,

アラームクロック
設定した時間が経過するごとにアラームイベントを発生させるコンポーネントです.

イベントカウンタ
ストリーム中のイベントの数を数えて,規定の数に達したらアラームイベントを発生させるコンポーネントです.

データフローメータ
データの流量を測ってそれを新たなデータとして添付し,下流のストリームに流すコンポーネントです.

例1) アラームクロック

設定した時間が経過するごとにアラームイベントを発生させるコンポーネントです. このコンポーネントのインターフェース宣言は以下のとおりです.
component KinokoAlarmClock { emits ring(); accepts setInterval(int interval_sec); accepts start(); accepts stop(); accepts quit(); }
このコンポーネントの完全なソースコードは kinoko/local/tutorials/Component にある以下のファイルです.

このコンポーネントは,単純に現在時刻を取得して経過時間を計算し,設定値以上になったらアラームイベントを発生させるだけのものです.実装に必要なのは,設定時間の指定や動作の開始・停止をコントロールするコンポーネントイベントスロットの実装と,アラームイベントを発生させるコンポーネントイベントソースの実装,および経過時間の計算のみです.

この単純なコンポーネントを実装するのに必要な手順は,以下の通りです.

  1. 抽象コンポーネントフレームクラスを継承し,AlarmClock クラスを作る.
  2. イベントスロットやイベントソースなどの宣言をコンポーネントデスクリプタに追加する.
  3. イベントを受け取った時に行う処理を記述する.
  4. 普段行う処理(コンポーネントタスク)を記述する.
  5. コンポーネントオブジェクトをコンポーネントプロセスオブジェクトで包み,main() 関数から制御を渡す.
以下,これらを順に追っていきます.
1) カスタムコンポーネントクラスの定義
KinokoAlarmClockCom.hh ファイルを作成し,その中でTKinokoAlarmClockComクラスを定義します.
13: class TKinokoAlarmClockCom: public TKinokoSystemComponent { 14: public: 15: TKinokoAlarmClockCom(void); 16: virtual ~TKinokoAlarmClockCom(); 17: virtual void BuildDescriptor(TKcomComponentDescriptor& Descriptor); 18: virtual int ProcessEvent(int EventId, TKcomEvent& Event); 19: virtual int DoTransaction(void) throw(TKcomException); 20: protected: 21: int ProcessSetIntervalEvent(TKcomEvent& Event); 22: int ProcessStartEvent(TKcomEvent& Event); 23: int ProcessStopEvent(TKcomEvent& Event); 24: void EmitRingEvent(void); 25: protected: 26: enum TEventId { 27: EventId_SetInterval = TKinokoSystemComponent::_NumberOfEvents, 28: EventId_Start, 29: EventId_Stop, 30: EventId_Ring, 31: _NumberOfEvents 32: }; 33: protected: 34: bool _IsRunning; 35: long _StartTime, _Interval; 36: };
17-19 行目の3つのメソッドは,コンポーネントフレームクラス TKcomComponent で宣言されているもので,各コンポーネント実装クラスでオバーライドされなければならないものです.21-24行目のメソッドは,内部で補助的に使われるものです.全てのイベントは,ユニークなイベントIDで識別されます.独自のイベントを追加するためには,それらにイベントIDを割り当てなければなりません.26-32行目のenum定数でそれを行っています.34-35行目のメンバ変数は,内部で使用されるものです.

次に,KinokoAlarmClockCom.cc ファイルを作成し,TKinokoAlarmClockCom クラスのメソッドを実装して行きます.まずコンストラクタ,デストラクタです.コンポーネントフレームクラスは,コンストラクタの引数にコンポーネント型名をとります.これは,通常はコンポーネントクラス名と同じです.メンバ変数の初期化もコンストラクタで行ないます.

12: TKinokoAlarmClockCom::TKinokoAlarmClockCom(void) 13: : TKinokoSystemComponent("KinokoAlarmClock") 14: { 15: _IsRunning = false; 16: _Interval = 0; 17: _StartTime = 0; 18: } 19: 20: TKinokoAlarmClockCom::~TKinokoAlarmClockCom() 21: { 22: }
2) コンポーネントデスクリプタの構築
コンポーネントの外部インターフェース(コンポーネント定義)を記述するのが,コンポーネントデスクリプタです.メソッド BuildDescriptor() をオーバライドして,デスクリプタオブジェクトを組み立てまず.デスクリプタにイベントスロットやプロパティなどを記述すると,コンポーネントインターフェース宣言に反映されるようになります(上記のインターフェース宣言と比べてみてください).
24: void TKinokoAlarmClockCom::BuildDescriptor(TKcomComponentDescriptor& Descriptor) 25: { 26: TKinokoSystemComponent::BuildDescriptor(Descriptor); 27: 28: TKcomEventDeclaration SetIntervalEvent("setInterval"); 29: SetIntervalEvent.AddArgument(TKcomPropertyDeclaration( 30: "interval_sec", TKcomPropertyDeclaration::Type_Int 31: )); 32: Descriptor.RegisterEventSlot(EventId_SetInterval, SetIntervalEvent); 33: 34: TKcomEventDeclaration StartEvent("start"); 35: Descriptor.RegisterEventSlot(EventId_Start, StartEvent); 36: 37: TKcomEventDeclaration StopEvent("stop"); 38: Descriptor.RegisterEventSlot(EventId_Stop, StopEvent); 39: 40: TKcomEventDeclaration RingEvent("ring"); 41: Descriptor.RegisterEventSource(EventId_Ring, RingEvent); 42: }
まず親クラスの同メソッドを呼んで,親コンポーネントから継承する分のインターフェースを取り込みます.次に,独自のイベントデスクリプタオブジェクトを生成し,コンポーネントデスクリプタに登録していきます.イベントディスクリプタはイベントのシグニチャの情報しか保持しないので,コンポーネントデスクリプタに登録するときは,対応するイベントIDもあわせて渡します.デスクリプタの登録メソッドは,引数をコピー渡しで受け取るので,イベントオブジェクトには一時オブジェクトをそのまま渡してしまって構いません.
3) イベント処理関数の実装
コンポーネントがイベントを受け取ると,ProcessEvent() メソッドが呼び出されます.引数にはイベントスロットのイベントIDと,イベントの引数などを保持しているイベントオブジェクトが渡されます.ここでは,単純にイベントIDを見て,適切な処理関数に割り振ることにします.イベントが適切に処理された場合は 1 を,そうでなければ 0 を返してください.
44: int TKinokoAlarmClockCom::ProcessEvent(int EventId, TKcomEvent& Event) 45: { 46: int Result = 0; 47: 48: switch (EventId) { 49: case EventId_SetInterval: 50: Result = ProcessSetIntervalEvent(Event); 51: break; 52: 53: case EventId_Start: 54: Result = ProcessStartEvent(Event); 55: break; 56: 57: case EventId_Stop: 58: Result = ProcessStopEvent(Event); 59: break; 60: 61: default: 62: Result = TKinokoSystemComponent::ProcessEvent(EventId, Event); 63: } 64: 65: return Result; 66: }
各イベント処理関数は,以下のように実装されています.
81: int TKinokoAlarmClockCom::ProcessSetIntervalEvent(TKcomEvent& Event) 82: { 83: int Result = 0; 84: if (istrstream(Event.ArgumentList()[0].c_str()) >> _Interval) { 85: Result = 1; 86: } 87: 88: return Result; 89: } 90: 91: int TKinokoAlarmClockCom::ProcessStartEvent(TKcomEvent& Event) 92: { 93: _StartTime = TMushDateTime().AsLong(); 94: _IsRunning = true; 95: 96: return 1; 97: } 98: 99: int TKinokoAlarmClockCom::ProcessStopEvent(TKcomEvent& Event) 100: { 101: _IsRunning = false; 102: 103: return 1; 104: }
ProcessSetIntervalEvent() では,イベントの第1引数を読んで,_Intervalメンバ変数にセットしています. この例に示されているように,TKcomEvent クラスは ArgumentList() メソッドでイベント引数のリストを返します.ArgumentList() が返す型は文字列のベクタ(vector<string>&) です.start イベントを受け取ったら,現在時刻を取得して,動作中フラグ (_IsRunning) をセットしています.
4) コンポーネントタスクの実装
コンポーネントは,動作中,DoTransaction() メソッドを繰り返し呼び出します.ここに,コンポーネントが通常行うタスク(コンポーネントタスク)を記述します.この例では,動作中状態なら現在時刻を取得し,経過時間を計算して,この経過時間が設定時間を過えていたらアラームイベントを発行するようにしています.
68: int TKinokoAlarmClockCom::DoTransaction(void) throw(TKcomException) 69: { 70: if (_IsRunning && (_Interval > 0)) { 71: int CurrentTime = TMushDateTime().AsLong(); 72: if ((CurrentTime - _StartTime) > _Interval) { 73: _StartTime = CurrentTime; 74: EmitRingEvent(); 75: } 76: } 77: 78: return TKinokoSystemComponent::DoTransaction(); 79: }

コンポーネントタスク中で何もすることが無い場合,あるいはコンポーネントタスクがごく短時間で終了し,かつ直後に再び呼び出される必要が無い場合,親クラスの同メソッドを呼び出してください.コンポーネントフレームクラスのこのメソッドでは,適当な時間 CPU を開放してプロセスをスリープさせます(msのオーダ).もしこれを行わないと,不必要な処理のために CPU 使用率を大きく増やしてしまうことになります(他に同じようなプロセスがいない場合なら,100%近く使用する).

アラームイベントを発行するメソッド EmitRingEvent() は以下のように実装されています.

106: void TKinokoAlarmClockCom::EmitRingEvent(void) 107: { 108: TKcomEvent Event; 109: EmitEventOneWay(EventId_Ring, Event); 110: }
このイベントはイベント引数を取らないので,作ったイベントオブジェクトをそのまま投げています(EventId だけが使われる).引数をとるイベントなら,イベントオブジェクトの ArgumentList() メソッドで引数リストを取得して,そこに値を設定してください.

イベントの発行は EmitEvent() もしくは EmitEventOneWay() メソッドで行います.EmitEvent() はイベントが宛て先に通知され,それが処理されるまで実行を停止します.EmitEventOneWay() は,イベントを投げたら応答を待たずにすぐに処理を先に進めます.コンポーネント間で同期を取る必要がない場合は,EmitEventOneWay() を使用してください.

5) コンポーネントプロセスの作成
最後に,作成したカスタムコンポーネントクラスのインスタンスを生成し,コンポーネントプロセスオブジェクトに登録して,main() 関数に結びつけます.ファイル KinokoAlarmClock-kcom.cc を作成し,以下の内容を記述してください.ここで,ファイル名は "コンポーネント型名-kcom.cc" でなければなりません.
6: #include <iostream.h> 7: #include "KcomProcess.hh" 8: #include "KinokoAlarmClockCom.hh" 9: 10: 11: int main(int argc, char** argv) 12: { 13: TMushArgumentList ArgumentList(argc, argv); 14: 15: TKcomComponent* Component = new TKinokoAlarmClockCom(); 16: TKcomProcess* ComProcess = new TKcomProcess(Component); 17: 18: try { 19: ComProcess->Start(ArgumentList); 20: } 21: catch (TKcomException &e) { 22: cerr << "ERROR: " << argv[0] << ": " << e << endl; 23: } 24: 25: delete ComProcess; 26: delete Component; 27: 28: return 0; 29: }
ここでは,作成したコンポーネントクラスとのインスタンスを作成し,コンポーネントプロセスクラスのインスタンスで包んで,コンポーネントプロセスオブジェクトに実行を与えているだけです.この形は,全てのコンポーネントの実装で同じになります.

例2) イベントカウンタ

ストリーム中のイベントの数を数えて,規定の数に達したらアラームイベントを発生させるコンポーネントです. このコンポーネントのインターフェース宣言は以下のとおりです(ストリームシンクコンポーネントに共通の部分は省略してあります).
component KinokoEventCounter { // KinokoStreamSinkComponent から引き継ぐ分は省略 emits alarm(); accepts setNumberOfEvents(long number_of_events); }
このコンポーネントの完全なソースコードは kinoko/local/tutorials/Component にある以下のファイルです. このコンポーネントは,例1) のアラームクロックでの時間計測の代わりに,ストリーム中に流れてくるイベントの数を使って,アラームイベントを発生させるコンポーネントです.コンポーネントタスクの中でデータストリームにアクセスしていること以外は例1のアラームクロックとほとんど同じです.ここでは,例1で述べなかったことを中心に解説します.

データストリームにアクセスするコンポーネントは,ストリームへの接続形態に応じて,以下のいずれかのクラスを継承して実装されます.

TKinokoStreamSourceComponent
データストリームに対し,データの生成元となるコンポーネント

TKinokoStreamPipeComponent
データストリームにおいて,データを受け取り,必要なら加工し,再びストリームにデータを流すコンポーネント

TKinokoStreamSinkComponent
データストリームに対し,データの受け取るだけのコンポーネント

これらのクラスでは,派生クラスでオーバライドして中身を実装するための仮想関数として,以下のメソッドが宣言されています.

void Construct(void)
コンポーネントが StreamReady ステートから SystemReady ステートに移行する際に呼ばれる.ここで内部オブジェクトの構築やデータデスクリプタの構築と送りだしなどを行う.

void Destruct(void)
コンポーネントが SystemReady ステートから StreamReady ステートに移行する際に呼ばれる.ここで,内部オブジェクトの破壊・片付けなどを行う.このあと再び Construct() が呼び出されることもあるので,その際に矛盾がないようにしなければならない.

int ProcessData(void)
データ処理のループからくり返し呼び出される.ここで必要に応じてストリームからデータを受け取り(Source 以外),処理し,送り出す(Sink 以外).

void OnStart(void)
コンポーネントが SystemReady ステートから Running ステートに移行する際に呼び出される.必要が無ければこのメソッドは実装しなくてもよい.

void OnStop(void)
コンポーネントが Running ステートから SystemReady ステートに移行する際に呼び出される.必要が無ければこのメソッドは実装しなくてもよい.
さらに,これらのフレームワーククラスが派生クラスで使うために提供しているオブジェクトがいくつかあります.
TKinokoInputStream* _InputDataStream;
データを受け取る側のデータストリームオブジェクト.

TKinokoOutputStream* _OutputDataStream;
データを送り出す側のデータストリームオブジェクト.

TKinokoStreamCommandProcessor* _StreamCommandProcessor;
ストリームから受け取ったコマンドを処理し,コンポーネントに伝えるオブジェクト.

TKinokoLogger* _Logger;
ロガーコンポーネントからインポートしたオブジェクト.ログを記録するために使用する.

以下,これらを使って実際にイベントカウンタコンポーネントを作成していきます,イベントカウンタはストリームからデータを読むだけで書き出しは行なわないので,ストリームシンクコンポーネントを継承して実装します.

15: class TKinokoEventCounterCom: public TKinokoStreamSinkComponent { 16: public: 17: TKinokoEventCounterCom(void); 18: virtual ~TKinokoEventCounterCom(); 19: virtual void BuildDescriptor(TKcomComponentDescriptor& Descriptor); 20: virtual int ProcessEvent(int EventId, TKcomEvent& Event); 21: protected: 22: virtual int ProcessSetNumberOfEventsEvent(TKcomEvent& Event); 23: virtual void EmitAlarmEvent(void); 24: virtual void Construct(void) throw(TKinokoException); 25: virtual void Destruct(void) throw(TKinokoException); 26: virtual int ProcessData(void)throw(TKinokoException); 27: protected: 28: enum TEventId { 29: EventId_SetNumberOfEvents = TKinokoStreamSinkComponent::_NumberOfEvents, 30: EventId_Alarm, 31: _NumberOfEvents 32: }; 33: protected: 34: int _EventCount; 35: int _MaxNumberOfEvents; 36: TKinokoDataStreamScanner* _StreamScanner; 37: };
17-18行目のコンストラクタとデストラクタ,および24-26行目のメソッドがここで解説するものです.それ以外の部分については,例1のアラームクロックの説明を参照してください.

まずはコンストラクタとデストラクタです.

10: TKinokoEventCounterCom::TKinokoEventCounterCom(void) 11: : TKinokoStreamSinkComponent("KinokoEventCounter") 12: { 13: _MaxNumberOfEvents = 0; 14: _StreamScanner = new TKinokoDataStreamScanner(); 15: } 16: 17: TKinokoEventCounterCom::~TKinokoEventCounterCom() 18: { 19: delete _StreamScanner; 20: }
親クラスのコンストラクタの引数はコンポーネント型名です. StreamScanner は,データストリームから受け取ったデータパケット(型無しポインタ)から,パケットタイプなどの情報を取り出すオブジェクトです.

Construct()メソッドでは通常内部オブジェクトの構築と初期化を行いますが,ここではイベントカウンタをリセットするだけです.Destruct()では何もする必要はありません.

79: void TKinokoEventCounterCom::Construct(void) throw(TKinokoException) 80: { 81: _EventCount = 0; 82: } 83: 84: void TKinokoEventCounterCom::Destruct(void) throw(TKinokoException) 85: { 86: }

ProcessData() はデータストリームからデータを読み出し,処理を行うメソッドです. まずストリームからデータを読み,バイトオーダを(必要なら)補正します._InputDataStreamNextEntry() メソッドは,ストリームにデータが到着するまで待ち,データのサイズを返しますが,データ待ちが何らかの理由で中断された場合や,データストリームの末尾に到達した場合は 0 を返します.

88: int TKinokoEventCounterCom::ProcessData(void)throw(TKinokoException) 89: { 90: static const int Trailer_Event = TKinokoDataStreamScanner::Trailer_Event; 91: 92: void* Data; 93: int DataSize = _InputDataStream->NextEntry(Data); 94: if (DataSize <= 0) { 95: return 0; 96: } 97: _StreamScanner->CorrectByteOrder(Data, DataSize); 98:

次に受け取ったパケットが特殊なパケット(データデスクリプタやコマンド)だった場合の処理を行います.この例では,受け取ったデータに関する詳細(意味や構造など)は必要無いので,デスクリプタパケットは単に破棄しています.コマンドパケットに対しては,親クラスが提供している _StreamCommandProcessor オブジェクトに処理を任せます.

99: if (_StreamScanner->IsDataDescriptorPacket(Data)) { 100: ; 101: } 102: else if (_StreamScanner->IsCommandPacket(Data)) { 103: int CommandValue = _StreamScanner->CommandValueOf(Data); 104: _StreamCommandProcessor->ProcessCommand(CommandValue); 105: }

もしイベントトレイラパケットを受け取ったら,イベントカウントをインクリメントし,設定イベント数に達しているかチェックします.もし達していたら,それをログに記録し,アラームイベントを発行します.

106: else if ( 107: _StreamScanner->IsTrailerPacket(Data) && 108: (_StreamScanner->TrailerValueOf(Data) == Trailer_Event) 109: ){ 110: _EventCount++; 111: 112: if (_EventCount % _MaxNumberOfEvents == 0) { 113: _Logger->WriteNotice( 114: ComponentName(), "event counts reached the maximum value" 115: ); 116: EmitAlarmEvent(); 117: } 118: } 119:

最後に,ストリームに保持されているのデータパケットを開放し,リターンします.

120: _InputDataStream->Flush(Data); 121: 122: return 1; 123: }

これでコンポーネントは完成です.例1と同様に,これをコンポーネントプロセスオブジェクトで包めば,スクリプトから利用できるようになります.

例3) データフローメータ

データの流量を測ってそれを新たなデータとして添付し,下流のストリームに流すコンポーネントです. このコンポーネントが生成するデータのデータデスクリプタは以下のとおりです.
datasource "flow_meter"<1> { section "data_flow"<1>: tagged { field "event_rate": int-32bit; field "data_flow": int-32bit; } }
このコンポーネントの完全なソースコードは kinoko/local/tutorials/Component にある以下のファイルです. このコンポーネントは,データストリームからデータを読み,それに対して簡単な計算をし,データストリームに書き出すだけのコンポーネントで,独自のコンポーネントイベントを受けたり発行したりはしません.このような,コンポーネントイベントを直接扱わないタイプのコンポーネントは,すでに実装済みの KinokoDataProcessorCom コンポーネントクラスを使って,それに独自の DataProcessor オブジェクトを登録することにより,簡単に作成することができます.詳しくは,使用方法の「DataProcessor を使う」の章を参照してください.

ここで作成するコンポーネントは,パイプ型のコンポーネントなので,元になるアナリシスフレームワークのクラスは,TKinokoDataProcessor です.このフレームワーククラスおよびその親クラスである TKinokoDataSender および TKinokoDataReceiver は,派生クラスでオーバライドして中身を実装するための仮想関数として以下のメソッドを宣言しています.

void BuildDataSource(TKinokoDataSource* DataSource)
システムの構築時に呼び出される.ここで,出力するデータに対応する DataSource オブジェクトを構築する.このメソッドは必ず実装しなければならない.

void OnConstruct(void)
システムの構築時に呼び出される.ここで内部オブジェクトの構築など必要な初期化を行なう.必要がなければ実装しなくてもよい.

void OnDestruct(void)
システムの解体時に呼び出される.ここで内部オブジェクトの解体や必要な終了処理を行なう.必要がなければ実装しなくてもよい.

void OnReceivePacket(void* DataPacket, long PacketSize)
ストリームからパケットを受け取った時に,そのパケットを引数に呼び出される.パケットタイプにかかわらず,全てのパケットに対して呼び出される.必要がなければ実装しなくてもよい.

void OnReceiveDataPacket(void* DataPacket, long PacketSize)
ストリームからデータパケットを受け取った時に,そのパケットを引数に呼び出される.必要がなければ実装しなくてもよい.

void OnReceiveEventTrailerPacket(void* DataPacket, long PacketSize)
ストリームからイベントトレイラパケットを受け取った時に,そのパケットを引数に呼び出される.必要がなければ実装しなくてもよい.
OnReceiveDataPacket() などが実装されていても,OnReceivePacket() は必ず呼び出されることに注意してください.

また,これらのフレームワーククラスが派生クラスで使うために提供している以下のメソッドがあります.

void SendDataDestructorPacket(void)
データデスクリプタパケットを生成して,出力ストリームに送り出す.

void SendRunBeginPacket(void)
RunBegin パケットを生成して,出力ストリームに送り出す.

void SendRunEndPacket(void)
RunEnd パケットを生成して,出力ストリームに送り出す.

void SendEventTrailerPacket(void)
EventTrailer パケットを生成して,出力ストリームに送り出す.

void SendPacket(void* Packet, long PacketSize)
引数に渡されたパケットを出力ストリームに送り出す.

さらに,いくつかのオブジェクトが,派生クラスで使用するために提供されています.

TKinokoInputStream* _InputStream
データを受け取る側のデータストリームオブジェクト

TKinokoDataStreamScanner* _InputStreamScanner
受け取ったデータパケットから情報を取り出すオブジェクト

TKinokoDataDescriptor* _InputDataDescriptor
入力データストリームから来るデータのデータデスクリプタオブジェクト

TKinokoOutputStream* _OutputStream
データを送り出す側のデータストリームオブジェクト

TKinokoDataStreamFormatter* _OutputStreamFormatter
送り出すパケットに情報を書き出すオブジェクト

TKinokoDataDescriptor* _InputDataDescriptor
出力データストリームに送り出すデータのデータデスクリプタオブジェクト

TKinokoDataSource* _OutputDataSource
出力データストリームに送り出すデータのデータソース定義オブジェクト
これらのメソッドをオブジェクトを使って,DataFlowMeter クラスを実装します.以下は,DataFlowMeter クラスのクラス定義です.(ファイル KinokoDataFlowMeter.hh に書く)
14: class TKinokoDataFlowMeter: public TKinokoDataProcessor { 15: public: 16: TKinokoDataFlowMeter(void); 17: virtual ~TKinokoDataFlowMeter(); 18: protected: 19: virtual void BuildDataSource(TKinokoDataSource* DataSource); 20: virtual void OnConstruct(void) throw(TKinokoException); 21: virtual void OnReceivePacket(void* Packet, long PacketSize) throw(TKinokoException); 22: virtual void OnReceiveDataPacket(void* Packet, long PacketSize) throw(TKinokoException); 23: virtual void OnReceiveEventTrailerPacket(void* Packet, long PacketSize) throw(TKinokoException); 24: protected: 25: virtual void SendReportPacket(int EventRate, int DataFlow) throw(TKinokoException); 26: protected: 27: TKinokoTaggedDataSection* _DataSection; 28: TKinokoTaggedDataSectionFormatter* _Formatter; 29: long _EventCount, _DataAmount; 30: int _EventRateIndex, _DataFlowIndex; 31: int _LastReportTime, _ReportInterval; 32: };
25行目の SendReportPacket() は,このコンポーネントで作成したデータフローデータのパケットをストリームに送るために内部で使用されるメソッドです.27行目の _DataSection は,出力するデータフローデータのセクション構造を記述したオブジェクト,28行目の _Formatter はデータストリームパケットにデータを書き込むためのユーティリティオブジェクトです.

ファイル KinokoDataFlowMeter.cc を作成し,その中でこれらのメソッドを実装します.

コンストラクタとデストラクタです.この例では特に何もする必要はありません.

13: TKinokoDataFlowMeter::TKinokoDataFlowMeter(void) 14: { 15: } 16: 17: TKinokoDataFlowMeter::~TKinokoDataFlowMeter() 18: { 19: }

BuildDataSource() メソッドは,出力データストリームに流すデータの構造を記述したデータソースオブジェクトを構築するメソッドです.

21: void TKinokoDataFlowMeter::BuildDataSource(TKinokoDataSource* DataSource) 22: { 23: _DataSection = new TKinokoTaggedDataSection(DataSource, "data_flow"); 24: 25: int DataWidth = 32; 26: _EventRateIndex = _DataSection->AddField("event_rate", DataWidth); 27: _DataFlowIndex = _DataSection->AddField("data_flow", DataWidth); 28: 29: DataSource->AddDataSection(_DataSection); 30: _Formatter = _DataSection->Formatter(); 31: }
生成するデータはタグ付きセクション (Tagged Section) なので,まず TKinokoTaggedDataSection のオブジェクトを作成します.この際,例のようにコンストラクタの引数にデータソースオブジェクトのポインタとセクション名を渡す必要があります.

次に,作成したセクションに,必要なフィールドを追加していきます.TKinokoTaggedDataSectionAddField() メソッドは,引数にフィールド名とデータ幅をとり,データセクションにフィールドを追加して,そのフィールドのインデクスを返します.このインデクスは後でフィールドにアクセスするときに必要になるので,メンバ変数に保存しておきます.

データセクションオブジェクトの構築が終了したら,それをデータソースオブジェクトに登録して,データソースの構築は終了です.後で,データ値をセクションのデータパケットに記録する際に,そのセクションのフォーマッタオブジェクトが必要になるので,セクションからフォーマッタオブジェクトを取得し,メンバ変数に保存しておきます.

次は内部オブジェクトの生成と解体のために呼び出される OnConstruct()OnDestruct() メソッドです.この例では,構築すべき内部オブジェクトはないので,OnConstruct() では単にカウンタのクリアと現在時刻の取得だけを行なっています.また,OnDestruct() では特に必要な処理はないので,オーバライドはしていません.

33: void TKinokoDataFlowMeter::OnConstruct(void) throw(TKinokoException) 34: { 35: _EventCount = 0; 36: _DataAmount = 0; 37: 38: _LastReportTime = TMushDateTime().AsLong(); 39: _ReportInterval = 10; 40: }

さて,いよいよデータパケットを受け取ったときの処理です. メソッド OnReceiveDataPacket() がデータパケットを受け取るたびに,メソッド OnReceiveEventTrailerPacket() がイベントトレイラパケットを受け取るたびに呼び出されるので,これらをオーバーライドし,中身を実装します.

このコンポーネントは,受け取ったデータのサイズとイベントの数を記録するだけなので,データパケットを受け取った時の処理はサイズを記録することだけです. イベントトレイラを受け取ったときには,記録しているイベント数をインクリメントします.

OnReceiveEventTrailer() では,最後にデータフローデータをストリームに流してからの経過時間を計算し,これが設定された時間間隔を越えていたら,イベントレート等を計算して,データパケットをストリームに送り,イベントカウンタ等をクリアします.

47: void TKinokoDataFlowMeter::OnReceiveDataPacket(void* Packet, long PacketSize) throw(TKinokoException) 48: { 49: _DataAmount += PacketSize; 50: } 51: 52: void TKinokoDataFlowMeter::OnReceiveEventTrailerPacket(void* Packet, long PacketSize) throw(TKinokoException) 53: { 54: _EventCount++; 55: 56: long PastTime = TMushDateTime().AsLong() - _LastReportTime; 57: if (PastTime > _ReportInterval) { 58: int EventRate = _EventCount / PastTime; 59: int DataFlow = _DataAmount / PastTime; 60: SendReportPacket(EventRate, DataFlow); 61: 62: _EventCount -= EventRate * PastTime; 63: _DataAmount -= DataFlow * PastTime; 64: _LastReportTime += PastTime; 65: } 66: }

データパケットの送出は,メソッド SendReportPacket() で行なっています.このメソッドの実装は以下の通りです.

68: void TKinokoDataFlowMeter::SendReportPacket(int EventRate, int DataFlow) throw(TKinokoException) 69: { 70: int DataSize = _Formatter->DataSize(); 71: int PacketSize = _Formatter->PacketSizeFor(DataSize); 72: 73: void* Buffer; 74: do { 75: _OutputStream->NextEntry(Buffer, PacketSize); 76: } while (Buffer == 0); 77: 78: _Formatter->WriteHeaderTo(Buffer, DataSize); 79: _Formatter->WriteTo(Buffer, _EventRateIndex, EventRate); 80: _Formatter->WriteTo(Buffer, _DataFlowIndex, DataFlow); 81: 82: _OutputStream->Flush(Buffer, PacketSize); 83: 84: SendEventTrailerPacket(); 85: }
まず,パケットに必要なメモリのサイズを計算します(70-71行目).DataSize は,パケットヘッダを除くデータそのものを格納する部分のサイズです.Formatter の PacketSizeFor() メソッドは,このデータサイズを引数に取って,パケットのサイズを計算します.この例の Tagged Section の場合はデータサイズは固定されていますが,他の全てのセクションタイプでは,データサイズは毎回異なった値になる可能性があることに注意してください.

パケットサイズを計算したら,このサイズのメモリをストリームから確保します(73-76行目). _OutputStream は,一時的に書き出しができないとき(出力がバッファで,領域が空いていないときなど),アドレスに 0 を返します.ここでは,この様な場合には黙ってもう一度メモリ要求をくり返すようにしています.

次にフォーマッタオブジェクトを使用して,パケットの各フィールドに値を書きこんでいきます(78-80行目).それが終ったら,_OutputStreamFlush() メソッドにより,ストリームへのパケットの送り出しと,バッファメモリの開放をします(82 行目.Flush() メソッドはこの両方を行なう).

1イベント分のデータパケットを全て送ったら,最後に SendEventTrailer() を呼び出して,イベントトレイラパケットを送ることを忘れないようにしてください.データストリームの下流では,このパケットによってイベントの区切りが判別されます.

この例で使っている Tagged セクションタイプに限っては,データサイズおよびパケットサイズが毎回同じなので,以下のように固定メモリを確保して,使い回すこともできます.

//// 以下は,OnConstruct() に書く //// if (_Buffer == 0) { int DataSize = _Formatter->DataSize(); int PacketSize = _Formatter->PacketSizeFor(DataSize); _Buffer = new char[PacketSize]; } - - - - - - 8< - - - - - き り と り - - - - - 8< - - - - - - //// 以下は SendReportPacket() の 70-83行目に相当する内容 //// _Formatter->WriteHeaderTo(Buffer, DataSize); _Formatter->WriteTo(Buffer, _EventRateIndex, EventRate); _Formatter->WriteTo(Buffer, _DataFlowIndex, DataFlow); SendPacket(Buffer, PacketSize); - - - - - - 8< - - - - - き り と り - - - - - 8< - - - - - - //// 以下は,OnDestruct() に書く //// delete[] _Buffer; _Buffer = 0;

以上のコードによって,入力データストリームからデータを受け取り,データフローを計算して,それを出力ストリームに流すコンポーネント(の中核部分)が完成しました.しかし,このコンポーネントは受け取ったデータを全て吸収してしまうので,実用上やや不便です(下流にはデータフローのパケットしか流れてこない).おそらく,下流のコンポーネントは,全てのデータを受け取りたいことの方が多いでしょう.

そこで,受け取ったデータはそのまま下流に流し,かつ生成したデータフローの情報も加えるように改良します.と言っても,その方法は簡単です.パケットを受け取った時に必ず呼ばれるメソッド OnReceivePacket() をオーバーライドして,受け取ったパケットをそのまま出力ストリームに流すようにするだけです.

42: void TKinokoDataFlowMeter::OnReceivePacket(void* Packet, long PacketSize) throw(TKinokoException) 43: { 44: SendPacket(Packet, PacketSize); 45: }

最後に,main() 関数で作成したクラスのインスタンスを作成し,これをアナリシスフレームワーク用コンポーネントオブジェクトで包んで,さらにそれをコンポーネントプロセスオブジェクトで包めば,完成です.

10: #include "KinokoDataProcessorCom.hh" 11: #include "KinokoDataFlowMeter.hh" 12: 13: int main(int argc, char** argv) 14: { 15: TMushArgumentList ArgumentList(argc, argv); 16: 17: TKinokoDataProcessor* MyDataProcessor = new TKinokoDataFlowMeter(); 18: TKcomComponent* Component = new TKinokoDataProcessorCom(MyDataProcessor); 19: TKcomProcess* ComProcess = new TKcomProcess(Component); 20: 21: try { 22: ComProcess->Start(ArgumentList); 23: } 24: catch (TKcomException &e) { 25: cerr << "ERROR: " << argv[0] << ": " << e << endl; 26: } 27: 28: delete ComProcess; 29: delete Component; 30: delete MyDataProcessor; 31: 32: return 0; 33: }

スクリプト言語を拡張する

Kinoko で使用している基本スクリプトは全て kinoko/src/kernel/lib-common/kisc にある TKiscScript を使用して実装してあります.TKiscScript は,Kinoko の一部である Parasol 構文解析ライブラリで作成したパーザモジュールをまとめただけの非常に小さいクラスで,ここに新しいパーザモジュールを追加することにより Kinoko で使用している全てのスクリプトを拡張することができます.

パーザモジュールを作成する

Parasol 構文解析ライブラリを用いてパーザモジュールを作成する方法は,以下の Parasol ページの末尾にあるチュートリアル「パーザの拡張」を参照してください. Parasol ライブラリ自体は,kinoko/src/kernel/lib-common/parasol にあります.

若干複雑ですが,kinoko/src/kernel/lib-common/kisc にある KiscSystemScript.hh/cc は独立したパーザモジュールの実装例です.Mush システムコールインターフェースライブラリを使ってシステム周りの機能をスクリプトに追加するパーザモジュールで,Mush ライブラリの詳細を気にしなければ独立した実装の例として参考にできます.

  • Mush システムインターフェースライブラリ Mush ライブラリ自体は,kinoko/src/kernel/lib-common/mush にあります.

    スクリプトにパーザモジュールを追加する

    KinokoScript の本体部分は kinoko/src/kernel/lib-common/kisc/KiscScript.hh にある TKiscScript クラスです.これは,以下のように Parasol のパーザモジュールを集約しただけのものです.
    class TKiscScript: public TParaStandardParser { public: TKiscScript(void); TKiscScript(int argc, char** argv, TMushConsole* Console = 0); virtual ~TKiscScript(); protected: virtual void OnConstruct(void); protected: TParaParser* _SystemScript; TParaParser* _DatabaseScript; TParaParser* _MathScript; TMushConsole* _Console; };
    この例では SystemScript, DatabaseScript および MathScript の3つのパーザモジュールが追加されています(基本部分は TParaStandardParser から継承されています).

    KiscScript.cc においてインスタンスを作成し,登録しています.

    #include "ParaParser.hh" #include "KiscSystemScript.hh" #include "KiscDatabaseScript.hh" #include "KiscMathScript.hh" #include "KiscScript.hh" using namespace std; TKiscScript::TKiscScript(void) { _SystemScript = 0; _DatabaseScript = 0; _MathScript = 0; } TKiscScript::TKiscScript(int argc, char** argv, TMushConsole* Console) : TParaStandardParser(argc, argv) { _SystemScript = 0; _DatabaseScript = 0; _MathScript = 0; _Console = Console; } TKiscScript::~TKiscScript() { delete _MathScript; delete _DatabaseScript; delete _SystemScript; } void TKiscScript::OnConstruct(void) { TParaStandardParser::OnConstruct(); Merge(_SystemScript = new TKiscSystemScript(_Console)); Merge(_DatabaseScript = new TKiscDatabaseScript()); Merge(_MathScript = new TKiscMathScript()); }
    上記と同様にして新しいパーザモジュールを追加し, Kinoko 全体を再コンパイルすれば追加機能が使えるようになっているはずです(Kinoko 本体を変更しているので,全体の再コンパイルが必要です).
    % cd $KINOKO_ROOT/src % make uninstall % make


    Edited by: Enomoto Sanshiro