Last updated on 8 months ago
是什么 mmap 是一个符合 POSIX 标准的 系统调用,它允许将一个文件或者其他对象(如设备、匿名内存等)映射到进程的地址空间中,应用程序可以通过内存访问方式来读写文件或者与其他对象进行交互。
能解决什么问题?
io 性能优化; 将一个普通文件映射到内存中,需要对文件进行频繁读写时,用内存读写取代I/O读写,以获得较高的性能
提供进程间共享内存及相互通信的方式,当多个进程将同一个对象映射到内存时,数据在所有进程之间共享
进程间通信
怎么用? 函数申明 1 void *mmap (void *addr, size_t length, int prot, int flags, int fd, off_t offset) ;
addr
:指定映射区域的起始地址。通常设为 NULL
,表示让系统自动选择合适的地址。
length
:指定映射区域的长度,即映射的文件的大小。
prot
: 指定映射区域的保护方式,即对映射区域的访问权限。可以使用以下标志的组合:
PROT_NONE
:无权限,不能访问映射区域。
PROT_READ
:可读权限,可以读取映射区域的内容。
PROT_WRITE
:可写权限,可以写入映射区域的内容。
PROT_EXEC
:可执行权限,可以执行映射区域的内容。
flags
:指定映射区域的其他选项。常用的标志包括:
MAP_SHARED
:表示映射区域可以被多个进程共享,对映射区域的修改会影响到其他进程。
MAP_PRIVATE
:表示映射区域只能被当前进程访问,对映射区域的修改不会影响到其他进程。
MAP_ANONYMOUS
:表示映射的是匿名内存而不是文件,此时 fd
和 offset
参数会被忽略。
MAP_FIXED
:表示要求系统将映射区域映射到指定的地址 addr
,如果无法满足要求,则 mmap()
函数会失败。
fd
:文件描述符,用于指定要映射的文件。如果是匿名映射,则可以设为 -1
。
offset
:文件偏移量,用于指定文件的起始偏移位置。
mmap()
函数的返回值是映射区域的起始地址,如果映射失败,则返回 MAP_FAILED
宏
简单使用过程
打开文件: 首先,使用 open()
函数打开要映射的文件,并获取文件描述符。
获取文件大小: 使用 fstat()
函数获取文件的大小和其他相关信息。
映射文件到内存: 使用 mmap()
函数将文件映射到进程的地址空间中。在调用 mmap()
函数时,需要指定文件描述符、文件大小、映射权限等参数,并获得映射的指针。
访问文件内容: 一旦文件映射到内存中,就可以通过访问指针来读取或写入文件内容,就好像访问内存一样。
解除映射: 在不再需要访问文件内容时,使用 munmap()
函数解除文件的内存映射。
关闭文件: 最后,使用 close()
函数关闭文件描述符。
代码说明: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main () { const char *filename = "example.txt" ; int fd; struct stat sb ; char *file_contents; fd = open(filename, O_RDONLY); if (fd == -1 ) { perror("open" ); exit (EXIT_FAILURE); } if (fstat(fd, &sb) == -1 ) { perror("fstat" ); exit (EXIT_FAILURE); } file_contents = mmap(NULL , sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0 ); if (file_contents == MAP_FAILED) { perror("mmap" ); exit (EXIT_FAILURE); } printf ("File contents:\n%s\n" , file_contents); if (munmap(file_contents, sb.st_size) == -1 ) { perror("munmap" ); exit (EXIT_FAILURE); } if (close(fd) == -1 ) { perror("close" ); exit (EXIT_FAILURE); } return 0 ; }
编译并执行,符合预期
1 2 3 4 5 root@ubuntu:/home# echo "test ddddddddd" > example.txt root@ubuntu:/home# gcc mmap.c -o mmap && ./mmap File contents: test ddddddddd root@ubuntu:/home#
具体场景的使用: 物理设备映射到虚拟内存 物理设备映射到内存,即设备的物理地址映射到磁盘上,这样可以以普通的内存读写操作来访问设备数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #define DEVICE_FILE "/dev/sda" #define BUFFER_SIZE (1024 * 1024) int main () { int fd; struct stat sb; char *buffer; fd = open (DEVICE_FILE, O_RDWR); if (fd == -1 ) { perror ("open" ); exit (EXIT_FAILURE); } if (fstat (fd, &sb) == -1 ) { perror ("fstat" ); exit (EXIT_FAILURE); } buffer = mmap (NULL , BUFFER_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if (buffer == MAP_FAILED) { perror ("mmap" ); exit (EXIT_FAILURE); } printf ("Reading data from device...\n" ); for (int i = 0 ; i < BUFFER_SIZE; ++i) { printf ("%c" , buffer[i]); } if (munmap (buffer, BUFFER_SIZE) == -1 ) { perror ("munmap" ); exit (EXIT_FAILURE); } if (close (fd) == -1 ) { perror ("close" ); exit (EXIT_FAILURE); } return 0 ; }
频繁的文件 io访问 有个些场景 需要对文件进行频繁读写时使用,使用内存读写取代I/O读写,以获得较高的性能,用两个daemon 测试下,一个是用mmap 映射,指针操作,一个是直接操作fd;
mmap_example.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <time.h> #define FILENAME "example.txt" #define FILE_SIZE 1024 #define NUM_ITERATIONS 500 int main (int argc, char *argv[]) { if (argc != 3 ) { printf ("Usage: %s <num1> <num2>\n" , argv[0 ]); return 1 ; } int fd; char *file_memory; clock_t start_total, end_total; double total_time; int size = atoi(argv[1 ]); int count = atoi(argv[2 ]); start_total = clock(); fd = open(FILENAME, O_RDWR); if (fd == -1 ) { perror("open" ); exit (EXIT_FAILURE); } file_memory = mmap(NULL , size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if (file_memory == MAP_FAILED) { perror("mmap" ); close(fd); exit (EXIT_FAILURE); } clock_t start_mmap = clock(); for (int i = 1 ; i <= count; ++i) { sprintf (file_memory, "This is modified content for iteration %d (mmap)." , i); } clock_t end_mmap = clock(); total_time = ((double ) (end_mmap - start_mmap)) / CLOCKS_PER_SEC; printf ("Total time taken (mmap): %f seconds\n" , total_time); if (munmap(file_memory,size) == -1 ) { perror("munmap" ); close(fd); exit (EXIT_FAILURE); } if (close(fd) == -1 ) { perror("close" ); exit (EXIT_FAILURE); } return 0 ; }
io_example.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <time.h> #define FILENAME "example.txt" #define FILE_SIZE 1024 #define NUM_ITERATIONS 500 int main (int argc, char *argv[]) { if (argc != 3 ) { printf ("Usage: %s <num1> <num2>\n" , argv[0 ]); return 1 ; } int fd; clock_t start_total, end_total; double total_time; int size = atoi(argv[1 ]); int count = atoi(argv[2 ]); start_total = clock(); fd = open(FILENAME, O_RDWR); if (fd == -1 ) { perror("open" ); exit (EXIT_FAILURE); } clock_t start_io = clock(); for (int i = 1 ; i <= count; ++i) { char buffer[size]; if (read(fd, buffer, size) == -1 ) { perror("read" ); close(fd); exit (EXIT_FAILURE); } sprintf (buffer, "This is modified content for iteration %d (IO)." , i); if (write(fd, buffer, size) == -1 ) { perror("write" ); close(fd); exit (EXIT_FAILURE); } } clock_t end_io = clock(); total_time = ((double ) (end_io - start_io)) / CLOCKS_PER_SEC; printf ("Total time taken (IO): %f seconds\n" , total_time); if (close(fd) == -1 ) { perror("close" ); exit (EXIT_FAILURE); } return 0 ; }
两者比对下来(反复读写1M 10000次)
有使用 mmap的程序,大多数时间花费再 mmap映射上,以及进本打开关闭操作
没有使用 mmap ,则时间花费再 read 、write系统调用(用户态和内核态之间的切换)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 root@ubuntu:/home Total time taken (mmap): 0.006987 seconds % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 25.75 0.000120 20 6 mmap 13.52 0.000063 16 4 mprotect 11.59 0.000054 18 3 openat 10.30 0.000048 16 3 3 access 9.87 0.000046 15 3 close 5.36 0.000025 8 3 fstat 5.15 0.000024 12 2 munmap 5.15 0.000024 8 3 clock_gettime 3.65 0.000017 6 3 brk 3.22 0.000015 15 1 execve 2.79 0.000013 13 1 read 2.58 0.000012 12 1 arch_prctl 1.07 0.000005 5 1 write ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000466 34 3 total root@ubuntu:/home Total time taken (IO): 9.971689 seconds % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 71.36 5.038501 50 100001 read 28.64 2.021914 20 100001 write 0.00 0.000041 14 3 close 0.00 0.000035 12 3 brk 0.00 0.000018 6 3 fstat 0.00 0.000018 6 3 clock_gettime 0.00 0.000000 0 5 mmap 0.00 0.000000 0 4 mprotect 0.00 0.000000 0 1 munmap 0.00 0.000000 0 3 3 access 0.00 0.000000 0 1 execve 0.00 0.000000 0 1 arch_prctl 0.00 0.000000 0 3 openat ------ ----------- ----------- --------- --------- ---------------- 100.00 7.060527 200032 3 total root@ubuntu:/home
虽然不用比较也很明确知道…. 一个是映射到内存读写,而 read 通常将读取的数据读到buffer中,先是系统调用,陷入内核(每次使用read都要进入内核态,进行上下文切换),内核首先将文件数据从磁盘读入page cache缓存,再将数据从page cache拷贝到buffer中,相比下来,用mmap的方式肯定更占优势。
待更新………………
共享内存 原理是?
参考文章
https://eric-lo.gitbook.io/memory-mapped-io/shared-memory
https://zhuanlan.zhihu.com/p/640169233