HandyPak で共有メモリ (shared memory) を扱うための拡張 岡村 弘之 東京大学理学部物理 okamura@timshel.riken.go.jp 0. Introduction 実験時のオンライン解析結果を (ファイルに落す等の操作をせずに) ディスプレイしたいという要求は一般的に起こる事です。これを実現 する簡便で効率的な方法の一つとして、ヒストグラム領域のメモリを プロセス間で共有設定するというのがあります。 VMS では shared common library という仕掛けがこれを実現していま した。FORTRAN の COMMON 文のみからなるライブラリを、予めシステム の共有ライブラリとしてインストールしておきます。それをリンクした 実行形式を走らせると、各々のプロセスの間でデータの共有が行なわ れるというものです。HandyPak を含め、FORTRAN で書かれた多くの パッケージは、ヒストグラム領域を COMMON 変数に割り当てています から、FORTRAN を使う限りではとても便利な機構でした。一方欠点と しては、システムライブラリをインストールするために特権が必要で ある事が挙げられます。 unix でも共有メモリの機構は用意されていますが、事情は大分異なり ます。unix における基本言語は C ですから、共有メモリの設定も 多分に C の言語仕様に則した物になっています。即ち、malloc 等と 同様に、プロセスは共有メモリの割り当てをカーネルに要求し、その 先頭アドレスを受け取る事になります。当然ながら、既に使用されて いるメモリ領域を共有設定する事はできません。FORTRAN の COMMON 領域は、プログラムの実行開始時にメモリに割り当てられてしまい ますから、VMS で実現していたように、全くプログラムの変更無しに ヒストグラムデータを共有するという事は、できないわけです。 対策として直に思い付くのは、ヒストグラム領域は所詮配列でストア されているわけだから、強引にオフセットを付けてアクセス出来ない か? という事です。例えば COMMON /HCOM/ M(1) が宣言されており、共有メモリ要求 (SysV IPC ならば shmat コール) に対して返って来たアドレスが SHMADR だったとします。 SHMOFS = ( SHMADR - %LOC(M) ) / 4 という変数を COMMON (しかし共有メモリでない領域) に保存しておき、 これまで M(I) としてアクセスされていた所を M(I+SHMOFS) と換えれば 目的は達せられます。しかし、この書き換えが大量箇所に及ぶと困ります。 HandyPak の例ですと、 COMMON /HCOM/ NNODES, IHCOM(2), NHCOM, ... EQUIVALENCE (NNODES, M(1)) 等となっていますが、例えば NNODES 宣言を削除して、プリプロセッサに #define NNODES M(SHMOFS+1) という処理をさせれば、基本的にソースの書き換えは不要です。HandyPak では IHCOM がシステム予約、つまり使われていないので、ここに SHMOFS を割り当てておき、共有メモリを使わない時は SHMOFS=0 としておけば、 これまでのソフトとの互換性も図れます。 # ただし、大小文字を区別しない Fortran ソースでプリプロセッサを使う # のは注意が必要です。更に、Linux や SunOS の cpp では、シンボルの # 前にピリオドが付いていると (例えば上の例では ….AND.NNODES.EQ.1 # 等という場合) 置換が行なわれないのは注目に値します (OSF/1 の cpp ^^^^^^^^^^^^^^^^^^ # では何故か置換されるのですが...)。 問題なのは、一般には SHMOFS がプロセス毎に異なっているという点です。 物理的に割り当てられる領域は一つなのですが、論理的なマッピングは プロセス毎にページ単位 (Linux の場合は 4096 バイト) で管理され、 1 バイト単位で同じアドレスに設定する事は不可能です。通常は、HCOM に おけるポインタ (配列のインデックス) を保存する変数が共有メモリ中に 幾つか置かれるわけですが、これらには相対的な値、つまり SHMOFS を 引いた値を入れておき、実際にアクセスする段に SHMOFS を足す、という 風に書き換えなければなりません。 HandyPak の場合、これが必要なのは NHCOM NODEND NODE1 HSHLOC HSHCNT SCTLOC ハッシュテーブルの M(HSHLOC+...) 各ノードの M(NODE+1) M(NODE+3) です。ちょっと大変ですが、これをやってみました。繰り返しになります が、共有メモリを使わない時は SHMOFS=0 なので、HCOM の内容は、これ までのソフトで作られるものと互換性が有ります。SHMOFS の値のセットは、 全てのノードオペレーションに先行する必要がありますので、HINIT を書き 換える事にします。 随分面倒臭い手順を強いられましたが、これで何とかプロセス間のヒスト グラムデータの共有が unix 上でもできるようになりました。VMS と比較 すると、root 特権が無くても使えるという一つの (非常に大きな) 利点が 挙げられます。 なお、同様の機構は cernlib の HBOOK4 および PAW 等でも実現しています。 HBOOK3 では HandyPak と同様にヒストグラムを COMMON 領域に割り当てて おり、VMS の shared common library でしか共有ができませんでしたが、 unix の共有メモリに対応したのが HBOOK4 の新機能の一つと言えるでしょう。 HandyPak で共有メモリを実現するための着想は、HBOOK4 のソースから得ら れた部分の多い事を付記しておきます。 1. 利用法 共有メモリを使うか否かの選択は、いちいちソースを書き換えるのも ナンですので、リンクするライブラリで選択するようにしました。 通常の形式 % f77 hogehoge.f hpk.a でコンパイルすると、HSHMAT のダミールーチンがリンクされます。これは、 割り当てを要求された領域のアドレスとして M(1) のそれを返しますので、 これまでの HandyPak の HCOM と互換性のある内容となります。一方 % f77 hogehoge.f hshmat.o hpk.a でコンパイルすると、共有メモリを割り当てて、そのアドレスを返す HSHMAT がリンクされ、共有領域にヒストグラム等がストアされる事に なります。 共有領域はダイナミックに割り当てられるので、これまでのソースで必要 だった COMMON /HCOM/MM(500000) 等という、領域を確保するための宣言は必要有りません (宣言を残して おいても致命的な問題はありませんが)。 共有メモリは、プログラム実行開始時に呼び出される HINIT (明示的に HINIT を呼び出さなくても、HandyPak 関連のルーチンを呼ぶと自動的に 呼ばれます) が割り当てます。共有メモリを解放するためのルーチンと しては HFREEM (引数無し) というサブルーチンを用意しました。しかし、実際の運用では最後に終了 すべき特定のプロセスが明らかでない場合も多いと思います。その場合は、 プログラムで明示的に解放する代わりに、シェルコマンドで削除する方が 現実的でしょう。Linux の場合、 % ipcs -m によって割り当てられた共有メモリの ID を確認し、 % ipcrm shm [ID#] を実行すると、該当領域が削除されます。 共有メモリは、上記の積極的操作によって削除しない限りメモリ (または スワップ領域) に残ってしまいますので、注意してください。また、一度に 確保できる共有メモリの大きさには一般に制限が有りますので、日常的な 全てのヒストグラム処理も共有メモリでやってしまおうとするのは無理が 有ります。サイズの上限は、SysV IPC ならば 以下の SHMMAX で定義されており (※追記参照)、 Linux-1.2.13 0x3fa000 ( 3.98 MB) Linux-2.0.30 0x1000000 ( 16 MB) OSF1 V3.0 0x400000 ( 4 MB) SunOS-4.1.x 0x40000000 ( 1024 MB ?) となっているようです。 共有メモリには key (ID とは異なるが、識別子として使われる)、owner、 アクセス権等の情報が付加されています。HINIT では簡便さのため KEY = 'HPAK' + uid (Hollerith 整数型) アクセス権 = '0644'o (owner=read+write, group=others=read) をデフォルトとして与えています。KEY に uid を足しているのは、一つの マシンで複数の人が HandyPak の共有メモリを使おうとした際に、衝突が 起こるのを避けるためです。 複数のユーザーの各々のプロセスから同一の共有メモリに書き込みたい等の 要求も有るかと思いますので、KEY とアクセス権を指定する HSHKEY ( KEY , ACCESS ) というサブルーチンも用意しました。ACCESS には、例えば '0640'o (o=r+w, g=r, o=アクセス権無し) という形式の octal で与えるのが便利です。 要求されたものと同じ KEY の共有メモリが既に存在する場合、HINIT は 領域を新しく割り当てる代わりに、既存の領域のアドレスから SHMOFS を 計算します。領域が既存 (既に初期化された) ものか否かの判断は、 HSHEXT ( ICODE ) というサブルーチンをコールし、ICODE に返って来る値 (0=新規、1=既存) で行なってください。 ==================================================================== ※追記 2007.12.11                               岡村弘之                             大阪大学RCNP                      okamura@rcnp.osaka-u.ac.jp Linux では kernel-2.2 以降 SHMMAX を動的に変更できるようになりました。 簡単な説明が man shmget にありますが、現在の値は cat /proc/sys/kernel/shmmax で表示され (kernel-2.{2,4,6} ともデフォルトは 32 MB)、例えば echo 0x8000000 > /proc/sys/kernel/shmmax とすれば SHMMAX を 128 MB に設定できます。これをシステムのデフォルト にしたければ、/etc/sysctl.conf に kernel.shmmax = 0x8000000 という行を書き足すと次回起動時から反映されます。 AIX では (少なくとも 5.1 以降) 共有メモリの大きさに関する特別な制限は 無く、全てのメモリが共有メモリとして使用可能らしいです。