The BAQ/Data Acquisition/DAQ System/Babarl/Components

Japanese English
Nuclear Physics Data Acquisition Orchestra Gallery Memo Diary Link Home
DAQ System DAQ Module Pulse Shape Analysis RTLinux Linux Device Driver Home
Babirl Babarl NBBQ Home

もどる

Device Driver

Device DriverはKinetic 2915 PCIを動かすためのもので、RT-Linuxローダブルモジュールの形をとっている。 RT-Linuxとしての機能を使用している部分はこのDevice Driverで、babarlDAQの心臓部となっている。 Device Driverと言っても汎用性はなく、随時書き直して使用する必要がある。基本的な部分はライブラリ、雛形を用意してあるので、それをもとに書き直せばよい。

Device Driverは収集したデータを1Block(16KBytes)バッファリングしておき、データが溜まったらTransferがそのデータを他の構成要素に転送する。通常RT-Linuxローダブルモジュールと通常のプロセスとの通信にはRT-FIFOと呼ばれる専用のFIFO(First In First Out)を使用するが、babarlDAQではバッファ用のメモリをあらかじめ確保してRT-FIFOを使わないで通信を行っている。


実現している機能


Device Driverの動作
Device DriverはCollectorによってロードされる。モジュールがロードされた瞬間からデータ収集が開始される。この時点ですでにTransferもCollectorによって起動されており、Transferと通信しつつCAMACからデータを収集する。データ収集はCollectorによりDevice Driverが取り除かれることで停止し、同時にTransferも終了する。


Interrupt RequestとPolling
イベントを処理する方法としてInterrupt Requestで割り込みをかけるものと、Pollingで監視するものを選択できる。

割り込みを使用する場合は

void intr_handler(void){
  /* イベント処理 */
}
.
.
.
int init_module(void){
  .
  .
  rfs_enable_interrupt();             // Enable Interrupt (PCI Card)
  request_RTirq(IRQ,intr_handler);    // Reqest IRQ
  return 0;
}

のように簡潔に記述することができるが、Driverとユーザープロセスとの同期をとるのが難しいため、 Single Bufferしか扱えない。

Pollingで監視する場合は、Pollingの周期毎にCreate ControllerのLAMをチェックすればよいのだが、 K3922 + K2915の組み合わせでのCAMAC Single Functionはえらい時間がかかる。(8us程度) なのでPCI Cardの割り込みは許可し、Driverではその割り込みをハンドリングしないようにしておいて、 PCI CardのRFSをチェックすることで1us程度でLAMをチェックできる。
Pollingの周期はとりあえず50us周期にしてある。過去にPentium 90MHz Linux Kernel 2.2.33のマシンでテストしたところ、25us周期まで耐えることができたが、あまり周期を早くしても、ユーザープロセスが動作できる時間が少なくなるため、50us周期というのが妥当な数字かと思う。
PollingのコーディングはKernel 2.0.*のときはrt_task_make_periodicという関数で行っていたが、2.2.*では興味深いことにpthreadを使う。

void *daq_handler(void *t){
  while(1){
    pthread_wait_np();                                  // Wait for next period
    .
    .
  }
}

int init_module(void){
  struct sched_param p;
  hrtime_t now = gethrtime();                           // Get Now Time
  .
  .
  rfs_enable_interrupt();                               // Enable Interrupt
  pthread_create(&daqtask,NULL,daq_handler,(void *)1);  // Create Thread
  pthread_make_periodic_np(daqtask,now+100000,50000);   // Set period
  p.sched_priority = 1;                                 // Set Priority
  pthread_setschedparam(daqtask,SCHED_FIFO,&p);         // Set Schedule Parameter

  return 0;
}

Pollingを使用するとユーザープロセスとの同期をとるのが難しくないため、Double Bufferが使える。ユーザープロセスが片方のBufferを転送している間にも、データ収集を行うことができるのでパフォーマンスをよくすることができる。パフォーマンス、安定性、危険度から考えてPollingを使用することをお勧めする。
ただし、コンピュータ1台でデータ収集を行う場合はユーザープロセスにかけられる時間が十分ほしいので、割り込みを使用した方が効率的かもしれない。


