linux 内存管理

2021-01-30  本文已影响0人  Vackine

内存工作原理

内存管理模块是Linux系统最主要的模块,系统和应用程序的指令、数据和缓存等都是存储在内存。常说的32位或64位系统,其中32位/64位指的是单个进程可寻址的虚拟内存大小,进程在创建时,系统为进程分配了独立的连续的虚拟内存空间。在linux 的内存管理中,只有内核才能访问物理内存,所有的进程需要访问物理内存时都需要先切换到内核态才能进行访问。

不同位数的系统的虚拟地址空间:

# 32 位
+++++++++++++++++++++ 0xFFFF FFFF  
+                   +
+     内核空间1G     +
+———————————————————+ 0xC000 0000
+                   +
+                   +
+   用户空间 3G      +
+                   +
+                   +
+++++++++++++++++++++ 0x0000 0000


# 64 位
+++++++++++++++++++++ 0xFFFF FFFF FFFF FFFF
+                   +
+     内核空间128T   +
+———————————————————+ 0xFFFF 8000 0000 0000
+                   +
+    未定义          +
+-------------------+ 0x0000 7FFF FFFF F000
+                   +
+   用户空间128T     +
+++++++++++++++++++++ 0x0000 0000 0000 0000


虚拟内存空间分布


|_____________________|
|                     |
|  内核空间 1G         |
|_____________________|
|   栈空间8M           |
|_____________________|
|                     |
|   文件映射           |
|                     |
|—————————————————————|
|    堆               |
|—————————————————————|
|  数据段              |
|—————————————————————|
|  只读段              |
|_____________________|

用户空间内存,从低到高分别是五种不同的内存段。

在这五个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。

内存的分配和回收

malloc() 是 C 标准库提供的内存分配函数,对应到系统调用上,有两种实现方式,即 brk() 和 mmap()。

brk() 方式的缓存,可以减少缺页异常的发生,提高内存访问效率。不过,由于这些内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片。

而 mmap() 方式分配的内存,会在释放时直接归还系统,所以每次 mmap 都会发生缺页异常。在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大。这也是 malloc 只对大块内存使用 mmap 的原因。

同时,当这两种调用发生后,其实并没有真正分配内存。这些内存,都只在首次访问时才分配,也就是通过缺页异常进入内核中,再由内核来分配内存。

对内存来说,如果只分配而不释放,就会造成内存泄漏,甚至会耗尽系统内存。所以,在应用程序用完内存后,还需要调用 free() 或 unmap() ,来释放这些不用的内存。当然,系统也不会任由某个进程用完所有内存。在发现内存紧张时,系统就会通过一系列机制来回收内存,比如下面这三种方式:

内存查看


# 注意不同版本的free输出可能会有所不同
$ free
              total        used        free      shared  buff/cache   available
Mem:        8169348      263524     6875352         668     1030472     7611064
Swap:             0           0           0

available 不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。不过,并不是所有缓存都可以回收,因为有些缓存可能正在使用中。

# top 查看

# 按下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
...

虚拟内存通常并不会全部分配物理内存。从上面的输出,可以发现每个进程的虚拟内存都比常驻内存大得多。同时,共享内存 SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态链接库,也都算在 SHR 里。当然,SHR 也包括了进程间真正共享的内存。所以在计算多个进程的内存使用时,不要把所有进程的 SHR 直接相加得出结果。

小结

  1. 对普通进程来说,它能看到的其实是内核提供的虚拟内存,这些虚拟内存还需要通过页表,由系统映射为物理内存。

  2. 当进程通过 malloc() 申请内存后,内存并不会立即分配,而是在首次访问时,才通过缺页异常陷入内核中分配内存。

  3. 由于进程的虚拟地址空间比物理内存大很多,Linux 还提供了一系列的机制,应对内存不足的问题,比如缓存的回收、交换分区 Swap 以及 OOM 等。

  4. 当需要了解系统或者进程的内存使用情况时,可以用 free 和 top 、ps 等性能工具。它们都是分析性能问题时最常用的性能工具,要能熟练使用并真正理解各个指标的含义。

上一篇下一篇

猜你喜欢

热点阅读