共享内存是进程间通信的方式之一,且也是最为高效的通信方式。在32位的Linux系统上,每一个进程都有自己独立的3GB用户空间,这3GB空间中其中有一部分是内存映射区域,而共享内存也是在此发挥了它的作用。假设有两个进程,共享内存则将其内存空间分别映射到两个进程的地址空间中,这样两个进程操作共享内存就像操作自己的地址空间一样,共享内存中的数据变化会影响到双方,例如某个进程向共享内存写入数据,另一个进程可以从共享内存读出数据。
在Linux上提供了一组相关的API使用共享内存。这些接口的声明都在#include
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结构体
接下来通过一个小小的例子,来看看如何使用共享内存实现进程间通信。该例子有两个进程,write进程写五个数据到共享内存,read进程从共享内存读取这五个数。
具体实现如下:
write.cpp
#include
#include
#include
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
#include
#include
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来删除指定的共享内存。
通过上面的小例子可以看出,简单的共享内存创建和使用流程如下:
1.ftok函数为共享内存生成键值
2.shmget函数获取共享内存的唯一标志符。
3.shmaddr函数获取共享内存的映射到地址空间后的首地址。
4.shmdt函数将进程与共享内存分离(上例未体现)
5.shmctl函数删除共享内存(上例未体现)
另外,不得不提的是,共享内存本身提供同步机制,一般在多进程的共享内存使用过程中都会使用信号量实现同步操作。