本页目录

共享内存与信号量

覆盖: shmget/shmat (SysV) → mmap(MAP_SHARED) → POSIX semaphore → file locking → memfd_create → 对比与选择 适用: Linux 用户态

概述

管道和 socket 传递数据靠拷贝,共享内存则让多个进程访问同一块物理内存——零拷贝。这是最高性能的 IPC 方式,但需要额外的同步机制(信号量、file lock、或 lockless 数据结构)。

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
//   - 可以 sealing (防止修改): fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW)
//   - 可以传给另一个进程: sendmsg(SCM_RIGHTS) → fd passing

// systemd 用 memfd 做所有内部 IPC

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);

// 为什么过时: 全局 key 冲突, 资源泄露风险, 不够灵活

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);  // undo
        futex_wait(&sem->count, val);
    }
}
// 与 pthread_mutex 相同: 无竞争时零 syscall → futex 的力量

File Locking: 进程间互斥

// fcntl record lock: 对文件某区域加锁
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 sem
守护进程单实例 + 进程间锁            file lock (F_SETLK)
线程同步 (同进程)                    pthread_mutex (futex)
线程/进程间计数信号量                POSIX sem (futex)
可靠凭证传递                        AF_UNIX + SCM_CREDENTIALS

参考

  • man: mmap(2), shm_overview(7), sem_overview(7), fcntl(2) (record locks), memfd_create(2)
  • 源码⁠: ipc/sem.c (SysV), ipc/shm.c, mm/mmap.c
  • LWN: "memfd_create and sealing"

关键词: mmap MAP_SHARED, memfd_create, POSIX semaphore, sem_wait, futex, file lock, F_SETLK, shared memory