当前位置: 首页 > news >正文

7.5 文件系统

目录

一 文件系统是个什么玩意

二 常见文件系统的实现

   1、Fat32文件系统

   2、Unix文件系统

   3、其他形形色色的各种文件系统

三 文件系统在存储介质(物理介质)上的映像

四 Linux下文件系统如何与内核挂钩

五 文件系统概念拓展

六 从设计层面考虑


   文件系统,顾名思义,就是用来管理文件的。又因为文件都是存在于磁盘上的,所以,更进一步,文件系统又是用来管理磁盘上的文件的。文件本质上是有组织的数据,所以,再进一步,文件系统是用来管理磁盘上的数据的。

   大部分普通用户对文件系统的直观感受,来自于磁盘的格式化操作,或者重装系统。在这两步操作中,都会提示用户选择文件系统的类型。许多人并不清楚,不同类型的文件系统有什么差别,做出的选择要么是默认配置项,要么是别人或者网络上的建议。在这个过程中,很多人并不关心文件系统的类型,只要保证所选择的文件系统类型不要影响自己后续的使用即可。

   对程序员来讲,接触文件系统最多的应该就是平台提供的文件操作接口。包括创建文件、打开文件,向文件中写入内容或者读取文件里的内容。而在这个过程中,也只需要保证特定目录下的文件能成功的被读写即可。至于文件是如何存到磁盘上,又如何写到磁盘上,再又是如何从磁盘读出,则并不需要关心。

   所以,不管是对普通用户还是程序设计人员来讲,看到的都是简化了的,逻辑化的文件系统,直观的表现就是基于盘符或目录的文件管理器。至于文件到底是在磁盘上如何存放的,都并不关心,也不需要了解。总的来讲,文件系统就是简化对磁盘的使用,方便用户以更加形象的文件方式存储各种数据。

   但也正是因为如此,导致文件系统的功能作用比较单一,分层也比较清晰,虽然也有很多的细节和技巧技巧要掌握,但是相对于内存管理,它的实现逻辑还是要简单一点,也比较容易理解。不过在这一部分,我们不会讨论太多底层硬件相关的细节,而是更多的向读者展示文件系统的逻辑框架。

一 文件系统是个什么玩意

   要更进一步了解文件系统是做什么的,到底是怎么工作的,还需要从一个实际例子入手。

   基于操作系统的图形界面,我们看到文件系统最直接的表现,就是文件管理器。这也是我们直观感受文件系统最简单的方式。最顶层是磁盘,然后有目录,目录下有文件夹和文件。在文件这一级上,又有不同种类不同格式的文件。但是,不管是照片、视频还是各种可执行程序,也不管是什么格式的照片、视频和程序,在操作系统看来,统统都是文件。

   上图为WIN10系统典型的文件管理器界面。

   上图为Linux类系统Ubuntu发行版的文件管理界面。

   上述两张图展现的文件系统,虽然底层实现大相径庭,但是面向用户所展现的概念还是比较接近的,都是磁盘、文件夹和文件。很显然,如果没有图形界面,那么就看不到形象的磁盘、目录、文件夹的图标,也看不到各种不同的文件格式对应的图标,有的只是字符串及斜杠组成的枯燥的"表达式",操作上也由图形界面的点鼠标方式,变成需要通过学习才能掌握的命令行。如下图所示:

