Linux IPC 之 共享内存(Shared Memory)
共享内存(Shared Memory)是Linux 进程间通信(IPC
)的一种方式,它允许多个进程访问同一块内存区域,从而避免频繁的数据复制,提高效率。Linux 提供了 SysV 共享内存 和 POSIX 共享内存 两种主要机制。
- 其他Linux进程间通信机制(IPC)之 《消息队列》
共享内存技术类别
- SysV 共享内存
System V 共享内存(shmge
t / shmat
/ shmdt
/ shmctl
)是最早的一种共享内存实现,基于 IPC 机制,使用起来复杂。SysV 共享内存 适用于老系统,但 API 复杂。
- POSIX 共享内存
POSIX 共享内存(shm_open
/ mmap
/ shm_unlink
)是较新的方式,提供更好的权限控制和文件描述符操作。POSIX 共享内存 更易用,推荐用于现代应用。
共享内存技术实现
由于现代应用几乎都用POSIX标准,所以下面只会对POSIX进行探究。
POSIX 共享内存(shm_open
/ mmap
/ shm_unlink
)是较新的方式,提供更好的权限控制和文件描述符操作。
使用步骤
1. 创建或打开共享内存
int shm_open(const char *name, int oflag, mode_t mode);
name
:共享内存对象的名称(如 “/shm_example
”)oflag
:O_CREAT | O_RDWR
:创建并读写O_RDONLY
只读
mode
:权限(如0666
八进制)- 返回值:成功时返回文件描述符
fd
- 创建共享内存成功后会在
/dev/shm/
下出现创建的共享内存对象的名称 - 调用
shm_open
时并不会指定内存的实际大小。
2. 调整共享内存大小
ftruncate
是一个系统调用,用于调整由文件描述符指定的文件或共享内存对象的大小。它通常用于在操作文件或内存映射时调整文件大小。对于共享内存对象,它的功能是调整映射的内存区域的大小。
- 设定共享内存大小
int ftruncate(int fd, off_t length);
参数说明
-
fd
:一个有效的文件描述符,可以是普通文件、设备文件或共享内存对象的文件描述符。共享内存对象通常通过shm_open
获取。 -
length
:文件或共享内存对象的新大小(字节)。如果length
小于当前大小,则文件或内存区域会被截断;如果length
大于当前大小,则文件或共享内存对象会被扩展。
返回值
- 返回 0 表示成功。
- 如果失败,返回 -1 并将
errno
设置为相应的错误代码。
其他
- 在调用
ftruncate
扩展共享内存对象的大小后,通常需要使用mmap
映射更新后的大小。
3. 映射到进程地址空间
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr
:通常设为NULL
length
:大小prot
:PROT_READ
只读PROT_WRITE
可写
flags = MAP_SHARED
共享映射fd
:shm_open
返回的文件描述符- 返回值:映射地址
- 在调用
ftruncate
扩展共享内存对象的大小后,通常需要使用mmap
映射更新后的大小。
4. 使用共享内存
- 进程可直接访问
mmap
返回的地址
5. 解除映射
int munmap(void *addr, size_t length);
6. 关闭共享内存
int close(int fd);
- 用
close
关闭共享内存通常是减少对共享内存对象的引用计数,并不会立即释放内存,只有在最后一个引用被关闭或解除映射时,共享内存才会被释放。
7. 删除共享内存
int shm_unlink(const char *name);
-
shm_unlink
是用于删除 POSIX 共享内存对象的系统调用。它的作用是 从系统中删除共享内存对象的名称,使得它在shm_open
中不可见,但不会立即销毁共享内存,直到所有进程关闭该共享内存对象后,系统才会回收它。 -
不会立即删除共享内存的实际数据,如果有进程仍然持有文件描述符或映射该共享内存,则共享内存仍然存在,直到所有引用(文件描述符和
mmap
映射)都被释放。 -
name
:shm_open
使用的共享内存名称,应当是以/
开头的 POSIX 名称,会在/dev/shm/
下创建name
不要/
的设备文件。 -
可以查看共享内存的信息
ls -l /dev/shm/<name>
stat /dev/shm/<name>
-
强制删除 POSIX 共享内存
rm /dev/shm/<name>
接口详细说明
1. shm_open(const char *name, int oflag, mode_t mode)
oflag
标志 | 描述 |
---|---|
O_RDONLY | 只读打开共享内存对象 |
O_RDWR | 读写模式打开共享内存对象 |
O_CREAT | 如果共享内存对象不存在,则创建 |
O_EXCL | 与 O_CREAT 一起使用,若对象已存在,则返回错误 |
O_TRUNC | 如果共享内存对象已存在,则将其截断为 0 大小 |
O_CLOEXEC | 在 exec 时自动关闭共享内存对象 |
O_RDONLY
- 以 只读模式 打开共享内存对象。
- 如果目标对象不存在,会返回错误。
示例
int fd = shm_open("/shm_example", O_RDONLY, 0666);
注意:只读模式下,mmap
也必须以 PROT_READ
映射,否则会失败:
mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
O_RDWR
- 以 可读可写模式 打开共享内存对象。
- 如果目标对象不存在,会返回错误。
示例
int fd = shm_open("/shm_example", O_RDWR, 0666);
O_CREAT
- 创建 共享内存对象(如果不存在)。
- 若对象已存在,
shm_open
仍会成功返回文件描述符。
示例
int fd = shm_open("/shm_example", O_CREAT | O_RDWR, 0666);
- 如果
/dev/shm/shm_example
已存在,不会报错,直接返回文件描述符。
O_EXCL
- 与
O_CREAT
组合使用,确保共享内存对象唯一性。 - 如果对象已存在,会返回错误(
EEXIST
)。
示例
int fd = shm_open("/shm_example", O_CREAT | O_EXCL | O_RDWR, 0666);
if (fd == -1) {perror("shm_open failed");
}
- 如果对象已存在,
shm_ope
n 会失败,返回 -1 并设置errno = EEXIST
。
O_TRUNC
- 截断(清空)已存在的共享内存对象。
- 仅适用于可写模式(
O_RDWR
)。 O_TRUNC
会清空已存在的共享内存,谨慎使用。
示例
int fd = shm_open("/shm_example", O_CREAT | O_RDWR | O_TRUNC, 0666);
- 如果
/dev/shm/shm_example
已存在,则其大小被清零。
O_CLOEXEC
exec
调用(如execve
)时自动关闭共享内存文件描述符。- 避免子进程继承文件描述符。
O_CLOEXEC
可防止exec
后文件描述符泄露。
示例
int fd = shm_open("/shm_example", O_CREAT | O_RDWR | O_CLOEXEC, 0666);
mode
shm_open
中的 mode
参数
shm_open
的mode
参数用于指定共享内存对象的访问权限,其语义与ope
n 系统调用的mode
参数相同。它仅在O_CREAT
标志被使用时才生效,否则会被忽略。
mode
常见权限
模式(宏) | 数值(八进制) | 描述 |
---|---|---|
S_IRUSR | 0400 | 用户可读 |
S_IWUSR | 0200 | 用户可写 |
S_IXUSR | 0100 | 用户可执行(对共享内存无意义) |
S_IRGRP | 0040 | 组可读 |
S_IWGRP | 0020 | 组可写 |
S_IXGRP | 0010 | 组可执行(无意义) |
S_IROTH | 0004 | 其他用户可读 |
S_IWOTH | 0002 | 其他用户可写 |
S_IXOTH | 0001 | 其他用户可执行(无意义) |
mode
组合方式
可以通过 按位或 (|) 运算符 组合多个权限。例如:
0666
(所有用户可读写):
shm_open("/shm_example", O_CREAT | O_RDWR, 0666);
等价于:
shm_open("/shm_example", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
POSIX 共享内存代码示例
进程 1(写入共享内存)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>#define SHM_NAME "/shm_example"
#define SHM_SIZE 1024int main() {int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);ftruncate(fd, SHM_SIZE);char *data = (char *)mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);strcpy(data, "Hello from process 1!");printf("Process 1 wrote: %s\n", data);munmap(data, SHM_SIZE);close(fd); //并没有删除内存共享return 0;
}
进程 2(读取共享内存)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>#define SHM_NAME "/shm_example"
#define SHM_SIZE 1024int main() {int fd = shm_open(SHM_NAME, O_RDWR, 0666);char *data = (char *)mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);printf("Process 2 read: %s\n", data);munmap(data, SHM_SIZE);close(fd);shm_unlink(SHM_NAME); // 删除共享内存return 0;
}