当前位置: 首页 > ai >正文

多进程通信之共享内存

       

目录

共享内存的POSIX方法

        POSIX方法的共享内存基本原理:

        详解5个核心函数的各个参数:

        示例代码:test_A创建共享内存,test_B读取共享内存 

共享内存的系统调用方法

       系统调用方法的共享内存基本原理:

        详解4个核心函数的各个参数: 

        示例代码:test_A创建共享内存,test_B读取共享内存        test_A        

        额外说明:


        关于共享内存的讲解,我们在写代码举例的时候,往往还需要考虑一些其它事情,为了更加轻松和更加清晰的认识共享内存,我们先忽略了这些“其它事情”,在后续的代码中我会做出说明。

共享内存的POSIX方法

        首先总览一下要用到的5个关键函数:大家先浏览一下即可。

  • int shm_open(const char *name, int oflag, mode_t mode);创建一个新的 POSIX 共享内存对象或打开一个已存在的共享内存对象。成功时返回共享内存对象的文件描述符,失败时返回-1并设置errno以指示错误原因。
  • int ftruncate(int fd, off_t length);设置与文件描述符fd关联的共享内存对象的大小为length字节。创建共享内存对象时只是创建了一个 “外壳”,需要通过此函数来分配实际的内存空间。成功时返回0,失败时返回-1并设置errno
  • void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);将共享内存对象映射到调用进程的地址空间,使得进程可以像访问普通内存一样访问共享内存。成功时返回映射区域的起始地址,失败时返回MAP_FAILED(即(void *)-1)并设置errno
  • int munmap(void *addr, size_t length);解除之前通过mmap映射的内存区域,断开进程与共享内存对象的映射关系,但不删除共享内存对象本身。成功时返回0,失败时返回-1并设置errno
  • int shm_unlink(const char *name);删除指定名称的 POSIX 共享内存对象。只有当所有映射该共享内存对象的进程都解除映射(通过munmap)后,该共享内存对象才会真正从系统中删除。成功时返回0,失败时返回-1并设置errno

        POSIX方法的共享内存基本原理

        详解5个核心函数的各个参数:

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

  • name:一个以/开头的字符串,用于唯一标识共享内存对象。不同进程可以通过相同的name来访问同一个共享内存对象,实现进程间通信。例如"/my_shared_memory"
  • oflag:打开标志,常见取值有:
    • O_CREAT:如果共享内存对象不存在,则创建它。若对象已存在,该标志通常不会导致错误,而是直接打开已存在的对象。
    • O_RDONLY:以只读方式打开共享内存对象。
    • O_RDWR:以读写方式打开共享内存对象。
  • mode:当使用O_CREAT创建共享内存对象时,mode用于设置其权限,类似于文件权限设置。例如,0666表示所有者、组和其他用户都有读写权限;0600表示只有所有者有读写权限。这个参数仅在使用O_CREAT时有效。

        int ftruncate(int fd, off_t length);

  • fd:由shm_open函数返回的共享内存对象的文件描述符。
  • length:要设置的共享内存对象的大小,以字节为单位。这个大小必须是系统页大小(通常为 4096 字节)的整数倍。

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

  • addr:通常设为NULL,表示由系统自动选择合适的地址进行映射。如果不为NULL,系统会尽量使用该地址作为映射起始地址,但并不保证一定使用。
  • length:要映射的共享内存区域的长度,以字节为单位,应与之前ftruncate设置的大小一致。
  • prot:指定映射区域的保护权限,常见取值有:
    • PROT_READ:映射区域可读。
    • PROT_WRITE:映射区域可写。
    • PROT_EXEC:映射区域可执行(用于映射共享库等可执行代码)。
    • PROT_NONE:映射区域不可访问。
    • 这些标志可以通过逻辑或(|)组合使用,比如PROT_READ | PROT_WRITE表示映射区域可读可写。
  • flags:指定映射的类型和其他一些选项,常见取值有:
    • MAP_SHARED:对映射区域的写入会反映到共享内存对象中,同时其他映射该对象的进程也能看到这些修改,实现进程间共享数据。这是进程间通信的关键。
    • MAP_PRIVATE:对映射区域的写入不会反映到共享内存对象中,而是产生一个写时复制(Copy - On - Write)的效果,即当进程对映射区域进行写入操作时,系统会为该进程分配新的物理页面,而原始共享内存对象不受影响。
  • fd:由shm_open返回的共享内存对象的文件描述符。
  • offset:通常设为0,表示从共享内存对象的起始位置开始映射。必须是系统页大小的整数倍。

        int munmap(void *addr, size_t length);

  • addr:由mmap返回的映射区域起始地址。
  • length:映射区域的长度,应与mmap时的长度一致。

        int shm_unlink(const char *name);

  • name:要删除的共享内存对象的名称,即shm_open函数中使用的名称。

        示例代码:test_A创建共享内存,test_B读取共享内存
        test_A
 

