操作系统的内存管理是指操作系统在运行过程中对内存资源的管理。内存管理是操作系统的核心功能之一,主要包括内存分配、内存回收、内存保护和虚拟内存等。
内存分配方式是操作系统对内存管理的重要策略之一。根据内存的分配方式,可以将其分为连续分配和非连续分配两种方式。
连续分配是将内存空间按照一定的大小划分成若干个固定大小的分区,每个进程需要内存时,就从这些分区中选择一个连续的空闲分区分配给进程使用。
连续分配的缺点是内存碎片问题,容易出现外部碎片和内部碎片,导致内存空间的浪费。
非连续分配是将内存空间划分成若干个大小不等的块,每个进程需要内存时,就分配一个或多个不连续的块给进程使用。
常用的非连续分配方式有分页式和分段式两种方式。
只用物理地址无法让多个进程同时访问,因为多个进程可以并发运行,每个进程都需要独立的内存空间。但是如果每个进程都有自己独立的物理地址空间,会导致内存浪费和操作系统的开销变得更大。此外,将每个进程的地址空间映射到不同的物理地址上需要更多的硬件支持,这将增加系统的成本和复杂性。
因此,操作系统需要通过内存管理机制来为每个进程分配独立的虚拟地址空间,并将虚拟地址映射到物理地址上。此内存管理机制为内存管理单元(Memory Management Unit,MMU)。它允许多个进程共享同一块物理内存,但每个进程看到的是独立的虚拟地址空间。这种方法使得操作系统能够更好地管理内存,同时也使得进程之间的相互干扰得到了控制。
我们程序所使用的内存地址叫做虚拟内存地址(Virtual Memory Address)
实际存在硬件里面的空间地址叫物理内存地址(Physical Memory Address)。
进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存。
操作系统将虚拟地址空间划分为用户空间和内核空间两部分。每个进程都有自己独立的用户空间,而所有进程共享同一个内核空间。用户空间是用于存储进程代码、数据和堆栈等用户程序所需的空间,而内核空间则用于存储操作系统内核、设备驱动程序和操作系统所需的其他数据结构和代码的空间。
当多个进程访问同一块物理内存时,可能会发生相互干扰的情况,例如一个进程修改了共享内存中的数据,而另一个进程正在读取该数据。为了避免这种干扰,操作系统通常使用锁和同步机制来协调进程之间的访问。
使用虚拟内存,多个进程可以同时访问相同的虚拟地址,但是它们访问的是不同的物理地址。操作系统可以使用分页机制或段表机制来实现虚拟内存。这样,操作系统就可以为每个进程提供独立的内存空间,从而保证了多进程的并发执行的正确性。
程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,故分段机制会把程序的虚拟地址分成 4 个段,虚拟地址和物理地址通过段表进行映射。
内存地址转换步骤:
把虚拟地址切分为段选择因子和段内偏移量,找到段选择因子中的段号,从段表里找对应段基地址,将基地址加偏移量即可得到物理内存地址。
内存分段缺点:
内部碎片:段式分配内存容易产生内部碎片,即一个段所占用的内存空间比其实际需要的空间要大,因为段的大小是固定的,而进程所需要的空间大小可能不是段的整数倍,导致在一个段中浪费了一部分内存空间。
外部碎片:由于每个段的大小是固定的,因此如果当前没有足够的连续空闲内存块来满足某个段的需求,那么就会出现外部碎片。即使在物理内存中有足够的总内存,但由于内存分配是非连续的,导致某些空闲的碎片内存无法被利用。
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫页(Page)。在 Linux 下,每一页的大小为 4KB
。虚拟地址与物理地址之间通过页表来映射。
缺页异常是指进程访问的某个页面不在物理内存中,需要从磁盘中将其读入内存,并更新页表的映射关系。分页释放的内存都是以页为单位释放的,不会产生无法给进程使用的小内存。故有效的较少了碎片问题。
若内存空间不够,操作系统会把其他正在运行的进程中(最近没被使用)的内存页面给释放掉,也就是暂时写在硬盘上,称为换出(Swap Out)。一旦需要再加载进来,称为换入(Swap In)。内存交换效率相对比较高,因为一次性写入磁盘的也只有少数的一个页或者几个页。
分页的方式:
只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去,不需要一次性都把程序加载到物理内存中。
地址转换步骤:
把虚拟地址切分为页号和偏移量,而后根据页号从页表里找对应的物理页号。直接将物理页号加偏移量即可得到物理内存地址。
由于需要维护一个页表来跟踪每个页面的位置和权限,因此页式分配需要一定的开销,这可能会影响系统的性能。而解决开销大的方法有:多级页表、反向页表、快表缓存、虚拟内存技术。
下面主要介绍其中的一个方法---多级页表
多级页表是一种分层的页表结构,这种结构可以减少整个页表的大小,从而减少页表的开销。
页表一定要覆盖全部虚拟地址空间,故如果是一级页表,需要定义所有空间对应的页表项。而对于多级页表来说,如果某个一级页表的页表项没有被用到,就不需要创建这个页表项对应的二级页表,可以在需要时才创建二级页表。
快表也是解决开销大问题方法之一。多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换的工序,这显然就降低了这俩地址转换的速度,也就是带来了时间上的开销。
程序的运行符合局部性原理:
时间局部性:即如果一个数据正在被访问,那么近期它可能还会被再次访问。
空间局部性:在不久的将来用到的数据很可能与现在正在使用的数据在空间地址上也是邻近的。
快表就是利用了局部性原理,把最常访问的几个页表项存储到访问速度更快的硬件,在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等。
在 CPU 芯片里面,封装了MMU芯片,它用来完成地址转换和 TLB 的访问与交互。有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查常规的页表。
段页式内存管理实现的方式:
地址结构就由段号、段内页号和页内位移三部分组成。
段页式地址变换中要得到物理地址须经过三次内存访问:
面试相关问题:
1. 进程如何从操作系统中获得内存空间?
进程可以通过系统调用向操作系统申请内存空间。操作系统会在物理内存中为该进程分配一定大小的内存空间,并在进程的页表或段表中记录该空间的位置信息。
2. 什么是物理内存和虚拟内存,它们之间有什么区别?
物理内存是指计算机实际存在的内存,而虚拟内存是一种操作系统技术,指操作系统将物理内存和磁盘空间组合成一个虚拟地址空间,对进程提供透明的内存管理和保护机制。
物理内存和虚拟内存之间的主要区别在于:
物理内存是实际存在的内存,而虚拟内存是一种抽象的内存管理机制;
物理内存容量有限,而虚拟内存容量可以很大,可以通过硬盘等外部存储器进行扩展;
物理内存访问速度快,而虚拟内存访问速度相对较慢。
3.为什么需要虚拟地址空间?
如果程序都直接访问和操作物理内存会存在着么两个问题:
- 用户程序任意访问内存,容易破坏操作系统,造成OS崩溃
- 运行多个程序,可能会访问同一个地址,导致程序崩溃
所以需要虚拟地址空间,有了虚拟地址空间我们可以做到:
- 使用相邻的虚拟地址访问不相邻的物理内存
- 可以使用虚拟地址来访问大于可用物理内存的内存缓冲区
- 不同进程使用的虚拟地址彼此隔离