目录
一、概念
工作原理:
特点:
适用场景:
二、详解mmap()函数
1. mmap的基本概念
2. mmap的特点
3. mmap的用途
4. mmap的优缺点
三、实验
实验一:基础读写实验
实验二:证明开始显示和最初赋值有关
实验三:开始打印的换行是哪来的
实验四:可打印长度和文件大小的关系
一、概念
使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。
工作原理:
- 内存映射是在进程的虚拟地址空间中创建一个映射,主要分为文件映射和匿名映射。
- 文件映射:把文件的一个区间映射到进程的虚拟地址空间,数据源是存储设备上的文件。
- 匿名映射:没有文件支持的内存映射,把物理内存映射到进程的虚拟地址空间,没有数据源。
- 创建内存映射时,在进程的用户虚拟地址空间中分配一个虚拟内存区域。内核采用延迟分配物理内存的策略,在进程第一次访问虚拟页时,产生缺页异常,然后分配物理页并将文件数据或匿名数据读入其中。
特点:
- 文件内容可以通过指针直接读写,而不需要使用read()和write()等系统调用。
- 多个进程可以映射同一个文件,实现共享数据。
- 对映射区的修改会影响到文件本身。
适用场景:
- 适用于对文件内容进行频繁读写的场景,如数据库、文本编辑器等。
二、详解mmap()函数
1. mmap的基本概念
- mmap是一种内存映射技术,它将文件或其他对象映射到进程的地址空间,使得进程可以直接通过指针访问文件数据,而无需使用传统的read和write系统调用。
- 通过mmap,文件内容可以被加载到进程的虚拟地址空间中,进程可以直接对这段内存进行读写操作,系统会自动将脏页面回写到对应的文件磁盘上。
2. mmap的特点
- 内存访问接口连续:mmap向应用程序提供的内存访问接口是内存地址连续的,但对应的磁盘文件的block可以不是地址连续的。
- 虚拟空间分配:mmap提供的内存空间是虚拟空间(虚拟内存),而不是物理空间(物理内存)。因此,可以分配远大于物理内存大小的虚拟空间。
- 文件逻辑连续映射:mmap负责将文件逻辑上一段连续的数据(物理上可以不连续存储)映射为连续内存。
- 线程共享:mmap由操作系统负责管理,对同一个文件地址的映射将被所有线程共享,操作系统确保线程安全以及线程可见性。
3. mmap的用途
- 文件映射:将整个文件或文件的一部分映射到进程的地址空间,提高文件操作的性能,特别是对于大文件的随机访问。
- 进程间通信(IPC):通过映射匿名内存(不与任何文件关联的内存区域),mmap可以在不同进程间进行数据共享。
- 创建高效的缓冲区:使用mmap创建的内存区域可以用作自定义的缓冲区,如网络数据传输或音视频数据的快速缓存。
- 动态内存管理:mmap还可以请求操作系统分配一块新的内存区域(匿名映射),用于自定义的内存管理策略。
- 反射和修改程序的行为:mmap可以用于实现程序的代码部分的动态修改,如即时编译(JIT)技术。
4. mmap的优缺点
- 优点:
- 高效访问:mmap使得文件的读写操作像访问内存一样高效,避免了频繁的系统调用和数据拷贝。
- 文件共享:多个进程可以将同一个文件映射到各自的地址空间,实现文件共享,方便进程间通信和数据共享。
- 零拷贝:与零拷贝技术结合,可以在网络传输中减少数据拷贝,提高传输性能。
- 缺点:
- 内存消耗:虽然mmap避免了一次性将整个文件读入内存,但映射的文件会占用进程的虚拟内存空间,处理大文件时可能导致内存消耗过多。
- 不适合小文件:对于小文件来说,mmap的开销可能超过传统的文件读写操作。
- 不可控制的缓存:mmap的文件访问由操作系统管理,可能导致数据缓存的不可控,影响性能预测。
注意事项:
(1) 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。
(2) 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误。
当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。
(3) 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。(没映射成功前不能关闭)
(4) 用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误,指定0大小创建映射区,报非法参数错误(Invalid argument)
(5) 文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误).
(操作系统的内存是一页一页分配的一页就是4k)
(6)映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误(下图中剩余部分) ,超出映射的内存大小报段错误
三、实验
实验一:基础读写实验
读程序:
#if 1
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
// memcpy((addr),"99999999999999",15);
while(1){
printf("read=%s\n",(char*)(addr));
sleep(1);
}
}
#endif
写程序:
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
memcpy(addr,"aaaaaaaaaaaaaaa",15);
int i=0;
while(i<2048){
memcpy((addr+i),"a",1);
i++;
sleep(1);
}
//memcpy(addr,"abcdefg",7);
//printf("read=%s\n",(char*)(addr));
}
会发现报了总线错误。对应注意中的第六条
给他加了22个空格
这是现象,是不是很奇怪。
左面就是先写了15个a然后从第一个位置开始往后写,右面是一直读。这就是为什么前面都一样长因为它有初值了。i大于15时才会显现出来。验证一下
实验二:证明开始显示和最初赋值有关
把开始的初值改成空格
并且不写入字符串结束标志
因为开始有初值所以变成这个形状了,我们还发现一个问题,没有字符串结束标志后开始没换行了,现在把test清空测一下。
实验三:开始打印的换行是哪来的
就变成这个样子了。
发现当超过限制后就没换行了,这个addr的最后是有一个换行的,当把他覆盖掉后就没了。他最多可以多打印四个。
实验四:可打印长度和文件大小的关系
事实证明这个长度最多就是和test的大小一致,可以突破限制多打印的数量是随机的有时候多4个有时候多一个,这个和内存中字符串结束标志在哪可能有关,毕竟是已经超出可控范围的东西。