这篇文章上次修改于 765 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

1 内存映射

每个进程都有独立的虚拟空间,虚拟空间包含了用户空间和内核空间,不同进程的内核空间关联的都是相同的物理内存。

内存映射,其实就是将虚拟地址映射为物理内存地址。内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系。

页表位于内存管理单元 MMU 中,正常情况下,处理器可通过硬件访问内存。当虚拟地址没有在页表中查到时,会产生缺页中断,进入内核空间分配内存,更新页表,然后回到用户空间,恢复进程运行。

TLB 是页表的高速缓存。

内存映射的最小单位是页,通常是 4KB 或者 4KB 的整数倍。

为了解决页表项过多的问题,Linux 引入了两种解决方式:多级页表和大页。

img

2 虚拟内存空间分布

img=100*100

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

3 内存分配与回收

malloc() 是 C 库提供的函数,对应两种系统调用,brk() 和 mmap()。

brk() 用于分配小于 128K 的内存。内存释放后不会不会立刻归还系统,而是缓存起来,提供内存访问效率,但是频繁的分配和释放会造成内存碎片。

mmap() 用于分配大于 128K 的内存,在文件映射区分配。内存释放后会立刻归还系统,频繁的内存分配会导致大量缺页异常。

以上两种系统调用发生后,并不会真正分配内存,而是首次访问时通过缺页异常进入内核,由内核分配。

Linux 通过伙伴系统管理内存分配。伙伴系统以页为单位管理内存。

如果要比页小的内存,用户空间中,malloc() 通过 brk() 分配内存。内核空间中,Linux 通过 slab 内存分配器管理小内存。可将 slab 视为伙伴系统的缓存。

当内存紧张时,会通过如下方式回收内存:

  • 回收缓存,比如通过 LRU 算法回收最近最少使用的内存页面。
  • 回收不常访问的内存,通过交换分区直接写到磁盘。读磁盘速度远慢于读内存速度,当内存不足时使用该方式。
  • 杀死进程,通过 OOM 杀死占用内存大的进程。

    • 进程占用的内存越大,oom_score 越大。
    • 进程占用的 CPU 越多,oom_score 越小。
    • oom_score 越大,约容易被 OOM 杀死。

4 如何查看内存使用情况

# 注意不同版本的free输出可能会有所不同
$ free
              total        used        free      shared  buff/cache   available
Mem:        8169348      263524     6875352         668     1030472     7611064
Swap:             0           0           0
  • total:总内存大小。
  • used:已使用内存大小。
  • free:未使用内存大小。
  • shared:共享内存大小。
  • buff/cache:缓存区和缓冲区大小。

    • buffer 是对磁盘数据的缓存。

      • 磁盘数据是指块设备文件。读写块设备文件时,会跳过与文件系统,直接与磁盘交互,即“裸 I/O“。
    • cache 是对文件数据的缓存。

      • 文件是指普通的文件。读写普通文件时,I/O 请求会经过文件系统,由文件系统负责与磁盘的交互。
  • available:新进程的可用内存大小。包括未使用内存和可回收缓存。
# 按下M切换到内存排序
$ top
...
KiB Mem :  8169348 total,  6871440 free,   267096 used,  1030812 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  7607492 avail Mem


  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  430 root      19  -1  122360  35588  23748 S   0.0  0.4   0:32.17 systemd-journal
 1075 root      20   0  771860  22744  11368 S   0.0  0.3   0:38.89 snapd
 1048 root      20   0  170904  17292   9488 S   0.0  0.2   0:00.24 networkd-dispat
    1 root      20   0   78020   9156   6644 S   0.0  0.1   0:22.92 systemd
12376 azure     20   0   76632   7456   6420 S   0.0  0.1   0:00.01 systemd
12374 root      20   0  107984   7312   6304 S   0.0  0.1   0:00.00 sshd
...
  • VIRT:虚拟内存,包括已申请未分配的内存。
  • RES:常驻内存,指已经分配的内存。
  • SHR:共享内存,包括动态库,程序代码段和与其他进程共同使用的共享内存等。
  • MEM:占用内存比例。
# 每隔1秒输出1组数据
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 7743608   1112  92168    0    0     0     0   52  152  0  1 100  0  0
 0  0      0 7743608   1112  92168    0    0     0     0   36   92  0  0 100  0  0
  • buff 和 cache:单位为 KB
  • bi 和 bo:分别表示块设备的读取和写入大小,单位为 KB/s

参考

倪朋飞. Linux 性能优化实战.