补充回答:
小文件,你考虑更换文件系统格式,linux有很多fs可以选择,象你这样有特殊用处的文件系统可以考虑单独分个区使用专为小文件设计的文件系统格式化,具体是什么分区格式我记不清了,有些非常见的文件系统:如说xfs、jffs2等,就是为这些特殊用途设计的,你需要找对文件系统效率分析的文档以确定自己的需求,冒昧问一下……,你是不是在做ldap啊?
Linux作为一个类UNIX系统,其文件系统保留了原始UNIX文件系统的表象形式,它看起来是这个样子:
它其实是一棵目录树(没有画全):
然而,虽然所有的UNIX系统以及类UNIX系统的文件系统看起来一样,但是它们的实现却是不尽相同。
作为普通用户,了解文件系统的基本 *** 作就够了;作为应用开发人员,了解文件系统的POSIX接口足矣,但是作为一个对 *** 作系统有着浓厚兴趣的爱好者而言,自己可能就是一个新的文件系统的潜在实现者,所以必须一窥究竟,看看如此外观的文件系统到底是如何实现的。
网上已经有了很多关于UNIX/Linux文件系统实现的资源,但是无一例外,都太复杂了,除了整体的源码分析外,几乎就是针对某个特定文件系统的详解了,如此复杂的这些对于初涉该领域的满腔热情者无疑是一盆冷水,很多人因此望而却步。
几乎所有的关于Linux文件系统实现的资源都在用不同的语言解释上面的这些问题,这很容易陷入细节的泥潭。
本文以Linux内核为例,用一种稍微不同的方式去描述文件系统的实现。嗯,我会分3个部分来介绍Linux内核的文件系统:
本文中,我会通过一个实实在在的文件系统实现的例子,试图阐述 实现一个文件系统,哪些是必须的,哪些不是必须的。 这是一个任务驱动的过程,从简单的例子开始。
读过本文之后,相信会对Linux文件系统的实现有一个总体上的宏观把握,然后再去反复推敲上述的细节问题,重读网上的那些经典资源,相信会事半功倍。
当然,在给出最简单的tinyfs实现之前,还是会有一个总体的介绍。
如果我们把本文最初描述的那个在几乎所有UNIX/类UNIX系统中长的一模一样的文件系统表面刨开,在Linux内核中,文初的那棵树其实它长下面的样子(其实在大多数类UNIX系统中,它们长得都差不多):
【这张图基于我一张手绘图修改而成】
我们看到,Linux系统的文件目录树就是靠上图中的这一系列的链表穿针引线给串在一起的,就像缝制一件衣服一样,最终的成衣就是我们看到的Linux系统目录树,而缝制这件成衣的线以及指导走线的规则便是VFS本身了。
现在只要记住两个重要链表:
然后读完本文之后再去结合代码深入分析它们是如何串起整个文件系统的。
VFS之所有可以将机制大相径庭的完全不同的文件系统对外统一成一个样子,完全就是依靠了它的统一的对POSIX文件调用的接口,该接口的结构看上去是下面的样子:
注意上图最下面的那个椭圆,如果要实现一个文件系统,这个椭圆里的东西是关键,它完成了穿针引线的大部分工作。
现在让我们纵向地看一下一个完整的文件系统实现都包括什么,我指的是从POSIX系统调用开始,一直到数据落盘。Linux内核关于文件系统IO,完整的视图如下所示:
注意VFS提供的三类接口:
一个文件系统如果能实现上面三类接口,那它就是个完整的文件系统了。
我们恰好可以从设计并实现一个最基本的这样的文件系统开始。一个基本的文件系统,其着重点在于上图中红色的部分,而其它部分则不是必不可少的,但是却是让该文件系统变得优秀(而不仅仅是可用)所必须的。
为什么要实现这么一个文件系统,难道没人已经做了这个工作吗?做这个工作的意义何在?
原因如下:
然而确实,我没有找到简单的 最小文件系统 实现,也许你会说Linux内核自带的ramfs难道不就是一个现成的吗?的确算一个,但它有两个问题导致你无法领略实现一个文件系统的全过程,注意,我说的可是全过程:
为了 追求完整, 如果你把如何组织一块内核作为ramfs的底层介质这部分代码全部看完,如果你把libfs.c里的库实现全部看完,我想ramfs也就不算一个 足够简单 的文件系统实例了。
看到了么?要想代码简单,你就不得不使用libfs.c里的现成的例程,这将损失你实现一个文件系统的完整性体验,反之,要想完整实现一个文件系统,你可能不得不自己写大量的代码,这却并不简单。
如何既完备,又足够简单呢?
对于我这种编程水平渣渣的内核爱好者而言,如何在堵车的一个多小时内完成一个可以编译通过的文件系统(我承认完全能跑是我回到家后又调试了一个多小时才完成的...),这对于我而言,是一个挑战,但我要试一试,没想到就成功了。所以才有了今天的分享。
我从最底层的介质结构的设计开始。
我并没有真实的硬件介质,也并不打算编写专门的 格式化程序 去格式化一块内存区域,所以我直接用大数组定义一块内存,它便是我的模拟介质了,我的tinyfs的文件格式如下:
这个文件系统的格式非常的Low:
之所以这么Low是因为它只是一个开始, 当这个文件系统实现并且能跑之后,你会发现它因为Low而带来的不足和一些代价,而弥补这些不足正好是优化的动机,带着你逐步实现一个更加不Low的文件系统,在实现的过程中,你会窥见并掌握Linux内核文件系统的全貌和细节。 完美的学习过程,OK!
下面是代码:
review代码后,你可能已经发现了几个问题:
嗯,其实这些问题目前而言还都不是问题,它们并不阻碍这个文件系统的真实性,它用起来是那么的真实。
没有任何规范规定一个文件系统存储格式必须有什么或者必须没有什么,文件系统格式只是一个 看上去还可以的信息持久化记录格式 ,只要下次能根据某些信息将文件读取出来,任何格式都是OK的。
之所以很多人会认为一个文件系统的格式必须要符合某种规范,完全是因为人们看的最多的那些文件系统ext3,ext4,ntfs等恰好是那样做的罢了。不过事实证明,那样做确实是很好的。在可用的玩具完成之后,就要考虑 性能,健壮性,可扩展性 等这些工程因素了。而ext3/4,ntfs,xfs则充分考虑了这些。
逐渐的,权威变成了规范,至少成了一种范式或者模式,这是计算机领域常有的事,见怪不怪。
我们来使用下这个文件系统:
OK,挂载成功。
这里又有疑问了,为什么是none挂载,而不是一个块设备,比如/dev/sda1之类的。
这是因为我根本没有将介质(其实是一块连续的内存)抽象成块设备,如果引入块设备抽象,势必要导出device层 *** 作接口,这样做并不困难,但却太麻烦,且块设备抽象和文件系统的实现核心无关。本文不是讲块设备的,加之班车上的堵车时间有限,故不做抽象。
好了,现在让我们来折腾下/mnt目录,该目录就是我的tinyfs的挂载目录了,在其下读写文件,就是在tinyfs的内存介质上读写文件:
除了最后一个删除目录的 *** 作,其它的都OK,这也是预期之中,毕竟删除目录是TODO嘛。
一共300来行的代码(省去了很多异常判断和处理,真实情况下,这些要占据80%的代码量),非常容易读懂,你会发现这个文件系统实现是如此之low,然而却能看起来像真的一样。
这意味着完成和完美真的是两回事!
很多最终看起来很大型的东西,都是都这种刚刚完成可以用开始的。
很明显,这个代码没有使用块层来和底层介质通信,而是直接 *** 作了底层介质,也就是那块连续的内存。因为我用内存模拟介质,尚且OK,如果底层真的有一个类似磁盘那样的慢速介质,每次 *** 作直接读写block将是不可接受的。但如果你想获得性能上的提升,就必须使用块层的缓存机制,以及pagecache机制。
所以,方向很明确,我们有了一个todolist:
这些todo完成,意味着对Linux内核文件系统实现原理的彻底掌握,从一个简单的刚刚可用的tinyfs开始(这花不了多少时间),到整理出一份todolist,到完成这些todo,这便是一个任务驱动的学习过程。
回过头来看Linux文件系统IO的纵向视图:
这次注意蓝色部分,我们的TODO就是要补充这部分的实现。
好了,换一个视角看VFS。
我们把Linux内核内存中的VFS看作是磁盘等慢速介质中特定文件系统的缓存,这是一个典型的 分级存储结构, 就好像CPU cache和内存的关系一样。
在这个视角下,如何完成上图蓝色框框中的部分,可参考的现成范式就太多了。但无论如何都要解决的是:
Linux内核已经给了我们一个现成的答案:
当然,如果你觉得这些不够好,你也可以设计你自己的。总之,这是一块非常独立的工作,正如我图中所示,这部分工作的目标是,在文件系统刚好可以工作后, 让事情变得更加完美有趣!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)