(一)文件 文件的基本概念:文件元数据和索引结点(inode) 文件的操作:建立,删除,打开,关闭,读,写 文件的保护;文件的逻辑结构;文件的物理结构
(二)目录 目录的基本概念;树形目录;目录的操作;硬链接和软链接
(三)文件系统 文件系统的全局结构(layout):文件系统在外存中的结构,文件系统在内存中的结构外存空闲空间管理办法;虚拟文件系统;文件系统挂载(mounting)
本章内容较为具体,要注意对概念的理解。重点掌握文件系统的结构及其实现、文件分配和空闲空间管理等。要掌握文件系统的文件控制块、物理分配方法、索引结构、树形目录结构、文件共享原理、文件系统的布局、虚拟文件系统原理等。这些都是统考真题易考查的内容。
在学习本节时,请读者思考以下问题:
本节内容较为抽象,要注意区分文件的逻辑结构和物理结构。在学习过程中,可尝试以上面的两个问题为线索,构建整个文件系统的概念。在前面的学习中,曾经提醒过读者不要忽略对基本概念的理解。从历年的情况来看,大部分同学对进程管理、内存管理有较好的掌握,但对于文件管理及后面的 I/O 管理,往往理解的不太深入。在考试中,即使面对一些基本问题也容易失分,这十分可惜。主要原因还是对概念的理解不够全面和透彻,希望读者能够关注这个问题。
文件(File)是以硬盘为载体的存储在计算机上的信息集合,文件可以是文本文档、图片、程序等。在系统运行时,计算机以进程为基本单位进行资源的调度和分配;而在用户进行的输入、输出中,则以文件为基本单位。大多数应用程序的输入都是通过文件来实现的,其输出也都保存在文件中,以便信息的长期存储及将来的访问。当用户将文件用于程序的输入、输出时,还希望可以访问、修改和保存文件等,实现对文件的维护管理,这就需要系统提供一个文件管理系统,操作系统中的文件系统(File System)就是用于实现用户的这些管理要求的。
要清晰地理解文件的概念,就要了解文件究竟由哪些东西组成。
首先,文件中肯定包括一块存储空间,更准确地说,是存储空间中的数据;其次,由于操作系统要管理成千上万的数据,因此必定需要对这些数据进行划分,然后贴上“标签”,以便于分类和索引,所以文件必定包含分类和索引的信息;最后,不同的用户拥有对数据的不同访问权限,因此文件中一定包含一些关于访问权限的信息。
再举一个直观的例子“图书馆管理图书”来类比文件。可以认为,计算机中的一个文件相当于图书馆中的一本书,操作系统管理文件,相当于图书管理员管理图书馆中的书。
首先,一本书的主体一定是书中的内容,相当于文件中的数据;其次,不同类别的书需要放在不同的书库,然后加上编号,再把编号登记在图书管理系统中,方便读者查阅,相当于文件的分类和查找;最后,有些已经绝版或价格比较高的外文书籍,只能借给VIP会员或权限比较高的其他读者,而有些普通的书籍可供任何人借阅,这就是文件中的访问权限。所举例子与实际操作系统中的情形并不等价。但对于某些关键的属性,图书馆管理图书和操作系统管理文件的思想却有相一致的地方,因此通过这种类比可使初学者快速认识陌生的概念。
从用户的角度看,文件系统是操作系统的重要部分之一。用户关心的是如何命名、分类和查找文件,如何保证文件数据的安全性及对文件可以进行哪些操作等。而对于其中的细节,如文件如何存储在辅存上、如何管理文件辅存区域等方面关心甚少。
文件系统提供了与二级存储相关的资源的抽象,让用户能在不了解文件的各种属性、文件存储介质的特征及文件在存储介质上的具体位置等情况下,方便快捷地使用文件。用户通过文件系统建立文件,用于应用程序的输入、输出,对资源进行管理。
首先了解文件的结构,我们通过自底向上的方式来定义。
数据项。是文件系统中最低级的数据组织形式,可分为以下两种类型;
记录。是一组相关的数据项的集合,用于描述一个对象在某方面的属性。
文件。是指由创建者所定义的、具有文件名的一组相关元素的集合;可分为有结构文件和无结构文件两种。在有结构的文件中,文件由若干个相似的记录组成,如一个班的学生记录;而无结构文件则被视为一个字符流,比如一个二进制文件或字符文件。
虽然上面给出了结构化的表述,但实际上关于文件并无严格的定义。在操作系统中,通常将程序和数据组织成文件。文件可以是数字、字符或二进制代码,基本访问单元可以是字节或记录。文件可以长期存储在硬盘中,允许可控制的进程间共享访问,能够被组织成复杂的结构。
与进程管理一样,为便于文件管理,在操作系统中引入了文件控制块的数据结构。
除了文件数据,操作系统还会保存与文件相关的信息,如所有者、创建时间等,这些附加信息称为文件属性或文件元数据。文件属性在不同系统中差别很大,但通常都包括如下属性。
操作系统通过文件控制块(FCB)来维护文件元数据。
文件控制块(FCB)是用来存放控制文件需要的各种信息的数据结构,以实现“按名存取”。FCB 的有序集合称为文件目录,一个FCB 就是一个文件目录项。图 4.1是一个典型的FCB。为了创建一个新文件,系统将分配一个 FCB 并存放在文件目录中,称为目录项。
FCB主要包含以下信息:
一个文件目录也被视为一个文件,称为目录文件。
文件目录通常存放在磁盘上,当文件很多时,文件目录会占用大量的盘块。在查找目录的过程中,要先将存放目录文件的第一个盘块中的目录调入内存,然后用给定的文件名逐一比较,若未找到指定文件,就还需要不断地将下一盘块中的目录项调入内存,逐一比较。我们发现,在检索目录的过程中,只用到了文件名,仅当找到一个目录项(其中的文件名与要查找的文件名匹配)时,才需从该目录项中读出该文件的物理地址。也就是说,在检索目录时,文件的其他描述信息不会用到,也不需要调入内存。因此,有的系统(如UNIX,图4.2)便采用了文件名和文件描述信息分开的方法,使文件描述信息单独形成一个称为索引结点的数据结构,简称 i 结点(inode)。在文件目录中的每个目录项仅由文件名和指向该文件所对应的 i 结点的指针构成。
假设一个FCB为 64B,盘块大小是 1KB,则每个盘块中可以存放 16 个 FCB(FCB 必须连续存放),若一个文件目录共有 640 个 FCB,则查找文件平均需要启动磁盘 20 次。而在 UNIX 系统中,一个目录仅占 16B,其中 14B 是文件名,2B 是 i 节点指针。在 1KB 的盘块中可存放 64 个目录项。这样,可使查找文件的平均启动磁盘次数减少到原来的1/4,大大节省了系统开销。
它是指存放在磁盘上的索引结点。每个文件有一个唯一的磁盘索引结点,主要包括以下内容:
iaddr(0)~iaddr(12)
,它们以直接或间接方式给出数据文件所在盘块的编号。它是指存放在内存中的索引结点。当文件被打开时,要将磁盘索引结点复制到内存的索引结点中,便于以后使用。在内存索引结点中增加了以下内容:
FCB 或索引结点相当于图书馆中图书的索书号,我们可以在图书馆网站上找到图书的索书号,然后根据索书号找到想要的书本。
文件属于抽象数据类型。为了正确地定义文件,需要考虑可以对文件执行的操作。操作系统提供系统调用,它对文件进行创建、写、读、重定位、删除和截断等操作。
这6个基本操作可以组合起来执行其他文件操作。例如,一个文件的复制,可以创建新文件、从旧文件读出并写入新文件。
当用户对一个文件实施操作时,每次都要从检索目录开始。为了避免多次重复地检索目录,大多数操作系统要求, 在文件使用之前通过系统调用open被显式地打开。操作系统维护一个包含所有打开文件信息的表(打开文件表)。所谓“打开”,是指调用 open 根据文件名搜索目录,将指明文件的属性(包括该文件在外存上的物理位置),从外存复制到内存打开文件表的一个表目中,并将该表目的编号(也称索引)返回给用户。当用户再次向系统发出文件操作请求时,可通过索引在打开文件表中查到文件信息,从而节省再次搜索目录的开销。 当文件不再使用时,可利用系统调用 close 关闭它,操作系统将会从打开文件表中删除这一条目。
在多个不同进程可以同时打开文件的操作系统中,通常采用两级表:整个系统表和每个进程表。 整个系统的打开文件表包含FCB的副本及其他信息。 每个进程的打开文件表根据它打开的所有文件,包含指向系统表中适当条目的指针。 一旦有进程打开了一个文件,系统表就包含该文件的条目。当另一个进程执行调用 open 时,只不过是在其文件打开表中增加一个条目,并指向系统表的相应条目。通常,系统打开文件表为每个文件关联一个打开计数器(Open Count),以记录多少进程打开了该文件。每个关闭操作 close 使 count 递减,当打开计数器为0时,表示该文件不再被使用,并且可从系统打开文件表中删除相应条目。图4.3展示了这种结构。
文件名不必是打开文件表的一部分,因为一旦完成对 FCB 在磁盘上的定位,系统就不再使用文件名。对于访问打开文件表的索引,UNIX称之为文件描述符,而 Windows 称之为文件句柄。因此,只要文件未被关闭,所有文件操作就通过打开文件表来进行。
每个打开文件都具有如下关联信息:
为了防止文件共享可能会导致文件被破坏或未经核准的用户修改文件,文件系统必须控制用户对文件的存取,即解决对文件的读、写、执行的许可问题。为此,必须在文件系统中建立相应的文件保护机制。文件保护通过口令保护、加密保护和访问控制等方式实现。其中,口令和加密是为了防止用户文件被他人存取或窃取,而访问控制则用于控制用户对文件的访问方式。
对文件的保护可从限制对文件的访问类型中出发。可加以控制的访问类型主要有以下几种。
此外还可以对文件的重命名、复制、编辑等加以控制。这些高层的功能可以通过系统程序调用低层系统调用来实现。保护可以只在低层提供。例如,复制文件可利用一系列的读请求来完成,这样,具有读访问权限的用户同时也就具有了复制和打印权限。
解决访问控制最常用的方法是根据用户身份进行控制。而实现基于身份访问的最为普通的方法是,为每个文件和目录增加一个访问控制列表(Access-Control List, ACL),以规定每个用户名及其所允许的访问类型。这种方法的优点是可以使用复杂的访问方法,缺点是长度无法预计并且可能导致复杂的空间管理,使用精简的访问列表可以解决这个问题。
精简的访问列表采用拥有者、组和其他三种用户类型。
这样,只需用三个域即可列出访问表中这三类用户的访问权限。文件主在创建文件时,说明创建者用户名及所在的组名,系统在创建文件时也将文件主的名字、所属组名列在该文件的FCB中。用户访问该文件时,若用户是文件主,按照文件主所拥有的权限访问文件;若用户和文件主在同一个用户组,则按照同组权限访问,否则只能按其他用户权限访问。
口令和密码是另外两种访问控制方法。
口令指用户在建立一个文件时提供一个口令,系统为其建立 FCB 时附上相应口令,同时告诉允许共享该文件的其他用户。用户请求访问时必须提供相应的口令。这种方法时间和空间的开销不多,缺点是口令直接存在系统内部,不够安全。
密码指用户对文件进行加密,文件被访问时需要使用密钥。这种方法保密性强,节省了存储空间,不过编码和译码要花费一定的时间。
口令和密码都是防止用户文件被他人存取或窃取,并没有控制用户对文件的访问类型。
注意两个问题:
文件的逻辑结构是从用户观点出发看到的文件的组织形式。文件的物理结构(又称文件的存储结构,见下一节)是从实现观点出发看到的文件在外存上的存储组织形式。文件的逻辑结构与存储介质特性无关,它实际上是指在文件的内部,数据逻辑上是如何组织起来的。
按逻辑结构,文件可划分为无结构文件和有结构文件两大类。
无结构文件是最简单的文件组织形式。无结构文件将数据按顺序组织成记录并积累、保存,它是有序相关信息项的集合,以字节(Byte)为单位。由于无结构文件没有结构,因而对记录的访问只能通过穷举搜索的方式,因此这种文件形式对大多数应用不适用。但字符流的无结构文件管理简单,用户可以方便地对其进行操作。所以,那些对基本信息单位操作不多的文件较适于采用字符流的无结构方式,如源程序文件、目标代码文件等。
有结构文件按记录的组织形式可以分为如下几种:
文件中的记录一个接一个地顺序排列,记录通常是定长的,可以顺序存储或以链表形式存储。顺序文件有以下两种结构:第一种是串结构,记录之间的顺序与关键字无关,通常是按存入时间的先后进行排列,对串结构文件进行检索必须从头开始顺序依次查找,比较费时。第二种是顺序结构,指文件中的所有记录按关键字顺序排列,可采用折半查找法,提高了检索效率。
在对记录进行批量操作,即每次要读或写一大批记录时,顺序文件的效率是所有逻辑文件中最高的。此外,对于顺序存储设备(如磁带),也只有顺序文件才能被存储并能有效地工作。在经常需要查找、修改、增加或删除单个记录的场合,顺序文件的性能也比较差。
对于定长记录文件,要查找第 i 条记录,可直接根据下式计算得到第 i 条记录相对于第1条记录的地址:
注意:假定每条记录前用一个字节指明该记录的长度。
变长记录文件只能顺序查找,效率较低。为此,可以建立一张索引表,为主文件的每个记录在索引表中分别设置一个表项,包含指向变长记录的指针(即逻辑起始地址)和记录长度,索引表按关键字排序,因此其本身也是一个定长记录的顺序文件。这样就把对变长记录顺序文件的检索转变为对定长记录索引文件的随机检索,从而加快了记录的检索速度。图4.4所示为索引文件示意图。
索引顺序文件是顺序文件和索引文件的结合。最简单的索引顺序文件只使用了一级索引。索引顺序文件将顺序文件中的所有记录分为若干组,为顺序文件建立一张索引表,在索引表中为每组中的第一条记录建立一个索引项,其中含有该记录的关键字值和指向该记录的指针。
如图 4.5 所示,主文件名包含姓名和其他数据项。姓名为关键字,索引表中为每组的第一条记录(不是每条记录)的关键字值,用指针指向主文件中该记录的起始位置。索引表只包含关键字和指针两个数据项,所有姓名关键字递增排列。主文件中记录分组排列,同一个组中的关键字可以无序,但组与组之间的关键字必须有序。查找一条记录时,首先通过索引表找到其所在的组,然后在该组中使用顺序查找,就能很快地找到记录。
对于含有 N 条记录的顺序文件,查找某关键字的记录时,平均需要查找 N/2 次。在索引顺序文件中,假设 N 条记录分为
索引文件和索引顺序文件都提高了存取的速度,但因为配置索引表而增加了存储空间。
给定记录的键值或通过散列函数转换的键值直接决定记录的物理地址。这种映射结构不同于顺序文件或索引文件,没有顺序的特性。
散列文件有很高的存取速度,但是会引起冲突,即不同关键字的散列函数值相同。
复习了数据结构的读者读到这里时,会有这样的感觉:有结构文件逻辑上的组织,是在为文件中查找数据服务的(顺序查找、索引查找、索引顺序查找、哈希查找)。
前面介绍了文件内部的逻辑结构,下面介绍多个文件之间在逻辑上是如何组织的,这实际上是文件“外部”的逻辑结构的问题。
前面说过,文件实际上是一种抽象数据类型,我们要研究它的逻辑结构、物理结构,以及关于它的一系列操作。文件的物理结构就是研究文件的实现,即文件数据在物理存储设备上是如何分布和组织的。同一个问题有两个方面的回答:一是文件的分配方式,讲的是对磁盘非空闲块的管理;二是文件存储空间管理,讲的是对磁盘空闲块的管理(详见4.3节)。
文件分配对应于文件的物理结构,是指如何为文件分配磁盘块。常用的磁盘空间分配方法有三种:连续分配、链接分配和索引分配。有的系统(如 RDOS 操作系统)对三种方法都支持,但更普遍的是一个系统只支持一种方法。对于本节的内容,读者要注意与文件的逻辑结构区分,从历年的经验来看,这是很多读者容易搞混的地方(读者复习完数据结构后,应该了解线性表、顺序表和链表之间的关系,类比到这里就不易混淆)。
连续分配方法要求每个文件在磁盘上占有一组连续的块,如图 4.6所示。磁盘地址定义了磁盘上的一个线性排序,这种排序使作业访问磁盘时需要的寻道数和寻道时间最小。
采用连续分配时,逻辑文件中的记录也顺序存储在相邻接的块中。一个文件的目录项中“文件物理地址”字段应包括第一块的地址和该文件所分配区域的长度,若文件长n块并从位置b开始,则该文件将占有块
连续分配支持顺序访问和直接访问。优点是实现简单、存取速度快。缺点是:①文件长度不宜动态增加,因为一个文件末尾后的盘块可能已分配给其他文件,一旦需要增加,就需要大量移动盘块。②为保持文件的有序性,删除和插入记录时,需要对相邻的记录做物理上的移动,还会动态改变文件的长度。③反复增删文件后会产生外部碎片(与内存管理分配方式中的碎片相似)。④很难确定一个文件需要的空间大小,因而只适用于长度固定的文件。
链接分配是一种采用离散分配的方式。它消除了磁盘的外部碎片,提高了磁盘的利用率。可以动态地为文件分配盘块,因此无须事先知道文件的大小。此外,对文件的插入、删除和修改也非常方便。链接分配又可分为隐式链接和显式链接两种形式。
隐式链接方式如图 4.7所示。目录项中含有文件第一块的指针和最后一块的指针。每个文件对应一个磁盘块的链表;磁盘块分布在磁盘的任何地方,除最后一个盘块外,每个盘块都含有指向文件下一个盘块的指针,这些指针对用户是透明的。
隐式链接的缺点是只适合顺序访问,若要访问文件的第 i 个盘块,则只能从第1个盘块开始通过盘块指针顺序查找到第 i 块,随机访问效率很低。隐式链接的稳定性也是一个问题,系统在运行过程中由于软件或硬件错误导致链表中的指针丢失或损坏,会导致文件数据的丢失。
通常的解决方案是,将几个盘块组成簇(cluster),按簇而不按块来分配,可以成倍地减少查找时间。比如一簇为4块,这样,指针所占的磁盘空间比例也要小得多。这种方法的代价是增加了内部碎片。簇可以改善许多算法的磁盘访问时间,因此应用于大多数操作系统。
显式链接是指把用于链接文件各物理块的指针,从每个物理块的末尾中提取出来,显式地存放在内存的一张链接表中。该表在整个磁盘中仅设置一张,称为文件分配表(File Allocation Table,FAT)。每个表项中存放链接指针,即下一个盘块号。文件的第一个盘块号记录在目录项“物理地址”字段中,后续的盘块可通过查FAT找到。例如,盘块号下一块某磁盘共有100个磁盘块,存放了两个文件:文件“aaa”占三个盘块,依次是2→8→5;文件“bbb”占1-1两个盘块,依次是7→1。其余盘块都是空闲盘块,则该磁盘的FAT表如图4.8所示。
不难看出,文件分配表FAT的表项与全部磁盘块一一对应,并且可以用一个特殊的数字-1表示文件的文件目录85最后一块,可以用-2表示这个磁盘块是空闲的(当然-2也可指定为 -3,-4)。因此,FAT不仅记录了文件各块之间的先后链接关系,同时还标记了空闲的磁盘块,FAT(文件分配表)操作系统也可以通过FAT对文件存储空间进行管理。当某进程请求操作系统分配一个磁盘块时,操作系统只需从FAT中找到-2的表项,并将对应的磁盘块分配给进程即可。
FAT表在系统启动时就会被读入内存,因此查找记录的过程是在内存中进行的,因而不仅显著地提高了检索速度,而且明显减少了访问磁盘的次数。
链接分配解决了连续分配的外部碎片和文件大小管理的问题。但依然存在问题: ①链接分配不能有效支持直接访问(FAT除外): ②FAT需要占用较大的内存空间。 事实上,在打开某个文件时,只需将该文件对应盘块的编号调入内存即可,完全没有必要将整个FAT调入内存。为此,索引分配将每个文件所有的盘块号都集中放在一起构成索引块(表),如图4.9所示。
每个文件都有其索引块,这是一个磁盘块地址的数组。索引块的第 i 个条目指向文件的第 i 个块。要读第 i 块,通过索引块的第i个条目的指针来查找和读入所需的块。
索引分配的优点是支持直接访问,且没有外部碎片问题。缺点是由于索引块的分配,增加了系统存储空间的开销。索引块的大小是一个重要的问题,每个文件必须有一个索引块,因此索引块应尽可能小,但索引块太小就无法支持大文件。可以采用以下机制来处理这个问题。
·链接方案。一个索引块通常为一个磁盘块,因此它本身能直接读写。为了支持大文件,可以将多个索引块链接起来。·多层索引。通过第一级索引块指向一组第二级的索引块,第二级索引块再指向文件块。查找时,通过第一级索引查找第二级索引,再采用这个第二级索引查找所需数据块。这种方法根据最大文件大小,可以继续到第三级或第四级。例如,4096B的块,能在索引块中存入1024个4B 的指针。两级索引支持·1048576个数据块,即支持最大文件为4GB。单级索引分配方式或两级索引分配方式。该内容为高频考点,下面用一节专门介绍。此外,访问文件需两次访问外存,先读取索引块的内容,然后访问具体的磁盘块,因而降低了文件的存取速度。为了解决这一问题,通常将文件的索引块读入内存,以提高访问速度。
为了能够较全面地照顾到小型、中型、大型和特大型文件,可采用混合索引分配方式。对于小文件,为了提高对众多小文件的访问速度,最好能将它们的每个盘块地址直接放入FCB,这样就可以直接从FCB中获得该文件的盘块地址,即为直接寻址。对于中型文件,可以采用单级索引方式,需要先从 FCB 中找到该文件的索引表,从中获得该文件的盘块地址,即为一次间址。对于大型或特大型文件,可以采用两级和三级索引分配方式。UNIX 系统采用的就是这种分配方式,在其索引结点中,共设有13个地址项,即 i.addr(0)~i.addr(12)
,如图 4.10所示。
i.addr(0)~i.addr(9)
来存放直接地址,即文件数据盘块的盘块号。假如每个盘块的大小为4KB,当文件不大于40KB时,便可直接从索引结点中读出该文件的全部盘块号。i.addr(0)
来提供一次间接地址。这种方式的实质就是一级索引分配方式。图中的一次间址块也就是索引块,系统将分配给文件的多个盘块号记入其中。在一次间址块中可存放1024个盘块号,因而允许文件长达4MB。i.addr(11)
提供二次间接地址。该方式的实质是两级索引分配方式。系统此时在二次间址块中记入所有一次间址块的盘号。地址项 i.addr(11)
作为二次间址块,允许文件最大长度可达4GB。同理,地址项 iaddr(12)
作为三次间址块,其允许的文件最大长度可达4TB本节开头提出的问题的参考答案如下。
学到这里时,读者应能有这样的体会:现代操作系统的思想中,到处能见到面向对象程序设计的影子。本节我们学习的一个新概念一一文件,实质上就是一个抽象数据类型,也就是一种数据结构,若读者在复习操作系统之前已复习完数据结构,则遇到一种新的数据结构时,一定会有这样的意识:要认识它的逻辑结构、物理结构;以及对这种数据结构的操作。操作系统对文件的操作不是本课程重点关心的问题,无须做深入研究。
在学习本节时,请读者思考以下问题:
上节介绍了文件的逻辑结构和物理结构,本节将介绍目录的实现。文件目录也是一种数据结构,用于标识系统中的文件及其物理地址,供检索时使用。通常,一个文件目录也被视为一个文件。在学习本节的内容时,读者可以围绕目录管理的要求来思考。
上节说过,FCB 的有序集合称为文件目录,一个 FCB 就是一个文件目录项。与文件管理系统和文件集合相关联的是文件目录,它包含有关文件的属性、位置和所有权等。
首先来看目录管理的基本要求:从用户的角度看,目录在用户(应用程序)所需要的文件名和文件之间提供一种映射,所以目录管理要实现“按名存取”;目录存取的效率直接影响到系统的性能,所以要提高对目录的检索速度;在多用户系统中,应允许多个用户共享一个文件,因此目录还需要提供用于控制访问文件的信息。此外,应允许不同用户对不同文件采用相同的名字,以便于用户按自己的习惯给文件命名,目录管理通过树形结构来解决和实现。
在整个文件系统中只建立一张目录表,每个文件占一个目录项,如图4.11所示。
当访问一个文件时,先按文件名在该目录中查找到相应的FCB,经合法性检查后执行相应的操作。当建立一个新文件时,必须先检索所有目录项,以确保没有“重名”的情况,然后在该目录中增设一项,把新文件的属性信息填入到该项中。当删除一个文件时,先从该目录中找到该文件的目录项,回收该文件所占用的存储空间,然后清除该目录项。
单级目录结构实现了“按名存取”,但是存在查找速度慢、文件不允许重名、不便于文件共享等缺点,而且对于多用户的操作系统显然是不适用的。
为了克服单级目录所存在的缺点,可以采用两级方案,将文件目录分成主文件自录(Master File Directory,MFD)和用户文件目录(User File Directory,UFD)两级,如图4.12所示。
主文件目录项记录用户名及相应用户文件目录所在的存储位置。用户文件目录项记录该用户文件的 FCB 信息。当某用户欲对其文件进行访问时,只需搜索该用户对应的 UFD,这既解决了不同用户文件的“重名”问题,又在一定程度上保证了文件的安全。
两级目录结构提高了检索的速度,解决了多用户之间的文件重名问题,文件系统可以在目录上实现访问限制。但是两级目录结构缺乏灵活性,不能对文件分类。
将两级目录结构加以推广,就形成了树形目录结构,如图4.13所示。它可以明显地提高对目录的检索速度和文件系统的性能。当用户要访问某个文件时,用文件的路径名标识文件,文件路径名是个字符串,由从根目录出发到所找文件通路上所有目录名与数据文件名用分隔符“/
”链接而成。从根目录出发的路径称为绝对路径。当层次较多时,每次从根目录查询会浪费时间,于是加入了当前目录(又称工作目录),进程对各文件的访问都是相对于当前目录进行的。当用户要访问某个文件时,使用相对路径标识文件,相对路径由从当前目录出发到所找文件通路上所有目录名与数据文件名用分隔符“/
”链接而成。
图 4.13 是Linux操作系统的目录结构,"/dev/hda"
就是一个绝对路径。若当前目录为 "/bin"
,则 “./ls”
就是一个相对路径,其中符号“.
”表示当前工作目录。
通常,每个用户都有各自的“当前目录”,登录后自动进入该用户的“当前目录”。操作系统提供一条专门的系统调用,供用户随时改变“当前目录”。例如,在UNIX系统中,/etc/passwd
文件就包含有用户登录时默认的“当前目录”,可用 cd
命令改变“当前目录”。
树形目录结构可以很方便地对文件进行分类,层次结构清晰,也能够更有效地进行文件的管理和保护。在树形目录图4.13树形目录结构中,不同性质、不同用户的文件,可以分别呈现在系统目录树的不同层次或不同子树中,很容易地赋予不同的存取权限。但是,在树形目录中查找一个文件,需要按路径名逐级访问中间结点,增加了磁盘访问次数,这无疑会影响查询速度。目前,大多数操作系统如 UNIX、Linux 和 Windows 系统都采用了树形文件目录。
树形目录结构能便于实现文件分类,但不便于实现文件共享,为此在树形目录结构的基础上增加了一些指向同一结点的有向边,使整个目录成为一个有向无环图,如图4.14所示。
当某用户要求删除一个共享结点时,若系统只是简单地将它删除,则当另一共享用户需要访问时,会因无法找到这个文件而发生错误。为此,可为每个共享结点设置一个共享计数器,每当图中增加对该结点的共享链时,计数器加1:每当某用户提出删除该结点时,计数器减1。仅当共享计数器为0时,才真正删除该结点,否则仅删除请求用户的共享链。
共享文件(或目录)不同于文件拷贝(副本)。若有两个文件拷贝,则每个程序员看到的是拷贝而不是原件;然而,若一个文件被修改,则另一个程序员的拷贝不会改变。对于共享文件,只存在一个真正的文件,任何改变都会为其他用户所见。
无环图目录结构方便地实现了文件的共享,但使得系统的管理变得更加复杂。
在理解一个文件系统的需求前,我们首先考虑在目录这个层次上所需要执行的操作,这有助于后面文件系统的整体理解。
在访问一个文件时,操作系统利用路径名找到相应目录项,目录项中提供了查找文件磁盘块所需要的信息。目录实现的基本方法有线性列表和哈希表两种,要注意目录的实现就是为了查找,因此线性列表实现对应线性查找,哈希表的实现对应散列查找。
最简单的目录实现方法是,采用文件名和数据块指针的线性列表。当创建新文件时,必须首先搜索目录以确定没有同名的文件存在,然后在目录中增加一个新的目录项。当删除文件时,则根据给定的文件名搜索目录,然后释放分配给它的空间。当要重用目录项时有许多种方法:可以将目录项标记为不再使用,或将它加到空闲目录项的列表上,还可以将目录的最后一个目录项复制到空闲位置,并减少目录的长度。采用链表结构可以减少删除文件的时间。
线性列表的优点在于实现简单,不过由于线性表的特殊性,查找比较费时。
除了采用线性列表存储文件目录项,还可以采用哈希数据结构。哈希表根据文件名得到一个值,并返回一个指向线性列表中元素的指针。这种方法的优点是查找非常迅速,插入和删除也较简单,不过需要一些措施来避免冲突(两个文件名称哈希到同一位置)。
目录查询是通过在磁盘上反复搜索完成的,需要不断地进行IVO操作,开销较大。所以如前所述,为了减少I/O操作,把当前使用的文件目录复制到内存,以后要使用该文件时只需在内存中操作,因此降低了磁盘操作次数,提高了系统速度。
文件共享使多个用户共享同一个文件,系统中只需保留该文件的一个副本。若系统不能提供共享功能,则每个需要该文件的用户都要有各自的副本,会造成对存储空间的极大浪费。
现代常用的两种文件共享方法如下
在树形结构的目录中,当有两个或多个用户要共享一个子目录或文件时,必须将共享文件或子目录链接到两个或多个用户的目录中,才能方便地找到该文件,如图4.15所示。
在这种共享方式中,诸如文件的物理地址及其他的文件属性等信息,不再放在目录项中,而放在索引结点中。在文件目录中只设置文件名及指向相应索引结点的指针。在索引结点中还应有一个链接计数 count,用于表示链接到本索引结点(即文件)上的用户目录项的数目。当 count = 2 时,表示有两个用户目录项链接到本文件上,或者说有两个用户共享此文件。
用户 A 创建一个新文件时,他便是该文件的所有者,此时将 count 置为1。用户 B 要共享此文件时,在B的目录中增加一个目录项,并设置一个指针指向该文件的索引结点。此时,文件主仍然是用户 A,count=2。如果用户 A 不再需要此文件,能否直接将其删除呢?答案是否定的。因为若删除了该文件,也必然删除了该文件的索引结点,这样便会使用户B 的指针悬空,而 B可能正在此文件上执行写操作,此时将因此半途而废。因此用户A不能删除此文件,只是将该文件的 count减1,然后删除自己目录中的相应目录项。用户B仍可以使用该文件。当count=0时,表示没有用户使用该文件,才会删除该文件。如图 4.16 给出了用户B 链接到文件上的前、后情况。
为使用户 B 能共享用户 A 的一个文件 F,可以由系统创建一个 LINK 类型的新文件,也取名为F,并将该文件写入用户B的目录中,以实现用户B的目录与文件F的链接。在新文件中只包含被链接文件F的路径名。当用户B要访问被链接的文件F且正要读LINK类新文件时,操作系统查看到要读的文件是LINK类型,则根据该文件中的路径名去找到文件F,然后对它进行读,从而实现用户B对文件F的共享。这样的链接方法被称为符号链接。
在利用符号链方式实现文件共享时,只有文件主才拥有指向其索引结点的指针。而共享该文件的其他用户只有该文件的路径名,并不拥有指向其索引结点的指针。这样,也就不会发生在文件主删除一共享文件后留下一悬空指针的情况。当文件主把一个共享文件删除后,若其他用户又试图通过符号链去访问它时,则会访问失败,于是将符号链删除,此时不会产生任何影响。
在符号链的共享方式中,当其他用户读共享文件时,系统根据文件路径名逐个查找目录,直至找到该文件的索引结点。因此,每次访问共享文件时,都可能要多次地读盘。使得访问文件的开销甚大,且增加了启动磁盘的频率。此外,符号链的索引结点也要耗费一定的磁盘空间。
利用符号链实现网络文件共享时,只需提供该文件所在机器的网络地址及文件路径名。
硬链接和软链接都是文件系统中的静态共享方法,在文件系统中还存在着另外的共享需求,即两个进程同时对同一个文件进行操作,这样的共享称为动态共享。
可以这样说:文件共享,“软” “硬”兼施。硬链接就是多个指针指向一个索引结点,保证只要还有一-个指针指向索引结点,索引结点就不能删除;软链接就是把到达共享文件的路径记录下来,当要访问文件时,根据路径寻找文件。可见,硬链接的查找速度要比软链接的快。
文件系统(File system)提供高效和便捷的磁盘访问,以便允许存储、定位、提取数据。文件系统有两个不同的设计问题:第一个问题是,定义文件系统的用户接口,它涉及定义文件及其属性、所允许的文件操作、如何组织文件的目录结构。第二个问题是,创建算法和数据结构,以便映射逻辑文件系统到物理外存设备。现代操作系统有多种文件系统类型,因此文件系统的层次逻辑文件系统结构也不尽相同。图4.17是一个合理的文件系统层次结构。
文件组织模块包括设备驱动程序和中断处理程序,在内存和磁盘系统之间传输信息。设备驱动程序将输入的命令翻译成底层硬件的特定指令,硬件控制器利用这些指令使I/O设备与系统交互。设备驱动程序告诉I/O控制器对设备的什么位置采取什么动作。
向对应的设备驱动程序发送通用命令,以读取和写入磁盘的物理块。每个物理块由磁盘地址标识。该层也管理内存缓冲区,设备并保存各种文件系统、目录和数据块的缓存。在进行磁盘块传输前,分配合适的缓冲区,并对缓冲区进行管理。管理它们对于系统性能的优化至关重要。
组织文件及其逻辑块和物理块。文件组织模块可以将逻辑块地址转换成物理块地址,每个文件的逻辑块从0到N编号,它与数据的物理块不匹配,因此需要通过转换来定位。文件组织模块还包括空闲空间管理器,以跟踪未分配的块,根据需求提供给文件组织模块。
用于管理元数据信息。元数据包括文件系统的所有结构,而不包括实际数据(或文件内容)。逻辑文件系统管理目录结构,以便根据给定文件名称为文件组织模块提供所需要的信息。它通过文件控制块来维护文件结构。逻辑文件系统还负责文件保护。
文件系统存放在磁盘上,多数磁盘划分为一个或多个分区,每个分区中有一个独立的文件系统。文件系统可能包括如下信息:启动存储在那里的操作系统的方式、总的块数、空闲块的数量和位置、目录结构以及各个具体文件等。图4.18所示为一个可能的文件系统布局。
简单描述如下:
内存中的信息用于管理文件系统并通过缓存来提高性能。这些数据在安装文件系统时被加载,在文件系统操作期间被更新,.在卸载时被丢弃。这些结构的类型可能包括:
为了创建新的文件,应用程序调用逻辑文件系统。逻辑文件系统知道目录结构的格式,它将为文件分配一个新的 FCB。然后,系统将相应的目录读入内存,使用新的文件名和 FCB 进行更新,并将它写回磁盘。
一旦文件被创建,它就能用于 I/O。不过,首先要打开文件。系统调用 open()
将文件名传递给逻辑文件系统。调用 open()
首先搜索整个系统的打开文件表,以确定这个文件是否已被其他进程使用。如果已被使用,则在单个进程的打开文件表中创建一个条目,让其指向现有整个系统的打开文件表的相应条目。该算法在文件已打开时,能节省大量开销。如果这个文件尚未打开,则根据给定文件名来搜索目录结构。部分目录结构通常缓存在内存中,以加快目录操作。找到文件后,它的FCB会复制到整个系统的打开文件表中。该表不但存储FCB,而且跟踪打开该文件的进程的数量。然后,在单个进程的打开文件表中创建一个条目,并且通过指针将整个系统打开文件表的条目与其他域(如文件大哥前位置的指针和文件访问模式等)相连。调用 open()
返回的是一个指向单个进程的打开文件表中的适当条目的指针。以后,所有文件操作都通过该指针执行。一旦文件被打开,内核就不再使用文件名来访问文件,而使用文件描述符(Windows 称之为文件句柄)。
当进程关闭一个文件时,就会删除大哥进程打开文件表中的相应条目,整个系统的打开文件表的文件打开数量也会递减。当所有打开某个文件的用户都关闭该文件后,任何更新的元数据将复制到磁盘的目录结构中,并且哼歌系统的打开文件表的对应条目也会被删除。
一个存储设备可以按整体用于文件系统,也可以细分。例如,一个磁盘可以划分为 4 个分区,每个分区都可以有单独的文件系统。包含文件系统的分区通常称为卷(volume)。卷可以是磁盘的一部分,也可以是整个磁盘,还可以是多个磁盘组成RAID集,如图4.19.所示。
在一个卷中,存放文件数据的空间(文件区)和 FCB 的空间(目录区)是分离的。由于存在很多种类的文件表示和存放格式,所以现代操作系统中一般都有很多不同的文件管理模块,通过它们可以访问不同格式的卷中的文件。卷在提供文件服务前,必须由对应的文件程序进行初始化,划分好目录区和文件区,建立空闲空间管理表格及存放卷信息的超级块。
文件存储设备分成许多大小相同的物理块,并以块为单位交换信息,因此,文件存储设备的管理实质上是对空闲块的组织和管理,它包括空闲块的组织、分配与回收等问题。
空闲表法属于连续分配方式,它与内存的动态分配方式类似,为每个文件分配一块连续的存储空间。系统为外存上的所有空闲区建立一张空闲表,每个空闲区对应一个空闲表项,其中包括表项序号、该空闲区的第一个盘块号、该区的空闲盘块数等信息。再将所有空闲区按其起始盘块号递增的次序排列,如表4.1所示。
空闲盘区的分配与内存的动态分配类似,同样采用首次适应算法和最佳适应算法等。例如,在系统为某新创建的文件分配空闲盘块时,先顺序地检索空闲盘块表的各表项,直至找到第一个其大小能满足要求的空闲区,再将该盘区分配给用户,同时修改空闲盘块表。
系统在对用户所释放的存储空间进行回收时,也采取类似于内存回收的方法,即要考虑回收区是否与空闲盘块表中插入.点的前区和后区相邻接,对相邻接者应予以合并。
将所有空闲盘区拉成一条空闲链。根据构成链所用基本元素的不同,分为两种形式:
位示图是利用二进制的一位来表示磁盘中一个盘块的使用情况,磁盘上所有的盘块都有一个二进制位与之对应。当其值为“0”时,表示对应的盘块空闲;为“1”时,表示已分配。这样,一个 m×n位组成的位示图就可用来表示m×n个盘块的使用情况,如图4.20所示。
注意:本书如无特别提示,所使用的位示图法中行和列都从1开始编号。特别注意,若题目中指明从0开始编号,则上述计算方法要进行相应调整。
盘块的分配:
map[i, j] = 1
盘块的回收:
将回收盘块的盘块号转换成位示图中的行号和列号。转换公式为:
修改位示图,令 map[i, j] = 0
空闲表法和空闲链表法都不适用于大型文件系统,因为这会使空闲表或空闲链表太大
在UNIX系统中采用的是成组链接法,这种方法结合了空闲表和空闲链表两种方法,它具有上述两种方法的优点,克服了两种方法均有的表太长的缺点。
用来存放一组空闲盘块号(空闲盘块的块号)的盘块称为成组链块。成组链接法的大致思想是:把顺序的n个空闲盘块号保存在第一个成组链块中,其最后一个空闲盘块(作为成组链块)则用于保存另一组空闲盘块号,如此继续,直至所有空闲盘块均予以链接。系统只需保存指向第一个成组链块的指针。假设磁盘最初全为空闲盘块,其成组链接如图4.21所示。
盘块的分配: 根据第一个成组链块的指针,将其对应的盘块分配给用户,然后将指针下移一格。若该指针指向的是最后一个盘块(即成组链块),由于该盘块记录的是下一组空闲盘块号,因此要将该盘块读入内存,并将指针指向新的成组链块的第一条记录,然后执行上述分配操作。
盘块的回收: 成组链块的指针上移一格,再记入回收盘块号。当成组链块的链接数达到n时,表示已满,便将现有已记录n个空闲盘块号的成组链块号记入新回收的盘块(作为新的成组链块)。 表示空闲空间的位向量表或第一个成组链块,以及卷中的目录区、文件区划分信息都要存放在磁盘中,一般放在卷头位置,在UNIX系统中称为超级块。在对卷中的文件进行操作前,超级块需要预先读入系统空闲的主存,并且经常保持主存超级块与磁盘卷中超级块的一致性。
虚拟文件系统(VFS)为用户程序提供了文件系统操作的统一接口,屏蔽了不同文件系统的差异和操作细节,如图4.22所示。用户程序可以通过VFS提供的统一调用函数(如 open()
等)来操作不同文件系统(如 ext3
等)的文件,而无须考虑具体的文件系统和实际的存储介质。
虚拟文件系统采用了面向对象的思想,它抽象出一个通用的文件系统模型,定义了通用文件系统都支持的接口。新的文件系统只要支持并实现这些接口,即可安装和使用。以 Linux 中调用 write()
操作为例,它在 VFS
中通过 sys_write()
函数处理,sys_write()
找到具体文件系统,将控制权交给该文件系统,最后由具体文件系统与物理介质交互并写入数据,如图4.23所示。
为了实现 VFS,Linux 主要抽象了四种对象类型。每个 VFS 对象都存放在一个适当的数据结构中,其中包括对象的属性和指向对象方法(函数)表的指针。
Linux 将目录当作文件对象来处理,文件操作能同时应用于文件或目录。文件系统是由层次目录组成的,一个目录项可能包含文件名和其他目录名。目录项作为单独抽象的对象,是因为目录可以层层嵌套,以便于形成文件路径,而路径中的每一部分其实就是目录项。
/test
时,内核为根目录“/
”创建一个目录项对象,为根目录下的test创建一个第二级目录项对象。目录项对象包含指向关联索引结点的指针,还包含指向父目录和指向子目录的指针。不同于前面两个对象,目录项对象在磁盘上没有对应的数据结构,而是VFS在遍历路径的过程中,将它们逐个解析成目录项对象的。open()
调用打开一个文件,通过 close()
调用关闭一个文件。文件对象和物理文件的关系类似于进程和程序的关系。由于多个进程可以打开和操作同一文件,所以同一文件在内存中可能存在多个对应的文件对象,但对应的索引结点和目录项是唯一的。文件对象仅在进程观点上代表已经打开的文件,它反过来指向其索引结点。文件对象包含与该文件相关联的目录项对象,包含该文件的文件系统、文件指针等,还包含在该文件对象上调用的一系列操作函数。图 4.24 所示是一个进程与文件进行交互的简单实例。三个不同的进程已打开了同一个文件,其中两个进程使用同一个硬链接。在这种情况下,每个进程都使用自己的文件对象,但只需要两个目录项对象,每个硬链接对应一个目录项对象。这两个目录项对象指向同一个索引结点对象,这个索引结点对象标识的是超级块对象及随后的普通磁盘文件。
VFS还有另一个重要作用,即提高系统性能。最近最常使用的目录项对象被放在目录项高速缓存的磁盘缓存中,以加速从文件路径名到最后一个路径分量的索引结点的转换过程。
对用户来说,不需要关心不同文件系统的具体实现细节,只需要对一个虚拟的文件操作界面进行操作。VFS对每个文件系统的所有细节进行抽象,使得不同的文件系统在系统中运行的其他进程看来都是相同的。严格来说,VFS并不是一种实际的文件系统,它只存在于内存中,不存在于任何外存空间中。VFS在系统启动时建立,在系统关闭时消亡。
一个磁盘可以划分为多个分区,每个分区都可以用于创建单独的文件系统,每个分区还可以包含不同的操作系统。分区可以是原始的,没有文件系统,当没有合适的文件系统时,可以使用原始磁盘。例如,UNIX交换空间可以使用原始磁盘格式,而不使用文件系统。
第1章中介绍过操作系统的引导。Linux启动后,首先载入MBR,随后MBR 识别活动分区,并且加载活动分区中的引导程序。图4.25 中显示了一个典型的Linux分区。
分区的第一部分是引导块,里面存储着引导信息,它有自身的格式,因为在引导时系统并未加载文件系统代码,因此不能解释文件系统的格式。引导信息是一系列可以加载到内存中的连续块,加载到内存后从其第一条代码开始执行,引导程序便启动一个具体的操作系统。引导块之后是超级块,它存储文件系统的有关信息,包括文件系统的类型、i 结点的数目、数据块的数目。随后是多个索引结点,它们是实现文件存储的关键,每个文件对应一个索引结点,索引结点中包含多个指针,指向属于该文件的各个数据块。最后是文件数据块。
如文件在使用前必须打开一样,文件系统在进程使用前必须先安装,也称挂载。
Windows 系统维护一个扩展的两级目录结构,用驱动器字母表示设备和卷。卷具有常规树结构的目录,与驱动器号相关联,还含有指向已安装文件系统的指针。特定文件的路径形式为 driverletter:path\to\file
,操作系统找到相应文件系统的指针,并且遍历该设备的目录结构,以查找指定的文件。新版本的 Windows 允许文件系统安装在目录树下的任意位置,就像UNIX一样。在启动时,Windows 操作系统自动发现所有设备,并且安装所有找到的文件系统。
UNIX 使用系统的根文件系统,由内核在引导阶段直接安装,其他文件系统要么由初始化脚本安装,要么由用户安装在已安装文件系统的目录下。作为一个目录树,每个文件系统都拥有自己的根目录。安装文件系统的这个目录称为安装点,安装就是将磁盘分区挂载到该安装点下,进入该目录就可以读取该分区的数据。已安装文件系统属于安装点目录的一个子文件系统。安装的实现是在目录 inode 的内存副本上加上一个标志,表示该目录是安装点。还有一个域指向安装表的条目,表示哪个设备安装在哪里,这个条目还包括该设备的文件系统超级块的一个指针。
假定将存放在 /dev/fd0
软盘上的 ext2
文件系统通过 mount
命令安装到 /flp
:
mount -t ext2 /dev/fd0 /flp
如需卸载该文件系统,可以使用 umount
命令。
我们可以这么理解:UNIX本身是一个固定的目录树,只要安装就有,但是如果不给它分配存储空间,就不能对它进行操作,所以首先要给根目录分配空间,这样才能操作这个目录树。
贯穿本章内容有两条主线:第一条主线是介绍一种新的抽象数据类型一一文件,从逻辑结构和物理结构两个方面进行;第二条主线是操作系统是如何管理“文件”的,介绍了多文件的逻辑结构的组织,即目录,还介绍了如何处理用户对文件的服务请求,即磁盘管理。只从宏观上认识是远不够的,从宏观上把握知识的目的是从微观上更加准确地掌控细微知识点,在考试中得到好成绩。读者要通过反复做题、反复思考,不断加深自己对知识点的掌握程度。
访问第n条记录 | 优点 | 缺点 | |
---|---|---|---|
连续分配 | 需访问磁盘1次 | 颅序存取时速度快,文件定长时可根据文件起始地址及记录长度进行随机访问 | 文件存储要求连续的存储空间,会产生碎片,不利于文件的动态扩充 |
链接分配 | 需访问磁盘n次 | 可解决外存的碎片问题,提高外存空间的利用率,动态增长较方便 | 只能按照文件的指针链序访问,查找效率低,指针信息存放消耗外存空间 |
索引分配 | m级需访问磁盘 m+1 次 | 可以随机访问,文件易于增删 | 索引表增加存储空间的开销,索引表的查找策略对文件系统效率影响较大 |