Friday, June 5, 2015

共有メモリによるプロセス間通信

Unix の共有メモリを使ったプロセス間通信について調べて実験をしてみた.対象は1つのホスト上での複数のプロセスである.ネット上でいくつか例題はないかと探したが,どうも良い例となるコードが見当たらなかった.結局はある解説記事と,Stack Overflow の議論と,man page を見て作ってみたものになったので,例をここに置くのも有用かと考え,この記事を書く.(もしかしたら探し方が悪くて良いコード例をみつけられなかっただけかもしれない.)

mmap を使うかどうかという話がいくつもでていたが,POSIX の方向としては,shmem_open と mmap を使うという方向があるということだったので,それを信じてその形での実装を試してみた.

基本的なコードの流れは次のようになる.

  1. 共有メモリ領域を1つのプロセスが shm_open() を使って作成する.その際に,プロセス間で共通の文字列を識別子(``identifier'')とする.(Linux ではこれが /dev/shm/identifier のように見える.)
  2. 共有メモリ領域を mmap() でメモリにマップする.共有メモリポインター (shared_ptr)が得られる.
  3. shared_ptr を使って複数のプロセスで通信をする.
  4. 利用終了後は munmap() をつかってマップを消す.
  5. 共有メモリオブジェクトを shm_unlink() によって消す.

以下に示すプログラムは,server と client の2つのプロセスが共有メモリを使って通信をするものである.ここで,server プロセス数と client プロセス数は共に 1 を仮定する.server と client は自分の領域にしか値を書き込まないことで,ロックを避けている.互いに相手の値を読み,それよりも1大きい数を一定の期間ごとに自分の領域に書くという例題である.シンプルではあるが,共有メモリで通信をする基本としては十分なものだと思う.ソースコード(shmem_test.cpp)を以下に付加する.ソースコードのコメントにコンパイル方法とどのように利用するかを書いておく.

/*
  Shared memory inter process communication minimal example
  Copyright (C) 2015 Hitoshi

  Compile:
    g++ shmem_test.cpp -o shmem_test -lrt

  Run (as a server):
    shmem_test server

  Run (as a client)
    shmem_test client

  Note: no locking. (but writes are not the same location.)

  Basic idea: server, shared memory creator

  1. Create a shared memory object by shm_open().
  2. Change the shared memory object by ftruncate().
  3. mmap the shared memory object to access via a pointer.
  4. Use the pointer to share the memory (may need lock, etc.)
  5. munmap the shared memory object
  6. Remove the shared memory object by shm_unlink()

  Basic idea: client

  1. Open the created shared memory object by shm_open().
  2. mmap the shared memory object to access via a pointer.
  3. Use the pointer to share the memory (may need lock, etc.)
  4. munmap the shared memory object

 */

#include <iostream>

#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

//----------------------------------------------------------------------
/// shard memory identifier name
const char* SHMEM_NAME = "/test.shmem";
/// shared memory size (align to the page size)
const size_t SHMEM_SIZE = 4096;

//----------------------------------------------------------------------
/// print out the usage and exit of this example
void usage_exit()
{
    std::cerr
        << "Shard memory inter process communication minimal test\n"
        << "shmem_test [server|client]\n"
        << "  server ... create/read/write shared memory\n"
        << "  client ... read/write shared memory"
        << std::endl;
    exit(1);
}

