内存那点事:让我们一点点的搞懂它

2023年 12月 25日 62.7k 0

内存是计算机系统中至关重要的组成部分,它不仅储存了运行中的程序和数据,还直接关系到系统的性能和稳定性。让我们一起深入探讨Linux系统下内存管理的核心原理,揭开它的神秘面纱。

基础概念

物理地址

  • 概念:物理地址是指计算机内存中实际的硬件地址,它对应着计算机中的物理存储单元(如RAM),物理地址是唯一的。内存的一个地址的容量是一个字节(Byte)
  • 特点: 物理地址是唯一的,每个物理存储单元都有一个对应的物理地址。

虚拟地址

  • 概念:虚拟地址是在程序执行过程中由操作系统提供的地址空间,它不直接对应物理硬件,而是经过虚拟内存系统的映射,最终映射到物理地址上。每个运行的进程都有自己的虚拟地址空间,这使得每个进程认为它拥有整个系统的内存。
  • 特点: 虚拟地址具有抽象性,它使得程序无需关心实际的硬件细节,而是可以使用一个相对于程序自身的地址空间。

内存布局

  • 32位操作系统:支持32位的地址空间,最多可以寻址2^32个地址,即4GB的内存。
  • 64位操作系统: 支持64位的地址空间,最多可以寻址的地址数量为2^64,即128TB。
  • 不同位宽的操作系统地址空间的范围也不同,下面的两张图来分别表示它们的虚拟地址空间:
  • 每个进程的虚拟内存空间都包括用户空间和内核空间,每个进程都认为它拥有整个系统的内存资源。
  • 每个进程的内核空间,其实关联的都是相同的物理内存(公用的)。

内存映射

既然每个进程都有一个这么大的地址空间,那么所有进程的虚拟内存加起来,自然要比实际的物理内存大得多。所以,并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,内存分配的机制是通过内存映射来管理的,内存映射支持按段分配和按页分配。

按段分配

分段是比较早提出的,它将整个物理内存划分为若干个不同用途的段,每个段用于存放特定类型的数据,这些逻辑分段包括只读段、数据段、堆段、栈段组成。

  • 只读段:包括代码和常量等。
  • 数据段:包括全局变量等。
  • 堆段:包括动态分配的内存,从低地址开始向上增长。
  • 文件映射段: 包括动态库、共享内存等,从高地址开始向下增长。
  • 栈段:包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB

存在的问题:

  • 外部内存碎片:因为分段机制分配的是连续的内存空间,假设有 1G 的物理内存,A程序占用了512MB,B程序占用了128MB,C程序占用了256MB,空闲128MB,B程序关闭了,因为内存不连续,导致没有足够空间在打开一个200MB的程序,就会交换到磁盘,从磁盘换入、唤出效率低下(多个不连续的物理内存空间)。
  • 复杂性: 程序员需要管理多个内存段,增加了编程的复杂性。
  • 不同段的交叉访问: 由于段之间的独立性,跨越多个段的访问会更加复杂。

按页分配

  • 将物理内存和虚拟内存划分为固定大小的页(通常为4KB)
  • 操作系统维护一个页表,将虚拟内存的页映射到物理内存的页上。
  • 页表(快速、高效)。
  • MMU:页表实际上存储在 CPU 的内存管理单元 MMU 中。
  • TLB 是MMU 中页表的高速缓存,加速虚拟地址到物理地址的转换,减少对主存(RAM)的访问次数,提高系统性能。
  • 多级页表:页的大小是4K,随着内存的增大,页表记录会特别多,为了解决页表项过多的问题,Linux 提供了两种机制,也就是多级页表和大页(HugePage)。

优点

  • 消除外部碎片: 由于页是固定大小的,减少了外部碎片的产生。
  • 简化内存管理: 操作系统负责页的映射,程序员无需关心具体的内存分配和释放。
  • 更好的内存共享: 易于实现页面的共享,不同进程可以共享相同的页。

内存分配与回收

内存分配

进程可以通过调用malloc等函数在堆上动态分配内存。这些内存块的管理由C库提供,但最终涉及到系统调用,如brk和mmap

brk

  • 作用:用于调整进程的数据段的结束地址,即扩展或缩小堆的大小。
  • 操作对象:操作的是堆空间,对整个数据段的结束地址进行调整。
  • 分配粒度:分配的内存是以页为单位的,较大的内存请求可能会导致内部碎片。
  • 适用场景:适用于较小的内存分配,比如动态内存分配。

mmap

  • 作用:用于在进程的地址空间中映射文件或匿名内存区域。
  • 操作对象:可以操作文件映射,也可以用于匿名内存映射,即映射到无关联文件的内存。
  • 分配粒度:可以以页为单位进行内存分配,也支持更细粒度的映射。
  • 适用场景:适用于大块的内存分配,比如映射大文件、共享内存、内存映射 I/O 等。

内存回收

  • 手动回收:调用 free() 或 unmap() 来释放这些不用的内存。
  • 自动回收(内存紧张时系统触发)。
  • 回收缓存:比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面。
  • 回收不常访问的内存:把不常用的内存通过交换分区直接写到磁盘中(Swap)。
  • 杀死进程:内存紧张时系统还会通过OOM(Out of Memory)直接杀掉占用大量内存的进程。
  • 一个进程消耗的内存越大,oom_score 就越大。
  • 一个进程运行占用的 CPU 越多,oom_score 就越小。
# oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死
# -17 表示禁止 OOM
echo -16 > /proc/$(pidof sshd)/oom_adj

结束语

今天我们从概念开始,一点点的展开讲了关于内存映射的两种内存分配机制,以及特点,讲了内存分配和回收,留下几个问题,系统大家一起讨论学习:

  • 按页分配下会存在内存碎片吗?为什么?
  • Linux 操作系统采用了哪种方式来管理内存呢?

相关文章

服务器端口转发,带你了解服务器端口转发
服务器开放端口,服务器开放端口的步骤
产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
如何使用 WinGet 下载 Microsoft Store 应用
百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

发布评论