Linux 文件系统体系结构--Linux文件系统中元数据的加锁机制与组织方式_VMware, Unix及操作系统讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  VMware, Unix及操作系统讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3869 | 回复: 0   主题: Linux 文件系统体系结构--Linux文件系统中元数据的加锁机制与组织方式        下一篇 
cc
注册用户
等级:中校
经验:1900
发帖:195
精华:0
注册:2011-7-25
状态:离线
发送短消息息给cc 加好友    发送短消息息给cc 发消息
发表于: IP:您无权察看 2011-9-9 10:27:39 | [全部帖] [楼主帖] 楼主

1. 概述

元数据是一个文件系统的重要组成部分,元数据操作也在文件系统中起着非常关键的作用。据统计,涉及元数据的操作约占文件系统所有操作的83%以上,因此元数据操作直接关系到文件系统的性能与表现。对于元数据操作,很多书籍和文章都介绍过其概念和功能,但却很少有文献涉及到这些元数据操作之间的互斥及加锁机制的问题。一些涉及到锁机制的文章也大都是讲解 Linux 的文件锁,而元数据的加锁机制因为涉及到元数据组织方式对其的影响,要更复杂也更不容易理解。同时,元数据的加锁及死锁避免的机制还会直接影响到元数据操作的并发度与性能。对这部分内容进行仔细分析,我们会发现元数据操作的加锁机制是很有系统性的,并且它同元数据的组织设计有着密切的联系,而所有这些都会直接影响到整个文件系统的性能与带宽。了解这部分内容,是一个很有意思的过程,可以深入认识 Linux 文件系统中元数据操作的进行,同时这也是构建文件系统所必须的知识。



2. 为什么要对元数据进行加锁

文件系统为什么要对元数据进行加锁呢?对于这个问题,我们先来了解一下元数据操作的特性。元数据操作是一种事务操作。所谓事务操作,就是要保证事务内的多个子操作要么全部完成,要么都没有完成。文件系统的一个元数据操作通常是由一系列更基本的操作(basic operations)构成,每个基本操作只修改一个元数据。这些基本操作必须按指定的顺序完成。构成一个元数据操作的基本操作序列是一个不可分割的整体。作为一种事务操作,ACID 特性是事务(transaction)的根本特征。元数据操作也具有 ACID 四个特性:

  • 原子性(Atomicity):一个元数据操作的结果要么是所有基本操作都成功时的结果,要么是任何一个基本操作都没有做过的结果。通常,与某个事务关联的基本操作具有共同的目标,并且是相互依赖的。如果系统只执行这些基本操作的一个子集,则可能会破坏事务的总体目标。
  • 一致性(Consistency):每个元数据操作的完成都必须保证文件系统的所有元数据结构都是一致的。
  • 独立性(Isolation):由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看元数据时,元数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
  • 持久性(Durability):一旦操作完成,其对于系统的影响是永久性的。

正因为元数据操作的这种特性,它会产生两个问题:一个是元数据的一致性问题,一个是元数据操作的干扰问题。第一个问题往往是由于突发性的服务器崩溃所带来的,比如发生了断电等情况。此时,如果一个元数据操作没有完全完成,导致该操作中所涉及的元数据修改一部分写回了磁盘,一部分没有写回而丢失了,那么当服务器重启恢复后,就会导致元数据之间的不一致。比如父目录中记录了新创建的对象,而新创建对象的元数据却丢失了。这种不一致的元数据状态会使服务器无法正常提供元数据服务。元数据的一致性问题可以通过对磁盘进行全面的检查修复,或者采用日志技术来避免,这部分内容不在本文的讨论范围内。第二个问题是元数据操作的干扰问题,这就需要通过对元数据进行加锁来避免。前面介绍过,一个元数据操作包含了多个子操作,当多个元数据操作并发进行时,如果没有对相应的元数据进行加锁,就可能导致多个元数据操作的子操作之间互相交叠,从而使元数据操作产生错误。举一个简单的例子来说明:

图 1. 无加锁机制时,两个元数据操作的交叠干扰
北京联动北方科技有限公司

如上图所示,此时系统中有两个元数据操作并发进行。操作①要在目录 a 下创建一个对象 b;操作②要删除目录 a。在有加锁的情况下,操作①会先对目录 a 进行加锁,然后创建对象 b,最后递增目录 a 的 nlink 值,再对 a 解锁,这一连串操作会连续进行,中间不会有其他操作的干扰。如果此时系统中没有相应的加锁机制对元数据操作进行互斥,那么当操作①创建了对象 b 以后,接下来操作②有可能就将目录 a 删除了,当操作①要递增目录 a 的 nlink 值时,就会发现没有可操作的对象了,于是操作出错。

从上面的例子可以看出,通过对要操作的元数据进行加锁就能够很好地保证元数据操作的事务特性。然而同样从上面的例子,我们发现,一个元数据操作可能要涉及到对多个元数据进行加锁,因此元数据操作的加锁机制需要考虑的一个很重要的问题就是死锁避免的问题。所谓死锁,就是两个或两个以上的元数据操作在执行过程中,因争夺加锁的元数据而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。实际上,这里我们要探讨的加锁机制主要就是对死锁避免的考虑,对于死锁的问题,Linux 是通过制定加锁规则来处理的。



