进程写文件时,进程发生了崩溃,已写入的数据会丢失吗?
大概就是进程写文件使用缓冲 IO过程中写一半的时候进程发生了崩溃已写入的数据会丢失吗答案是不会的。因为进程在执行 write 使用缓冲 IO系统调用的时候实际上是将文件数据写到了内核的 page cache它是文件系统中用于缓存文件数据的缓冲所以即使进程崩溃了文件数据还是保留在内核的 page cache我们读数据的时候也是从内核的 page cache 读取因此还是依然读的进程崩溃前写入的数据。内核会找个合适的时机将 page cache 中的数据持久化到磁盘。但是如果 page cache 里的文件数据在持久化到磁盘之前系统发生了崩溃那这部分数据就会丢失了。当然 我们也可以在程序里调用 fsync 函数在写文件的时候立刻将文件数据持久化到磁盘这样就可以解决系统崩溃导致的文件数据丢失的问题。我在网上看到一篇介绍 page cache 很好的文章 分享给大家一起学习。作者spongecaptain原文地址Linux 的 Page Cache(opens new window)#Page Cache#Page Cache 是什么为了理解 Page Cache我们不妨先看一下 Linux 的文件 I/O 系统如下图所示上图中红色部分为 Page Cache。可见 Page Cache 的本质是由 Linux 内核管理的内存区域。我们通过 mmap 以及 buffered I/O 将文件读取到内存空间实际上都是读取到 Page Cache 中。#如何查看系统的 Page Cache通过读取/proc/meminfo文件能够实时获取系统内存情况$ cat /proc/meminfo ... Buffers: 1224 kB Cached: 111472 kB SwapCached: 36364 kB Active: 6224232 kB Inactive: 979432 kB Active(anon): 6173036 kB Inactive(anon): 927932 kB Active(file): 51196 kB Inactive(file): 51500 kB ... Shmem: 10000 kB ... SReclaimable: 43532 kB ...根据上面的数据你可以简单得出这样的公式等式两边之和都是 112696 KBBuffers Cached SwapCached Active(file) Inactive(file) Shmem SwapCached两边等式都是 Page Cache即Page Cache Buffers Cached SwapCached通过阅读下面的小节就能够理解为什么 SwapCached 与 Buffers 也是 Page Cache 的一部分。#page 与 Page Cachepage 是内存管理分配的基本单位 Page Cache 由多个 page 构成。page 在操作系统中通常为 4KB 大小32bits/64bits而 Page Cache 的大小则为 4KB 的整数倍。另一方面并不是所有 page 都被组织为 Page Cache。Linux 系统上供用户可访问的内存分为两个类型即File-backed pages文件备份页也就是 Page Cache 中的 page对应于磁盘上的若干数据块对于这些页最大的问题是脏页回盘Anonymous pages匿名页不对应磁盘上的任何磁盘数据块它们是进程的运行是内存空间例如方法栈、局部变量表等属性为什么 Linux 不把 Page Cache 称为 block cache这不是更好吗这是因为从磁盘中加载到内存的数据不仅仅放在 Page Cache 中还放在 buffer cache 中。例如通过 Direct I/O 技术的磁盘文件就不会进入 Page Cache 中。当然这个问题也有 Linux 历史设计的原因毕竟这只是一个称呼含义随着 Linux 系统的演进也逐渐不同。下面比较一下 File-backed pages 与 Anonymous pages 在 Swap 机制下的性能。内存是一种珍惜资源当内存不够用时内存管理单元Memory Mangament Unit需要提供调度算法来回收相关内存空间。内存空间回收的方式通常就是 swap即交换到持久化存储设备上。File-backed pagesPage Cache的内存回收代价较低。Page Cache 通常对应于一个文件上的若干顺序块因此可以通过顺序 I/O 的方式落盘。另一方面如果 Page Cache 上没有进行写操作所谓的没有脏页甚至不会将 Page Cache 回盘因为数据的内容完全可以通过再次读取磁盘文件得到。Page Cache 的主要难点在于脏页回盘这个内容会在后面进行详细说明。Anonymous pages 的内存回收代价较高。这是因为 Anonymous pages 通常随机地写入持久化交换设备。另一方面无论是否有写操作为了确保数据不丢失Anonymous pages 在 swap 时必须持久化到磁盘。#Swap 与缺页中断Swap 机制指的是当物理内存不够用内存管理单元Memory Mangament UnitMMU需要提供调度算法来回收相关内存空间然后将清理出来的内存空间给当前内存申请方。Swap 机制存在的本质原因是 Linux 系统提供了虚拟内存管理机制每一个进程认为其独占内存空间因此所有进程的内存空间之和远远大于物理内存。所有进程的内存空间之和超过物理内存的部分就需要交换到磁盘上。操作系统以 page 为单位管理内存当进程发现需要访问的数据不在内存时操作系统可能会将数据以页的方式加载到内存中。上述过程被称为缺页中断当操作系统发生缺页中断时就会通过系统调用将 page 再次读到内存中。但主内存的空间是有限的当主内存中不包含可以使用的空间时操作系统会从选择合适的物理内存页驱逐回磁盘为新的内存页让出位置选择待驱逐页的过程在操作系统中叫做页面替换Page Replacement替换操作又会触发 swap 机制。如果物理内存足够大那么可能不需要 Swap 机制但是 Swap 在这种情况下还是有一定优势对于有发生内存泄漏几率的应用程序进程Swap 交换分区更是重要这可以确保内存泄露不至于导致物理内存不够用最终导致系统崩溃。但内存泄露会引起频繁的 swap此时非常影响操作系统的性能。Linux 通过一个 swappiness 参数来控制 Swap 机制这个参数值可为 0-100控制系统 swap 的优先级高数值较高频率的 swap进程不活跃时主动将其转换出物理内存。低数值较低频率的 swap这可以确保交互式不因为内存空间频繁地交换到磁盘而提高响应延迟。最后为什么 SwapCached 也是 Page Cache 的一部分这是因为当匿名页Inactive(anon) 以及 Active(anon)先被交换swap out到磁盘上后然后再加载回swap in内存中由于读入到内存后原来的 Swap File 还在所以 SwapCached 也可以认为是 File-backed page即属于 Page Cache。这个过程如下图所示。#Page Cache 与 buffer cache执行 free 命令注意到会有两列名为 buffers 和 cached也有一行名为 “-/ buffers/cache”。~ free -m total used free shared buffers cached Mem: 128956 96440 32515 0 5368 39900 -/ buffers/cache: 51172 77784 Swap: 16002 0 16001其中cached 列表示当前的页缓存Page Cache占用量buffers 列表示当前的块缓存buffer cache占用量。用一句话来解释Page Cache 用于缓存文件的页数据buffer cache 用于缓存块设备如磁盘的块数据。页是逻辑上的概念因此 Page Cache 是与文件系统同级的块是物理上的概念因此 buffer cache 是与块设备驱动程序同级的。Page Cache 与 buffer cache 的共同目的都是加速数据 I/O写数据时首先写到缓存将写入的页标记为 dirty然后向外部存储 flush也就是缓存写机制中的 write-back另一种是 write-throughLinux 默认情况下不采用读数据时首先读取缓存如果未命中再去外部存储读取并且将读取来的数据也加入缓存。操作系统总是积极地将所有空闲内存都用作 Page Cache 和 buffer cache当内存不够用时也会用 LRU 等算法淘汰缓存页。在 Linux 2.4 版本的内核之前Page Cache 与 buffer cache 是完全分离的。但是块设备大多是磁盘磁盘上的数据又大多通过文件系统来组织这种设计导致很多数据被缓存了两次浪费内存。所以在 2.4 版本内核之后两块缓存近似融合在了一起如果一个文件的页加载到了 Page Cache那么同时 buffer cache 只需要维护块指向页的指针就可以了。只有那些没有文件表示的块或者绕过了文件系统直接操作如dd命令的块才会真正放到 buffer cache 里。因此我们现在提起 Page Cache基本上都同时指 Page Cache 和 buffer cache 两者本文之后也不再区分直接统称为 Page Cache。下图近似地示出 32-bit Linux 系统中可能的一种 Page Cache 结构其中 block size 大小为 1KBpage size 大小为 4KB。Page Cache 中的每个文件都是一棵基数树radix tree本质上是多叉搜索树树的每个节点都是一个页。根据文件内的偏移量就可以快速定位到所在的页如下图所示。关于基数树的原理可以参见英文维基这里就不细说了。#Page Cache 与预读操作系统为基于 Page Cache 的读缓存机制提供预读机制PAGE_READAHEAD一个例子是用户线程仅仅请求读取磁盘上文件 A 的 offset 为 0-3KB 范围内的数据由于磁盘的基本读写单位为 block4KB于是操作系统至少会读 0-4KB 的内容这恰好可以在一个 page 中装下。但是操作系统出于局部性原理会选择将磁盘块 offset [4KB,8KB)、[8KB,12KB) 以及 [12KB,16KB) 都加载到内存于是额外在内存中申请了 3 个 page下图代表了操作系统的预读机制上图中应用程序利用 read 系统调动读取 4KB 数据实际上内核使用 readahead 机制完成了 16KB 数据的读取。#Page Cache 与文件持久化的一致性可靠性现代 Linux 的 Page Cache 正如其名是对磁盘上 page页的内存缓存同时可以用于读/写操作。任何系统引入缓存就会引发一致性问题内存中的数据与磁盘中的数据不一致例如常见后端架构中的 Redis 缓存与 MySQL 数据库就存在一致性问题。Linux 提供多种机制来保证数据一致性但无论是单机上的内存与磁盘一致性还是分布式组件中节点 1 与节点 2 、节点 3 的数据一致性问题理解的关键是 trade-off吞吐量与数据一致性保证是一对矛盾。首先需要我们理解一下文件的数据。文件 数据 元数据。元数据用来描述文件的各种属性也必须存储在磁盘上。因此我们说保证文件一致性其实包含了两个方面数据一致元数据一致。文件的元数据包括文件大小、创建时间、访问时间、属主属组等信息。我们考虑如下一致性问题如果发生写操作并且对应的数据在 Page Cache 中那么写操作就会直接作用于 Page Cache 中此时如果数据还没刷新到磁盘那么内存中的数据就领先于磁盘此时对应 page 就被称为 Dirty page。当前 Linux 下以两种方式实现文件一致性Write Through写穿向用户层提供特定接口应用程序可主动调用接口来保证文件一致性Write back写回系统中存在定期任务表现形式为内核线程周期性地同步文件系统中文件脏数据块这是默认的 Linux 一致性方案上述两种方式最终都依赖于系统调用主要分为如下三种系统调用方法含义fsync(intfd)fsync(fd)将 fd 代表的文件的脏数据和脏元数据全部刷新至磁盘中。fdatasync(int fd)fdatasync(fd)将 fd 代表的文件的脏数据刷新至磁盘同时对必要的元数据刷新至磁盘中这里所说的必要的概念是指对接下来访问文件有关键作用的信息如文件大小而文件修改时间等不属于必要信息sync()sync()则是对系统中所有的脏的文件数据元数据刷新至磁盘中上述三种系统调用可以分别由用户进程与内核进程发起。下面我们研究一下内核线程的相关特性。创建的针对回写任务的内核线程数由系统中持久存储设备决定为每个存储设备创建单独的刷新线程关于多线程的架构问题Linux 内核采取了 Lighthttp 的做法即系统中存在一个管理线程和多个刷新线程每个持久存储设备对应一个刷新线程。管理线程监控设备上的脏页面情况若设备一段时间内没有产生脏页面就销毁设备上的刷新线程若监测到设备上有脏页面需要回写且尚未为该设备创建刷新线程那么创建刷新线程处理脏页面回写。而刷新线程的任务较为单调只负责将设备中的脏页面回写至持久存储设备中。刷新线程刷新设备上脏页面大致设计如下每个设备保存脏文件链表保存的是该设备上存储的脏文件的 inode 节点。所谓的回写文件脏页面即回写该 inode 链表上的某些文件的脏页面系统中存在多个回写时机第一是应用程序主动调用回写接口fsyncfdatasync 以及 sync 等第二管理线程周期性地唤醒设备上的回写线程进行回写第三是某些应用程序/内核任务发现内存不足时要回收部分缓存页面而事先进行脏页面回写设计一个统一的框架来管理这些回写任务非常有必要。Write Through 与 Write back 在持久化的可靠性上有所不同Write Through 以牺牲系统 I/O 吞吐量作为代价向上层应用确保一旦写入数据就已经落盘不会丢失Write back 在系统发生宕机的情况下无法确保数据已经落盘因此存在数据丢失的问题。不过在程序挂了例如被 kill -9Page Cache 中的数据操作系统还是会确保落盘#Page Cache 的优劣势#Page Cache 的优势1.加快数据访问如果数据能够在内存中进行缓存那么下一次访问就不需要通过磁盘 I/O 了直接命中内存缓存即可。由于内存访问比磁盘访问快很多因此加快数据访问是 Page Cache 的一大优势。2.减少 I/O 次数提高系统磁盘 I/O 吞吐量得益于 Page Cache 的缓存以及预读能力而程序又往往符合局部性原理因此通过一次 I/O 将多个 page 装入 Page Cache 能够减少磁盘 I/O 次数 进而提高系统磁盘 I/O 吞吐量。#Page Cache 的劣势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。其名字中的”直接”二字用于区分使用 page cache 机制的缓存 I/O。缓存文件 I/O用户空间要读写一个文件并不直接与磁盘交互而是中间夹了一层缓存即 page cache直接文件 I/O用户空间读取的文件直接与磁盘交互没有中间 page cache 层“直接”在这里还有另一层语义其他所有技术中数据至少需要在内核空间存储一份但是在 Direct I/O 技术中数据直接存储在用户空间中绕过了内核。Direct I/O 模式如下图所示此时用户空间直接通过 DMA 的方式与磁盘以及网卡进行数据拷贝。Direct I/O 的读写非常有特点Write 操作由于其不使用 page cache所以其进行写文件如果返回成功数据就真的落盘了不考虑磁盘自带的缓存Read 操作由于其不使用 page cache每次读操作是真的从磁盘中读取不会从文件系统的缓存中读取。参考资料Linux内核技术实战课(opens new window)Reconsidering swapping(opens new window)访问局部性(opens new window)DMA 与零拷贝技术