大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
mmap/munmap接口是用户空间的最常用的一个系统调用接口,无论是在用户程序中分配内存、读写大文件,链接动态库文件,还是多进程间共享内存,都可以看到mmap/munmap的身影。
mmap说明
mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。
mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
mmap/munmap函数声明如下:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
addr
addr用于指定映射到进程空间的起始地址,为了应用程序的可移植性,一般设置为NULL,让内核来选择一个合适的地址。
mmap函数就是为了寻找一个可用的地址空间,将文件描述符fd对应的文件从偏移量offset开始,copy文件的length个字符进入用户地址空间。将起始地址返回。为什么第一个参数还要传用户地址空间的指针呢?这不是骑驴找驴吗?
这个addr参数可以NULL,这种情况我们比较容易理解。当addr不为NULL的时候,这其实是个建议查找地址,用来指导内存区定位的线索,是个用户指定的经验值。
- 如果flags带上了MAP_FIXED标志,表示用户王八吃秤砣,铁了心要addr这个地址作为映射起始地址,直接把addr返回。
- 如果没带上MAP_FIXED标志,则首先在建议地址addr附近寻找合适的区域。
- addr为NULL,让内核自己选择。
length、fd、offset
将fd对应的文件,从offset位置开始,长为len的内容映射到内存地址空间。
prot
prot参数通常表示映射页面的的读写权限,可以有如下参数组合:
- PROT_EXEC:表示映射的页面是可以执行的。
- PROT_READ:表示映射的页面是可以读取的。
- PROT_WRITE:表示映射的页面是可以写入的。
- PROT_NONE:表示映射的页面是不可访问的。
flags
flags参数也是一个重要的参数,有如下常见的参数:
- MAP_SHARED:创建一个共享映射的区域。多个进程可以通过共享映射方式来映射一个文件,这样其他进程也可以看到映射内容的改变,修改后的内容会同步到磁盘文件中。
- MAP_PRIVATE:创建一个私有的写时复制的映射。多个进程可以通过私有映射的方式来映射一个文件,这样其他进程不会看到映射内容的改变,修改后的内容也不会同步到磁盘文件中。
- MAP_ANONYMOUS:创建一个匿名映射,即没有关联到文件的映射。
- MAP_FIXED:使用参数addr创建映射,如果内核无法映射指定地址addr,那么mmap会返回失败,参数addr要求按页对齐。如果addr和length指定的进程地址空间和已有的VMA区域重叠,那么内核会调用do_munmap()函数把这段重叠区域销毁,然后重新映射新的内容。
- MAP_POPULATE:对于文件映射来说,会提前预读文件内容到映射区域,该特性只支持私用映射。
mmap映射类型
参数fd可以看出mmap映射是否和文件相关联,因此Linux内核中映射可以分为匿名映射和文件映射。
- 匿名映射:没有映射对应的相关文件,这种映射的内存区域的内容会被初始化为0。
- 文件映射:映射和实际文件相关联,通常是把文件的内容映射到进程地址空间,这样应用程序就可以像操作进程地址空间一样读写文件。
最后根据文件关联性和映射区域是否共享等属性,又可以分为如下4种:
私有映射 | 共享映射 | |
---|---|---|
匿名映射 | 私有匿名映射-通常用于内存分配 | 共享匿名映射-通常用于进程间共享内存 |
文件映射 | 私有文件映射-通常用于加载动态库 | 共享文件映射-通常用于内存映射IO,进程间通信 |
私有匿名映射
当使用参数fd=-1且flags=MAP_ANONYMOUS | MAP_PRIVATE时,创建的mmap映射是私有匿名映射。私有匿名映射最常见的用途是在glibc分配大块内存中,当需要的分配的内存大于MMAP_THREASHOLD(128KB)时,glibc会默认使用mmap代替brk来分配内存。
共享匿名映射
当使用参数fd=-1且flags=MAP_ANONYMOUS | MAP_SHARED。在这种情况下,创建共享匿名映射。共享匿名映射让相关进程共享一块内存区域,通常用于父子进程的之间通信。
创建共享匿名映射有如下两种方式:
- fd=-1且flags= MAP_ANONYMOUS|MAP_SHARED。在这种情况下,do_mmap_pgoff()->mmap()函数最终调用shmem_zero_setup()来打开一个”/dev/zero”特殊的设备文件。
- 另外一个是直接打开”/dev/zero”设备文件,然后使用这个文件句柄来创建mmap。
私有文件映射
私有文件映射时flags的标志位被设置为MAP_PRIVATE,那么就会创建私有文件映射。
私有文件映射的最常用的场景是加载动态共享库。
共享文件映射
创建文件映射时flags的标志位被设置为MAP_SHARED,那么就会创建共享文件映射。如果prot参数指定了PROT_WRITE,那么打开文件需要制定O_RDWR标志位。共享文件映射通常有如下场景:
- 读写文件:把文件内容映射到进程地址空间,同时对映射的内容做了修改,内核的回写机制(writeback)最终会把修改的内容同步到磁盘中。
- 进程间通信:进程之间的进程地址空间相互隔离,一个进程不能访问到另外一个进程的地址空间。如果多个进程都同时映射到一个相同的文件,就实现了多进程间的共享内存的通信。如果一个进程对映射内容做了修改,那么另外的进程是可以看到的。
mmap的使用
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/syscall.h>
int main() {
int fd = open("/root/demo/aa", O_CREAT | O_RDWR, 777);
size_t length = 1024;
ftruncate(fd, length); // 一定要这句,否则会报Bus error
char *addr = mmap(NULL, length, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
char* data = "hello";
memcpy(addr, data, 5);
char result[5];
memcpy(result, addr, 5);
printf("%s\n", result);
sleep(100);
munmap(addr, length);
close(fd);
return 0;
}
对一个空的新文件进行mmap前,需要往里面写入一点内容,否则会报错Bus error。发生错误的原因是因为mmap不能去扩展一个内容为空的新文件,因为大小为0,所有本没有与之对应的合法的物理页,不能扩展。
解决方案有2个:
- 一个就是上面的链接里的方案: 只需要在新创建的空文件中先写入一些数据即可;
- 另外一个是通过ftruncate对新建立的文件进行扩展后再映射修改。
查询进程所有的mmap映射:
# cat /proc/1078/maps
5636aadb5000-5636aadb6000 r--p 00000000 08:10 41881 /root/demo/a.out
5636aadb6000-5636aadb7000 r-xp 00001000 08:10 41881 /root/demo/a.out
5636aadb7000-5636aadb8000 r--p 00002000 08:10 41881 /root/demo/a.out
5636aadb8000-5636aadb9000 r--p 00002000 08:10 41881 /root/demo/a.out
5636aadb9000-5636aadba000 rw-p 00003000 08:10 41881 /root/demo/a.out
5636abae0000-5636abb01000 rw-p 00000000 00:00 0 [heap]
7f9f4e83f000-7f9f4e861000 r--p 00000000 08:10 12367 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f9f4e861000-7f9f4e9d9000 r-xp 00022000 08:10 12367 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f9f4e9d9000-7f9f4ea27000 r--p 0019a000 08:10 12367 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f9f4ea27000-7f9f4ea2b000 r--p 001e7000 08:10 12367 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f9f4ea2b000-7f9f4ea2d000 rw-p 001eb000 08:10 12367 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f9f4ea2d000-7f9f4ea33000 rw-p 00000000 00:00 0
7f9f4ea3b000-7f9f4ea3c000 r--p 00000000 08:10 12234 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f9f4ea3c000-7f9f4ea5f000 r-xp 00001000 08:10 12234 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f9f4ea5f000-7f9f4ea67000 r--p 00024000 08:10 12234 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f9f4ea67000-7f9f4ea68000 rw-s 00000000 08:10 768 /root/demo/aa
7f9f4ea68000-7f9f4ea69000 r--p 0002c000 08:10 12234 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f9f4ea69000-7f9f4ea6a000 rw-p 0002d000 08:10 12234 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f9f4ea6a000-7f9f4ea6b000 rw-p 00000000 00:00 0
7ffe68bf2000-7ffe68c13000 rw-p 00000000 00:00 0 [stack]
7ffe68dd5000-7ffe68dd8000 r--p 00000000 00:00 0 [vvar]
7ffe68dd8000-7ffe68dd9000 r-xp 00000000 00:00 0 [vdso]
查看内存映射的区域地址范围:
# ll /proc/1078/map_files/
total 0
dr-x------ 2 root root 0 Jul 12 11:02 ./
dr-xr-xr-x 9 root root 0 Jul 12 11:02 ../
lr-------- 1 root root 64 Jul 12 11:02 5636aadb5000-5636aadb6000 -> /root/demo/a.out*
lr-------- 1 root root 64 Jul 12 11:02 5636aadb6000-5636aadb7000 -> /root/demo/a.out*
lr-------- 1 root root 64 Jul 12 11:02 5636aadb7000-5636aadb8000 -> /root/demo/a.out*
lr-------- 1 root root 64 Jul 12 11:02 5636aadb8000-5636aadb9000 -> /root/demo/a.out*
lr-------- 1 root root 64 Jul 12 11:02 5636aadb9000-5636aadba000 -> /root/demo/a.out*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4e83f000-7f9f4e861000 -> /usr/lib/x86_64-linux-gnu/libc-2.31.so*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4e861000-7f9f4e9d9000 -> /usr/lib/x86_64-linux-gnu/libc-2.31.so*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4e9d9000-7f9f4ea27000 -> /usr/lib/x86_64-linux-gnu/libc-2.31.so*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4ea27000-7f9f4ea2b000 -> /usr/lib/x86_64-linux-gnu/libc-2.31.so*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4ea2b000-7f9f4ea2d000 -> /usr/lib/x86_64-linux-gnu/libc-2.31.so*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4ea3b000-7f9f4ea3c000 -> /usr/lib/x86_64-linux-gnu/ld-2.31.so*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4ea3c000-7f9f4ea5f000 -> /usr/lib/x86_64-linux-gnu/ld-2.31.so*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4ea5f000-7f9f4ea67000 -> /usr/lib/x86_64-linux-gnu/ld-2.31.so*
lrw------- 1 root root 64 Jul 12 11:02 7f9f4ea67000-7f9f4ea68000 -> /root/demo/aa*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4ea68000-7f9f4ea69000 -> /usr/lib/x86_64-linux-gnu/ld-2.31.so*
lr-------- 1 root root 64 Jul 12 11:02 7f9f4ea69000-7f9f4ea6a000 -> /usr/lib/x86_64-linux-gnu/ld-2.31.so*
查看内存映射地址范围的内容:
# cat /proc/1078/map_files/7f9f4ea67000-7f9f4ea68000
hello
我们看到/root/demo/aa
文件已经映射到内存中取了,占用空间0x7f9f4ea67000-0x7f9f4ea68000=4K,虽然申请的是1K,但是映射的分配是以页面为单位分配的,即最小分配4K。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/192741.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...