一、Buffer Cache
1、free命令
提到查看linux主机内存,我们总会想到free命令也一般用该命令,如下面的输出:
# free
total used free shared buff/cache available
Mem: 8010456 3635428 207372 622640 4167656 3446408
Swap: 1407952 157028 1250924
free 命令显示系统内存的使用情况,包括物理内存、交换内存(swap)和内核缓冲区内存,比如总内存、已用内存、缓存、可用内存等。其中缓存是 Buffer 和 Cache 两部分的总和 。
- Mem 行(第二行)是内存的使用情况。
- Swap 行(第三行)是交换空间的使用情况。
- total 列显示系统总的可用物理内存和交换空间大小。
- used 列显示已经被使用的物理内存和交换空间。
- free 列显示还有多少物理内存和交换空间可用使用。
- shared 列显示被共享使用的物理内存大小。
- buff/cache 列显示被 buffer 和 cache 使用的物理内存大小。
- available 列显示还可以被应用程序使用的物理内存大小。
1、语法
free(选项)
2、选项
-b # 以Byte为单位显示内存使用情况;
-k # 以KB为单位显示内存使用情况;
-m # 以MB为单位显示内存使用情况;
-g # 以GB为单位显示内存使用情况。
-o # 不显示缓冲区调节列;
-s<间隔秒数> # 持续观察内存使用状况;
-t # 显示内存总和列;
-V # 显示版本信息。
3、实例
free -t # 以总和的形式显示内存的使用信息
free -s 10 # 周期性的查询内存使用信息,每10s 执行一次命令
显示内存使用情况
free -m
total used free shared buffers cached
Mem: 2016 1973 42 0 163 1497
-/+ buffers/cache: 312 1703
Swap: 4094 0 4094
第一部分 Mem 行解释:
total:内存总数;
used:已经使用的内存数;
free:空闲的内存数;
shared:当前已经废弃不用;
buffers Buffer:缓存内存数;
cached Page:缓存内存数。
关系:total = used + free
第二部分(-/+ buffers/cache)解释:
(-buffers/cache) used内存数:第一部分Mem行中的 used – buffers – cached
(+buffers/cache) free内存数: 第一部分Mem行中的 free + buffers + cached
可见-buffers/cache 反映的是被程序实实在在吃掉的内存,而+buffers/cache 反映的是可以挪用的内存总数。
第三部分是指交换分区。
输出结果的第四行是交换分区 SWAP 的,也就是我们通常所说的虚拟内存。
区别:第二行(mem)的 used/free 与第三行(-/+ buffers/cache) used/free 的区别。 这两个的区别在于使用的角度来看,第一行是从 OS 的角度来看,因为对于 OS,buffers/cached 都是属于被使用,所以他的可用内存是 2098428KB,已用内存是 30841684KB,其中包括,内核(OS)使用+Application(X, oracle,etc)使用的+buffers+cached.
第三行所指的是从应用程序角度来看,对于应用程序来说,buffers/cached 是等于可用的,因为 buffer/cached 是为了提高文件读取的性能,当应用程序需在用到内存的时候,buffer/cached 会很快地被回收。
所以从应用程序的角度来说,可用内存=系统 free memory+buffers+cached。
如本机情况的可用内存为:
18007156=2098428KB+4545340KB+11363424KB
接下来解释什么时候内存会被交换,以及按什么方交换。
当可用内存少于额定值的时候,就会开会进行交换。如何看额定值:
# cat /proc/meminfo
MemTotal: 16140816 kB
MemFree: 816004 kB
MemAvailable: 2913824 kB
Buffers: 17912 kB
Cached: 2239076 kB
SwapCached: 0 kB
Active: 12774804 kB
Inactive: 1594328 kB
Active(anon): 12085544 kB
Inactive(anon): 94572 kB
Active(file): 689260 kB
Inactive(file): 1499756 kB
Unevictable: 116888 kB
Mlocked: 116888 kB
SwapTotal: 8191996 kB
SwapFree: 8191996 kB
Dirty: 56 kB
Writeback: 0 kB
AnonPages: 12229228 kB
Mapped: 117136 kB
Shmem: 58736 kB
Slab: 395568 kB
SReclaimable: 246700 kB
SUnreclaim: 148868 kB
KernelStack: 30496 kB
PageTables: 165104 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 16262404 kB
Committed_AS: 27698600 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 311072 kB
VmallocChunk: 34350899200 kB
HardwareCorrupted: 0 kB
AnonHugePages: 3104768 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 225536 kB
DirectMap2M: 13279232 kB
DirectMap1G: 5242880 kB
交换将通过三个途径来减少系统中使用的物理页面的个数:
- 减少缓冲与页面 cache 的大小,
- 将系统 V 类型的内存页面交换出去,
- 换出或者丢弃页面。(Application 占用的内存页,也就是物理内存不足)。
事实上,少量地使用 swap 是不是影响到系统性能的。
那 buffers 和 cached 都是缓存,两者有什么区别呢?
为了提高磁盘存取效率, Linux 做了一些精心的设计, 除了对 dentry 进行缓存(用于 VFS,加速文件路径名到 inode 的转换), 还采取了两种主要 Cache 方式:
Buffer Cache 和 Page Cache。前者针对磁盘块的读写,后者针对文件 inode 的读写。这些 Cache 有效缩短了 I/O 系统调用(比如 read,write,getdents)的时间。
磁盘的操作有逻辑级(文件系统)和物理级(磁盘块),这两种 Cache 就是分别缓存逻辑和物理级数据的。
Page cache 实际上是针对文件系统的,是文件的缓存,在文件层面上的数据会缓存到 page cache。文件的逻辑层需要映射到实际的物理磁盘,这种映射关系由文件系统来完成。当 page cache 的数据需要刷新时,page cache 中的数据交给 buffer cache,因为 Buffer Cache 就是缓存磁盘块的。但是这种处理在 2.6 版本的内核之后就变的很简单了,没有真正意义上的 cache 操作。
Buffer cache 是针对磁盘块的缓存,也就是在没有文件系统的情况下,直接对磁盘进行操作的数据会缓存到 buffer cache 中,例如,文件系统的元数据都会缓存到 buffer cache 中。
简单说来,page cache 用来缓存文件数据,buffer cache 用来缓存磁盘数据。在有文件系统的情况下,对文件操作,那么数据会缓存到 page cache,如果直接采用 dd 等工具对磁盘进行读写,那么数据会缓存到 buffer cache。
所以我们看 linux,只要不用 swap 的交换空间,就不用担心自己的内存太少.如果常常 swap 用很多,可能你就要考虑加物理内存了.这也是 linux 看内存是否够用的标准.
如果是应用服务器的话,一般只看第二行,+buffers/cache,即对应用程序来说 free 的内存太少了,也是该考虑优化程序或加内存了。
2、buffer和cache的数据来源及含义
1、数据来源
从字面上来说,Buffer 是缓冲区,而 Cache 是缓存,两者都是数据在内存中的临时存储。那么,这两种“临时存储”有什么区别吗?free出来的数据是来自哪里呢?
用 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 的含义又是什么呢?往下看。
2、Buffers、Cached 和 SReclaimable
/proc 是 Linux 内核提供的一种特殊文件系统,是用户跟内核交互的接口。比方说,用户可以从 /proc 中查询内核的运行状态和配置选项,查询进程的运行状态、统计数据等,也可以通过 /proc 来修改内核的配置。proc 文件系统同时也是很多性能工具的最终数据来源。比如 free 就是通过读取/proc/meminfo,得到内存的使用情况。继续说回/proc/meminfo,既然 Buffers、Cached、SReclaimable 这几个指标不容易理解,还得继续查 proc 文件系统,获取它们的详细定义。
/proc/meminfo
This file reports statistics about memory usage on the system. It is used by free(1) to report the amount of free and used memory (both physical and swap) on the system as well as the shared memory and buffers used by the kernel.
Each line of the file consists of a parameter name, followed by a colon, the value of the parameter,and an option unit of measurement (e.g., "kB"). The list below describes the parameter names and the format specifier required to read the field value.
Except as noted below, all of the fields have been present since at least Linux 2.6.0. Some fileds are displayed only if the kernel was configured with various options; those dependencies are noted in the list.
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.
SwapCached %lu
Memory that once was swapped out, is swapped back in but still also is in the swap file. (If memory pressure is high, these pages don't need to be swapped out
again because they are already in the swap file. This saves I/O.)
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 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
- SReclaimable 是 Slab 的一部分。Slab 包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。
3、其他注意点
文档上只提到,Cache 是文件读的缓存
写文件时会用到 Cache 缓存数据,而写磁盘则会用到 Buffer 来缓存数据。读文件时数据会缓存到 Cache 中,而读磁盘时数据会缓存到 Buffer 中。
虽然文档提供了对 Buffer 和 Cache 的说明,但是仍不能覆盖到所有的细节:
- Buffer 既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。
- Cache 既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。这样,我们就回答了案例开始前的两个问题。
简单来说,Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。
Buffer 和 Cache 分别缓存磁盘和文件系统的读写数据。
- 从写的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作。
- 从读的角度来说,既可以加速读取那些需要频繁访问的数据,也降低了频繁 I/O 对磁盘的压力。
3、如何利用Buffer 和 Cache优化程序的运行效率?
1、缓存命中率
该处的“缓存”,通指数据在内存中的临时存储。
所谓缓存命中率,是指直接通过缓存获取数据的请求次数,占所有数据请求次数的百分比。用来衡量缓存使用的好坏,评估提升程序的运行效率的效果。
命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好。
在现在所有的高并发系统中,缓存是必不可少,更是必不可缺的核心模块,主要作用就是把经常访问的数据(也就是热点数据),提前读入到内存中。这样,下次访问时就可以直接从内存读取数据,而不需要经过硬盘,从而加快应用程序的响应速度。
这些独立的缓存模块通常会提供查询接口,方便我们随时查看缓存的命中情况。不过 Linux 系统中并没有直接提供这些接口,主要通过cachestat 和 cachetop 查看查看系统缓存命中情况。
- cachestat 提供了整个操作系统缓存的读写命中情况。
- cachetop 提供了每个进程的缓存命中情况。
注:
cachestat 和 cachetop工具都是 bcc 软件包的一部分,它们基于 Linux 内核的 eBPF(extended Berkeley Packet Filters)机制,来跟踪内核中管理的缓存,并输出缓存的使用和命中情况。使用前要安装 bcc 软件包,bcc-tools 需要内核版本为 4.1 或者更新的版本
CentOS:需要手动升级内核后再安装
yum update -y
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh https://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
yum remove -y kernel-headers kernel-tools kernel-tools-libs
yum --enablerepo=elrepo-kernel install -y kernel-ml kernel-ml-devel kernel-ml-headers
# 更新 Grub 后重启
grub2-mkconfig -o /boot/grub2/grub.cfg
grub2-set-default 0
reboot
uname -r
yum install -y bcc-tools
export PATH=$PATH:/usr/share/bcc/tools
运行 cachestat 和 cachetop 命令:
(1)cachestat 的运行界面,它以 1 秒的时间间隔,输出了 3 组缓存统计数据:
cachestat 1 3
TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB
2 0 2 1 17 279
2 0 2 1 17 279
2 0 2 1 17 279
cachestat 的输出其实是一个表格。每行代表一组数据,而每一列代表不同的缓存统计指标。这些指标从左到右依次表示:
- TOTAL ,表示总的 I/O 次数;
- MISSES ,表示缓存未命中的次数;
- HITS ,表示缓存命中的次数;
- DIRTIES, 表示新增到缓存中的脏页数;
- BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位;
- CACHED_MB 表示 Cache 的大小,以 MB 为单位。
(2)cachetop 的运行界面
cachetop
11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
13029 root python 1 0 0 100.0% 0.0%
2、指定文件的缓存大小
使用 pcstat 这个工具,来查看文件在内存中的缓存大小以及缓存比例。pcstat 是一个基于 Go 语言开发的工具,所以安装它之前,你首先应该安装 Go 语言。
安装完 Golang,再运行下面的命令安装 pcstat:
$ export GOPATH=~/go
$ export PATH=~/go/bin:$PATH
$ go get golang.org/x/sys/unix
$ go get github.com/tobert/pcstat/pcstat
运行 pcstat 来查看文件的缓存情况了。比如,下面就是一个 pcstat 运行的示例,它展示了 /bin/ls 这个文件的缓存情况:
pcstat /bin/ls
+---------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|---------+----------------+------------+-----------+---------|
| /bin/ls | 133792 | 33 | 0 | 000.000 |
+---------+----------------+------------+-----------+---------+
输出中,Cached 就是 /bin/ls 在缓存中的大小,而 Percent 则是缓存的百分比。你看到它们都是 0,这说明 /bin/ls 并不在缓存中。
接着,如果你执行一下 ls 命令,再运行相同的命令来查看的话,就会发现 /bin/ls 都在缓存中了:
ls
pcstat /bin/ls
+---------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|---------+----------------+------------+-----------+---------|
| /bin/ls | 133792 | 33 | 33 | 100.000 |
+---------+----------------+------------+-----------+---------+
4、总结
Buffer 和 Cache 分别缓存磁盘和文件系统的读写数据, 可以极大提升系统的 I/O 性能。
从写的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作。
从读的角度来说,既可以加速读取那些需要频繁访问的数据,也降低了频繁 I/O 对磁盘的压力。
通常,我们用缓存命中率,来衡量缓存的使用效率。命中率越高,表示缓存被利用得越充分,应用程序的性能也就越好。
可以用 cachestat 和 cachetop 这两个工具,观察系统和进程的缓存命中情况。其中,cachestat 提供了整个系统缓存的读写命中情况。cachetop 提供了每个进程的缓存命中情况。
不过要注意,Buffers 和 Cache 都是操作系统来管理的,应用程序并不能直接控制这些缓存的内容和生命周期。
所以,在应用程序开发中,一般要用专门的缓存组件,来进一步提升性能。比如,程序内部可以使用堆或者栈明确声明内存空间,来存储需要缓存的数据,或使用 Redis 这类外部缓存服务,优化数据的访问效率。
二、HugePages(大内存页)
1、HugePages 原理
有些场景我们希望使用更大的内存页作为映射单位(如 2MB)。使用更大的内存页作为映射单位有如下好处:
- 减少 TLB(Translation Lookaside Buffer) 的失效情况。
- 减少 页表 的内存消耗。
- 减少 PageFault(缺页中断)的次数。
Tips:TLB 是一块高速缓存,TLB 缓存虚拟内存地址与其映射的物理内存地址。MMU 首先从 TLB 查找内存映射的关系,如果找到就不用回溯查找页表。否则,只能根据虚拟内存地址,去页表中查找其映射的物理内存地址。
因为映射的内存页越大,所需要的 页表 就越小(很容易理解);页表 越小,TLB 失效的情况就越少。
使用大于 4KB 的内存页作为内存映射单位的机制叫 HugePages,目前 Linux 常用的 HugePages 大小为 2MB 和 1GB,我们以 2MB 大小的内存页作为例子。
要映射更大的内存页,只需要增加偏移量部分,如图所示:
如 图3 所示,现在把偏移量部分扩展到 21 位(页表部分被覆盖了,21 位能够表示的大小范围为 0 ~ 2MB),所以 页中间目录 直接指向映射的 物理内存页地址。
这样,就可以减少 页表 部分的内存消耗。由于内存映射关系变少,所以 TLB 失效的情况也会减少。
2、HugePages 使用
了解了 HugePages 的原理后,我们来介绍一下怎么使用 HugePages。
HugePages 的使用不像普通内存申请那么简单,而是需要借助 Hugetlb文件系统 来创建,下面将会介绍 HugePages 的使用步骤:
1、挂载 Hugetlb 文件系统
Hugetlb 文件系统是专门为 HugePages 而创造的,我们可以通过以下命令来挂载一个 Hugetlb 文件系统:
$ mkdir /mnt/huge
$ mount none /mnt/huge -t hugetlbfs
执行完上面的命令后,我们就在 /mnt/huge 目录下挂载了 Hugetlb 文件系统。
2、声明可用 HugePages 数量
要使用 HugePages,首先要向内核声明可以使用的 HugePages 数量。/proc/sys/vm/nr_hugepages 文件保存了内核可以使用的 HugePages 数量,我们可以使用以下命令设置新的可用 HugePages 数量:
免登录复制
$ echo 20 > /proc/sys/vm/nr_hugepages
上面命令设置了可用的 HugePages 数量为 20 个(也就是 20 个 2MB 的内存页)。
3、编写申请 HugePages 的代码
要使用 HugePages,必须使用 mmap 系统调用把虚拟内存映射到 Hugetlb 文件系统中的文件,如下代码:
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdio.h>
#define MAP_LENGTH (10*1024*1024) // 10MB
int main()
{
int fd;
void * addr;
// 1. 创建一个 Hugetlb 文件系统的文件
fd = open("/mnt/huge/hugepage1", O_CREAT|O_RDWR);
if (fd < 0) {
perror("open()");
return -1;
}
// 2. 把虚拟内存映射到 Hugetlb 文件系统的文件中
addr = mmap(0, MAP_LENGTH, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap()");
close(fd);
unlink("/mnt/huge/hugepage1");
return -1;
}
strcpy(addr, "This is HugePages example...");
printf("%s\n", addr);
// 3. 使用完成后,解除映射关系
munmap(addr, MAP_LENGTH);
close(fd);
unlink("/mnt/huge/hugepage1");
return 0;
}
编译上面的代码并且执行,如果没有问题,将会输出以下信息:
This is HugePages example...
三、ksmd
1、Ksm介绍
2.6.32内核引入了KSM(Kernel Samepage Merging)允许这个系统管理程序通过合并内存页面来增加并发虚拟机的数量。VMware 的 ESX 服务器系统管理程序将这个特性命名为 Transparent Page Sharing (TPS),而 XEN 将其称为 MemoryCoW。不管采用哪种名称和实现,这个特性都提供了更好的内存利用率,从而允许操作系统(KVM 的系统管理程序)过量使用内存,支持更多的应用程序或 VM。
假如操作系统和应用程序代码以及常量数据在 VMs 之间相同,那么这个特点就很有用。当页面惟一时,它们可以被合并,从而释放内存,供其他应用程序使用。
一台主机(Host)同时运行好几个相同类型的实例(guests),通过这种技术共享相同代码,比如每个guest的核心代码,那么随着guest实例增加,Host内存不会急剧的下降,有效的增加Host的provisioning能力。同时释放出内存,供其他系统或程序使用。这也是我们如何能够让一台16G内存的Server跑起52台1G内存XP系统的方法。VMware 的 ESX/ESXi 将这个特性定义为 Transparent Page Sharing (TPS),而 XEN 则将其称为 MemoryCoW (Copy-on-Write )。存储技术中的这种技术我们称为去耦合(de-duplication)。去耦合这种技术通过删除冗余数据(基于数据块,或者基于更大的数据片段,比如文件)来减少已存储的数据。公共数据片段被合并(以一种 copy-on-write [CoW] 方式),释放空间供其他用途。使用这种方法,存储成本更低,最终需要的存储器也更少。鉴于当前的数据增长速度,这个功能显得非常重要。
尽管 Linux 中的内存共享在虚拟环境中有优势(KSM 最初设计用于基于内核的虚拟机),但它在非虚拟环境中仍然有用。事实上,KSM 甚至在嵌入式 Linux 系统中也有用处,表明了这种方法的灵活性。
KSM 作为内核中的守护进程(称为 ksmd)存在。它定期执行页面扫描,识别副本页面并合并副本,释放这些页面以供它用。
KSM 执行上述操作的过程对用户透明。例如,副本页面被合并,然后被标记为只读,但是,如果这个页面的其中一个用户由于某种原因更改该页面,该用户将以 CoW 方式收到自己的副本。可以在内核源代码 ./mm/ksm.c 中找到 KSM 内核模块的完整实现。
KSM 应用程序编程接口(API)通过 madvise 系统调用和一个新的推荐参数MADV_MERGEABLE(表明已定义的区域可以合并)来实现。可以通过 MADV_UNMERGEABLE 参数(立即从一个区域取消合并任何已合并页面)从可合并状态删除一个区域。注意,通过 madvise 来删除一个页面区域可能会导致一个 EAGAIN 错误,因为该操作可能会在取消合并过程中耗尽内存,从而可能会导致更大的麻烦(内存不足情况)。
一旦某个区域被定义为 “可合并”,KSM 将把该区域添加到它的工作内存列表。启用 KSM 时,它将搜索相同的页面,以写保护的 CoW 方式保留一个页面,释放另一个页面以供它用。
KSM 使用的方法与内存去耦合中使用的方法不同。在传统的去耦合中,对象被散列化,然后使用散列值进行初始相似性检查。当散列值一致时,下一步是进行一个实际对象比较(本例中是一个内存比较),以便正式确定这些对象是否一致。KSM 在它的第一个实现中采用这种方法,但后来开发了一种更直观的方法来简化它。
在当前的 KSM 中,页面通过两个 “红-黑” 树管理,其中一个 “红-黑” 树是临时的。第一个树称为不稳定树,用于存储还不能理解为稳定的新页面。换句话说,作为合并候选对象的页面(在一段时间内没有变化)存储在这个不稳定树中。不稳定树中的页面不是写保护的。第二个树称为稳定树,存储那些已经发现是稳定的且通过 KSM 合并的页面。为确定一个页面是否是稳定页面,KSM 使用了一个简单的 32 位校验和(checksum)。当一个页面被扫描时,它的校验和被计算且与该页面存储在一起。在一次后续扫描中,如果新计算的校验和不等于此前计算的校验和,则该页面正在更改,因此不是一个合格的合并候选对象。
使用 KSM 进程处理一个单一的页面时,第一步是检查是否能够在稳定树中发现该页面。搜索稳定树的过程很有趣,因为每个页面都被视为一个非常大的数字(页面的内容)。一个 memcmp(内存比较)操作将在该页面和相关节点的页面上执行。如果 memcmp 返回 0,则页面相同,发现一个匹配值。反之,如果 memcmp 返回 -1,则表示候选页面小于当前节点的页面;如果返回 1,则表示候选页面大于当前节点的页面。尽管比较 4KB 的页面似乎是相当重量级的比较,但是在多数情况下,一旦发现一个差异,memcmp 将提前结束。
如果候选页面位于稳定树中,则该页面被合并,候选页面被释放。反之,如果没有发现候选页面,则应转到不稳定树。
在不稳定树中搜索时,第一步是重新计算页面上的校验和。如果该值与原始校验和不同,则本次扫描的后续搜索将抛弃这个页面(因为它更改了,不值得跟踪)。如果校验和没有更改,则会搜索不稳定树以寻找候选页面。不稳定树的处理与稳定树的处理有一些不同。第一,如果搜索代码没有在不稳定树中发现页面,则在不稳定树中为该页面添加一个新节点。但是如果在不稳定树中发现了页面,则合并该页面,然后将该节点迁移到稳定树中。
当扫描完成时,稳定树被保存下来,但不稳定树则被删除并在下一次扫描时重新构建。这个过程大大简化了工作,因为不稳定树的组织方式可以根据页面的变化而变化。由于稳定树中的所有页面都是写保护的,因此当一个页面试图被写入时将生成一个页面故障,从而允许 CoW 进程为写入程序取消页面合并(break_cow())。稳定树中的孤立页面将在稍后被删除。
2、KSM使用
KSM 的管理和监控通过sysfs(位于根 /sys/kernel/mm/ksm)执行。在这个 sysfs 子目录中,有些用于开关、控制和监控。
# cat /sys/kernel/mm/ksm/
full_scans pages_shared pages_unshared sleep_millisecs stable_node_dups
max_page_sharing pages_sharing pages_volatile stable_node_chains
merge_across_nodes pages_to_scan run stable_node_chains_prune_millisecs
1、开关:
Run文件用于启用和禁用 KSM 的页面合并。默认情况下,KSM 被禁用(0),但可以通过将一个 1 写入这个文件来启用 KSM 守护进程(例如,echo 1 > sys/kernel/mm/ksm/run)。通过写入一个 0,可以从运行状态禁用这个守护进程(但是保留合并页面的当前集合)。另外,通过写入一个 2,可以从运行状态(1)停止 KSM 并请求取消合并所有合并页面。
2、控制:
KSM 运行时,可以通过 其中3 个参数(sysfs 中的文件)来控制它。sleep_millisecs文件定义执行另一次页面扫描前 ksmd 休眠的毫秒数默认20ms。最后,pages_to_scan 文件定义一次给定扫描中可以扫描的页面数,默认100。max_kernel_pages为最多合并页面数
3、监控:
还有 5 个通过 sysfs 导出的可监控文件(均为只读),它们表明 ksmd 的运行情况和效果。
full_scans :表明已经执行的全区域扫描的次数
pages_shared: stable稳定树的节点数(共享后的物理页面数)。
pages_sharing:表示被共享的物理页面数。(例如将3个相同的页面合并成1个页面,则pages_shared=1,pages_sharing=2,两者比例体现了页面共享效率)
pages_unshared:ksm的暂未共享页面数,即unstable不稳定树的节点数。
ksm_rmap_items:反向映射条目数,可用来计算频繁改变的页面的数量。计算方式: ksm_pages_volatile =ksm_rmap_items - ksm_pages_shared-ksm_pages_sharing - ksm_pages_unshared
运行前检查配置项CONFIG_KSM是否配置
# mount-n -t sysfs none /sys
# echo 1> /sys/kernel/mm/ksm/run
# cat/sys/kernel/mm/ksm/run
1
#free
total used free shared buffers
Mem: 2076068 11228 2064840 0 0
Swap: 0 0 0
Total: 2076068 11228 2064840
# ./ksm200 3 & 200表示申请内存大小200M,3表示内存填充的值
# cat/sys/kernel/mm/ksm/pages_shared
1
# cat/sys/kernel/mm/ksm/pages_volatile
0
# cat/sys/kernel/mm/ksm/pages_sharing
51199
# cat/sys/kernel/mm/ksm/full_scans
3
# cat/sys/kernel/mm/ksm/full_scans
4
# cat/sys/kernel/mm/ksm/pages_volatile
0
# cat/sys/kernel/mm/ksm/pages_unshared
0
可见这场景下有效的节省了内存,申请200M物理页面,实际只占用了1个页。
评论区