#include <iostream>
#include <sys/mman.h>
#include <sys/stat.h>        
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
// test_A用于创建共享内存,让另一个程序test_B来读。int main()
{// 创建共享内存// int shm_open(const char *name, int oflag, mode_t mode);int shm_fd = shm_open("/my_share_memory",O_CREAT | O_RDWR,0666);// 设置共享内存大小// int ftruncate(int fd, off_t length);ftruncate(shm_fd,4096);// 将共享内存映射到进程地址空间,并且设置MAP_SHARED,对映射区域的修改会映射到共享内存中// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);void *ptr = mmap(NULL,4096,PROT_READ | PROT_WRITE,MAP_SHARED,shm_fd,0);// 在映射区域中输入"I am test_A"char message[] = "I am test_A";strcpy((char *)ptr, message);sleep(30);munmap(ptr, 4096);close(shm_fd);return 0;
}

        test_B
 

#include <iostream>
#include <sys/mman.h>
#include <sys/stat.h>        
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
using namespace std;
// test_B用于读取共享内存中的数据
int main()
{// 获取共享内存// int shm_open(const char *name, int oflag, mode_t mode);int shm_fd = shm_open("/my_share_memory",O_RDWR,0666);// 无需设置共享内存大小// 将共享内存映射到进程地址空间,并且设置MAP_SHARED,对映射区域的修改会映射到共享内存中// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);void *ptr = mmap(NULL,4096,PROT_READ | PROT_WRITE,MAP_SHARED,shm_fd,0);// 读取共享内存中的数据cout <<"test_B see: " << (char*)ptr << endl;munmap(ptr, 4096);close(shm_fd);shm_unlink("/my_share_memory");return 0;
}

        注意编译的时候要指定链接选项“-lrt"
        输出结果为:

        上述代码示例足以说明共享内存的逻辑,只不过稍有一些与共享内存原理无关的小问题;例如:test_A万一还没把数据写入共享内存,test_B就开始读了。例如:假如双方同时往共享内存中写数据,不就乱套了。但是这些问题是另一些话题上的了。

共享内存的系统调用方法

        首先总览一下要用到的4个关键函数:大家先浏览一下即可。

  • int shmget(key_t key, size_t size, int shmflg);创建一个新的共享内存段或获取一个已存在的共享内存段的标识符。成功时返回共享内存段的标识符(一个非负整数),失败时返回 -1 并设置 errno 以指示错误原因。
  • void *shmat(int shmid, const void *shmaddr, int shmflg);将共享内存段连接到调用进程的地址空间,使得进程可以访问共享内存。成功时返回指向共享内存段的指针,失败时返回 (void *)-1 并设置 errno
  • int shmdt(const void *shmaddr);将共享内存段从调用进程的地址空间分离。注意,这并不删除共享内存段,只是断开进程与共享内存段的连接。成功时返回指向共享内存段的指针,失败时返回 (void *)-1 并设置 errno
  • int shmctl(int shmid, int cmd, struct shmid_ds *buf);对共享内存段执行各种控制操作。成功时返回 0,失败时返回 -1 并设置 errno

       系统调用方法的共享内存基本原理

        详解4个核心函数的各个参数:
        int shmget(key_t key, size_t size, int shmflg);

  • key:一个 key_t 类型的值,用于唯一标识共享内存段。通常可以使用 ftok 函数生成一个 key。不同进程使用相同的 key 来获取同一个共享内存段,实现进程间通信。
  • size:指定共享内存段的大小,以字节为单位。这个大小必须是系统页大小的整数倍。
  • shmflg:一组标志位,用于控制共享内存段的创建和访问权限。常见的标志有:
    • IPC_CREAT:如果共享内存段不存在,则创建它。
    • IPC_EXCL:与 IPC_CREAT 一起使用,如果共享内存段已存在,则 shmget 调用失败。
    • 权限标志:如 0666 表示所有者、组和其他用户都有读写权限。

        void *shmat(int shmid, const void *shmaddr, int shmflg);

  • shmid:由 shmget 函数返回的共享内存段的标识符。
  • shmaddr:通常设为 NULL,表示让系统自动选择合适的地址进行连接。如果指定非 NULL 地址,系统会尽量使用该地址,但不保证一定使用。
  • shmflg:标志位,常见取值有 SHM_RDONLY(以只读方式连接),若设为 0 则以读写方式连接。

        int shmdt(const void *shmaddr);

  • shmaddr:由 shmat 函数返回的指向共享内存段的指针。

        int shmctl(int shmid, int cmd, struct shmid_ds *buf);

  • shmid:共享内存段的标识符。
  • cmd:要执行的命令,常见的命令有:
    • IPC_RMID:删除共享内存段。
    • IPC_STAT:获取共享内存段的状态信息,填充到 buf 指向的结构体中。
    • IPC_SET:设置共享内存段的属性,如权限等,属性信息来自 buf 指向的结构体。
  • buf:一个指向 struct shmid_ds 结构体的指针,用于传递或接收共享内存段的相关信息,具体取决于 cmd 的值。当 cmd 为 IPC_RMID 时,该参数可以为 NULL

        示例代码:test_A创建共享内存,test_B读取共享内存
        test_A
        

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>
// test_A用于创建共享内存,让另一个程序test_B来读。int main()
{// 创建共享内存int shmid = shmget(1234, 4096, IPC_CREAT | 0666);// 将共享内存连接到进程地址空间char *shm = (char *)shmat(shmid, NULL, 0);// 在映射区域中输入"I am test_A"char message[] = "I am test_A";strcpy(shm, message);sleep(30);// 分离共享内存shmdt(shm);return 0;
}

        test_B
 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