命令行形式的文件管理界面

   上面我们介绍了文件系统在直接面对用户的情况下的最直白表现,那么在文件系统的最低层,又是什么样子呢?在硬件模型部分,我们对磁盘进行了抽象建模,了解到磁盘实际的操作涉及到磁头、柱面、磁道、扇区等概念,而且有控制器来控制磁头的移动。要实现数据的读写,只要按照硬件要求,开发驱动即可。这里不考虑硬件细节,抽象一点,我们认为底层就是一块需要给定驻面、磁道以及扇区等参数,按块来读取的存储区。这样一来,底层就是在逻辑上由连续的小存储块构成的大块存储区,这与我们在抽象模型部分的介绍是一致的。

   在了解了上层和底层以后,就可以清楚的看到,文件系统就是把底层连续的存储块组织成上层见到的,基于目录树结构的,方便用户使用的一套操作接口。

   其实,图形界面傻瓜化了对文件系统的使用,同时也遮蔽了对底层细节的展露。相对而言,命令行界面反倒更贴近文件系统实际的接口,更易于理解文件系统。不过图形界面也是基于同一套接囗实现的,只是二者涉及的抽象层次不同。

   下面我们用形象的语言来为上层与底层之间搭桥。我们假设用户单击了一个盘符,想看看这个盘下面,有哪些文件夹与文件。这一操作最终会转化为对操作系统文件系统模块的一个调用,比如就叫scandir。在这个调用里,只需传入路径参数,比如这里的盘符,接口就会按照文件系统的组织,查找目录和文件,然后返回路径下查找到的内容,比如这里需要的文件夹和文件的名称。在用户界面展示层,将文件夹和文件以不同的形象的图标进行显示,我们就可以看到路径下的东西了。这里的关键就在scandir接囗调用。在scandir接口中,应该实现这样的功能,通过路径参数,找到存储该路径下内容的磁盘块,将其中保存的文件夹和文件信息,按照特定的格式(文件系统相关的)进行解析,将解析后的文件和文件夹名称返回。这就是scandir接口的功能实现。

   从上面的过程,我们看到,文件系统最简单的作用就是将磁盘存储块以特定格式组织起来,读写过程都遵守这个组织形式。这样,通过该组织形式,就可以将具体磁盘存储块与磁盘目录数据关联起来。这就好比是我们日常看的书,一页一页的内容,好比是磁盘块,而目录则是这种组织形式。通过目录,我们就可以方便的找到想要看的内容。如果要说二者有什么差别,最大的𣎴同在于书是静态的。一旦校订好,印刷完,所有就是定了的,包括目录是定了的,内容也是定了的,并且每一部分内容,都是连续存在的。但对于文件系统,则恰恰在于动。这个动来自于不仅可读,还要可写。这就是说,内容不一定是连续的,因为内容随时可增加删减;目录也不是固定的,内容中还可以嵌套更深的目录。文件系统的复杂性恰恰就在于既要通过目录来方便管理,又要满足这种动态变化带来的灵活性要求。下面我们就看两个常见的文件系统,来了解下实际使用的文件系统是怎么做到这种动的特性的。

二 常见文件系统的实现

   知道了文件系统倒底是个什么玩意后,就来看看几个流行的成熟的文件系统,从不同的角度、层次,来一窥文件系统的实现奥妙。同时,通过对同一目标系统的不同实现的研究了解,也可加深对文件系统背后原理的理解。

   1、Fat32文件系统

   Fat32是Windows系统上早期流行的文件系统。这里的32是指32位系统,因为Fat32是从最开始的Fat16系统演化发展而来的。在Fat32中,目录分级存放在扇区中表结构体中,因此整个结构相对而言很简单。如下图所示:

   其中,FAT1为表结构,FAT2为FAT1的备份。可以理解表结构就是整个文件系统的“钢”,纲举目张,通过FAT1表,就可以知道有哪些目录,有哪些文件,都是什么属性,比如是可读还是可写,文件在哪里等等。因此,这种实现非常符合人们的理解习惯。跟书本的目录能够贴切的对应起来。

   简单的结构虽然比较容易理解,容易实现,但是也容易造成灵活性和扩展性不足。所以,当下,Windows系统中,磁盘大都格式化为NTFS文件系统。但是FAT也并非一无是处,在嵌入式和一些小容量的SD卡等领域,还是在大量使用。

   2、Unix文件系统

   Unix基于树型结构来管理文件系统。下图为经典的Unix文件系统示意图。图片来自网络。

Unix文件系统的原理框图

   这个架构中,最有特点的就是多级索引。直接块部分,记录了可直接获取的数据信息,对于特别小的文件,就不需要有一级和二级索引了。如果文件比较大,超出了直接块能够表示的范围,则可继续通过一级索引来获取数据。一级索引对应的块中保存的是数据的直接索引,所以,通过一级索引,获取到直接索引,再通过直接索引,就可以获取到数据。这有点C语言里面的指针的指针概念。自然,二级索引保存的就是一级索引,在通过其保存的一级索引查找到直接索引。显然,只有很大的文件,才需要用到二级甚至三级索引。

   3、其他形形色色的各种文件系统

   上面介绍的两个,只是目前比较典型的,适合教学(复杂度适中,原理清晰)的两个文件系统。并不是说主要的文件系统就这两个,还有许多形形色色的其他文件系统,并且主流的文件系统也已经不是上述这两种了。另外,还有被用于许多生活中所见到的消费电子产品上的一些文件系统,这尤以嵌入式系统多为常见。像Cramfs、Yaafs、JIFFS等基于Flash的文件系统,可能很多人都不曾听到过。这些文件系统是针对具体设备、具体应用场景设计的,各有自己的特点,也在不断的向前发展。像Yaafs是目前流行的智能手机安卓所用的文件系统。所以文件系统也不是越复杂越好,还是量体裁衣才好。其实还是常说的那句话,没有最好的,只有最合适的。这些非主流文件系统,在丰富多样的消费电子产品中广泛使用,正是因为它们满足了具体产品的需求,也具备足够的灵活性。

   至此,我们了解了多个文件系统的实现原理。

   不同的文件系统,有不同特点,比如有日志型的文件系统,可以再异常情况下,协助恢复部分数据;有只读文件系统,可以用于代码文件的存放;有压缩文件系统,可以压缩存储,节省空间;有网络文件系统,方便通过网络搭建和使用。对于不同格式文件系统的优缺点,网络上也有很多资料,感兴趣的读者可以搜索了解。

