共享内存(Shared Memory)
Linux 共享内存(System V SHM / POSIX SHM)并不是"基于某个磁盘文件系统",而是基于内核自己实现的 tmpfs文件系统 。 具体位置:
- POSIX 共享内存
挂载点:/dev/shm
文件系统类型:tmpfs(内核 2.4 以后固定挂载,用户不可见磁盘后备)。
- System V 共享内存(shmget/shmat 系列)
内核同样把每个段放在匿名的 tmpfs 页上,通过 shm_mnt 这一内部挂载点(用户不可见)管理;用户空间看到的 /proc/sysvipc/shm 只是只读接口,真正的页在 tmpfs 中。
因此,答案一句话:
Linux 共享内存基于内核内置的 tmpfs 文件系统,对用户可见的部分挂在 /dev/shm。
补充:
- tmpfs 的页可以被 swap,因此严格说不是"纯物理内存",但一定不占用任何磁盘块。
- 从内核源码角度看,无论是 mm/shmem.c(POSIX)还是 ipc/shm.c(System V),最终都调用 shmem 层的接口,而 shmem 就是 tmpfs 的实现。
共享内存是一种高效的进程间通信方式,不同编程语言提供了不同级别的API支持:
C/C++ 封装的共享内存API
1. POSIX 共享内存(Linux/Unix)
arduino
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
// 主要函数:
int shm_open(const char *name, int oflag, mode_t mode); // 创建/打开
int ftruncate(int fd, off_t length); // 设置大小
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // 映射
int munmap(void *addr, size_t length); // 取消映射
int shm_unlink(const char *name); // 删除
示例:
ini
// 创建共享内存
int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);
ftruncate(fd, SIZE);
char *ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
2. System V 共享内存(Unix/Linux 平台通用)
arduino
#include <sys/shm.h>
// 主要函数:
key:唯一标识新创建的共享内存
size:共享内存的大小,向上取整成PAGE_SIZE的倍数
shmflg:一些标志信息.
IPC_CREAT:根据key判断对应的内存段是否存在,如果不存在,则创建;如果存在,则返回已经存在的共享内存段
IPC_EXCL: 和IPC_CREAT一起用,如果已经存在key对应的共享内存则失败
int shmget(key_t key, size_t size, int shmflg); // 创建/获取(返回根据key生成的shmid) shmat------》映射共享内存到进程的虚拟地址空间,返回映射的虚拟内存段的起始地址
shmid:共享内存的唯一标识id, shmget返回值。
shmaddr:内存映射起始地址,如果是NULL的话,内核会帮你分配。
shmflg:是一组标志位,通常为0
void *shmat(int shmid, const void *shmaddr, int shmflg); // 映射共享内存 shmdt------》解除映射,如果成功返回0,否则返回-1 shmaddr:内存映射起始地址
int shmdt(const void *shmaddr); // 解除共享内存映射
int shmctl(int shmid, int cmd, struct shmid_ds *buf); // 控制

Java 封装的共享内存API
1. Java NIO - MappedByteBuffer
java
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
// 创建内存映射文件
RandomAccessFile file = new RandomAccessFile("file.dat", "rw");
FileChannel channel = file.getChannel();
// 映射到内存
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
// 读写操作
buffer.put(0, (byte) 42);
byte value = buffer.get(0);
数据安全问题
在一个"进程"内, 多个线程同时更新共享资源, 有数据并发安全问题,解决方式如下:
- 原子操作
- 锁机制__管理
信号量 (并发编程)
多个进程同时更新共享内存(共享资源),也有数据安全问题,解决方式如下:
IPC的信号量 (semaphore)
IPC的信号量
原理思想和【操作系统: 并发编程】中的信号量是一样的, IPC的信号量把共享内存看成临界区,但是二者具体实现完全不一样。
- IPC的信号量实现非常复杂,在内核态中实现。 用于多个进程之间
- 并发编程的信号量是在用户态实现,基于原子操作实现。用于同一个进程的多个线程之间
下图,展示了进程A, 进程B都对共享内存做写操作,信号量在其中的重要作用,保证临界区"共享内存"同一时间只有一个进程在操作

1.System V ipc 信号量
c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
获取 或 创建 一个信号量集
key:唯一标识新创建的信号量集
nsems:信号量集中的 信号量 的 数量
semflg: 一些标志信息
IPC_CREAT:根据key判断对应的共享内存段是否存在,如果不存在则创建,如果存在则返回已经存在的共享内存段
IPC_EXCL: 和IPC_CREAT一起用,如果已经存在key对应的共享内存则失败
int semget(key_t key, int nsems, int semflg)
信号量的控制操作
semid: 唯一标识新创建的信号量集
semnum: 信号量集中 的 第几个信号量(你要对 哪个 信号量 做控制操作) cmd: 设置信号量方式
int semctl(int semid, int semnum, int cmd, .....)
信号量操作
semid: 唯一标识新创建的信号量集
sops: 对指定信号量执行的一组操作
nsops: sops的个数
int semop(int semid, struct sembuf *sops, unsigned nsops)