using namespace std;
// test_B用于读取共享内存中的数据
int main()
{// 获取共享内存int shmid = shmget(1234, 4096,0666);// 将共享内存连接到进程地址空间char *shm = (char *)shmat(shmid, NULL, 0);// 接收数据cout << "test_B see: " << shm << endl;// 分离共享内存shmdt(shm);// 删除共享内存段shmctl(shmid, IPC_RMID, NULL);return 0;
}

        额外说明:

        如果shmget用于创建共享内存,则这段共享内存的所有字节都被初始化为0,与之关联的内核数据结构shmid_ds将被创建并初始化。shmid_ds结构体的定义如下:

struct shmid_ds {struct ipc_perm shm_perm;    /* 共享内存段的权限结构 */size_t          shm_segsz;   /* 共享内存段的大小(字节数) */time_t          shm_atime;   /* 上次连接到共享内存段的时间 */time_t          shm_dtime;   /* 上次从共享内存段分离的时间 */time_t          shm_ctime;   /* 共享内存段的创建时间或上次修改时间 */pid_t           shm_cpid;    /* 创建共享内存段的进程的 PID */pid_t           shm_lpid;    /* 上次对共享内存段执行操作的进程的 PID */shmatt_t        shm_nattch;  /* 当前连接到共享内存段的进程数 *//* 以下字段是系统保留字段,应用程序一般不应直接访问 */void            *shm_unused1; void            *shm_unused2; void            *shm_unused3; 
};

        shmget对shmid_ds结构体的初始化包括:

  • 将shm_perm.cuid和shm_perm.uid设置为调用进程的有效用户ID。
  • 将shm_perm.cgid和shm_perm.gid设置为调用进程的有效组ID。
  • 将shm_perm.mode的最低9位设置为shmflg参数的最低9位。
  • 将shm_segsz设置为size。
  • 将shm_lpid、shm_nattach、shm_atime、shm_dtime设置为0。
  • 将shm_ctime设置为当前的时间。

        shmid_ds结构体与shmctl()一起用于对共享内存段信息的获取。

        对于共享内存更加完善的应用,在后续文章中会进行说明。

http://www.xdnf.cn/news/13076.html

相关文章:

  • 0,freeRTOS基础知识
  • SpringBoot API接口签名(防篡改)
  • win11 找不到 GPEDIT.MSC Win11找不到gpedit.msc怎么办?Win11提示缺少gpedit.msc文件怎么办???
  • PyCharm 和 Anaconda 搭建Python环境【图文教程】
  • 32位寻址与64位寻址
  • 轻量级屏蔽文件管理方案
  • Ascend NPU上适配Step1X-Edit模型
  • 线程池与并发工具:让并发编程更简单、高效!
  • CodeRider 2.0 体验手记:当 AI 编程照进现实,颠覆想象的开发之旅
  • 【51单片机】4. 模块化编程与LCD1602Debug
  • 中国大学本科专业采用‌学科门类—专业类—具体专业‌三级体系
  • 【DAY44】预训练模型
  • sql语句执行流程
  • 指令管理—弹幕/礼物/快捷键—抖音直播伴侣—使用教程
  • omi开源程序是AI 可穿戴设备的源码。戴上它,说话,转录,自动完成
  • 用大白话解释一下“高基数特征”
  • java+webstock
  • 什么是API调用?通俗解释+实际应用场景详解
  • 【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!
  • PAN/FPN
  • Flotherm许可的并发用户数限制
  • 【案例解析】一次 TIME_WAIT 导致 TPS 断崖式下降的排查与优化
  • ThreadLocal 源码
  • RabbitMq安装
  • deepseek: GPU 配套
  • 联邦学习同态加密以及常见问题
  • Vue动态/异步组件
  • 1991-2024年上市公司个股换手率数据
  • Haption 力反馈遥操作机器人:6 自由度 + 低延迟响应,解锁精准远程操控体验
  • 设置Outlook关闭时最小化