从 NILFS2 看 Log-Structure 文件系统(4)
- UID
- 1066743
|
从 NILFS2 看 Log-Structure 文件系统(4)
磁盘布局NILFS2 将磁盘平均分成多个 segment,除了第一个 segment 之外,其他的 segment 大小都相同。第一个 segment 之所以特殊,是因为这里是磁盘的开头,superblock 存放于此。因此第一个 segment 的大小需要减去 superblock 所占用的空间。如图所示:
图 10. segment 布局在每一个 Segment 内,NILFS2 顺序追加 LOG。因此在磁盘上,segment 由连续的 log 组成。每个 log 有一个 summary block;之后是 payload,即文件系统的修改记录,这里不仅存储了普通文件的修改记录,还有 metadata file 的修改记录;最后有一个 SR。
Checkpoint 和 Snapshot追加写 log 的设计使得 snapshot 的实现非常容易。因为 NILFS2 从来不覆盖原始数据,所以 snapshot 可以很方便地产生。图 11 试图描述 NILFS2 snapshot 的实现原理。
图 11. 快照图 11 中引入了新的术语 checkpoint,checkpoint 是一种特殊的 snapshot,是对文件系统某个一致状态的快照。Checkpoint 这个概念是数据库系统中最先使用的,在事务处理中,使用 Log 记录所有的修改操作,当事务失败,便可以根据 Log undo 所有的修改操作,从而达到回滚或灾难恢复的目的。但随着时间推移,日志会越来越长,以至于恢复的时候需要 undo 的操作过多。checkpoint 就是这些日志中的某个点,在这个点上,数据库处于一致状态,因此数据库恢复时,一旦遇到一个 checkpoint,便可以停止读取日志。Checkpoint 之前的老日志记录也可以被删除以便节省磁盘空间。在 NILFS2 中的 checkpoint 也很类似,表示了文件系统某个一致性状态的点。
在 NILFS2 中,checkpoint 自动产生,当文件系统被修改时,新的 Log 被追加到 Disk 末尾。每个 Log 中都会插入一个 SR 数据块,其中保存了 checkpoint 的相关数据。在图 11 中,通过 checkpoint A 便可以找到文件 File1 的旧数据 Block A 和 Block B。同理,通过 checkpoint B 便可以读到文件 File1 的新数据。
Snapshot 和 Checkpoint 不同。Checkpoint 由 NILFS2 自动建立,但也会自动被删除。当磁盘空间不够,或者 checkpoint 的年龄已经比较老的时候,checkpoint 会被系统的垃圾收集机制自动删除,以便释放新的磁盘空间。Snapshot 由用户建立,用户可以将一个 checkpoint 转换为 snapshot,也可以建立一个 Snapshot,系统不会自动回收 Snapshot。其他方面 snapshot 和 checkpoint 是相同的。
事务处理和 segment construction很多关于文件的操作都是由多个子操作组成,每个子操作只修改一个特定的元数据,只有所有的子操作都完成,文件操作才算成功;任何子操作失败,就应该回滚到文件系统之前的状态。这些子操作便是一个事务。
事务提交之后,文件系统便处于一致性状态。如前所述,这便是一个 checkpoint。
创建 checkpoint 在 NILFS2 的术语中叫做 segment construction。NILFS2 用一个专门的内核线程来处理 segment construction 的工作。该线程定时被唤醒,假如需要,便创建一个 segment,生成一个 checkpoint。这就是 NILFS2 不间断 snapshot 的具体实现。此外,每次事务提交之后,NILFS2 也将唤醒后台线程,创建一个 checkpoint。
下面以创建文件为例理解事务和 segment construction。
用户程序调用 open() 系统调用,并制定 O_CREAT 选项。VFS 将调用 nilfs 文件系统注册的 nilfs_create() 函数。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| static int nilfs_create(struct inode *dir, struct dentry *dentry, int mode,
struct nameidata *nd)
{
err = nilfs_transaction_begin(dir->i_sb, &ti, 1);
inode = nilfs_new_inode(dir, mode);
err = PTR_ERR(inode);
if (!IS_ERR(inode)) {
inode->i_op = &nilfs_file_inode_operations;
inode->i_fop = &nilfs_file_operations;
inode->i_mapping->a_ops = &nilfs_aops;
mark_inode_dirty(inode);
err = nilfs_add_nondir(dentry, inode);
}
if (!err)
err = nilfs_transaction_commit(dir->i_sb);
else
nilfs_transaction_abort(dir->i_sb);
}
|
这里主要有两个步骤:创建新的 inode;修改 dir 文件。Nilfs 用 transaction 保护这两个动作。
在 nilfs_transaction_begin 中,nilfs2 将获得一个内核信号量,这样就保证了 transaction 的互斥性。此后调用 nilfs_new_inode 和 nilfs_add_nondir 来修改相应的元数据文件。当这些操作都成功完成后,调用 nilfs_transaction_commit 提交这次事务。
nilfs_transaction_commit 函数首先释放内核信号量,然后唤醒负责处理事务的 nilfs 内核线程。该线程被唤醒后将执行 nilfs_construct_segment 函数,构建一个完整的 Log,即添加 checkpoint,修改 cpfile,插入 super block root,最后更改 superblock 使其指向新的 super block root。 |
|
|
|
|
|