3. 元数据操作的加锁规则

在 Linux 文件系统中,元数据的加锁操作基本上都是由其虚拟文件系统(VFS)来规定的。这样做的好处是可以统一管理所有元数据操作的加锁机制,底层的具体文件系统可以不理会这些问题,只需要按照 VFS 的调用来执行对元数据的操作。

在 VFS 中,对于大多数的元数据操作,可以通过制定统一的加锁顺序来避免死锁的发生。这个顺序是:先对父目录加锁,再对要操作的对象(目录或文件)加锁。以创建操作为例,当我们要在一个目录下创建新的对象时,必须先对这个目录进行加锁,然后才能放心地进行创建对象的步骤,由此也不用担心该目录会被中途删除,或者其他操作对该创建操作所造成的干扰。按照元数据名字空间的树状结构来看,我们可以认为,“先对父目录加锁,再对要操作的对象(目录或文件)加锁”是一种从上到下的加锁顺序。那么,只要所有的元数据操作都遵循这个规则,就不会出现相反的加锁顺序(即从下到上的顺序),那么也就不会出现两个操作因为互相等待对方的锁而产生死锁的情况。

这个普遍的加锁规则对于绝大多数元数据操作都是适用的。比如创建、删除等操作,它们的加锁目标都是要操作的对象及其父目录。但有两个元数据操作例外:rename 和 link,因为它们的操作对象不止两个,而这些对象可能位于名字空间树状结构的任意几个位置,导致加锁的路径有可能不在名字空间树状结构“从上到下”的范畴内。对于这两个元数据操作,我们需要对其进行特殊的考虑和处理。



4. rename 操作的加锁方式

我们先看 rename 操作。Rename 操作的目的是将一个对象进行重命名,因此它涉及到两个操作对象:源(source)与目的(target)。rename操作的复杂性在于它需要先分别对 source 的父目录以及 target 的父目录进行加锁,那么多个 rename 操作之间很容易就会出现死锁的状况。举一个简单的例子来说明:

图 2. 多个 rename 操作的死锁隐患
北京联动北方科技有限公司

比如,对于同一个名字空间树状结构,如果有两个 rename 操作同时进行,操作①要将 c1 重命名为 c2,操作②要将 c2 重命名为 c1。假设 rename 操作每次都先对 source 的父目录加锁,再对 target 的父目录加锁。那么,操作①会先对目录 b1 加锁,再对目录 b2 加锁;而操作②会先对目录 b2 加锁,再对目录 b1 加锁。这就产生了一个很典型的死锁场景。

产生死锁隐患的原因是由于 source 与 target 各自的父目录可以在名字空间树状结构的任意位置。那么,要加锁的两个对象就完全可能不在父子关系的范畴内,因此无法以Linux文件系统的统一加锁规则来处理。而如果从 rename 操作本身的视角出发,以“源”或“目的”的关系来规定加锁顺序,也无法避免死锁隐患,就如上图的例子所示。这样看来,我们似乎无法对 rename 操作制定统一的加锁顺序。那么,Linux 是如何处理这个问题的呢?

Linux 也没有办法解决多个 rename 操作所造成的死锁隐患,所以它规定每个文件系统内每次只能进行一个 rename 操作。从源代码中可以看到,VFS 在 superblock 结构中定义了一个互斥的 mutex :s_vfs_rename_mutex(在较早的内核版本中是 s_vfs_rename_sem)。在 Linux 中,每次进行 rename 操作都需要先获得 s_vfs_rename_mutex 这把锁,由此保证每次只会有一个 rename 操作在进行。

在 rename 操作中,对 source 与 target 各自的父目录进行加锁的函数是 lock_rename()。它同样也须在必要的时候保持“从上到下”的加锁顺序,以确保 rename 不会同其他元数据操作发生死锁。因此,如果这两个父目录(这里设为 A 和 B)是不同的两个目录,必须先判断 A 和 B 是否有亲戚关系,即 A 的父目录的父目录……的父目录是 B,或者相反。如果存在这种关系,必须先对祖先目录进行加锁。如果不存在这种关系,则不需要有明确的先后加锁顺序,可以先对 A 加锁,也可以先对 B 加锁。

到此为止,我们看到 rename 操作不会同另一个 rename 操作产生死锁(因为一次只会有一个 rename 操作进行)。也不会同其他元数据操作(这里指除了 rename 与 link 操作之外的操作)产生死锁,因为 rename 操作在必要的时候,与其他操作同样遵循“从上到下”的加锁顺序。还需要考虑的问题就是 rename 操作会不会同 link 操作产生死锁,接下来我们就介绍 link 操作的加锁方式。



5. link 操作的加锁方式

