之前写过一篇
理解程序内存
, 当时主要是针对用户态,下面再稍微深刻一点:
咱们以32位程序为例(不启用AWE), 总共4G虚拟空间,其中低2G属于用户态, 高2G属于操做系统内核, 每一个程序都有本身的低2G用户空间, 高2G内核空间是全部程序共享的。
高2G内核空间中, 属于同一Session的程序又共享相同的session空间:
x86系统全部的内存以64K边界粒度, 4K页面大小分配。
用户态的内存空间,按用途分能够分为: image, mapped file, heap, stack, free等
按状态分能够分为:Free, reserved, commit;
Commit的内存,在被访问时又可能以不一样的状态存在, 多是已经提交到物理内存(RAM),也多是已页文件的形式存在后台, 若是是页文件形式,访问时会触发换页操做。
咱们平时以任务管理器或者Process Explorer, 常常会看到一些不一样内存术语:
virtual size: reserve和commit的虚拟内存
Private bytes: 已经commit的私有虚拟内存
working set: commit的虚拟内存中已经被加载到物理内存中的部分
WS private /
内存(专用工做集)
: 不能和其余程序共享的working set
这些内存的大小关系怎么样?
virtual size 确定是最大的; WS private确定是最小的;working set和private bytes大小很差定, 由于working set虽然是表示物理内存, 但它包含共享和非共享两部分, 而private bytes虽然是虚拟内存,却只包含私有部分。
另外咱们平时看程序的内存泄漏,主要能够看private bytes 和 WS private.
咱们程序里使用的虚拟地址, 它在访问时是如何别转成真正的物理地址的?
1. 咱们的虚拟地址被分为页目录索引,页表索引,字节偏移三部分
2. 根据CR3寄存器获得当前进程的页目录表地址, 根据页目录索引获得页目录表项目(PDE), 而后就能够获得该页表的地址
3. 根据页表索引,获得页表项目(PTE)的地址, 而后便可定位到该页面, 根据偏移字节便可访问真正的物理内存
操做系统采用按需换页的算法来实现内存的访问, 也就是说系统会在真正访问一个地址的时候才会把该地址转成有效的物理地址, 若是访问失败, 会触发换页异常, 再真正加载该页面换到物理内存。系统用虚拟地址描述符(VAD, virtual address descriptor)组成的平衡二叉树来跟踪全部的虚拟内存,以肯定全部虚拟内存的状态(free, reserver, commit)和属性。
下面说下应用层对程序内存的访问
, 按照内存的用途就能够大概划分:
Image: 主要是指二进制模块在内存中存在方式, 好比Exe和Dll, 对应的API好比LoadLibrary。
Mapped file: 主要是指内存映射文件, 能够用来快速的加载大文件 ,或者跨进程共享内存, 对应的API好比 CreateFileMapping.
Stack: 每一个线程都有本身的堆栈, 包括用户态堆栈和内核堆栈,虽然堆栈内存分配有大小限制, 可是很是高效,
函数的局部变量都存在里面,
程序的运行过程(函数的调用过程)其实是不停的压栈和出栈的过程,大小通常默认保留1M(参见线程堆栈是如何增加的
)
Heap: 系统有本身的堆管理器, 虽然效率堆内存分配效率低, 可是没有大小限制, 对应的API好比new, malloc, HeapAlloc
操做系统为咱们访问内存提供了各类渠道,咱们能够根据须要本身选择, 由下往上能够分为:
虚拟内存: 对应的API如VirtualAlloc(Ex), VirtualFree(Ex), VirtualLock, VirtualProtect, 经过这些API,咱们能够直接分配(reserver, commit)大块内存( 4K页面大小), 同时定义修改页面属性, 这是最高效的大内存分配方式。
Win32 堆内存: 对应的API如HeapCreate, HeapAlloc, 堆内存创建在虚拟内存之上,不少时候咱们不须要虚拟内存的大块内存,只须要小块内存,操做系统经过堆管理器帮咱们解决了这个问题。每一个进程启动时系统都会建立一个默认堆,同时咱们也能够建立本身的私有堆, 不一样模块之间是否共享同一个CRT堆取决于模块的编译选项,(参见
基于WinDbg的内存泄漏分析
)
CRT 堆内存:C/C++代码中咱们最经常使用的内存分配方式是malloc和new, 一般状况下malloc只负责内存分配, 而new在调用malloc分配内存的同时还有在分配的内存上构造对象的功能。至于malloc的实现方式, 不一样的编译器厂商会有不一样的实现, 有些多是经过Win32堆实现,也多是经过虚拟内存API直接实现。
思考为何有了虚拟内存API和Win32堆API,还要有CRT堆API?
软件工程里一条比较经典的话是: 任何问题均可以加一个间接层加以解决。操做系统提供的API都是平台相关的, 经过CRT这个间接层实现了平台无关, 同时咱们能够在这个间接层上作不少事情, 好比内存泄漏跟踪, 实现本身的内存池等。
若是咱们直接调用虚拟内存API分配内存, 这种内存属于那种类型?
实际上按照VMMap的说法, 内存类型还有更多: Image, Mapped File, Shareable, Heap, Managed Heap, Stack, Private Data, Page Table, Unusable, Free.
直接经过VirtualAlloc分配的内存不属于Heap, 应该属于Private Data.