POSIX 共享内存

POSIX 共享内存是一种在 Linux 系统上使用的共享内存机制,它允许多个进程可以访问同一个内存区域,从而实现进程间的数据共享。共享内存是可用IPC机制中最快的,使用共享内存不必频繁拷贝数据。但也需要注意,由于共享内存段中的数据可以被多个进程同时访问,因此需要在程序设计中考虑好数据同步和互斥机制,以避免出现数据竞争和不一致的情况。

共享内存使用的基本步骤:

  • 通过 shm_open() 函数创建了共享内存区域,此时会在 /dev/shm/ 创建共享内存文件。
  • 通过 ftruncate() 函数改变共享内存的大小,一般设置为页大小 sysconf(_SC_PAGE_SIZE) 的整数倍。
  • 通过 mmap() 函数将创建的共享内存文件映射到内存。
  • 通过 munmap() 卸载共享内存。
  • 通过 shm_unlink() 删除内存共享文件。

下面分别介绍这些函数。

创建共享内存-shm_open() 函数

shm_open() 函数是用于创建或打开一个共享内存对象,该函数定义如下:

#include <sys/mman.h>

int shm_open(const char *name, int oflag, mode_t mode);

参数说明

  • name:指定共享内存对象的名称,其命名规则类似于文件系统中的文件名,不同进程可以通过相同的名字来访问同一个共享内存对象。
  • oflag:指定打开方式。
  • mode:创建文件时的权限。

返回值

  • 如果函数执行成功,返回一个文件描述符,可以用于后续操作共享内存对象。
  • 如果发生错误,返回 -1。可以通过 errno 变量来获取具体的错误信息。

共享内存对象通过IPC名字描述,当成功创建共享内存对象,系统会以文件形式将其保存在 /dev/shm 目录下。

更改文件大小-ftruncate() 函数

ftruncate() 函数用于更改文件大小,该函数定义如下:

#include <unistd.h>

int ftruncate(int fd, off_t length);

参数说明

  • fd:一个已经打开的文件描述符,用于指定需要更改大小的文件。
  • length:指定文件应当调整到的新大小,单位是字节。

返回值

  • 如果函数执行成功,返回值为0。
  • 如果发生错误,返回 -1。可以通过 errno 变量来获取具体的错误信息。

如果文件在调整大小后比原来更大,文件的数据将会被扩展,多出的部分以0填充。如果文件在调整大小后比原来更小,多出的部分将会被删除。

在调整文件大小时,尽量选择当前系统页大小的整数倍,可以通过 sysconf(_SC_PAGE_SIZE) 获取当前系统的页大小。

内存映射-mmap()函数

mmap() 函数用于创建内存映射区域,该函数定义如下:

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot,
int flags, int fd, off_t offset);

参数说明

  • addr:指定希望内存映射区域的起始地址,通常设为 NULL,由系统自动选择合适的地址。
  • length:指定需要映射的内存区域的长度,单位是字节。
  • prot:用于设置内存区域的保护权限,不能与文件的打开模式冲突。可以是如下值的组合:
    • PROT_NONE:内存区域不可访问。
    • PROT_WRITE:内存区域可以被写入。
    • PROT_READ:内存区域可以被读取。
    • PROT_EXEC:内存区域可以被执行。
  • flags:指定映射对象的类型。下面列出一些常用项:
    • MAP_SHARED:与文件进行共享映射,可以实现多个进程之间共享数据的操作。对映射区域的修改会反映到文件中,同样文件的修改也会反映到映射区域中。
    • MAP_PRIVATE:创建一个私有的映射副本,进程之间不共享数据。对映射区域的修改不会反映到文件中,也不会影响其他映射该文件的进程。
    • MAP_LOCKED:映射区域会被锁定在物理内存中,防止页面被交换出去,保证内存访问速度,但可能会导致内存资源消耗较大。
  • fd:已打开文件的文件描述符,用于与内存映射区域关联。
  • offset:文件中的偏移量,指定文件的起始映射位置。

返回值

  • 如果函数执行成功,返回一个指向映射区域的指针。
  • 如果发生错误,返回 MAP_FAILED(-1) 。可以通过 errno 变量来获取具体的错误信息。

映射方式如下:

数据同步-msync()函数

如果指定了 MAP_SHARED 标志,Linux内核会保持内存映射文件与内存映射区的同步,但这种同步可能不会立即生效,此时,可以使用 msync() 函数来确保数据已经同步完成。该函数定义如下:

#include <sys/mman.h>

int msync(void *addr, size_t length, int flags);

参数说明

  • addr:指向共享内存区域的指针。
  • length:需要同步的共享内存区域的长度。
  • flags:用来指定额外的行为。有如下取值:
    • MS_SYNC:强制将修改同步到存储设备,数据量大时,可能会导致阻塞。
    • MS_ASYNC:将修改排入写队列,不会等待写操作完成。
    • MS_INVALIDATE:标记共享内存区域已经无效,使下次访问该区域时重新从底层存储器加载数据。

返回值

  • 如果函数执行成功,函数返回 0。
  • 如果函数执行失败,函数返回 -1,可以通过 errno 变量来获取具体的错误信息。

注意事项

  • msync() 函数的调用可能会比较耗时,因此需要考虑性能问题。
  • msync() 函数只适用于共享内存,对于普通的内存操作并不适用。

卸载共享内存-munmap()函数

munmap() 函数用于取消指定的地址空间内存映射。该函数定义如下:

#include <sys/mman.h>

int munmap(void *addr, size_t length);

参数说明

  • addr:要取消映射的内存区域的起始地址。是由 mmap() 函数返回的地址。
  • length:要取消映射的内存区域的长度。

返回值

  • 如果函数执行成功,函数返回 0。
  • 如果函数执行失败,函数返回 -1,可以通过 errno 变量来获取具体的错误信息。

注意事项

  • 只能取消由 mmap() 函数创建的内存映射区域,否则会导致未定义行为。
  • 要确保取消映射的地址和长度是有效的,否则会导致程序崩溃或内存泄漏。
  • 取消映射后,原先映射的内存区域就会被释放,程序不应再访问这部分内存。

删除共享内存文件-shm_unlink()函数

shm_unlink()函数用于删除共享内存文件,后续其他进程将无法通过这个名称打开该共享内存对象。该函数定义如下:

#include <sys/mman.h>

int shm_unlink(const char *name);

参数说明

  • name:要删除的 POSIX 共享内存对象的名称。

返回值

  • 如果函数执行成功,函数返回 0。
  • 如果函数执行失败,函数返回 -1,可以通过 errno 变量来获取具体的错误信息。

用例

数据同步

 1 #include<stdio.h>
2 #include<fcntl.h>
3 #include<sys/mman.h>
4 #include<unistd.h>
5 #include<sys/stat.h>
6
7 int main(int argc, char** argv)
8 {
9 size_t mem_size = sysconf(_SC_PAGE_SIZE) * 2;
10 int fd;
11
12 AGAIN:
13 fd = shm_open("/my_shm", O_CREAT | O_RDWR | O_EXCL, 0666);
14 if(fd == -1)
15 {
16 if(errno == EEXIST)
17 {
18 shm_unlink("/my_shm");
19 goto AGAIN;
20 }
21 perror("shm_open");
22 return -1;
23 }
24
25 int ret = ftruncate(fd, mem_size);
26 if(ret == -1)
27 {
28 perror("ftruncate");
29 return -1;
30 }
31
32 void *ptr = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
33 if(ptr == MAP_FAILED)
34 {
35 perror("mmap");
36 return -1;
37 }
38
39 int i = 0;
40 sprintf(ptr, "Data%d", i);
41 while(1);
42
43 close(fd);
44 munmap(ptr, mem_size);
45 return 0;
46 }

输出:

$ cat /dev/shm/my_shm
Data1

当指定 MAP_SHARED 后,对指针 ptr 的操作,都会同步至 my_shm 文件中,数据是以明文形式存储,可以被其他进程读取。

如果将 MAP_SHARED 改为 MAP_PRIVATE,执行结果如下:

$ cat /dev/shm/my_shm
                //无数据

简单用例

读端代码如下:

 1 #include<stdio.h>
2 #include<fcntl.h>
3 #include<sys/mman.h>
4 #include<unistd.h>
5 #include<sys/stat.h>
6 #include<errno.h>
7
8 int main(int argc, char** argv)
9 {
10 size_t mem_size = sysconf(_SC_PAGE_SIZE) * 2;
11 int fd = shm_open("/my_shm", O_CREAT | O_TRUNC | O_RDWR, 0666);
12 if (fd == -1 && errno != EEXIST)
13 {
14 perror("shm_open");
15 return -1;
16 }
17
18 int ret = ftruncate(fd, mem_size);
19 if(ret == -1)
20 {
21 perror("ftruncate");
22 return -1;
23 }
24
25 void *ptr = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
26 if(ptr == MAP_FAILED)
27 {
28 perror("mmap");
29 return -1;
30 }
31
32 while (1)
33 {
34 printf("read data : %s\n", (char *)ptr);
35 sleep(1);
36 }
37
38 close(fd);
39
40 return 0;
41 }

写端代码如下:

 1 #include<stdio.h>
2 #include<fcntl.h>
3 #include<sys/mman.h>
4 #include<unistd.h>
5 #include<errno.h>
6 #include<sys/stat.h>
7
8 int main(int argc, char** argv)
9 {
10 size_t mem_size = sysconf(_SC_PAGE_SIZE) * 2;
11 int fd;
12 AGAIN:
13 fd = shm_open("/my_shm", O_CREAT | O_TRUNC | O_RDWR, 0666);
14 if (fd == -1)
15 {
16 if (errno == EEXIST)
17 {
18 goto AGAIN;
19 }
20 perror("shm_open");
21 return -1;
22 }
23
24 int ret = ftruncate(fd, mem_size);
25 if(ret == -1)
26 {
27 perror("ftruncate");
28 return -1;
29 }
30
31 void *ptr = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
32 if(ptr == MAP_FAILED)
33 {
34 perror("mmap");
35 return -1;
36 }
37
38 int i = 0;
39 while(1)
40 {
41 sprintf(ptr, "Data%d", i++);
42 printf("write data : %s\n", (char*)ptr);
43 sleep(1);
44 }
45
46 close(fd);
47 munmap(ptr, mem_size);
48
49 return 0;
50 }

