Posts Tagged ‘filesystem’

LinuxFB版聚总结

April 28th, 2010

2010年4月25日,linuxfb(北邮真情流露Linux版)在老据点之一——北邮教三某实验室的会议室举行了一月一次的版聚。由于聪明的同学们都看了天气预报没来,追星的同学都因为Coly同学缺席没来,版聚史无前例的只有11人出席,不过,还是一如既往的深入、一如既往的愉快。在此感谢李东阳、彭涛和Casparant同学的分享。参加的同学在Google Wave里有记录,搜索“linuxfb with:public”就能找到了,这里不再赘述,仅仅提一下几位突出贡献同学:

  • bergwolf,也就是彭涛同学,每次在自我介绍时都会一如既往地将戒指从无名指挪到中指,这次贡献了一个很时髦的话题,一会再介绍,希望下次再聚会的时候不再需要这么挪戒指了——呃,我不是说要他提前挪好哈,大家明白吧。
  • lidongyang,李东阳每次来都给我留下深刻印象,尤其这次,带来了第一个精彩话题,当然,一会还是会介绍到的。
  • 上面两位同学各带来一位围观者,不同的是,含蓄的东阳不想让在他组实习的mm来,而bergwolf则邀请来一位师妹,不知道这两位可爱的mm是不是以后还会来参加啊,呵呵
  • jerry/文捷,还有saka,都是来参加版聚新同学,热烈欢迎他们,saka让我们想起了久违的LeoVirgo,啥时候能再聚聚啊
  • bunbun是版聚的老面孔了,搞技术的女孩,呵呵,redsand后继有人啊,话说如果redsand下次带宝宝来的话,我也带我家儿子来,哈哈。
  • gmoto,linuxfb五年版聚史中的第一偶像派的帅哥,这次也来了,可惜没有topic和众多的男女fans
  • hzmalgel,版聚的元老,这次也一如既往的拒绝承认有女朋友,一样的活跃,而且参与了中间的caspar的VPN环节,试用了cooler提供的VPN账号,当然,没成功
  • casparant,本次版聚的多面手,测试了投影仪,担任了摄影师,还在第二个话题中介绍了多种翻墙技巧。

李东阳这次介绍了一个ocfs2的一个bug,大概是这样的,fsstress的truncate会出错,内存中和磁盘上的元数据不一致,东阳同学在VFS的direct write位置定位了这个问题。背景是这样的:OCFS2的Direct Write不允许改变元数据,以获得更好的并发性能。但如果发现有O_DIRECT而不能directio,就送回VFS去做buffered io,可是,VFS的aio还是会检查 o_direct,回到 ocfs2_direct….,最后,该函数只查看 offset,不判断是否超出inode的范围,于是就出现了问题。最终的修改比较直接,就是要确定不要回到direct,只要确定进aio,就一定进aio,这样就不会出问题了。曲折的debug故事一直是我们比较喜欢的话题,非常有趣。

最后,bergwolf介绍了ATA的新指令TRIM,用于宣称某个块已经没有用了,可以被删除了,希望来帮助SSD更主动地垃圾回收,从而提升性能。2.6.33的内核已经支持了TRIM,btrfs和ext4也会使用TRIM。但是TRIM指令的设计师非常匪夷所思的,不能进入queue,类似barrier,这样,需要等前面的指令执行完才能执行TRIM,而TRIM完成之后才能有新的指令进入queue,一次的时延有上百毫秒,如果大量使用这个命令的话,反而会影响性能。结论是——这个命令暂时没啥好处……

总之,感谢讲话题的三位同学:lidongyang,bergwolf,casparant,也感谢永远的组织者,colyli和hzmangel,当然,我也会一如既往的记下去。

[译文] Linux: The Journaling Block Device

August 21st, 2008

June 21, 2006 – 2:40am

Submitted by Kedar Sovani on June 21, 2006 – 2:40am.
http://kerneltrap.org/node/6741
原作者与版权信息:

Amey Inamdar (www.geocities.com/amey_inamdar) is a kernel developer working at Kernel Corporation. His interest areas include filesystems and distributed systems.

Kedar Sovani (www.geocities.com/kedarsovani) works for Kernel Corporation as a kernel developer. His areas of interest include filesystems and storage technologies.

Copyright (c) 2004-2006 Kedar Sovani and Amey Inamdar

