欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

Linux 内存管理

程序员文章站 2022-07-02 21:46:51
内存映射 Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以很方便地访问内存,更确切地说是访问虚拟内存。 虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同字长(也就是单个 CPU 指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。比 ......

内存映射

linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以很方便地访问内存,更确切地说是访问虚拟内存。

虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同字长(也就是单个 cpu 指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。比如最常见的 32 位和 64 位系统,如图:
Linux 内存管理

并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。

内存映射,其实就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张页表(页表实际上存储在 cpu 的内存管理单元 mmu 中),记录虚拟地址与物理地址的映射关系。

当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。(内存调用,都只在首次访问时才分配,也就是通过缺页异常进入内核中,再由内核来分配内存。

多级页表和大页

mmu 规定了一个内存映射的最小单位,也就是页,通常是 4 kb 大小。这样,每一次内存映射,都需要关联 4 kb 或者 4kb 整数倍的内存空间。

多级页表(multilevel page tables)就是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移。由于虚拟内存空间通常只用了很少一部分,那么,多级页表就只保存这些使用中的区块,这样就可以大大地减少页表的项数。linux 用的正是四级页表来管理内存页。如下图所示,虚拟地址被分为 5 个部分,前 4 个表项用于选择页,而最后一个索引表示页内偏移。
Linux 内存管理

大页(hugepage)就是比普通页更大的内存块,常见的大小有 2mb 和 1gb。大页通常用在使用大量内存的进程上,比如 oracle、dpdk 等。

虚拟内存空间分布

Linux 内存管理

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

内存回收机制

系统不会任由某个进程用完所有内存。在发现内存紧张时,系统就会通过一系列机制来回收内存:

  • 回收缓存,比如使用 lru(least recently used)算法,回收最近使用最少的内存页面;
  • 回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中;

回收不常访问的内存时,会用到交换分区(以下简称 swap)。swap 其实就是把一块磁盘空间当成内存来用。它可以把进程暂时不用的数据存储到磁盘中(这个过程称为换出),当进程访问这些内存时,再从磁盘读取这些数据到内存中(这个过程称为换入)。

  • 杀死进程,内存紧张时系统还会通过 oom(out of memory),直接杀掉占用大量内存的进程。

oom它监控进程的内存使用情况,并且使用 oom_score 为每个进程的内存使用情况进行评分。管理员可以通过 /proc 文件系统,手动设置进程的 oom_adj ,从而调整进程的 oom_score。

内存工具

free

[root@k8s ~]# watch -d free

every 2.0s: free                                                                                                                                          wed apr  8 15:59:31 2020

              total        used        free	 shared  buff/cache   available
mem:        8173864     4094104      276572	 436676     3803188     3333024
swap:             0           0           0

  • 第一列,total 是总内存大小;
  • 第二列,used 是已使用内存的大小,包含了共享内存;
  • 第三列,free 是未使用内存的大小;
  • 第四列,shared 是共享内存的大小;
  • 第五列,buff/cache 是缓存和缓冲区的大小;
  • 最后一列,available 是新进程可用内存的大小(包括了可回收的缓存,所以一般会比未使用内存更大)。

top

[root@k8s ~]# top
…………
kib mem :  8173864 total,   275696 free,  4094212 used,  3803956 buff/cache
kib swap:        0 total,        0 free,        0 used.  3332920 avail mem

 pid user      pr  ni    virt    res    shr s  %cpu %mem     time+ command
 3482 root      20   0 2430460   1224    760 s  85.1  0.0   3557:06 kswapd0
 
 …………
  • virt 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
  • res 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 swap 和共享内存。
  • shr 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
  • %mem 是进程使用物理内存占系统总内存的百分比。

buffer 和 cache

  • 为了协调 cpu 与磁盘间的性能差异,linux 还会使用 cache 和 buffer ,分别把文件和磁盘读写的数据缓存到内存中。
  • buffers memory used by kernel buffers (buffers in /proc/meminfo)
  • cache memory used by the page cache and slabs (cached and sreclaimable in /proc/meminfo)
  • buff/cache sum of buffers and cache

man proc 对 proc 文件系统的说明

  • buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20mb 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
  • cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
  • sreclaimable 是 slab 的一部分。slab 包括两部分,其中的可回收部分,用 sreclaimable 记录;而不可回收部分,用 sunreclaim 记录。

buffer 是对磁盘数据的缓存,而 cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。

总结

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

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

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

buffer 和 cache 分别缓存磁盘和文件系统的读写数据。

  • 从写的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作。
  • 从读的角度来说,既可以加速读取那些需要频繁访问的数据,也降低了频繁 i/o 对磁盘的压力。

整理自极客时间:《linux性能优化实战》