4 IPC共享内存
共享内存(Shared Memory),是指两个或多个进程共享一个给定的存储区。在所有的IPC中,共享内存是用的比较多的IPC方式。
4.1 POSIX共享内存
- 本质
多个进程访问同一个逻辑内存
直接访问内存,不用read()/write()非常方便 - 查看
1、man shm_overview
2、ls /dev/shm
4.2 分类
1、内存映射文件
注:共享内存大小 = 文件大小
2、共享内存区对象(非亲缘进程)
3、匿名内存映射(亲缘进程)
风格 | 方式 |
---|---|
BSD | MAP_ANON+mmap() |
Systerm V | /dev/zero+open() |
4.3 接口
头文件:sys/mman.h
库:librt.so
POSIX 共享内存的几个函数。
操作 | 函数 |
---|---|
创建 | 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) |
4.3.1 创建
int shm_open(const char *name, int oflag, mode_t mode)
-参数
参数 | 含义 |
---|---|
name | posix IPC名字,格式为/somename |
oflag | 标志 |
mode | 权限 |
- 标志
标志 | 作用 |
---|---|
O_CREAT | 没有该对象则创建 |
O_EXCL | 如果O_CREAT 指定,但name 不存在,就返回错误 |
O_NONBLOCK | 以非阻塞方式打开消息队列 |
O_RDONLY | 只读 |
O_RDWR | 读写 |
O_WRONLY | 只写 |
O_TRUNC | 若存在则截断 |
- 权限
权限 | 作用 |
---|---|
S_IWUSR | 用户/属主写 |
S_IRUSR | 用户/属主读 |
S_IWGRP | 组成员写 |
S_IRGRP | 组成员读 |
S_IWOTH | 其他用户写 |
S_IROTH | 其他用户读 |
- 返回值
返回值 | 含义 |
---|---|
-1 | 出错 |
其他 | 共享内存描述符 |
### 4.3.2 删除
```cpp
int shm_unlink(const char *name)
- 参数
name
: posix IPC名字 - 返回值
返回值 | 含义 |
---|---|
-1 | 出错 |
0 | 成功 |
4.3.3 建立内存映射
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset)
- 参数
参数 | 含义 |
---|---|
start | 映射区的开始地址,通常使用NULL,让系统决定映射区的起始地址 |
length | 映射区的长度,单位字节,不足一内存页按一内存页处理 |
prot | 内存保护标志 |
flags | 映射对象的类型 |
fd | 文件描述符,不能是套接字和终端的fd,-1为匿名内存映射 |
offset | 被映射对象内容的起点,只能是页大小的整数倍 |
- 内存保护标志
参数 | 含义 |
---|---|
PROT_EXEC | 页内容可以被执行 |
PROT_READ | 页内容可以被读取 |
PROT_WRITE | 页可以被写入 |
PROT_NONE | 页不可访问,不能与文件的打开模式冲突 |
- 映射对象的类型
参数 | 含义 |
---|---|
MAP_SHARED | 变动共享 |
MAP_PRIVATE | 变动私有 |
MAP_ANON | 匿名内存映射 |
- 返回值
返回值 | 含义 |
---|---|
MAP_FAILED | 失败 |
非MAP_FAILED | 共享内存地址 |
4.3.4 关闭映射内存
int munmap(void *start,size_t length)
- 参数
参数 | 含义 |
---|---|
start | 映射内存起始地址 |
length | 内存大小 |
- 返回值
返回值 | 含义 |
---|---|
0 | 成功 |
-1 | 失败 |
注:关闭mmap
中的文件描述符不能删除内存映射。
4.4 实例
4.4.1 内存映射文件
#include <iostream>
#include <sys/shm.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main(int argc,char* argv[]){
int fd = open(argv[1],O_RDONLY);
if(-1 == fd){
perror("shm_open error");
}
void* p = mmap(NULL,1024,PROT_READ,MAP_PRIVATE,fd,0);
if(MAP_FAILED == p){
perror("mmap error");
return 1;
}
cout << (char*)p << endl;
munmap(p,1024);
p = NULL;
close(fd);
}
执行 ./a.out ./已有文件名
完成文件的简单映射
[root@localhost 4]# ./a.out ./file_map.cpp
#include <iostream>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
using namespace std;
int main(int argc,char* argv[]){
int fd = open(argv[1],O_RDONLY);
if(-1 == fd){
perror("shm_open error");
return 1;
}
void* p = mmap(NULL,1024,PROT_READ,MAP_PRIVATE,fd,0);
if(MAP_FAILED == p){
perror("mmap error");
return 1;
}
cout << (char*)p << endl;
munmap(p,1024);
close(fd);
}
[root@localhost 4]#
4.4.2 共享内存区域(非亲缘过程)
普通方式读写文件 vs. 内存映射方式读写文件
(1)普通方式读写文件
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
using namespace std;
int main(int argc,char* argv[]){
int fd = open(argv[1],O_RDWR);
if(-1 == fd){
perror("open error");
return 1;
}
char buffer[1024] = {0};
read(fd,buffer,sizeof(buffer));
cout << buffer << endl;
string s;
cin >> s;
strcat(buffer,s.c_str());
lseek(fd,0,SEEK_SET);
write(fd,buffer,sizeof(buffer));
close(fd);
}
[root@localhost 4]# g++ append_file.cpp -lrt
[root@localhost 4]# cat > text.txt
1234
^C
[root@localhost 4]# cat text.txt
1234
[root@localhost 4]# ./a.out ./text.txt
1234
abcd
[root@localhost 4]# cat text.txt
1234
abcd[root@localhost 4]#
(2)内存映射方式读写文件(同一进程既读又写)
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <sys/mman.h>
using namespace std;
int main(int argc,char* argv[]){
int fd = open(argv[1],O_RDWR);
if(-1 == fd){
perror("open error");
return 1;
}
char* p = (char*)mmap(NULL,1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(MAP_FAILED == p){
perror("mmap error");
return 1;
}
cout << p << endl;
string s;
cin >> s;
strcat(p,s.c_str());
munmap(p,1024);
p = NULL;
close(fd);
}
[root@localhost 4]# ./a.out ./text.txt
1234
abcd
!@#$
[root@localhost 4]# cat ./text.txt
1234
abcd!@#$[root@localhost 4]#
(3) 内存映射方式读写文件(两个进程分别进行读和写)
write.cpp
#include <iostream>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <fcntl.h>
using namespace std;
int main(int argc,char* argv[]){
if(2 != argc){
cout << "arguement error" << endl;
cout << "Usage: " << argv[0] << "shm file" << endl;
return 1;
}
int fd = shm_open(argv[1],O_RDWR|O_CREAT,0666);
if(-1 == fd){
perror("open error");
return 1;
}
ftruncate(fd,sizeof(int));
int* p = (int*)mmap(NULL,sizeof(int),PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
if(MAP_FAILED == p){
perror("map error");
return 1;
}
cin >> *p;
munmap(p,sizeof(int));
p = NULL;
}
read.cpp
#include <iostream>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <fcntl.h>
using namespace std;
int main(int argc,char* argv[]){
if(2 != argc){
cout << "arguement error" << endl;
cout << "Usage: " << argv[0] << "shm file" << endl;
return 1;
}
int fd = shm_open(argv[1],O_RDWR|O_CREAT,0666);
if(-1 == fd){
perror("open error");
return 1;
}
int* p = (int*)mmap(NULL,sizeof(int),PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
if(MAP_FAILED == p){
perror("map error");
return 1;
}
cout << "*p:" << *p << endl;
munmap(p,sizeof(int));
p = NULL;
}
步骤:
g++ writer.cpp -lrt -o write
g++ reader.cpp -lrt -o read
./write /abc
输入数据
./read /abc
结果:
[root@localhost RD_WR]# g++ read.cpp -lrt -o read
[root@localhost RD_WR]# ./write /abc
16
[root@localhost RD_WR]# ./read /abc
*p:16
[root@localhost RD_WR]#
ll /dev/shm/
可以看到创建的共享内存/abc
od -A x -t x1z -v /dev/shm/abc
可以查看存储的内容
[root@localhost RD_WR]# ll /dev/shm/
total 4
-rw-r--r--. 1 root root 4 May 26 03:11 abc
[root@localhost RD_WR]# od -A x -t x1z -v /dev/shm/abc
000000 10 00 00 00 >....<
000004
从结果来看,存储的二进制数10 00 00 00 为16。
注:
1、普通方式读写文件的read
和write
较为耗时,一般采用内存映射的方式进行读写
2、当多个进程同时访问同一共享内存时,可能因为没有阻塞而出现错误(即出现数据竞争的时候),这就需要加入信号量。
4.4.3 匿名内存映射(亲缘过程)
1、Systerm V风格
#include <iostream>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdio>
#include <fcntl.h>
using namespace std;
//共享内存使得父子进程访问同一地址
int main(){
int fd = open("/dev/zero",O_RDWR);
if(-1 == fd){
perror("open error");
return 1;
}
int* p = (int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(MAP_FAILED == 0){
perror("mmap error");
return 1;
}
*p = 0;
if(fork()){
for(int i = 0;i < 10;++i){
cout << "parent:" << p << ":" << ++*p << endl;
}
wait(NULL);
}else{
for(int i = 0;i < 10;++i){
cout << "child:" << p << ":" << --*p << endl;
}
}
munmap(p,sizeof(int));
p = NULL;
}
[root@localhost 4]# ./a.out
parent:0x7fba04c21000:1
parent:0x7fba04c21000:2
parent:0x7fba04c21000:3
parent:0x7fba04c21000:4
parent:0x7fba04c21000:5
parent:0x7fba04c21000:6
parent:0x7fba04c21000:7
parent:0x7fba04c21000:8
parent:0x7fba04c21000:9
parent:0x7fba04c21000:10
child:0x7fba04c21000:9
child:0x7fba04c21000:8
child:0x7fba04c21000:7
child:0x7fba04c21000:6
child:0x7fba04c21000:5
child:0x7fba04c21000:4
child:0x7fba04c21000:3
child:0x7fba04c21000:2
child:0x7fba04c21000:1
child:0x7fba04c21000:0
2、BSD风格
#include <iostream>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdio>
#include <fcntl.h>
using namespace std;
//共享内存使得父子进程访问同一地址
int main(){
int* p = (int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
// -1表示无效的文件描述
if(MAP_FAILED == 0){
perror("mmap error");
return 1;
}
*p = 0;
if(fork()){
for(int i = 0;i < 10;++i){
cout << "parent:" << p << ":" << ++*p << endl;
}
wait(NULL);
}else{
for(int i = 0;i < 10;++i){
cout << "child:" << p << ":" << --*p << endl;
}
}
munmap(p,sizeof(int));
p = NULL;
}
[root@localhost 4]# ./a.out
parent:0x7ff14cd6c000:1
parent:0x7ff14cd6c000:2
parent:0x7ff14cd6c000:3
parent:0x7ff14cd6c000:4
parent:0x7ff14cd6c000:5
parent:0x7ff14cd6c000:6
parent:0x7ff14cd6c000:7
parent:0x7ff14cd6c000:8
parent:0x7ff14cd6c000:9
parent:0x7ff14cd6c000:10
child:0x7ff14cd6c000:9
child:0x7ff14cd6c000:8
child:0x7ff14cd6c000:7
child:0x7ff14cd6c000:6
child:0x7ff14cd6c000:5
child:0x7ff14cd6c000:4
child:0x7ff14cd6c000:3
child:0x7ff14cd6c000:2
child:0x7ff14cd6c000:1
child:0x7ff14cd6c000:0
4.4.4 共享内存风格对比
4.5 使用mmap
容易出现的问题
- 现象:总线错误Bus Error
原因:映射文件的大小为0
解决:使用ftruncate()
扩展文件的大小,stat.st_size
。 - 现象:段错误
原因:munmap()
解除映射的大小大于申请内存大小
解决:解除映射大小与申请内存大小保持一致 - 现象:Permisstion denied
原因:文件打开权限与文件映射对象访问权限不一致。
解决:只读PROT_READ
的文件映射对象使用O_RDONLY
打开文件;读写PROT_READ|PROT_WRITE
的文件映射对象使用O_RDWR
打开文件