文章目录
共享内存基本概念
共享内存是目前比较主流的进程之间互相通信的方式,这种通信中读取数据的对象和写入数据的对象不一定是同一个,写入的所有数据放在一块共享的内存中,这样方式的通信十分方便快捷。但是这样就会有一些数据的同步问题,因为在这个内存上的读取时没有阻塞的,每个人都不知道自己读取的数据是不是最新的自己需要的数据,这也是我们在使用共享内存进行通信的时候需要注意的问题。
共享内存可以将将一个文件映射到一块内存上;这种不同于以往读取文件内容的方式,我们使用管道或者其他方式读写文件内容时,往往需要打开两次文件,一次读取文件,将文件内容读入一个buff
中,然后在buff中进行修改,再将buff
中的内容重新写回去。而共享内存可以直接在内存上进行修改最终映射回去,它相比而言效率更高。
内存映射文件
相关函数
函数功能 | 函数格式 |
---|---|
创建共享内存 | int shm_open(const char *name, int oflag, mode_t mode) |
取消内存共享 | int shm_unlink(const char *name) |
建立内存映射 | void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset) |
关闭内存映射 | int munmap(void *start,size_t length) |
利用文件映射的方式读文件内容
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <cstring>
using namespace std;
int main(int argc, char** argv){
if(3!=argc){
printf("Usage:%s filepath size\n",argv[0]);
return 1;
}
int fd = open(argv[1],O_RDONLY);
void* buff = mmap(NULL,stoi(argv[2]),PROT_READ,MAP_SHARED,fd,0); // 进行映射
if(buff == MAP_FAILED){ // 测试文件是否映射失败
perror("mmap error");
return 1;
}
cout << static_cast<char*>(buff) << endl; // 显示被映射的文件内容
munmap(buff,stoi(argv[2])); // 将文件映射回去
close(fd);
}
利用文件映射的方式写文件内容
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <cstring>
using namespace std;
// ./a.out filepath size
int main(int argc, char** argv){
if(3!=argc){
printf("Usage:%s filepath size\n",argv[0]);
return 1;
}
int fd = open(argv[1],O_RDWR);
void* buff = mmap(NULL,stoi(argv[2]),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(buff == NULL){
perror("mmap error");
return 1;
}
cout << static_cast<char*>(buff) << endl;
int pos;
string s;
cin >> pos >> s;
strcpy(static_cast<char*>(buff)+pos,s.c_str()); // 对映射的内存内容的pos位置增添内容s
munmap(buff,stoi(argv[2]));
close(fd);
}
利用共享内存直接创建文件
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <cstring>
using namespace std;
// ./a.out filepath size
int main(int argc, char** argv){
if(3!=argc){
printf("Usage:%s filepath size\n",argv[0]);
return 1;
}
int fd = open(argv[1],O_RDWR|O_CREAT,0666); // 如果文件存在就打开该文件,如果文件不存在就创建一个权限为0666的文件
ftruncate(fd,stoi(argv[2])); // 因为我们直接使用open函数创建的文件大小为0,所以在对该文件进行映射前需要使用该函数对文件大小进行扩充。
void* buff = mmap(NULL,stoi(argv[2]),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(buff == NULL){
perror("mmap error");
return 1;
}
string s;
cin >> s;
strcpy(static_cast<char*>(buff),s.c_str()); // 对映射的内存内容增添内容s
munmap(buff,stoi(argv[2]));
close(fd);
}
使用共享内存进行进程间通信
我们首先创建两个进程,工作代码如下:
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
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;
}
}
运行结果如下:
接下来,我们创建一个共享内容,让该父子进程同时访问该内存上的内容。
代码如下:
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
using namespace std;
// 共享内存:让父子进程共享内存
int main(){
int fd = open("/dev/zero",O_RDWR); // /dev/zero创建共享内存的一个特
殊文件
if(-1 == fd){ // 判断文件是否打开成功
perror("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;
}
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);
}
运行结果如下:
除了以上这种创建共享内存之外,我们还可以使用另外一种风格,这两种方法效果相同,主要区别是不需要打开文件,直接创建一个匿名共享内存,创建方法相对简单,但是这种匿名共享内存只能在亲缘进程中使用。具体代码如下:
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
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
函数,在这一节我们会写三个文档,分别用于创建共享内存、向共享内存中写数据以及读出共享内存中的数据。具体如下:
该文件内容如下:
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
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
运行程序,然后再做以下命令:
我们发现该文件创建后默认在dev/shm
文件夹下。
文件内容如下:
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
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);
}
我们在终端执行如下命令:
需要注意的是,执行命令
./a.out /abc
中的/abc
并不是指根目录下的abc文件,而是指使用shm_open
创建的文件的默认路径下。
文件内容如下:
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
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);
}
终端执行命令如下: