LoginSignup
35
35

More than 5 years have passed since last update.

プロセス間通信 ~共有メモリ~

Last updated at Posted at 2018-08-04

目的

プロセス間通信(IPC)のうち「共有メモリ」について調べたことを備忘録としてまとめました。

プロセス間通信のやり方のまとめとしては以下の記事がとても参考になりました。
コードに関してもかなり参考にさせていただきました。
Linuxのプロセス間通信 (Qiita)

ここでの環境は以下の通りです。コードはC++で書いています。
MacOS 10.13.4
gcc 7.3.0

不備等あれば指摘していただけると幸いです。

[追記]
以下で解説しているのはSystemVの共有メモリで、POSIXの共有メモリも存在するそうです。

解説

共有メモリとは

Wikipediaによると「共有メモリはプログラム間でデータをやりとりする効率的手段である。」「1つのプロセスがメモリ上に他のプロセスからもアクセスできる領域を作成する。」とのことです。
1つのメモリ領域を使ってデータをやりとりするのは処理としてかなり高速でしょう。
ただ複数のプロセスが同じメモリにアクセスできるので競合には気をつけないといけないみたいです。

共有メモリ操作の流れ

  • 共有メモリ用のkey(一意な値)を発行する
  • 大きさを指定したセグメントとkeyを紐づけて共有メモリを作成する
  • プロセスと共有メモリを紐付ける
  • (行いたい処理を行う)
  • プロセスと共有メモリの紐付けを解除する。
  • メモリを解放する

大事なことは最初にプロセスとは独立に共有メモリが作成される点です。
その後に複数のプロセスと紐付けられていきます。

共有メモリ操作の詳細

共有メモリを扱うためにUnix系では以下の関数が用意されています。
shmget() (Man page of SHMGET)
shmat() (Man page of SHMOP)
shmdt() (Man page of SHMOP)
shmctl() (Man page of SHMCTL)
細かいことはそれぞれの Man page を読んでみてください。

ここでは大事な部分だけ解説していきます。

shmget()

この関数は共有メモリを作り、その識別子を発行する関数です。
またすでに作成された共有メモリの識別子を入手するためにも使います。
3つの引数を取ります。
key
これはIPCキーのことで、ftok()IPC_PRIVATEを使って取得できます。
ただの一意な値だと考えていいと思います。
詳しくはSystem V IPC 基礎Man Page of FTOKなどを読んでみてください。
このkeyはkey_t型ですが、これを使うために<sys/types.h>が必要です。

メモリサイズ
これはPAGE_SIZEの倍数でないといけません。
そうなってなかったら、ちょっと増やしたりして勝手に直しておいてくれるらしいです。

flag
共有メモリ作成のオプションや、作成したメモリの権限の指定ができます。
詳しくはMan Pageをみてください。
ここに載っている以外にも権限を設定するフラグ(chmodで使うやつ)が使えるみたいです。

作成済みの共有メモリのIDを入手したい場合は、keyのみを指定しそれ以外を0としておけばOKです。

shmat()

この関数は作成した共有メモリをプロセスのアドレス空間にアタッチしてくれます。
3つの引数を取ります。
共有メモリID
shmget()が作成し返してくれた値です。

メモリ番地
共有メモリをアタッチしたいアドレスです。
ここは0にしておけば勝手に選んでおいてくれるみたいです。

flag
オプションを指定するためのものです。
詳しくはMan Pageを読んでください。

shmdt()

この関数は共有メモリをプロセスのアドレス空間から切り離してくれます。
引数は1つだけでshmat()が返してくれたアドレスでいいみたいです。
非常に簡単です。

shmctl()

この関数は共有メモリに関して様々な制御を行うために使います。
色んな機能があるので細かいことはMan Pageに委ねたいと思います。
3つの引数を取ります。
共有メモリID
制御を行いたい共有メモリのIDです。
shmget()が発行してくれたやつですね。

コマンド
共有メモリに対して行いたい制御の内容です。
この記事ではIPC_RMIDというメモリを解放するためのものを使います。

shmid_ds構造体へのポインタ
共有メモリに対して付加したい情報を収めておくための構造体です。
IPC_STATIPC_SETといったコマンドを使用する際には必要です。
今回は必要ないのでNULL(あるいは0)としておきます。

実際に使ってみる

これだけ解説すればきっともう大丈夫。
せっかく解説したので実際に動くものを作ってみます。
shm_a.cppが共有メモリを管理し、そこに好きな文字列を書き込みます。
shm_b.cppは書き込まれた内容を読み取って表示します。

2つを同じディレクトリにおいて別のターミナルから実行しましょう。
shm_aで書き込んだ内容がshm_bでも反映されるのが分かると思います。

shm_a.cpp
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
using namespace std;


int main(){
    // 空のファイル作成
    FILE *fp;
    const string file_path = "./key.dat";
    fp = fopen(file_path.c_str(), "w");
    fclose(fp);

    // IPC keyの取得
    const int id = 50;
    const key_t key = ftok(file_path.c_str(), id);
    if(key == -1){
        cerr << "Failed to acquire key" << endl; 
        return EXIT_FAILURE;  
    }

    // 共有メモリIDの取得
    const int size = 0x6400;
    const int seg_id = shmget(key, size, 
                              IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
    if(seg_id == -1){
        cerr << "Failed to acquire segment" << endl;
        return EXIT_FAILURE;
    }

    // 共有メモリをプロセスにアタッチする
    char* const shared_memory = reinterpret_cast<char*>(shmat(seg_id, 0, 0));

    // 共有メモリに書き込む
    string s;
    int flag = 0;
    cout << "if you want to close, please type 'q'" << endl;
    while(flag == 0){
        cout << "word: ";
        cin >> s;
        if(s == "q") flag = 1;
        else sprintf(shared_memory, s.c_str());
    }

    // 共有メモリをプロセスから切り離す
    shmdt(shared_memory);

    // 共有メモリを解放する
    shmctl(seg_id, IPC_RMID, NULL);    

    return 0;
}
shm_b.cpp
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
using namespace std;


int main(){
    // 作成済みの共有メモリのIDを取得する
    const string file_path = "./key.dat";
    const int id = 50;

    const key_t key = ftok(file_path.c_str(), id);

    const int seg_id = shmget(key, 0, 0);
    if(seg_id == -1){
        cerr << "Failed to acquire segment" << endl;
        return EXIT_FAILURE;
    }

    // 共有メモリをプロセスにアタッチする
    char* const shared_memory = reinterpret_cast<char*>(shmat(seg_id, 0, 0));

    // 共有メモリの文字を読み取る
    int flag = 0;
    char c;
    cout << "If you want to close, please type 'q'" << endl;
    cout << "If you want to read the shared memory, push enter button." << endl;
    while(flag == 0){
        cin.get(c);
        if(c == 'q') flag = 1;
        else printf("%s\n", shared_memory);
    }

    // 共有メモリをプロセスから切り離す
    shmdt(shared_memory);

    return 0;
}

困ったときは

プロセス間通信を管理するコマンドとしてipcsがあります。
これは存在している共有メモリの情報を教えてくれます。
なのでshm_a.cppを実行しているときに別のターミナルでipcsを実行すると共有メモリの存在を確認できます。
また、このプログラムを強制終了させたりして共有メモリがちゃんと解放されていない場合は、ipcrmを使って削除することができます。
使い方は以下のサイトを読んでみてください。
ipcs コマンド - IBM
ipcrm - プロセス間通信 ID を削除する - IBM

参考

35
35
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
35