虚拟内存与缓存的关系(在这一篇中,缓存,内存和物理内存是同样的东西)
虚拟内存被分割为若干个虚拟页,物理内存(也就是主存)也被分割为若干个物理页
虚拟内存开始是被存放在磁盘上的(个人理解),要想被程序使用,先要从磁盘中将内容复制到物理内存中,才可以被使用。
所以虚拟页分为三种:
1.未分配的:虚拟内存还未分配或者创建的虚拟页(我的理解是在linux系统中,每个进程都有4G的虚拟内存,但是并不是都要用4G的内存,所以有很大一部分虚拟内存中完全没有数据,这一部分的虚拟页就是未分配的),这一部分是不占用磁盘空间的。
2.未缓存的:虚拟页已经被创建了(就是虚拟页中存在有用的数据,但是没有被加入到缓存中),这一部分占用磁盘空间,但是不占用内存空间
3.缓存的:这一部分是已经加入到缓存中的,这一部分既占用磁盘空间,又占用缓存空间。
页表就是判断虚拟内存中每一页是否被加入缓存的表格,页表的每一行被称为页表条目(PTE)都代表了虚拟内存的一页(单级页表是这样,多级页表的每一列代表了一片,片是由若干页构成的),页表时存储在缓存中的
页命中就是当使用到某一虚拟页时,如果该虚拟页的PTE中的有效位时1.就是命中了。如果是0,就是没有命中,本质上说就是如果所用的虚拟页已经存入内存了,就是命中了,如果没有存入就是未命中。
缺页就是没有命中,如果发生缺页,会执行缺页异常,整个过程如下:
1.通过PTE发现所需虚拟页不在缓存中
2.触发缺页异常,在缓存中设置牺牲页(牺牲页就是准备把所需的虚拟页加入到缓存的位置,可能是空白的物理页,也可能是暂时不用的存放虚拟页的物理页)
3.如果牺牲页上有数据并且发生更改,就将牺牲页的内容放回磁盘中
4.将所需虚拟页存入缓存中
5.重新执行命中虚拟页
页表有一个特殊的功能就是可以带许可位,也就是这块页可以执行那些权限(比如读写等操作)
地址翻译
就是将虚拟地址变成缓存中的物理地址。
物理地址分为物理页号(PPN)和物理地址偏移量(PPO)两部分
虚拟地址分为虚拟页号(VPN)和虚拟地址偏移量(VPO)两部分,
通过VPN可以找到页表中的物理页号(就是除了有效位以外PTE的其他数位),而根据有效位可以判断是否执行缺页异常,
这样就将物理地址的PPN和PPO找到了,这样就完成翻译了。
整个过程可以参考这个流程图
linux虚拟内存组织
linux是虚拟内存是分段的,比如代码段,数据段等等,用一下的数据结构来记录一个进程中虚拟内存的存储区域
内存映射
就是讲磁盘中的一片地址和虚拟内存关联起来,可以理解为将磁盘中的一片数据存放到虚拟内存中。
这里有个地方比较难以理解:就是本来虚拟内存就是存放在磁盘上的,为什么还要从磁盘上去映射。
这是因为本来虚拟内存可能是4G,但是磁盘上真正占用的空间小于等于4G,如果小于4G的情况下。我想让磁盘上的另一块空间使用虚拟内存的方式存入缓存,就可以使用内存映射的方式。
共享对象的内存映射
内存映射分为两种,一种私有对象,也就是这种映射只能一个进程使用,一种是共享对象,这样可以多个进程共用一个映射对象。
注意:如果有多个进程的虚拟内存中映射了磁盘上的同一份地址空间,那么在缓存中也只会复制一次该磁盘上的内容,如果都是共享对象,就多个进程一起在缓存中修改那一块内容,如果是私有对象,那么就采用写时复制的策略。
fork函数创建子进程时就是采用的这个策略,子进程和父进程的进程信息是在同一块缓存空间中的,但是如果需要修改进程信息都是采用写时复制的策略。所以在子进程创建开始,子进程的信息和父进程时一样的,除了PID号。
内存分配器(主要分配的是虚拟内存中堆上内存空间)
内部碎片:内部碎片就是已经被分配出去(能明确指出属于哪个进程)却不能被利用的内存空间;内部碎片是处于(操作系统分配的用于装载某一进程的内存)区域内部或页面内部的存储块。占有这些区域或页面的进程并不使用这个存储块。而在进程占有这块存储块时,系统无法利用它。直到进程释放它,或进程结束时,系统才有可能利用这个存储块。
外部碎片:指的是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。
隐式空闲链表:空闲块的头部存储的是该空闲块的大小
显式空闲链表:除了存储空闲块的大小外,还会存储两个指针,一个指向前一个空闲块,一个指向后一个空闲块。
分离的空闲链表:就是将大小相近的空闲链表分成一组,这样减少寻找合适空闲链表的时间
伙伴系统:每一类的大小都是2的幂(linux中就是使用的伙伴系统,但是我现在还不清楚linux如何避免内部碎片的产生)。
垃圾收集器:将堆中不可达的内存都销毁,腾出更多堆栈的空间。