文章目录
- 使用示例
- 函数原型
- mmap
- munmap
- 传统读写文件
- mmap 原理
- eager实现
- lazy实现
- 缺点
使用示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
int main(int argc, char* argv[])
{
int fd;
void *start;
struct stat sb;
fd = open("text.txt", O_RDONLY|O_CREAT); // 打开文件text.txt
printf("fd=%d\n",fd);
fstat(fd, &sb); // 获取文件状态
start = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); // 建立内存映射
if(start == MAP_FAILED){
return (-1);
}
strcpy((char*)start,"asd");
printf("%s\n", (char*)start); // 输出内存内容
munmap(start, sb.st_size); // 解除内存映射
close(fd); // 关闭文件
return 0;
}
这段代码实现将文件text.txt 打开,并用mmap函数将文件映射到虚拟内存中,通过执政start对文件进行读写,可以在中断中看到由文件写入的数据,程序结束后,可以查看text.txt文件,来查看写入的数据
函数原型
mmap
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
addr
:制定映射的起始地址,通常是NULL,由内核来分配,是一个虚拟地址len
:代表将文件中映射到内存的部分的长度,以及内存地址的区间大小prot
:映射区域(内存)的保护方式,这块地址的方式PROT_EXEC
:映射区域可执行,XPROT_READ
: 映射区域可读取,RPROT_NONE
: 映射区域不能存取,PROT_WRITE
: 映射区域可以写入 ,W
flag
:映射区的特性标志位MAP_SHARD
:写入映射区的数据会复制回文件,和其他映射文件的进程共享,多个进程可以共享,实现进程间通信
MAP_PRIVATE
:对映射区的写入操作会产生一个映射区的复制,对此区域的修改不会写会原文件,这一部分的内容只会出现的内存,而不对文件修改,
fd
:要映射到内存中的文件描述符,有open函数打开文件时返回的值,内核可以通过他得到对应的struct file
offset
:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小(4K)的整数倍。offset移动,相当与从文件的不同位置进行映射
函数的返回值
实际分配的内存的起始地址,我们可以使用这个地址,来对文件进行修改,读取
munmap
int munmap( void * addr, size_t len )
该调用在进程地址空间中解除一个映射关系,来表明应用程序完成了对文件的操作,addr是mmap时返回的地址,len是映射区的长度
如果这个len就是内存中对应的映射区地址,这一块就直接释放掉了,如果不是,就把addr+len这一部分给解除映射,我们使用的addr移动len
解除映射之后,对原来映射地址的访问会导致段错误
传统读写文件
- 把文件内容读入到内存中,从内核态拷贝回用户态,获得对应文件的数据。
- 用户态修改文件相应的内容。
- 把修改过的数据从用户态拷贝回内核态文件中。
read(fd, buf, 1024); // 读取文件的内容到buf
... // 修改buf的内容
write(fd, buf, 1024); // 把buf的内容写入到文件
其中,(页缓存) page cache
类似inode cache,把磁盘中的数据缓存在内存中,减少和磁盘进行交互,提高效率,内核使用page cache 将文件的数据块关联起来,所以我们在读写文件的时候,实际上操作的是 page cache
最大的影响就是,读写都需要进行数据的拷贝,如果数据两很大,那对性能影响就很大
mmap 原理
与传统读写文件相比,mmap就是可以直接在用户空间读写 page cache
,这样就可以免去将 page cache
的数据在内核与用户之间的拷贝,mmap映射的正是文件的 page cache
,而非磁盘
mmap
将文件映射到进程的虚拟内存空间中,通过对这段内存的 lord
和 store
,实现对文件的读取和修改,不使用 read
和 write
off为映射的部分在文件中的偏移量,len为映射的长度
图中实际含义
从文件描述符对应的offset开始映射长度为len的内容到虚拟地址va(由内核决定),va+len,范围内都是其对应的虚拟地址
eager实现
如果内存使用的是eager方式来实现
对于文件的读写,内核会从文件的offset开始,将数据拷贝到内核中,设置好PTE指向物理内存的位置,后程序就可以使用load或者store来修改内存中文件的内容,完成后,使用munmap,将dirty block写回文件中,我们可以很容易找到哪个block是dirty,因为对应的PTE_D被设置了
lazy实现
但是现在的计算机都不会这样做,都是以 lazy
的方式实现
- 记录这个PTE属于这个文件描述符
- 存储相应的信息在VMA(Virtual Memory Area))结构体中(这些信息来表示对应的虚拟地址的实际内容在哪里)
- 文件描述符
- 偏移量等
- 地址范围
- 标志位
- 长度
调用mmap是不会开辟物理地址的,只会把数据存储起来,等待后续实际的调用,再实际的对对应的page进行开辟物理内存
- 对VMA记录的某个范围内进行读写操作,触发page fault,就会实际的开辟物理页,将该va和该物理地址进行映射,将VMA中记录的offset标志位开始读取数据到对应的物理地址中
如果其他进程直接修改了文件的内容,内容不会出现在内存中,
mmap并不会主动将 mmap
修改的page cache 同步
到磁盘,而是需要用户进行触发
munmap
解除文件映射的时候会触发- msync函数主动进行数据同步
- 进程退出
- 系统关机
- 虚拟地址空间获得一段连续的地址
- 在没有读写的时候,这个地址指向不存在的地方(所以上图中,起始地址和终止地址还没分配给进程)
- 根据偏移量,进程要读取文件了,数据占两个页
- 进程开始使用内存,所以OS要给这两个页分配内存,触发page fault
- 将对应的offset文件数据拷贝到物理内存对应的page上
缺点
- 如果文件很小,小于4KB,但是再内存中都是按照4KB为基本单位,就会造成一个内存空间的浪费
- 创建mmap,销毁munmap,page fault开销很大