Linux 进程间通信——共享内存

目录

0.前言

1.共享内存的数据结构

2.代码示例及共享内存函数

2.1代码示例

[2.2 shmget 函数](#2.2 shmget 函数)

[2.3 shmat 函数](#2.3 shmat 函数)

[2.4 shmdt 函数](#2.4 shmdt 函数)

[2.5 shmctl 函数](#2.5 shmctl 函数)

3.共享内存和管道的比较

4.进一步了解systemV

5.结语


(图像由AI生成)

0.前言

在上篇博客中,我们介绍了 Linux 进程间通信的方式之一------管道。管道是一种简单、方便的进程间通信方式,但它的效率和数据持久性较低,适用于父子进程之间的数据传输。在本篇中,我们将继续深入 Linux IPC(Inter-Process Communication,进程间通信)机制,介绍另一种高效的通信方式------System V 共享内存。共享内存是一种允许多个进程直接访问内存区域的机制,能够提供比管道更快的数据交换速度,非常适合在进程间传输大量数据。

1.共享内存的数据结构

共享内存的示意图如下所示:

在 Linux 系统中,struct shmid_ds 是一个描述共享内存段的内核数据结构,用于管理每个共享内存段的状态和属性。以下是该结构体的主要成员及其作用说明:

cpp 复制代码
struct shmid_ds {
    struct ipc_perm shm_perm;      /* 访问权限和标识符 */
    int shm_segsz;                 /* 共享内存段的大小(字节) */
    __kernel_time_t shm_atime;     /* 上次连接到共享内存的时间 */
    __kernel_time_t shm_dtime;     /* 上次断开共享内存的时间 */
    __kernel_time_t shm_ctime;     /* 共享内存的上次修改时间 */
    __kernel_ipc_pid_t shm_cpid;   /* 创建共享内存的进程 ID */
    __kernel_ipc_pid_t shm_lpid;   /* 上次操作共享内存的进程 ID */
    unsigned short shm_nattch;     /* 当前连接到该共享内存段的进程数 */
    unsigned short shm_unused;     /* 兼容性字段,已废弃 */
    void *shm_unused2;             /* 保留字段(DIPC 使用) */
    void *shm_unused3;             /* 未使用的保留字段 */
};

主要字段说明:

  • shm_perm :这是一个 ipc_perm 类型的结构体,包含共享内存的权限信息,包括用户 ID、组 ID 和访问权限等。它允许系统控制对共享内存段的访问。

  • shm_segsz:共享内存段的大小,以字节为单位。在分配共享内存段时,指定该段的大小,以确定可用内存空间。

  • shm_atimeshm_dtimeshm_ctime :这些字段记录了共享内存段的时间信息。shm_atime 表示上次连接的时间,shm_dtime 表示上次断开的时间,shm_ctime 表示最后一次更改(如调整权限)的时间。

  • shm_cpidshm_lpidshm_cpid 是创建共享内存段的进程 ID,shm_lpid 是最后一个操作该共享内存的进程 ID,用于跟踪对共享内存的访问和管理。

  • shm_nattch :表示当前连接到此共享内存段的进程数。该值用于确定共享内存是否在使用中,通常只有当 shm_nattch 为 0 时,才可以销毁该段。

  • shm_unusedshm_unused2shm_unused3:这些字段为兼容性和扩展预留,目前在大多数系统中未被使用。

这些字段的设置和更新由内核在共享内存的管理过程中自动完成,因此开发者在调用共享内存相关的 API(如 shmgetshmat 等)时无需直接操作这些字段。

2.代码示例及共享内存函数

2.1代码示例

以下代码展示了如何使用 System V 共享内存来进行进程间的通信。此示例包括创建共享内存、写入数据、读取数据,以及在多个进程之间同步共享内存的操作,旨在演示共享内存的基本用法。每个代码片段均附有详细注释以便理解。

cpp 复制代码
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

#define SHM_SIZE 1024  // 共享内存的大小(字节)

int main() {
    // Step 1: 创建唯一的键值
    key_t key = ftok("shmfile", 65);  // 使用ftok生成一个唯一的key
    if (key == -1) {
        perror("ftok failed");
        exit(1);
    }

    // Step 2: 创建共享内存段
    int shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT);  // 0666代表读写权限
    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }

    // Step 3: 创建子进程
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork failed");
        exit(1);
    } else if (pid == 0) {
        // 子进程:写入数据到共享内存
        char *data = (char*) shmat(shmid, NULL, 0);  // 连接到共享内存
        if (data == (char *)(-1)) {
            perror("shmat failed");
            exit(1);
        }

        printf("子进程:写入数据到共享内存...\n");
        strcpy(data, "Hello, Shared Memory! This is child process writing.");
        printf("子进程:数据写入完成,内容为:%s\n", data);

        // 分离共享内存
        if (shmdt(data) == -1) {
            perror("shmdt failed");
            exit(1);
        }

        exit(0);  // 子进程完成写入后退出
    } else {
        // 父进程等待子进程完成
        wait(NULL);

        // 父进程:读取共享内存中的数据
        char *data = (char*) shmat(shmid, NULL, 0);  // 连接到共享内存
        if (data == (char *)(-1)) {
            perror("shmat failed");
            exit(1);
        }

        printf("父进程:读取共享内存中的数据...\n");
        printf("父进程:读取的数据为:%s\n", data);

        // 分离共享内存
        if (shmdt(data) == -1) {
            perror("shmdt failed");
            exit(1);
        }

        // 删除共享内存段
        if (shmctl(shmid, IPC_RMID, NULL) == -1) {
            perror("shmctl failed");
            exit(1);
        }
    }

    return 0;
}

代码说明:

  1. Step 1 : 使用 ftok 函数生成一个唯一的键值 key。这个键将用于识别共享内存段。ftok 可以通过指定文件路径和项目标识符生成一个独特的 key,便于共享内存段在不同进程间的访问。

  2. Step 2 : 使用 shmget 函数创建一个大小为 SHM_SIZE 的共享内存段,并设置其权限为读写。IPC_CREAT 标志表示如果该共享内存段不存在,则创建新的。

  3. Step 3 : 使用 fork 创建一个子进程,演示不同进程对共享内存的访问。

子进程:

  • 使用 shmat 函数将共享内存段映射到自身的地址空间,获取指向共享内存的指针 data
  • 将字符串写入共享内存,并显示写入的内容。
  • 完成后使用 shmdt 函数分离共享内存。

父进程:

  • 使用 wait 等待子进程完成写入。
  • 使用 shmat 连接到共享内存段,读取内容并打印。
  • 断开连接并使用 shmctl 删除共享内存段,确保内存资源被释放。

运行结果

  • 子进程会将数据写入共享内存,而父进程读取并显示数据,展示了进程间共享内存通信的基本工作流程。

2.2 shmget 函数

功能shmget 用于创建或获取一个共享内存段。

原型

cpp 复制代码
int shmget(key_t key, size_t size, int shmflg);

参数

  • key:共享内存段的标识符,通常通过 ftok 函数生成。用于区分不同的共享内存段。
  • size:共享内存段的大小(字节)。这是共享内存分配的内存空间大小。
  • shmflg:控制共享内存段的权限和行为,通常是访问权限和创建标志的组合。
    • 取值 IPC_CREAT:若共享内存段不存在,则创建新段;若已存在,则返回现有段。
    • 取值 IPC_CREAT | IPC_EXCL:若共享内存段不存在,则创建;若已存在,则返回错误。

返回值 :成功时返回共享内存段的标识符(非负整数);失败时返回 -1,并设置 errno 标记错误原因。

2.3 shmat 函数

功能shmat 将指定的共享内存段附加到当前进程的地址空间,允许进程访问共享内存中的数据。

原型

cpp 复制代码
void *shmat(int shmid, const void *shmaddr, int shmflg);

参数

  • shmid:共享内存段的标识符,由 shmget 返回。
  • shmaddr:用于指定共享内存的连接地址。通常为 NULL,让系统选择合适的地址。
  • shmflg:附加模式,可设为 SHM_RDONLY 以只读方式附加共享内存;若为 0,则为读写模式。

返回值 :成功时返回共享内存段在当前进程地址空间中的首地址;失败时返回 (void *) -1

2.4 shmdt 函数

功能shmdt 用于将共享内存段从当前进程的地址空间中分离。

原型

cpp 复制代码
int shmdt(const void *shmaddr);

参数

  • shmaddr:共享内存段在进程地址空间中的首地址,即 shmat 返回的地址。

返回值:成功时返回 0;失败时返回 -1。

2.5 shmctl 函数

功能shmctl 用于控制共享内存段的操作,如删除共享内存段、获取共享内存信息等。

原型

cpp 复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数

  • shmid:共享内存段的标识符。
  • cmd:控制命令,常用的有:
    • IPC_RMID:删除共享内存段。
    • IPC_STAT:获取共享内存段的状态信息,存储在 buf 中。
    • IPC_SET:设置共享内存段的状态信息,如更改权限。
  • bufshmid_ds 结构体指针,用于存储或设置共享内存段的状态信息。

返回值 :成功时返回 0;失败时返回 -1,并设置 errno 标记错误原因。

3.共享内存和管道的比较

特性 共享内存 管道
数据交换速度 高速(直接内存访问) 较慢(需读写操作)
数据存储持久性 手动控制,需显式删除 随进程结束自动销毁
访问方式 允许多个进程读写,需同步控制 适用于父子进程之间
数据量 适合大数据量传输 适合小数据量传输
通信方向 双向读写 单向(单向管道)
编程复杂度 较高(需同步机制) 较低
创建方式 使用 shmget 创建 使用 pipemkfifo

4.进一步了解systemV

System V 是 UNIX 系统的一个重要版本,其提供了一套经典的进程间通信(IPC)机制,包括 共享内存消息队列信号量,它们被称为 System V IPC。这些机制用于满足多进程间的数据共享、消息传递和同步需求,是传统 UNIX 系统中常见的 IPC 手段。

System V 其他通信方式:

  1. 消息队列(Message Queue):消息队列允许进程通过有序的消息进行通信。进程可以将消息放入队列,其他进程按顺序读取消息,实现了异步、结构化的数据传递。

  2. 信号量(Semaphore):信号量用于控制多个进程对共享资源的访问,主要解决并发情况下的同步问题。信号量可以增加或减少资源计数,常用于进程的互斥控制,防止多个进程同时操作同一资源。

这些 System V IPC 方式各自有不同的应用场景,消息队列适用于消息传递,信号量适用于同步控制,而共享内存适用于高效的数据共享。

5.结语

通过本篇博客,我们深入探讨了 Linux 中 System V 共享内存的使用方法,包括其数据结构、关键函数及其使用示例。同时,我们将共享内存与管道进行了对比,并简要介绍了 System V 的其他通信方式,如消息队列和信号量。共享内存作为一种高效的进程间通信机制,适用于大数据量的传输和多进程的资源共享,是 Linux IPC 中不可或缺的一部分。希望通过本篇博客,大家能够更好地理解和运用共享内存来优化进程间的通信效率。

相关推荐
woshilys6 分钟前
sql server 查询对象的修改时间
运维·数据库·sqlserver
疯狂飙车的蜗牛36 分钟前
从零玩转CanMV-K230(4)-小核Linux驱动开发参考
linux·运维·驱动开发
恩爸编程1 小时前
探索 Nginx:Web 世界的幕后英雄
运维·nginx·nginx反向代理·nginx是什么·nginx静态资源服务器·nginx服务器·nginx解决哪些问题
Michaelwubo3 小时前
Docker dockerfile镜像编码 centos7
运维·docker·容器
远游客07133 小时前
centos stream 8下载安装遇到的坑
linux·服务器·centos
马甲是掉不了一点的<.<3 小时前
本地电脑使用命令行上传文件至远程服务器
linux·scp·cmd·远程文件上传
jingyu飞鸟3 小时前
centos-stream9系统安装docker
linux·docker·centos
好像是个likun3 小时前
使用docker拉取镜像很慢或者总是超时的问题
运维·docker·容器
超爱吃士力架3 小时前
邀请逻辑
java·linux·后端
LIKEYYLL5 小时前
GNU Octave:特性、使用案例、工具箱、环境与界面
服务器·gnu