三 文件系统在存储介质(物理介质)上的映像

   在前面,我们看了实际的文件系统后,再来思考一下,有没有什么特别的东西,是这些文件系统共有的特质?下面我们就探寻这个问题。

   在看过几个文件系统后,在这一部分,我们来看看Linux早期版本上,文件系统的实现细节。这里就以Minux文件系统为例。在多本有关Linux早期版本实现原理的书籍中,对该文件系统都有过详细的讲解。在这里,我们更多的从内存和整体[就是从磁盘到内存的映像]的角度来谈讨其实现。

   从本质上来讲,Minux文件系统是类Unix的文件系统,但是更为简单,细节也更少,确实很适合做文件系统的案例讲解,达到举一反三的效果。

   首先,还是从必不可少的几个概念介绍开始。如下图所示:

   上图展示了Minux文件系统涉及到的概念,包括:

   超级块概念:超级块记录了文件系统的整体信息。比如是什么文件系统,分区的大小,分区的块数,mount挂载的相关信息,i节点的统计信息等。系统要挂载和读取一个文件系统,就从超级块开始。超级块就像是控制中心,通过超级块可以直接获取文件系统的总体信息,并可以通过一些间接指针,逐步的掌握该文件系统相关的所有信息。因此,被称作了超级块。上图是linux早期版本的实现方式,可以看到,在系统里使用一个超级块数组,用于保存所有挂载到系统的文件系统。

   i节点概念:i节点的概念跟Unix文件系统中的i节点类似。内核通过i节点组织文件系统。无论是文件还是目录,最终都抽象到i节点上,由i节点最终找到具体的存储内容的磁盘块。内核中使用i节点位图表示所有的i节点使用情况。

   逻辑块概念:这是最接近文件底层的物理概念。逻辑块用于实实在在保存文件内容。其实可以感受得到,当我们提到一个文件时,隐含的包含了文件本身的内容和文件的一些属性。这自然的就可以对应到逻辑块和i节点上。

   对于内核来讲,除了上述物理层面组织文件的概念外,还有不少逻辑层面描述文件的东西,比如文件的描述符。文件是全局的概念,系统中每个打开的文件,都有相应的文件描述符指向。文件描述符是进程相关的概念,如果多个进程打开同一个文件,那么它们各自保存对文件的指向,也就是各自都有指向该文件的描述符。其中一个对文件的修改,最终也会被另一个观察到。

   结合上面这些概念,我们来看看磁盘上格式化后的文件系统是什么样子。这个样子也就是我们使用文件系统之前,磁盘的状态。如下图所示:

文件系统格式化后磁盘的映像

   有了磁盘映像,就有了基础。从上图也可以看出,格式化就是用于说明磁盘后续该如何使用。这个说明的内容,跟虚拟内存的页面类似,也是属于内耗,也就是其本身也存储在磁盘上。系统运行时,如果要挂载该磁盘,就会读取磁盘开始的内容,并按照文件系统的格式要求,解析相关内容,然后保存到内核数据区,构建一个动态的运行时结构。随着系统的运行,文件的创建和删除动作的进行,这个动态运行时的结构也会发生着变化。其实,我们可以想像出,工作一段时间后,磁盘的样子,它可能发生了如下一些变化:

   1 磁盘空间占用情况在动态变化中;

   2 i节点位图的空闲i节点情况在动态变化中;

   3 逻辑区块位图中有关逻辑区块的使用情况在动态变化中;

   4 i节点的关联关系在变化中;

   5 最直接的,物理区块上有关文件的内容在动态变化中,从底层根目录到叶子文件。

   下图是一个示例(图片来自网络)

   感兴趣的读者,可以自己设想几个操作场景,然后将相关数据结构内容和关系的变化构图出来。这个变化最终反映到最终的物理磁盘上,这样断电后相关信息不会丢失。如果我们重新格式化了磁盘,自然,所有动态变化中产生的修改都将被抹平,一切都将回归到初始状态。这就是为什么使用格式化操作时要特别谨慎。

   其实,如果读者刚才想像了在文件系统运行过程中,磁盘内容可能产生的变化,那么实际促使这个变化产生的过程,就是这一部分要介绍的文件系统在内存中的映像,而我们想的过程,就是CPU具体操作的过程。

   有了磁盘映像后,内存映像并不复杂。因为,那些在磁盘映像中出现的数据块,在内存中都需要有相关的数据给构,以方便CPU的管理。这样,将这些需要用到的数据结构关联整合起来,就构成了文件系统的内存映像。如下图所示:(图片来自网络)

   有了内存映像之后,就可以配合磁盘映像一起,展示Linux文件系统的整体工作过程了。

   假设文件系统现在是处于格式化后的状态,还没有有效的目录和文件。首先,先创建一个目录,就叫TestDir,过程如下:

   1 找到当前文件系统所在的超级块以及该超级块所在的挂载点。找到挂载点,也就找到了当前文件系统的“根”目录;

   2 通过超级块找到一个空闲的i节点,用来创建目录;

   3 通过超级块找到一个空闲的逻辑快,创建一个目录文件,该目录文件目前只有目录名;

   4 将3中的逻辑块关联到2中的i节点上;

   5 将2中的i节点关联到文件系统的根节点目录上;

   6 同步内存和磁盘上的结构化数据。

   第二步,我们在TestDir目录下创建一个文件,叫做TestFile,过程同上面类似,不过此时创建的是真正的实体文件对象。

   通过上面两个流程的介绍,对整个文件系统的实现和工作过程,会有一个更加淸淅的认识。至此,算是解决了文件系统倒底是个什么玩意这个问题。此时,也就可以回答本节开始的问题----即文件系统共性的东西是什么了。个人理解,这个共性的点,本质上就是一种组织结构。同样的磁盘可以格式化为不同的文件系统,应用于同一个文件系统中,这中间的关联就是大家对磁盘和内存映像的组织结构达成了一致。不同的组织结构设计,就是不同的文件系统设计,自然也就有不同的复杂度和其自身的特点。

四 Linux下文件系统如何与内核挂钩

   首先,通过上面的贴图,我们补充丰富了内存里面有关文件系统的内容。比如填加上文件系统相关的数据结构。部分内容可参考《Linux内核设计艺术》一书。

   有了内核文件系统,那么内核文件系统与磁盘上的文件系统内容是怎么关联到一起的?因为CPU始终是在内存中执行逻辑,因此,即使是磁盘上的文件结构,也要与内存建立关系后,才可以发挥效果。在类Linux系统中,这种关联是通过挂载操作实现的。

   文件系统的挂载概念,是存在于类Unix系统中的一个概念。因为类Unix系统的文件系统都采用了树型结构,这种结构在文件系统的可扩展性上,具有先天的巨大优势。挂载就是这一可灵活扩展的体现。挂载者,意为挂接扩展,其实也是一个形象的术语,其作用就如同下图的挂接过程:

   显然,只要维护了上下级关系和根节点,从任何一个点出发,都是可用遍历整个树的。这也与我们文件系统中目录和文件的层级关系比较贴近。

   上面这幅图形象的展示了挂载的含义。通过挂载,可以方便的实现对不同文件系统格式的外部存储的动态支持。通过挂载,也可以实现网络这种非实体存储的文件系统的支持。关于挂载所关联的非实体存储文件系统的相关概念,在拓展部分介绍,这里重点讨论基于实体存储的挂载实现流程。

   通过上图我们初步了解了挂载的含义,目的。挂载作为操作系统中与文件系统相关的一个非常重要的基本操作,具体是如何实现的呢?后面,我们将详细的介绍这一过程。其实挂载过程并不复杂。

   首先,我们看一下Linux下,挂载的具体命令是什么。通过命令,间接对挂载有一个接近实现的认识。

   mount -t (xxx)  /dev/sd[x]  /root/mnt/

   一个最简单的挂载命令如上。T选项用于指明挂载时采用的文件系统;dev目录下对应的是具体的设备。最后是要挂载到的位置。也就是将某个设备,按照某个文件系统挂载到现有文件系统的某个目录下。有时候,文件系统类型不是必须的,系统会自动解读设备上的文件系统类型。所以,最关键的两个参数就是将那个设备挂载到那个目录下。

   其次,我们需要明确,一种文件系统类型是一个相对封闭的小系统,这里说封闭,是说文件系统自身的信息,足以满足该文件系统相关的所有操作,不再需要额外的辅助信息,这就为挂载的实现,提供了可能和便利。更直白的描述就是,操作系统内核中有一些数据结构,保存了文件系统的关键信息,包括节点信息,系统表等。依据这些信息,内核就可以很容易掌握整个文件系统的信息,这些节点就像是纲,起提纲挈领的作用。到此,读者可能隐约的感觉到了,对于我们之前介绍的minux文件系统,通过超级块,及相关辅助数据结构,就可以对文件系统包括的东西,有完整的掌握。超级块就像是一本书的最顶层的目录,即使所要寻找的内容隐藏很深,也能通过目录,顺藤摸瓜,一级接一级,最终找到需要的内容。

   注意,上面的描述有一个隐含的信息,那就是超级块的位置是确定的。指定设备参数,就等于间接告诉内核,超级块在哪里了。

   有了上述两条内容建立的基本概念后,这里以Linux内核为例,来介绍Linux内核挂载根文件系统的过程。

   1 内核为什么挂根文件系统

   我们先看看什么是根文件系统。根文件系统也是一个文件系统,但是与一个单纯的文件系统不同在于,根文件系统中集成了系统运作的必要程序。内核完成自己的工作后,挂载根文件系统,然后执行根文件系统中的init程序,并将交互控制交给该程序。基于该程序,我们可以实现各种不同的应用场景。最普通、最普遍的就是调出命令行,也就是我们常说的shell环境,然后等待用户使用系统。

   内核要跑起来,必须有根文件系统的支持吗?从能力来讲,这个要求不见得是必须的。内核完全可以设计成这样:启动后构建一个基于内存的最小系统,然后等待用户的使用。这个基于内存的最小系统,不需要其他辅助条件,内核自己就可以搞定。但是,从设计的角度来看,从分工的实现来看,将根文件系统和内核分开,有助于内核专注自身的进化,也有助于支持多种多样的系统。

   这就好比,内核设计的可以执行程序,但是只需要约定好二进制格式就可以了,没必要内核自己去实现各种程序。我们可以认为,不同的发行版,就是不同的根文件系统。内核是系统的核心,但不是系统的全部。围绕内核构建的一个小生态,才是我们传统意义上说的系统,面向用户的系统。包括Windows、Ubuntu、Android、iOS等等,都是从这个角度来表达操作系统概念的。

   2 内核挂根文件系统前的准备工作

   参考《Linux内核设计艺术》

   3 内核挂根文件系统执行的命令

   《Linux内核设计艺术》

   4 命令执行的过程

   《Linux内核设计艺术》

   5 挂载完成后内存中的内核情景图

   参考上面的文件系统内存与磁盘映像图。

   好了,至此,对文件系统的挂载基本就介绍完了。

   通过上面介绍,可以对挂载的条件做一个总结梳理:一,要挂载文件系统所在存储设备;二,要挂载文件系统的类型;三,新的文件系统要挂载到的目的地,也就是目的目录。满足了这三条,整个挂载过程就是,从源设备上找到文件系统的“钢”,经过一系列的处理,挂接到目的目录上。后续用户操作目的目录时,即按照新挂接的文件系统类型规则进行,用户看到的也是新设备文件系统中的内容。但是具体的操作或者说展示给用户的视图,并不改变,仍然是基础的文件系统层次结构,而且用户感觉不到文件系统的切换过程。

