通过读取 /proc/meminfo
文件,能够实时获取系统内存情况
Page Cache =
Buffers + Cached + SwapCached =
Active(file) + Inactive(file) + Shmem + SwapCached
page 是内存管理分配的基本单位, Page Cache 由多个 page 构成。page 在操作系统中通常为 4KB 大小(32bits/64bits),而 Page Cache 的大小则为 4KB 的整数倍。
但并不是所有 page 都被组织为 Page Cache。
Linux 系统上供用户可访问的内存分为两个类型,即:
为什么 Linux 不把 Page Cache 称为 block cache?
这是因为从磁盘中加载到内存的数据不仅仅放在 Page Cache 中,还放在 buffer cache 中。
例如通过 Direct I/O 技术的磁盘文件就不会进入 Page Cache 中。
比较一下 File-backed pages 与 Anonymous pages 在 Swap 机制下的性能。
当内存不够用时,内存管理单元(MMU)需要提供调度算法来回收相关内存空间。内存空间回收的方式通常就是 swap,即交换到持久化存储设备上。
File-backed pages(Page Cache)的内存回收代价较低。Page Cache 通常对应于一个文件上的若干顺序块,因此可以通过顺序 I/O 的方式落盘。另一方面,如果 Page Cache 上没有进行写操作(所谓的没有脏页),甚至不会将 Page Cache 回盘,因为数据的内容完全可以通过再次读取磁盘文件得到。
Page Cache 的主要难点在于脏页回盘。
Anonymous pages 的内存回收代价较高。这是因为 Anonymous pages 通常随机地写入持久化交换设备。另一方面,无论是否有写操作,为了确保数据不丢失,Anonymous pages 在 swap 时必须持久化到磁盘。
Swap 机制指的是当物理内存不够用,内存管理单元(MMU)需要提供调度算法来回收相关内存空间,然后将清理出来的内存空间给当前内存申请方。
Swap 机制存在的本质原因是 Linux 系统提供了虚拟内存管理机制,每一个进程认为其独占内存空间,因此所有进程的内存空间之和远远大于物理内存。所有进程的内存空间之和超过物理内存的部分就需要交换到磁盘上。
操作系统以 page 为单位管理内存,当进程发现需要访问的数据不在内存时,操作系统可能会将数据以页的方式加载到内存中。上述过程被称为缺页中断,当操作系统发生缺页中断时,就会通过系统调用将 page 再次读到内存中。
但主内存的空间是有限的,当主内存中不包含可以使用的空间时,操作系统会从选择合适的物理内存页驱逐回磁盘,为新的内存页让出位置,选择待驱逐页的过程在操作系统中叫做页面替换(Page Replacement),替换操作又会触发 swap 机制。
如果物理内存足够大,那么可能不需要 Swap 机制,但是 Swap 在这种情况下还是有一定优势:对于有发生内存泄漏几率的应用程序(进程),Swap 交换分区更是重要,这可以确保内存泄露不至于导致物理内存不够用,最终导致系统崩溃。但内存泄露会引起频繁的 swap,此时非常影响操作系统的性能。
Linux 通过一个 swappiness 参数来控制 Swap 机制:这个参数值可为 0-100,控制系统 swap 的优先级:
为什么 SwapCached 也是 Page Cache 的一部分?
这是因为当匿名页(Inactive(anon) 以及 Active(anon))先被交换(swap out)到磁盘上后,然后再加载回(swap in)内存中,由于读入到内存后原来的 Swap File 还在,所以 SwapCached 也可以认为是 File-backed page,即属于 Page Cache。
Page Cache 用于缓存文件的页数据,buffer cache 用于缓存块设备(如磁盘)的块数据。
Page Cache 与 buffer cache 的共同目的都是加速数据 I/O:
在 Linux 2.4 版本的内核之前,Page Cache 与 buffer cache 是完全分离的。但是,块设备大多是磁盘,磁盘上的数据又大多通过文件系统来组织,这种设计导致很多数据被缓存了两次,浪费内存。
在 2.4 版本内核之后,两块缓存近似融合在了一起:如果一个文件的页加载到了 Page Cache,那么同时 buffer cache 只需要维护块指向页的指针就可以了。只有那些没有文件表示的块,或者绕过了文件系统直接操作(如dd命令)的块,才会真正放到 buffer cache 里。
因此,现在 Page Cache,基本上都同时指 Page Cache 和 buffer cache 两者。
Page Cache 中的每个文件都是一棵基数树(radix tree,本质上是多叉搜索树),树的每个节点都是一个页。根据文件内的偏移量就可以快速定位到所在的页。
操作系统为基于 Page Cache 的读缓存机制提供预读机制(PAGE_READAHEAD),一个例子是:
即应用程序利用 read 系统调动读取 4KB 数据,实际上内核使用 readahead 机制完成了 16KB 数据的读取。
Linux 的 Page Cache 是对磁盘上 page(页)的内存缓存,同时可以用于读/写操作。
任何系统引入缓存,就会引发一致性问题:内存中的数据与磁盘中的数据不一致。
Linux 提供多种机制来保证数据一致性,理解的关键是 trade-off:吞吐量与数据一致性保证是一对矛盾。
文件 = 数据 + 元数据。元数据用来描述文件的各种属性,也必须存储在磁盘上。因此,保证文件一致性其实包含了两个方面:数据一致+元数据一致。
文件的元数据包括:文件大小、创建时间、访问时间、属主属组等信息。
一致性问题:如果发生写操作并且对应的数据在 Page Cache 中,那么写操作就会直接作用于 Page Cache 中,此时如果数据还没刷新到磁盘,那么内存中的数据就领先于磁盘,此时对应 page 就被称为 Dirty page。
当前 Linux 下以两种方式实现文件一致性:
上述两种方式最终都依赖于系统调用,主要分为如下三种系统调用:
方法 | 含义 |
---|---|
fsync(intfd) | fsync(fd):将 fd 代表的文件的脏数据和脏元数据全部刷新至磁盘中。 |
fdatasync(int fd) | fdatasync(fd):将 fd 代表的文件的脏数据刷新至磁盘,同时对必要的元数据刷新至磁盘中,这里所说的必要的概念是指:对接下来访问文件有关键作用的信息,如文件大小,而文件修改时间等不属于必要信息 |
sync() | sync():则是对系统中所有的脏的文件数据元数据刷新至磁盘中 |
上述三种系统调用可以分别由用户进程与内核进程发起。下面我们研究一下内核线程的相关特性。
Write Through 与 Write back 在持久化的可靠性上有所不同:
1.加快数据访问
如果数据能够在内存中进行缓存,那么下一次访问就不需要通过磁盘 I/O 了,直接命中内存缓存即可。
由于内存访问比磁盘访问快很多,因此加快数据访问是 Page Cache 的一大优势。
2.减少 I/O 次数,提高系统磁盘 I/O 吞吐量
得益于 Page Cache 的缓存以及预读能力,而程序又往往符合局部性原理,因此通过一次 I/O 将多个 page 装入 Page Cache 能够减少磁盘 I/O 次数, 进而提高系统磁盘 I/O 吞吐量。
page cache 也有其劣势,最直接的缺点是需要占用额外物理内存空间,物理内存在比较紧俏的时候可能会导致频繁的 swap 操作,最终导致系统的磁盘 I/O 负载的上升。
Page Cache 的另一个缺陷是对应用层并没有提供很好的管理 API,几乎是透明管理。应用层即使想优化 Page Cache 的使用策略也很难进行。因此一些应用选择在用户空间实现自己的 page 管理,而不使用 page cache,例如 MySQL InnoDB 存储引擎以 16KB 的页进行管理。
Page Cache 最后一个缺陷是在某些应用场景下比 Direct I/O 多一次磁盘读 I/O 以及磁盘写 I/O。
Direct I/O 即直接 I/O。
“直接”在这里还有另一层语义:其他所有技术中,数据至少需要在内核空间存储一份,但是在 Direct I/O 技术中,数据直接存储在用户空间中,绕过了内核。
Direct I/O 的读写特点:
《小林 coding》
《深入理解计算机系统 第3版》