进程间通信-POSIX 共享内存的更多相关文章

  1. 进程间通信--POSIX共享内存

    1.参考:https://www.cnblogs.com/Anker/archive/2013/01/19/2867696.html

  2. Linux环境进程间通信(五): 共享内存(下)

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  3. Linux环境进程间通信(五): 共享内存(上)

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  4. Linux IPC实践(10) --Posix共享内存

    1. 创建/获取一个共享内存 #include <sys/mman.h> #include <sys/stat.h> /* For mode constants */ #inc ...

  5. Linux进程间通信(四) - 共享内存

    共享内存的优势 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝.对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只 ...

  6. 浅析Linux下进程间通信:共享内存

    浅析Linux下进程间通信:共享内存 共享内存允许两个或多个进程共享一给定的存储区.因为数据不需要在客户进程和服务器进程之间复制,所以它是最快的一种IPC.使用共享内存要注意的是,多个进程之间对一给定 ...

  7. POSIX共享内存

    DESCRIPTION 共享内存是最快的可用IPC形式.它允许多个不相关(无亲缘关系)的进程去访问同一部分逻辑内存. 如果需要在两个进程之间传输数据,共享内存将是一种效率极高的解决方案.一旦这样的内存 ...

  8. POSIX 共享内存和 系列函数

    在前面介绍了system v 共享内存的相关知识,现在来稍微看看posix 共享内存 和系列函数. 共享内存简单来说就是一块真正的物理内存区域,可以使用一些函数将这块区域映射到进程的地址空间进行读写, ...

  9. Posix共享内存区

    1.概述 Posix提供了两种在无亲缘关系进程间共享内存区的方法: (1)内存映射文件:先有open函数打开,然后调用mmap函数把得到的描述符映射到当前进程地址空间中的一个文件(上一篇笔记所用到的就 ...

  10. 细说linux IPC(四):posix 共享内存

    [版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途]         上一节讲了由open函数打开一 ...

随机推荐

  1. MDK Debug时No target connected,STM32 ST-LINK Utility连接不上单片机的解决办法“Can not connect to target!”

    芯片下载程序成功,再次下载时出现,以下错误. 点击确认后,如下提示. 或提示如下. 不管怎么设置都侦测不到芯片. 使用STM32 ST-LINK Utility连接单片机时提示下边错误 "C ...

  2. LINUX 服务器安装nginx redis jdk等步聚

    1.安装指令步聚 sudo yum update 更新linux系统 yum install -y nginx 安装nginx systemctl enable nginx 设置开机启动nginx s ...

  3. Springboot 在项目启动时将数据缓存到全局变量

    有写字典数据不会频繁更新,但是会频繁查询,想要减少数据库链接次数,把内容缓存到项目的全局变量中,提高方法查询速度 import javax.annotation.PostConstruct; impo ...

  4. MSBuild属性

    MSBuild 属性 MSBuild属性是键值对的集合,提前声明好这些属性之后,整个项目的生成都可以引用这些属性. 属性名不区分大小写. 属性都是写在 PropertyGroup 标签中. 1.声明属 ...

  5. Qt关于使用QSqlQuary::size()这个函数值返回是-1

    QSqlQuary::size( ) 今天做项目的时候,用Qt连接Oracle数据库,前面都是连接成功,但是用SQL语句去操作数据库的时候,发现老是读不到内容,卡了好久. QSqlQuery Rule ...

  6. linux中如何判断一个rpm是手动安装还是通过yum安装的

    现状 对于一个不熟悉的服务器或者是虽然是自己的服务器,但历史比较久远,对于上面安装了的一些软件包,我们记忆都慢慢模糊了. 我今天遇到一个情况,在安装一个工具x2openEuler时,安装失败,提示依赖 ...

  7. nnUNet 使用方法

    首先明确分割任务. 其次明确研究方法和步骤. 再做好前期准备,如数据集的采集.标注以及其中的训练集/测试集划分. 其中的参考链接: (四:2020.07.28)nnUNet最舒服的训练教程(让我的奶奶 ...

  8. JMeter BeanShell 获取 HTTP Request 中的 Name

    场景:添加 JMeter log 输出,想输入自定义请求的名称 // 获取 response body prev.getResponseDataAsString(); // 获取 HTTP Reque ...

  9. dockerfile 由于公钥不可用,无法验证以下签名

    报错 当我在打包 docker镜像时,发生了报错 $ sudo docker build -t dcgm-exporter:3.2.5 . 1.772 The following signatures ...

  10. Golang 入门 : Go语言介绍

    简介 Go 语言又称 Golang,由 Google 公司于 2009 年发布,近几年伴随着云计算.微服务.分布式的发展而迅速崛起,跻身主流编程语言之列,和 Java 类似,它是一门静态的.强类型的. ...