全面解析虚拟内存
一个系统中的进程是与其他进程共享CPU和主存资源的。随着对CPU需求的增长,进程以某种合理的平滑的方式慢了下来。这里给大家分享一些关于全面解析虚拟内存,希望对大家能有所帮助。
虚拟内存空间
1.保留区(受保护的地址)
保留区即为受保护的地址,大小为128M,位于虚拟地址空间的最低部分,未赋予物理地址。任何对它的引用都是非法的,用于捕捉使用空指针和小整型值指针引用内存的异常情况。
它并不是一个单一的内存区域,而是对地址空间中受到操作系统保护而禁止用户进程访问的地址区域的总称。大多数操作系统中,极小的地址通常都是不允许访问的,如NULL。C语言将无效指针赋值为0也是出于这种考虑,因为0地址上正常情况下不会存放有效的可访问数据。。
2.代码段
代码段也称正文段或文本段,通常用于存放程序执行代码(即CPU执行的机器指令)。一般C语言执行语句都编译成机器代码保存在代码段。通常代码段是可共享的,因此频繁执行的程序只需要在内存中拥有一份拷贝即可。代码段通常属于只读,以防止其他程序意外地修改其指令(对该段的写操作将导致段错误)。某些架构也允许代码段为可写,即允许修改程序。
3.数据段(.data段)
数据段通常用于存放程序中已初始化的全局变量和静态局部变量。数据段属于静态内存分配(静态存储区),可读可写。由于全局变量未初始化时,其默认值为0,因此值为0的全局变量位于.bbs段(不位于数据段)。对于未初始化的局部变量,其值是不可预测的。注意:在代码段和数据段之间还包括其它段:只读数据段和符号段等。
4. .bbs段
该段用于存放未初始化的全局变量和静态局部变量,包括值为0的全局变量。 数据段和.bbs段又称为全局数据区,前者初始化,后者未初始化。
ELF段包括:代码段、其它段(在.data段和.text段之间,包括只读数据段和符号段等)、.data段(数据段)和.bbs段,都属于可执行程序部分。
5.堆空间
new( )和malloc( )函数分配的空间就属于堆空间。
分配的堆内存是经过字节对齐的空间,以适合原子操作。堆管理器通过链表管理每个申请的内存,由于堆申请和释放是无序的,最终会产生内存碎片。堆内存一般由应用程序分配释放,回收的内存可供重新使用。若程序员不释放,程序结束时操作系统可能会自动回收。
堆的末端由break指针标识,当堆管理器需要更多内存时,可通过系统调用brk()和sbrk()来移动break指针以扩张堆,一般由系统自动调用。
使用堆时经常出现两种问题:1) 释放或改写仍在使用的内存(“内存破坏”);2)未释放不再使用的内存(“内存泄漏”)。当释放次数少于申请次数时,可能已造成内存泄漏。泄漏的内存往往比忘记释放的数据结构更大,因为所分配的内存通常会圆整为下个大于申请数量的2的幂次(如申请212B,会圆整为256B)。
6.内存映射段(共享库)
内核将硬盘文件的内容直接映射到内存, 任何应用程序都可通过Linux的mmap()系统调用请求这种映射。内存映射是一种方便高效的文件I/O方式, 因而被用于装载动态共享库。如C标准库函数(fread、fwrite、fopen等)和Linux系统I/O函数,它们都是动态库函数,其中C标准库函数都被封装在了/lib/libc.so库文件中,都是二进制文件。这些动态库函数都是与位置无关的代码,即每次被加载进入内存映射区时的位置都是不一样的,因此使用的是其本身的逻辑地址,经过变换成线性地址(虚拟地址),然后再映射到内存。而静态库不一样,由于静态库被链接到可执行文件中,因此其位于代码段,每次在地址空间中的位置都是固定的。
7.栈空间
用于存放局部变量(非静态局部变量,C语言称为自动变量),分配存储空间时从上往下。
虚拟内存实现方式
页面和页框
我们来打个比方,假设你现在有一台32KB内存的电脑,虚拟内存是64KB。首先我们先将64KB的虚拟内存切个片,一个片大小为4KB,所以总共切了16片。同时,把32KB的物理内存也按4KB的切片,总共切了8片。那么我们就称虚拟内存的一个片叫做页面,物理内存的一个片叫做页框。
页表
同学们可能已经猜到了,没错,虚拟内存和物理内存之间是有一个映射关系的。这个映射该怎么实现呢,这就需要我们页表的登场啦!页表中维护着页表和页框的对应关系。举个栗子,一个地址为0x0010000000000100的16位地址。16位地址可拆分为两部分,前4位和后12位。前4位对应着16个虚拟页表的号牌用于在页表中寻找对应的页框号,后12位用于页内偏址。0010即是2号,我们在页表中查找2号选手对应的页框,假设是100。那么我们虚拟地址所对应的物理地址即可得出,为100+之前的后12位页内偏移地址,即0x100000000000100。
不过毕竟页面比页框为16:8,那么肯定会有一部分的页面没有所对应的页框,如果我们的虚拟地址就在这些页面中我们该怎么办呢?
缺页中断
没错,如果我们的虚拟地址所在页面没有对应的页框,那么系统会产生一个缺页中断。缺页中断使CPU停下手头的工作,转而去寻找一个使用最少的一个页框,将其写入到磁盘中(如果页框范围内地址的内容有改变的话,没有改变则不需要写入磁盘)。然后修改将页面指向该页框,修改映射关系,至此缺页中断处理结束。CPU继续执行之前的工作。
TLB
到这里,虚拟内存已经可以完整的映射到物理内存了,但还有一些问题。那就是速度问题。我们设想一下,如果把页表存储在进程中,那么每次CPU都得进入内存中查询,非常的影响速度。那我们如果设置一个寄存器在CPU中,那速度不就很快了么?确实这样CPU的访问速度会非常的快,但是每次的进程切换,都会有新的页表载入该寄存器中,同样非常影响速度。那我们就没有办法了么?根据计算机科学家的统计,其实有一些页面映射是非常频繁的,而剩余的映射则非常少,那么我们就可以将这些映射最频繁的页面放入CPU寄存器中,而这个CPU寄存器我们就叫他TLB。
虚拟内存原理
内存在计算机中的作用很大,电脑中所有运行的程序都需要经过内存来执行,如果执行的程序很大或很多,就会导致内存消耗殆尽。为了解决这个问题,Windows中运用了虚拟内存技术,即拿出一部分硬盘空间来充当内存使用,当内存占用完时,电脑就会自动调用硬盘来充当内存,以缓解内存的紧张。举一个例子来说,如果电脑只有128MB物理内存的话,当读取一个容量为200MB的文件时,就必须要用到比较大的虚拟内存,文件被内存读取之后就会先储存到虚拟内存,等待内存把文件全部储存到虚拟内存之后,跟着就会把虚拟内里储存的文件释放到原来的安装目录里了。
当系统运行时,先要将所需的指令和数据从外部存储器(如硬盘、软盘、光盘等)调入内存中,CPU再从内存中读取指令或数据进行运算,并将运算结果存入内存中,内存所起的作用就像一个“二传手”的作用。当运行一个程序需要大量数据、占用大量内存时,内存这个仓库就会被“塞满”,而在这个“仓库”中总有一部分暂时不用的数据占据着有限的空间,所以要将这部分“惰性”的数据“请”出去,以腾出地方给“活性”数据使用。这时就需要新建另一个后备“仓库”去存放“惰性”数据。由于硬盘的空间很大,所以微软Windows操作系统就将后备“仓库”的地址选在硬盘上,这个后备“仓库”就是虚拟内存。在默认情况下,虚拟内存是以名为Pagefile.sys的交换文件保存在硬盘的系统分区中。