//----------------------------------------------------------------------
/// server
void run_server()
{
    // create/open the shared memory object
    int fd = shm_open(SHMEM_NAME,
                      // oflags
                      O_CREAT|  // create if not exist
                      // O_TRUNK|  // destroy once if exists
                      O_EXCL|   // error if exists
                      O_RDWR    // read/write
                      ,
                      // mode
                      S_IRUSR|  // user read
                      S_IWUSR   // user write
        );

    if (fd >= 0)
    {
        std::cout << "server: shmem_open succeed fd: " << fd << std::endl;
    }
    else
    {
        std::cerr << "server: shmem_open failed: " << fd
                  << ", if /dev/shm"    << SHMEM_NAME
                  << " exists, remove it." << std::endl;
        exit(1);
    }

    // resize the shmem object
    const int ret = ftruncate(fd, SHMEM_SIZE);
    if (ret != 0)
    {
        std::cerr << "Server: failed to ftrancate to " << SHMEM_SIZE
                  << std::endl;
        exit(1);
    }

    // get shmem address
    const size_t access_offset = 0;
    void* shmem_adder = mmap(0, SHMEM_SIZE,
                             // memory protection mode
                             PROT_READ| // Pages may be read.
                             PROT_WRITE // Pages may be written.
                             ,
                             // mapping flag
                             MAP_SHARED, // Share this mapping with other process
                             fd,
                             access_offset);
    if (shmem_adder == 0)
    {
        std::cerr << "Serevr failed to mmap." << std::endl;
        exit(1);
    }
    close(fd);                 // fd is no longer needed

    // server process work
    int* int_ptr = reinterpret_cast<int*>(shmem_adder);

    // initialize
    int_ptr[0] = 0;
    int_ptr[1] = 0;

    for (int i = 0; i < 10; ++i)
    {
        int my_int    = int_ptr[0];
        int other_int = int_ptr[1];

        if (my_int <= other_int)
        {
            my_int = other_int + 1;
            int_ptr[0] = my_int;
            std::cout << "I am taller. " << my_int << ", "
                      << other_int << std::endl;
        }
        usleep(800000);
        std::cout << "Checking client " << i << std::endl;
    }
    std::cout << "Quit server: " << int_ptr[0] << ", " << int_ptr[1] << std::endl;

    // unmap
    int ummap_ret = munmap(shmem_adder, SHMEM_SIZE);
    if (ummap_ret != 0)
    {
        std::cerr << "Failed to munmap. " << ummap_ret << std::endl;
        exit(1);
    }

    // remove shmem object
    int unlink_ret = shm_unlink(SHMEM_NAME);
    if (unlink_ret != 0)
    {
        std::cerr << "Failed to shm_unlink. " << unlink_ret << std::endl;
        exit(1);
    }
}

//----------------------------------------------------------------------
void run_client()
{
    // open the shared memory object
    int fd = shm_open(SHMEM_NAME,
                      // oflags
                      O_RDWR    // read/write
                      ,
                      // mode
                      S_IRUSR|  // user read
                      S_IWUSR   // user write
        );

    if (fd >= 0)
    {
        std::cout << "shmem_open succeed fd: " << fd << std::endl;
    }
    else
    {
        std::cerr << "shmem_open failed: " << fd
                  << ", Are you running the server?" << std::endl;
        exit(1);
    }

    // get shmem address
    const size_t access_offset = 0;
    void* shmem_adder = mmap(0, SHMEM_SIZE,
                             // memory protection mode
                             PROT_READ| // Pages may be read.
                             PROT_WRITE // Pages may be written.
                             ,
                             // mapping flag
                             MAP_SHARED, // Share this mapping with other process
                             fd,
                             access_offset);
    if (shmem_adder == 0)
    {
        std::cerr << "Failed to mmap." << std::endl;
        exit(1);
    }
    close(fd);                 // fd is no longer needed

    // server process work
    int* int_ptr = reinterpret_cast<int*>(shmem_adder);

    // initialize
    int_ptr[0] = 0;
    int_ptr[1] = 0;

    for (int i = 0; i < 10; ++i)
    {
        int my_int    = int_ptr[1];
        int other_int = int_ptr[0];

        if (my_int <= other_int)
        {
            my_int = other_int + 1;
            int_ptr[1] = my_int;
            std::cout << "I am taller. " << my_int << ", "
                      << other_int << std::endl;
        }
        usleep(500000);
        std::cout << "Checking server " << i << std::endl;
    }
    std::cout << "Quit client: " << int_ptr[0] << ", " << int_ptr[1] << std::endl;

    // unmap
    int ummap_ret = munmap(shmem_adder, SHMEM_SIZE);
    if (ummap_ret != 0)
    {
        std::cerr << "Failed to munmap. " << ummap_ret << std::endl;
        exit(1);
    }
}

//----------------------------------------------------------------------
/// main
int main(int argc, char* argv[])
{
    if (argc == 1)
    {
        usage_exit();
    }
    else if ((argc == 2) && std::string(argv[1]) == "server")
    {
        run_server();
    }
    else if ((argc == 2) && std::string(argv[1]) == "client")
    {
        run_client();
    }
    else
    {
        usage_exit();
    }

    return 0;
}

この例題が誰かの役に立てば幸いです.

No comments:

Post a Comment