0. 共享内存
-
比喻
火锅 -
本质
- 多个进程访问同一个逻辑内存
- 直接访问内存,不用
read()
/write()
非常方便
1. POSIX 共享内存
- 资料:
unpv22e-ch13
- 查看:
man shm_overview
ls /dev/shm
2. 分类
-
内存映射文件
内存映射文件
注意:共享内存大小 = 文件大小 共享内存区对象(非亲缘进程)
- 匿名内存映射(亲缘进程)
风格 | 方式 |
---|---|
BSD |
MAP_ANON +mmap()
|
Systerm V |
/dev/zero +open()
|
3 接口
- 头文件:
sys/mman.h
- 库:
librt.so
3.1 函数
POSIX 共享内存有几个函数。
No. | 操作 | 函数 |
---|---|---|
1 | 创建 | int shm_open(const char *name, int oflag, mode_t mode) |
2 | 删除 | int shm_unlink(const char *name) |
3 | 建立内存映射 | void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset) |
4 | 关闭内存映射 | int munmap(void *start,size_t length) |
3.1.1 创建
int shm_open(const char *name, int oflag, mode_t mode)
- 参数
No. | 参数 | 含义 |
---|---|---|
1 | name |
posix IPC名字,格式为/somename
|
2 | oflag |
标志 |
3 | mode |
权限 |
- 标志
No. | 标志 | 作用 |
---|---|---|
1 | O_CREAT |
没有该对象则创建 |
2 | O_EXCL |
如果O_CREAT指定,但name不存在,就返回错误 |
3 | O_NONBLOCK |
|
4 | O_RDONLY |
只读 |
5 | O_RDWR |
读写 |
6 | O_WRONLY |
|
7 | O_TRUNC |
若存在则截断 |
- 权限
No. | 权限 | 作用 |
---|---|---|
1 | S_IWUSR |
用户/属主写 |
2 | S_IRUSR |
用户/属主读 |
3 | S_IWGRP |
组成员写 |
4 | S_IRGRP |
组成员读 |
5 | S_IWOTH |
其他用户写 |
6 | S_IROTH |
其他用户读 |
- 返回值
No. | 返回值 | 含义 |
---|---|---|
1 | -1 |
出错 |
2 | 其他 | 共享内存描述符 |
- 示例
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = shm_open(argv[1],O_CREAT|O_RDWR,0644);
ftruncate(fd,atoi(argv[2]));
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
}
3.1.2 删除
int shm_unlink(const char *name)
- 参数
No. | 参数 | 含义 |
---|---|---|
1 | name |
posix IPC名字 |
- 返回值
No. | 返回值 | 含义 |
---|---|---|
1 | -1 |
出错 |
2 | 0 |
成功 |
- 示例
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
shm_unlink(argv[1]);
}
3.1.3 建立内存映射
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset)
- 参数
No. | 参数 | 含义 |
---|---|---|
1 | start |
映射区的开始地址,通常使用NULL ,让系统决定映射区的起始地址 |
2 | length |
映射区的长度,单位字节,不足一内存页按一内存页处理 |
3 | prot |
内存保护标志 |
4 | flags |
映射对象的类型 |
5 | fd |
文件描述符,不能是套接字和终端的fd ,-1 为匿名内存映射 |
6 | offset |
被映射对象内容的起点,只能是页大小的整数倍 |
如何获取页大小?
sysconf(_SC_PAGESIZE)
- 内存保护标志
No. | 参数 | 含义 |
---|---|---|
1 | PROT_EXEC |
页内容可以被执行 |
2 | PROT_READ |
页内容可以被读取 |
3 | PROT_WRITE |
页可以被写入 |
4 | PROT_NONE |
页不可访问,不能与文件的打开模式冲突 |
- 映射对象的类型
No. | 参数 | 含义 |
---|---|---|
1 | MAP_SHARED |
变动共享 |
2 | MAP_PRIVATE |
变动私有 |
3 | MAP_ANON |
匿名内存映射 |
- 返回值
No. | 返回值 | 含义 |
---|---|---|
1 | MAP_FAILED |
失败 |
2 | 非MAP_FAILED
|
共享内存地址 |
- 示例
写
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = shm_open(argv[1],O_RDWR,0);
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
strcpy(buf,argv[2]);
munmap(buf,BUFSIZ);
}
读
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = shm_open(argv[1],O_RDONLY,0);
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_READ,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
printf("%s\n",buf);
munmap(buf,BUFSIZ);
}
3.1.4 关闭内存映射
int munmap(void *start,size_t length)
- 参数
No. | 参数 | 含义 |
---|---|---|
1 | start |
映射内存起始地址 |
2 | length |
内存大小 |
- 返回值
No. | 返回值 | 含义 |
---|---|---|
1 | 0 |
成功 |
2 | -1 |
失败 |
- 注意
关闭mmap
中的文件描述符不能删除内存映射。
4 示例
4.1 内存映射文件
- 创建文件并且写入数据
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(){
int fd = open("./mmap.txt",O_CREAT|O_RDWR,0644);
char str[] = "hello mmap\n";
ftruncate(fd,sizeof(str));
void* buf = NULL;
if(( buf = mmap(NULL,sizeof(str),PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
strcpy(buf,str);
munmap(buf,sizeof(str));
close(fd);
}
问题
如果没有ftruncate(fd,sizeof(str));
会出现什么情况?
- 读取数据并且重新写入数据
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = open("./mmap.txt",O_RDWR);
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
printf("%s\n",buf);
strcpy(buf,"this sdfdsfdsfdsfdsfdsfdsfdsfdsfdsf\n");
munmap(buf,BUFSIZ);
close(fd);
}
- 亲缘进程读写数据
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = open("./mmap.txt",O_CREAT|O_RDWR,0644);
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
if(fork()){
strcpy(buf,argv[1]);
}else{
printf("%s\n",buf);
}
munmap(buf,BUFSIZ);
close(fd);
}
- 非亲缘进程读写数据
写
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = open("./mmap.txt",O_CREAT|O_RDWR,0644);
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
strcpy(buf,argv[1]);
munmap(buf,BUFSIZ);
close(fd);
}
读
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = open("./mmap.txt",O_CREAT|O_RDWR,0644);
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
printf("%s\n",buf);
munmap(buf,BUFSIZ);
close(fd);
}
4.2 共享内存区对象(非亲缘进程)
写
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = shm_open(argv[1],O_RDWR,0);
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
int i;
for(i=2;i<argc;i++){
strcpy(buf,argv[i]);
sleep(3);
}
munmap(buf,BUFSIZ);
close(fd);
}
读
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = shm_open(argv[1],O_RDONLY,0);
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_READ,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
sleep(1);
for(;;){
printf("read:%s\n",buf);
sleep(3);
}
munmap(buf,BUFSIZ);
close(fd);
}
4.3 匿名内存映射(亲缘进程)
- Systerm V风格
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = open("/dev/zero",O_RDWR);
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
if(fork()){
strcpy(buf,argv[1]);
}else{
printf("%s\n",buf);
}
munmap(buf,BUFSIZ);
close(fd);
}
- BSD风格
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char* argv[]){
void* buf = NULL;
if(( buf = mmap(NULL,BUFSIZ,PROT_WRITE|PROT_READ,MAP_SHARED|MAP_ANON,-1,0)) == MAP_FAILED){
perror("mmap error\n");
return 1;
}
if(fork()){
strcpy(buf,argv[1]);
}else{
printf("%s\n",buf);
}
munmap(buf,BUFSIZ);
}
比较
实验
使用gdb查看共享内存的建立映射和解除映射。
info proc mapping
5. 使用mmap容易出现的问题
- 现象:总线错误Bus Error
原因:映射文件的大小为0
解决:使用ftruncate()
扩展文件的大小,stat.st_size
。 - 现象:段错误
原因:munmap()
解除映射的大小大于申请内存大小
解决:解除映射大小与申请内存大小保持一直 - 现象:Permisstion denied
原因:文件打开权限与文件映射对象访问权限不一致。
解决:只读PROT_READ
的文件映射对象使用O_RDONLY
打开文件;读写PROT_READ|PROT_WRITE
的文件映射对象使用O_RDWR
打开文件