Device DriverとTransferとの通信 (Single Buffer with RT-FIFO)
バッファに使うMemoryを確保するためにLinuxをappend="xxxm"のオプション付きで起動する。例えば全体でMemoryが128MBあり、2MBの空きを確保するにはappend="126m"を指定する。

Device Driver側からアクセス

#define ADDRESS (126*0x100000)
char *rt_ptr;

rt_ptr = __va(ADDRESS);

とすれば、直接rt_ptrを使ってアクセスできる。

Transfer側からアクセス

#define ADDRESS (126*0x100000)
int fdm;
char *user_ptr;

fdm = open("/dev/mem",O_RDONLY);
user_ptr = (char *)mmap(0,0x4004,PROT_READ,MAP_FILE|MAP_PRIVATE,fdm,ADDRESS);

とすれば、user_ptrを使ってアクセスできる。

バッファが溜まるとDevice DriverはFlagをたて、FIFOハンドラ(RT-Linuxのページ参照)を登録して休止する。 TransferではこのFlagを常に監視しており、Flagがたてば即座にデータを転送し、その後Device DriverのFIFOハンドラを解除する。こうすることで安定に、高速にデータ転送を行なっている。


Device DriverとTransferとの通信 (Double Buffer without RT-FIFO)
バッファに使うMemoryを確保するためにLinuxをappend="xxxm"のオプション付きで起動するのは Single Buffer時と同様である。
Device Driver側からアクセス (Single Bufferと同様)

#define ADDRESS (126*0x100000)
char *rt_ptr;

rt_ptr = __va(ADDRESS);

とすれば、直接rt_ptrを使ってアクセスできる。

Transfer側からアクセス

#define ADDRESS (126*0x100000)
int fdm;
char *user_ptr;

fdm = open("/dev/mem",O_RDWR);
user_ptr = (char *)mmap(0,0x8008,PROT_READ|PROT_WRITE,MAP_FILE|MAP_SHARED,fdm,ADDRESS);

とすれば、user_ptrを使ってアクセス(読み書き)できる。
Driver側はバッファが溜まるとそのバッファのFlagをたて次のBufferにデータを格納する。 2つのバッファにFlagがたっている場合はなにもしないで待機をする。
Transfer側は常にDriverがたてるFlagを監視し、Flagがたったら即座にデータ転送をする。データ転送が終了すればFlagを解除する。
このようにして同期をとり、デッドタイムを少なくしてデータ転送を行うことができる。 Storageの書き込みさえ間に合えば、データ転送によるデッドタイムは生じない。


Transfer

TransferはDevice Driverで収集したデータをDatabaseとRecorder(データ保存するRUNのみ)に転送するプロセスである。 Recorderには転送が確実なTCP、DatabaseにはUDPプロトコルをそれぞれ使用して転送する。 Transferの動作はほとんどDevice Driverの頁で述べられているので省略する。
Transferはmmapで/dev/memを使ったりnice(-20)でプロセスの優先順位を上げているので、rootでコンパイルしてsuidをたてておく必要がある。


Database
DatabaseはRunの情報を管理するプロセスである。 Run Number、Start Timeなどの静的な情報だけでなく、最新のRaw Data(1Block)やテープ残量の動的な情報も有している。 Databaseは複数のDataserverへRaw Dataを配信することができ、複数のマシンでオンライン解析が可能である。


Databaseの実体
Databaseのプロセスは以下の4つの非同期に発生するイベントを処理しなければならない。

  1. Controllerから命令の受け付け (TCP)
  2. Controllerやその他からの情報要求の応答 (TCP)
  3. Transferから最新のRaw Dataを受信 (UDP)
  4. Recorderからテープ残量を受信 (UDP)

これらを満たすため、上記の1、3、4についてはスレッドを生成して処理している。 2だけメインループで処理している。

