05 December 2008

CとEusLisp間の、共有メモリでの通信

C言語とEusLispというlispの処理系との間で、共有メモリを使ってデータをやりとりする方法について調べました。

共有メモリとは

共有メモリを使うと、関係のない複数のプロセスが、同じ論理メモリにアクセスすることができるようになります。

共有メモリは特別なメモリ範囲で、他のプロセスは共有メモリセグメントを自分のアドレス空間に"アタッチ"することができます。アタッチしたプロセスは、共有メモリに、mallocで割り当てられたメモリ同様、自由にアクセスできます。あるプロセスが共有メモリに書き込みを行うと、変更内容は他のプロセスからも、すぐに見えるようになります。

EusLispとは

EusLispはロボット・プログラミングに特化したLispの処理系です。ロボット・プログラミングに必要な幾何モデルの操作や、オブジェクト指向などの機能を持つことが特徴です。松井俊浩氏によって開発されました。

Object-Oriented Concurrent Lisp with Solid Modeling Facilities: EusLisp

Matsui’s Homepage

EusLispにおける共有メモリ

通常共有メモリの使用には、shm*関数を用いますが、EusLispの共有メモリは、SunOSのmmapによって提供されています。よってC言語とEusLispで書かれたプログラム間で、共有メモリによる通信を実現するためには、mmapを用いる必要があります。

EusLispでは、共有メモリはベクトルとして扱われます。ベクトルの各要素は、1バイトの整数です。例えばベクトルの10要素目は、共有メモリの10バイト目に相当します。このように1バイトごとに分かれているため、255以上の値を扱いたいときには工夫が必要なのかもしれません。

共有メモリ

サンプルプログラム

EusLisp側でマップファイル(共有メモリ)を作成、値を書き込み、C言語側で読み出すサンプルです。

まずは、EusLisp側のサンプル。64バイトのファイルを作成し、共有メモリとする。標準入力から書き込む値と場所(何バイト目か)を取得し、書き込みを行います。

; 共有メモリ初期化用の関数
(defun shmInit ()
  ; 共有メモリ用のファイルを作成する。名前は"MapFile"。サイズは64byte
  (with-open-file (f "MapFile" :direction :output)
  (princ (make-string 64) f))
  ; 作成したMapFileを読み込み、 shared-string1 にバインドする
  (setf shared-string1 (map-file "MapFile" :direction :io))
)

; 共有メモリに書き込む関数
(defun writeTest ()
  (while 1
    ; 標準入力からの入力を、codeへバインド。この値を書き込む。
    (princ "code?:")
    (setf code (read))

    ; 何バイト目に書き込むかを、posへバインドする
    (princ "position?:")
    (setf pos (read))

    ; 入力値を表示
    (princ "code=")
    (print code)
    (princ "pos=")
    (print pos)

    ; shared-string1 の pos 番目の要素に、 code を書き込む。
    (setf (aref shared-string1 pos) code)
  )
)

(defun run ()
  (shmInit)
  (writeTest)
)

次はC側のサンプル。1秒置きに共有メモリの内用を表示します。

#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
  int fd;
  int size;
  char *ptr; // 共有メモリへのポインタ。EusLispでは共有メモリが1バイト区切りになっているため、ここでもchar型を使用する。

  // MapFile を開く
  fd = open("MapFile",O_RDWR);
  if(fd == -1)
    {
      perror("open");
      return -1;
    }

  // MapFile の64バイト分(EusLisp側でそのサイズで作ったため)を、自分のメモリ空間にマップする。
  // マップしたメモリへのポインタを、ptrに入れる。
  size = 64;
  ptr = (char *)mmap(0, size, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);

  while(1)
    {
      // ptr を表示
      printf("%d\n", ptr[0]);

      // 1秒待つ
      sleep(1);
    }

  return 0;
}

次はCで書き込み、EusLispで読み出すサンプル。

まずはC側の、書き込みのサンプル。scanfで標準入力から値を得て、それを書き込んでいます。ファイルをメモリにマップし、そのメモリを書き換えたあと、msync関数でメモリとファイルを同期させる必要があります。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
  int fd;
  char c;
  int size;
  char *ptr;
  int val;

  // マップファイルを開く
  fd = open("MapFile",O_RDWR|O_CREAT,0666);
  if(fd == -1)
    {
      perror("open");
      return -1;
    }

  size = 64;

  // マップする
  ptr = (char *)mmap(0,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
  if((int)ptr == -1)
    {
      perror("mmap");
      return -1;
    }

  while(1)
    {
      // 標準入力から読み込む
      scanf(&c);
      if(feof(stdin))
break;

      // 整数に変換
      val = atoi(c);

      // 値をセット
      ptr[0] = val;

      // マップしたメモリとファイルを同期させる。メモリを書き換えた後にこの命令を行うと、変更がファイルへ反映される。
      msync(ptr,size,MS_ASYNC);
    }

  // アンマップ
  if(munmap(ptr,size) == -1)
    perror("munmap");

  close(fd);

  return 0;
}

次はEusLispによる読み出しのサンプル。関数startを呼ぶたびに、共有メモリの1バイト目を出力するだけの内容です。

(defun init ()
  (setf shared-string1 (map-file "MapFile" :direction :io))
  )

(defun read ()
  (setf ans (aref shared-string1 0))
  (print ans)
  )

(defun start ()
  (init)
  (read)
)

TODO

現状では1バイトごとに、数値だけしか扱うことができません。大きな値や2バイトの文字を扱いたいときには不便です。なにか方法を探す・考える必要があります。