このページの目次

共有メモリとセマフォ

カバー範囲: shmget/shmat (SysV) → mmap(MAP_SHARED) → POSIX セマフォ → ファイルロック → memfd_create → 比較と選択 対象: Linux ユーザー空間

概要

パイプやソケットはデータ転送にコピーを利用しますが、共有メモリは複数のプロセスが同じ物理メモリ領域にアクセスすることを可能にし、ゼロコピーを実現します。これは最も高性能な IPC(プロセス間通信)方式ですが、追加の同期メカニズム(セマフォ、ファイルロック、またはロックレスデータ構造)が必要です。

mmap(MAP_SHARED): POSIX 共有メモリ

// 書き方 1: 匿名共有メモリ (fork 後に共有)
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE,
                  MAP_SHARED|MAP_ANONYMOUS, -1, 0);
// fork 後: 親プロセスと子プロセスは同じ物理ページを参照する (COW ではない!)

// 書き方 2: ファイルベースの共有メモリ
int fd = open("/dev/shm/myregion", O_CREAT|O_RDWR, 0600);
ftruncate(fd, size);
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 無関係なプロセス: 同じファイルを open → mmap → 共有

// 各プロセスが同じ物理ページを参照する → 変更すると互いに可視になる → 同期が必要!

memfd_create: 匿名ファイルハンドル

int fd = memfd_create("myregion", MFD_CLOEXEC);
ftruncate(fd, size);
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

// 利点:
//   - ファイルシステムのマウントが不要 → /dev/shm が不要
//   - シーリング (変更防止) 可能: fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW)
//   - 別のプロセスに渡せる: sendmsg(SCM_RIGHTS) → ファイル記述子の受け渡し

// systemd は内部の IPC に memfd を使用している

SysV 共有メモリ (旧式、非推奨)

int shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
void *addr = shmat(shmid, NULL, 0);
// ... read/write ...
shmdt(addr);
shmctl(shmid, IPC_RMID, NULL);

// 時代遅れとなった理由: グローバルキーの衝突、リソースリークのリスク、柔軟性の欠如

POSIX セマフォ

#include <semaphore.h>

// 匿名セマフォ (スレッドまたは共有メモリ用):
sem_t sem;
sem_init(&sem, 1, 1);   // pshared=1 → 共有メモリ内で動作
sem_wait(&sem);          // P (1 減算、ブロック)
sem_post(&sem);          // V (1 加算、待機中のスレッドを再起動する可能性あり)
sem_destroy(&sem);

// 命名セマフォ (無関係なプロセス用):
sem_t *sem = sem_open("/mysem", O_CREAT, 0600, 1);
sem_wait(sem);
sem_post(sem);
sem_close(sem);
sem_unlink("/mysem");

sem_wait の内部実装: futex

// glibc の sem_wait 実装 (簡略化):
sem_wait(sem_t *sem) {
    while (1) {
        if (atomic_fetch_sub(sem->count, 1) > 0)
            return;  // 成功! システムコールなし

        // count が負の値になった → 待機が必要
        atomic_fetch_add(sem->count, 1);  // 元に戻す
        futex_wait(&sem->count, val);
    }
}
// pthread_mutex と同じ: 競合がない場合はシステムコールなし → futex の力

ファイルロック: プロセス間排他制御

// fcntl レコードロック: ファイルの特定領域にロックをかける
struct flock fl = {
    .l_type   = F_WRLCK,  // 排他ロック
    .l_whence = SEEK_SET,
    .l_start  = 0,
    .l_len    = 0,        // 0 = ファイル末尾まで
};
fcntl(fd, F_SETLKW, &fl);  // ブロックして待機

// 利点: カーネルが自動管理 → プロセスがクラッシュしてもロックが自動的に解放される
// 適している用途: デーモンの単一インスタンス実行、データベースファイルの保護

選択ガイド

シナリオ                              推奨ソリューション
─────────────────────────────────────────────
親子プロセス間の単方向データ転送        pipe
親子プロセス間の双方向データ転送        socketpair
無関係なプロセス間での大容量データ共有   mmap(MAP_SHARED) + POSIX セマフォ
デーモンの単一インスタンス + プロセス間ロック  ファイルロック (F_SETLK)
スレッド同期 (同一プロセス内)            pthread_mutex (futex)
スレッド/プロセス間のカウントセマフォ     POSIX セマフォ (futex)
信頼性の高い認証情報の受け渡し            AF_UNIX + SCM_CREDENTIALS

参考

  • man: mmap(2), shm_overview(7), sem_overview(7), fcntl(2) (レコードロック), memfd_create(2)
  • ソースコード⁠: ipc/sem.c (SysV), ipc/shm.c, mm/mmap.c
  • LWN: "memfd_create and sealing"

キーワード: mmap MAP_SHARED, memfd_create, POSIX セマフォ, sem_wait, futex, ファイルロック, F_SETLK, 共有メモリ