以上我们了解了共享内存的基本的函数用法,但是都不是共享内存的主要功能。接下来我们来学习如何使用共享内存进行进程间通信。
使用共享内存进行进程间通信
我们首先简单地创建两个进程,了解一下多线程工作代码如下:
using namespace std;
int main(){
int n = 0;
if(fork() == 0)
{
// 子进程
for(int i=0; i<10; i++)
cout << getpid() << ":" << ++n << endl;
}
else
{
// 父进程
for(int i=0; i<10; i++)
cout << getpid() << ":" << --n << endl;
}
}
这里用fork()函数在执行父进程的时候创建了子进程,然后再有了子进程。
运行结果如下:
我们可以看出来这两个进程独自运行,互不干扰。
接下来,我们创建一个共享内容,让该父子进程同时访问该内存上的内容。
创建步骤:
打开/dev/zero文件,这是一个用作共享内存的特殊文件;
使用mmap函数进行映射,创建共享内存,在这个实验中我们只需要一个初值为0的整数,所以我们申请了一个int型整数的内存大小;
申请好了内存,接下来我们如何将其作为一个变量来使用呢?借助&来为这块内存上的内容起别名就如下文代码中一样,或者直接对buff进行类型转化为int*型的内存地址,对其进行修改;
让两个进程在同一个内存上进行操作,输出操作结果;
结束映射;
代码如下:
using namespace std;
// 共享内存:让父子进程共享内存
int main()
{
// /dev/zero是创建共享内存的一个特殊文件,直接Read,不用creta
int fd = open("/dev/zero",O_RDWR);
// 判断文件是否打开成功
if(-1 == fd){
perror("open error");
return 1;
}
// 文件映射到内存(只映射一个int)
void* buff = mmap(NULL,sizeof(int),PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
if(MAP_FAILED == buff){ // 判断文件是否映射成功
perror("mmap error");
return 1;
}
// 取文件中的buff 到变量中
int &n = *static_cast<int*>(buff);
if(fork() == 0) // 子进程
{
for(int i=0; i<10; i++)
cout << getpid() << ":" << ++n << endl;
}
else // 父进程
{
for(int i=0; i<10; i++)
cout << getpid() << ":" << --n << endl;
}
// 关闭映射
munmap(buff, sizeof(int));
// 共享内存清空
buff = NULL;
// 关闭文件
close(fd);
}
运行结果如下:
从运行结果我们就可以看出父子进程是在同一个内存上进行的。
除了以上这种创建共享内存之外,我们还可以使用另外一种风格,这两种方法效果相同,主要区别是不需要打开文件,直接创建一个匿名共享内存,创建方法相对简单,但是这种匿名共享内存只能在亲缘进程中使用(即进程之间是相互创建出来的)。
申请匿名的共享内存和普通的创建共享内存的一个很大的区别在于不需要打开一个文件进行映射获取内存地址,匿名的共享内存直接在映射时获取,和普通mmap的参数不同的是映射对象类型的不同,它的类型为MAP_SHARED|MAP_ANON。
具体代码如下:
using namespace std;
int main(){
// 申请匿名共享内存
void* buff = mmap(NULL,sizeof(int),PROT_WRITE|PROT_READ,MAP_SHARED|MAP_ANON,-1,0);
// 判断文件是否映射成功
if(MAP_FAILED == buff){
perror("mmap error");
return 1;
}
int &n = *static_cast<int*>(buff);
// 父子进程使用共享内存
if(fork() == 0)
{
for(int i=0; i<10; i++)
cout << getpid() << ":" << ++n << endl;
}else{
for(int i=0; i<10; i++)
cout << getpid() << ":" << --n << endl;
}
// 关闭映射
munmap(buff, sizeof(int));
// 共享内存清空
buff = NULL;
}
执行结果如下:
创建可命名的共享内存
我们知道匿名的共享内存只能父子进程才能使用,那么非亲缘进程该怎么使用共享内存呢?这一部分我们就需要写一个又名的共享内存,使用这个命名的共享内存进行通信。
这就用到了我们开头提及的shm_open函数,在这一节我们会写三个cpp文件,分别用于:
创建共享内存
向共享内存中写数据
读出共享内存中的数据
具体如下:
create_shared_memory.cpp:创建共享内存
这一部分的主要函数是shm_open,可以使用它打开一个共享内存。
代码内容如下:
/* 创建共享内存 */
using namespace std;
int main(int argc, char* argv[]){
// 确保输入正确
if(3 != argc){
printf("Usage:%s name size\n",argv[0]);
return 1;
}
// 创建&打开一个共享内存
int fd = shm_open(argv[1],O_CREAT|O_RDWR,0666);
if(-1 == fd){
perror("shm_open error");
return 1;
}
// 按输入的第三个参数为大小对内存进行扩充
ftruncate(fd,stoi(argv[2]));
// 关闭内存使用权
close(fd);
}
然后我们在终端链接库执行 g++ create_shared_memory.cpp -lrt 进行编译.
然后再做以下命令,创建一个大小为100的名为shm_aaa的共享内存
我们发现该文件创建后默认在dev/shm文件夹下。
shm_write.cpp:使用共享内存写数据
这里我们还是直接使用shm_open来获取共享内存的文件,但是需要注意的是,在创建的时候我们使用的是标志参数为创建O_CREAT和读写O_RDWR,而在读取的时候使用的标志为O_RDWR。
文件内容如下:
using namespace std;
int main(int argc, char* argv[]){
// 根据共享内存文件的名称打开映射
int fd = shm_open(argv[1], O_RDWR, 0);
if(-1 == fd){
perror("shm_open error");
return 1;
}
// 映射
void* buff = mmap(NULL, sizeof(int), PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
if(MAP_FAILED == buff){
perror("mmap error");
return 1;
}
// 输入一个数据到共享内存
cin >> *(int*)buff;
// 关闭映射
munmap(buff, sizeof(int));
// 关闭文件
close(fd);
}
我们在终端执行如下命令,向共享内存写入了数据——100:
需要注意的是,执行命令./shm_write /shm_aaa中的/shm_aaa并不是指根目录下的shm_aaa文件,而是指使用shm_open创建的文件的默认路径下。
打开的就是之前创建的共享内存文件shm_aaa
shm_read.cpp:使用共享内存读数据
在读数据时,我们使用shm_open函数打开共享内存的时候,其中的标志参数为O_RDONLY
代码如下:
using namespace std;
int main(int argc, char* argv[]){
// 打开共享内存文件
int fd = shm_open(argv[1], O_RDONLY, 0);
if(-1 == fd){
perror("shm_open error");
return 1;
}
// 映射
void* buff = mmap(NULL, sizeof(int), PROT_READ, MAP_SHARED, fd, 0);
if(MAP_FAILED == buff){
perror("mmap error");
return 1;
}
// 读取共享内存中的内容
cout << *(int*)buff << endl;
// 关闭共享内存
munmap(buff, sizeof(int));
// 关闭共享内存文件
close(fd);
}
终端执行命令如下:
可以发现程序成功读取了共享内存文件名为shm_aaa中的数据。