参考链接:
https://blog.csdn.net/qq_44361695/article/details/99075993
https://segmentfault.com/a/1190000014616732
http://www.cppblog.com/jerryma/archive/2011/08/02/152279.html
1. 内存共享映射的概念
mmap的作用就是把磁盘文件的一部分直接映射到进程的内存中,那么进程就可以直接对该内存文件进行操作。
mmap也设置了两种机制:共享(share)和私有(private)。
如果是共享映射,那么在内存中对文件进行修改,磁盘中对应的文件也会被修改,相反,磁盘中的文件有了修改,内存中的文件也被修改。如果是私有映射,那么内存中的文件是独立的,二者进行修改都不会对对方造成影响。通过这样的内存共享映射就相当于是进程直接对磁盘中的文件进行读写操作一样,那么如果有两个进程来mmap同一个文件,就实现了进程间的通信。
下面是一个把普通文件映射到用户空间的内存区域示意图。
2. mmap和munmap函数
磁盘中的文件通过mmap函数来实现映射,然后通过munmap函数取消映射。先来看一下函数的原型:
1. #include <sys/mman.h> 2. void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 3. int munmap(void *start, size_t length);
mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占掉你的 virutal memory), 然后你就可以用memcpy等操作写文件, 而不用write()了.写完后,内存中的内容并不会立即更新到文件中,而是有一段时间的延迟,你可以调用msync()来显式同步一下, 这样你所写的内容就能立即保存到文件里了.这点应该和驱动相关。 不过通过mmap来写文件这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了.如果想取消内存映射,可以调用munmap()来取消内存映射。
mmap用于把文件映射到内存空间中,简单说mmap就是把一个文件的内容在内存里面做一个映像。映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。
mmap函数参数的说明:
Start |
指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。 |
length |
代表将文件中多大的部分映射到内存。 |
prot |
映射区域的保护方式。可以为以下几种方式的组合: PROT_EXEC 映射区域可被执行 PROT_READ 映射区域可被读取 PROT_WRITE 映射区域可被写入 PROT_NONE 映射区域不能存取 |
flags |
影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。 MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。 MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。 MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。 MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。 MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。 MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。 |
fd |
要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。 |
offset |
文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。 |
返回值 | 若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。 错误代码: EBADF 参数fd 不是有效的文件描述词 EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。 EINVAL 参数start、length 或offset有一个不合法。 EAGAIN 文件被锁住,或是有太多内存被锁住。 ENOMEM 内存不足。 |
系统调用mmap()用于共享内存的两种方式:
(1)使用普通文件提供的内存映射:
适用于任何进程之间。此时,需要打开或创建一个文件,然后再调用mmap()。典型调用代码如下:
1. fd=open(name, flag, mode); if(fd<0) ... 2. 3. ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,可以参看UNIX网络编程第二卷。
(2)使用特殊文件提供匿名内存映射:
适用于具有亲缘关系的进程之间。由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用 fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区 域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。
3. 通过代码理解mmap
3.1 code1,通过mmap以shared的方式映射,实现修改映射文件,查看本地文件是否修改
先在本地创建一个hello文件,并写上helloworld
1. #include <stdio.h> 2. #include <unistd.h> 3. #include <sys/stat.h> 4. #include <sys/types.h> 5. #include <fcntl.h> 6. #include <sys/mman.h> 7. #include <stdlib.h> 8. 9. int main(void) 10. { 11. int fd, len; 12. int *p; 13. fd = open("hello", O_RDWR); 14. if(fd < 0) 15. { 16. perror("open"); 17. exit(1); 18. } 19. len = lseek(fd, 0, SEEK_END); 20. 21. p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 22. if(p == MAP_FAILED) 23. { 24. perror("mmap"); 25. exit(1); 26. } 27. close(fd); 28. p[0] = 0x30313233; 29. 30. munmap(p, len); 31. 32. return 0; 33. } 34. 35. 36. /* 37. ** 内存泄漏的话题: 38. * malloc,未free 39. * 打开文件,没有close 40. * 映射了文件,没有munmap 41. */
执行结果如下:
可以看到hello文件中的helloworld已经被替换成了3210oworld,证明通过内存共享去写的方式成功。
3.2 创建两个进程,一个往映射文件中写,一个从映射文件中读
proce_mmap_w.c
1. #include <stdio.h> 2. #include <unistd.h> 3. #include <sys/stat.h> 4. #include <sys/types.h> 5. #include <fcntl.h> 6. #include <sys/mman.h> 7. #include <stdlib.h> 8. 9. #define MAPLEN 0x1000 10. 11. void sys_err(char *str, int exitno) 12. { 13. perror(str); 14. exit(exitno); 15. } 16. 17. int main(int argc, char * argv[]) 18. { 19. char *mm; 20. int fd, i = 0; 21. if(argc < 2) 22. { 23. printf("./a.out filename\n"); 24. exit(1); 25. } 26. 27. fd = open(argv[1], O_RDWR | O_CREAT, 0777); 28. if(fd < 0) 29. sys_err("open", 1); 30. lseek(fd, MAPLEN-1, SEEK_SET); 31. write(fd, "\0", 1); 32. 33. mm = mmap(NULL, MAPLEN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 34. close(fd); 35. if(mm == MAP_FAILED) 36. sys_err("mmap", 2); 37. 38. while(1) 39. { 40. sprintf(mm, "hello%d", i++); 41. sleep(1); 42. } 43. munmap(mm, MAPLEN); 44. return 0; 45. 46. }
proce_mmap_r.c
1. #include <stdio.h> 2. #include <unistd.h> 3. #include <sys/stat.h> 4. #include <sys/types.h> 5. #include <fcntl.h> 6. #include <sys/mman.h> 7. #include <stdlib.h> 8. 9. #define MAPLEN 0x1000 10. 11. void sys_err(char *str, int exitno) 12. { 13. perror(str); 14. exit(exitno); 15. } 16. 17. int main(int argc, char * argv[]) 18. { 19. char *mm; 20. int fd, i = 0; 21. if(argc < 2) 22. { 23. printf("./a.out filename\n"); 24. exit(1); 25. } 26. 27. fd = open(argv[1], O_RDWR); 28. if(fd < 0) 29. sys_err("open", 1); 30. 31. mm = mmap(NULL, MAPLEN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 32. close(fd); 33. if(mm == MAP_FAILED) 34. sys_err("mmap", 2); 35. 36. while(1) 37. { 38. printf("%s\n", mm); 39. sleep(1); 40. } 41. munmap(mm, MAPLEN); 42. return 0; 43. 44. }
执行结果如下: