文件原理

文件原理

磁盘操作

磁盘组织与管理

由于文件系统是磁盘的抽象,因此不了解磁盘就很难深刻理解文件系统。

那么什么是磁盘呢?通俗地讲,磁盘就是形状像盘子的磁性存储介质。这种存储介质的特性是持久,正常情况下数据存放在上面不会消失;磁性、圆形是它的显著特征,所以称为磁盘;成本低廉,所以可以大量使用。

磁盘的结构

一块磁盘实际上并不只有一块盘片,而是由多块盘片组成,每块盘片的正反两面皆可以存放数据。每个盘面上都配有一个读写磁头,而所有的读写磁头连在一根共享的磁臂上。当磁臂运动时,所有的磁头均作相同的运动。盘片则以常速不停地旋转。这个旋转速度通常为每分钟3500转到10 000转,即大约每6毫秒到17毫秒旋转一圈。

除此之外,一块磁盘还含有信号和界面电子系统。通过这些电路和电子系统,我们得以将系统用户与系统的运作原理隔离开来。例如,对于一个磁盘驱动器用户来说,他无须了解磁性表面上数据字位的读写操作是如何完成的。现代的磁盘都带有一个微控制器,如IDE磁盘。这种磁盘提供较为复杂的功能,如纠错、数据分拆与组装、磁盘调度等。也有的磁盘非常简单,仅仅提供字位流的读写,而其他复杂的功能则交给集成在计算机主板上的设备控制器来做。

在每一个盘片表面上只有一个读写磁头。该磁头由少量的空气垫层浮起,悬浮在盘面上方约几个微米的高度。磁头在盘面上的移动操作由一个伺服机构(ser-vomechanism)负责控制。读写磁头可以在6毫秒到17毫秒的时间内遍历磁盘的整个活动区域。这个时间与磁盘旋转一圈的时间一样。不管一个磁盘驱动系统有多少个读写磁头、多少磁道或多少盘面,在任何时候只能有一个磁头处于活动状态,即从盘面输出的数据流或输入盘面的数据流总是以位串方式出现。

盘面的结构

为方便存储数据,人们将每块盘面分为磁道和扇面。磁道是一个个的同心环。而每个磁道又被分为扇面(也称为扇区)。数据则以扇面进行存储。扇面也是磁盘I/O的最小单位。

扇面的内容通常分为3部分:标题、数据部分和ECC纠错信息。

  • 扇面的标题通常包含同步和位置信息。
    • 同步信息允许磁头的定位电路将磁头保持在磁道的中心位置
    • 位置信息则允许磁盘控制器决定扇面的身份。如果是读取操作,则磁头将捕捉存放于盘面上的信息;在写入操作
      时,则将信息写入盘面。
  • ECC信息的功用是对数据段提供错误侦测和纠正。

有的书籍会将同步信息从标题部分分出来,并加上一个尾区,从而形成五个部分的扇面结构。

对于不同的磁盘系统来说,扇面里面每个区的大小可能不一样。但数据部分则通常都是512 B。至少该部分应该是2的指数次方大小。

一般来讲,磁盘驱动器的制造商会将磁盘进行初始化或格式化,即将原始的磁道和扇面信息存入盘面,并对盘面进行检查以判断数据是否可以写入每个扇面或从其读出。如果某个扇面确认被损坏,即在使用ECC的情况下也不能使用该扇面时,则格式化过程将对这个扇面作出标记,这样操作系统就不会在这个扇面上进行任何读写操作(避免使用这个扇面)。

一个磁盘驱动器的所有盘面上处于同一位置的磁道构成磁柱。

磁盘驱动器的访问速度

磁盘在读写数据时的访问速度称为磁盘的动态属性。这是衡量一个磁盘性能的重要数据。数据访问时间或速度的计算并不简单,而这种计算不仅依赖于磁盘的旋转速度,而且还依赖于在访问开始时读写磁头所处的位置。数据访问时间也有几种衡量办法。

  • 寻道时间(seek time)指的是把读写磁头移动到所要求的磁道位置所需要的平均时间。实际的寻道时间则依赖于在读写请求收到时读写磁头所处的位置和磁头需要移动的距离。但由于没有办法在收到访问要求时获知这些数据,因此我们常常使用平均寻道时间这个概念。平均寻道时间则是通过测量来决定。该数值将有赖于磁盘驱动器组件的物理尺寸和磁头进行加速和减速的快慢程度。寻道时间通常在8ms~20ms内,并且在近年来没有多少变化。
  • 磁道到磁道的访问时间(track-to-track time)衡量的是磁头从一个磁道移动到一个相邻磁道所需要的时间。 这个时间通常在2毫秒~3毫秒内。该时间在有的资料上也称为柱面切换时间(cylinder switch time)或磁道切换时间。
  • 旋转延迟时间指的是在磁头到达所要求的磁道位置后,等待所要求的扇面旋转到磁头下方的平均时间。若平均来讲,所需要的扇面将在磁头刚刚移动到所需磁道时处于离磁头半圈远的距离,旋转延迟通常为旋转时间的一半。当前磁盘驱动器的旋转速度所处的范围为每分钟3500转到10 000转,因此旋转延迟的范围在3ms~8.57ms内。
  • 平均访问时间是寻道时间加上旋转延迟。
  • 爆发速度衡量的是在磁头到达所需扇面后,磁盘驱动器输出或接收数据的最大速度。这个数值等于磁头发送数据位流的速度(如果系统里其他部分也能以此速度产生或接收数据)。
  • 可持续数据速度则指的是在一段可持续时间内数据可以被访问的速度。这个参数在设计那些必须进行大文件实时访问(如视频或音频数据)的系统时非常重要。如前面所说,这个数值可以从磁盘驱动器的规格上进行估算,但该数值也需要经过实际测试的验证。不可预见的因素有可能影响这个可持续数据的速度。例如,许多磁盘驱动器周期性地进行磁头校正操作,在这段时间内,磁头将在盘面上移动并进行校正,因此不能在这段时间内接收任何数据读写要求。如果磁头校正在可持续文件传输中间发生,则其造成的数据访问中断可能是难以接受的。一些制造商正在推出“多媒体”磁盘驱动器。这种磁盘系统在进行可持续数据访问时将暂停任何磁头校正操作。

例如,假定某一磁盘有8个盘面,每个盘面有1024个磁道,每一个磁道则有64个扇面。我们还假定磁盘的扇面尺寸为1KB。平均寻道时间为8ms,磁道到磁道的访问时间为1.5ms,磁盘驱动器旋转速度为7200rpm。对处于一个磁柱上的磁道进行访问时无须移动读写磁头。

  1. 请问该磁盘驱动器的容量为多少?

    答:容量=8×1024×64×1KB=512MB

  2. 请问该磁盘驱动器的平均访问时间为多少?

    因为:旋转延迟=旋转时间/2=60×1000/7200/2 ms=4.17ms

    所以:平均访问时间=寻道时间+旋转延迟=(8+4.17)ms=12.17ms

  3. 如果每个扇面的尺寸为512B,请估算该磁盘驱动器传送一个5MB尺寸的文件所需的时间。

    答:为方便计算,假定这个文件为连续存放,即用完一个磁柱后才用下一个磁柱,而在一个磁柱上,磁道一个一个地连续存放,而在磁道上,扇面一个一个地连续存放。假定其起始地址为扇面号#0,盘面号#0,和磁柱号#i。由于1MB=1024KB,而一个扇面为512B,一个5MB尺寸的文件将需要10 240个扇面。该文件占住的空间将从磁柱i,盘面0,扇面0开始,到磁柱i+19,盘面8,扇面63结束。我们还假定磁盘驱动器的缓冲区的尺寸为无限大(即系统其他部分能以磁盘数据传输速度相同的速度接收数据)。

    在上述假设下,磁盘驱动器需要8ms的时间进行寻道操作,即找到磁柱i需要8ms的时间,4.17ms时间用于等待0号扇面旋转到磁头下方,8×(60×1000/7200)=66. 67ms的时间来读取该磁柱上所有8个磁道的数据。然后,磁头将花费1.5ms的时间移动到下一个相邻的磁道(即磁道切换时间)。假定在访问每个新磁道时都有 一个旋转延迟,则我们有:

    访问时间≈[8+20×(4.17+66.67+1.5)-1.5]ms≈1453.3ms

  4. 该磁盘驱动器的爆发传输速度为多少?

    答:爆发传输速度=旋转次数/秒 * 扇面/一次旋转 * 字节数/扇面 = 7200/60s * 64 * 1KB = 7680KB/s

操作系统界面

对于磁盘来说,操作系统的一种功能是将磁盘驱动器系统里面的各种细节屏蔽起来,这样用户和应用程序都无须知道数据到底是如何存放在磁盘上的。操作系统在对磁盘进行管理时通常以磁盘块作为最小单位。而定位一个磁盘块则通过磁盘块地址进行。磁盘块通常为扇面数的整数倍,更为常见的是2的幂次方倍,如32个扇面或64个扇面等。

磁盘块地址类似于访问内存时需要提供的内存地址,操作系统访问磁盘块的时候需要提供磁盘块地址。不过磁盘块地址比内存地址更复杂,它包括多个部分,具体细节因操作系统的不同而不同。但典型的操作系统通常都会标明磁道(磁柱)、扇面、和盘面(磁头)3个参数。以这种方式给出的地址称为逻辑块地址(Logical Block Address, LBA)。

LBA用于将磁盘读写请求传送给磁盘控制器。在磁盘读取请求里,所要读取信息的起始地址由磁道号(磁柱号)、扇面号、和盘面号(或磁头号)组成,而扇面计数则给出了该次磁盘访问将要读取的扇面数量。磁盘控制器则将这些参数转换为一系列的低层磁盘操作。在对磁盘进行读取时,操作系统负责将该数值写入磁盘驱动器界面里指定的I/O端口。另外5个额外的、宽度均为一个字节的寄存器则用来驱动读写命令和状态信息。(这里之所以使用“逻辑”这个术语是因为LBA不一定与磁盘上面的块存储区域的真实物理地址对应;而磁盘控制器将负责进行从LBA到物理块地址的映射。)

下列步骤描述的是从磁盘上读取一个扇面的过程:

  1. 操作系统将要读取的LBA传送给磁盘驱动器并启动读取命令。
  2. 磁盘驱动器通过将磁头移动到正确的位置,并启动处于指定盘面上的磁头来搜索指定的磁道。在磁头的移动过程中,读取磁头将不断地检查下面的扇面号直到找到所要求的扇面为止。
  3. 磁盘控制器将扇面数据和ECC信息传送到一个处于磁盘界面里的缓冲区。这里的ECC信息是在数据流动的动态过程中进行计算而得出的。
  4. 磁盘驱动器向操作系统发出“数据就绪”信号。
  5. 操作系统从磁盘界面里的缓冲区读取数据,既可以按一个字节一个字节的方式来读取,又可以启动DMA命令来读取。

磁盘调度算法

影响磁盘读写时间的因素有3个:

  • 寻道时间
  • 旋转延迟
  • 数据传输时间

而在这3者中,前两者为机械运动,数据传输主要为电子运动。显然机械运动的速度远低于电子运行的速度。在两个机械运动部分,寻道时间又较长。因此,在上述3个因素中,寻道时间居于支配地位。为了提高磁盘的读写效率,需要降低磁盘的寻道时间,实现的手段则是磁盘调度。磁盘调度的算法主要有以下几种:

  • 先来先服务FCFS(First Come, First Serve):自然公平的策略效率十分低下
  • 短任务优先STF(Shortest Task First):读写的数据量对整个磁盘读写时间的影响并不大
  • 短寻道优先SSF(Shortest Seek First):考虑当前磁头离谁的数据最近
  • 电梯调度ES(Elevator Scheduling):先满足一个方向的所有请求,再满足所有反方向的请求,循环往复
  • 提前查看电梯调度ESLA(Elevator Scheduling with Look Ahead):如果一个方向的请求全部满足后,即反转运行。无需扫描到底
  • 单向电梯调度OWES(One Way Elevator Scheduling):只向一个方向扫描,当该方面没有剩余请求时,则回到0道继续循环。

讨论:固态盘

前面的讨论告诉我们,磁盘效率低下的瓶颈是寻道和旋转延迟。寻道和旋转操作之所以效率低下是因为这两项运动是机械运动:磁臂的直线来回运动和磁盘的旋转运动。目前,磁臂移动速度和磁盘旋转速度都已经达到或接近极限,再行提高将导致定位精度的降低。因此,提高磁盘的运行效率只剩下一个办法,那就是将机械运动从磁盘的结构中去除。

内存不就是没有机械运动的存储介质吗?如果我们用与内存相同或相似的存储介质来构建磁盘,不就去除机械运动了吗?而使用与内存介质相同或相仿的存储介质构建的磁盘就是所谓的固态盘(Solid State Disk, SSD)。“固态”的原始意义是半导体驱动器,但在今天的存储工业界已经演化为表示没有机械运动部件的驱动器。由于没有移动的部件,整个驱动器似乎是固定的,因而称为固态盘。目前的固态盘使用的是与内存相同的介质。由于内存有SRAM和DRAM的区别,因此固态盘自然也可以分成这两类。

由于没有移动的机械部件,固态盘具有很多普通硬盘不具备的优点:

  • 可靠性高(到底高多少还有待于进一步的压力试验)。
  • 没有噪音(由于没有风扇,因此不存在风扇发出的噪音)。
  • 访问速度高,接近内存的访问速度。
  • 热耗低于普通硬盘,更省电。
  • 由于不需要旋转,其启动时间短。

另外,由于没有机械运动部分,固态盘的空间利用率很高,至少是普通硬盘的两倍。另外,由于没有磁头磁臂之类的移动,所有存储单元的访问速度均衡,固态盘也无需任何调度。

当然,有利必有弊,固态盘虽然有着上述优点,但也存在一些缺点:

  • 其中最大的问题是成本。固态盘的单位容量成本为普通硬盘的10~20倍。这对于小容量的固态盘来说,可能算不得什么,但对于大容量的固态盘来说,这个成本十分可观。而人们制造固态盘的一个目的就是追求大容量,因此这个成本让很多人承受不了。
  • 其次是“非均衡磨损”问题。这是由对固态盘不同部分的使用频率不同而导致。这个问题可导致固态盘的生命周期缩短。为了克服非均衡磨损,固态盘通常采用所谓的“磨损平衡”(wear leveling)策略,即将修改后的数据存放在新的位置,而这将导致安全漏洞。
  • 另外,固态盘的读写速度差异大于普通硬盘的读写速度差异, 这对于那些要求读写速度相当的应用来说是一个大问题。当然这个问题可以通过降低读操作速度来达到。

讨论:智能磁盘系统

本章讨论的磁盘为非智能磁盘。这种磁盘本身提供的仅仅是存储容量,而这个存储容量的管理需要由磁盘之外的操作系统或者卷管理器来负责。除了非常细微或程度很轻的读写错误外,这种磁盘一般不能检测和恢复本身的错误。而一旦磁盘崩溃,整个系统就宣告完结。与此相对的另一种磁盘,即智能磁盘(smart disk)。这种磁盘具有一定的智能,除了提供存储容量外,还能够进行自我检测并从某些错误中恢复。当错误的频率和烈度达到一定的临界点时,这种磁盘能够向系统发出警报,以便系统管理员及时更换磁盘,避免由磁盘崩溃所造成的数据损失。当然,这种功能需要操作系统提供支持。

文件系统

为什么需要文件系统

使用磁盘存在一个巨大的问题:对于一般的用户来说,使用磁盘是非常困难的,他们不知道如何驱动一个磁盘,以及计算数据在磁盘上的存放位置。

操作系统为磁盘提供的抽象就是文件及文件系统。

文件系统

文件系统令磁盘变得容易使用,从而将用户从数据存放的细节中解放出来:用户不需要知道内容存放在什么地方,也不需要知道如何存放,更不需要知道磁盘到底是如何工作的。因此,文件系统就是磁盘的一个抽象。

简单地说,文件系统将其接触的磁盘物理特性转换为用户看到的路径名和文件名。用户对磁盘进行访问只需要给出文件名和路径名即可,而无须知道磁柱、磁道、扇面、数据块等信息。

文件系统的主要特性就是存储大量的信息,多个进程可以同时访问一个文件,进程结束也不会影响文件的持续存在。

文件系统的目标

文件系统也需要达到两个目的:地址独立和地址保护。

  • 地址独立就是一个文件在产生的时候无须担心其存放的磁盘地址,即文件数据的产生与文件将来存放的磁盘地址相互独立。
  • 地址保护则需要对文件的访问进行一定的限制,即不是任何人都可以访问任何文件的。

从用户角度看文件系统

文件命名

当用户给出一个文件名,操作系统就可以根据这个名字找到对应文件的内容。具体来说,就是找到该文件存放在磁盘上的所有数据块。操作系统如何在磁盘上找到所有这些数据块呢?很简单,将该文件名翻译成其数据块在磁盘上存放的地址。由于磁盘的寻址单位是扇面,而这些磁盘地址实际上就是一个个的扇面号。由于 一个文件可能占用多个扇面,因此文件在磁盘上的地址通常有多个。根据文件在磁盘上的组织方式不同,这种从名字到磁盘地址的翻译将有所不同。

命名规则则因文件系统的不同而不同。一个文件名通常有2~8个字符,但也可以长达255个符号。早期的操作系统通常将文件名长度限制在8个字符之内,并且只能使用字符集合的一部分作为文件名的构成。例如, 空白符在过去不能出现在文件名内。现代的操作系统则允许文件名长度达到255个字符,且允许使用的字符集合也扩大了许多。一个文件名到底可以使用何种字符因操作系统不同而不同。读者可参阅具体操作系统的使用手册。

扩展名

扩展名通常用来表明文件的类型。

这里需要注意的是,扩展名对文件类别的指示仅仅是指示性的,并不具有强制性。但有些系统对扩展名进行了强制服从,即扩展名必须和文件类型相同,否则无法使用。

内容寻址

那么除了给出文件名之外,还可以给出文件的内容,供操作系统查找使用。什么样的系统是以这种方式实现的呢?数据库。当然,现在也有专门以内容寻址为访问方式的存储系统。在这种寻址模式下,用户无须提供文件名,而是提供文件里的某些内容来进行访问。

内容寻址在某些时候非常方便,比如你想寻找电脑上的某张照片。由于照片太多,况且名字又不具有可记性(数码相机照出来的照片都使用一种毫无意义的命名规则,很难记住),你只能一张一张查看。但如果有内容寻址的存储器,则寻址就可以方便很多。

文件内容组织

文件里面存放的是数据,而数据和数据之间有可能存在某种关系。这种关系是否由文件系统捕捉就决定了文件内容的组织形式。从哲学的高度看,文件内容的组织有两种:关系导向型组织和非关系导向型组织。

  1. 关系导向型组织

    关系型导向型文件的内容的组织形式就是将数据之间的关系记录在文件里面,文件的构造与数据之间的关系存在某种对应关系。例如,记录流存放方式下,数据以一个记录一个记录的方式顺序排列,并不捕捉每个记录之间的关系;在树型存放方式下,数据不只是按记录存放,且捕捉记录之间的关系,但记录之间的关系是 一种层次结构,不形成环;在网络存放方式下,记录之间的关系可以形成环。

  2. 非关系导向型组织

    非关系导向型文件内容的组织形式不考虑数据里面的任何结构。对于此种方式来说,数据就是一个数据流, 没有记录,没有关系。这个数据流既可以是字位流,也可以是字节流,也可以是数据块流。它们之间的不同就是这个“流”的单位不同。

  • 在记录方式下,一个记录就是数据的最小构成单位。对文件的访问按记录为单位进行,也就说每次读取要读取一个或多个记录,不能在记录中间开始读写操作。因此,以这种方式构建的文件系统就必须维护或者知道很多信息

  • 在树型或网络型存放方式下,文件系统要知道的信息就更多了,除了记录本身的信息外,还需要知道记录之间的关系,从而造成文件系统更加复杂。

  • 相对关系导向型组织方式,非关系导向型方式就简单多了。在这类模式下,文件系统无须知道关于数据的任何细节。对于这种方式来说,数据就是0101这种数据流。至于这个0101代表的是什么东西,文件不必知道, 也不想知道。在这种方式下,文件系统将大大简化。

    在关系导向型组织方式下,文件系统需要知道数据的很多细节,这将使得文件系统复杂。这种复杂性带来了两个巨大的负面效应:一是文件系统本身的设计将更为费时、费力;二是一个文件不能在另一个文件系统下被访问,因为另一个文件系统的记录格式可能不同。这样势必造成文件可靠性和兼容性存在问题。

    更关键的是,这些细节不是文件系统的责任范围,而是应用软件或用户的职责范围。因为这些细节直接涉及文件里面数据的语义。前面说过,文件系统的目的是让磁盘更加容易使用,而不是为用户管理数据的语义, 即这种管理语义的能力超出了文件系统设计的初衷。况且,由文件系统掌握语义也不安全。在这种情况下, 用户的数据无法得到保障,因为任何人可以打开任何文件。

    因此,从可靠性、安全性和兼容性来说,关系导向型组织方式都不合适。

    对于非关系导向型文件内容组织来说,由于操作系统辨认的是数据块、字节或字位,数据的语义并没有包含在组织方式里,任何操作系统都可以读写所有的文件。因为所有操作系统都知道一个字节或字位是多少。用户要读多少个字节,操作系统就读多少个字节。这就解决了兼容性的问题。至于读出的一串010101010到底是什么意思,操作系统就不用管了,也不应该管。因为对数据进行翻译是应用程序的事情。这样,应用程序可以根据需要定义和翻译数据的意义,还可以进行编码和加密,安全性也大为提高。而且,操作系统不管语义,其所要维护的信息就很少,设计起来简单,可靠性自然也就更高。

    现代的商业操作系统基本上都使用字节流方式来组织文件内容。

    因为通用计算机的寻址最小单位是字节,而不是字位。对于计算机来说,读取一个字位比读取一个字节困难得多。因此,从计算机的角度来看,字节是最为简单的了。

    总体来说,字节流的文件系统的好处是更加方便、操作系统的代码更加可靠、更加的灵活、用户程序编写也更加方便。例如,用户程序可以对数据进行编码。如果用户不想让别人读懂文件内容,就可以对文件进行加密,而加密和编码丝毫不会影响操作系统对文件的读写。这些优势皆是关系导向型组织方式所缺乏的。

文件类型

根据文件的内容是用户数据还是文件系统本身的数据,文件分为3种:

  • 目录

    目录是记录文件的文件,即它的内容是关于别的文件。

  • 一般文件

    一般文件用于保存数据。对于一般文件来说,根据其内容的组织方式不同,可以分为两种文件:文本文件和二进制文件。

    • 文本文件存放的是没有经过处理的数据,即以ASCII码表示的数据。任何编辑器都可以打开这种文件。
    • 二进制文件是经过编码的文件,普通编辑器打不开,必须用专门的应用软件才能打开。另外,加密文件也是二进制文件。
  • 块文件

    块文件既不是关于别的文件,也不是关于用户数据,而是关于输入输出设备的。具体来说,块文件模拟的是输入输出,或者说是为输入输出提供的一个抽象。对于每个输入输出设备,我们以一个文件来表示。需要与该设备发生数据交换时就以该文件来替代。

文件格式

不同类型的文件有着不同的逻辑单元,而这些逻辑单元在空间上的关系也因文件类型的不同和操作系统(文件系统)的不同而不同。

对于可执行文件来说,它有一个标题(header)、一个代码部分、一个数据部分、一个重定位部分和一个符号表。而标题部分又分为多个逻辑单元:魔数、文本尺寸、数据尺寸、BSS尺寸、符号表尺寸、程序入点和各种标志位。这里各部分的信息除魔数外均不言自明。

魔数是用来告诉操作系统这个文件的类型的。对于可执行文件来说,这个魔数有一个固定的值。如果不是这个值,则该文件不能被执行。魔数是一个很重要的数字,如果把一个不是可执行的文件的魔数改成可执行文件的魔数,将会产生不可预料的后果。

当然,不同的文件类型也可能共享同一种文件格式。例如,Windows里面的PE格式就是对象文件(obj文件)、可执行文件、动态链接库、驱动程序等共用的一种格式。

文件访问

文件的访问类型只有两种:顺序访问和随机访问。

顺序访问就是从开头开始访问,按先来后到的顺序读取数据。不能在中间随便跳转,但可以进行快进或者快退。早期的操作系统只提供顺序访问。这是因为早期的计算机主要以纸带、磁带等作为存储媒介,而这种媒介比较适合顺序访问。对于需要随便跳转的随机访问,纸带和磁带都不太胜任。显然,顺序访问的缺点很多,你不能想访问什么地方就访问什么地方。就像看录像带一样,倒来倒去很不方便。

随机访问则可以按照任意顺序读取数据记录。我们先对文件进行选点(seek),到达指定的位置后再开始读写。读写一段数据后又可以再次选择新的位置。这种随机读写是数据库能够工作的基础。

现代操作系统无一例外地都提供随机访问,这是因为我们的存储媒介已经是可以随机读写的磁盘了。

文件属性

我们知道每个文件都有一个文件名,而文件里面存放的是数据。但除此之外,文件还有许多信息是我们感兴趣的。例如,文件是谁创立的,谁拥有这个文件,是不是可以随机访问,是不是随机文件,是否上锁,有何保护措施等。这些信息称为文件的属性。对于不同的操作系统来说,文件的属性种类和数量可以不同。但每一种属性都有自己的用途。表16-1列出的是常见的文件属性。

表16-1中列举的属性大部分都直接明了,部分需要解释,如下所示:

  • 临时文件标志用来指示该文件为临时创建的,用来存放中间结果,在创建该文件的进程结束后,该文件将
    被删除。
  • 锁标志用来指示该文件是否已上锁。为1时表明该文件当前被某个进程锁定,其他进程将不能打开该文
    件。利用对文件上锁,两个进程可以进行同步。
  • 记录长度、关键字位置、关键字长度几个属性针对的是记录流文件。对于字节流文件组织方式来说,这些
    属性是没有的。
  • 最后修改时间、最后访问时间等属性经常由备份软件用来判断是否是差分增量。

文件的属性可以被修改。修改的方式有两种:一是直接在shell下用工具程序(utilities)进行修改;二是在程序中使用系统调用(system call)进行修改。

文件操作

可以对文件进行的操作有:创建/删除、打开/关闭、读写添加、寻找访问位置、读取属性/设置属性、重命名等。

地址独立的实现机制:文件夹

需要一个数据结构来记录每个文件在磁盘上的地址,这个数据结构就是文件夹。

文件夹也称为目录夹(folder),它保存的不是用户数据,而是关于文件及文件系统的信息。简单地说,文件夹的角色就是跟踪文件,里面存放的是从文件到文件在磁盘的地址的映射,即“文件名→文件在磁盘上的地址”。文件夹对于文件来说,就相当于动态地址翻译对于虚拟地址的作用,即从虚拟地址到实际地址的一 种翻译机制。

给予一个文件名时,我们从文件夹里可以找到该文件所在的位置,从而发出正确的读取命令。那么文件在磁盘上的地址是什么呢?这就要看文件在磁盘上是如何存放了。如果文件在磁盘上是连续存放的,即 一个文件占用一片连续的磁盘空间,则文件在磁盘上的地址就是该文件的第一块数据块所在的磁盘扇面号。

既然文件夹存放的是文件名到磁盘地址的映射,那么自然地我们可以以一个简单数组来实现。这个数组是以文本文件存放的,你可以自己写一个程序打开这个文件就可以看到里面的内容。而读取文件夹的内容就变成读取这个简单数组的内容。而这正是shell utility"ls"(UNIX、Linux、Windows Power Shell)和"dir"(Windows)所做的事情。

文件夹本身也是文件。使用同样的存储结构来存放文件夹的数据;文件夹里面的每个记录本身又可以是文件夹。

文件夹结构

根目录是一个文件系统的总起点,它在操作系统启动的时候加载到内存。从根目录开始,该文件系统里面的所有文件都可以找出来。由于根目录是整个文件系统的源点,如果根目录损坏,则整个文件系统都无法访问,也就说文件系统已经崩溃。

相对路径与绝对路径

绝对路径是从根目录的视角看一个文件的访问路径。与此相对的是相对路径,即从某个工作目录,如用户当前所处的目录的角度看某一个文件的访问路径。

使用相对路径的好处是没有必要寻遍整个目录夹,可以节省磁盘访问次数,从而提高文件访问效率。

但需要注意的是,相对路径不一定总是比绝对路径节省磁盘访问次数。如果你要访问的文件是当前这个目录的“直系后代”,则将节省磁盘访问次数;否则,即如果不是“直系后代”,你就可能多花费时间。

共享与链接

访问一个文件也可以选择多条路径。实现多条路径依靠的是链接。

链接的建立使用操作系统的Link系统调用,该调用给出一个目录名和一 个文件名,在操作后,从该目录到该文件将存在一个“直系下属”关系,我们从该目录可以直接访问该文件。 也就说,打开该目录我们可以看到该文件。

链接使得文件的共享相当方便。尽管我们可以通过文件拷贝来实现共享,但那种共享不是即时共享,即一个人修改文件内容后,需要再次拷贝才能被另外一个人看见。而使用链接就无须这种循环往复的拷贝。修改的内容另一个人可以直接看到。

切断链接可以使用操作系统的unlink系统调用。

文件系统调用

从用户角度来看,操作系统提供的文件系统界面就是文件系统调用。用户可以在程序中通过这些系统调用对文件系统进行读写操作。这些系统调用包括打开文件、关闭文件、读文件内容、写文件内容等。

每次系统调用都需要进行磁盘操作, 而磁盘操作的效率远远低于内存操作的效率。解决这个问题的办法是可以将缓冲区变大,从而降低系统调用和磁盘访问的次数。但缓冲区越大,需要的内存就越大,将造成程序的可执行性下降。而且,就算缓冲区很大,文件还可以更大,还是不能从根本上解决效率低的问题。

内存映射的文件访问

解决读写文件效率低的中心思想就是把磁盘访问变成内存访问。实现这种访问转变的手段就是内存映射的文件访问。

内存映射的文件访问原理很简单:把需要访问的文件映射到一个进程的虚拟地址内。这样,访问该虚拟地址就相当于访问文件。这样文件访问就变成内存访问。

这样做的好处就是磁盘访问变成了内存访问,效率自然提高。

不过需要提醒的是,内存映射的文件访问不是为了文件拷贝而提出的,而是为了文件的共享。具体来说,我们可以将同一个文件映射到两个或多个进程的虚地址空间。这样每个进程对相应的虚地址空间进行访问时就是对同一个文件进行访问。

文件系统实现

分区的理由多种多样:

  • 首先,分区可以方便我们使用磁盘,因为不同的分区可以建立不同的文件系统。
  • 其次,分区有安全性上的优势,因为一个分区毁坏了,另一个分区仍然可以使用。
  • 最后,分区还有可靠性上的优势,因为一个分区的故障不影响另一个分区的运行。

这些分区理由虽然是好的理由,但都不是必须分区的理由。

必须分区的理由是对磁盘空间的使用。因为计算机的内存字长度通常有限,而磁盘地址需要存放在内存字里面。这样,操作系统能够访问的磁盘地址数量就是一个有限数。这个有限数将限制操作系统能够访问的磁盘空间大小。如果一个磁盘容量超过这个上限,则多余的空间将无法访问。

当然,我们可以加大磁盘数据块的尺寸使得能够访问的磁盘空间增大。但这样带来的坏处是磁盘空间的浪费,因为很多文件占不到一个磁盘块。再说,即使这样,也只不过增大能访问的磁盘空间,并不能从根本上解决问题。但如果使用分区,则可以将磁盘分解为大小为32MB的多个分区,这样整个磁盘就都可以被访问到。

那么为什么一个分区只能建立一个文件系统呢?

文件系统的布局

第0个扇面在整个文件系统中占有重要意义。该扇面存放的是主引导记录(Master Boot Record, MBR)。该记录的内容是一个小小的程序,用来启动计算机。如果该扇面损坏,则整个磁盘就无法使用。这时唯一的办法就是将磁盘拿到硬件制造商那里对磁盘扇面重新编号。

在MBR后面紧接着的是磁盘分区表。磁盘分区表里给出的是磁盘的所有分区及其开始和终结地址,其中一 个分区为主分区。操作系统就装载在这个分区中。主分区里面最前面的是引导记录(Boot Record)。

在计算机启动时,处于主板ROM里面的BIOS程序首先运行。BIOS在进行一些基本的系统配置扫描后对磁盘的扇面0进行读操作,将MBR里面的程序读到内存并运行。MBR程序接下来找到系统主分区,并将主分区里面的Boot Record加载并运行。Boot Record里面的内容是一个小程序,该程序将负责找到操作系统映像,并加载到内存,从而启动操作系统。所有的文件系统都必须按照这种格式存放,操作系统才能正常启动。

在Boot Record记录块后面的内容就因情况而异了。一般来说,在该记录块后面的磁盘内容布局因文件系统的不同可以不同。但在通常情况下,紧接着引导记录后面的是一个超级数据块(Super Block),该块里面存放的是关于该文件系统的各种参数,如文件系统类型、文件系统数据块尺寸等。在Super Block后面则是磁盘的自由空间,其后面是I-NODE区,再后面是文件系统根目录区。分区的最后存放的是用户文件和文件夹区。

上述文件系统布局仅仅是一个例子。不同的文件系统布局是不一样的。

文件的实现

文件的实现,归根结底,就是要能够把文件的内容存放在合适的地方,并能够在需要时很容易地读出这些数据。这样,文件的实现要解决的就是如下几个问题:

  • 给文件分配磁盘空间。
  • 记住这些磁盘空间的位置。
  • 将文件内容存放在这些空间。

数据在磁盘上的存放方式,就像程序在内存中存放的方式那样,有下面两种:

  • 连续空间存放方式
  • 非连续空间存放方式
    • 链表方式
    • 索引方式

连续空间存放方式

在一个文件需要磁盘空间时,为其分配一片连续的磁盘空间,这样一个文件的数据紧密相连,读写起来非常方便。在这种模式下,一个文件占住一片连续的数据块,这种连续的块通常叫做盘区(extent)。连续存放方式的读写效率很高,因为一次磁盘寻道就可以读出整个文件。

使用连续存放的方式必须知道一个文件所需要的空间大小。所以使用这种方式必须事先声明所需的最大空间是多少。

文件的连续存放的优点就是简单:从文件名到文件地址的映射变为从文件名到文件第一个数据块的地址映射。即只要给出I-NODE地址和文件大小,就可以寻找到所有的文件数据块。这个和内存管理中的基址和极限很相似。另外一个优点是读写效率高。

文件连续存放的缺点自然也与内存的连续分配的缺点一样:空间浪费(磁盘碎片)和不易扩展。

非连续空间存放方式

使用链表的解决办法很简单。在每个数据块里面留出一个指针的空间,用来存放下一个数据块所在的地址。 这样一个数据块连着一个数据块,从最前面的数据块开始就可以顺着指针找到所有的数据块。在链表存放方式下,文件的映射就是给出文件第一个磁盘快的位置。

这种存放方式比起连续存放方式的优点是可以利用碎片。

但缺点呢?

  • 访问速度慢。尤其是随机访问,访问任何一个数据块均需要从头数据块开始一 个指针一个指针地找下去。如果有任何一个指针损坏,则将无法重构整个文件
  • 增加了存储开销,因为指针要占空间。
  • 计算机里面的尺寸都与2的指数次方有关系,因为计算机里2的整数次方比较容易处理(方便读写)。如果使用磁盘块里面的一部分空间来存放指针,那一个数据块里面存放的数据就很有可能不是2的指数次方了。这将造成数据处理的效率下降。

那么我们有什么办法克服上述问题呢?把所有指针从单个数据块抽取出来,全部放在一起,形成一张表不就解决问题了吗?这样,要想知道一个数据块的位置,只需要查找该表即可。而且,该表可以存放在内存里面。这样既解决了数据块里面数据不是2的指数次方的问题,又解决了随机访问速度很慢的问题。这张存放文件数据块指针的表称为文件分配表(File Allocation Table, FAT)。

FAT文件系统

使用FAT机制的文件系统称为FAT文件系统。而FAT文件系统又有FAT12、FAT16、FAT32 3种。这3种的区别在于用来表示磁盘地址的内存字位数。不过FAT32却并不是使用32位内存字位来表示磁盘地址,而是28位。但因为28听上去不像32那样像2的指数次方,所以把它叫做FAT32。

那么FAT是否占用大量的内存空间呢?这得看磁盘有多大了。因为FAT表的大小与物理磁盘大小和磁盘数据块大小有关。由于FAT的记录项与物理磁盘块数一样,磁盘越大,磁盘数据块越小,FAT越大。当然,我们可以采取分页系统里采取的办法,例如通过只将FAT的一部分存放在物理内存,其他存放在磁盘上来减轻这个问题。但这种解决方案的代价则有可能造成文件访问效率的降低。

那还有什么办法改进呢?当然有。答案就是索引文件组织。

索引文件组织

问题就是FAT太大。但仔细分析却发现,FAT虽然很大,但里面存放有用信息的记录不一定很多。例如,如果系统里面文件数量较少,或者个体文件的尺寸很小,则FAT里面的很多记录都是空的。这样将整个FAT放在内存里就显得有点不必要了。

如果能够将每个文件的所有数据块的磁盘地址收集起来,集中放在一个索引数据块里,而在文件打开时将该数据块加载到内存,以后访问任何一个数据块都可以从该索引数据块里面获得物理磁盘地址。 这样,内存里面存放的只是我们需要使用的文件的数据块的所有地址,而不是各种不相干的文件的地址全部 一次加载到内存。这样,FAT占用内存太多的问题就解决了。

这种索引数据块称为I-NODE。这里的I即是索引(Index)一词的缩写。当然,也有人认为I不是代表索引, 而是代表信息(Information)。因为I-NODE里面存放不仅仅是文件地址索引,还有其他关于文件的信息, 如文件属性等。

使用索引组织方式时,用户先宣称文件大概有多大,文件系统则按照用户的声明分配一个含有相应数量指针空间的I-NODE,但是先不分配真正的磁盘空间。在真正需要分配数据磁盘空间时,每分配一个磁盘数据块,更新I-NODE里面的相应指针。

如果要访问一个文件,则首先将其对应的I-NODE打开。根据I-NODE的内容,我们就可以找到任何要访问的数据块。

这个I-NODE非常类似于内存管理时的页表。页表存放的是虚拟页面到物理页面的对应,而I-NODE存放的是逻辑数据块到物理数据块的对应。

索引文件由于也属于非连续组织方式,自然继承了链表和FAT方式的优点。而且,由于I-NODE按照个体文件而加载、打开、关闭,避免了将所有文件的地址同时加载到内存而造成内存空间紧张的困难。另外,随机访问很方便,因为只要从I-NODE获得所需数据块的物理磁盘地址,一次磁盘访问就可以搞定。索引组织的另一个优点是文件增长灵活。只要文件的尺寸不超过索引头里面预留的指针数×磁盘数据块大小,则文件可以自由地增长。只要在磁盘上找到一个自由磁盘块,分配给文件,再更新I-NODE以反映这个新的映射即可。

索引组织的缺点也与FAT方式类似:比较麻烦。因为每个数据块都需要选点,相当于一系列随机访问,即顺序访问效率不太高。

另外,虽然文件增长灵活,但如果一次文件的大小增长超过了预期,其需要的数据块数超过索引头预留的指针数时怎么办?我们当然可以重新分配一个更大的I-NODE,将原来I-NODE里面的数据拷贝到新的I-NODE,然后再分配新的磁盘空间,更新I-NODE。

不过这种做法有两个问题:一是需要进行数据的转移(从一个I-NODE移到另一个I-NODE),浪费时间;二是使得I-NODE大小可以任意变化,而这将令文件系统的管理变得复杂。

17.2.5 多级索引组织

解决的办法有:多级索引组织。不过,这种多级索引组织有一个缺点,就是访问数据块所需要的磁盘访问次数增加了。因为访问次级I-
NODE需要一次磁盘访问,因此磁盘访问速度很低。这种次数的增加显然不好。另外,多级索引还有一个缺点,就是对于小文件来说,它们使用的磁盘数据块可能不会超过一个I-NODE里面的指针数。但由于使用多级索引,就浪费了I-NODE空间。

17.2.6 非对称多级索引

在非对称多级索引组织下,索引既可以是单级,也可以是多级。到底是单级还是多级取决于文件的大小。所有的文件都有一个顶级索引节点(I-NODE)。不过这里比较特别的是该NODE里面的指针域被分为两个部分:一部分用来存放数据块所在的磁盘地址;另一部分用来存放次级I-NODE的地址。这样,如果文件尺寸较小,其所占数据块数没有超过I-NODE里面分配的数据块指针数,则其数据块磁盘地址全部存放在顶级I-NODE上。这样的文件将没有次级索引。如果文件较大,则除了顶级数据块磁盘地址外,还将有次级I-NODE。

I-NODE里面指向数据块的指针称为直接指针,指向间接磁盘块的指针称为间接指针。

非对称多级索引对小文件来说速度快,空间省,对大文件来说可以容纳。但是它对大文件的访问速度提高了吗?从表面上看,没有。因为大文件需要使用多级I-NODE,进而需要多次磁盘访问。这和普通多级索引没有什么区别。

由于使用非对称多级索引,大文件的前面数个数据块的磁盘地址存放在顶级I-NODE里,后面的数据块的磁盘地址存放在次级I-NODE里。这样,访问前面的数据块显然比访问后面的数据块要快,其速度和单级索引一样。如果我们对这个特点加以利用,先读出前面的数个数据块并呈献给用户,在用户忙于处理前面的数据块时,再在后台读出后面的数据块。这样用户看完或处理完前面几个数据块,需要看到后面时,后面的也读出来了。这样用户的感觉是速度很快(像单级索引那样快)。因此,从用户体验看,非对称索引也提高了大文件的访问速度。

那么我们如何在顶级和次级之间分配指针域呢?即多少指针域用来存放直接磁盘地址,多少用来存放间接磁盘地址呢?这就要对文件系统的状况和人的心理进行研究,一个是用户的文件一般多大;另一 个是用户的忍耐时间是多久。如果大多数文件都小,那就要多留一点直接块,让用户能够一次读出来。如果文件都大,那就多留间接空间,但却不能将直接空间挤压过小,使得直接访问的数据量小于用户正常阅读速度×访问次级数据块需要的时间,从而造成用户体验下降。

如果一级间接还不能容纳系统里的大文件,那就可以使用两级间接、三级间接、四级间接……当然,随着间接层次的增加,文件的访问速度将随之下降,最后可能达到无法容忍的地步。

而一旦间接层数设定,则一个文件系统里能够存放的文件最大尺寸也就确定了。

文件缓存

单级也好,多级也好,文件的读取相对于内存访问来说就是慢。对于慢我们有什么解决办法吗?答案就是使用缓存。

将文件里面经常要访问的内容存放在缓存里,使得文件的访问可以在缓存满足,而无须到磁盘上去读写。这种缓冲称为文件缓存。

目录实现:地址独立的实现

文件夹的任务是提供从文件名到文件地址的映射。那么对于不同的文件组织形式,文件的地址表示也不一样。

如果文件是连续存放的,我们只需要文件的第一个数据块的磁盘地址即可,后面的数据块紧接在该数据块后面。这种情况下文件夹里面存放的映射是到I-NODE数据块地址。

如果是链表组织形式,我们只需要文件的第一个数据块磁盘地址即可,后面的数据块可以通过前面数据块里面的指针获得。这种情况下文件夹里面存放的映射也是到I-NODE数据块地址。如果是FAT组织形式,映射仍然保持不变。我们可以从FAT里面找到后继数据块所在的物理磁盘地址。

如果使用的是I-NODE组织形式,我们只需要知道文件对应的顶级I-NODE地址即可。文件的数据块地址可以从I-NODE里面获得。这种情况下文件夹里面存放的映射是到I-NODE编号。

17.3.1 文件属性的存放

文件属性应该保持在什么地方呢?这也是根据文件组织方式的不同而异。如果是连续或链表组织形式, 只有文件夹里面可以存放文件属性。但如果是I-NODE组织形式,则属性既可以存放在文件夹里,也可以存放在I-NODE里。

17.3.2 长文件名的存放

为了便于读取文件,文件夹里面的记录长度通常是固定的。那么对于文件名很长的情况,即文件名长度超过记录能容纳的长度时,我们怎样存放文件名呢?

当然,最简单的办法是不允许长文件名。但这个办法很可能会激怒用户。

另外一个办法则是在目录夹里给长文件名的文件分配多个记录空间,用以存放长文件名。但这样就将造成每个文件占用目录夹记录数不一样的情况。而这将使得文件的读写操作复杂性增加。在这种存放模式下,每个文件夹记录的前面一个字应该存放该文件记录的长度,这样就可以告诉操作系统下一个文件记录开始的位置在哪里。但这种方式要求在查找文件时顺序扫描文件夹,并动态计算下一个文件开始位置,因此,效率十分低。

除此之外,还有一个办法是保持文件记录的长度不变,将文件名存放在另一个分开的地方,如系统堆空间(磁盘上专门划分出来的一片空间)。这样,每一个文件记录里面保存一个指针指向堆里存放该文件名的地址。在此种模式下,由于文件记录长度不变,在扫描文件夹时无须动态记录下一个文件开始位置,效率将提高。

17.3.3 文件共享

在使用符号链接后,一个文件可以从多个路径进行访问。即一个文件有多个路径名,从多个路径都可以找到该文件在磁盘上存放的地址,也就是说,该文件的映射情况保存在多个文件夹里。具体来说,就是当用户将 一个文件链接到一个文件夹时,该文件夹里面会增加一条记录,用来保存该文件到文件地址的映射。而为了保持系统一致性,我们此时在文件的I-NODE里面记录其被链接的次数。

有了链接后,文件删除的操作需要进行修改。删除文件时不是马上将文件删除(不管是逻辑上还是物理上),而是将该文件对应I-NODE里面的链接计数减1。如果结果为0,就删除该文件(逻辑删除);如果不为0,则不删除该文件。这是因为还有目录需要使用该文件。如果这个时候删除了,将出现所谓魅影,即一 个文件在目录夹里面显示,但却不存在。

17.3.4 硬链接

上面讲到的链接方式将文件的地址映射直接加到链接目录下,这种链接也称为硬链接。这是因为另外一条路径断开后(如删除文件或者路径上的文件夹),其他的链接不会受到影响。

硬链接有一个大问题。这种链接使得文件的创建者没有权利断开已经链接用户的链接。

17.3.5 软链接

软链接,在创建者将文件删除后,链接用户将无法再访问该文件。

就是在链接目录里存放到链接文件的原始路径名(创建文件时使用的路径名)。即从链接目录访问文件需要走原始路径过来。这样当原始路径断开时,该链接就自然也断开了。这种在原始路径断开后不能维持的链接就称为软链接。

软链接从严格意义上说不是链接,因为它并没有直接连到文件上,而是保存了文件的原始访问路径而已。正因为此,Windows的快捷方式不是链接。因为快捷方式保存的就是路径名。那为什么不用硬链接呢?这是因为Windows上经常使用FAT文件系统,没有地方存放文件的链接计数,硬链接无法实现。当然,如果是NTFS,则可以使用链接。但为了保险起见,还是不用为妙。

17.3.6 切断链接

切断链接就是将目录夹里面相应的文件记录删除。对于软链接来说,就是将存放原始访问路径的文件删除。 链接切断后,在目录里面将看不到原来被链接的文件

17.3.7 链接带来的问题

链接虽然方便共享,但也存在诸多问题。最主要的问题是造成备份困难。在进行逻辑备份时,如果选择备份的文件夹里面存在符号链接,则从逻辑上来看,该文件存在多个路径。这样将造成该文件备份多次。但是备份多次并不是什么大问题,只不过浪费空间而已。

问题出在恢复的时候。由于被链接的文件备份了多个拷贝,在恢复的时候自然也会被恢复多个拷贝,这样将造成每个路径有自己的文件拷贝。这样就达不到共享的目的了。当然,我们可以对备份软件进行修改,使其能够辨认出符号链接,而只备份和恢复一个拷贝。但此种修改复杂性较大。

17.3.8 文件系统挂载

了解了文件的实现后,我们就可以理解文件系统是如何实现挂载的了。挂载是用来将一个文件系统并入到另 一个文件系统的方法。挂载时需要提供被挂载的文件系统的根目录和挂载点。而挂载实际上就是修改挂载点的目录内容,增加一个记录将该文件系统的根目录I-NODE地址保存起来。

比如说,我们平时用的光盘、U盘、软盘等上面都存在一个文件系统,但是我们在平时是无法访问该文件系统的。要想访问,需要将它们插入计算机的相应设备或接口上。而这种插入就是挂载操作,使得光盘、U盘、软盘上的文件系统成为整个计算机文件系统的一部分,从而可以对其进行访问。

17.3.9 卸载

挂上去的文件也可以卸下来,称为卸载。卸载即是将挂载点的目录内容中的相关记录删除。对于光盘来说, 弹出光盘就是卸载光盘文件系统。对于U盘来说,拔出就是卸载。(不过需要指出的是,卸载却不一定需要拔出。)

闲置空间管理

文件系统的主要任务是为用户提供一个方便、持久和可靠的存储媒介,并根据用户需要随时对文件的数据进行访问。为了能够让用户程序进行磁盘空间分配,文件系统必须保持磁盘空间使用情况的记录,即哪些空间被占用,哪些空间为闲置。而对闲置空间进行管理的机制与内存空间管理一样,也是位图和链表。

17.4.1 磁盘分配块

文件系统分配空间时是按照块来分配的,Windows操作系统里面将这种分配块称为簇。块或者簇的尺寸记录在超级数据块中。有了这个参数,我们就可以把字节数转换成块或簇数。

这里存在一个问题:分配块应该为多大?如果分配块尺寸太大了,浪费的空间多;如果太小了,数据传输速度慢(参见第15章)。显然,一个恰当的尺寸是某种折中的产物。而折中的参数就是一个系统里面文件的平均大小。这个分配块大小应当与文件的平均尺寸存在某种联系。图给出的是在文件平均大小为2KB的情况下,磁盘分配块大小与磁盘利用率和有效磁盘数据传输之间的关系。

17.4.2 磁盘配额

在多用户环境下,文件系统通常给每个用户一个磁盘配额。这个配额既可以是以磁盘容量来表示,也可以用文件数量来标定。当然,更为常见的是两者同时使用。即一个用户只能创立一定的文件数,且其创立的所有文件加起来不能超过某个容量限度。

对容量配额的实现,不同的操作系统使用的方法不一定相同。在UNIX环境下,通常存在所谓的软配额和硬配额。软配额就是你可以临时超过,但必须在下次登录前解决这个问题,否则超出配额的文件将被删除。

文件系统性能

文件授权管理

地址保护不是文件系统必须实现的功能。如果没有这个功能,用户可以承担起文件保护的角色。但是,这种做法比较麻烦。另外,在一些大型主机上也不能实现。因此,由操作系统提供文件保护经常是更为可行的办法,有时甚至是唯一的办法。

我们对文件的保护也可以从两个角度出发:从文件的角度和从用户的角度。从文件的角度实施将访问控制附在每个个体文件上。从用户角度出发则将访问控制附在用户身上。

主动控制:访问控制表

主动控制将保护措施构建在被保护者身上。对于文件系统来说,我们为每个文件设置一个哨兵。这个哨兵对每个试图访问该文件的用户进行检查,看看其能否访问。而这个检查的规则存放在一张表里面。这张表称为访问控制表。这张表存放在内核空间,对于每个具有访问权限的用户设置一个记录。该记录记载着用户ID
和具体的访问权限,如读、写、执行、拷贝等。凡是不在这张表上的用户将不具备访问资格。

这里需要注意的是,访问控制表是存放在内核空间,由操作系统进行设置与访问。用户不能直接修改访问控制表。

当然,访问控制表里的用户不一定非是个体用户,也可以是集体用户。

而且,这里的文件也不一定非是个体文件名,也可以是文件集合,如文件夹名。

访问控制表的优点是容易理解和实现,对个体用户的权限赋予与取消也容易。只需要将该用户从访问控制表里面删除即可终止该用户对文件的访问,而缺点是效率低。每次访问一个文件时,我们需要搜索访问控制表。对于那些不支持用户组的系统来说,将需要给每一位个体用户进行权限赋予,而这将是十分繁琐的。

另外,访问控制表并不是十分可靠。它可以很容易攻破。攻破的办法也很简单:攻击者只要让系统认为他是某个拥有访问权限的用户即可。

能力表

另外一种访问控制手段是将控制措施附在用户身上。在这种情况下,每个用户手上拿着一把钥匙。这把钥匙就是用户具有的能力。其基本思想是:如果一个计算机程序需要访问一个对象,该程序必须具备一个特殊的令牌。这个令牌记录该程序可以进行的操作,而该令牌称为能力。

由于一个用户可以拥有对多个对象的访问能力,这些能力放在一起就形成一张表,称为能力表。这张表为所有该用户具有访问权限的文件设置一个记录。

与访问控制表一样,能力表也保存在内核空间,由操作系统进行设置与访问。用户不能直接修改能力表。

当然,能力表里的文件名不一定非是个体文件名,也可以是文件集合。

而且,这里的用户也不一定必须是个体用户ID,也可以是用户集合,即用户组。

首先,能力表的优点是效率高。如果用户要访问的文件是能力表所指向的文件,则无须进行任何检查。其次,能力表有较好的封装性。用户和他所能够访问的文件存放在同一个列表中。

而缺点则是删除文件对象困难。如果要禁止所有用户对某个文件的访问,则需要对所有用户的能力表进行遍历。

不过,与访问控制表一样,能力表也可以被黑客攻破。比如说,黑客可以伪造一个能力表。而为了保护能力表,我们可以将其置于操作系统的管辖内。显然,这种实现需要硬件的支持。

我们也可以对能力表进行加密。这样可以将能力表置于用户空间,但用户需要解密钥匙才能使用能力表,从而防止篡改。

访问控制的实施

如果访问控制表或能力表非常大,那么执行访问控制的时间成本将增大。这时就需要进行某种优化以控制访问控制表或能力表所需的时间。这个手段就是保护域。

访问控制表和能力表的一个共同缺点就是针对个体的文件需要设置个体的访问控制。如果某些文件或对象的访问控制权限一样,这种个体区别的记录方式就显得有些浪费了。如果这个时候使用保护域就可以解决这个问题。

保护域就是将访问控制权限一样的文件和对象组织成同一个域。而访问控制权限与每个域直接相关。每个域的控制独立于其他域。每个进程都运行在一定的域里,从而具有访问该域里面文件和对象的权限。

那么系统如何跟踪保护域及其相关权限呢?一个自然的措施是使用矩阵:矩阵的行代表域,列表示文件和对象。

进程运行中的域切换也可以用矩阵实现。

使用保护域的优点是可以将保护权限相同的对象组成一个集合进行处理。缺点是实现方式可能浪费空间。

其他文件安全措施

访问控制虽然能够保护文件不被没有授权的用户或进程非法访问,但这种保护措施十分脆弱,可以很容易被攻破。由于文件存放的数据可能对于用户来说非常重要,设计额外的保护措施就显得非常重要。这些措施中最主要的是加密,即根据某个给定算法和参数(密钥)对数据进行转换,使得转换后的数据无法辨认。要想
正常阅读文件的内容,则需要先进行解密。加解密技术在过去的几十年中取得了很大的发展,现在仍然是很多人的研究课题。

一种较为新颖的数据保护方法是隐写文件或隐写文件系统(crypt file sys-tems)。隐写就是将要保护的数据嵌入到其他文件中而达到隐身的目的。例如,我们可以将一个文件甚至整个文件系统隐写在一系列音频文件中。另外一种更有意思的技术是数据隐藏,即将数据隐藏起来,使得病毒或者试图进行非法访问的人无法看到数据,甚至无法得知数据的存在。

文件系统性能

文件的性能主要体现在两个方面:可靠性和速度。用于文件系统是用来存放数据的,其可靠性自然非常重要。否则存放的数据将没有人敢用。由于磁盘访问比内存访问慢很多,速度问题比起内存访问时更加重要。

文件系统可靠性

文件系统的可靠性体现在两个方面:持久性和一致性。持久性是指经久耐用,就是存放在文件系统的文件一 万年都不变。一致性则指里面存放的数据必须是好的。那么什么样的数据是好的数据呢?就是说存放在一个系统里面的数据之间必须一致,且必须符合客观现实。

文件系统持久性

要想文件系统保存的数据持久,单单依靠文件系统本身并无法解决。因为不管文件系统本身做得多好,如果磁盘被人丢进大火里,上面的数据也不会持久。当然,造成数据损坏的可能性还有很多。那么实现持久性的手段主要是备份和复制。

数据备份就是制作多余的数据拷贝,并保存在分开的存储介质上。这样,源数据出现问题时,可以使用备份介质上的数据,从而达到数据持久的目的。

从数据的角度可以将备份分为两种:一种是逻辑备份,另一种是物理备份。逻辑备份就是备份用户选择好的文件或文件夹。在逻辑备份时,备份的基本单位是文件。而备份过程就是将文件一个个拷贝到备份介质上。

物理备份则是备份整个磁盘。在物理备份时,备份的基本单位是字节或数据块。而备份过程就是将数据块一 个个拷贝到备份介质上。这里的拷贝是绕过文件系统而进行的,即通过裸读和裸写而进行数据拷贝的。

那为什么逻辑备份比物理备份容易呢?

  • 首先,物理备份不经过文件系统,因此需要裸读磁盘,即绕过文件系统对磁盘数据进行读写。而裸读或裸写对很多人来说比在文件系统上读写要困难很多。逻辑备份使用文件系统进行读写,备份软件无须与磁盘驱动器的许多底层细节打交道。
  • 其次,在进行逻辑备份的时候你知道一部分语意;在物理备份的时候你不知道它的语意。在这种情况下,如果备份中途出现错误,你将很难侦测到它的错误。即使侦测到,进行恢复也不容易。而逻辑备份则可以文件为单位进行正确性确认。如果出现错误文件,只需要重新备份有问题的文件即可。当然,在物理备份时可以数据块为单位进行错误纠正。
  • 最后,物理备份由于是拷贝磁盘上的一切数据,将造成物理磁盘的卷标也被拷贝。这样将造成物理备份的备份磁盘和源磁盘卷标相同。由于备份磁盘在备份过程中发生卷标改变,将造成备份磁盘无法工作。另外,如果两个卷标相同的磁盘接入到同一部计算机,将造成系统故障或瘫痪。

根据备份方式的不同,备份可以分为很多种类。例如,备份方式有下列几种:

  • 主动和被动备份:以谁提要求来区分。
  • 在线和离线备份:以是否停止服务区分。
  • 实时和延时备份:以主备数据时差区分。
  • 等分和差分备份:以主备数据量差区分。
  • 增量和差分增量:以备份量和差分标来区分。
  • 分裂和并列备份:以备份是否减少来区分。
  • 近程与远程备份:以主备中心距离区分。

文件系统一致性

一致性要求数据是好的。而好的则意味着数据必须彼此之间保持一致,且不与客观现实相悖。

建立一个新的文件有两个操作步骤:

  1. 建立文件的I-NODE,将I-NODE写入磁盘。
  2. 将I-NODE的磁盘地址和文件名写入该文件所属文件夹里。

如果在这些操作中间发生系统崩溃,则文件系统就有可能出现不一致。例如,如果在将I-NODE写入磁盘后,但把I-NODE的磁盘地址和文件名写入该文件所属文件夹之前发生系统故障,则将出现“孤儿文件”,即文件不在文件系统里出现(目录里没有),但却在磁盘上。如果将上述两个操作步骤顺序颠倒,则有可能出现“魅影文件”,即文件在文件系统里出现(在目录里有),却没有在磁盘上。

孤儿文件和魅影文件的危害很大。对于魅影文件来说,其问题有三。首先就是文件夹指向了一个没有被写入的I-NODE,如果对魅影文件进行读写,读写的东西将是不可预料的,而这种不可预料有可能造成数据损毁。另外,我们无法确定这个I-NODE里面的内容有没有被改写过。还有就是文件夹能容纳的目录项是有限的,那么魅影文件占用了文件目录项,从而使得真正有用的文件可能没有地方存放。对于孤儿文件来说,其问题是无法访问和占用磁盘空间。

移动一个文件时候有两步操作:

  1. 将文件从老的文件夹里删除。
  2. 将文件添加到新的文件夹。

如果在上述两个步骤之间发生问题,也将出现孤儿文件。如果将上述两个步骤顺序颠倒,则有可能出现两个文件拷贝,即“幻影文件”。注意,这里步骤1的删除指的是对文件夹的修改,并不是物理删除。从逻辑上讲,一个文件的删除在文件夹被修改后就实现了。物理删除是另外一回事。由于移动文件时需要从移动的目录夹里面删除信息,再在目的文件夹上增加信息,在逻辑上相当于删除和添加两个操作。当然,文件并没有被物理删除。对于有些文件系统来说,这和删除是不同的,因为真正的删除还需要修改空间表(space_map)。

