什么是内存映射
Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟区域的内如,这个过程称为内存映射。
代码示例:
/*******************************************************************
* > File Name: mmap.c
* > Create Time: 2021年09月28日 星期二 19时30分22秒
******************************************************************/
#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 = open("test.txt", O_RDWR); // 打开文件test.txt
if (fd < 0)
{
perror("open file faild!");
return -1;
}
char *start = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(start == MAP_FAILED){
perror("mmap faild");
return -1;
}
printf("%s\n", start); // 输出内存内容
char t[] = "helloword!\n";
memcpy(start, t, sizeof(t));
munmap(start, 4096); // 解除内存映射
close(fd); // 关闭文件
return 0;
}
内存映射原理
分析效率
从代码层面上看,从硬盘上将文件读入内存,都要经过文件系统进行数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一样的。但是通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么呢?原因是read()是系统调用,其中进行了数据拷贝,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,如图2中过程1,然后再将这些数据拷贝到用户空间,如图2中过程2,在这个过程中,实际上完成了 两次数据拷贝 ;而mmap()也是系统调用,如前所述,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了 一次数据拷贝 。因此,内存映射的效率要比read/write效率高
内存映射的特点
- 不会在映射的时候分配物理页
- 仅仅是建立一种关联
- 虚拟地址和文件地址偏移建立关联
- 设置好缺页异常回调函数
- 返回分配的虚拟地址给用户程序
- 读写的时候触发缺页异常
再看fork函数
当fork函数被当前进程调用的时候,内核为新进程创建各种数据结构,并分配一个唯一ID,为了给这个新进程创建虚拟内存,它创建当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork函数返回的时候,新进程现在的虚拟内存和调用的时候完全一样,当两个进程中的任何一个进行写的时候,写时复制机制会创建新的页面
再看execve函数
例如:execve("wc",NULL,NULL)
加载并运行wc需要以下几步:
- 删除已存在的用户区域
删除当前进程虚拟地址的用户部分已存在的区域结构。
- 映射私有区域
为新程序的代码、数据、bss、和栈区域创建新的区域结构,所有这些新的区域结构都是私有的、写时复制的
- 映射共享区域
如果wc还需要链接一些其他目标,比如C标准库中的libc.so,这些对象都是动态链接到程序中,然后再映射到用户虚拟地址空间中的共享区域内
- 设置程序计数器
execve做的最后一件事情,就是设置当前进程的上下文的程序计数器。