1. 前言

共享内存是进程间通信的方式之一,且也是最为高效的通信方式。在32位的Linux系统上,每一个进程都有自己独立的3GB用户空间,这3GB空间中其中有一部分是内存映射区域,而共享内存也是在此发挥了它的作用。假设有两个进程,共享内存则将其内存空间分别映射到两个进程的地址空间中,这样两个进程操作共享内存就像操作自己的地址空间一样,共享内存中的数据变化会影响到双方,例如某个进程向共享内存写入数据,另一个进程可以从共享内存读出数据。

2. 共享内存API介绍

在Linux上提供了一组相关的API使用共享内存。这些接口的声明都在#include <sys/shm.h>以及在#include <sys/ipc.h>中声明的用于获取键值的接口。接下来就来一一介绍一下这些API的含义。

ftok函数
函数原型:

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

别纠结ket_t是什么类型,实际上是int型的别称。pathname是一个指定的文件名,该文件必须存在且可访问。proj_id可以随意设置,在linux上他的值在1-255之间随意取值即可。
这个函数是什么作用呢?要知道在使用共享内存时,首先需要创建一个文件来充当共享内存,但是在系统中可能存在许许多多这样的文件,所以需要唯一一个编号来标志一个个这样的文件。ftok正是根据文件inode和proj_id来产生一个唯一的编号来表示一个共享内存。

shmget函数
函数原型:

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

该函数的作用是创建一个共享内存对象并返回共享内存标识符,如果系统中已经有与当前key相等的共享内存对象,则直接返回共享内存标志符。
参数说明:
key为ftok产生的键值
size为新建共享内存的大小,以字节为单位。
shmflag表示创建的共享内存的操作权限,shmflag为0表示:取共享内存标识符,若不存在则函数会报错;shmflag为IPC_CREAT表示如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符。shmflag为IPC_EXCL时表示如果存在这样的共享内存则报错。
返回值:返回共享内存标识符,出错返回-1。

shmat函数
函数原型:

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

该函数将共享内存对象映射到调用进程的地址空间中,连接成功后会返回共享内存在地址空间中的首地址。
参数说明:
shmid:由shmget函数得到的共享内存对象标识符,指定将哪个共享内存对象映射到地址空间中。
shmaddr:表示共享内存对象从进程空间的哪个地址开始映射,一般为NULL,表示让内核选定一个合适的地址即可。
shmflg:共享内存的读写权限,SHM_RDONLY表示只读模式,其他为读写模式。

shmdt函数
函数原型:

int shmdt(const void *shmaddr);

该函数是将共享内存与该进程分离,分离后该进程的地址空间就没有共享内存部分,也不能对共享内存进行操作。注意,共享内存与进程分离后,共享内存还是存在的,系统中的进程依旧可以将共享内存映射到自己的地址空间中。
参数说明:
shmaddr: 该地址是shmat函数返回的地址,表示共享内存的首地址。

shmctl函数
函数原型:

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

该函数用于对共享内存进行控制,最常见的莫过于删除共享内存了。
参数说明:
shm_id: 由shmget返回的共享内存标志符。
cmd: 表示对共享内存的操作,该变量有三个取值:

// cmd的取值含义:
1. IPC_RMID:删除共享内存
2. IPC_STAT:得到共享内存的状态,buf用于存储共享内存的shmid_ds结构
3. IPC_SET:改变共享内存的状态,把buf中的结构体赋值给本共享内存的shmid_ds结构体

3. 简单的小实例

接下来通过一个小小的例子,来看看如何使用共享内存实现进程间通信。该例子有两个进程,write进程写五个数据到共享内存,read进程从共享内存读取这五个数。
具体实现如下:
write.cpp

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;
const char* path = "/usr/IPC";
int main()
{
	int data[5] = {1,2,3,4,5};
	int i = 0;
	key_t key;
	int shmId;
	
	key = ftok(path, 1); //为共享内存生成键值
	if(key == -1)
	{
		cout << "ftok failed" << endl;
	}
	
	shmId = shmget(key, 100, IPC_CREAT|IPC_EXCL | 0600); //获取共享内存标志符
	cout <<"shmId=" << shmId << endl;
	if(shmId == -1)
	{
		cout << "shmget failed" << endl;
	}
	
	int* shmaddr = (int*)shmat(shmId, NULL, 0); //获取共享内存地址
	for(i = 0; i < 5; i++)
	{
	//往共享内存写数据
		*shmaddr = data[i];
		shmaddr++;
	}
	cout << "write end" << endl;
	return 0;
}

读进程如下:
read.cpp

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;
const char* path = "/usr/IPC";
int main()
{
	key_t key;
	int shmId;
	int i = 0;
	
	key = ftok(path, 1);//生成键值
	if(key == -1)
	{
		cout << "ftok failed" << endl;
	}
	
	shmId = shmget(key, 0, 0);
	cout << "shmId=" << shmId << endl;
	if(shmId == -1)
	{
		cout << "shmget failed" << endl;
	}
	
	int* shmaddr = (int*)shmat(shmId, NULL, 0); //获取共享内存地址
	
	for(i = 0; i < 5;i++)
	{
	//从共享内存读取数据
		cout << *shmaddr << endl;
		shmaddr++;
	}
	cout << "read end" << endl;	
}

实验结果如下:结果如预期。

1
2
3
4
5

上例中没有使用shmctl删除共享内存,可以在终端执行ipcrm -m + shmid来删除指定的共享内存。

4. 总结

通过上面的小例子可以看出,简单的共享内存创建和使用流程如下:
1.ftok函数为共享内存生成键值
2.shmget函数获取共享内存的唯一标志符。
3.shmaddr函数获取共享内存的映射到地址空间后的首地址。
4.shmdt函数将进程与共享内存分离(上例未体现)
5.shmctl函数删除共享内存(上例未体现)
另外,不得不提的是,共享内存本身提供同步机制,一般在多进程的共享内存使用过程中都会使用信号量实现同步操作。

Logo

更多推荐