五 文件系统概念拓展

   到目前为止,我们更多介绍的,还是传统意义上的文件系统。其实在计算机技术发展的很早期,一些非传统意义上的文件系统就已经存在,并且很流行了,像网络文件系统等。这就是我们这一部分要介绍的文件系统概念的拓展。

   由于文件系统的操作接口相对已经很成熟了,且又存在大量的文件系统类型(且还不断有新的文件系统类型研发出来),并且还有非传统意义上的文件系统存在,那么该如何应对这各种各样的文件系统类型呢?显然,得做很好的抽象,才能应对未知的变化。操作系统的设计者就是这样做的。这跟面向对象设计里的抽象思想是一致的。操作系统的设计者们也设计了一个抽象文件系统(在linux里,这个文件系统叫虚拟文件系统VFS),定义了文件系统应有的接口,然后在这套接口的约束下,可以有各种具体的文件系统设计与实现。最终,形成了类似如下图所示的文件系统架构(图片来自网络):

   从上图可以看出,这种架构中,包括了一个虚拟文件系统层,向上,屏蔽底层各种不同文件系统类型及接囗差异,向用户提供统一的操作接口;向下,为不同文件系统提供统一的对接接口,文件系统的实现者可以根据需求,设计自己的文件系统,最终只要正确对接到虚拟层的接口上即可。

   通过这种分层的设计,既达到了简化文件系统架构的目的,同时又满足了灵活性和可扩展性的要求。相当于搭好了一个框框,里面的内容可根据需求灵活选择。对于Linux操作系统而言,框框里具体文件系统类型的选择,既可以是动态的,也可以是静态的。这个动静态怎么讲呢?在编译内核时,可以选择将某个文件系统编译到内核代码中,也可以根据磁盘存储空间大小,选择不将某些文件系统编译到内核代码段和数据段中,从而达到减少磁盘空间占用的目的。这就是静态的选择。另外,文件系统也可以被设计为动态库的形式,在运行过程中根据需求,动态加载到内核中,以支持特定的存储设备。这种可在操作系统运行过程中根据需要来支持某种文件系统的方式,就是动态的选择。Linux内核通过动静结合,可以满足多样的需求,尤其是在嵌入式领域。

   在前面,我们提到了传统意义上的文件系统,还有非传统意义上的文件系统。这里所谓的传统意义上的文件系统,多指最终面对各种实际存储设备的文件系统,比如磁盘,U盘等。而非传统意义上的文件系统,则多指不直接面对实际存储设备的文件系统,像前面提到的网络文件系统就是这种。那内核是如何支持这类非传统意义上的文件系统的呢?

   其实道理是一样的。这里的关键就在于如何定义文件系统的各种接口行为。比如,以Unix中一切皆文件的抽象思想为例,网络设备也可以看做是一个文件。打开网络设备时,可以完成socket的创建和连接过程。读网络文件设备时,就对应到socket的读上,写对应到socket的写上,这样我们就可以将网络设备关联到文件上。网络文件系统也是类似道理。虽然网络文件系统不对应本机实际的存储设备,但是往往会对应到目标节点的物理存储设备,网络接口只是中间的一个代理而已。这样,我们就可以将文件系统的相关操作通过网络转发,自然就可以实现基于网络的文件系统了。

