进程通信——共享内存

文章目录

  • 1.基本认识
    • 1.1 概念介绍
    • 1.2主要原理
  • 2.使用方法
    • 2.1创建共享内存shmget
      • 2.1.1 shmget
      • 2.1.2 ftok
    • 2.2映射地址空间shmat
    • 2.3 访问共享内存
    • 2.4 同步和互斥
  • 3.接口封装
    • 3.1 创建shm_create
    • 3.2 映射shm_connect
    • 3.3 多进程共享内存

共享内存是一种机制,也是进程间进行通信的一种方式

1.基本认识

1.1 概念介绍

不同进程的资源通常是独立的,他们所占用的内存互相独立,不可互相访问。
共享内存允许多个进程访问同一块内存区域,从而实现数据的共享和交换。是一种高效的进程通信方法。

在共享内存中,多个进程可以将同一块物理内存,映射到它们各自的虚拟地址中,使它们可以直接读写该内存的内容,而无需通过消息传递等其它通信方式,也就是“完全无需额外的拷贝”。这种直接的内存访问,使得数据交换更高效。

1.2主要原理

要理解共享内存,首先要理解“虚拟内存”这一概念。

虚拟内存是进程直接操作的地址,不同进程可以存在相同的虚拟内存地址,因为实际所代表的物理地址并不相同

每个进程内的虚拟地址是连续的,但实际映射在物理地址上,却不是连续的。

通常来说各进程之间不会共用物理内存地址,如下图所示:
在这里插入图片描述
进程A和进程B各自的虚拟内存,分别映射在实际物理内存的不同区域上。

那么,共享内存就是改变这种映射关系,让不同的进程“共用物理内存”。如下图所示:
在这里插入图片描述
这个时候,进程A的第3块虚拟内存,与进程B的第1块虚拟内存,就指向同一块物理内存了。

也就是它们共享内存了。之后其中一个改写了这块内存的内容,另一个就可以直接读到,省去了中间(系统内核)的数据拷贝。这就是所谓“共享内存效率最高”的原因。

但从中我们不难看出,共享内存也是有缺点的,那就是大家都是读写同一块内存,很容易冲突。所以共享内存通常还需要搭配其它同步机制使用。

2.使用方法

共享内存的使用并不难,就是改变内存映射,获取到同一个指向共享内存的地址,然后读写数据。

2.1创建共享内存shmget

第一步是创建一个共享内存区域,并分配一块内存来存储数据。

2.1.1 shmget

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

/***********************************
* @para key 共享内存的键值,用于标识共享内存段。通常使用 ftok 函数生成键值。
* @para size 共享内存段的大小,以字节为单位
* @para shmflg 共享内存的标志位,用于指定创建共享内存的权限和行为
* @return 成功时,返回共享内存的标识符(即共享内存的ID)
* 		  失败时,返回-1,并设置相应的错误码
* 
*********************************/
int shmget(key_t key, size_t size, int shmflg);

//example
key_t key = ftok(".", 123); //详见下一节
int shmid = shmget(key, 1024, IPC_CREAT | 0666);

2.1.2 ftok

上述出现了一个 ftok 函数,这个函数返回了一个 key_t

这个有什么作用呢?其实,这个 key 是不同进程间使用同一片物理内存的关键。如果要进行共享,就大家都得使用同一个 key。否则,系统如何知道哪几个进程之间要共享呢。

而 ftok 则是使用一个“路径(文件或目录)”加一个“立即数”,来产生一个 key。只要大家的这两个参数相同,计算出来的 key 就是一致的。

其原型如下:

#include <sys/types.h>
#include <sys/ipc.h> 
key_t ftok(const char *pathname, int proj_id);

参数:
pathname:一个存在的文件路径名,用于生成键值。通常选择一个已经存在的文件。
proj_id:一个整数,用于区分不同的IPC资源。通常选择一个非零的整数。

返回值:
如果成功,返回一个键值(key)。
如果失败,返回-1,并设置相应的错误码。

关于ftok,还有几个要点:

  • ftok 的路径可以随便设置,但必须是实际存在的
  • ftok 只是想取文件 inode 这个唯一数值,和文件权限无关
  • proj_id 在一些系统(如 UNIX)上,实际取值是1到255。

为什么对 ftok 说那么多,因为这个 key 对很多进程间通信都是一个关键。不只是共享内存,其它诸如消息队列、信号量之类的方法,也会用到。

2.2映射地址空间shmat

第二步就是,将创建出的共享内存空间,映射到本进程的虚拟地址空间。
映射使用的是 shmat,原型如下:

