共享内存-进程间通信

发布时间:2024年01月09日

共享内存

1. 概述

共享内存是一种进程间通信的机制,它允许多个进程在它们的地址空间中映射相同的一块物理内存,从而实现进程之间的数据共享。通过共享内存,不同进程可以直接读取和写入这块内存,而无需经过内核空间,因此通常比其他进程间通信机制更为高效。在所有进程间通信的方式中共享内存的效率是最高的。

共享内存的主要特点包括:

  1. 直接访问: 共享内存允许多个进程直接访问相同的内存区域,无需通过中间缓冲区或消息传递的方式,提高了数据传输的效率。
  2. 高效: 由于不涉及内核空间的数据传输,共享内存通常比其他进程间通信方式(如管道或消息队列)更为高效。
  3. 持久性: 共享内存通常是持久性的,即在进程结束后,共享内存仍然存在,其他进程仍然可以访问其中的数据。
  4. 不同步机制: 共享内存本身并不提供同步机制,因此在多进程访问共享内存时,需要使用其他同步手段,如信号量或互斥锁,以避免数据一致性问题。

2. 创建/打开共享内存

2.1 shmget函数

shmget 是一个用于创建或获取共享内存段的系统调用。在 POSIX 系统中,它通常用于进程间通信,允许多个进程共享同一块内存。以下是 shmget 函数的一般形式:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

参数说明:

  • key:共享内存段的键值,用于唯一标识一个共享内存段。可以使用 ftok 函数生成键值,或者使用 IPC_PRIVATE 表示创建一个新的共享内存段。
  • size:共享内存段的大小,以字节为单位。
  • shmflg:标志参数,用于指定操作的方式。可以是以下标志的组合:
    • IPC_CREAT:如果共享内存段不存在,则创建一个新的。
    • IPC_EXCL:与 IPC_CREAT 一起使用,如果共享内存段已存在,则报错。
    • 权限标志(例如 0666):指定共享内存段的权限。

返回值:

  • 成功时,返回共享内存段的标识符(非负整数),可以用于后续的操作。
  • 失败时,返回 -1,并设置全局变量 errno 表示错误原因。

函数使用举例:

  • 创建一块大小为4k的共享内存
shmget(100, 4096, IPC_CREAT|0664);
  • 创建一块大小为4k的共享内存, 并且检测是否存在
// 	如果共享内存已经存在, 共享内存创建失败, 返回-1, 可以perror() 打印错误信息
shmget(100, 4096, IPC_CREAT|0664|IPC_EXCL);
2.2 ftok函数

ftok 函数是一个用于生成键值(key)的辅助函数,通常用于创建或获取共享内存、消息队列等 IPC(Inter-Process Communication)机制的标识符。ftok 的声明如下:

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

参数说明:

  • pathname一个已经存在的文件的路径名ftok 通过使用指定文件的 inode 和 proj_id 来生成唯一的键值。
  • proj_id:是一个整数值,通常是 0 到 255 之间的一个数字。它被用于加密 inode,以产生最终的键值。

返回值:

  • 如果成功,返回生成的键值。
  • 如果失败,返回 -1,并设置全局变量 errno 表示错误原因。

使用举例:

// 根据路径生成一个key_t
key_t key = ftok("/home/robin", 'a');
// 创建或打开共享内存
shmget(key, 4096, IPC_CREATE|0664);

3. 关联和解除关联

3.1 shmat

创建**/打开共享内存之后还必须和共享内存进行关联,这样才能得到共享内存的起始地址**,通过得到的内存地址进行数据的读写操作,关联函数的原型如下:

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

参数:

  • shmid: 要操作的共享内存的ID, 是 shmget() 函数的返回值
  • shmaddr: 共享内存的起始地址, 用户不知道, 需要让内核指定, 写NULL
  • shmflg: 和共享内存关联的对共享内存的操作权限
    • SHM_RDONLY: 读权限, 只能读共享内存中的数据
    • 0: 读写权限,可以读写共享内存数据

返回值:关联成功,返回值共享内存的起始地址,关联失败返回 (void *) -1

3.2 shmdt

进程不需要再操作共享内存,可以让进程和共享内存解除关联,另外如果没有执行该操作,进程退出之后,结束的进程和共享内存的关联也就自动解除了。

int shmdt(const void *shmaddr);
  • 参数:shmat() 函数的返回值, 共享内存的起始地址
  • 返回值:关联解除成功返回0,失败返回-1