六 从设计层面考虑

   至此,我们对文件系统的概念有了一些介绍。当然,这其中,有的部分详细,有的部分粗略。在这一节,要考虑的是,当我们要自己动手设计一个完善的文件系统时,应该考虑些什么?看了、学了别人设计的文件系统,我们能不能也设计一个有特点的文件系统岀来?这与其说是一个问题,还不如说是一个总结。因为将别人设计过程中考虑的共性问题总结出来,答案也许就自然而然明了了。关于这个主题,是一个开放的话题,这里粗略总结如下一些内容,权当抛砖引玉用。

   1 读写原则:只读、读写。显然,如果是一个只读文件系统,就可以少考虑很多问题,从而简化设计,将焦点关注于文件系统的其他特性上,比如更少的内存占用,更高的吞吐率等。推而广之,还可能存在是否修改的特性。比如对于大数据应用场景,常常是写完数据后,后续主要是查询,而很少存在修改的操作,针对这种场景,也需要对文件系统的设计做出相应的改变。

   2 节点原则:节点占用尽量少的内存空间。这一方面可以节省空间,另一方面也可能加快加载速度。当然,这不是绝对的。对于现在的系统,还要考虑CPU访问总线的宽度、cache的大小、缓存的设计等。其实,这一点可以更准确的总结为更高效的节点设计。

   3 速度原则:高速。这是一个很重要的指标。如何在特定的存储设备上优化设计,达到高的读写速度,是特别体现综合设计能力的。

   4 文件大小:比如是否支持超大文件等。这会影响节点的组织和层级设计。

   5 压缩原则:是否可压缩。这里说得压缩,不是说是否支持压缩文件,而是文件系统自身是否支持压缩。这种情况大多可能出现在嵌入式系统上面,应用于存储空间较少的情况。显然,压缩会影响速度。

   6 日志原则:是否带有日志信息。这一点主要是考虑是否需要对文件进行恢复。当然,任何特性都可能是需要付出代价的。比如,这里的恢复,就需要冗余设计,需要占用额外的存储空间等。

   7 缓存原则:通常来讲,CPU内部的寄存器访问在纳秒级别,内存访问通常在几十到百纳秒内,而一旦访问物理磁盘,则可能在几十毫秒,所以,为了优化磁盘访问,提高文件读写效率,常常需要在内存中提供缓存,用于保存预读的磁盘内容或者预写到磁盘的内容。

   8 备份原则:如前第一条提到的,针对大数据场景的文件系统设计,就有很高的可靠性要求。这种场景下,往往需要将数据写入多个物理无关的磁盘区块,从而在某些物理硬盘损坏时,仍然能够保证数据的完整性。

   9 索引原则:这主要是针对快速检索的需求。类似数据库查询的设计实现。

   10 均衡原则:对于flash这类设备,读寿命远远大于写寿命,这是因为写操作通常都需要关联一个擦除操作。针对这类设备,实现文件系统时,就需要考虑均衡性,也就是写数据所在的区块,要整体上相对均衡一些,避免出现热点区块,导致坏块的出现,从而整体延长设备的使用寿命。另外,像一些日志型的文件系统,也不太适合这种设备,因为大量的日志写操作,会明显影响设备的使用寿命。

   11 随机原则:这也是与物理存储设备相关的一个特性。有的设备支持随机的读写,而有的类型的设备对随机读写支持的不是很好。这样,在设计文件系统时,就需要考虑这个问题,有针对性的进行优化设计。

   从上面这些原则可以看出,有些之间是相互冲突的,这也就解释了为啥会有这么多种类的文件系统了。我们很难在一个文件系统中包含所有的特性,据此,我们可以说没有最好的文件系统,只能说是最合适的文件系统。

   以上为个人的一点总结,仅供参考。其实对于这个话题的深入思考和讨论,对理解现有的各种文件系统也是很有帮助的。这就像做题,如果是依赖答案,而不是自己深入思考做出来的,下次遇到仍然可能做不出来。思考就是内化的过程,这也是为啥要总结这一节的原因。

相关文章:

  • java计算机毕业设计伊伊物流公司的管理系统源码+数据库+系统+lw文档+部署
  • PCB设计笔记
  • 图卷积神经网络(GCN)
  • 【数据结构】八大排序
  • D*(Dynamic A*)路径规划算法
  • 16.12 - 基于数据流设计用例
  • 大数据工程师、数据挖掘师和数据分析师有啥区别
  • 面试让我手写红黑树?!
  • C/C++学习笔记 资源获取是初始化 (RAII) 理解
  • 高项 16 战略管理
  • Vue框架背后的故事
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • 计算摄影——妆造迁移
  • 【物理应用】基于Zernike多项式的大气湍流相位屏的数值模拟附matlab代码
  • 【工具网站推荐】文字转语音
  • 「面试题」如何实现一个圣杯布局?
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • C++入门教程(10):for 语句
  • conda常用的命令
  • css系列之关于字体的事
  • ES6之路之模块详解
  • exports和module.exports
  • Javascript Math对象和Date对象常用方法详解
  • JavaScript-Array类型
  • JAVA并发编程--1.基础概念
  • k8s 面向应用开发者的基础命令
  • laravel with 查询列表限制条数
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • mysql外键的使用
  • PAT A1120
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • yii2权限控制rbac之rule详细讲解
  • 简析gRPC client 连接管理
  • 驱动程序原理
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 详解NodeJs流之一
  • 移动端解决方案学习记录
  • 硬币翻转问题,区间操作
  • 运行时添加log4j2的appender
  • postgresql行列转换函数
  • # 安徽锐锋科技IDMS系统简介
  • #include<初见C语言之指针(5)>
  • $forceUpdate()函数
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (三)Honghu Cloud云架构一定时调度平台
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • .htaccess配置重写url引擎
  • .NET Core 控制台程序读 appsettings.json 、注依赖、配日志、设 IOptions
  • .NET Standard 的管理策略
  • .Net Winform开发笔记(一)
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】