CC/7000 CAMAC クレートコントローラのためのデバイスドライバ

以下で説明するのは、 東陽テクニカ製 CAMAC クレートコントローラ CC/7000 および AT 互換機用インターフェースCC/7000AT-IF (ISAバス) を用いて、 Linux から CAMAC をコントロールするためのデバイスドライバです。 特に、原子核・素粒子実験で行なわれるような、 イベントタイプのデータ収集を支援する事に重点が置かれています。 ドライバは九州大学タンデム加速器施設で開発されたもので、 若干の修正と簡単なデータ収集システムのサンプルの作成、 および文書の整備を岡村が行ないました。 再配布は自由との事ですので多いに利用してください。

岡村 弘之 (東京大学理学部)
okamura@timshel.riken.go.jp


パフォーマンス ― Linux を使う理由

使ってみてから落胆するのを防ぐため(^_^;、凡よその性能を最初にまとめます。

まず対象と考えているのは、 簡単なカウンターテストや学生実験レベルのデータ収集に適した 小規模なシステムです。 フロントエンドとホストが分離された本格的システムと比較するのは、 無意味ですから止めましょう。

Pentium-100MHz マシンを用いて行なった簡単なデータ収集テストでは、 LAM 発生から測った1イベント処理に要する最短時間は

約20+(10×読込みワード数)μs

でした。 CC/7000 は I/O ポートを通してしか制御されない (例えば CES2180 StarBurst J11 のように CAMAC アクセスを ハード的にマッピングする仕掛けを持っていない) 事を考慮すると、妥当な値と思われます。

Linux (および一般の UNIX) はリアルタイム OS ではないので、 カーネルが他のサービスを行なっていると、 それが終了するまで CAMAC の割り込み処理は遅延されます。 その影響は、システムにどの程度の負荷がかかっているかに 依存しますが、通常のディスクアクセスやグラフィックス描画 等を行なった所では、 50〜100μs程度のデッドタイムの増大として観測されました。 系統的なテストは行なっていませんが (調べられた方が居ましたら報告してください)、 実用上問題になる事は少ないと思われます。

OS のオーバーヘッドが少ないという意味では、 MS-DOS 等の (ハードウェア資源が自由自在に使える) 単純なシステムの方が有利とされています。 しかし、一方でヒストグラミングからディスプレイまで全てを備えた プログラムを開発しないと使い物にならないという難点が挙げられます。 速度に関しては、 CC/7000 のような I/O ポート経由の制御では所詮限界が有り、 むしろOS のオーバーヘッドは 今日の驚異的な CPU 速度向上によって マイナーな問題と化して来たように思われます。 Linux 等の本格的 OS を使う事は、 マルチプロセス、ネットワーク制御、充実した数々のツールと言った点で 大きなアドバンテージをもたらしてくれます。 ちょっとしたカウンターテストでも、 出来合いのヒストグラミングツールを流用して paw++ で表示したりできるのは、 素晴らしいと言えるのではないでしょうか。


デバイスドライバとローダブルモジュール

一般的な UNIX の I/O 制御と同様に、 CAMAC 制御もデバイスドライバを通して行なわれます。 後述のように、デフォルトでは /dev/dc{,1,2,3} という名前が major number 61 に割り当てられます。 LAM の enable/disable、バスの初期化等の速度を要求されない オペレーションは、このデバイスに ioctl コマンドを 発行する事によって行なわれます。

しかし、 原子核・素粒子実験で一般的なイベントタイプのデータ収集では、 非同期に発生するイベントに対して遅延無く データの読み出しを行なう必要が有り、 ユーザープログラムがデバイスドライバを介して処理していたのでは とても間に合いません。 そこで、割り込み発生時の処理をデバイスドライバの中に記述し、 取り敢えずデータを適当なバッファに貯めておいて、 ユーザープログラムがデバイスドライバから read で読み出す方法が採られます。 つまり、実験装置や読み出しデータに応じて、 デバイスドライバを書き換える必要があるわけです。

元々 Linux は、 複雑になり過ぎて実現が困難になった OS に対するアンチテーゼ(?)として、 モノリシックカーネルによる、小さくて軽いシステムの実装を基本としていました。 しかしモノリシックカーネルでは、 デバイスドライバを変更するとカーネル全体を作り直す事になってしまいます。 これでは次から次に現れる有象無象のデバイスや、 PCMCIA 等の着脱型デバイスに対応できないので、 カーネルを止めずに組み込み/削除可能なデバイスドライバの仕組みが 加えられました。これがローダブルモジュールです。 ローダブルモジュールによるデバイスドライバの作成に関しては、 Linux Jounal #23 (Mar. '96) から 5 号連載で 非常に判りやすく解説されていますので、興味の有る方は参照してください。 また、この記事の日本語訳が LINUX JAPAN 創刊号にまとめて掲載されています。

本デバイスドライバもローダブルモジュールによる実装をサポートしています (カーネルに静的に組み込む事を拒むものではありませんが)。 ユーザーは、測定条件に応じて一つのルーチンを用意するだけで済むように なっています。 加えて、後述するように一部の (あまり危険でないと思われる?) コマンドを setuid すれば、 全く root 権限を要求する事無しに データ収集システムを構築する事が可能です。


make およびインストール

配付パッケージには以下のファイルが含まれています。 現在の配布元は こちらです。
      README.html  ... このファイル
      Makefile     ... デバイスドライバ作成/組み込み、および
                       サンプルシステム作成用 make ファイル
      cc7000.gif   ... CC/7000 のレジスタ表

  デバイスドライバ関連
      camac.h      ... 関連ソースのヘッダファイル
      camack.c     ... 割り込み処理支援ファンクション集
      dc.c         ... デバイスドライバ本体
      dc_int.c     ... 割り込み処理ルーチン
                       (int_test.c へのシンボリックリンク)
      int_test.c   ... 割り込み処理ルーチンのサンプル
      test_led.c   ... テストプログラム

  データ収集システムのサンプル
      crashm.h     ... 共通ヘッダファイル
      commander.c  ... コマンド解釈メインプログラム
      recorder.c   ... データ記録プロセス
      analyzer.c   ... オンライン解析プロセス
      event1.f     ... analyzer から呼ばれる 1 イベント解析ルーチン
      player.c     ... データ読み出しプロセス
      rei          ... Tcl/Tk による GUI 制御スクリプト
      camac.gif    ... rei の実行画面サンプル
インストールは以下の手順で行ないます。

先ず、Makefile の中の

         DEVICE = dc
          MAJOR = 61
         IOPORT = 0x310
            IRQ = 11
          DELIM = 0x8001
を適当に編集してください。 特に IOPORT と IRQ は、 ハードウェアの設定と一致している事をよく確認してください (ハードの設定に関しては 付録A を参照してください)。 現在の所、Linux の多くのデバイスドライバが行なうような 自動検出の機能はサポートしていません。 DELIM (デリミタ) は イベントの区切り子に使いますが、 データ収集システム全体の整合性のために Makefile の中で指定しています。

次に root になって

      % make devices
を実行すると、(デフォルトの設定では) /dev/dc{,1,2,3}のデバイスが定義されます。 恒久的な設定はこれで終りです。

以下は測定毎の手順です。 先ず読み込みたいデータを記述した割り込みルーチンを "dc_int.c" として用意します (詳しくは後述の説明を参照してください)。 次に

      % make [all]
によってデバイスドライバをコンパイルします。 生成されるオブジェクトファイル "dc.o" がローダブルモジュールです。 dc.o をカーネルに組み込むには
      % make install
を実行します。 古いバージョンが組み込まれている場合には入れ換えを行ないます。

make install (および uninstall) は 本来 root になって行なうべきものですが、

      % chmod +s /sbin/insmod
によってコマンドを setuid しておくと、 平ユーザーでもデバイスの組み込み/切り離しができるようになります (これによるセキュリティの問題は無いと思いますが…)。

正常にドライバが組み込まれれば、

      Character devices:
       .....
      61 dc
       .....
といったメッセージが出力されるはずです。 また、
      ... ... ... <<< dc >>> ... ... ... installed.
       dc: Buffer size is 131072 bytes
というメッセージが /var/adm/messages というファイルの 最後に書き加えられます。 通常このファイルは root のみが読み出し可の設定となっていますが、
      % dmesg
によって、 平ユーザーでも末尾部分を出力させる事ができます。 デバイスがうまく組み込まれなかった場合には、 失敗となった原因のヒント (およびダンプ情報) が /var/adm/messages に書き込まれますから、参照してください。 最も有り勝ちなのは IRQ や I/O ポートの衝突です。

ドライバが組み込まれたら、簡単なテストをしてみましょう。 プログラム "test_led.c" は Dataway Indicator (LED) を点滅表示させるものです。 これをコンパイルし (ライブラリは何も要りません)、

      % cc -o test_led test_led.c
      % test_led 4
のように、LED のステーションナンバーを引数に与えて 起動してみてください。

以上で CAMAC 制御の準備は完了です。 dc.o をカーネルから切り離すには

      % make uninstall
を実行してください。 dmesg コマンドの出力の末尾には
      ... ... ... <<< dc >>> ... ... ... uninstalled.
の一行が加えられるはずです。

参考のために、 ローダブルモジュールおよびデバイスドライバに関連する コマンドを以下に列挙しておきます。 make … は、内部でこれらを実行しているだけです。

% /sbin/lsmod
現在組み込まれているローダブルモジュール一覧を表示します。
% /sbin/insmod module
moduleをカーネルに組み込みます。
% /sbin/rmmod module
moduleをカーネルから切り離します。
% mknod /dev/name c major minor
name という名の char 型デバイスを メジャー番号majorおよびマイナー番号minorに 関連付けて定義します。
% cat /proc/devices
デバイスドライバ一覧を表示します。
% cat /proc/ioports
各デバイスドライバの使用する I/O ポート一覧を表示します。
% cat /proc/interrupts
各デバイスドライバの使用する IRQ 一覧を表示します。
dc.c では ioctl(..., DC_INTON,..) で 割り込み許可を与えるまで IRQ を使用しません (/proc/intterupts に表示されない)。
蛇足ですが、/proc/... には cat しか使えません (more や less、ましてエディタは使えません)。


ioctl ファンクション

既に述べたように、 速度を要求されない CAMAC オペレーション (LAM の enable/disable、バスの初期化等)は、 /dev/dc に ioctl コマンドを 発行する事によって行なわれます。 実際のプログラムのテンプレートとしては、 以下のようなものになります。
      #include <fcntl.h>
      #include <sys/types.h>
      #include <sys/wait.h>
      #include <unistd.h>
      #include "camac.h"

      int      fd , result ;
       ....
      fd = open ( "/dev/dc" , O_RDWR ) ;
      result = ioctl ( fd , DC_<func> , <arg> );
       ....
      close ( fd ) ;
DC_<func> に指定可能なシンボルは "camac.h" 中で 定義されています。 各々のコマンドの機能と <arg> の説明を以下に示します。 DC_INITIAL と DC_I を例外として、 <arg> にはポインタを渡す事に注意してください。
DC_INITIAL
CC/7000 のクレートの選択 (0〜3) と初期化を行ないます。 戻り値は 0 (ONLINE) または -1 (OFFLINE) です。 CC/7000 は4台までのマルチクレートシステムを サポートしていますので、操作対象とするクレートの ディップスイッチ設定を予め確認し、 0〜3 のを <arg> に渡してください。 しかし、通常は 0 を与えると思っていて間違いないでしょう。
DC_Z
CAMAC バスに初期化信号 (Z) を送出します。 戻り値は 0 (success) または -1 (time out) です。 <arg> は参照しません。
DC_C
CAMAC バスにクリア信号 (C) を送出します。 戻り値は 0 (success) または -1 (time out) です。 <arg> は参照しません。
DC_I
CAMAC バスのインヒビット信号 (I) を ON (<arg>≠0) または OFF (<arg>=0) します (<arg> は参照です)。
DC_L_P
全モジュールの LAM 状況のビットパターンを、 long*<arg> に返します。 ビット 0 はステーションナンバー 1 のモジュールに対応します。
DC_L_E
LAM が立っているモジュールの中、 ステーションナンバーの最も小さいものの値を long*<arg> に返します。 LAM の立っているモジュールが無い時は 0 を返します。
DC_CYCLE
CAMAC ファンクションを実行します。 <arg> として渡されるのは、 camac.h の中で定義されている DCCycle という構造型の変数です。 例えば
       DCCycle  dcc ;
       ....
       dcc.n = 8 ; dcc.a = 0 ; dcc.f = 16 ;
       dcc.data = 0xffff ;
       result = ioctl ( fd , DC_CYCLE , &dcc ) ;
       
N=8, A=00xFFFF の値を書き込むオペレーションです。 戻り値は 0 (success) または -1 (failure) です。
DC_INTTST
動作テストのために割り込みルーチンをコールします。 <arg> は参照しません。
DC_INTON
割り込みを許可 (データ収集をスタート) し、 割り込みカウンタを0にセットします。 割り込みカウンタは割り込み発生の度に+1されます。 <arg> は参照しません。 後述する割り込みルーチン "dc_int.c" の中で 以下のシンボルを定義しておくと、 DC_INTON の中にちょっと便利な機能が付加されます。
LAM_N, LAM_A
指定された (N, A) に対し F=26 (LAM Enable) を送出します。
OUTREG
N=OUTREG に対し、 データ 0xFF00F=16 で送出します。 多くの Output Register (海津、テクノランド製等) は F=16 がレベル出力ですから、 トリガロジックやスケーラのゲートに用いる事ができます。
DC_INTOFF
割り込みを禁止 (データ収集をストップ) します。 <arg> は参照しません。 後述する割り込みルーチン "dc_int.c" の中で 以下のシンボルを定義しておくと、 DC_INTON と同様に便利な機能が付加されます。
LAM_N, LAM_A
指定された (N, A) に対し F=24 (LAM Disable) を送出します。
OUTREG
N=OUTREG に対し、 データ 0x0000F=16 で送出します。
DC_INTCLR
割り込みカウンタやバッファポインタを全て0にセットします。 <arg> は参照しません。 通常は DC_INTON の前に実行するのが望まれます。
DC_INTCNT
割り込みカウンタの値を long*<arg> に返します。
DC_INTLOS
バッファに取り込まれなかった割り込みの回数を long*<arg> に返します (割り込みの発生頻度が非常に高くバッファが溢れる場合には、 割り込み処理がスキップされます)。
DC_INTEOF
バッファにデータの終了を示す区切り子 (0xFFFF) を書き込み、 データ待ちのプロセスが有ればフラッシュします。 データ書き込みプロセスにファイルクローズを合図するために使います。 <arg> は参照しません。


割り込み処理ルーチンの記述

割り込み処理ルーチンは "dc_int.c" という名前のファイルに記述してください。 デバイスドライバの make の際に自動的にインクルードされます。 実際には、実験のセットアップ毎に適当なファイル名 (例えば hogehoge.c) を付け、
      % ln -s hogehoge.c dc_int.c
によってシンボリックリンクを張るのが便利でしょう。

割り込みルーチンでは、概ね以下の事柄を記述します。

  1. LAM のチェック (CAMAC からの割り込みとは限らないため)
  2. バッファ残量のチェック
  3. CAMAC からバッファへのデータの読み込み
  4. モジュールのクリア (必要ならば処理終了のパルス出力)
サンプルとして "int_test.c" というファイルを用意しましたので、 あまり内部に立ち入りたくないという人は、 その中の
    #define COINREG  1
    #define OUTREG   5
    #define ADC1     6
    #define LAM_N    ADC1
    #define LAM_A    0
          .....

          /* Data AcQuisition */
          camacCcycle2(COINREG,0,2,&dc_evbuff[(dc_wp++)%EV_MAX],&stat);
          for (i=0;i<8;i++)
            camacCcycle2(ADC1,i,0,&dc_evbuff[(dc_wp++)%EV_MAX],&stat);
          .....
        camacCcycle2(ADC1,0,9,0,&stat);  /* clear module */
辺りを書き換えて使うのが簡単です。 camacCcycle2 等のファンクションの類の説明は 次節を参照してください。 この例では、 先ずステーションナンバー1の Coincidence Register の値を読み込み、 次にステーションナンバー6にある ADC のサブアドレス0〜7のデータを 順次読み込んでバッファにストアします。 最後にモジュールをクリアしますが、 この例では F=9 を使って個別にクリアしています。 camacCc() によって バスにクリア信号を送出する方法は処理が短時間で済みますが、 スケーラを使ったり Output Register のレベル出力を使っている場合には、 これらも一緒にクリアされてしまうので不適当です。

例題では更に、 イベントデータを以下のレコード形式に整えて バッファに書き込んでおり、 後述のデータ収集システムのサンプル に適合させています。

          +--1--2--+------------------L-+---L+2-+
          | EVTLEN |      Contents      | DELIM |
          +--------+--------------------+-------+
EVTLEN はバイト単位のイベント長 (ここでは L+2)、 DELIM は区切り子 (0x8001) です。

LAM_N および LAM_A のシンボルは割り込みルーチンでは使用されませんが、 これらを定義して make すると DC_INTON 等の動作が便利に使えます ので、OUTREG と合わせて宣言しておく事を薦めます。 "int_test.c" の例題では、 Output Register の下位 8 ビットにのみ パルスを出力 (上位はレベルで使うので) している事に注意してください。

以下は、自分でもっと便利なルーチンを書きたい、 或いは、通常と異なる凝った処理をしたいという人向けですので、 深入りしたくない方は読み飛ばしてください。

データ収集ルーチンとは言え、 Linux のような汎用 OS のデバイスドライバの一部を書くわけですから、 次のような一般的注意が考えられます。

例えば、 インテリジェント補助クレートコントローラ (ACC) で 割り込みを記述する場合には、 (Interrupt Register や ACC への直接のシグナル等によって) 出来るだけ早いタイミングで割り込みを発生させ、 割り込みルーチンの中では Q を見ながら ADC/TDC の変換終了を待って、 データ読み出しを行なうのが一般的と思います。 これは、データ収集の dead time を最小に抑えようとする配慮の結果ですが、 ACC が割り込み処理以外に重要な仕事をしていないからこそ 許される方法と言えます。

一方 Linux (を含む汎用 OS) では、 システムの他のサービスに対する遅延を防ぐために、 個々の割り込み処理を出来るだけ短時間に終了させたいという要請が有ります。 データ収集の効率は多少犠牲になりますが、 システム全体の有効な運用としては、 例えば以下のような方法が薦められます。

また、カーネルの一部である事から次のような注意も加えられます。 普通はあまり気にする必要は無いと思いますが、 簡単なデバッグ情報を printf() で出力させたいという事は 有るでしょう。 この場合には printk() という関数を使います。 使用法は printf() と同様ですが、 出力先は /var/adm/messages となります (ただし、カーネル 1.3.37 以降はコンソールに出力されるそうです)。 /var/adm/messages への出力は (万が一カーネルパニックが起こった時も最期の断末魔の記録を残すため) バッファされません。 従って、頻繁に printk() を行なうとシステムの負荷が グッと増える事に注意してください。 /var/adm/messages の末尾部分は dmesg によって出力する事ができます。

最後の注意は当たり前の事ですが、

繰り返しますがカーネルの一部なわけですから、 これらの異常動作はカーネルパニックの引金となります。 UNIX システムによっては全く無防備なものもあるようですが、 幸いな事に Linux はこの点で (無限ループを除きますが) 非常に堅牢であり、 /var/adm/messages に“Oops”というメッセージ とダンプ情報を残すだけで大事に至る事は少ないようです。 しかし用心に越した事はありません。


割り込み処理ルーチン支援関数

以下は camack.c で定義されている関数です。 dc_int.c の最初で include しておくと便利です。
int camacCinitialize ()
CC/7000 を初期化します。 割り込みルーチンの中で使う必要は先ず無いでしょう。 戻り値は 0 (ONLINE) または -1 (ONLINE) です。
int camacCz ()
CAMAC バスに初期化信号 (Z) を送出します。 割り込みルーチンで使われる事は少ないでしょう。 戻り値は 0 (success) または -1 (time out) です。
int camacCc ()
CAMAC バスにクリア信号 (C) を送出します。 全てのモジュールを一度にクリアする際に便利ですが、 スケーラ等を使用していると一緒にクリアされてしまうので、 注意が必要です。 戻り値は 0 (success) または -1 (time out) です。
int camacCi (int onoff)
CAMAC バスのインヒビット信号 (I) を ON (onoff≠0) または OFF (onoff=0) します。 割り込みルーチンで使われる事は少ないでしょう。 戻り値は 0 のみです。
long camacClamp ()
全モジュールの LAM 状況をビットパターンで返します。 ビット 0 はステーションナンバー 1 のモジュールに対応します。
int camacClam ()
LAM が立っているモジュールの中、 ステーションナンバーの最も小さいものの値を返します。 LAM の立っているモジュールが無い時は 0 を返します。
int camacCcycle (int N, int A, int F, long *data, int *state)
N, A, F に対する 24-bit の CAMAC ファンクションを実行します。 戻り値は 0 (success) または -1 (failure) です。 state には Q (bit 0) および X (bit 1) が返されます。
int camacCcycle2 (int N, int A, int F, short *data, int *state)
N, A, F に対する 16-bit の CAMAC ファンクションを実行します。 戻り値は 0 (success) または -1 (failure) です。 state には Q (bit 0) および X (bit 1) が返されます。
これらのファンクションの内部を覗くと、 随分間怠っこい記述の仕方をしているとの印象を受けるでしょう。 マクロで書けば劇的に速度向上できるように思うかも知れませんが、 実際は (8 MHz の ISA バスの) I/Oアクセスに 大部分の時間を喰われているので、 計測可能な改善は得られないようです。


バッファデータの読み出し

割り込みルーチンの中で読み込まれ、バッファに貯められたデータは、 以下のデバイスを open して read で読み出す事ができます。 目的に応じてデバイスを使い分けてください。 バッファの大きさは、デフォルトでは128 kbytes です。
/dev/dc
データをバイナリで返します。 バッファが空でも直ちに制御が返ります (read が終了する)。 本来の char 型デバイスでは open(..., O_NONBLOCK) で 実現する機能ですが、本ドライバではデバイスのマイナー番号で 機能を分離しており、 O_NONBLOCK フラグはサポートしていません。
/dev/dc1
データをバイナリで返します。 バッファが空の場合には、 データが取り込まれるまで制御を戻さずに待ちます。
/dev/dc2
データをアスキー (cat で表示できる数字列) で返します。 イベントの区切りは LF (コード 10) です。 バッファが空の場合には、 データが取り込まれるまで制御を戻さずに待ちます。
/dev/dc3
データをアスキーで返します。 イベントの区切りは CR (コード 13) です。 バッファが空の場合には、 データが取り込まれるまで制御を戻さずに待ちます。
例えば、オンライン解析をせずに ひたすら raw data をファイルに落すだけなら
      % cat /dev/dc1 > hogehoge.dat
で用が足ります。 UNIX のファイルシステムでは close 以前に他のプロセスからもアクセス可能 (VMS の shared open に相当) なので、 データ収集中に hogehoge.dat から データを読み込んで解析する事もできます。

なお、バッファデータの一区切り (DC_INTCLR 実行から次の DC_INTCLR 実行までに収集されるデータ量) は、ポインタの都合上符合無し 32 ビットポインタで表される範囲に とどめておいてください (8 Gbytes のデータを区切り無しに 採る事はまず無いと思いますが)。


データ収集システムのサンプル

以上の機能を使って、 簡単なデータ収集システムのサンプルを作ってみました。 構成としては、以下の三つのプロセスが連係して動作します。
commander
標準入力からコマンド文字列を読んで、 データ収集の制御 (start/stop/reset) や ファイル名の指定/クローズ処理の指示を行ないます。
recorder
バッファされたイベントデータを /dev/dc1 から読み込み、 ファイルに書いたり analyzer に渡したりします。 analyzer の処理が遅くデータ収集に追い付かない時は、 イベント単位で間引きを行ないます。
analyzer
オンライン解析プロセスです。 analyzer 本体が面倒を見るのは、 各イベントのデータをユーザールーチンに渡す所までです。 ユーザールーチンは使用者が用意しリンクするわけですが、 処理内容 (ヒストグラミング、ディスプレイ方法等) は全く自由です。 ただし、以下の三つのエントリーが必要です。
void hstdef_()
analyzer 起動時に一度だけ呼ばれます。 各種初期設定やヒストグラムの定義を行ないます。
void hstend_()
analyzer 終了時に一度だけ呼ばれます。 各種後処理やヒストグラム領域の解放を行ないます。
void event1_(unsigned short data[])
各イベント毎に呼ばれます。 ヒストグラムのカウントアップを行ないます。
末尾にアンダースコア "_" が付いているのは、 Fortran でユーザールーチンを記述する便宜を考慮したものです。 Fortran で書く場合には、各々
 SUBROUTINE HSTDEF
 SUBROUTINE HSTEND
 SUBROUTINE EVENT1(DATA)
 INTEGER*2 DATA(*)
がエントリーとなります。 HBOOK と PAW を使った例題 として "event1.f" を付けましたので参考にしてください。
各プロセス間のデータ交換は共有メモリを介して行なっており、 データの構成やキー等の情報は "crashm.h" に記述されています。 また、analyzer/recorder 間のデータ待ち/解除の制御はセマフォを 使っています。 いずれも常識的な方法ですが、詳しく知りたい人は SystemV IPC (Inter-Process Communication) に 関する参考書を読んでください (BSD のプロセス間通信の方法はこれと少し異なります…、 と教科書には書いてありますが、 実際は FreeBSD でも SunOS4 でも使えたります)。 上記のソースは、いずれも 1〜2 ページの極短いものなので、 このテの「お勉強」をしたい人にも適当かも知れません。

所謂オフライン解析のためのプログラムも用意しました。

player
一旦ファイルに落したイベントデータを読み込んで解析を行ないます。 制御は commander と互換のコマンド文字列を標準入力から 読み込んで行なわれます。 イベント処理は analyzer と同じユーザールーチンをリンクして 実行します。 CAMAC デバイスドライバをインストールしていないシステムでも、 同じインターフェースを利用できるメリットが有ります。
オンラインではイベント処理の「非同期実行」のため、 commander, recorder, analyzer にプロセスを分けましたが、 オフラインでは同期実行なので一つにまとめています。 ただし解析中もキー入力を受け付けるようにするため、 fork によるプロセスの二重化と signal と pipe によるプロセス間通信を 使っています (これらは IPC 以前から有るマルチプロセス制御で、 BSD でも共通です)。

サンプルシステムの実行形式の make は

      % make DAQ
で行なわれます。 ただし analyzer に関しては、ユーザールーチンの記述 および使用するライブラリの問題がありますので、 Makefile を適当に修正する必要があるでしょう。

さて、 commander は最低限の機能しか持っていないので、 とても使い易いとは言えません (VMS 風のコマンドの省略ぐらいはサポートしていますが)。 そこで、Tcl/Tk による GUI の「皮」を被せて使い易さの向上を図ったのが "rei" というスクリプトです。

Tcl/Tk とはスクリプト言語 (インタープリタ) で、 近年では当たり前となったマウス操作による制御を 短いステップで書く事ができ、 速度の要求されない処理 (大抵の対話型処理はそうですが) の部分を 手っ取り早く記述するには便利です。 Tcl/Tk に関する詳細は適当な参考書を御覧ください。 ただし "rei" はバージョン 4.0 以降の Tk でしか動きませんが、 原典 (Tcl/Tk の作者自身による解説書) は 3.6 を対象にしているので 注意が必要です。 ちなみに "rei" というのは例題の rei ではなく、 火川神社の巫女さんの名前のつもりで付けました。

使用に際しては、"rei" の一行目に書かれている

      #!/usr/bin/wish
を、実際の Tk インタープリタの所在に換えてください。
      % make wish
を実行すれば自動的に書き換えられるはずです。 後は、このスクリプトをコマンドラインから直接起動すれば、 commander, recorder, analyzer がサブプロセスとして立ち上げられます。 "rei" の起動は、PATH 指定でも alias でもシンボリックリンクでも構いません。 ただし、commander, recorder, analyzer は "rei" と同じディレクトリに なければなりません。 また、起動時に
      % rei offline
のように引数を与えれば、player が起動されます。

"rei" の実行画面は以下のようなものです。 使える機能は全てボタンに割り当てられていますので、 説明が無くても使い方はわかると期待します。 誤操作を防ぐために、 システムの状態 (データ収集中、ファイル書き込み中) に応じて 危険なボタンはグレーアウト (使用禁止) されます。 ボタンをマウスクリックする代わりに、 下線の引かれた文字と ALT キーを同時に押しても 同様の動作をします。

一秒毎に表示が更新される統計情報は以下の意味を持っています。

Interrupt
CAMAC の LAM によって発生した割り込みの回数です。
Lost
バッファが溢れて採り込めなかったイベントの個数です。
Recorded
recorder が /dev/dc1 から読み出したイベントの個数です。 一回の Start/Stop サイクルでは Interrupt - Lost になると 期待されます。
Analyzed
analyzer が解析したイベントの個数です。
Error
イベント長とデリミタの非整合性から、 データが壊れていると判断されたイベントの個数です。 これらは analyzer には渡されません。
表示画面下部の Scaler では、 CAMAC スケーラの値を一秒毎に読み出して表示しています。 スケーラのステーションナンバーと読み出すチャネル数は、 各々 N と CH で設定します (数字または nil と表示された部分をマウスクリックしてください)。 表示されたデータは Write ボタンによって (イベントデータとは別の) ファイルに書き出す事ができます。

スケーラ情報をイベントデータと同等に扱うのは難があるので (もちろん“スケーライベント”をハード的に作れば別ですが)、 システムの無垢な性格(?)を残すために、 デバイスドライバまで巻き込んで スケーラの扱いを支援する事は止めました。 が、実際上は不便な事も多かろうと思いまして、 このような形の支援を付け加えた次第です。

デフォルトではスケーラ情報の更新間隔は一秒となっていますが、起動時に

      % rei 2000
という形式でミリ秒単位の指定が可能です。

さて、 "rei" を eXit によって正常終了すると、 commander, recorder, analyzer のサブプロセスは自動的に消去され、 共有メモリおよびセマフォも削除されます。 しかし何らかの理由で異常終了した場合には、 サブプロセスが残っているため "rei" を再起動できなくなりますので、 ps によってプロセス状態を確認し、kill してください。 特にデータ収集の最中に異常終了した場合は 割り込みが発生し続けてしまいますから、 再起動により初期化を行なうのは重要です。 一方、共有メモリやセマフォは残っていても ("rei" を使い続ける限り) 特に害はありませんが、 状態の確認 (ipcs) や削除 (ipcrm) をしたい場合には、 次節の方法を参照してください。


オンライン解析 ― PAW/HBOOK を用いた例題

ヒストグラミングおよびディスプレイは、 好みに応じて適当なものを使用すれば良い訳ですが、 ここでは共有メモリにヒストグラム領域を割り当てる簡便さから、 cernlib の HBOOK と PAW を使った例題を示します (cernlib の Linux 用 aout および ELF バイナリは CERN の FTP サーバーから入手可能ですが、 無償配布は高エネルギー物理分野のみとの条件がありますので、 アクセス前に レジストレーションを済ませてください)。

先ず、ヒストグラミングを行なうプログラムでは、 通常の HLIMIT の代わりに HLIMAP を使います。

      PARAMETER (NHOGE=10000)
      ......
      CALL HLIMAP(NHOGE,'sample')
      CALL HBOOK1(1,'Hogehoge',100,0.,100.,0.)
      ......
ソースをコンパイルし、(lib)packlib.a とリンクして実行すると 共有メモリが割り当てられます。 その状況は、
      % ipcs -m

      ------ Shared Memory Segments --------
      shmid     owner     perms     bytes     nattch    status
      1408      okamura   666       40000     0
という具合に確認する事ができます。 次に、paw を起動して
      PAW > hist/hio/global sample
      PAW > cdir
       Current Working Directory = //SAMPLE
      PAW > ldir
                 1  1D Hogehoge
                 .......
等とやれば、 共有メモリ上に定義されたヒストグラムが //SAMPLE としてアクセスできる事がわかります。 以降の作業は通常の //PAWC に定義されたヒストグラムと同様です。

ところで、HBOOK は割り当てた共有メモリを解放する機能を 持っていないようです。 先程の例ですと (shmid1408 でしたので)、 プログラム終了後に

      % ipcrm shm 1408
を実行して共有メモリを解放してください (少なくとも 95a-97a のソースを眺める限りでは、 HFREEM が共有メモリ解放を意図したルーチンと思われるのですが、 肝心の部分 shmctl がコメントアウトされているのです。何故…?)。


付録 A ― CC/7000AT-IF のディップスイッチの設定

このテの説明書きは紛失し易いので(^_^;、 以下に転載しておきます。

スイッチ1、2
(I/Oベースアドレスの設定)
スイッチ ビット デフォルト スイッチ ビット デフォルト
1-1 [12] ON 2-1 [ 7] ON
1-2 [11] ON 2-2 [ 6] ON
1-3 [10] ON 2-3 [ 5] ON
1-4 [ 9] OFF 2-4 [ 4] OFF
1-5 [ 8] OFF 2-5 [ 3] ON

※ビット[15]-[13]は0、[2]-[0]はレジスタに割り当てられます。
デフォルトでは 0x310(ベース) 〜 0x317 が使用されます。

スイッチ3
スイッチ デフォルト
3-1 OFF
3-2 OFF
3-3 OFF
3-4 OFF
3-5 OFF

スイッチ4
(割り込み等の設定)
スイッチ デフォルト 意味
4-1 ON I/O ch ready WAIT
4-2 OFF IRQ5
4-3 OFF IRQ10
4-4 OFF IRQ11
4-5 OFF IRQ12

割り込みを使用する場合には、SW 4 の 2〜5 のいずれかを ON にしておきます。 CC/7000 の SW-14 も ON にする必要があります。

言うまでも有りませんが、他のデバイスと競合しない IRQ および I/O アドレスを使用してください。 PCI マシンの場合には、Plag & Play が該当 IRQ を使用しないように ICU や BIOS で設定しておく必要があります。
Linux で使用している IRQ や I/O ポートは、 以下のコマンドで見る事ができます。

      % cat /proc/interrupts
      % cat /proc/ioports
ただし、Linux カーネルが使用していないデバイスについては、 この方法ではわかりません (例えばグラフィックスボードが IRQ を使用するタイプでも、 X サーバーがカーネルに申告しなければ表示されない) ので、 MS-DOS の MSD 等も使って確認してください (MSD も絶対信用できる わけではありませんが。 Windows95 が使える環境ならば、 デバイスマネージャで確認するのが最も簡単/確実です)。


付録 B ― CC/7000 のレジスタ配置

これが無いと、ソースを見ても理解できませんから … (^_^;。

図の意味はかなり自明と思いますが、幾つか捕捉します。

D1 の説明が不完全ですが、クレート番号 (0〜3) の選択に使います。 CC/7000 の最初のバージョンではシングルクレートのみのサポート だったので、D1 という如何にも後から付け足した事を暗示する ポートが使われています。

D7 の GO とは、 この I/O ポートへのアクセスによって 実際の CAMAC アクションが行なわれる事を意味します。 従って、それ以前に各パラメータをセットしなければなりません。

また、 16 ビットポートは 16 ビットアクセス (outw, inw) でしか 入出力できません。 例えば、ファンクション (F) をセットするのに outb(...,BASE+6) を 用いるのは間違いです。


Frequently Asked Questions

割り込みが発生しない?
クレートコントローラの LSAM (LAM の総和) の LED が 点灯している、即ち何らかの LAM が発生している場合は、

LSAM が点灯していない場合は、 LAM を発生させようとするモジュールに対して

を確認してください。

バッファにデータは溜って行くのに、 /dev/dc?からリアルタイムで読み出せない (データ収集をストップすると一気に読み出される) ?
割り込みルーチンの最後で、 LAM を発生するモジュールが正しくクリアされているか 確認してください。 /dev/dc?からの読み出しは、 バッファが空である場合に wait 状態になります。 wait 状態の解除は kernel からの割り込みによって行われますが、 LAM による割り込みとの区別は、 LAM が立っているか否かで行われるからです。

PAW(++) でヒストグラムのディレクトリが表示されない?
cernlib 96a の PAW および PAW++ には、標記の (GLOBAL SECTION に対して LDIR が働かないという) バグが有ります。 これは Linux 用バイナリだけでなく、 他のプラットホーム用バイナリでも確認されますので、 ソース自体のバグと思われます。 現在の最新バージョンは 97a になっており、 この問題は解決されていますので、こちらを入手しましょう。 古いリリース (95a, 95b) でも正常動作するのですが、 既にオリジナルサイトからは削除されてしまっています。

PAW(++) のグラフィックス画面が再描画されない?
# cernlib の解説書を書くつもりは無いんですけど… (^^;
X Window System で言う所の EXPOSURE イベント (他の Window の背後に隠れていた Window が前面に現れた時に 発生する割り込み) に際して再描画を行うのは、 X アプリケーション (Client) の責任です。 ところが、PAW(++) および cernlib の一般的グラフィックスライブラリは この作業をサボっており、X Server 側で描画情報の保存・再生 (Backing Store と言います) を行わないといけないようです。 例えば Accelerated-X を使っている場合、 BackingStore を有効にするには、 "/etc/Xaccel.ini" (または相当するファイル) に
[SETTINGS]
       ………
    BackingStore = YES;
という記述を加える必要があります。 XFree86 の場合はデフォルトで BackingStore が有効になっているようです。 X Server によっては (例えば Windows95/NT 上の X エミュレータ等)、 BackingStore を有効にすると随分描画速度が落ちるものも有るので 注意が必要です。
# EXPOSURE イベント処理は Client 側で行うのが常識だと思いますけどね…。

何故サンプルプログラムの "evnet1" だけ FORTRAN で書かれているか?
HBOOK は元々 FORTRAN で書かれた FORTRAN のためのライブラリなので、 HBOOK ユーザーには FORTRAN の方が自然ではないかと思っただけです。