虚拟内存机制 虚拟内存不是存放在磁盘上的也不是假的数据。它本质上是一套虚拟地址 → 实际存储位置的映射机制这些实际存储位置可能是物理内存RAM最常见磁盘上的分页文件pagefile.sys内存不足时甚至暂时没有任何实际存储尚未分配假设你的程序写了 int* p new int; *p 100; 程序认为变量地址0x000001A23F800000 这个地址就是虚拟地址Virtual Address 注意 CPU不能直接访问这个地址对应的 RAM。 真正的过程是 使用虚拟地址 程序----------------》0x000001A23F800000---》CPU MMU内存管理单元--》查询页表(Page Table)---》物理地址---》0x0000000123456000---》RAM 所以 虚拟内存首先是一种地址而不是数据。数据到底放在哪里情况1正常情况下99%的时间数据就在 RAM。虚拟地址 物理地址 0x1000---》页表--》0x8A001000---》RAM情况2内存不够Windows 会把一部分页面写到pagefile.sys 虚拟地址 pagefile.sys 0x1000---》页表---》偏移 100MB CPU 发现这页不在 RAM在 pagefile。 于是 发生 Page Fault缺页异常 Windows 把数据从 pagefile 读回 RAM 修改页表 程序继续运行情况3还没有真正分配例如char* p new char[10LL * 1024 * 1024 * 1024];申请了10GB 但是始末也没写很多操作系统采用按需分配Demand Paging。此时虚拟地址已经存在但是RAM里面还没有真正分配第一次p[0] 1; 才真正分配一页 RAM。虚拟地址空间 (程序看到的连续地址) 0x1000 ------------------------- 0x2000 ------------------------- 0x3000 ------------------------- 0x4000 ------------------------- │ ▼ 页表(Page Table) │ ┌──────────────┬──────────┴───────────┐ ▼ ▼ ▼ RAM中的页 pagefile中的页 尚未分配 (物理内存) (磁盘分页文件) (只有地址没有数据)mmap 原理LinuxWindowsopen()CreateFile()ftruncate()SetFilePointerEx()SetEndOfFile()mmap()CreateFileMapping()MapViewOfFile()munmap()UnmapViewOfFile()msync()FlushViewOfFile()close()CloseHandle()#pragma once #include fcntl.h #include sys/mman.h #include unistd.h #include cstring #include iostream #include stdexcept #include string // 把一个文件伪装成一个 vector templatetypename T class LargeVector { public: explicit LargeVector(const std::string file, size_t initialCapacity 1024) { fd open(file.c_str(), O_RDWR | O_CREAT, 0666); if(fd 0) throw std::runtime_error(open failed); capacity_ initialCapacity; resizeFile(capacity_); mapMemory(); } ~LargeVector() { sync(); if(data_) munmap(data_, capacity_ * sizeof(T)); if(fd 0) close(fd); } void push_back(const T value) { if(size_ capacity_) grow(); data_[size_] value; size_; } T operator[](size_t index) { return data_[index]; } const T operator[](size_t index) const { return data_[index]; } size_t size() const { return size_; } size_t capacity() const { return capacity_; } void sync() { msync(data_, capacity_ * sizeof(T), MS_SYNC); } private: void grow() { sync(); munmap(data_, capacity_ * sizeof(T)); capacity_ * 2; resizeFile(capacity_); mapMemory(); } void resizeFile(size_t cap) { off_t filesize cap * sizeof(T); if(ftruncate(fd, filesize) -1) throw std::runtime_error(ftruncate failed); } void mapMemory() { data_ static_castT*( mmap(nullptr, capacity_ * sizeof(T), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); if(data_ MAP_FAILED) throw std::runtime_error(mmap failed); } private: int fd{-1}; T* data_{nullptr}; size_t size_{0}; size_t capacity_{0}; };这段代码真正体现mmap原理的地方其实就三点文件不是主动read()到内存而是通过mmap()建立文件 ↔ 虚拟地址的映射。operator[]和push_back()访问的是普通指针data_CPU 并不知道背后是文件第一次访问某个页面时由缺页异常触发内核按需加载。修改data_指向的内存就是在修改映射页面由于使用了MAP_SHARED这些修改最终会同步回磁盘文件而整个过程中应用程序没有调用read()或write()。所以从学习mmap的角度来说最值得关注的不是push_back()或扩容逻辑而是为什么一个文件能被当成int*数组直接访问。理解了这一点就真正理解了mmap的核心思想。为什么文件可以当int*数组访问因为mmap()不是把文件变成数组而是把文件映射到一段虚拟地址空间返回这段虚拟地址的起始地址所以程序可以像访问数组一样访问它。mmap不是把文件搬进内存而是告诉操作系统“这段虚拟地址的数据来源是这个文件”。真正访问时操作系统才通过页表和缺页机制把文件内容加载到RAM。int* p (int*)mmap(...); p[0] 10; std::cout p[1];对于 CPU 来说它根本不关心p是来自newmallocmmapCPU 只会访问一个虚拟地址。如果访问的这一页还没有在 RAM 中操作系统会自动把对应的文件内容读入 RAM发生缺页异常然后程序继续执行。整个过程程序感觉不到。所以new虚拟地址 → RAMmmap虚拟地址 → 文件按需加载到 RAM程序看到的都是一个int*区别只是虚拟地址最终映射到哪里。read/writemmap数据关系文件和内存两份文件映射到内存访问方式读文件接口像数组一样访问拷贝需要拷贝避免用户态拷贝加载时机调用read时加载访问页面时加载写回手动write系统自动或msync适合普通文件读写大文件、数据库、随机访问