1、虚拟地址空间
1、概念与原因
虚拟地址空间是一个抽象的概念,在IBM中,这样说道:它存在,但你看不见,就是虚拟的。虚拟地址空间就是这样一个东西。(注意区分虚拟内存与虚拟地址空间)
虚拟地址空间,操作系统为了使每个进程都拥有独立的地址空间,使得每个进程都隔离开,便为每个进程都分配了独立的地址空间。它可以用来加载程序数据(数据可能被加载到物理内存上,空间不够就加载到虚拟内存中)
虚拟内存,在磁盘中存在交换区,以解决虚拟内存大于物理内存的情况,通过换页机制,达到进程层面的内存扩容。
为什么要用虚拟地址空间:1、每个进程的地址不隔离,这样不安全,别的进程可以通过内存寻址来修改此进程的值,有危险;2、进程中的地址是不确定,每次都会发生变化,这样每次加载到物理地址,都需要使用相对地址,效率低。3、如果直接使用物理内存的话,在物理内存不够的情况下,需要更换的是整个进程,效率低。
2、虚拟地址空间的分区
老生常谈的问题,内核区与用户区。用户区又常分为:代码区、数据区(.data, .bss)、堆、栈等。具体画分如下:
3、虚拟地址空间与物理地址空间的关系
我们在程序中使用的地址是虚拟地址,硬件CPU在执行的时候使用的是物理地址。那么进程中的数据是如何进出入到物理内存中的呢?其实是通过 CPU 中的内存管理单元 MMU(Memory Management Unit)从进程的虚拟地址空间中映射过去的。
通常有三种映射方式:分段,分页,段页式。
1、分段
程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(Segmentation)的形式把这些段分离出来。
分段机制下的虚拟地址由两部分组成,段选择因子和段内偏移量。
大概模型可以参考如下:
如果要访问段 3 中偏移量 500 的虚拟地址,我们可以计算出物理地址为,段 3 基地址 7000 + 偏移量 500 = 7500。
分段存在的问题: 内存碎片;内存交换效率低。
1、外部内存碎片:就是在段的加载与释放上,产生了不能用的小段物理内存,导致新的程序无法被加载。
2、内部内存碎片:程序某个段的所有都被加载到内存中,但是有部分并不使用,造成浪费。
针对内存碎片的问题,需要加载新的程序,此时就有虚拟内存使用,交换段(整个交换)
效率低的原因就是段太大,交换的时候耗时。
2、分页
1、由于内存空间都是预先划分好的,也就不会像分段会产生间隙非常小的内存,这正是分段会产生内存碎片的原因。而采用了分页,那么释放的内存都是以页为单位释放的,也就不会产生无法给进程使用的小内存。
2、如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为换出(Swap Out)。一旦需要的时候,再加载进来,称为换入(Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。
3、段页式
就是结合分段与分页,先分段,再分页。
4、Linux如何管理
Linux 内存主要采用的是页式内存管理,但同时也不可避免地涉及了段机制。
这主要是上面 Intel 处理器发展历史导致的,因为 Intel X86 CPU 一律对程序中使用的地址先进行段式映射,然后才能进行页式映射。既然 CPU 的硬件结构是这样,Linux 内核也只好服从 Intel 的选择。
但是事实上,Linux 内核所采取的办法是使段式映射的过程实际上不起什么作用。也就是说,“上有政策,下有对策”,若惹不起就躲着走。
Linux 系统中的每个段都是从 0 地址开始的整个 4GB 虚拟空间(32 位环境下),也就是所有的段的起始地址都是一样的。这意味着,Linux 系统中的代码,包括操作系统本身的代码和应用程序代码,所面对的地址空间都是线性地址空间(虚拟地址),这种做法相当于屏蔽了处理器中的逻辑地址概念,段只被用于访问控制和内存保护。
2、虚拟文件系统
文件系统的种类众多,而操作系统希望对用户提供一个统一的接口,于是在用户层与文件系统层引入了中间层,这个中间层就称为虚拟文件系统。
VFS 定义了一组所有文件系统都支持的数据结构和标准接口,这样程序员不需要了解文件系统的工作原理,只需要了解 VFS 提供的统一接口即可。
VFS的作用就是采用标准的系统调用读写位于不同物理介质上的不同文件系统。VFS是一个可以让open()、read()、write()等系统调用不用关心底层的存储介质和文件系统类型就可以工作的粘合层。