这是Java建设者第 79 篇原创长文html
全部的应用程序都须要存储和检索信息。进程运行时,它可以在本身的存储空间内存储必定量的信息。然而,存储容量受虚拟地址空间大小的限制。对于一些应用程序来讲,存储空间的大小是充足的,可是对于其余一些应用程序,好比航空订票系统、银行系统、企业记帐系统来讲,这些容量又显得过小了。node
第二个问题是,当进程终止时信息会丢失。对于一些应用程序(例如数据库),信息会长久保留。在这些进程终止时,相关的信息应该保留下来,是不能丢失的。甚至这些应用程序崩溃后,信息也应该保留下来。程序员
第三个问题是,一般须要不少进程在同一时刻访问这些信息。解决这种问题的方式是把这些信息单独保留在各自的进程中。web
所以,对于长久存储的信息咱们有三个基本需求:算法
必需要有可能存储的大量的信息shell
信息必须可以在进程终止时保留数据库
必须可以使多个进程同时访问有关信息数组
磁盘(Magnetic disk) 一直是用来长久保存信息的设备。近些年来,固态硬盘逐渐流行起来。缓存
固态硬盘不只没有易损坏的移动部件,并且可以提供快速的随机访问。相比而言,虽然磁带和光盘也被普遍使用,可是它们的性能相对较差,一般应用于备份。咱们会在后面探讨磁盘,如今姑且把磁盘看成一种大小固定块的线性序列好了,而且支持以下操做安全
读块 k
事实上磁盘支持更多的操做,可是只要有了读写操做,原则上就可以解决长期存储的问题。
然而,磁盘还有一些不便于实现的操做,特别是在有不少程序或者多用户使用的大型系统上(如服务器)。在这种状况下,很容易产生一些问题,例如
你如何找到这些信息?
你如何保证一个用户不会读取另一个用户的数据?
你怎么知道哪些块是空闲的?等等问题
咱们能够针对这些问题提出一个新的抽象 - 文件。进程和线程的抽象、地址空间和文件都是操做系统的重要概念。若是你能真正深刻了解这三个概念,那么你就走上了成为操做系统专家的道路。
文件(Files)是由进程建立的逻辑信息单元。一个磁盘会包含几千甚至几百万个文件,每一个文件是独立于其余文件的。事实上,若是你能把每一个文件都看做一个独立的地址空间,那么你就能够真正理解文件的概念了。
进程可以读取已经存在的文件,并在须要时从新建立他们。存储在文件中的信息必须是持久的,这也就是说,不会由于进程的建立和终止而受影响。一个文件只能在当用户明确删除的时候才能消失。尽管读取和写入都是最基本的操做,但还有许多其余操做,咱们将在下面介绍其中的一些。
文件由操做系统进行管理,有关文件的构造、命名、访问、使用、保护、实现和管理方式都是操做系统设计的主要内容。从整体上看,操做系统中处理文件的部分称为 文件系统(file system),这就是咱们所讨论的。
从用户角度来讲,用户一般会关心文件是由什么组成的,如何给文件进行命名,如何保护文件,以及能够对文件进行哪些操做等等。尽管是用链表仍是用位图记录内存空闲区并非用户所关心的主题,而这些对系统设计人员来讲相当重要。下面咱们就来探讨一下这些主题
1
文件
1.1
文件命名
文件是一种抽象机制,它提供了一种方式用来存储信息以及在后面进行读取。可能任何一种机制最重要的特性就是管理对象的命名方式。在建立一个文件后,它会给文件一个命名。当进程终止时,文件会继续存在,而且其余进程能够使用名称访问该文件。
文件命名规则对于不一样的操做系统来讲是不同的,可是全部现代操做系统都容许使用 1 - 8 个字母的字符串做为合法文件名。
某些文件区分大小写字母,而大多数则不区分。UNIX 属于第一类;历史悠久的 MS-DOS 属于第二类(顺便说一句,尽管 MS-DOS 历史悠久,但 MS-DOS 仍在嵌入式系统中很是普遍地使用,所以它毫不是过期的);所以,UNIX 系统会有三种不一样的命名文件:maria、Maria、MARIA 。在 MS-DOS ,全部这些命名都属于相同的文件。
这里可能须要在文件系统上预留一个位置。Windows 95 和 Windows 98 都使用了 MS-DOS 文件系统,叫作 FAT-16,所以继承了它的一些特征,例若有关文件名的构造方法。Windows 98 引入了对 FAT-16 的一些扩展,从而致使了 FAT-32 的生成,可是这二者很类似。另外,Windows NT,Windows 2000,Windows XP,Windows Vista,Windows 7 和 Windows 8 都支持 FAT 文件系统,这种文件系统有些过期。然而,这些较新的操做系统还具备更高级的本机文件系统(NTFS),有不一样的特性,那就是基于 Unicode 编码的文件名。事实上,Windows 8 还配备了另外一种文件系统,简称 ReFS(Resilient File System),但这个文件系统通常应用于 Windows 8 的服务器版本。下面除非咱们特殊声明,不然咱们在提到 MS-DOS 和 FAT 文件系统的时候,所指的就是 Windows 的 FAT-16 和 FAT-32。这里要说一下,有一种相似 FAT 的新型文件系统,叫作 exFAT。它是微软公司对闪存和大文件系统开发的一种优化的 FAT 32 扩展版本。ExFAT 是如今微软惟一可以知足 OS X读写操做的文件系统。
许多操做系统支持两部分的文件名,它们之间用 . 分隔开,好比文件名 prog.c。原点后面的文件称为 文件扩展名(file extension) ,文件扩展名一般表示文件的一些信息。例如在 MS-DOS 中,文件名是 1 - 8 个字符,加上 1 - 3 个字符的可选扩展名组成。在 UNIX 中,若是有扩展名,那么扩展名的长度将由用户来决定,一个文件甚至能够包括两个或更多的扩展名,例如 homepage.html.zip,html 表示一个 web 网页而 .zip 表示文件homepage.html 已经采用 zip 程序压缩完成。一些经常使用的文件扩展名以及含义以下图所示
在 UNIX 系统中,文件扩展名只是一种约定,操做系统并不强制采用。
名为 file.txt 的文件是文本文件,这个文件名更多的是提醒全部者,而不是给计算机传递信息。可是另外一方面,C 编译器可能要求它编译的文件以.c 结尾,不然它会拒绝编译。然而,操做系统并不关心这一点。
对于能够处理多种类型的程序,约定就显得及其有用。例如 C 编译器能够编译、连接多种文件,包括 C 文件和汇编语言文件。这时扩展名就颇有必要,编译器利用它们区分哪些是 C 文件,哪些是汇编文件,哪些是其余文件。所以,扩展名对于编译器判断哪些是 C 文件,哪些是汇编文件以及哪些是其余文件变得相当重要。
与 UNIX 相反,Windows 就会关注扩展名并对扩展名赋予了新的含义。用户(或进程) 能够在操做系统中注册扩展名,而且规定哪一个程序可以拥有扩展名。当用户双击某个文件名时,拥有该文件名的程序就启动并运行文件。例如,双击 file.docx 启动了 Word 程序,并以 file.docx 做为初始文件。
1.2
文件结构
文件的构造有多种方式。下图列出了经常使用的三种构造方式
上图中的 a 是一种无结构的字节序列,操做系统不关心序列的内容是什么,操做系统能看到的就是字节(bytes)。其文件内容的任何含义只在用户程序中进行解释。UNIX 和 Windows 都采用这种办法。
把文件当作字节序列提供了最大的灵活性。用户程序能够向文件中写任何内容,而且能够经过任何方便的形式命名。操做系统不会为为用户写入内容提供帮助,固然也不会干扰阻塞你。对于想作特殊操做的用户来讲,后者是十分重要的。全部的 UNIX 版本(包括 Linux 和 OS X)和 Windows 都使用这种文件模型。
图 b 表示在文件结构上的第一步改进。在这个模型中,文件是具备固定长度记录的序列,每一个记录都有其内部结构。把文件做为记录序列的核心思想是:读操做返回一个记录,而写操做重写或者追加一个记录。第三种文件结构如上图 c 所示。在这种组织结构中,文件由一颗记录树构成,记录树的长度不必定相同,每一个记录树都在记录中的固定位置包含一个key 字段。这棵树按 key 进行排序,从而能够对特定的 key 进行快速查找。
在记录树的结构中,能够取出下一个记录,可是最关键的仍是根据 key 搜索指定的记录。如上图 c 所示,用户能够读出指定的 pony 记录,而没必要关心记录在文件中的确切位置。用户也能够在文件中添加新的记录。可是用户不能决定添加到何处位置,添加到何处位置是由操做系统决定的。
1.3
文件类型
不少操做系统支持多种文件类型。例如,UNIX(一样包括 OS X)和 Windows 都具备常规的文件和目录。除此以外,UNIX 还具备字符特殊文件(character special file) 和 块特殊文件(block special file)。常规文件(Regular files) 是包含有用户信息的文件。用户通常使用的文件大都是常规文件,常规文件通常包括 可执行文件、文本文件、图像文件,从常规文件读取数据或将数据写入时,内核会根据文件系统的规则执行操做,写入可能被延迟,记录日志或者接受其余操做。
字符特殊文件和输入/输出有关,用于串行 I/O 类设备,如终端、打印机、网络等。块特殊文件用于磁盘类设备。咱们主要讨论的是常规文件。
常规文件通常分为 ASCII 码文件或者二进制文件。ASCII 码文件由文本组成。在一些系统中,每行都会用回车符结束(ASCII 码是 13,控制字符 CR,转义字符\r。),另一些则会使用换行符(ASCII 码是 10,控制字符 LF,转义字符\n)。一些系统(好比 Windows)二者都会使用。
ASCII 文件的优势在于显示 和 打印,还能够用任何文本编辑器进行编辑。进一步来讲,若是许多应用程序使用 ASCII 码做为输入和输出,那么很容易就可以把多个程序链接起来,一个程序的输出多是另外一个程序的输入,就像管道同样。
其余与 ASCII 不一样的是二进制文件。打印出来的二进制文件是没法理解的。下面是一个二进制文件的格式,它取自早期的 UNIX 。尽管从技术上来看这个文件只是字节序列,可是操做系统只有在文件格式正确的状况下才会执行。
这个文件有五个段:文件头、正文、数据、重定位位和符号表。文件头以 魔数(magic number) 为开始,代表这个文件是一个可执行文件(以防止意外执行非此格式的文件)。而后是文件各个部分的大小,开始执行的标志以及一些标志位。程序自己的正文和数据在文件头后面,他们被加载到内存中或者重定位会根据重定位位进行判断。符号表则用于调试。
二进制文件的另一种形式是存档文件,它由已编译但没有连接的库过程(模块)组合而成。每一个文件都以模块头开始,其中记录了名称、建立日期、全部者、保护码和文件大小。和可执行文件同样,模块头也都是二进制数,将它们复制到打印机将会产生乱码。
全部的操做系统必须至少可以识别一种文件类型:它本身的可执行文件。之前的 TOPS-20 系统(用于 DECsystem 20)甚至要检查要执行的任何文件的建立时间,为了定位资源文件来检查自动文件建立后是否被修改过。若是被修改过了,那么就会自动编译文件。在 UNIX 中,就是在 shell 中嵌入 make 程序。此时操做系统要求用户必须采用固定的文件扩展名,从而肯定哪一个源程序生成哪一个二进制文件。
什么是 make 程序?在软件发展过程当中,make 程序是一个自动编译的工具,它经过读取称为 Makefiles 的文件来自动从源代码构建可执行程序和库,该文件指定了如何导出目标程序。尽管集成开发环境和特定语言的编译器功能也能够用于管理构建过程,但 Make 仍被普遍使用,尤为是在 Unix 和相似 Unix 的操做系统中使用。
当程序从文件中读写数据时,请求会转到内核处理程序(kernel driver)。若是文件是常规文件,则数据由文件系统驱动程序处理,而且一般存储在磁盘或其余存储介质上的某块区域中,从文件中读取的数据就是以前在该位置写入的数据。
当数据读取或写入到设备文件时,请求会被设备驱动程序处理。每一个设备文件都有一个关联的编号,该编号标示要使用的设备驱动程序。设备处理数据的工做是它本身的事儿。
块设备 也叫作块特殊文件,它的行为一般与普通文件类似:它们是字节数组,而且在给定位置读取的值是最后写入该位置的值。来自块设备的数据能够缓存在内存中,并从缓存中读取;写入能够被缓冲。块设备一般是可搜索的,块设备的概念是,相应的硬件能够一次读取或者写入整个块,例如磁盘上的一个扇区
目录(Directories) 是管理文件系统结构的系统文件。它是用于在计算机上存储文件的位置。目录位于分层文件系统中,例如 Linux,MS-DOS 和 UNIX。
它显示全部本地和子目录(例如,cdn 目录中的 big 目录)。当前目录是 C 盘驱动器的根目录。之因此称为根目录,是由于该目录下没有任何内容,而其余目录都在该目录下分支。
1.4
文件访问
早期的操做系统只有一种访问方式:序列访问(sequential access)。在这些系统中,进程能够按照顺序读取全部的字节或文件中的记录,可是不能跳过并乱序执行它们。顺序访问文件是能够返回到起点的,须要时能够屡次读取该文件。当存储介质是磁带而不是磁盘时,顺序访问文件很方便。
在使用磁盘来存储文件时,能够不按照顺序读取文件中的字节或者记录,或者按照关键字而不是位置来访问记录。这种可以以任意次序进行读取的称为随机访问文件(random access file)。许多应用程序都须要这种方式。
随机访问文件对许多应用程序来讲都必不可少,例如,数据库系统。若是乘客打电话预约某航班机票,订票程序必须可以直接访问航班记录,而没必要先读取其余航班的成千上万条记录。
有两种方法能够表示从何处开始读取文件。第一种方法是直接使用 read 从头开始读取。另外一种是用一个特殊的 seek 操做设置当前位置,在 seek 操做后,从这个当前位置顺序地开始读文件。UNIX 和 Windows 使用的是后面一种方式。
1.5
文件属性
文件包括文件名和数据。除此以外,全部的操做系统还会保存其余与文件相关的信息,如文件建立的日期和时间、文件大小。咱们能够称这些为文件的属性(attributes)。有些人也喜欢把它们称做 元数据(metadata)。文件的属性在不一样的系统中差异很大。文件的属性只有两种状态:设置(set) 和 清除(clear)。下面是一些经常使用的属性
没有一个系统可以同时具备上面全部的属性,但每一个属性都在某个系统中采用。
前面四个属性(保护,口令,建立者,全部者)与文件保护有关,它们指出了谁能够访问这个文件,谁不能访问这个文件。
保护(File Protection):用于保护计算机上有价值数据的方法。文件保护是经过密码保护文件或者仅仅向特定用户或组提供权限来实现。
在一些系统中,用户必须给出口令才能访问文件。标志(flags)是一些位或者短属性可以控制或者容许特定属性。
隐藏文件位(hidden flag)表示该文件不在文件列表中出现。
存档标志位(archive flag)用于记录文件是否备份过,由备份程序清除该标志位;若文件被修改,操做系统则设置该标志位。用这种方法,备份程序能够知道哪些文件须要备份。
记录长度(record-length)、键的位置(key-position)和键的长度(key-length)等字段只能出如今用关键字查找记录的文件中。它们提供了查找关键字所须要的信息。
不一样的时间字段记录了文件的建立时间、最近一次访问时间以及最后一次修改时间,它们的做用不一样。例如,目标文件生成后被修改的源文件须要从新编译生成目标文件。这些字段提供了必要的信息。
当前大小字段指出了当前的文件大小,一些旧的大型机操做系统要求在建立文件时指定文件最大值,以便让操做系统提早保留最大存储值。可是一些服务器和我的计算机却不用设置此功能。
1.6
文件操做
使用文件的目的是用来存储信息并方便之后的检索。对于存储和检索,不一样的系统提供了不一样的操做。如下是与文件有关的最经常使用的一些系统调用:
Create,建立不包含任何数据的文件。调用的目的是表示文件即将创建,并对文件设置一些属性。
Delete,当文件再也不须要,必须删除它以释放内存空间。为此总会有一个系统调用来删除文件。
Open,在使用文件以前,必须先打开文件。这个调用的目的是容许系统将属性和磁盘地址列表保存到主存中,用来之后的快速访问。
Close,当全部进程完成时,属性和磁盘地址再也不须要,所以应关闭文件以释放表空间。不少系统限制进程打开文件的个数,以此达到鼓励用户关闭再也不使用的文件。磁盘以块为单位写入,关闭文件时会强制写入最后一块,即便这个块空间内部还不满。
Read,数据从文件中读取。一般状况下,读取的数据来自文件的当前位置。调用者必须指定须要读取多少数据,而且提供存放这些数据的缓冲区。
Write,向文件写数据,写操做通常也是从文件的当前位置开始进行。若是当前位置是文件的末尾,则会直接追加进行写入。若是当前位置在文件中,则现有数据被覆盖,而且永远消失。
append,使用 append 只能向文件末尾添加数据。
seek,对于随机访问的文件,要指定从何处开始获取数据。一般的方法是用 seek 系统调用把当前位置指针指向文件中的特定位置。seek 调用结束后,就能够从指定位置开始读写数据了。
get attributes,进程运行时一般须要读取文件属性。
set attributes,用户能够本身设置一些文件属性,甚至是在文件建立以后,实现该功能的是 set attributes 系统调用。
2
目录
文件系统一般提供目录(directories) 或者 文件夹(folders) 用于记录文件的位置,在不少系统中目录自己也是文件,下面咱们会讨论关于文件,他们的组织形式、属性和能够对文件进行的操做。
2.1
一级目录系统
目录系统最简单的形式是有一个可以包含全部文件的目录。这种目录被称为根目录(root directory),因为根目录的惟一性,因此其名称并不重要。在最先期的我的计算机中,这种系统很常见,部分缘由是由于只有一个用户。下面是一个单层目录系统的例子
该目录中有四个文件。这种设计的优势在于简单,而且可以快速定位文件,毕竟只有一个地方能够检索。这种目录组织形式如今通常用于简单的嵌入式设备(如数码相机和某些便携式音乐播放器)上使用。
2.2
层次目录系统
对于简单的应用而言,通常都用单层目录方式,可是这种组织形式并不适合于现代计算机,由于现代计算机含有成千上万个文件和文件夹。若是都放在根目录下,查找起来会很是困难。为了解决这一问题,出现了层次目录系统(Hierarchical Directory Systems),也称为目录树。经过这种方式,能够用不少目录把文件进行分组。进而,若是多个用户共享同一个文件服务器,好比公司的网络系统,每一个用户能够为本身的目录树拥有本身的私人根目录。这种方式的组织结构以下
根目录含有目录 A、B 和 C ,分别属于不一样的用户,其中两个用户个字建立了子目录。用户能够建立任意数量的子目录,现代文件系统都是按照这种方式组织的。
2.3
路径名
当目录树组织文件系统时,须要有某种方法指明文件名。经常使用的方法有两种,第一种方式是每一个文件都会用一个绝对路径名(absolute path name),它由根目录到文件的路径组成。举个例子,/usr/ast/mailbox 意味着根目录包含一个子目录usr,usr 下面包含了一个 mailbox。绝对路径名老是以 / 开头,而且是惟一的。在 UNIX 中,路径的组件由/分隔。在 Windows 中,分隔符为\。在 MULTICS 中,它是>。所以,在这三个系统中,相同的路径名将被编写以下
1Windows \usr\ast\mailbox 2UNIX /usr/ast/mailbox 3MULTICS >usr>ast>mailbox
不论使用哪一种方式,若是路径名的第一个字符是分隔符,那就是绝对路径。
另一种指定文件名的方法是 相对路径名(relative path name)。它经常和 工做目录(working directory) (也称做 当前目录(current directory))一块儿使用。用户能够指定一个目录做为当前工做目录。例如,若是当前目录是 /usr/ast,那么绝对路径 /usr/ast/mailbox能够直接使用 mailbox 来引用。也就是说,若是工做目录是 /usr/ast,则 UNIX 命令
1cp /usr/ast/mailbox /usr/ast/mailbox.bak
和命令
1cp mailbox mailbox.bak
具备相同的含义。相对路径一般状况下更加方便和简洁。而它实现的功能和绝对路径安全相同。
一些程序须要访问某个特定的文件而没必要关心当前的工做目录是什么。在这种状况下,应该使用绝对路径名。
支持层次目录结构的大多数操做系统在每一个目录中有两个特殊的目录项. 和 ..,读做 dot 和 dotdot。dot 指的是当前目录,dotdot 指的是其父目录(在根目录中例外,在根目录中指向本身)。能够参考下面的进程树来查看如何使用。
一个进程的工做目录是 /usr/ast,它可采用 .. 沿树向上,例如,可用命令
cp ../lib/dictionary .
把文件 usr/lib/dictionary 复制到本身的目录下,第一个路径告诉系统向上找(到 usr 目录),而后向下到 lib 目录,找到 dictionary 文件
第二个参数 . 指定当前的工做目录,当 cp 命令用目录名做为最后一个参数时,则把所有的文件复制到该目录中。固然,对于上述复制,键入
cp /usr/lib/dictionary .
是更经常使用的方法。用户这里采用 . 能够避免键入两次 dictionary 。不管如何,键入
cp /usr/lib/dictionary dictionary
也可正常工做,就像键入
cp /usr/lib/dictionary /usr/lib/dictionary
同样。全部这些命令都可以完成一样的工做。
2.4
目录操做
不一样文件中管理目录的系统调用的差异比管理文件的系统调用差异大。为了了解这些系统调用有哪些以及它们怎样工做,下面给出一个例子(取自 UNIX)。
Create,建立目录,除了目录项 . 和 .. 外,目录内容为空。
Delete,删除目录,只有空目录能够删除。只包含 . 和 .. 的目录被认为是空目录,这两个目录项一般不能删除
opendir,目录内容可被读取。例如,未列出目录中的所有文件,程序必须先打开该目录,而后读其中所有文件的文件名。与打开和读文件相同,在读目录前,必须先打开文件。
closedir,读目录结束后,应该关闭目录用于释放内部表空间。
readdir,系统调用 readdir 返回打开目录的下一个目录项。之前也采用 read 系统调用来读取目录,可是这种方法有一个缺点:程序员必须了解和处理目录的内部结构。相反,不论采用哪种目录结构,readdir 老是以标准格式返回一个目录项。
rename,在不少方面目录和文件都类似。文件能够更换名称,目录也能够。
link,连接技术容许在多个目录中出现同一个文件。这个系统调用指定一个存在的文件和一个路径名,并创建从该文件到路径所指名字的连接。这样,能够在多个目录中出现同一个文件。有时也被称为硬连接(hard link)。
3
文件系统的实现
在对文件有了基本认识以后,如今是时候把目光转移到文件系统的实现上了。以前用户关心的一直都是文件是怎样命名的、能够进行哪些操做、目录树是什么,如何找到正确的文件路径等问题。而设计人员关心的是文件和目录是怎样存储的、磁盘空间是如何管理的、如何使文件系统得以流畅运行的问题,下面咱们就来一块儿讨论一下这些问题。
3.1
文件系统布局
文件系统存储在磁盘中。大部分的磁盘可以划分出一到多个分区,叫作磁盘分区(disk partitioning) 或者是磁盘分片(disk slicing)。每一个分区都有独立的文件系统,每块分区的文件系统能够不一样。磁盘的 0 号分区称为 主引导记录(Master Boot Record, MBR),用来引导(boot) 计算机。在 MBR 的结尾是分区表(partition table)。每一个分区表给出每一个分区由开始到结束的地址。系统管理员使用一个称为分区编辑器的程序来建立,调整大小,删除和操做分区。这种方式的一个缺点是很难适当调整分区的大小,致使一个分区具备不少可用空间,而另外一个分区几乎彻底被分配。
MBR 能够用在 DOS 、Microsoft Windows 和 Linux 操做系统中。从 2010 年代中期开始,大多数新计算机都改用 GUID 分区表(GPT)分区方案。
下面是一个用 GParted 进行分区的磁盘,表中的分区都被认为是 活动的(active)。
当计算机开始引导 boot 时,BIOS 读入并执行 MBR。
引导块
MBR 作的第一件事就是肯定活动分区,读入它的第一个块,称为引导块(boot block) 并执行。引导块中的程序将加载分区中的操做系统。为了一致性,每一个分区都会从引导块开始,即便引导块不包含操做系统。引导块占据文件系统的前 4096 个字节,从磁盘上的字节偏移量 0 开始。引导块可用于启动操做系统。
在计算机中,引导就是启动计算机的过程,它能够经过硬件(例如按下电源按钮)或者软件命令的方式来启动。开机后,电脑的 CPU 还不能执行指令,由于此时没有软件在主存中,因此一些软件必须先被加载到内存中,而后才能让 CPU 开始执行。也就是计算机开机后,首先会进行软件的装载过程。
重启电脑的过程称为从新引导(rebooting),从休眠或睡眠状态返回计算机的过程不涉及启动。
除了从引导块开始以外,磁盘分区的布局是随着文件系统的不一样而变化的。一般文件系统会包含一些属性,以下
超级块
紧跟在引导块后面的是 超级块(Superblock),超级块 的大小为 4096 字节,从磁盘上的字节偏移 4096 开始。超级块包含文件系统的全部关键参数
文件系统的大小
文件系统中的数据块数
指示文件系统状态的标志
在计算机启动或者文件系统首次使用时,超级块会被读入内存。
空闲空间块
接着是文件系统中空闲块的信息,例如,能够用位图或者指针列表的形式给出。
BitMap 位图或者 Bit vector 位向量
位图或位向量是一系列位或位的集合,其中每一个位对应一个磁盘块,该位能够采用两个值:0 和 1,0 表示已分配该块,而 1 表示一个空闲块。下图中的磁盘上给定的磁盘块实例(分配了绿色块)能够用 16 位的位图表示为:0000111000000110。
使用链表进行管理
在这种方法中,空闲磁盘块连接在一块儿,即一个空闲块包含指向下一个空闲块的指针。第一个磁盘块的块号存储在磁盘上的单独位置,也缓存在内存中。
碎片
这里不得不提一个叫作碎片(fragment)的概念,也称为片断。通常零散的单个数据一般称为片断。磁盘块能够进一步分为固定大小的分配单元,片断只是在驱动器上彼此不相邻的文件片断。若是你不理解这个概念就给你举个例子。好比你用 Windows 电脑建立了一个文件,你会发现这个文件能够存储在任何地方,好比存在桌面上,存在磁盘中的文件夹中或者其余地方。你能够打开文件,编辑文件,删除文件等等。你可能觉得这些都在一个地方发生,可是实际上并非,你的硬盘驱动器可能会将文件中的一部分存储在一个区域内,另外一部分存储在另一个区域,在你打开文件时,硬盘驱动器会迅速的将文件的全部部分汇总在一块儿,以便其余计算机系统能够使用它。
inode
而后在后面是一个 inode(index node),也称做索引节点。它是一个数组的结构,每一个文件有一个 inode,inode 很是重要,它说明了文件的方方面面。每一个索引节点都存储对象数据的属性和磁盘块位置
有一种简单的方法能够找到它们 ls -lai 命令。让咱们看一下根文件系统:
inode 节点主要包括了如下信息
模式/权限(保护)
全部者 ID
组 ID
文件大小
文件的硬连接数
上次访问时间
最后修改时间
文件分为两部分,索引节点和块。一旦建立后,每种类型的块数是固定的。你不能增长分区上 inode 的数量,也不能增长磁盘块的数量。
紧跟在 inode 后面的是根目录,它存放的是文件系统目录树的根部。最后,磁盘的其余部分存放了其余全部的目录和文件。
3.2
文件的实现
最重要的问题是记录各个文件分别用到了哪些磁盘块。不一样的系统采用了不一样的方法。下面咱们会探讨一下这些方式。分配背后的主要思想是有效利用文件空间和快速访问文件 ,主要有三种分配方案
连续分配
链表分配
连续分配
最简单的分配方案是把每一个文件做为一连串连续数据块存储在磁盘上。所以,在具备 1KB 块的磁盘上,将为 50 KB 文件分配 50 个连续块。
上面展现了 40 个连续的内存块。从最左侧的 0 块开始。初始状态下,尚未装载文件,所以磁盘是空的。接着,从磁盘开始处(块 0 )处开始写入占用 4 块长度的内存 A 。而后是一个占用 6 块长度的内存 B,会直接在 A 的末尾开始写。
注意每一个文件都会在新的文件块开始写,因此若是文件 A 只占用了 3 又 1/2 个块,那么最后一个块的部份内存会被浪费。在上面这幅图中,总共展现了 7 个文件,每一个文件都会从上个文件的末尾块开始写新的文件块。
连续的磁盘空间分配有两个优势。
第一,连续文件存储实现起来比较简单,只须要记住两个数字就能够:一个是第一个块的文件地址和文件的块数量。给定第一个块的编号,能够经过简单的加法找到任何其余块的编号。
第二点是读取性能比较强,能够经过一次操做从文件中读取整个文件。只须要一次寻找第一个块。后面就再也不须要寻址时间和旋转延迟,因此数据会以全带宽进入磁盘。
所以,连续的空间分配具备实现简单、高性能的特色。
不幸的是,连续空间分配也有很明显的不足。随着时间的推移,磁盘会变得很零碎。下图解释了这种现象
这里有两个文件 D 和 F 被删除了。当删除一个文件时,此文件所占用的块也随之释放,就会在磁盘空间中留下一些空闲块。磁盘并不会在这个位置挤压掉空闲块,由于这会复制空闲块以后的全部文件,可能会有上百万的块,这个量级就太大了。
刚开始的时候,这个碎片不是问题,由于每一个新文件都会在以前文件的结尾处进行写入。然而,磁盘最终会被填满,所以要么压缩磁盘、要么从新使用空闲块的空间。压缩磁盘的开销太大,所以不可行;后者会维护一个空闲列表,这个是可行的。可是这种状况又存在一个问题,为空闲块匹配合适大小的文件,须要知道该文件的最终大小。
想象一下这种设计的结果会是怎样的。用户启动 word 进程建立文档。应用程序首先会询问最终建立的文档会有多大。这个问题必须回答,不然应用程序就不会继续执行。若是空闲块的大小要比文件的大小小,程序就会终止。由于所使用的磁盘空间已经满了。那么现实生活中,有没有使用连续分配内存的介质出现呢?
CD-ROM 就普遍的使用了连续分配方式。
CD-ROM(Compact Disc Read-Only Memory)即只读光盘,也称做只读存储器。是一种在电脑上使用的光碟。这种光碟只能写入数据一次,信息将永久保存在光碟上,使用时经过光碟驱动器读出信息。
然而 DVD 的状况会更加复杂一些。原则上,一个 90 分钟 的电影可以被编码成一个独立的、大约 4.5 GB 的文件。可是文件系统所使用的 UDF(Universal Disk Format) 格式,使用一个 30 位的数来表明文件长度,从而把文件大小限制在 1 GB。因此,DVD 电影通常存储在 三、4 个连续的 1 GB 空间内。这些构成单个电影中的文件块称为扩展区(extends)。
就像咱们反复提到的,历史老是惊人的类似,许多年前,连续分配因为其简单和高性能被实际使用在磁盘文件系统中。后来因为用户不但愿在建立文件时指定文件的大小,因而放弃了这种想法。可是随着 CD-ROM 、DVD、蓝光光盘等光学介质的出现,连续分配又流行起来。从而得出结论,技术永远没有过期性,如今看似很老的技术,在将来某个阶段可能又会流行起来。
链表分配
第二种存储文件的方式是为每一个文件构造磁盘块链表,每一个文件都是磁盘块的连接列表,就像下面所示
每一个块的第一个字做为指向下一块的指针,块的其余部分存放数据。若是上面这张图你看的不是很清楚的话,能够看看整个的链表分配方案
与连续分配方案不一样,这一方法能够充分利用每一个磁盘块。除了最后一个磁盘块外,不会由于磁盘碎片而浪费存储空间。一样,在目录项中,只要存储了第一个文件块,那么其余文件块也可以被找到。
另外一方面,在链表的分配方案中,尽管顺序读取很是方便,可是随机访问却很困难(这也是数组和链表数据结构的一大区别)。
还有一个问题是,因为指针会占用一些字节,每一个磁盘块实际存储数据的字节数并再也不是 2 的整数次幂。虽然这个问题并不会很严重,可是这种方式下降了程序运行效率。许多程序都是以长度为 2 的整数次幂来读写磁盘,因为每一个块的前几个字节被指针所使用,因此要读出一个完成的块大小信息,就须要当前块的信息和下一块的信息拼凑而成,所以就引起了查找和拼接的开销。
使用内存表进行链表分配
因为连续分配和链表分配都有其不可忽视的缺点。因此提出了使用内存中的表来解决分配问题。取出每一个磁盘块的指针字,把它们放在内存的一个表中,就能够解决上述链表的两个不足之处。下面是一个例子
上图表示了链表造成的磁盘块的内容。这两个图中都有两个文件,文件 A 依次使用了磁盘块地址 四、七、 二、 十、 12,文件 B 使用了六、三、11 和 14。也就是说,文件 A 从地址 4 处开始,顺着链表走就能找到文件 A 的所有磁盘块。一样,从第 6 块开始,顺着链走到最后,也可以找到文件 B 的所有磁盘块。你会发现,这两个链表都以不属于有效磁盘编号的特殊标记(-1)结束。内存中的这种表格称为 文件分配表(File Application Table,FAT)。
使用这种组织方式,整个块均可以存放数据。进而,随机访问也容易不少。虽然仍要顺着链在内存中查找给定的偏移量,可是整个链都存放在内存中,因此不须要任何磁盘引用。与前面的方法相同,无论文件有多大,在目录项中只需记录一个整数(起始块号),按照它就能够找到文件的所有块。
这种方式存在缺点,那就是必需要把整个链表放在内存中。对于 1TB 的磁盘和 1KB 的大小的块,那么这张表须要有 10 亿项。。。每一项对应于这 10 亿个磁盘块中的一块。每项至少 3 个字节,为了提升查找速度,有时须要 4 个字节。根据系统对空间或时间的优化方案,这张表要占用 3GB 或 2.4GB 的内存。FAT 的管理方式不能较好地扩展并应用于大型磁盘中。而这正是最初 MS-DOS 文件比较实用,并仍被各个 Windows 版本所安全支持。
inode
最后一个记录各个文件分别包含哪些磁盘块的方法是给每一个文件赋予一个称为 inode(索引节点) 的数据结构,每一个文件都与一个 inode 进行关联,inode 由整数进行标识。
下面是一个简单例子的描述。
给出 inode 的长度,就可以找到文件中的全部块。
相对于在内存中使用表的方式而言,这种机制具备很大的优点。即只有在文件打开时,其 inode 才会在内存中。若是每一个 inode 须要 n 个字节,最多 k 个文件同时打开,那么 inode 占有总共打开的文件是 kn 字节。仅需预留这么多空间。
这个数组要比咱们上面描述的 FAT(文件分配表) 占用的空间小的多。缘由是用于保存全部磁盘块的连接列表的表的大小与磁盘自己成正比。若是磁盘有 n 个块,那么这个表也须要 n 项。随着磁盘空间的变大,那么该表也随之线性增加。相反,inode 须要节点中的数组,其大小和可能须要打开的最大文件个数成正比。它与磁盘是 100GB、4000GB 仍是 10000GB 无关。
inode 有一个问题是若是每一个节点都会有固定大小的磁盘地址,那么文件增加到所能容许的最大容量外会发生什么?一个解决方案是最后一个磁盘地址不指向数据块,而是指向一个包含额外磁盘块地址的地址,如上图所示。一个更高级的解决方案是:有两个或者更多包含磁盘地址的块,或者指向其余存放地址的磁盘块的磁盘块。Windows 的 NTFS 文件系统采用了类似的方法,所不一样的仅仅是大的 inode 也能够表示小的文件。
NTFS 的全称是 New Technology File System,是微软公司开发的专用系统文件,NTFS 取代 FAT(文件分配表) 和 HPFS(高性能文件系统) ,并在此基础上进一步改进。例如加强对元数据的支持,使用更高级的数据结构以提高性能、可靠性和磁盘空间利用率等。
3.3
目录的实现
文件只有打开后才可以被读取。在文件打开后,操做系统会使用用户提供的路径名来定位磁盘中的目录。目录项提供了查找文件磁盘块所须要的信息。根据系统的不一样,提供的信息也不一样,可能提供的信息是整个文件的磁盘地址,或者是第一个块的数量(两个链表方案)或 inode 的数量。不过无论用那种状况,目录系统的主要功能就是 将文件的 ASCII 码的名称映射到定位数据所需的信息上。
与此关系密切的问题是属性应该存放在哪里。每一个文件系统包含不一样的文件属性,例如文件的全部者和建立时间,须要存储的位置。一种显而易见的方法是直接把文件属性存放在目录中。有一些系统刚好是这么作的,以下。
在这种简单的设计中,目录有一个固定大小的目录项列表,每一个文件对应一项,其中包含一个固定长度的文件名,文件属性的结构体以及用以说明磁盘块位置的一个或多个磁盘地址。
对于采用 inode 的系统,会把 inode 存储在属性中而不是目录项中。在这种状况下,目录项会更短:仅仅只有文件名称和 inode 数量。这种方式以下所示
到目前为止,咱们已经假设文件具备较短的、固定长度的名字。在 MS-DOS 中,具备 1 - 8 个字符的基本名称和 1 - 3 个字符的可拓展名称。在 UNIX 版本 7 中,文件有 1 - 14 个字符,包括任何拓展。然而,几乎全部的现代操做系统都支持可变长度的扩展名。这是如何实现的呢?
最简单的方式是给予文件名一个长度限制,好比 255 个字符,而后使用上图中的设计,并为每一个文件名保留 255 个字符空间。这种处理很简单,可是浪费了大量的目录空间,由于只有不多的文件会有那么长的文件名称。因此,须要一种其余的结构来处理。
一种可选择的方式是放弃全部目录项大小相同的想法。在这种方法中,每一个目录项都包含一个固定部分,这个固定部分一般以目录项的长度开始,后面是固定格式的数据,一般包括全部者、建立时间、保护信息和其余属性。这个固定长度的头的后面是一个任意长度的实际文件名,以下图所示
上图是 SPARC 机器使用正序放置。
处理机中的一串字符存放的顺序有正序(big-endian) 和逆序(little-endian) 之分。正序存放的就是高字节在前低字节在后,而逆序存放的就是低字节在前高字节在后。
这个例子中,有三个文件,分别是 project-budget、personnel 和 foo。每一个文件名以一个特殊字符(一般是 0 )结束,用矩形中的叉进行表示。为了使每一个目录项从字的边界开始,每一个文件名被填充成整数个字,以下图所示
这个方法的缺点是当文件被移除后,就会留下一块固定长度的空间,而新添加进来的文件大小不必定和空闲空间大小一致。
这个问题与咱们上面探讨的连续磁盘文件的问题是同样的,因为整个目录在内存中,因此只有对目录进行紧凑拼接操做才可节省空间。另外一个问题是,一个目录项可能会分布在多个页上,在读取文件名时可能发生缺页中断。
处理可变长度文件名字的另一种方法是,使目录项自身具备固定长度,而将文件名放在目录末尾的堆栈中。如上图所示的这种方式。这种方法的优势是当目录项被移除后,下一个文件将可以正常匹配移除文件的空间。固然,必需要对堆进行管理,由于在处理文件名的时候也会发生缺页异常。
到目前为止的全部设计中,在须要查找文件名时,全部的方案都是线性的从头至尾对目录进行搜索。对于特别长的目录,线性搜索的效率很低。提升文件检索效率的一种方式是在每一个目录上使用哈希表(hash table),也叫作散列表。咱们假设表的大小为 n,在输入文件名时,文件名被散列在 0 和 n - 1 之间,例如,它被 n 除,并取余数。或者对构成文件名字的字求和或相似某种方法。
不管采用哪一种方式,在添加一个文件时都要对与散列值相对 应的散列表进行检查。若是没有使用过,就会将一个指向目录项的指针指向这里。文件目录项紧跟着哈希表后面。若是已经使用过,就会构造一个链表(这种构造方式是否是和 HashMap 使用的数据结构同样?),链表的表头指针存放在表项中,并经过哈希值将全部的表项相连。
查找文件的过程和添加相似,首先对文件名进行哈希处理,在哈希表中查找是否有这个哈希值,若是有的话,就检查这条链上全部的哈希项,查看文件名是否存在。若是哈希不在链上,那么文件就不在目录中。
使用哈希表的优点是查找很是迅速,缺点是管理起来很是复杂。只有在系统中会有成千上万个目录项存在时,才会考虑使用散列表做为解决方案。
另一种在大量目录中加快查找指令目录的方法是使用缓存,缓存查找的结果。在开始查找以前,会首先检查文件名是否在缓存中。若是在缓存中,那么文件就能马上定位。固然,只有在较少的文件下进行屡次查找,缓存才会发挥最大功效。
3.4
共享文件
当多个用户在同一个项目中工做时,他们一般须要共享文件。若是这个共享文件同时出如今多个用户目录下,那么他们协同工做起来就很方便。下面的这张图咱们在上面提到过,可是有一个更改的地方,就是 C 的一个文件也出如今了 B 的目录下。
若是按照如上图的这种组织方式而言,那么 B 的目录与该共享文件的联系称为 连接(link)。那么文件系统如今就是一个 有向无环图(Directed Acyclic Graph, 简称 DAG),而不是一棵树了。
在图论中,若是一个有向图从任意顶点出发没法通过若干条边回到该点,则这个图是一个有向无环图,咱们不会在此着重探讨关于图论的东西,你们能够自行 google。
将文件系统组织成为有向无环图会使得维护复杂化,但也是必需要付出的代价。
共享文件很方便,但这也会带来一些问题。若是目录中包含磁盘地址,则当连接文件时,必须把 C 目录中的磁盘地址复制到 B 目录中。若是 B 或者 C 随后又向文件中添加内容,则仅在执行追加的用户的目录中显示新写入的数据块。这种变动将会对其余用户不可见,从而破坏了共享的目的。
有两种方案能够解决这种问题。
第一种解决方案,磁盘块不列入目录中,而是会把磁盘块放在与文件自己相关联的小型数据结构中。目录将指向这个小型数据结构。这是 UNIX 中使用的方式(小型数据结构就是 inode)。
在第二种解决方案中,经过让系统创建一个类型为 LINK 的新文件,并把该文件放在 B 的目录下,使得 B 与 C 创建连接。新的文件中只包含了它所连接的文件的路径名。当 B 想要读取文件时,操做系统会检查 B 的目录下存在一个类型为 LINK 的文件,进而找到该连接的文件和路径名,而后再去读文件,这种方式称为 符号连接(symbolic linking)。
上面的每一种方法都有各自的缺点,在第一种方式中,B 连接到共享文件时,inode 记录文件的全部者为 C。创建一个连接并不改变全部关系,以下图所示。
第一开始的状况如图 a 所示,此时 C 的目录的全部者是 C ,当目录 B 连接到共享文件时,并不会改变 C 的全部者关系,只是把计数 + 1,因此此时 系统知道目前有多少个目录指向这个文件。而后 C 尝试删除这个文件,这个时候有个问题,若是 C 把文件移除并清除了 inode 的话,那么 B 会有一个目录项指向无效的节点。若是 inode 之后分配给另外一个文件,则 B 的连接指向一个错误的文件。系统经过 inode 可知文件仍在被引用,可是没有办法找到该文件的所有目录项以删除它们。指向目录的指针不能存储在 inode 中,缘由是有可能有无数个这样的目录。
因此咱们能作的就是删除 C 的目录项,可是将 inode 保留下来,并将计数设置为 1 ,如上图 c 所示。c 表示的是只有 B 有指向该文件的目录项,而该文件的前者是 C 。若是系统进行记帐操做的话,那么 C 将继续为该文件付帐直到 B 决定删除它,若是是这样的话,只有到计数变为 0 的时刻,才会删除该文件。
对于符号连接,以上问题不会发生,只有真正的文件全部者才有一个指向 inode 的指针。连接到该文件上的用户只有路径名,没有指向 inode 的指针。当文件全部者删除文件时,该文件被销毁。之后若试图经过符号连接访问该文件将会失败,由于系统不能找到该文件。删除符号连接不会影响该文件。
符号连接的问题是须要额外的开销。必须读取包含路径的文件,而后要一个部分接一个部分地扫描路径,直到找到 inode 。这些操做也许须要不少次额外的磁盘访问。此外,每一个符号连接都须要额外的 inode ,以及额外的一个磁盘块用于存储路径,虽然若是路径名很短,做为一种优化,系统能够将它存储在 inode 中。符号连接有一个优点,即只要简单地提供一个机器的网络地址以及文件在该机器上驻留的路径,就能够链接全球任何地方机器上的文件。
还有另外一个由连接带来的问题,在符号连接和其余方式中都存在。若是容许连接,文件有两个或多个路径。查找一指定目录及其子目录下的所有文件的程序将屡次定位到被连接的文件。例如,一个将某一目录及其子目录下的文件转存到磁带上的程序有可能屡次复制一个被连接的文件。进而,若是接着把磁带读入另外一台机器,除非转出程序具备智能,不然被连接的文件将被两次复制到磁盘上,而不是只是被连接起来。
3.5
日志结构文件系统
技术的改变会给当前的文件系统带来压力。这种状况下,CPU 会变得愈来愈快,磁盘会变得愈来愈大而且愈来愈便宜(但不会愈来愈快)。内存容量也是以指数级增加。可是磁盘的寻道时间(除了固态盘,由于固态盘没有寻道时间)并无得到提升。
这些因素结合起来意味着许多系统文件中出现性能瓶颈。为此,Berkeley 设计了一种全新的文件系统,试图缓解这个问题,这个文件系统就是 日志结构文件系统(Log-structured File System, LFS)。
日志结构文件系统由 Rosenblum和 Ousterhout于 90 年代初引入,旨在解决如下问题。
不断增加的系统内存
顺序 I/O 性能赛过随机 I/O 性能
现有低效率的文件系统
文件系统不支持 RAID(虚拟化)
另外一方面,当时的文件系统不管是 UNIX 仍是 FFS,都有大量的随机读写(在 FFS 中建立一个新文件至少须要 5 次随机写),所以成为整个系统的性能瓶颈。同时由于 Page cache的存在,做者认为随机读不是主要问题:随着愈来愈大的内存,大部分的读操做都能被 cache,所以 LFS 主要要解决的是减小对硬盘的随机写操做。
在这种设计中,inode 甚至具备与 UNIX 中相同的结构,可是如今它们分散在整个日志中,而不是位于磁盘上的固定位置。因此,inode 很定位。为了可以找到 inode ,维护了一个由 inode 索引的 inode map(inode 映射)。表项 i 指向磁盘中的第 i 个 inode 。这个映射保存在磁盘中,可是也保存在缓存中,所以,使用最频繁的部分大部分时间都在内存中。
日志结构文件系统主要使用四种数据结构:Inode、Inode Map、Segment、Segment Usage Table。
到目前为止,全部写入最初都缓存在内存中,而且追加在日志末尾,全部缓存的写入都按期在单个段中写入磁盘。因此,如今打开文件也就意味着用映射定位文件的索引节点。一旦 inode 被定位后,磁盘块的地址就可以被找到。全部这些块自己都将位于日志中某处的分段中。
真实状况下的磁盘容量是有限的,因此最终日志会占满整个磁盘空间,这种状况下就会出现没有新的磁盘块被写入到日志中。幸运的是,许多现有段可能具备再也不须要的块。例如,若是一个文件被覆盖了,那么它的 inode 将被指向新的块,可是旧的磁盘块仍在先前写入的段中占据着空间。
为了处理这个问题,LFS 有一个清理(clean)线程,它会循环扫描日志并对日志进行压缩。首先,经过查看日志中第一部分的信息来查看其中存在哪些索引节点和文件。它会检查当前 inode 的映射来查看 inode 是否在当前块中,是否仍在被使用。若是不是,该信息将被丢弃。若是仍然在使用,那么 inode 和块就会进入内存等待写回到下一个段中。而后原来的段被标记为空闲,以便日志能够用来存放新的数据。用这种方法,清理线程遍历日志,从后面移走旧的段,而后将有效的数据放入内存等待写到下一个段中。由此一来整个磁盘会造成一个大的环形缓冲区,写线程将新的段写在前面,而清理线程则清理后面的段。
3.6
日志文件系统
虽然日志结构系统的设计很优雅,可是因为它们和现有的文件系统不相匹配,所以尚未普遍使用。不过,从日志文件结构系统衍生出来一种新的日志系统,叫作日志文件系统,它会记录系统下一步将要作什么的日志。微软的 NTFS 文件系统、Linux 的 ext3 就使用了此日志。OS X 将日志系统做为可供选项。为了看清它是如何工做的,咱们下面讨论一个例子,好比 移除文件 ,这个操做在 UNIX 中须要三个步骤完成:
在目录中删除文件
释放 inode 到空闲 inode 池
在 Windows 中,也存在相似的步骤。不存在系统崩溃时,这些步骤的执行顺序不会带来问题。可是一旦系统崩溃,就会带来问题。假如在第一步完成后系统崩溃。inode 和文件块将不会被任何文件得到,也不会再分配;它们只存在于废物池中的某个地方,并所以减小了可利用的资源。若是崩溃发生在第二步后,那么只有磁盘块会丢失。日志文件系统保留磁盘写入期间对文件系统所作的更改的日志或日志,该日志可用于快速重建可能因为系统崩溃或断电等事件而发生的损坏。
通常文件系统崩溃后必须运行 fsck(文件系统一致性检查)实用程序。
为了让日志可以正确工做,被写入的日志操做必须是 幂等的(idempotent),它意味着只要有必要,它们就能够重复执行不少次,并不会带来破坏。像操做 更新位表并标记 inode k 或者块 n 是空闲的 能够重复执行任意次。一样地,查找一个目录而且删除全部叫 foobar 的项也是幂等的。相反,把从 inode k 新释放的块加入空闲表的末端不是幂等的,由于它们可能已经被释放并存放在那里了。
为了增长可靠性,一个文件系统能够引入数据库中 原子事务(atomic transaction) 的概念。使用这个概念,一组动做能够被界定在开始事务和结束事务操做之间。这样,文件系统就会知道它必须完成全部的动做,要么就一个不作。
3.7
虚拟文件系统
即便在同一台计算机上或者在同一个操做系统下,都会使用不少不一样的文件系统。Windows 中的主要文件系统是 NTFS 文件系统,但不是说 Windows 只有 NTFS 操做系统,它还有一些其余的例如旧的 FAT -32 或FAT -16 驱动器或分区,其中包含仍须要的数据,闪存驱动器,旧的 CD-ROM 或 DVD(每一个都有本身的独特文件系统)。Windows 经过指定不一样的盘符来处理这些不一样的文件系统,好比 C:,D: 等。盘符能够显示存在也能够隐式存在,若是你想找指定位置的文件,那么盘符是显示存在;若是当一个进程打开一个文件时,此时盘符是隐式存在,因此 Windows 知道向哪一个文件系统传递请求。
相比之下,UNIX 采用了一种不一样的方式,即 UNIX 把多种文件系统整合到一个统一的结构中。一个 Linux 系统能够使用 ext2 做为根文件系统,ext3 分区装载在 /usr 下,另外一块采用 Reiser FS 文件系统的硬盘装载到 /home下,以及一个 ISO 9660 的 CD - ROM 临时装载到 /mnt 下。从用户的观点来看,只有一个文件系统层级,可是事实上它们是由多个文件系统组合而成,对于用户和进程是不可见的。
UNIX 操做系统使用一种 虚拟文件系统(Virtual File System, VFS) 来尝试将多种文件系统构成一个有序的结构。关键的思想是抽象出全部文件系统都共有的部分,并将这部分代码放在一层,这一层再调用具体文件系统来管理数据。下面是一个 VFS 的系统结构
仍是那句经典的话,在计算机世界中,任何解决不了的问题均可以加个代理来解决。全部和文件相关的系统调用在最初的处理上都指向虚拟文件系统。这些来自用户进程的调用,都是标准的 POSIX 系统调用,好比 open、read、write 和 seek 等。VFS 对用户进程有一个 上层 接口,这个接口就是著名的 POSIX 接口。
VFS 也有一个对于实际文件的 下层 接口,就是上图中标记为 VFS 的接口。这个接口包含许多功能调用,这样 VFS 能够使每个文件系统完成任务。所以,要建立一个能够与 VFS 一块儿使用的新文件系统,新文件系统的设计者必须确保它提供了 VFS 要求的功能。一个明显的例子是从磁盘读取特定的块,而后将其放入文件系统的缓冲区高速缓存中,而后返回指向该块的指针的函数。所以,VFS 具备两个不一样的接口:上一个到用户进程,下一个到具体文件系统。
当系统启动时,根文件系统在 VFS 中注册。另外,当装载其余文件时,无论在启动时仍是在操做过程当中,它们也必须在 VFS 中注册。当一个文件系统注册时,根文件系统注册到 VFS。另外,在引导时或操做期间挂载其余文件系统时,它们也必须向 VFS 注册。当文件系统注册时,其基本做用是提供 VFS 所需功能的地址列表、调用向量表、或者 VFS 对象。所以一旦文件系统注册到 VFS,它就知道从哪里开始读取数据块。
装载文件系统后就能够使用它了。好比,若是一个文件系统装载到 /usr 而且一个进程调用它:
1open("/usr/include/unistd.h",O_RDONLY)
当解析路径时, VFS 看到新的文件系统被挂载到 /usr,而且经过搜索已经装载文件系统的超级块来肯定它的超块。而后它找到它所转载的文件的根目录,在那里查找路径 include/unistd.h。而后 VFS 建立一个 vnode 并调用实际文件系统,以返回全部的在文件 inode 中的信息。这个信息和其余信息一块儿复制到 vnode (内存中)。而这些其余信息中最重要的是指向包含调用 vnode 操做的函数表的指针,好比 read、write 和 close 等。
当 vnode 被建立后,为了进程调用,VFS 在文件描述符表中建立一个表项,并将它指向新的 vnode,最后,VFS 向调用者返回文件描述符,因此调用者能够用它去 read、write 或者 close 文件。
当进程用文件描述符进行一个读操做时,VFS 经过进程表和文件描述符肯定 vnode 的位置,并跟随指针指向函数表,这样就调用了处理 read 函数,运行在实际系统中的代码并获得所请求的块。VFS 不知道请求时来源于本地硬盘、仍是来源于网络中的远程文件系统、CD-ROM 、USB 或者其余介质,全部相关的数据结构以下图所示
从调用者进程号和文件描述符开始,进而是 vnode,读函数指针,而后是对实际文件系统的访问函数定位。
4
文件系统的管理和优化
可以使文件系统工做是一回事,可以使文件系统高效、稳定的工做是另外一回事,下面咱们就来探讨一下文件系统的管理和优化。
4.1
磁盘空间管理
文件一般存在磁盘中,因此如何管理磁盘空间是一个操做系统的设计者须要考虑的问题。在文件上进行存有两种策略:分配 n 个字节的连续磁盘空间;或者把文件拆分红多个并不必定连续的块。在存储管理系统中,主要有分段管理和 分页管理 两种方式。
正如咱们所看到的,按连续字节序列存储文件有一个明显的问题,当文件扩大时,有可能须要在磁盘上移动文件。内存中分段也有一样的问题。不一样的是,相对于把文件从磁盘的一个位置移动到另外一个位置,内存中段的移动操做要快不少。所以,几乎全部的文件系统都把文件分割成固定大小的块来存储。
块大小
一旦把文件分为固定大小的块来存储,就会出现问题,块的大小是多少?按照磁盘组织方式,扇区、磁道和柱面显然均可以做为分配单位。在分页系统中,分页大小也是主要因素。
拥有大的块尺寸意味着每一个文件,甚至 1 字节文件,都要占用一个柱面空间,也就是说小文件浪费了大量的磁盘空间。另外一方面,小块意味着大部分文件将会跨越多个块,所以须要屡次搜索和旋转延迟才能读取它们,从而下降了性能。所以,若是分配的块太大会浪费空间;分配的块过小会浪费时间。
记录空闲块
一旦指定了块大小,下一个问题就是怎样跟踪空闲块。有两种方法被普遍采用,以下图所示
第一种方法是采用磁盘块链表,链表的每一个块中包含很可能多的空闲磁盘块号。对于 1 KB 的块和 32 位的磁盘块号,空闲表中每一个块包含有 255 个空闲的块号。考虑 1 TB 的硬盘,拥有大概十亿个磁盘块。为了存储所有地址块号,若是每块能够保存 255 个块号,则须要将近 400 万个块。一般,空闲块用于保存空闲列表,所以存储基本上是空闲的。
另外一种空闲空间管理的技术是位图(bitmap),n 个块的磁盘须要 n 位位图。在位图中,空闲块用 1 表示,已分配的块用 0 表示。对于 1 TB 硬盘的例子,须要 10 亿位表示,即须要大约 130 000 个 1 KB 块存储。很明显,和 32 位链表模型相比,位图须要的空间更少,由于每一个块使用 1 位。只有当磁盘快满的时候,链表须要的块才会比位图少。
若是空闲块是长期连续的话,那么空闲列表能够改为记录连续分块而不是单个的块。每一个块都会使用 8 位、16 位、32 位的计数来与每一个块相联,来记录连续空闲块的数量。最好的状况是一个空闲块能够用两个数字来表示:第一个空闲块的地址和空闲块的计数。另外一方面,若是磁盘严重碎片化,那么跟踪连续分块要比跟踪单个分块运行效率低,由于不只要存储地址,还要存储数量。
这种状况说明了一个操做系统设计者常常遇到的一个问题。有许多数据结构和算法能够用来解决问题,可是选择一个最好的方案须要数据的支持,而这些数据是设计者没法预先拥有的。只有在系统部署完毕真正使用使用后才会得到。
如今,回到空闲链表的方法,只有一个指针块保存在内存中。建立文件时,所须要的块从指针块中取出。当它用完时,将从磁盘中读取一个新的指针块。相似地,删除文件时,文件的块将被释放并添加到主存中的指针块中。当块被填满时,写回磁盘。
在某些特定的状况下,这个方法致使了没必要要的磁盘 IO,以下图所示
上面内存中的指针块仅有两个空闲块,若是释放了一个含有三个磁盘块的文件,那么该指针块就会溢出,必须将其写入磁盘,那么就会产生以下图的这种状况。
若是如今写入含有三个块的文件,已满的指针不得再也不次读入,这将会回到上图 a 中的状况。若是有三个块的文件只是做为临时文件被写入,在释放它时,须要进行另外一次磁盘写操做以将完整的指针块写回到磁盘。简而言之,当指针块几乎为空时,一系列短暂的临时文件可能会致使大量磁盘 I/O。
避免大部分磁盘 I/O 的另外一种方法是拆分完整的指针块。这样,当释放三个块时,变化再也不是从 a - b,而是从 a - c,以下图所示
如今,系统能够处理一系列临时文件,而不须要进行任何磁盘 I/O。若是内存中指针块满了,就写入磁盘,半满的指针块从磁盘中读入。这里的思想是:要保持磁盘上的大多数指针块为满的状态(减小磁盘的使用),可是在内存中保留了一个半满的指针块。这样,就能够既处理文件的建立又同时能够处理文件的删除操做,而不会为空闲表进行磁盘 I/O。
对于位图,会在内存中只保留一个块,只有在该块满了或空了的情形下,才到磁盘上取另外一个块。经过在位图的单一块上进行全部的分配操做,磁盘块会紧密的汇集在一块儿,从而减小了磁盘臂的移动。因为位图是一种固定大小的数据结构,因此若是内核是分页的,就能够把位图放在虚拟内存中,在须要时将位图的页面调入。
4.2
磁盘配额
为了防止一些用户占用太多的磁盘空间,多用户操做一般提供一种磁盘配额(enforcing disk quotas)的机制。系统管理员为每一个用户分配最大的文件和块分配,而且操做系统确保用户不会超过其配额。咱们下面会谈到这一机制。
在用户打开一个文件时,操做系统会找到文件属性和磁盘地址,并把它们送入内存中的打开文件表。其中一个属性告诉文件全部者是谁。任何有关文件的增长都会记到全部者的配额中。
第二张表包含了每一个用户当前打开文件的配额记录,即便是其余人打开该文件也同样。如上图所示,该表的内容是从被打开文件的全部者的磁盘配额文件中提取出来的。当全部文件关闭时,该记录被写回配额文件。
当在打开文件表中创建一新表项时,会产生一个指向全部者配额记录的指针。每次向文件中添加一个块时,文件全部者所用数据块的总数也随之增长,并会同时增长硬限制和软限制的检查。能够超出软限制,但硬限制不能够超出。当已达到硬限制时,再往文件中添加内容将引起错误。一样,对文件数目也存在相似的检查。
什么是硬限制和软限制?硬限制是软限制的上限。软限制是为会话或进程实际执行的限制。这容许管理员(或用户)将硬限制设置为容许它们但愿容许的最大使用上限。而后,其余用户和进程能够根据须要使用软限制将其资源使用量自限制到更低的上限。
当一个用户尝试登录,系统将检查配额文件以查看用户是否超出了文件数量或磁盘块数量的软限制。若是违反了任一限制,则会显示警告,保存的警告计数减 1,若是警告计数为 0 ,表示用户屡次忽略该警告,于是将不容许该用户登陆。要想再获得登陆的许可,就必须与系统管理员协商。
若是用户在退出系统时消除所超过的部分,他们就能够再一次终端会话期间超过其软限制,但不管什么状况下都不会超过硬限制。
4.3
文件系统备份
文件系统的毁坏要比计算机的损坏严重不少。不管是硬件仍是软件的故障,只要计算机文件系统被破坏,要恢复起来都是及其困难的,甚至是不可能的。由于文件系统没法抵御破坏,于是咱们要在文件系统在被破坏以前作好数据备份,可是备份也不是那么容易,下面咱们就来探讨备份的过程。
许多人认为为文件系统作备份是不值得的,而且很浪费时间,直到有一天他们的磁盘坏了,他们才意识到事情的严重性。相对来讲,公司在这方面作的就很到位。磁带备份主要要处理好如下两个潜在问题中的一个
这个问题主要是因为外部条件的缘由形成的,好比磁盘破裂,水灾火灾等。
第二个问题一般是因为用户意外的删除了本来须要还原的文件。这种状况发生的很频繁,使得 Windows 的设计者们针对 删除 命令专门设计了特殊目录,这就是 回收站(recycle bin),也就是说,在删除文件的时候,文件自己并不真正从磁盘上消失,而是被放置到这个特殊目录下,等之后须要的时候能够还原回去。文件备份更主要是指这种状况,可以容许几天以前,几周以前的文件从原来备份的磁盘进行还原。
作文件备份很耗费时间并且也很浪费空间,这会引发下面几个问题。首先,是要备份整个文件仍是仅备份一部分呢?通常来讲,只是备份特定目录及其下的所有文件,而不是备份整个文件系统。
其次,对上次未修改过的文件再进行备份是一种浪费,于是产生了一种增量转储(incremental dumps) 的思想。最简单的增量转储的形式就是周期性的作全面的备份,而天天只对增量转储完成后发生变化的文件作单个备份。
周期性:好比一周或者一个月
稍微好一点的方式是只备份最近一次转储以来更改过的文件。固然,这种作法极大的缩减了转储时间,但恢复起来却更复杂,由于最近的全面转储先要所有恢复,随后按逆序进行增量转储。为了方便恢复,人们每每使用更复杂的转储模式。
第三,既然待转储的每每是海量数据,那么在将其写入磁带以前对文件进行压缩就颇有必要。可是,若是在备份过程当中出现了文件损坏的状况,就会致使破坏压缩算法,从而使整个磁带没法读取。因此在备份前是否进行文件压缩需慎重考虑。
第四,对正在使用的文件系统作备份是很难的。若是在转储过程当中要添加,删除和修改文件和目录,则转储结果可能不一致。所以,由于转储过程当中须要花费数个小时的时间,因此有必要在晚上将系统脱机进行备份,然而这种方式的接受程度并不高。因此,人们修改了转储算法,记下文件系统的瞬时快照,即复制关键的数据结构,而后须要把未来对文件和目录所作的修改复制到块中,而不是处处更新他们。
磁盘转储到备份磁盘上有两种方案:物理转储和逻辑转储。物理转储(physical dump) 是从磁盘的 0 块开始,依次将全部磁盘块按照顺序写入到输出磁盘,并在复制最后一个磁盘时中止。这种程序的万无一失性是其余程序所不具有的。
第二个须要考虑的是坏块的转储。制造大型磁盘而没有瑕疵是不可能的,因此也会存在一些坏块(bad blocks)。有时进行低级格式化后,坏块会被检测出来并进行标记,这种状况的解决办法是用磁盘末尾的一些空闲块所替换。
然而,一些块在格式化后会变坏,在这种状况下操做系统能够检测到它们。一般状况下,它能够经过建立一个由全部坏块组成的文件来解决问题,确保它们不会出如今空闲池中而且永远不会被分配。那么此文件是彻底不可读的。若是磁盘控制器将全部的坏块从新映射,物理转储仍是可以正常工做的。
Windows 系统有分页文件(paging files) 和 休眠文件(hibernation files) 。它们在文件还原时不发挥做用,同时也不该该在第一时间进行备份。
物理转储和逻辑转储
物理转储的主要优势是简单、极为快速(基本上是以磁盘的速度运行),缺点是全量备份,不能跳过指定目录,也不能增量转储,也不能恢复我的文件的请求。所以句大多数状况下不会使用物理转储,而使用逻辑转储。
逻辑转储(logical dump)从一个或几个指定的目录开始,递归转储自指定日期开始后更改的文件和目录。所以,在逻辑转储中,转储磁盘上有一系列通过仔细识别的目录和文件,这使得根据请求轻松还原特定文件或目录。
既然逻辑转储是最经常使用的方式,那么下面就让咱们研究一下逻辑转储的通用算法。此算法在 UNIX 系统上广为使用,以下图所示
待转储的文件系统,其中方框表明目录,圆圈表明文件。黄色的项目表是自上次转储以来修改过。每一个目录和文件都被标上其 inode 号。
此算法会转储位于修改文件或目录路径上的全部目录(也包括未修改的目录),缘由有两个。第一是可以在不一样电脑的文件系统中恢复转储的文件。经过这种方式,转储和从新存储的程序可以用来在两个电脑之间传输整个文件系统。第二个缘由是可以对单个文件进行增量恢复。
逻辑转储算法须要维持一个 inode 为索引的位图(bitmap),每一个 inode 包含了几位。随着算法的进行,位图中的这些位会被设置或清除。算法的执行分红四个阶段。第一阶段从起始目录(本例为根目录)开始检查其中全部的目录项。对每个修改过的文件,该算法将在位图中标记其 inode。算法还会标记并递归检查每个目录(无论是否修改过)。
在第一阶段结束时,全部修改过的文件和所有目录都在位图中标记了,以下图所示
理论上来讲,第二阶段再次递归遍历目录树,并去掉目录树中任何不包含被修改过的文件或目录的标记。本阶段执行的结果以下
注意,inode 编号为 十、十一、1四、2七、29 和 30 的目录已经被去掉了标记,由于它们所包含的内容没有修改。它们也不会转储。相反,inode 编号为 5 和 6 的目录自己尽管没有被修改过也要被转储,由于在新的机器上恢复当日的修改时须要这些信息。为了提升算法效率,能够将这两阶段的目录树遍历合二为一。
如今已经知道了哪些目录和文件必须被转储了,这就是上图 b 中标记的内容,第三阶段算法将以节点号为序,扫描这些 inode 并转储全部标记为需转储的目录,以下图所示
为了进行恢复,每一个被转储的目录都用目录的属性(全部者、时间)做为前缀。
最后,在第四阶段,上图中被标记的文件也被转储,一样,由其文件属性做为前缀。至此,转储结束。
从转储磁盘上还原文件系统很是简单。一开始,须要在磁盘上建立空文件系统。而后恢复最近一次的完整转储。因为磁带上最早出现目录,因此首先恢复目录,给出文件系统的框架(skeleton),而后恢复文件系统自己。在完整存储以后是第一次增量存储,而后是第二次重复这一过程,以此类推。
尽管逻辑存储十分简单,可是也会有一些棘手的问题。首先,既然空闲块列表并非一个文件,那么在全部被转储的文件恢复完毕以后,就须要从零开始从新构造。
另一个问题是关于连接。若是文件连接了两个或者多个目录,而文件只能还原一次,那么而且全部指向该文件的目录都必须还原。
还有一个问题是,UNIX 文件实际上包含了许多 空洞(holes)。打开文件,写几个字节,而后找到文件中偏移了必定距离的地址,又写入更多的字节,这么作是合法的。但二者之间的这些块并不属于文件自己,从而也不该该在其上进行文件转储和恢复。
最后,不管属于哪个目录,特殊文件,命名管道以及相似的文件都不该该被转储。
4.4
文件系统的一致性
影响可靠性的一个因素是文件系统的一致性。许多文件系统读取磁盘块、修改磁盘块、再把它们写回磁盘。若是系统在全部块写入以前崩溃,文件系统就会处于一种不一致(inconsistent)的状态。若是某些还没有写回的块是索引节点块,目录块或包含空闲列表的块,则此问题是很严重的。
为了处理文件系统一致性问题,大部分计算机都会有应用程序来检查文件系统的一致性。例如,UNIX 有 fsck;Windows 有 sfc,每当引导系统时(尤为是在崩溃后),均可以运行该程序。
能够进行两种一致性检查:块的一致性检查和文件的一致性检查。为了检查块的一致性,应用程序会创建两张表,每一个包含一个计数器的块,最初设置为 0 。第一个表中的计数器跟踪该块在文件中出现的次数,第二张表中的计数器记录每一个块在空闲列表、空闲位图中出现的频率。
而后检验程序使用原始设备读取全部的 inode,忽略文件的结构,只返回从零开始的全部磁盘块。从 inode 开始,很容易找到文件中的块数量。每当读取一个块时,该块在第一个表中的计数器 + 1,应用程序会检查空闲块或者位图来找到没有使用的块。空闲列表中块的每次出现都会致使其在第二表中的计数器增长。
若是文件系统一致,则每个块或者在第一个表计数器为 1,或者在第二个表计数器中为 1,以下图所示
可是当系统崩溃后,这两张表可能以下所示
其中,磁盘块 2 没有出如今任何一张表中,这称为 块丢失(missing block)。尽管块丢失不会形成实际的损害,但它的确浪费了磁盘空间,减小了磁盘容量。块丢失的问题很容易解决,文件系统检验程序把他们加到空闲表中便可。
有可能出现的另一种状况以下所示
其中,块 4 在空闲表中出现了 2 次。这种解决方法也很简单,只要从新创建空闲表便可。
最糟糕的状况是在两个或者多个文件中出现同一个数据块,以下所示
好比上图的磁盘块 5,若是其中一个文件被删除,块 5 会被添加到空闲表中,致使一个块同时处于使用和空闲的两种状态。若是删除这两个文件,那么在空闲表中这个磁盘块会出现两次。
文件系统检验程序采起的处理方法是,先分配一磁盘块,把块 5 中的内容复制到空闲块中,而后把它插入到其中一个文件中。这样文件的内容未改变,虽然这些内容能够确定是不对的,但至少保证了文件的一致性。这一错误应该报告给用户,由用户检查受检状况。
除了检查每一个磁盘块计数的正确性以外,文件系统还会检查目录系统。这时候会用到一张计数器表,但这时是一个文件(而不是一个块)对应于一个计数器。程序从根目录开始检验,沿着目录树向下查找,检查文件系统的每一个目录。对每一个目录中的文件,使其计数 + 1。
注意,因为存在硬链接,一个文件可能出如今两个或多个目录中。而遇到符号连接是不计数的,不会对目标文件的计数器 + 1。
在检验程序完成后,会获得一张由 inode 索引的表,说明每一个文件和目录的包含关系。检验程序会将这些数字与存储在文件 inode 中的连接数目作对比。若是 inode 节点的连接计数大户目录项个数,这时即便全部文件从目录中删除,这个计数仍然不是 0 ,inode 不会被删除。这种错误不严重,却由于存在不属于任何目录的文件而浪费了磁盘空间。
另外一种错误则是潜在的风险。若是同一个文件连接两个目录项,可是 inode 连接计数只为 1,若是删除了任何一个目录项,对应 inode 连接计数变为 0。当 inode 计数为 0 时,文件系统标志 inode 为 未使用,并释放所有的块。这会致使其中一个目录指向一未使用的 inode,而颇有可能其块立刻就被分配给其余文件。
4.5
文件系统性能
访问磁盘的效率要比内存满的多,是时候又祭出这张图了
从内存读一个 32 位字大概是 10ns,从硬盘上读的速率大概是 100MB/S,对每一个 32 位字来讲,效率会慢了四倍,另外,还要加上 5 - 10 ms 的寻道时间等其余损耗,若是只访问一个字,内存要比磁盘快百万数量级。因此磁盘优化是颇有必要的,下面咱们会讨论几种优化方式
高速缓存
最经常使用的减小磁盘访问次数的技术是使用 块高速缓存(block cache) 或者 缓冲区高速缓存(buffer cache)。高速缓存指的是一系列的块,它们在逻辑上属于磁盘,但实际上基于性能的考虑被保存在内存中。
管理高速缓存有不一样的算法,经常使用的算法是:检查所有的读请求,查看在高速缓存中是否有所须要的块。若是存在,可执行读操做而无须访问磁盘。若是检查块再也不高速缓存中,那么首先把它读入高速缓存,再复制到所需的地方。以后,对同一个块的请求都经过高速缓存来完成。
高速缓存的操做以下图所示
因为在高速缓存中有许多块,因此须要某种方法快速肯定所需的块是否存在。经常使用方法是将设备和磁盘地址进行散列操做,而后,在散列表中查找结果。具备相同散列值的块在一个链表中链接在一块儿(这个数据结构是否是很像 HashMap?),这样就能够沿着冲突链查找其余块。
若是高速缓存已满,此时须要调入新的块,则要把原来的某一块调出高速缓存,若是要调出的块在上次调入后已经被修改过,则须要把它写回磁盘。这种状况与分页很是类似,全部经常使用的页面置换算法咱们以前已经介绍过,若是有不熟悉的小伙伴能够参考 好比 FIFO 算法、第二次机会算法、LRU 算法、时钟算法、老化算法等。它们都适用于高速缓存。
块提早读
第二个明显提升文件系统的性能是,在须要用到块以前,试图提早将其写入高速缓存,从而提升命中率。许多文件都是顺序读取。若是请求文件系统在某个文件中生成块 k,文件系统执行相关操做而且在完成以后,会检查高速缓存,以便肯定块 k + 1 是否已经在高速缓存。若是不在,文件系统会为 k + 1 安排一个预读取,由于文件但愿在用到该块的时候可以直接从高速缓存中读取。
固然,块提早读取策略只适用于实际顺序读取的文件。对随机访问的文件,提早读丝绝不起做用。甚至还会形成阻碍。
减小磁盘臂运动
高速缓存和块提早读并非提升文件系统性能的惟一方法。另外一种重要的技术是把有可能顺序访问的块放在一块儿,固然最好是在同一个柱面上,从而减小磁盘臂的移动次数。当写一个输出文件时,文件系统就必须按照要求一次一次地分配磁盘块。若是用位图来记录空闲块,而且整个位图在内存中,那么选择与前一块最近的空闲块是很容易的。若是用空闲表,而且链表的一部分存在磁盘上,要分配紧邻的空闲块就会困难不少。
不过,即便采用空闲表,也能够使用 块簇 技术。即不用块而用连续块簇来跟踪磁盘存储区。若是一个扇区有 512 个字节,有可能系统采用 1 KB 的块(2 个扇区),但却按每 2 块(4 个扇区)一个单位来分配磁盘存储区。这和 2 KB 的磁盘块并不相同,由于在高速缓存中它仍然使用 1 KB 的块,磁盘与内存数据之间传送也是以 1 KB 进行,但在一个空闲的系统上顺序读取这些文件,寻道的次数能够减小一半,从而使文件系统的性能大大改善。若考虑旋转定位则能够获得这类方法的变体。在分配块时,系统尽可能把一个文件中的连续块存放在同一个柱面上。
在使用 inode 或任何相似 inode 的系统中,另外一个性能瓶颈是,读取一个很短的文件也须要两次磁盘访问:一次是访问 inode,一次是访问块。一般状况下,inode 的放置以下图所示
其中,所有 inode 放在靠近磁盘开始位置,因此 inode 和它所指向的块之间的平均距离是柱面组的一半,这将会须要较长时间的寻道时间。
一个简单的改进方法是,在磁盘中部而不是开始处存放 inode ,此时,在 inode 和第一个块之间的寻道时间减为原来的一半。另外一种作法是:将磁盘分红多个柱面组,每一个柱面组有本身的 inode,数据块和空闲表,如上图 b 所示。
固然,只有在磁盘中装有磁盘臂的状况下,讨论寻道时间和旋转时间才是有意义的。如今愈来愈多的电脑使用 固态硬盘(SSD),对于这些硬盘,因为采用了和闪存一样的制造技术,使得随机访问和顺序访问在传输速度上已经较为相近,传统硬盘的许多问题就消失了。可是也引起了新的问题。
磁盘碎片整理
在初始安装操做系统后,文件就会被不断的建立和清除,因而磁盘会产生不少的碎片,在建立一个文件时,它使用的块会散布在整个磁盘上,下降性能。删除文件后,回收磁盘块,可能会形成空穴。
磁盘性能能够经过以下方式恢复:移动文件使它们相互挨着,并把全部的至少是大部分的空闲空间放在一个或多个大的连续区域内。Windows 有一个程序 defrag 就是作这个事儿的。Windows 用户会常用它,SSD 除外。
磁盘碎片整理程序会在让文件系统上很好地运行。Linux 文件系统(特别是 ext2 和 ext3)因为其选择磁盘块的方式,在磁盘碎片整理上通常不会像 Windows 同样困难,所以不多须要手动的磁盘碎片整理。并且,固态硬盘并不受磁盘碎片的影响,事实上,在固态硬盘上作磁盘碎片整理反却是画蛇添足,不只没有提升性能,反而磨损了固态硬盘。因此碎片整理只会缩短固态硬盘的寿命。