前面介绍的 rename 操作已经比较麻烦了,再加上 link 操作,情况似乎变得更加复杂。虽然 link 操作也涉及到两个操作对象,但有一个规定使得这种复杂性得到了很大的简化。这个规定就是:link 的对象不能是目录。基于这个规定,我们来看 link 操作的加锁方式及其与其他元数据操作的关系。

图 3. link 操作的加锁规则
北京联动北方科技有限公司

我们仍然通过举例来说明。如上图所示,我们要在目录 a 下创建一个链接文件 b,并将 b 链接到到 c 目录下的文件 d。那么 link 操作会先对目录 a 进行加锁,然后对文件 d 进行加锁。由于 link 的对象不能是目录,因此加锁的顺序一定是先对目录加锁,再对文件加锁。由此 link 操作有很确定的加锁顺序,所以一个 link 操作不会与另一个 link 操作产生死锁。同时,由于 rename 操作会先对两个目录进行加锁,所以也不可能与 link 操作产生死锁。最后,当目录 a 和文件 d 在名字空间树状结构的同一分支上时,a 不会是 d 的子孙(否则 d 就不会是一个文件,而是一个目录)。因此当目录 a 和文件 d 在名字空间树状结构的同一分支上时,先对 a 加锁再对 d 加锁,也一定满足“从上到下”的加锁顺序。由此 link 操作不会与其他元数据操作产生死锁。

“link 操作的对象不能是目录”这个规定的初衷似乎是为了防止出现目录的无穷嵌套,却同时也简化了 link 操作的死锁问题。可以看出,这种非常规的元数据操作确实比较麻烦,需要考虑很多情况及其与其他元数据操作的关系。



6. 元数据组织方式对元数据加锁机制的影响

通过上面的介绍我们可以发现一个问题,为什么一般的元数据操作可以通过一个“从上到下”的加锁顺序就成功地避免了死锁隐患,而 rename 操作与 link 操作却需要很多特殊的处理?实际上这与 Linux 文件系统中元数据的组织方式有关。在 Linux 的 VFS 中,元数据的组织方式是基于父子关系的树状结构来构建的,比如每个 dentry 结构里都有 d_parent 域指向其父目录的 dentry 结构。因此,VFS 知道所有关于父子关系的信息。对于那些加锁对象的关系属于父子关系范畴的元数据操作,VFS 可以很轻松地指定这些操作对象的加锁顺序,从而避免死锁。而对于 rename 操作与 link 操作,它们的加锁对象的关系有可能不在父子关系的范畴内,于是 VFS 就无法明确这些操作对象之间的关系,也就很难相应地制定加锁顺序,从而也就出现了上面所介绍的那些特殊处理。也就是说,当文件系统要对元数据进行加锁时,它只能根据自己所能看到的元数据组织视图来指定加锁顺序。Linux 文件系统无法对所有元数据操作进行一致的加锁机制,本质上是由于其元数据的组织方式与加锁机制所需要的视图不一致所导致的。

作为最广泛采用的开源操作系统,Linux 的设计和机制都是很多人学习和研究的参照和样板,其实我们可以将眼光扩展开来,对其中的一些设计思路进行改变甚至颠覆,这在分布式文件系统领域更加普遍。比如 Google 文件系统所提出的全内存元数据的组织设计,由此带来了文件系统设计方面的许多新优势和新问题。当 Google 将其文件系统的设计以论文的形式发表后,就得到了大家广泛的关注,互联网海量存储及云计算所所提出的新挑战也促使大家探索存储方面的新突破。Yahoo! 模仿 Google 的文件系统及其他相关技术推出了自己的开源项目 Hadoop,其中 HDFS 更是想复制 Google 文件系统的成功,却无奈最终效果无法企及。从 HDFS 的源码中可以看出,它对 Google 文件系统的模仿其实比较表面,特别是元数据服务器,并没有看透 Google 文件系统的精髓所在,这也许也是其目前性能瓶颈的关键原因之一。DCFS3(Dawning Cluster File System v3)是中国科学院计算技术研究所研发的机群文件系统,旨在探索高性能低成本的并行 I/O 系统的新思想和新技术。DCFS3 也实现了全内存元数据的组织方式,其内存元数据的写回方式与 HDFS 相比,有很大的灵活性。从学术的角度来看,Google 文件系统也有其不足,它不支持标准的 VFS 架构,因此无法轻易地为通用文件系统所参考。无论如何,Google 文件系统是一个成功的架构,它支撑了 Google 众多产品及服务的正常运转。了解 Google 文件系统是一个很有意思的过程,大家依然按照本文的思路,从元数据加锁机制的角度去了解 Google 文件系统元数据的设计,可以探寻其全内存元数据给元数据组织方式带来的变化和对元数据加锁机制的影响。这部分内容已经超出了本文的讨论范围,感兴趣的读者可以根据后面的“参考资源”所列���的文献去阅读相关的论文。

文件系统与存储系统的设计本身就是一个很灵活、有很大发挥余地的领域;当然,同时也是一项非常具有挑战性的工作,需要扎实的基础知识和开阔的设计灵感。这也许就是系统设计的魅力所在。




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论