Linux-IPC-共享内存

news/2025/2/28 15:04:08/

Linux IPC 之 共享内存(Shared Memory)

共享内存(Shared Memory)是Linux 进程间通信(IPC)的一种方式,它允许多个进程访问同一块内存区域,从而避免频繁的数据复制,提高效率。Linux 提供了 SysV 共享内存 和 POSIX 共享内存 两种主要机制。

  • 其他Linux进程间通信机制(IPC)之 《消息队列》

共享内存技术类别

  1. SysV 共享内存

System V 共享内存(shmget / shmat / shmdt / shmctl)是最早的一种共享内存实现,基于 IPC 机制,使用起来复杂。SysV 共享内存 适用于老系统,但 API 复杂。

  1. 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 共享映射
  • fdshm_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 映射)都被释放。

  • nameshm_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_EXCLO_CREAT 一起使用,若对象已存在,则返回错误
O_TRUNC如果共享内存对象已存在,则将其截断为 0 大小
O_CLOEXECexec 时自动关闭共享内存对象
  1. O_RDONLY
  • 以 只读模式 打开共享内存对象。
  • 如果目标对象不存在,会返回错误。

示例

int fd = shm_open("/shm_example", O_RDONLY, 0666);

注意:只读模式下,mmap 也必须以 PROT_READ 映射,否则会失败:

mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
  1. O_RDWR
  • 以 可读可写模式 打开共享内存对象。
  • 如果目标对象不存在,会返回错误。

示例

int fd = shm_open("/shm_example", O_RDWR, 0666);
  1. O_CREAT
  • 创建 共享内存对象(如果不存在)。
  • 若对象已存在,shm_open 仍会成功返回文件描述符。

示例

int fd = shm_open("/shm_example", O_CREAT | O_RDWR, 0666);
  • 如果 /dev/shm/shm_example 已存在,不会报错,直接返回文件描述符。
  1. 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_open 会失败,返回 -1 并设置 errno = EEXIST
  1. O_TRUNC
  • 截断(清空)已存在的共享内存对象。
  • 仅适用于可写模式(O_RDWR)。
  • O_TRUNC 会清空已存在的共享内存,谨慎使用。

示例

int fd = shm_open("/shm_example", O_CREAT | O_RDWR | O_TRUNC, 0666);
  • 如果 /dev/shm/shm_example 已存在,则其大小被清零。
  1. 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_openmode 参数用于指定共享内存对象的访问权限,其语义与 open 系统调用的 mode 参数相同。它仅在 O_CREAT 标志被使用时才生效,否则会被忽略。
  1. mode常见权限
模式(宏)数值(八进制描述
S_IRUSR0400用户可读
S_IWUSR0200用户可写
S_IXUSR0100用户可执行(对共享内存无意义)
S_IRGRP0040组可读
S_IWGRP0020组可写
S_IXGRP0010组可执行(无意义)
S_IROTH0004其他用户可读
S_IWOTH0002其他用户可写
S_IXOTH0001其他用户可执行(无意义)
  1. 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;
}

http://www.ppmy.cn/news/1575527.html

相关文章

vue3+naiveUI开关switch

文档&#xff1a;https://www.naiveui.com/zh-CN/os-theme/components/switch <n-switch :value"active" update:value"onSwitch" :loading"loading" :rubber-band"false"><template #checked>正常</template><t…

【Viewer.js】vue3封装图片查看器

效果图 需求 点击图片放大可关闭放大的 图片 下载 cnpm in viewerjs状态管理方法 stores/imgSeeStore.js import { defineStore } from pinia export const imgSeeStore defineStore(imgSeeStore, {state: () > ({showImgSee: false,ImgUrl: ,}),getters: {},actions: {…

解决npm run dev报错

解决&#xff1a;Node.js 版本更新后与 OpenSSL 不兼容导致的npm报错“Error: error:0308010C:digital envelope routines::unsupported” 方法一&#xff1a;更改系统环境变量方法二&#xff1a;更改项目环境变量方法三&#xff1a;更换 Node.js 版本方法四&#xff1a;升级依…

DDD 架构之领域驱动设计【通俗易懂】

文章目录 1. 前言2. MVC 对比 DDD3. DDD 分层架构4. 完整业务流程 1. 前言 官方回答&#xff1a;DDD是一种应对复杂业务系统的设计方法&#xff0c;通过将软件设计与业务领域紧密结合&#xff0c;帮助开发人员构建清晰、可维护的领域模型。在复杂的业务系统中&#xff0c;它能…

【DeepSeek】本地部署,保姆级教程

deepseek网站链接传送门&#xff1a;DeepSeek 在这里主要介绍DeepSeek的两种部署方法&#xff0c;一种是调用API&#xff0c;一种是本地部署。 一、API调用 1.进入网址Cherry Studio - 全能的AI助手选择立即下载 2.安装时位置建议放在其他盘&#xff0c;不要放c盘 3.进入软件后…

【Python pro】函数

1、函数的定义及调用 1.1 为什么需要函数 提高代码复用性——封装将复杂问题分而治之——模块化利于代码的维护和管理 1.1.1 顺序式 n 5 res 1 for i in range(1, n1):res * i print(res) # 输出&#xff1a;1201.1.2 抽象成函数 def factorial(n):res 1for i in range(1…

【Golang】go语言异常处理快速学习

Go 语言的异常处理与很多传统的编程语言不同&#xff0c;它没有 try/catch 这样的异常捕获机制&#xff0c;而是通过 错误类型&#xff08;error&#xff09;来进行错误处理。Go 语言鼓励显式地处理错误&#xff0c;保持代码的简单性和可维护性。在 Go 中&#xff0c;错误处理不…

求最小值(数组)

题目描述 给出 n 和 n 个整数 ai​&#xff0c;求这 n 个整数中最小值是什么。 输入格式 第一行输入一个正整数 n&#xff0c;表示数字个数。 第二行输入 n 个非负整数&#xff0c;表示 a1​,a2​…an​&#xff0c;以空格隔开。 输出格式 输出一个非负整数&#xff0c;表…