0. 共享内存
-
比喻
火锅
-
本质
- 多个进程访问同一个逻辑内存
- 直接访问内存,不用
read()
/write()
非常方便
1. POSIX 共享内存
- 资料:
unpv22e-ch13
- 查看:
man shm_overview
ls /dev/shm
1.1 分类
- 内存映射文件
内存映射文件
注意:共享内存大小 = 文件大小
- 匿名内存映射(亲缘进程)
风格 |
方式 |
BSD |
MAP_ANON +mmap()
|
Systerm V |
/dev/zero +open()
|
- 共享内存区对象(非亲缘进程)
1.2 接口
1.3 函数
POSIX 共享内存有5个函数。
No. |
操作 |
函数 |
1 |
创建 |
int shm_open(const char *name, int oflag, mode_t mode) |
2 |
删除 |
int shm_unlink(const char *name) |
5 |
建立内存映射 |
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset) |
6 |
关闭内存映射 |
int munmap(void *start,size_t length) |
此外,在使用内存映射文件,经常要用到以下两个文件操作函数。
No. |
操作 |
函数 |
1 |
获取文件信息 |
int fstat(int fd,struct stat *buf) |
2 |
修改文件大小 |
int ftruncate(int fd, off_t length) |
① 获取文件信息
int fstat(int fd,struct stat *buf)
No. |
参数 |
含义 |
1 |
fd |
文件描述描述符 |
2 |
buf |
struct stat |
No. |
参数 |
含义 |
1 |
st_mode |
权限 |
2 |
st_size |
大小 |
3 |
st_uid |
属主ID |
4 |
st_guid |
组ID |
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
-
实例
获取共享内存大小
stat(fd,&stat)
stat.st_size
-
示例
编写一个命令行程序,获取文件的大小。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = open(argv[1],O_RDONLY,0644);
struct stat file_stat;
stat(fd,&file_stat);
printf("file :%s size: %d",argv[1],file_stat.st_size);
return 0;
}
② 修改文件大小
int ftruncate(int fd, off_t length)
No. |
参数 |
含义 |
1 |
fd |
文件描述描述符 |
2 |
length |
文件大小,如果原来的文件大小比参数length大,超过的部分删除。 |
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
ftruncate(fd,len)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd = open(argv[1],O_RDWR,0644);
struct stat file_stat;
fstat(fd,&file_stat);
printf("file :%s\told size: %d",argv[1],file_stat.st_size);
int new_size = atoi(argv[2]);
ftruncate(fd,new_size);
stat(fd,&file_stat);
printf("\tnew size: %d\n",file_stat.st_size);
return 0;
}
问题
- 文件扩大后,新增文件内容是什么?
- 文件缩小后,文件内容是否会被截取?
- 文件大小是否可以变为0?
1.3.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;
}
}
1.3.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]);
}
1.3.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 |
off_toffset |
被映射对象内容的起点 |
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);
}
1.3.4 关闭内存映射
int munmap(void *start,size_t length)
No. |
参数 |
含义 |
1 |
start |
映射内存起始地址 |
2 |
length |
内存大小 |
No. |
返回值 |
含义 |
1 |
0 |
成功 |
2 |
-1 |
失败 |
- 注意
关闭mmap
中的文件描述符不能删除内存映射。
1.4 示例
1.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);
}
1.4.2 匿名内存映射(亲缘进程)
- 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);
}
1.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);
}
3. 使用mmap容易出现的问题
- 现象:总线错误Bus Error
原因:映射文件的大小为0
解决:使用ftruncate()
扩展文件的大小,stat.st_size
。
- 现象:段错误
原因:munmap()
释放内存的大小大于申请内存大小
解决:释放内存大小与申请内存大小保持一直
- 现象:Permisstion denied
原因:文件打开权限与文件映射对象访问权限不一致。
解决:只读PROT_READ
的文件映射对象使用O_RDONLY
打开文件;读写PROT_READ|PROT_WRITE
的文件映射对象使用O_RDWR
打开文件
4. 练习
练习:写一个共享内存类
网友评论