目录
MMP共享映射的原理
把文件中的某一段,做个映射到mmap中(在内存中开辟的一块儿内存区),将文件中的某一块儿截取出来给映射到内存中去,这样的话,直接操作内存就可以使用了,这样比文件操作就快了很多。
创建映射区函数
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
- addr 传NULL
- length 映射区的长度
- prot
- PROT_READ 可读
- PROT_WRITE 可写
- flags
- MAP_SHARED 共享的,对内存的修改会影响到源文件
- MAP_PRIVATE 私有的
- fd 文件描述符,open打开一个文件
- offset 偏移量
- 返回值
- 成功 返回 可用的内存首地址
- 失败 返回 MAP_FAILED
释放映射区函数
int munmap(void *addr, size_t length);
- addr 传mmap的返回值
- length mmap创建的长度
- 返回值
示例说明
先在路径下面创建一个mem.txt,来作为一个接受数据的文件。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
int main()
{
int fd = open("mem.txt",O_RDWR);//创建并且截断文件
//int fd = open("mem.txt",O_RDWR|O_CREAT|O_TRUNC,0664);//创建并且截断文件
ftruncate(fd,8);
//创建映射区
char *mem = mmap(NULL,20,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); //这就是最头疼的填参数部分,相当于得到一块儿内存
//char *mem = mmap(NULL,8,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
if(mem == MAP_FAILED){
perror("mmap err");
return -1;
}
close(fd);
//拷贝数据
strcpy(mem,"helloworld");
// mem++;
//释放mmap
if(munmap(mem,20) < 0){
perror("munmap err");
}
return 0;
}
总体流程是,从进程中将数据映射到映射区,再从映射区拷贝到mem.txt中。
mmap九问
- 如果更改mem变量的地址,释放的时候munmap,传入mem还能成功吗?不能!!要不然释放的时候释放不了
- 如果对mem越界操作会怎么样?文件的大小对映射区操作有影响,尽量避免。
- 如果文件偏移量随便填个数会怎么样? offset必须是 4k的整数倍
- 如果文件描述符先关闭,对mmap映射有没有影响?没有影响
- open的时候,可以新创建一个文件来创建映射区吗?不可以用大小为0的文件,即不能用新创建的,没扩展的文件。
- open文件选择O_WRONLY,(如:int fd = open("mem.txt",O_WRONLY))可以吗? 不可以: Permission denied,这是当把文件映射到缓冲器的时候i,这个时候,包含了一次读的操作,必须有读权限才行。
- 当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PROT_WRITE吗?Permission denied ,SHARED的时候,必须要保证:映射区的权限 <= open文件的权限
- mmap什么情况下会报错?很多情况
- 如果不判断返回值会怎么样? 会死的很难堪!!
mmap实现父子进程通信
整个过程,就是将父子进程的内容都可以拿到映射区去处理,其中一个进行修改,另一个去读的时候有变化,顺序方面,应该先创建映射区,然后再进行fork创建子进程。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
int main()
{
// 先创建映射区
int fd = open("mem.txt",O_RDWR);
//int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
if(mem == MAP_FAILED){
perror("mmap err");
return -1;
}
// fork子进程
pid_t pid = fork();
// 父进程和子进程交替修改数据
if(pid == 0 ){
//son
*mem = 100;
printf("child,*mem = %d\n",*mem);
sleep(3);
printf("child,*mem = %d\n",*mem);
}
else if(pid > 0){
//parent
sleep(1);
printf("parent,*mem=%d\n",*mem);
*mem = 1001;
printf("parent,*mem=%d\n",*mem);
wait(NULL);
}
munmap(mem,4); //最后记得回收缓冲区
close(fd);
return 0;
}
父子进程都可以给缓冲区存数据。运行结果如下:
在写这段代码中的时候,在父子进程通信的过程中,父进程修改一个数值,然后,再传给子进程。可以发现"mem,txt"这个文件,作为一个中间载体,需要新创建一个文件,是非常鸡肋的,就是打开存了个数据,然后,又取出来了,为了弥补这个缺陷,就有了匿名映射的概念。
匿名映射
通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。 可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。可以通过使用MAP_ANONYMOUS (或MAP_ANON)来完成。
int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
下面是例子:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
int main()
{
int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
if(mem == MAP_FAILED){
perror("mmap err");
return -1;
}
pid_t pid = fork();
if(pid == 0 ){
//son
*mem = 101;
printf("child,*mem=%d\n",*mem);
sleep(3);
printf("child,*mem=%d\n",*mem);
}else if(pid > 0){
//parent
sleep(1);
printf("parent,*mem=%d\n",*mem);
*mem = 10001;
printf("parent,*mem=%d\n",*mem);
wait(NULL);
}
munmap(mem,4);
return 0;
}
其实,这个逻辑跟上个代码的逻辑一模一样,只不过是通过匿名的方式,没有再新创建一个文件而已。
了解:因为MAP_ANON,ANONYMOUS 这两个宏在有些unix系统没有,但可以使用/dev/zero文件中打开
-
/dev/zero 聚宝盆 ,可以随意映射
-
/dev/null 无底洞 ,一般错误信息重定向到这个文件中
用mmap支持无血缘关系进程通信
读过程,往结构体里面读入数据:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
typedef struct _Student{
int sid;
char sname[20];
}Student;
int main(int argc,char *argv[])
{
//open file
int fd = open(argv[1],O_RDWR);
//mmap
int length = sizeof(Student);
Student *stu = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(stu == MAP_FAILED){
perror("mmap err");
return -1;
}
//read data
while(1){
printf("sid=%d,sname=%s\n",stu->sid,stu->sname);
sleep(1);
}
//close and munmap
munmap(stu,length);
close(fd);
return 0;
}
写操作:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
typedef struct _Student{
int sid;
char sname[20];
}Student;
int main(int argc,char *argv[])
{
if(argc != 2){
printf("./a.out filename\n");
return -1;
}
// 1. open file
int fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666);
int length = sizeof(Student);
ftruncate(fd,length);
// 2. mmap
Student * stu = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(stu == MAP_FAILED){
perror("mmap err");
return -1;
}
int num = 1;
// 3. 修改内存数据
while(1){
stu->sid = num;
sprintf(stu->sname,"xiaoming-%03d",num++);
sleep(1);//相当于没隔1s修改一次映射区的内容
}
// 4. 释放映射区和关闭文件描述符
munmap(stu,length);
close(fd);
return 0;
}
关于"mmap_MAP_SHARED"的强调
如果进程之间有通信的话,必须是有共享的,即用“mmap_MAP_SHARED”,如果进程要通信,flags 必须设为 MAP_SHARED
int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); //通信的时候
//int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0); //不通信的时候