对文件读写的支持一般由操作系统提供的统一接口,而操作系统抽象接口由文件系统实现具体操作。文件系统除了提供基本的读写服务,还需要对权限控制,元数据访问,基本的错误恢复进行保证。而读写性能永远是文件系统的最重要方面,是众多文件系统存在的原因所在。但是一些基本的读写保证却是文件系统无法做到的,如原子性写,断电数据保护,断电数据跨界,增量写保护等等。本文主要从这些角度来深入对文件读写的讨论。
原子性写入
一般的硬盘最小的读写单位是扇区(Sector),一般为512字节,在闪存上(Flash Memory),最小的读单位要远小于最小写单位。因此,首先读写一个硬盘上的几个字节都会获得一个扇区大小的数据,扇区大小也是影响文件系统的读写的一个因素。另一方面,原子性写入是一个需要考虑的问题,既然最小写单位是一个扇区,那么当写入一个文件一个扇区大小的数据时,是否保证一定会写入一个扇区大小数据成功?如果在写入一个扇区一半的时候断电,显然写入就失败了,硬盘是否保证断电后仍然完成剩余写入是原子写的保证,这通常被现代硬盘驱动通过自身的电源支持,一般来说,现代硬盘自身的电源足够完成断电后一个扇区的写入操作以此来保证原子写。
措施:当硬件不保证原子性写入时,通常需要应用层采取一定手段来保证。可以采用两段式提交的方式来完成,应用层自身在写入文件时在文件首部保存原时间和修改时间,然后写入数据和修改“修改时间”并flush到硬盘上,当成功写入后,再次改变文件原时间并并flush。这个可以保证如果在写入失败时,应用层可以得知文件错误并采取一定措施挽回,比如回滚机制。
增量写保证
增量写指的是当需要append数据到一个文件或者创建一个新文件时,当调用操作系统提供的write操作进行时,通常是写入操作系统提供的写缓存中,由操作系统决定何时写入到硬盘上。这时,一般的程序员都知道需要flush()或者fsync()来强制刷新到硬盘上。通常来说操作系统会先需要扩大文件大小再写入数据。这时,如果在扩大文件大小以后写入数据时断电,这时填充文件增量空间的数据可能是“垃圾”数据,当重新启动程序时可能会得到一个已经更新文件大小的充满“垃圾”数据的文件。部分系统可能会实现先写入数据再扩大文件大小这时就保证了“垃圾”数据不会出现。
措施:当系统不支持增量写保证时,应用层保存一个文件大小,然后先写入数据并成功后,再修改文件大小来保证应用层能得知增量写是否成功并采取回滚措施。
安全覆盖写
指的是当一个应用向一个文件的指定地址范围内写入数据时,不管是系统崩溃还是断电都不会导致超出这个范围的数据改变。也就是说,即使断电和崩溃时,可能发生错误的只能是这个范围内的数据。考虑以下情景,当我们向一个文件的头两个字节覆盖写数据时,但是操作系统会向硬盘写入一个最小写单位的数据,这时如果发生断电,当重启后,文件系统的错误检测系统可能会“自作聪明”的检测到意外事故,然后读出这个扇区并置零返回。
措施:每次写入文件时使用一个最小写单位的数据写入。
小结
大多数目前实现和硬件自身电影支持是可以保证上述陷阱不会发生,但是如果想要打造一个健壮的、多平台的软件时,这些读写陷阱是需要考虑。
转载请注明:爱开源 » 深入文件的读与写—文件系统保证陷阱