虚拟是计算机系统中很重要的一个概念。
虚拟内存是对主存的一种抽象概念,它是硬件异常,硬件地址翻译,主存,磁盘文件和内核软件的交互。
虚拟内存系统将虚拟内存分割为虚拟页,分为三种类型,未分配的,已经缓存在物理内存中的已分配页,未缓存在物理内存中的已分配页。
为了判断这些虚拟页的情况,就需要要加字段来判断,所以又出现了一个叫页表的数据结构来进行记录这些字段。
每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。操作系统负责维护页表的内容。
页面表中单个页表条目由一个有效位和n位地址字段组成,有效位用来判断该虚拟页是缓存在主存中。
而CPU读取虚拟页时会先通过有效位来判断是否在主存缓存中,所以会出现两种情况,一种是页命中,一种是缺页。
页命中,就顺其自然的获得物理内存地址,构造出物理地址。
而如果是缺页,就会调用内核中的缺页异常处理程序,在物理内存中牺牲一个页来存放在磁盘中查找到的需要的页,然后再次查询就会获得需要的页。
虚拟页面因为时间局限性的缘故,所以其实出现不命中的情况比较少,所以编写程序时只要有好的时间局限性,虚拟内存系统的性能就很好。
Linux通过将一个虚拟内存与一个磁盘上的对象关联起来,来初始化这个虚拟内存区域的内容,这个过程称为内存映射。
虚拟内存区域可以映射到两种类型的对象中的一种,一种是Linux文件系统中的普通文件,一种是匿名文件。
关于映射,一个很好的应用就是共享对象,两个不同的进程可以在物理内存***享一个共享对象,除非一个进程需要对其进行写操作,这样就可以很好地利用物理内存。
这时我们再看fork函数,其实在没有写操作前父进程与子进程就是用到了映射。
谈到虚拟内存,就要谈到内存的分配和回收,虽然有低级的mmap和munmap函数来创建和删除虚拟内存的区域,但是C程序员还是觉得当运行时需要额外虚拟内存时,用动态分配器会更方便且有移植性,比如说经常到程序实际运行时才知道某些数据结构的大小,这样按需分配,比刚开始硬编码制定内存实用高效许多。
动态分配器分为两种,显示分配器和隐式分配器。
显示分配器,要求应用显示地释放任何已分配的块。例如C语言中一种叫malloc程序包的显示分配器,使用malloc和free进行分配和回收内存。
而隐式分配器则要求分配器检测一个已分配块何时不再被程序使用,就释放这个块,也叫垃圾收集器,Java中就靠垃圾收集器来回收内存。C语言中也能通过复制清除算法实现一种保守的清理方法。