王旭 (gnawux(at)gmail.com, http://wangxu.me/blog/) 于2008年8月19-21日译

原子性是操作的一种属性,标明这个操作要么完全成功,要么完全失败,不会处于中间状态。磁盘可以保证扇区级的原子性。这意味着写一个扇区的操作,要么完全成功,要么根本没写。不过,当一个操作涉及到多个扇区的时候,就需要高层机制了。这种机制应该确保全部的扇区修改都是原子性的。如果不能做到原子性的话将导致数据的不一致性。本文就将讨论 Linux 中的日志块设备(JBD)的实现。

首先来看看这些文件系统的不一致是如何产生的。假设一个应用程序建立了一个文件。文件系统内部于是就减少一个 inode 数量,初始化磁盘上的 inode,并为文件的父目录添加一个对应于新文件的条目。但如果在上述操作进行到一半的时候计算机崩溃了,那会怎样呢?在这种情况下,不一致性就被引入到文件系统当中了——可用的 inode 数量减少了,但磁盘上 inode 的初始化可能还没有进行。

要发现这种不一致性的惟一方法便是扫描整个文件系统,这个程序称为 fsck (filesystem consistency check)。对于很大的系统,一致性检查可能需要相当长的时间(可能高达数小时)来检查并修复这些不一致问题。和你想的一样,这么长的宕机时间是难以接受的。更好的手段当然是在第一时间就避免不一致性的产生,这可以通过为操作提供原子性来达到。日志就是为这些操作提供原子性的一种手段。简单地说,使用日志就像使用了一个草稿本。你在草稿本上进行操作,当操作正确,你满意了之后,可以把他们誊到最终版本上。

对于操作系统而言,所有的元数据和数据都存储在文件系统所在的块设备上。日志文件系统使用一个日志区域作为草稿。日志可以是同一个块设备的一部分,或者是一个单独的设备。一个日志文件系统首先记录所有在日志中的操作。一旦这些作为一个原子操作的操作们都被记录到日志之中了,它们才会被一起写到实际的块设备中。在后文中,“磁盘(disk)”将指代“真实的块设备”,而“日志(journal)”将指代“日志区域”。
日志恢复场景
这个例子中,根据上面的需求,改动了3个块——inode 计数块,包含了磁盘上的 inode 的块包含了用于插入条目的目录所在的块。所有这些块首先都被写入到日志中。之后,一个特殊的块——提交记录,被写入到日志中,提交记录用于指示写到日志中的所有的属于一个原子操作的块。

下面三个基本场景就反映了日志文件系统是如何工作的:

  • 当第一个块被写入日志的时候机器崩溃了。在这种情况下,当计算机重启之后检查日志,它会发现一个有没有提交的操作。这就标明可能有一个未完成操作。那么,既然现在还没有对磁盘进行修改操作,也就保持数据一致性。
  • 当提交记录被写入日志的时候机器崩溃。在这种情况下,机器重新启动并检查日志,它会发现一个操作和它的提交记录正在那里。提交记录指明有一个完成了的操作可以被写入到磁盘之中。所有的属于这个操作的块都会依据日志被写到他们在磁盘中的实际位置。
  • 所有的三块都被写入日志,但提交记录还没有进入日志的时候机器崩了。即使在这种情况下,因为没有提交记录,磁盘上还是没有任何修改。这个场景中实际和第一个场景区别不大。

类似的,任何其他的崩溃场景都可以归到上面的头两种场景之中。这样,日志确保了文件系统的一致性。用于查找和重放日志的时间与文件系统一致性检查的时间相比简直是微不足道的。

日志块设备

Linux 日志块设备(JBD)提供了这个用于提供操作原子性的草稿纸。这样,由控制着一个块设备的文件系统可以在同一个设备或是其他块设备上使用JBD来保障一致性。JBD 实现为一个提供一组供这些应用使用的 API 的模块。下面的章节将描述 2.6 内核中的 Linux JBD 的原理和实现。

在转入到 JBD 的实现细节之前,我们需要了解一些 JBD 用到的对象。日志(journal)是管理一个块设备的更新的内部记录(log)。正如上文提到的,更新首先会放到日志之中,然后反射到它们在磁盘上的真实位置。日志区域被当作一个环状链表来管子。也就是说,当日志记满的时候会重用之前用过的区域。

handle 代表一个原子更新。需要被原子地完成的全部一组改写被提取出来引用为一个 handle。

不过,将每个原子更新(handle)都写入到日志之中可能不那么高效。为了更高的性能,JBD 将一组 handle 打包为一个事务(transaction),并将事务一次写入日志。JBD 保障事务本身是原子性的。这样,作为事务的组成部分的 handle 们自然也是原子性的。

事务最重要的属性是它的状态(state)。当事务正在提交时,它的生命周期经历了下面的一系列状态。

  1. 运行(running):事务当前是活着的,并且可以接受新的句柄。在一个系统中,仅有一个事务可以处于运行状态。
  2. 锁定(locked):事务不再接受新的 handle,但现有 handle 们还没有完成。一旦所有 handle 都完成了,事务将进入下一个状态。
  3. 写入(flush):事务中的所有 handle 都完成了,这时事务将它自己写入到日志中去。
  4. 提交(commit):整个事务的记录都被写到日志里面这之后。事务会写一个提交块,来指到日志中的事务记录已经完成了。
  5. 完成:事务完整的写到日志种种之后,它会留在那直到所有的块都被更新到磁盘上的实际位置。

Transaction Committing 与 Checkpointing

一个处于运行状态的事务在一段时间之后会被写(write)到日志区域。这样,一个事务可能是在内存中(running)或是在磁盘上。将事务写入(flushing)到日志中并标记特定的事务已完成的过程称为事务的提交。

日志只控制一小块有限的区域,并且需要进行重用。对于已经提交的事务,也就是那些已经把全部块都写到磁盘上的事务,就不再需要停留在日志里了。这里,Checkpointing(这词儿咋翻译?咬不准啊)就是用于将完成的事务写入磁盘并重新声明日志中对应的空间可用的过程。

实现概要

JBD 层用于进行元数据的日志记录,期间数据简单的不经过日志直接写到磁盘上。但是这并不阻止应用程序记录数据的日志,因为它可以告诉 JBD,它自己是元数据。本文以 2.6.0 内核为例。

Commit

[journal_commit_transaction(journal object)]

Kjournald 线程与每个进行日志的设备相关联。Kjournald 线程保证运行中的事务会在一个特定间隔后被提交。事务提交的代码分成了如下八个不同阶段。图1 给出了日志的逻辑结构。

阶段0:将事务的状态从运行(T_RUNNING)变为锁定(T_LOCKED),这样,这个事务就不能再添加新的 handle 了。事务会等待所有的 handle 都完成的。当事务初始化的时候,会预留一部分缓冲区。在这一阶段,有些缓冲区可能没有使用,就被释放了。到这里,所有事务都完成了之后,事务就已经准备好了可以被提交了。

阶段1:事务进入到写入状态(T_FLUSH)。事务被标记为正在向日志提交的事。这一阶段中,也会标记该日志没有运行状态的事务存在;这样,新的 handles 请求将会导致新的事务的初始化。

阶段2:事务实际使用的缓冲们被写入到磁盘上。首先是数据缓冲。由于数据缓冲不存储到日志记录区域,所以没有什么麻烦的。相反,它们会直接写到磁盘的真实位置。这一阶段在这些缓冲都被接收、收到 IO 操作完成提示后结束。

阶段3:这时所有的数据缓冲都已经写入到磁盘上了,但它们的元数据还在内存之中。元数据的写入不像数据缓冲那么直接,因为元数据需要先写到日志区域之中,并需要记录它们实际要存放的位置。在这个阶段会首先写这些元数据缓存,这里需要一个日志描述块。日志描述块以标签(tag)的形式存储日志中的每个元数据缓存到它的实际位置的映射。之后,元数据缓存写入日志。一旦日志描述符中装满了标签,或者所有的元数据缓存都写入到日志之中了,日志描述符就会被写入到日志之中。现在,所有的元数据缓存都存在日志中了,而且它们在硬盘中的实际位置也被记录下来了。如果系统宕机,再启动的时候,这些要被永久化写入磁盘的数据就可以用来恢复数据了。

阶段4 和阶段5:这两个阶段分别在等元数据缓冲和日志描述符的 I/O 描述通告。一旦收到 I/O 完成消息,内存链表中对应的缓冲就可以释放掉了。

阶段6:现在所有数据和元数据都已经安全地保存着了,数据就在它们的实际位置,而元数据在日志中。现在,事务要被标记为已提交,这样就表明所有的更新都妥善保存在日志之中了。因此,日志描述块再次分配。一个标签会被写入,以示事务已经被成功提交,这个块是同步写入到到日志之中的。这之后,事务便进入了已提交状态,T_COMMIT。

阶段7:当很多事务被写入到日志中但还没有写入到磁盘中的时候。当前事务中的一些元数据缓存可能是一些之前的事务的一部分。这就不需要保存之前的事务了,因为我们的当前提交的事务已经有了更新的版本了。这些缓冲于是就被从老的事务中删除了。

阶段8:事务被标记为完成状态,T_FINISHED。日志结构被更新以将当前事务标记为最新提交的事务。同时也将自己标到要被 checkpoint 的事务列表中去。

Checkpointing

当日志要被写入磁盘的时候,checkpointing 就会被启动了——比如卸载文件系统,或者在新的 handle 开始的时候也可能会启动 checkpointing。一个新的 handle 可能发现需要保证的缓冲数量不足了,于是它就可能需要启动 checkpointing 进程来释放一些日志空间。

checkpointing 进程将写入那些还没有写到硬盘里实际位置上的元数据缓冲。于是这些事务就可以被从日志中删除了。一个日志区域可以有多个 checkpointing 事务,而每个 checkpointing 事务都可以有多个缓冲。checkpointing 进程处理每个提交的事务,对每个事务找出需要写入磁盘的元数据缓冲。所有这些缓冲被一批写入磁盘。一旦所有事务都被处理完,他们的记录就会被从日志中删掉了。

恢复
[journal_recover(journal object)]

当系统在崩溃之后启动的时候,它会发现日志条目不是空的,这就表明上次文件系统卸载并不成功,或者根本就没有进行。这就需要尝试进行修复了。图2 绘制了日志的物理结构的一个例子。修复分三个阶段进行。

  1. PASS_SCAN: 发现日志记录的尾部。

  2. PASS_REVOKE: 为日志记录准备一串要被撤销的块。

  3. PASS_REPLAY: 未被撤销的块以确保磁盘一致性的顺序被重新写入(重放)。

对于恢复而言,可用信息已经在日志中提供了。但是日志的实际状态是不清楚的,就像我们不知道系统在那个具体的点崩溃的一样。这样,最后的事务可能在 checkpointing 或是提交的状态。一个在运行中的事务是不可能被发现的,因为它还在内存中。

对于提交状态的事务,我们不得不忘掉这些更新,因为可能不是所有的更新都在这里。这样,在 PASS_SCAN 阶段中,最后的记录条目会被发现。到这里,恢复进程知道了哪些事务需要被重写。

每个事务可以有一组撤销(revoked)块。这些块非常重要,通过它们可以防止同一个块的老的日志记录被重放在新的数据之上。在 PASS_REVOKE 过程中,会准备一个撤销块的哈希表。每当我们要找出某个快是否需要被在重放中写入到磁盘的时候,这个表就会被用到。

在最后一个阶段,所有的需要被重演的块都需要被处理。每个块都会检查是否存在在解除块撤销表之中。如果这个块没在表里,那么它就可以被安全的写入到磁盘上的实际位置之中。而如果这个块在表中,那么只有最新的版本才会写入到磁盘中。注意,我们还没有改变磁盘上的日志的任何内容。这样,如果在修复过程中系统再次崩溃的话不会造成任何破坏。同样的日志可以在下次用来重新恢复,在恢复的过程中,无关的操作不会被进行。


Using OCFS2, A Clustering File System

June 4th, 2008

http://www.linux-mag.com/id/6070

了解一下下一代 Oracle 集群文件系统,GFS的一个替代品。

Jeremy Garcia

Monday, June 2nd, 2008
翻译:王旭
2008年6月3日,星期二

传统的 Linux 重,如果你需要从多台机器上访问一个文件系统,那么就必须在一台机器上加载它,然后通过 NFS、CIFS 或其他方式 export 它。这样做是稳定、成熟而且有良好说明文档的,不过,这样做有它的明显不足,性能还不是最显著的一个,这样还会带来锁问题、潜在的安全问题、还有最糟的单点故障问题。

如果你的 NFS 服务器宕了,所有的客户端都会立刻无法访问。幸运的是,还有另一个方法来达到这个功能。集群文件系统是一种同时加载到多台服务器上的文件系统。实现一个集群文件系统在过去是非常复杂而昂贵的。通常包含了高价的SAN,光纤通道 HBA 卡、交换机,以及私有版权的文件系统。

现在,我们有了 iSCSI 和开源的集群文件系统可用。最流行的集群文件系统是 GFS,这是 Red Hat 在收购 Sistina 之后开源的集群文件系统。如今,GFS 随 Fedora 和 CentOS 一起发布,并在 RHEL 上提供了可选支持。从 2.6.19 内核开始,GFS 进入了主线内核。

另一个可选的文件系统是OCFS2,这是下一代的 Oracle 集群文件系统。这是一种可扩展的、POSIX兼容的文件系统。与前一个版本(OCFS)不同,OCFS2 是一个通用文件系统。到本文写作时为止,你可能会注意到 OCFS2 不支持 mmap (译注:VFS 的文件操作方法中声明了 mmap 方法,用于将文件映射到指定的地址空间上;译注2:据Colyli介绍,OCFS2 1.4里,已经实现了mmap方法,因为他正在调试一个相关bug)。尽管在不久的将来 OCFS2 将会支持它,但如果你现在就需要 mmap 的支持,你最好还是去看看 GFS。

OCFS2 的用户空间工具在多个发布版中可用,可以从 Oracle 或发布版处获得。OCFS 以 GPL 发布。这里是项目的主页。要使用 OCFS2,你需要安装 ocfs2-tools 包和相应的内核模块(ocfs2-`uname -r`)。

ocfs2console 包不是必须的,但为了使用方便,建议安装。安装了这些工具后,我们就可以建立一个 OCFS2 分区了。设置共享存储不在本文的讨论范围之内,我们假设你已经完成了这一步了。本文的例子是在 RHEL 5.1 环境中进行的。

不同的发布版中,一些命令或文件的位置可能不尽相同。即使 Oracle 没有提供当前的 RHEL 的内核对应的 OCFS 内核模块 RPM 包,你也可以从官方的 ocfs2 tarball 轻松地编译一个出来。和GFS一样,现在OCFS2也是主线内核的一部分了,所以,如果你用的发布版足够新的话,找内核模块包之前可以看看是不是已经支持OCFS2了。

首先,你需要编辑 /etc/sysconfig/o2cb,确保它包含这行内容:

O2CB_ENABLED=true

接下来,在每台主机上创建 /etc/ocfs2/cluster.conf 文件。下面是用于三个节点的集群的设置。对于多个接入IP的主机,节点的参数应该对应于主机名,不论它在使用哪个IP。

node:        ip_port = 7777        ip_address = 192.168.100.1        number = 0        name = host1.domain.com        cluster = ocfs2node:        ip_port = 7777        ip_address = 192.168.100.2        number = 1        name = host2.domain.com        cluster = ocfs2node:        ip_port = 7777        ip_address = 192.168.100.3        number = 2        name = host3.domain.com        cluster = ocfs2cluster:        node_count = 3        name = ocfs2

现在,可以运行 /etc/init.d/o2cb start 来启动集群服务。如果一切都顺利的话,运行 /etc/init.d/o2cb status 可以看到类似这样的结果:

Module "configfs": LoadedFilesystem "configfs": MountedModule "ocfs2_nodemanager": LoadedModule "ocfs2_dlm": LoadedModule "ocfs2_dlmfs": LoadedFilesystem "ocfs2_dlmfs": MountedChecking O2CB cluster ocfs2: Online  Heartbeat dead threshold: 31  Network idle timeout: 30000  Network keepalive delay: 2000  Network reconnect delay: 2000Checking O2CB heartbeat: Active

现在,你可以格式化你的 OCFS2 分区了。

# mkfs.ocfs2 -b 4k -C 32K -L "label" -N 4 /dev/sdxX

现在在每台主机上加载你的分区。

# mount -L "label" /dir

如果一切正常的话,你应该把它添加到你的 fstab 里面,并确保 o2cb 和 ocfs2 服务在系统引导时启动。

/sbin/chkconfig o2cb on/sbin/chkconfig ocfs2 on

本文讨论了所有的基础只是,应该足可以搭建一个可用的 OCFS2 了。如果你计划在生产环境中使用 OCFS2,你应该看看 FAQ,其中包含乐更多的高级话题,如更改大小,quorum and fencing, 限制,以及 rolling upgrades。

Jeremy Garcia 是 LinuxQuestions 的创始人和管理员,那是一个使用 SpamAssassin 过滤邮件的自由、友善、活跃的 Linux 社区。请把问题和反馈发送到 jeremy@linuxquestions.org。

Switch to our mobile site