CとEusLisp間の、共有メモリでの通信
C言語とEusLispというlispの処理系との間で、共有メモリを使ってデータをやりとりする方法について調べました。
共有メモリとは
共有メモリを使うと、関係のない複数のプロセスが、同じ論理メモリにアクセスすることができるようになります。
共有メモリは特別なメモリ範囲で、他のプロセスは共有メモリセグメントを自分のアドレス空間に"アタッチ"することができます。アタッチしたプロセスは、共有メモリに、mallocで割り当てられたメモリ同様、自由にアクセスできます。あるプロセスが共有メモリに書き込みを行うと、変更内容は他のプロセスからも、すぐに見えるようになります。
EusLispとは
EusLispはロボット・プログラミングに特化したLispの処理系です。ロボット・プログラミングに必要な幾何モデルの操作や、オブジェクト指向などの機能を持つことが特徴です。松井俊浩氏によって開発されました。
Object-Oriented Concurrent Lisp with Solid Modeling Facilities: EusLisp
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バイトの文字を扱いたいときには不便です。なにか方法を探す・考える必要があります。