ちょっと前にC言語でプログラムを書いていた際、
「fork()を用いて分割したプロセス間で変数を共有したい!!!」
ってなって調べてpipeとmmapにたどり着いたのですが、pipeはなんだかうまくいかず...
じゃあmmap使うしかない!となってレファレンスを見たはいいものの、定義の羅列だらけで実用的な使い方が一切わからないし、調べてもファイルへの書き込みなどで使う記事が多くサクッと使い方がわかるサイトがなかなか見つからず...
またmmap使いたくなった時に簡単に思い出せるよう、記事にしておきます。
なお、ここでは自分の理解に基づいたものを書いていくので、詳しい定義に関してはMan page of MMAPを参照してください。

  • プロセス間での変数共有のイメージ
process
この図のように、普通はforkでプロセスが別れた途端に、それまでの変数は分けられたプロセスでは名前が同じでもそのまま別の領域に分けられて片方の変更はもう反映されない。
なので上の緑の枠のようにプロセス内のメモリとは違う場所に領域を確保し、ポインタにその住所を教えておけばそれぞれのプロセスから同じ場所にアクセスできるため、変数を共有できる。

  • mmap()の定義と使いかた 
#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 

mmap()を用いる際には必ず、sys/mman.hをincludeする必要があります。
そして各変数に何を入れていくかをステップを追って見ていきます。
  1. 第一変数void *addrにはどこから書き込むかを指定する必要がある。ここでは、単にプロセス間で変数を共有するだけなので、どこの領域にメモリを割り当てるかはコンピュータに任せれば良い。よって第一変数にはNULLを与える。
  2. 第二変数size_t lengthにはどれだけの長さを用意するかを指定する必要がある。そして共有したい変数の型に合わせて領域確保をしてあげれば良い。 int型を用いるならsizeof(int)で長さを定義してあげれば問題ないだろう。
  3. 第三変数int protにはこの領域にどのような動作を許可するかを論理和(OR)で与える。今回は読み込みと書き込みを行えればいいのでPROT_READ | PROT_WRITEを渡す。
  4. 第四変数にはこの領域の振る舞いを指定したのち、論理和(OR)でオプションをつけることができる。今回は他のプロセスから領域が見えた方がいいのでMAP_SHAREDを与え、プロセス間で変数を共有するだけで何かのファイルに書き込むわけではないので、 | MAP_ANONYMOUSも追加で与えてあげることとする。
  5. 先ほどflagsにMAP_ANONYMOUSを与えたため、後ろ2つのfdとoffsetは無視される。互換性のためにfdには-1を与えた方がいいとのことなのでそれに従い、offsetはとりあえず0を与える。
mmap()は確保した領域のアドレスを返戻値として返してきます。
よってその場所をポインタで示してあげればアクセスできるようになります。

  • 簡単な例

以下のソースではポインタptrを使って変数aのみ親プロセスと子プロセスで共有させ、子プロセスでの変更を親プロセスに反映させています。
それに対し、他の変数b,cをいくら子プロセスで更新しても、更新処理をしていないため親プロセスに反映されません。
つまり、親プロセスでは変数aのみ値の更新が反映され、printfで出力されるa,b,cのうちaのみがどんどん値が増えていくというプログラムとなっています。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> //for using sleep()
#include <sys/mman.h>  //for using mmap()

int main(void){
  //Propaties
  int a;
  int b = 1;
  int c = 2;
  int processId;

  int* ptr;

  //Function Declaration
  void parent_process (int* ptr, int a, int b, int c);
  void child_process (int* ptr, int a, int b, int c);

  //プロセス分割前にmapして別領域に保存領域を作成
  ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  //中身をゼロに
  *ptr = 0;
  //aをptrの中身(0)にする。
  a = *ptr;

  //forkでプロセス分割
  if ( ( processId = fork() ) == 0 ){
    //子プロセスの実行
      child_process(ptr,a,b,c);
  }else{
    //親プロセスの実行
      parent_process(ptr,a,b,c);
  }
}


void parent_process(int* ptr, int a, int b, int c){
    while (1) {
        a = *ptr;  //mmapで作った共有領域から値の読み込み
        printf("-parent-\n");
        printf("a = %d\n",a);  //この値のみ変更が反映される
        printf("b = %d\n",b);
        printf("c = %d\n",c);
        sleep(1);
    }
}


void child_process(int* ptr, int a, int b, int c){
    while (1) {
        a++;  //インクリメント
        *ptr = a;  //ptrが指し示す領域(mmapで作った共有領域)に反映
        b++;  //親プロセスに反映されない
        c += 10;  //親プロセスに反映されない
        sleep(2);
    }
}
  • 実行結果
 これを実行するとおおよそ1秒に一回a~cの中身を表示するループを繰り返します。
その中でおおよそ2秒に一回はaの値のみ更新されるように表示されるはずです。
terminal
  • まとめ
今回の例に示したプログラムはただ変数の共有を示したかったので無意味ですが、プロセスを分割して延々とキーボードの入力を受ける際などに別のプロセスと変数を共有できるとやれることが広がったりしますので、良ければ参考にしてみてください。