那么如何保证文件系统的一致性呢?答案是使用日志(logging)、事务(transac-tion)和随影(shadowing)。

  1. 日志

    日志就是将文件操作全部记录下来,存放在一个不同的地方,这个地方称为日志。当这些新数据全部写完后,在日志最后写上“完毕”(commit)。在这之后,我们就可以将日志里面的数据往源文件里写。这样就能确保文件数据的一致性。

    例如,如果在写入“完毕”前的任何时候出现故障,则新的数据将被丢弃,源文件的数据保持不变。即原来是 一致的数据仍然保持一致。如果在写入“完毕”后发生故障,例如在将数据往源文件里写的中途出现故障,造成只写了一半时怎么办?这也不是问题。在系统恢复后,将新的数据重新往源文件里写即可。可能有人会认为将日志写两遍会造成操作被执行两次而出现问题。但这是不可能的。因为日志里面记录的是操作结果,而不是操作。而结果数据写两遍还是一样的结果,因此不会产生任何问题。

  2. 事务

    另一种确保文件系统一致性的手段是使用“事务”。一个事务是一组操作。事务机制可以确保这一组操作要么全部发生,要么一个都不发生。事务实际上就是将一组操作转变为原子操作。

    那么事务是如何实现的呢?答案很显然,与日志一样。即将所有的磁盘写操作记录在与源数据分开的地方。 在“完毕”(commit)后,即可以将这些数据往源数据上拷贝。

    磁盘系统读写一个扇面的操作是一个原子操作,即该扇面要么全部写完或读完,要么就根本没有进行读写。这个硬件原子操作是由磁盘制造商提供的。有了硬件制造商提供的这个操作,事务的原子性就很容易实现了。在事务处理的最后一步是“完毕”(com-mit)操作。这个操作就是在磁盘上写入一个特殊的标志,告诉系统该事务处理正常完成。由于这个标志很小,所占空间少于一个扇面,其读写将具有原子性。如果我们看到有这个标志,则事务里面的所有读写操作都已经顺利完成,我们视其为有效。否则,所有操作均视为无效,即以前的操作全部作废。

  3. 随影

    日志和事务虽然可以提供数据一致性保障,但他们有一个共同的问题,那就是磁盘写操作均记录在一个不同的地方,在完毕后需要将数据拷贝到源数据处。而在往源数据拷贝的过程中,源数据将处于不一致状态。需要等到数据拷贝完毕,源数据才归于一致。这样,在拷贝中间,源数据就不能被使用,凡是使用源数据的应用均应该暂停服务。如果拷贝过程中出了问题,则这种暂停的时间将更为漫长。对于某些应用来说,这种漫长的暂停是完全不能接受的。

    而改变此种问题的解决办法就是随影。随影的原理很简单:保持两个数据版本。通过一个指针告诉用户和应用软件当前使用的是哪一个版本的数据。在需要更新数据时,将更新写到非当前使用的版本上。在更新完毕后,修改指针,使其指向新的数据版本。之后在切换下来的数据版本上进行更新。这样就能保障数据的一致性。

    随影技术主要应用于数据库系统。由于新旧版本切换只涉及一个指针的切换,这种切换将十分迅速。由此造成的应用暂停可以忽略不计。而且,指针切换本身是一个原子操作,不会出现指针破损问题。这是因为指针的大小不会超过一个磁盘扇面的尺寸,而磁盘的一个扇面读写由硬件保证为原子操作。

    随影的好处也不是没有代价的,而这个代价就是额外的磁盘空间。因为要保持两个数据版本。

  4. 文件系统和用户文件

    很显然,确保文件系统的一致性是要付出代价的。日志、事务和随影都需要使用额外的空间和时间。如果每次文件操作均需要使用这些方式来保护,则文件系统的效率将显著下降。但我们又不能不保护文件系统的一 致性。因为这是文件系统正确性的前提。

    那有没有什么办法既可以确保文件系统一致性,又能够不显著降低系统效率呢?

    有。那就是我们只是有选择地对某些文件进行一致性保护,而对另外一些则不予保护。我们要选择的文件必须是对文件系统非常重要的文件。那么什么文件对文件系统很重要呢?当然是文件系统的元数据,也就是文件夹、自由空间表格等。

    因为这些元数据的丢失将造成文件系统不能正常运行。因此,我们对其进行保护。而对于用户数据则不进行 一致性保护。因为用户数据损坏影响的是单个的用户或文件,并不会影响整个文件系统。而文件系统本身出现故障将影响所有的用户和文件。如果用户很关心自己数据的一致性和安全性,用户应该采取数据备份或容灾手段予以确保。而不应该依赖文件系统。

  5. 文件系统一致性检查

    具体来说,文件系统一致性包括如下几个方面:

    • 没有魅影文件。
    • 没有孤儿文件。
    • 文件I-NODE里面的链接计数与其出现在文件系统中不同位置的次数一致。
    • 没有消失的空间。
    • 没有额外的空间。

    对于前3点来说,这些信息保存在文件目录和I-NODE里面。后面两点的意思就是一个磁盘块要么在闲置空间里,要么被占用,而不能同时处于两种状态。为了对后两点进行判定,我们需要维护两张表:一张闲置空间表,一张占用空间表。通过两张表相互比对来判断是否存在消失或额外的空间。

有一个磁盘块对应的两种表的位置皆为1,这说明该数据块既闲置,又被占用,即出现了“一仆二主”问题。什么样的情况下会发生这种状况呢?如果系统释放一个磁盘块,我们首先将闲置空间的对应字位设置为1, 表示该空间已经释放。然后我们需要将占用空间闲置表里的对应字位设置为0。但在设置为0前,系统因故不
能继续。

有一个磁盘块对应的两种表的位置皆为0,这说明该数据块既非闲置,又未占用,即这个数据块消失了。这就是所谓的消失的空间问题(vanished space problem)。

文件系统效率性能

  • 将经常访问的内容置于缓存里。
  • 减少磁盘访问时磁臂移动的距离。
  • 提前将需要的数据块读入内存。

18.7.1 文件缓存

文件缓存就是将文件内容的部分置于缓存,从而从缓存满足用户的文件访问请求,而无须每次都到磁盘上去读写。这样可以大大提高访问效率。而放入缓存的数据既可以是用户数据,也可以是元数据。由于元数据的使用频率很高,缓冲元数据比缓存用户数据效果将更加明显。

那么文件缓存的实现有两种方式:

  • 缓存在虚地址空间。
  • 缓存在实地址空间。

缓存在虚地址空间就是缓存在虚拟内存里,即将文件映射到进程的虚拟地址空间,即我们前面讲过的内存映射的文件。这样的话对文件的访问将变为对内存的访问。这种方式的优点就是简单,因为我们将工作交给了内存管理单元(MMU)。但缺点是不一定能够提高文件访问效率。因为MMU不一定会把我们需要的文件保存在内存里,它随时可能被交换或替换出去。另外,如果有多个进程使用该文件的话也会出现问题。

缓存在实地址空间就是缓存在物理内存里。这种方式需要由文件系统自己负责管理。即文件系统需要决定什么数据需要缓存,什么数据需要更换等。这样自然导致文件系统变得复杂,但是可以确保需要的数据总是存放在缓存里。

18.7.2 虚拟内存和文件缓存

VM的根本目的是提供一个速度非常高、容量非常大的并不存在的内存空间。它从物理内存出发,为了增加内存空间而扩展到磁盘上;而FC是为了提高文件的访问效率而出现。它从磁盘出发,为了提高访问效率而将文件置于缓存。就是说VM和FC的根本目的不同,即有着本质区别。因此,其工作机制、语义和操作界面皆不相同。

正是由于这些机制、语义和操作界面的不同,使得它们不能相互替代对方的角色。

18.7.3 提前读取

提前读取就是在每次访问磁盘时,我们多读一些数据出来。因为按照时空局域性的原则,用户访问一个地方后,很可能会访问该地方后面的地方。因此我们提前将其后面的数据读出来。这样在用户随后需要访问这些数据的时候就可以从内存得到满足,从而提高文件访问效率。

由于磁盘访问的瓶颈是寻道和旋转延迟,而数据传输相对来说所占消耗很小。这样读出一个数据块和读出10个数据块所费时间几乎没有任何区别。因此,提前读取的代价几乎为0。

当然,提前读取只在用户对数据是顺序访问或者准顺序访问时效果最好,对于完全随机的访问效果则不太显著。

18.7.4 减少磁臂移动

磁盘访问的瓶颈是其机械运动部分,即磁臂移动和磁盘旋转,其中磁臂移动又占主导地位。 受运动精度限制,提高磁臂移动速度的空间有限。

那么剩下的节省时间的办法就是缩短磁臂移动距离。而缩短磁臂移动距离有两种办法:

  • 将文件头(I-NODE)和数据块放置在一起。
  • 将文件读写操作进行适当排序。

I-NODE是单独放在一起,与文件数据分开的。这样就造成读I-NODE和读文件数据之间需要进行磁盘寻道。而第1种办法的考虑因素是文件打开时需要读取文件头。而一般来说,文件头读取后立即或很快就需要访问文件内容。如果I-NODE和数据块放在一起,读取数据块时就不需要寻道。这样就会大大提高读取数据的速度。

不过,将I-NODE和文件数据块放在一起会在对文件一致性检查和清空磁盘的操作造成麻烦。由于在进行磁盘数据一致性文件检查时需要访问所有的I-NODE,如果它们放在一起效率就高,而分开放置需要一个一个I-NODE地找,效率就会很低。在删除磁盘上所有文件时也需要访问所有I-NODE。显然I-NODE的分散放置将降低这种操作的效率。

第2种方法是将一系列的磁盘访问操作按照磁臂最小移动的原则进行排序。使得从一个访问到下一个访问的磁臂移动距离最短,从而提高磁盘的整体访问效率。

posted @ 2021-05-24 19:13  睿阳  阅读(572)  评论(0编辑  收藏  举报