1. 共享内存的介绍

1.1 介绍

不同进程之间通信,通常可以使用共享内存/消息队列/信息量/管道等方法。

共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取,从而实现了进程间的通信。

相比于其他几种方法,共享内存的特点在于:是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝

  • 每个进程都有自己的用户空间,而内核空间是每个进程共享的。因此进程之间想要进行通信,就需要通过内核来实现
  • 为了在多个进程之间交换信息,内核专门留出一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间,进程就可以直接读取这一内存而不需要进行数据的拷贝,从而大大提高了效率
  • 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信息量等
1.2 对比

共享内存和其他进程通信方式的对比图:
在这里插入图片描述

1.3 步骤

共享内存的实现步骤:

①–> 创建/打开共享内存

②–> 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问

③–> 撤销共享内存映射

④–> 删除共享内存对象

2. 相关函数

2.1 shmget()函数:获取共享内存区域的ID
所需头文件#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型int shmget(key_t key, int size, int shmflg);
函数返回值成功:共享内存段标识符
出错:-1

函数参数

  • key
    • 0(IPC_PRIVATE):会建立新共享内存对象
    • 大于0的32位整数:视参数shmflg来确定操作,通常要求此值来源于ftop()函数返回的IPC键值
  • size
    • 大于0的整数:新建的共享内存大小,以字节为单位
    • 0:只获取共享内存时指定为0
  • shmflg
    • 0:取共享内存标识符,若不存在则函数会报错
    • IPC_CREAT:当shmflg & IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
    • IPC_CREAT | IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错

其中ftok()函数:

函数原型key_t ftok(char *fname, int id);
函数参数fname:指定的文件名(该文件必须存在而且可以访问)
id:至少8位的项目id,不能为0
函数返回值成功:返回一个key_t
出错:-1

ftok中的参数可以随便填写,但是要符合格式,ftok只是利用参数,再运用一套算法,算出一个唯一的key值返回。这个key值可以传给共享内存参数,作为stuct ipc_perm中唯一标识共享内存的key


为什么已经有一个key来标识共享内存了,还需要一个返回值来标识共享内存?
因为key是内核级别的,供内核标识,shmget返回值是用户级别的,供用户使用的。

2.2 shmat()函数:建立映射共享内存
所需头文件#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型void *shmat(int shmid, const void *shmaddr, int shmflg);
函数参数shmid:要映射的共享内存区标识符
shmaddr:将共享内存映射到指定地址(若为NULL,则表示由系统自动完成映射)
shmflg:权限,常见的有两个
-------------SHM_RDONLY:共享内存只读
-------------SHM_REMAP:重新映射一个进程地址空间,这样shmaddr不能为空
-------------默认0:共享内存可读写
函数返回值成功:共享内存段标识符
出错:-1
2.3 shmdt()函数:解除共享内存映射
所需头文件#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型void shmdt(const void *shmaddr);
函数参数shmaddr:共享内存映射后的地址
函数返回值成功:0
出错:-1
2.4 shmctl()函数:共享内存管理
所需头文件#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数参数shmid:要操作的共享内存区标识符
cmd:要进行的操作
---------IPC_STAT(获取对象属性)
---------IPC_SET(设置对象属性)
---------IPC_RMID(删除对象)
buf:指定IPC_STAT/IPC_SET时用以保存/设置属性
函数返回值成功:0
出错:-1

IPC(进程间通信)资源生命周期不随进程,而是随内核的,不释放会一直占用,除非重启。所以,shmget创建的共享内存一定要利用shmctl()释放掉,不然会内存泄漏。

3. 代码实例

3.1 把数据写入共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>

int main() {
	/**********实现步骤中的第1步**********/
	//利用ftok获得一个IPC键值,
	key_t key = ftok("/home", 0x666);
	if (key == -1) {
		perror("ftok error");
		return -1;
	}
	printf("ftok : %d\n", key);

	//获取共享内存区域的ID
	int shmId = shmget(key, 4096, IPC_CREAT | 0600);
	if (shmId == -1) {
		perror("shmget error");
		return -1;
	}
	printf("shmget : %d\n", shmId);
	
	/**********实现步骤中的第2步**********/
	//连接共享内存
	void *shm_addr = shmat(shmId, NULL, 0);

	//创建需写入内存的数据并将其写入共享内存区
	char data[30] = "test datas";
	memcpy(shm_addr, &data, sizeof(data));
	printf("Writing is finish !\n");
	
	/**********实现步骤中的第3步**********/
	//断开
	shmdt(shm_addr);
	
	// 写入程序中不需要释放共享内存(第4步),否则读取程序无法正确读取数据

	return 0;
	
}
3.2 从共享内存取出数据
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <iostream>

int main() {
	/**********实现步骤中的第1步**********/
	//ftok的第一个参数必须保证和写入程序中一致,最好别写相对路径
	key_t key = ftok("/home", 0x666);
	if (key == -1) {
		perror("ftok error");
		return -1;
	}
	printf("ftok : %d\n", key);

	int shmId = shmget(key, 4096, IPC_CREAT | 0600);
	if (shmId == -1) {
		perror("shmget error");
		return -1;
	}
	printf("shmget : %d\n", shmId);
	
	/**********实现步骤中的第2步**********/
	void *shm_addr = shmat(shmId, NULL, 0);

	//取出数据
    char data[30] = {0};
	memcpy(&data, shm_addr, sizeof(data));
    std::cout << "the data : " << data << std::endl;
	
	/**********实现步骤中的第3步**********/
	//断开
	shmdt(shm_addr);
	
	/**********实现步骤中的第4步**********/
    //将共享内存释放
    int sh = shmctl(shmId, IPC_RMID, NULL);
    if (sh == -1) {
        perror("shmctl error");
        return -1;
    }
	printf("shmctl is worked.\n");

	return 0;
}
3.3 执行结果

在这里插入图片描述

3.4 共享内存的查看和手动释放

如果在通讯结束后,没有使用shmctl将共享内存释放掉,那么就会造成内存泄漏,这时需要我们手动去释放,操作如下:

# 查看所有IPC设施
$ ipcs -m

------------ 共享内存段 --------------
键        shmid      拥有者  权限          字节     连接数       状态                
0x00000000 32800      zy    600        1048576    2          目标       
0x00000000 32805      zy    600        524288     2          目标       
0x66030001 98347      zy    600        4096       0                       
0x00000000 32818      zy    600        6897664    2          目标            
0x00000000 65599      zy    600        24576      2          目标

# 释放共享内存:ipcrm -m [shimd]
$ ipcrm -m 98347
$ ipcs -m

------------ 共享内存段 --------------
键        shmid      拥有者  权限          字节     连接数       状态                
0x00000000 32800      zy    600        1048576    2          目标       
0x00000000 32805      zy    600        524288     2          目标                
0x00000000 32818      zy    600        6897664    2          目标            
0x00000000 65599      zy    600        24576      2          目标

4. 参考

linux 进程通信-共享内存(shared memory)《Rice linux 学习开发》

【C++服务器入门基础------6.IPC进程间通信–共享内存】

进程间通信之共享内存

linux 进程间通信,示例代码。包括共享内存,消息队列,信号量。及用户态和内核态的通信

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