4. 删除共享内存

4.1 shmctl

shmctl 函数用于控制共享内存段,包括获取共享内存段信息、设置权限和删除共享内存段等操作。以下是 shmctl 函数的一般形式:

#include <sys/ipc.h>
#include <sys/shm.h>

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

参数说明:

  • shmid:共享内存段的标识符,通常shmget 返回
  • cmd:控制命令,用于指定 shmctl的操作方式。可以是以下常量之一:
    • IPC_STAT:获取共享内存段的信息,并将结果存储在 buf 结构体中。
    • IPC_SET:设置共享内存段的信息,使用 buf 结构体中的数据。
    • IPC_RMID:删除共享内存段。
  • buf:用于存储或传递信息的结构体指针,通常是 struct shmid_ds 类型的结构体。
    • cmd==IPC_STAT, 作为传出参数, 会得到共享内存的相关属性信息
    • cmd==IPC_SET, 作为传入参数, 将用户的自定义属性设置到共享内存中
    • cmd==IPC_RMID, buf就没意义了, 这时候buf指定为NULL即可

结构体:

// 参数 struct shmid_ds 结构体原型          
struct shmid_ds {
	struct ipc_perm shm_perm;    /* Ownership and permissions */
	size_t          shm_segsz;   /* Size of segment (bytes) */
	time_t          shm_atime;   /* Last attach time */
	time_t          shm_dtime;   /* Last detach time */
	time_t          shm_ctime;   /* Last change time */
	pid_t           shm_cpid;    /* PID of creator */
	pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    // 引用计数, 多少个进程和共享内存进行了关联
	shmatt_t        shm_nattch;  /* 记录了有多少个进程和当前共享内存进行了管联 */
	...
};

返回值:

  • 成功时,根据命令不同,返回相应的信息或执行成功的状态。通常返回 0。
  • 失败时,返回 -1,并设置全局变量 errno 表示错误原因。
4.2 相关shell命令

使用ipcs 添加参数-m可以查看系统中共享内存的详细信息

在这里插入图片描述

使用 ipcrm 命令可以标记删除某块共享内存

在这里插入图片描述

4.3 共享内存状态

struct shmid_ds的结构体中,其中有一个非常重要的成员叫做shm_nattch,在这个成员变量里边记录着当前共享内存关联的进程的个数,一般将其称之为引用计数。当共享内存被标记为删除状态,并且这个引用计数变为0之后共享内存才会被真正的被删除掉。

当共享内存被标记为删除状态之后,共享内存的状态也会发生变化,共享内存内部维护的key从一个正整数变为0,其属性从公共的变为私有的。这里的私有是指只有已经关联成功的进程才允许继续访问共享内存,不再允许新的进程和这块共享内存进行关联了。

5. 进程间通信

下面是一个简单的 C 语言示例,演示了如何使用共享内存实现两个相关进程之间的通信。一个进程写入数据到共享内存,另一个进程从共享内存读取数据:

进程1 - 写入数据到共享内存:
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <string.h>

#define SHARED_MEMORY_KEY 1234
#define SHARED_MEMORY_SIZE 1024

int main() {
    // 创建或获取共享内存段
    int shmid = shmget(SHARED_MEMORY_KEY, SHARED_MEMORY_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    // 连接到共享内存段
    void *shared_memory = shmat(shmid, NULL, 0);
    if (shared_memory == (void*)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    // 写入数据到共享内存
    char* char_shared_memory = (char*)shared_memory;
    strcpy(char_shared_memory, "Hello, shared memory!");

    // 等待一段时间,模拟进程间同步
    sleep(5);

    // 解除连接
    shmdt(shared_memory);

    return 0;
}
进程2 - 从共享内存读取数据:
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>

#define SHARED_MEMORY_KEY 1234
#define SHARED_MEMORY_SIZE 1024

int main() {
    // 获取共享内存段
    int shmid = shmget(SHARED_MEMORY_KEY, SHARED_MEMORY_SIZE, 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    // 连接到共享内存段
    void *shared_memory = shmat(shmid, NULL, 0);
    if (shared_memory == (void*)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    // 读取数据从共享内存
    printf("Received message: %s\n", (char*)shared_memory);

    // 解除连接
    shmdt(shared_memory);

    return 0;
}

在这里插入图片描述

在这里插入图片描述

记住要删除共享内存(代码中实现也可以)

在这里插入图片描述

文章来源:https://blog.csdn.net/f593256/article/details/135481063
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。