参数:
shmid:共享内存的标识符(即共享内存的ID),由shmget函数返回
shmaddr:指定共享内存段附加到当前进程地址空间的地址。通常设置为 NULL,表示由系统自动选择一个合适的地址
shmflg:共享内存的标志位,用于指定附加共享内存的权限和行为

返回值:
成功时,返回指向共享内存段附加地址的指针。
失败时,返回 -1,并设置相应的错误码。

例子:
#include <sys/types.h>
#include <sys/shm.h>
char *shmaddr = shmat(shmid, NULL, 0);

2.3 访问共享内存

这一步比较简单,就是向共享内存(也可以理解为自己的地址空间)写数据和读数据。

memcpy(shmaddr, source_buff, source_length); //写 
memcpy(target_buff, shmaddr, target_length); //读

2.4 同步和互斥

之前提到过共享内存的不足。由于多个进程可以同时访问共享内存,需要使用同步和互斥机制来确保数据的一致性和正确性。常见的同步机制包括信号量、互斥锁和条件变量等。详情请看进程互斥访问-信号量

3.接口封装

3.1 创建shm_create

#include <sys/types.h>
#include <sys/ipc.h> 
key_t ftok(const char *pathname, int proj_id);
#define KEY_NUM 126
#define FILE_PATH "./share.txt"

int shm_create_with_key(const char *filepath, int len, int key_num)
{
	key_t key = ftok(filepath, key_num);
	if(key < 0)
	{
		printf("create key error!\n");
		return key;
	}
	int shmid = shmget(key, len, IPC_CREATE|0666);//666代表可读可写
	if(shmid < 0)
	{
		return shmid;
		printf("create share mem error!\n")
	}
	return shmid
}

int shm_create(int len)
{
	return shm_create_with_key(FILE_PATH, len, KEY_NUM);
}

3.2 映射shm_connect


char* shm_connect(int shm_id)
{
	char *mem = shmat(shm_id, NULL, 0);
	if(mem < 0)
	{
		printf("shmat error!\n");
		return mem;
	}
	return mem;
}

3.3 多进程共享内存

#include <sys/types.h>
#include <sys/ipc.h> 
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define KEY_NUM 126
#define FILE_PATH "./share.txt"

//使用文件路径作为共享内存
int shm_create_with_key(const char *filepath, int len, int key_num)
{
	key_t key = ftok(filepath, key_num);
	if(key < 0)
	{
		printf("create key error!\n");
		return key;
	}
	int shmid = shmget(key, len, IPC_CREAT|0666);//666代表可读可写
	if(shmid < 0)
	{
		return shmid;
		printf("create share mem error!\n");
	}
	return shmid;
}

int shm_create(int len)
{
	return shm_create_with_key(FILE_PATH, 100, KEY_NUM);
}

char* shm_connect(int shm_id)
{
	char *mem = (char*)shmat(shm_id, NULL, 0);
	if(mem < 0)
	{
		printf("shmat error!\n");
		return NULL;
	}
	return mem;
}

int main()
{
    int shm_id = shm_create(10);
    if(shm_id < 0)
    {
        printf("shm_create error!\n");
        return -1;
    }   
    char *mem = shm_connect(shm_id);
    if(!mem)
    {
        printf("shm_connect error!\n");
        return -1;
    }
    int i = 10;
    while (--i)
    {
        printf("%s\n", mem);
        sleep(1);
    }

    shmdt(mem);
    return 0;
}

#include <sys/types.h>
#include <sys/ipc.h> 
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "string.h"

#define KEY_NUM 126
#define FILE_PATH "./share.txt"

int shm_create_with_key(const char *filepath, int len, int key_num)
{
	key_t key = ftok(filepath, key_num);
	if(key < 0)
	{
		printf("create key error!\n");
		return key;
	}
	int shmid = shmget(key, len, IPC_CREAT|0666);//666代表可读可写
	if(shmid < 0)
	{
		return shmid;
		printf("create share mem error!\n");
	}
	return shmid;
}

int shm_create(int len)
{
	return shm_create_with_key(FILE_PATH, 100, KEY_NUM);
}


char* shm_connect(int shm_id)
{
	char *mem = (char*)shmat(shm_id, NULL, 0);
	if(mem < 0)
	{
		printf("shmat error!\n");
		return NULL;
	}
	return mem;
}

int main()
{
    int shm_id = shm_create(10);
    if(shm_id < 0)
    {
        printf("shm_create error!\n");
        return 0;
    }   
    char *mem = shm_connect(shm_id);
    if(!mem)
    {
        printf("shm_connect error!\n");
        return 0;
    }
    //初始化
    memcpy(mem, "hello", 6);
    int i = 0;
    while (i<10)
    {
        mem[0] = '1'+i;
        ++i;
        sleep(1);
    }

    shmdt(mem);
    return 0;
}

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值