分类目录归档:文件系统

fsck与日志文件系统

日志文件系统(Journal File System)解决了掉电或系统崩溃造成元数据不一致的问题,细节参见《日志文件系统是怎样工作的》,它的原理是在进行写操作之前,把即将进行的各个步骤(称为transaction)事先记录下来,包括:从data block bitmap中分配一个数据块、在inode中添加指向数据块的指针、把用户数据写入数据块等,这些transaction保存在文件系统单独开辟的一块空间上,称为日志(journal),日志保存成功之后才进行真正的写操作–把文件系统的元数据和用户数据写进硬盘(称为checkpoint),万一写操作的过程中掉电,下次挂载文件系统之前把保存好的日志重新执行一遍就行了(术语叫做replay),保证文件系统的一致性。

Journal replay所需的时间很短,可以通过fsck或者mount命令完成。既然mount命令就可以做journal replay,那还要fsck干什么呢?fsck(file system check)所做的事情可不仅仅是journal replay这么简单,它可以对文件系统进行彻底的检查,扫描所有的inode、目录、superblock、allocation bitmap等等,称为full check。fsck进行full check所需的时间很长,而且文件系统越大,所需的时间也越长。

可能导致文件系统损坏的因素很多,不只是掉电或系统崩溃,比如软件bug和硬件错误都有可能损坏文件系统,而这类问题不是journal replay能解决的,必须用fsck才行。

fsck可以手工执行,也可以在boot时自动执行。无论手工执行还是自动执行,文件系统都必须处于未挂载(unmounted)状态。

fsck会否在boot过程中自动执行

最早的时候fsck缺省在boot的过程中自动执行,有了日志文件系统之后,掉电和系统崩溃对文件系统的潜在伤害可以通过journal replay得到解决,执行fsck就不是那么紧迫了,而且随着文件系统越来越大,full check所需的时间越来越长,在boot过程中自动进行fsck带来了诸多不便,也因此引发了很多争议,但fsck又不能不做,因为有些软硬件bug导致的文件系统损坏只有fsck才能处理。现在的倾向是把执行fsck的时机交给系统管理员去决定,是手工执行还是自动执行根据实际需要而定。其中一个讨论如下:
https://bugzilla.redhat.com/show_bug.cgi?id=879315

fsck是否在boot过程中自动执行是通过/etc/fstab的第6个字段控制的,如果为0就表示禁止fsck自动执行:

在已经禁止fsck自动执行的情况下,仍然可以强制下次boot时自动执行fsck,方法如下:

如果要reboot时不做fsck,可以用以下命令重启,它会忽略/etc/fstab的设置,启动时直接跳过fsck:

不同的文件系统有不同的fsck工具

不同类型的文件系统有各自的fsck工具,比如ext3文件系统对应的是fsck.ext3,xfs文件系统对应的是fsck.xfs。fsck命令只是个外壳,它本身没有能力去检查所有类型的文件系统,会根据文件系统的类型去调用相应的fsck工具。

  • ext2/ext3/ext4

ext2, ext3和ext4的fsck工具都是e2fsck,fsck.ext2、fsck.ext3和fsck.ext4都是指向e2fsck的链接。

ext2不是日志文件系统,没有日志(journal),e2fsck执行时会进行full check,耗时较长,尤其对大文件系统耗时更长。

ext3和ext4都是日志文件系统,e2fsck执行的时候,缺省做完journal replay就会返回,速度很快,除非superblock中的标记要求进行full check,(如果文件系统在运行过程中发现metadata不一致的话会在superblock中做标记,e2fsck执行时发现这个标记就会进行full check)。参见e2fsck的manpage:

ext3/ext4是否进行full check是由下列几个因素决定的:

  • superblock中的标记要求进行full check(即以上manpage所提到的);
  • e2fsck命令加了”-f”参数,强制进行full check;
  • ext3/ext4文件系统有两个参数决定是否进行full check:
    “Maximum mount count” 和 “Check interval”。

用”tune2fs -l”命令可以查看ext文件系统的参数,在下例中的文件系统每当mount次数达到25次的时候(Maximum mount count 值是25),一旦执行e2fsck就会进行full check;每过6个月(Check interval 值是6 months),一旦执行e2fsck就会进行full check:

修改这两个参数分别用 tune2fs 命令的 “-c” 和 “-i” 参数,比如”tune2fs -c 0 -i 0 /dev/sda”可以禁止”Maximum mount count”和”Check interval”达到指定值导致的full check。参见tune2fs的manpage:

 

  • XFS

XFS是日志文件系统,mount过程会进行journal replay等操作。如果允许boot过程中自动执行fsck,fsck会直接成功返回,因为对应的fsck.xfs什么也不做。

要想检查XFS文件系统,可以手工执行”xfs_repair -n”命令。
注:
虽然有xfs_check命令也可以对XFS文件系统做检查,但不建议使用(参见xfs_check的manpage),它很慢,尤其是大文件系统上更慢。

xfs_repair命令的前提条件是journal log必须干净,如果XFS文件系统没有正常umount,那在执行xfs_repair之前应该先mount一下,让它完成journal replay,然后再umount,因为xfs_repair应该在文件系统未挂载的状态下执行。
注:
如果XFS的journal replay失败,意味着journal log有可能损坏了,可以用”xfs_repair -L”清除journal log,但务必谨慎,因为丢弃journal有可能会导致文件系统一致性受损。