/* Control commnads */
void controlThread(void){
  if((contaccsock = accept(contsock,(struct sockaddr *)&contcaddr,&len)) < 0){
    perror("Error in accept contaccsock.\n");
    exit(1);
  }
  close(contsock);
  while(1){
    recv(contaccsock,recvbuf,1024,MSG_WAITALL);
    controllCommand(recvbuf[0]);
    bzero(recvbuf,1024);
    bzero(sendbuf,1024);
  }
  close(contaccsock);
}

/* Disk Used */
void diskThread(void){
  unsigned char diskbuf[8];
  while(1){
    recvfrom(disksock,diskbuf,8,0,(struct sockaddr *)&caddr,&listlen);
    memcpy((char *)&dynamic_data.fulldisk,diskbuf,4);
    memcpy((char *)&dynamic_data.useddisk,diskbuf+4,4);
  }
}

/* List Data */
void listThread(void){
  /* List Data Socket */
  while(1){
    recvfrom(listsock,dynamic_data.listdata,datasize,0,(struct sockaddr *)&caddr,&listlen);
    multisend((char *)&dynamic_data.listdata);
  }
}

.
.
.

int main(int argc,char *argv[]){
  pthread_t controlthread,diskthread,listthread;
  .
  .
  .

  /* Create Thread */
  pthread_create(&controlthread,NULL,(void *)&controlThread,NULL);
  pthread_create(&diskthread,NULL,(void *)&diskThread,NULL);
  pthread_create(&listthread,NULL,(void *)&listThread,NULL);

  .
  .
  .
}


Controller

Controllerはデータ収集のコントローラである。データ収集系のプロセスの内では唯一ユーザーインターフェイスを有している。 Xが動いていない状態で動作させたいので、キャラクターベースで作ってある。キャラクターベースということで、ncursesライブラリを使用している。

Controllerのコマンド



Controller 起動時のオプション


Collector

CollectorはControllerと通信し、Device DriverをロードしたりTransferを起動するためのプロセスである。 Collector自身はただControllerとDevice Driver、Transferの橋渡しのためだけに存在する。


Recorder

Recorderはデータ保存のためのプロセスである。 SCSIテープデバイスとハードディスクに書き込みをすることができる。また、テープ書き込み時にはテープ残量の情報をDatabaseに配信する。

SCSIテープデバイスが2台ある場合、2本同時書き込みにも対応している。


Analyzer

AnalyzerはAnapawやBlkmonitorなどのオンライン解析、ステータスモニタにデータを渡すためのプロセスである。共有メモリ、セマフォを使用することで同時に複数のオンライン解析、ステータスモニタからアクセス可能になっている。

Shared Memoryに関する記述

#define SHMKEY  17001

.
.
.

int main(int argc,char *argv[]){

  .
  .
  .

  if((shmid = shmget(SHMKEY,0x4004,IPC_CREAT|0777)) == -1){
    perror("Can't create shared memory.\n");
    exit(1);
  }
  shmp = shmat(shmid,0,0);

  .
  .
  .

}


Semaphoreに関する記述

#define SEMKEY  18001

/* Semaphore Interface */

/* Lock */
void semaphore_p(void){
  semb.sem_op = -1;
  semop(semid,&semb,1);
}

/* Unlock */
void semaphore_v(void){
  semb.sem_op = 1;
  semop(semid,&semb,1);
}

.
.
.

int main(int argc,char *argv[]){

  .
  .
  .

  if((semid = semget(SEMKEY,1,IPC_CREAT|0666)) == -1){
    perror("Can't create semaphore.\n");
    exit(1);
  }
  semunion.val = 1;
  if(semctl(semid,0,SETVAL,semunion) == -1){
    perror("Can't control semaphore.\n");
    semctl(semid,0,IPC_RMID,semunion);
    exit(1);
  }    

  .
  .
  .

}


Anapaw

AnapawはPAWベースのAnalysライクなオンライン解析プログラムである。(オフラインもできる) Analyzerのプロセスが同一コンピュータ上で動作していればオンラインでデータ解析が可能である。
このAnapaw本体はばばではなく先輩の武内さんが作っているので、詳細は彼に書いてもらうとする。
詳細はこちら

Last Update: 2005/4/7
Hidetada Baba
baba rarfaxp.riken.jp