那么,进程要访问内存时,该怎么办呢?Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以很方便地访问内存,更确切地说是访问虚拟内存。
独享内存空间的原理
我们可以类比一下现实世界的会议室,内存都被分成一块一块儿的,都编好了号。
页表实际上存储在 CPU 的内存管理单元 MMU 中,这样,正常情况下,处理器就可以直接通过硬件,找出要访问的内存。
当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,{BANNED}{BANNED}最佳佳后再返回用户空间,恢复进程的运行。
虚拟内存空间分布
进一步了解虚拟内存空间的分布情况,{BANNED}{BANNED}最佳佳上方的内核空间,下方的用户空间内存,其实又被分成了多个不同的段。以 32 位系统为例:
通过这张图你可以看到,用户空间内存,从低到高分别是五种不同的内存段。
? 只读段,包括代码和常量等。
? 数据段,包括全局变量等。
? 堆,包括动态分配的内存,从低地址开始向上增长。
? 文件映射段,包括动态库、共享内存等,从高地址开始向下增长。
? 栈,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB,执行ulimit -a查看如下
在这五个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。
如何查看内存使用情况
那么在了解内存的工作原理之后,我们又该怎么查看系统内存使用情况呢?
free命令
# 注意不同版本的free输出可能会有所不同
$ free
total used free shared buff/cache available
Mem: 8169348 263524 6875352 668 1030472 7611064
Swap: 0 0 0
你可以看到,free 输出的是一个表格,其中的数值都默认以字节为单位。表格总共有两行六列,这两行分别是物理内存 Mem 和交换分区 Swap 的使用情况,而六列中,每列数据的含义分别为:
? {BANNED}中国{BANNED}中国第一列,total 是总内存大小;
? 第二列,used 是已使用内存的大小,包含了共享内存;
? 第三列,free 是未使用内存的大小;
? 第四列,shared 是共享内存的大小;
? 第五列,buff/cache 是缓存和缓冲区的大小;
? {BANNED}{BANNED}最佳佳后一列,available 是新进程可用内存的大小。
这里尤其注意一下,{BANNED}{BANNED}最佳佳后一列的可用内存 available 。available 不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。不过,并不是所有缓存都可以回收,因为有些缓存可能正在使用中。
不过,我们知道,free 显示的是整个系统的内存使用情况。如果你想查看进程的内存使用情况,可以用 top 或者 ps 等工具。
top命令
下面是 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
...
top 输出界面的顶端,也显示了系统整体的内存使用情况,这些数据跟 free 类似。
接着看下面的内容,跟内存相关的几列数据,比如 VIRT、RES、SHR 以及 %MEM 等,这些数据,包含了进程{BANNED}{BANNED}最佳佳重要的几个内存使用情况。
? VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
? RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。
? SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段
? %MEM 是进程使用物理内存占系统总内存的百分比。
除了要认识这些基本信息,在查看 top 输出时,你还要注意两点。
{BANNED}中国{BANNED}中国第一,虚拟内存通常并不会全部分配物理内存。从上面的输出,你可以发现每个进程的虚拟内存都比常驻内存大得多。
第二,共享内存 SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态链接库,也都算在 SHR 里。当然,SHR 也包括了进程间真正共享的内存。所以在计算多个进程的内存使用时,不要把所有进程的 SHR 直接相加得出结果。
内存中的Buffer和Cache
# 注意不同版本的free输出可能会有所不同
$ free
total used free shared buff/cache available
Mem: 8169348 263524 6875352 668 1030472 7611064
Swap: 0 0 0
这个界面包含了物理内存 Mem 和交换分区 Swap 的具体使用情况,比如总内存、已用内存、缓存、可用内存等。其中缓存是 Buffer 和 Cache 两部分的总和 。
这里的大部分指标都比较容易理解,但 Buffer 和 Cache 可能不太好区分。从字面上来说,Buffer 是缓冲区,而 Cache 是缓存,两者都是数据在内存中的临时存储。那么,你知道这两种“临时存储”有什么区别吗?
先用 man 命令查询 free 的文档,就可以找到对应指标的详细说明 :
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
从 free 的手册中,你可以看到 buffer 和 cache 的说明。
? Buffers 是内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值。
? Cache 是内核页缓存和 Slab 用到的内存,对应的是 /proc/meminfo 中的 Cached 与 SReclaimable 之和。
这里的说明告诉我们,这些数值都来自 /proc/meminfo,但更具体的 Buffers、Cached 和 SReclaimable 的含义,还是没有说清楚。
proc 文件系统
/proc 是 Linux 内核提供的一种特殊文件系统,是用户跟内核交互的接口。比方说,用户可以从 /proc 中查询内核的运行状态和配置选项,查询进程的运行状态、统计数据等。
proc 文件系统同时也是很多性能工具的{BANNED}{BANNED}最佳佳终数据来源。比如刚才看到的 free ,就是通过读取/proc/meminfo,得到内存的使用情况。
继续说回/proc/meminfo,既然 Buffers、Cached、SReclaimable 这几个指标不容易理解,那我们还得继续查 proc 文件系统,获取它们的详细定义。
执行 man proc,你就可以得到 proc 文件系统的详细文档:
Buffers %lu
Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).
Cached %lu
In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached.
...
SReclaimable %lu (since Linux 2.6.19)
Part of Slab, that might be reclaimed, such as caches.
SUnreclaim %lu (since Linux 2.6.19)
Part of Slab, that cannot be reclaimed on memory pressure.
通过这个文档,我们可以看到:
? Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
? Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
简单来说,Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。
磁盘和文件的区别
磁盘是一个块设备,可以划分为不同的分区;在分区之上再创建文件系统,挂载到某个目录,之后才可以在这个目录中读写文件。
? 在读写普通文件时,会经过文件系统,由文件系统负责与磁盘交互
? 读写磁盘或者分区时,就会跳过文件系统,也就是所谓的“裸I/O“。
这两种读写方式所使用的缓存是不同的,也就是文中所讲的 Cache 和 Buffer 区别。
什么是Swap
当发生了内存泄漏时,或者运行了大内存的应用程序,导致系统的内存资源紧张时,系统就好自动的进行内存回收或者 OOM 杀死进程。
内存回收,也就是系统释放掉可以回收的内存,比如前面讲过的缓存和缓冲区,就属于可回收内存。它们在内存管理中,通常被叫做文件页(File-backed Page)。
大部分文件页,都可以直接回收,以后有需要时,再从磁盘重新读取就可以了。而那些被应用程序修改过,并且暂时还没写入磁盘的数据(也就是脏页),就得先写入磁盘,然后才能进行内存释放。
这些脏页,一般可以通过两种方式写入磁盘。
? 可以在应用程序中,通过系统调用 fsync ,把脏页同步到磁盘中;
? 也可以交给系统,由内核线程 pdflush 负责这些脏页的刷新。
除了缓存和缓冲区,通过内存映射获取的文件映射页,也是一种常见的文件页。它也可以被释放掉,下次再访问的时候,从文件重新读取。
除了文件页外,还有没有其他的内存可以回收呢?比如,应用程序动态分配的堆内存,也就是我们在内存管理中说到的匿名页(Anonymous Page),是不是也可以回收呢?
这些内存它们很可能还要再次被访问,自然不能直接释放。
但是,如果这些内存在分配后很少被访问,似乎也是一种资源浪费。是不是可以把它们暂时先存在磁盘里,释放内存给其他更需要的进程?
这正是 Linux 的 Swap 机制。Swap 把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。
Swap 原理
前面提到,Swap 说白了就是把一块磁盘空间或者一个本地文件,当成内存来使用。它包括换出和换入两个过程。
? 所谓换出,就是把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存。
? 换入,则是在进程再次访问这些内存的时候,把它们从磁盘读到内存中来。
所以Swap 其实是把系统的可用内存变大了。这样,即使服务器的内存不足,也可以运行大内存的应用程序。
既然 Swap 是为了回收内存,那么 Linux 到底在什么时候需要回收内存呢?前面一直在说内存资源紧张,又该怎么来衡量内存是不是紧张呢?
一个{BANNED}{BANNED}最佳佳容易想到的场景就是,有新的大块内存分配请求,但是剩余内存不足。这个时候系统就需要回收一部分内存(比如前面提到的缓存),进而尽可能地满足新内存请求。这个过程通常被称为直接内存回收。
除了直接内存回收,还有一个专门的内核线程用来定期回收内存,也就是 kswapd0。为了衡量内存的使用情况,kswapd0 定义了三个内存阈值(watermark,也称为水位),分别是页{BANNED}{BANNED}最佳佳小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high)。剩余内存,则使用 pages_free 表示。
kswapd0 定期扫描内存的使用情况,并根据剩余内存落在这三个阈值的空间位置,进行内存的回收操作。
? 剩余内存小于页{BANNED}{BANNED}最佳佳小阈值,说明进程可用内存都耗尽了,只有内核才可以分配内存。
? 剩余内存落在页{BANNED}{BANNED}最佳佳小阈值和页低阈值中间,说明内存压力比较大,剩余内存不多了。这时 kswapd0 会执行内存回收,直到剩余内存大于高阈值为止。
? 剩余内存落在页低阈值和页高阈值中间,说明内存有一定压力,但还可以满足新内存请求。
? 剩余内存大于页高阈值,说明剩余内存比较多,没有内存压力。
我们可以看到,一旦剩余内存小于页低阈值,就会触发内存的回收。这个页低阈值,其实可以通过内核选项 /proc/sys/vm/min_free_kbytes 来间接设置。min_free_kbytes 设置了页{BANNED}{BANNED}最佳佳小阈值,而其他两个阈值,都是根据页{BANNED}{BANNED}最佳佳小阈值计算生成的,计算方法如下 :
pages_low = pages_min*5/4
pages_high = pages_min*3/2
swappiness
回收的内存既包括了文件页,又包括了匿名页。
? 对文件页的回收,当然就是直接回收缓存,或者把脏页写回磁盘后再回收。
? 而对匿名页的回收,其实就是通过 Swap 机制,把它们写入磁盘后再释放内存。
不过,你可能还有一个问题。既然有两种不同的内存回收机制,那么在实际回收内存时,到底该先回收哪一种呢?
其实,Linux 提供了一个 /proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度。
swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。
虽然 swappiness 的范围是 0-100,不过要注意,这并不是内存的百分比,而是调整 Swap 积极程度的权重,即使你把它设置成 0,当剩余内存 + 文件页小于页高阈值时,还是会发生 Swap。
小结
为了在多进程环境下,使得进程之间的内存地址不受影响,相互隔离,于是操作系统就为每个进程独立分配一套的虚拟地址空间,每个程序只关心自己的虚拟地址就可以,实际上大家的虚拟地址都是一样的,但分布到物理地址内存是不一样的。
Buffer 和 Cache 分别缓存磁盘和文件系统的读写数据。
? 从写的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作。
? 从读的角度来说,既可以加速读取那些需要频繁访问的数据,也降低了频繁 I/O 对磁盘的压力。
作为程序,也不用关心物理地址的事情。每个进程都有自己的虚拟空间,而物理内存只有一个,所以当启用了大量的进程,物理内存必然会很紧张,于是操作系统会通过内存交换swap技术,把不常使用的内存暂时存放到硬盘,在需要的时候再装载回物理内存。
阅读(439) | 评论(0) | 转发(0) |