为什么有时候reboot过程中日志文件系统会进行很长时间的fsck?

通常是因为:boot时自动执行了fsck并且fsck在进行full check。需要检查以下事项:

  1. /etc/fstab的第6字段是否非0,因为它会自动执行fsck;
  2. 是否存在/forcefsck文件,因为它会强制下次boot时自动执行fsck;
  3. 是否通过 shutdown -Fr 重启的系统,因为它会强制boot时自动执行fsck;
  4. 针对具体的文件系统类型,看在什么情况下会进行full check,譬如ext2文件系统,任何时候执行e2fsck都会进行full check;而ext3/ext4则视情况而定–如果文件系统参数设定了”Maximum mount count”或”Check interval”,又或者superblock因为发现不一致而作了full check标记(详见前面段落),则e2fsck执行时会进行full check。

参考资料:

日志文件系统是怎样工作的

文件系统要解决的一个关键问题是怎样防止掉电或系统崩溃造成数据损坏,在此类意外事件中,导致文件系统损坏的根本原因在于写文件不是原子操作,因为写文件涉及的不仅仅是用户数据,还涉及元数据(metadata)包括 Superblock、inode bitmap、inode、data block bitmap等,所以写操作无法一步完成,如果其中任何一个步骤被打断,就会造成数据的不一致或损坏。举一个简化的例子,我们对一个文件进行写操作,要涉及以下步骤:

  1. 从data block bitmap中分配一个数据块;
  2. 在inode中添加指向数据块的指针;
  3. 把用户数据写入数据块。
  • 如果步骤2完成了,3未完成,结果是数据损坏,因为该文件认为数据块是自己的,但里面的数据其实是垃圾;
  • 如果步骤2完成了,1未完成,结果是元数据不一致,因为该文件已经把数据块据为己有,然而文件系统却还认为该数据块未分配、随后又可能会把该数据块分配给别的文件、造成数据覆盖;
  • 如果步骤1完成了、2未完成,结果就是文件系统分配了一个数据块,但是没有任何文件用到这个数据块,造成空间浪费;
  • 如果步骤3完成了,2未完成,结果就是用户数据写入了硬盘数据块中,但白写了,因为文件不知道这个数据块是自己的。

日志文件系统(Journal File System)就是为解决上述问题而诞生的。它的原理是在进行写操作之前,把即将进行的各个步骤(称为transaction)事先记录下来,保存在文件系统上单独开辟的一块空间上,这就是所谓的日志(journal),也被称为write-ahead logging,日志保存成功之后才进行真正的写操作、把文件系统的元数据和用户数据写进硬盘(称为checkpoint),这样万一写操作的过程中掉电,下次挂载文件系统之前把保存好的日志重新执行一遍就行了(术语叫做replay),避免了前述的数据损坏场景。

有人问如果保存日志的过程中掉电怎么办?最初始的想法是把一条日志的数据一次性写入硬盘,相当于一个原子操作,然而这并不可行,因为硬盘通常以512字节为单位进行操作,日志数据一超过512字节就不可能一次性写入了。所以实际上是这么做的:给每一条日志设置一个结束符,只有在日志写入成功之后才写结束符,如果一条日志没有对应的结束符就会被视为无效日志,直接丢弃,这样就保证了日志里的数据是完整的。

一条日志在它对应的写操作完成之后就没用了,占用的硬盘空间就可以释放。保存日志的硬盘空间大小是有限的,被循环使用,所以日志也被称为circular log。

至此可以总结一下日志文件系统的工作步骤了:

  1. Journal write : 把transaction写入日志中;
  2. Journal commit : 在一条日志保存好之后,写入结束符;
  3. Checkpoint : 进行真正的写操作,把元数据(metadata)和用户数据(user data)写入文件系统;
  4. Free : 回收日志占用的硬盘空间。

以上方式把用户数据(user data)也记录在日志中,称为Data Journaling,Linux EXT3文件系统就支持这种方式,这种方式存在效率问题:就是每一个写操作涉及的元数据(metadata)和用户数据(user data)实际上都要在硬盘上写两次,一次写在日志里,一次写在文件系统上。元数据倒也罢了,用户数据通常比较大,拷贝几个GB的电影文件也要乘以2实在是降低了效率。

一个更高效的方式是Metadata Journaling,不把用户数据(user data)记录在日志中,它防止数据损坏的方法是先写入用户数据(user data)、再写日志,即在上述”Journal write”之前先写用户数据,这样就保证了只要日志是有效的,那么它对应的用户数据也是有效的,一旦发生掉电故障,最坏的结果也就是最后一条日志没记完,那么对应的用户数据也会丢,效果与Data Journaling丢弃日志一样,重要的是文件系统的一致性和完整性是有保证的。Metadata Journaling又叫Ordered Journaling,大多数文件系统都采用这种方式。像Linux EXT3文件系统也是可以选择Data Journaling还是Ordered Journaling的。

参考资料:
Crash Consistency: FSCK and Journaling