操作系统它作为系统资源的管理者,也需要对文件进行管理。文件也属于一种系统资源,它其实就是一组有意义的信息或者说数据的集合。手机照片、word文档、PPT讲义、PDF文件,这些都是各种各样的文件。但是各种各样的文件它又在我们前面呈现出了不一样的一些特性。那这些特性其实也需要操作系统来关心。所以其实不同的文件它有不同的属性,而因为源文件属性的不同,所以我们在使用文件的时候也会有各种各样的差别。第二个问题,既然文件它是一组有意义的信息或者说数据的集合,那么这些信息在内部是怎么被组织起来的,也就是文件内部的这些数据的组织形式的问题。
第四个问题,从下往上看,操作系统的上层应该是应用程序和用户,所以操作系统应该为它的上层提供简单易用的一些接口,用来方便操作各种各样的文件。那操作系统应该为上层提供哪些功能,这是我们之后还会探讨的问题。
最后一个会探讨的问题,从上往下看,操作系统的下面一层是硬件,所以操作系统也需要对硬件进行管理。而我们知道,一般来说文件是存放在我们的磁盘也就是外存当中的,而外存作为一种计算机的硬件,肯定也需要操作系统对它进行管理。那么这些文件的数据应该怎么被存放在外存上,这是我们最后一个会探讨的问题。
那我们首先来看一下一个文件应该有哪些属性。
那为什么不允许有重名文件,这一点咱们会在之后的小节当中再进行进一步的解释。总之文件名对于用户来说是很重要甚至说是最重要的一个属性。因为我们都是根据文件名来找到一个文件的。
那文件的第二个重要属性是标识符。其实在一个系统当中,肯定是有很多重名文件的。虽然说在同一文件夹下不允许有重名的文件,所以其实用文件名并不能唯一地区分出每一个文件到底是哪一个。因此,操作系统会在背后为各个文件设置一个标识符。那每个文件的标识符都是唯一的,只不过这个标识符对用户来说毫无可读性,一般来说就是一连串没有意义的数字字母的组合。所以在这个界面,也没有向用户展示出一个文件的标识符。它只是操作系统用于区分各个文件的一种内部的名称。
第三,每个文件会有一个叫做文件类型的属性。
指明文件的类型有很多好处。比如说操作系统可以为不同类型的文件设置一个默认的打开的应用程序。
比如说.txt文件,就是默认用记事本这个应用程序打开的。那像.mp4这样的文件一般来说就是默认用一个视频播放的应用程序来打开并且呈现给用户。所以文件类型也是一个很重要的属性。
第四个很重要的属性是文件的位置。那这个位置包括文件的存放路径,这个存放路径是让用户使用的。不过这个文件平时是存放在外存也就是磁盘当中的,所以当我们双击打开这个文件的时候,其实操作系统需要从外存把文件的数据读入内存,因此操作系统也需要关心文件在外存当中存放的位置这个问题。只不过在外存当中存放的地址这个属性,对用户来说是不可见的,只有操作系统需要关心。
那除了上面介绍的这些很重要的属性之外,还有文件的大小。
另外还有一个很重要的属性就是文件的保护信息。只不过文件的保护信息这个属性咱们平时在使用的过程当中几乎体会不到,那大家可以看一下自己的文件属性安全这个页签里,那大家会发现其实操作系统对这个系统当中的各种用户进行了一个分组,那不同分组的用户对于这个文件的操作权限是不同的。有的分组的用户可以对这个文件进行读和写,而有的分组甚至也不能读也不能写,不能访问这个文件。所以文件的保护信息可以让操作系统更好地保护一个文件的安全。如果我们创建的文件不想让别的计算机用户来访问的话,那么我们就可以设置一下它的保护信息就可以实现这件事。因为我自己的那个账户是没有权限访问游戏的那个启动程序的,所以这是文件的保护信息的一个作用。
像.txt这种文件,它是一种看起来没有很明显的结构特性的文件。那这种文件它是由一些二进制或者说字符流组成,就是所谓的无结构文件,又称为流式文件。另外,在我们用户看来,还有一些文件表现出了很强的结构特性。比如说咱们使用的Excel表,还有有编程经验的同学肯定也使用过一些数据库,那数据库表也是一种有很明显的这种组织结构的一种文件。
所以对于这种有结构文件来说,它是由一条一条的记录组成。那像这个地方这样的一行就是一条记录。而每个记录又是由一组相关的数据项组成,比如说学号是一个数据项,姓名是一个数据项,性别和专业又是另外的数据项。
所以数据项它是文件系统当中最基本的一个数据单位。
因此文件可以分为无结构文件和有结构文件这样两大类。那无结构文件它就是由一系列二进制或者说字符流组成,它没有明显的结构特性。而对于有结构文件来说,它是由一个一个的记录组成。每一个记录又由一系列的数据项组成。
那这些记录应该如何组织的问题,也就是这些记录它们应该是顺序地存放,还是用一个索引表来表示它们之间的这种先后顺序?这些问题是咱们之后要讨论的文件的逻辑结构相关的一些问题,在这个地方暂时不展开。所以文件的逻辑结构要探讨的问题其实就是文件内部的这些记录这些数据应该被怎么组织起来的一个问题。那这是下个小节当中会讲解的内容。
接下来我们再来看一下文件之间应该怎样被组织起来。
所以其实我们平时使用的这个Windows操作系统的文件之间的组织形式,看起来是一种树状的组织形式。一个根目录下可以有各种各样的文件目录,也可以有一些普通的文件。那在各个目录下面,我们还可以创建更深一层的目录,也可以存放一些普通的文件。那其实这个地方所谓的目录,文件目录,就是我们熟悉的文件夹。
我们普通用户可以自己创建一层一层的目录,在各层目录当中存放一些我们想要存放的文件,所以我们的各种各样的文件,就通过目录这种功能把它们有序地组织起来了。
那其实我们平时看见的目录或者说文件夹,它也是一种特殊的有结构文件。那这个有结构文件它也是由一系列的记录组成,那具体这个目录应该是怎么实现的,这也是咱们之后会重点探讨的问题,这儿先不展开。总之文件通过目录这样的功能,把它们有序地一层一层地组织起来了,这样很方便我们用户使用。
接下来我们考虑一下操作系统应该对它的上层提供哪些功能。
那点了新建之后,其实在背后是使用了操作系统提供的一个创建文件的功能。在我们点了新建按钮之后,这个图形化交互进程其实是在背后调用了操作系统向上层提供的create系统调用来完成这个文件创建的一个工作。
那在我们创建了文件之后,这个文件的数据就已经在外存当中了。
那我们可以双击打开这个文件,为了读取这个文件的数据,那我们需要把文件的数据从外存读入内存。因为只有读入内存之后,才可以让CPU处理。所以这个地方就使用到了一个叫做读文件的功能。其实在我们双击这个txt文件之后,操作系统先是默认地打开了自动地打开了.txt对应的那种应用程序,也就是记事本这个应用程序。那这个应用程序又调用了操作系统向上提供的read系统调用来实现了读文件的功能,通过读文件的功能就可以把这个文件的数据放到内存,然后就可以呈现在用户面前。
那另外,当我们把这个文件编辑结束之后,我们可以保存文件。其实在我们点击了文件保存那个按钮之后,记事本这个应用程序在背后也做了一系列事情,主要是调用了操作系统向上层提供的这个write系统调用,write系统调用就实现了所谓的写文件功能。这个功能就是负责把文件的数据从内存再写回外存。因为我们平时编辑这个文件的时候,其实只是改变了文件在内存当中的那个副本的数据。所以为了保存这个文件的内容,我们必须再使用写文件的功能。那这是第三个需要向上层提供的很基本的功能。
第四个,既然我们可以创建一个文件,那当然我们也可以删除一个文件。
那其实在我们点了删除这个按钮之后,这个图形化交互进程是在背后调用了操作系统提供的删除文件的功能,也就是delete系统调用,这样的话就可以把文件的数据从外存当中抹除了。
那除了咱们平时肉眼可见的这一系列对文件的操作功能之外,操作系统还需要提供打开文件和关闭文件这两个基本的功能。那这个地方的打开和关闭和我们平时所谓的双击还有点那个关闭窗口的小XX其实是不一样的两个东西。那具体打开文件和关闭文件的时候需要做什么咱们之后再进行探讨。很多对文件的复杂操作可以用这些基本功能来进行组合。
因此可以看到,其实更复杂的这些功能也可以用这些基础的功能来进行实现。
那操作系统在处理这些系统调用的时候在背后需要做哪些事情这是咱们之后会再展开探讨的问题。
那接下来我们再来看一下,既然文件它是要存放在外存当中,而外存又属于一种硬件,从上往下看操作系统又是最接近硬件的一个层次,因此操作系统需要对硬件进行管理。那相应的,这些文件的数据应该被怎么放到外存这个硬件当中,当然也是操作系统需要关心和解决的问题。
首先大家需要知道的是,外存也会像内存一样,被分为一个一个的这种存储单元。那每个存储单元可以放一定量的数据,比如说是一个字节B。并且每个存储单元会对应一个物理地址。
那同样类似于内存的是,内存会被分为一个一个大小相等的内存块,外存也会被分为一个一个大小相等的块,或者叫磁盘块、物理块。每一个磁盘块的大小都是相等的,并且每个磁盘块一般会包含2的整数幂个地址。比如说在这个例子当中,一个磁盘块包含1024个地址,也就是2^10个地址,总共1KB这么大小。那相应的,文件的逻辑地址也可以分为逻辑块号和块内地址这样的两个部分。操作系统同样也需要把文件的逻辑地址转换为外存的物理地址,也就是物理块号和块内地址这样的形式。那块内地址的位数取决于这个磁盘块的大小。比如说在这个例子当中,一个磁盘块占2^10个字节这么多,所以要表示2^10个物理地址,总共就需要10个二进制位才可以表示。因此,在这个例子当中,块内地址的位数应该是10位。
操作系统是以块为单位为各个文件分配存储空间的。所以即使我们的文件大小只有10个字节,但是它依然会占用1整个块也就是1KB的磁盘块。那另外,操作系统在进行读写文件的时候,也是以块为单位进行这种数据交换的。
所以通过这个部分的学习我们知道,外存被分为一个一个的磁盘块。那么当文件比较大的时候,是不是应该连续地存放在这些一些相邻的磁盘块当中呢?
或者能不能找到某一种方法,让文件离散地存放在各个磁盘块当中,然后用某一种方式来记录下这些磁盘块的先后顺序呢?
那这是咱们之后在文件的物理结构那个部分会探讨的内容。那除了分配给文件的这些磁盘块应该怎么组织起来这个问题之外,操作系统还需要关心怎么管理这些空闲的磁盘块,怎么对这些磁盘块进行分配与回收这些问题,那这些都是咱们之后会再展开讲解的问题。所以其实文件的物理结构探讨的是文件这些数据在物理上应该是怎么存放怎么组织的一个问题。而刚才咱们提到的文件的逻辑结构,指的是文件的各个记录在逻辑上应该是什么样的一种组织关系的问题。那这些知识点通过之后的学习大家会有更精细的认识。
那除了咱们刚才探讨的这一系列功能之外,操作系统还需要实现文件的共享还有文件保护这两个比较重要的功能。并且会结合Windows操作系统文件共享还有文件保护的这个实际应用来进行探讨。
文件是一组有意义的信息的集合。另外,对于用户来说,文件名这个属性是特别重要的,还有文件的类型、文件的位置包括文件的这个存放路径还有文件在物理上的存放位置这两个信息都是特别重要的信息。关于文件有哪些属性这个问题建议大家在网上学习累的时候可以自己调试一下自己的电脑,随便找几个文件点开它的属性看一下然后这样的话就可以加深对文件属性相关的一些理解还有记忆。那在之后的讲解当中我们会依次介绍文件的逻辑结构还有目录的实现。另外操作系统对上层提供的一系列的系统调用的功能在背后做了一些什么事情这些都是之后会探讨的内容。那除了逻辑上的结构之外,文件在外存当中是怎么存放的,也就是文件物理结构的问题也十分重要。操作系统又应该怎么对文件的空闲块进行管理,那这是存储空间管理相关的内容。最后我们还会介绍文件共享和文件保护这样两个很重要但是平时咱们很少使用过的功能。其实文件管理的功能离我们生活并不遥远。我们每天都在使用操作系统提供的文件管理的功能。
那其實在數據結構那門課當中也接觸過很多邏輯結構和物理結構的問題。但是對一種邏輯結構來説,也可以用不同的物理結構來實現。那這個文件可以分爲無結構文件和有結構文件兩種。我們需要重點討論的是有結構文件的這幾種邏輯結構。
上個小节中我们已经知道,所谓无结构文件它其实就是由一系列的二进制流或者说字符流组成,又称为流式文件。比如说像咱们Windows操作系统里的.txt文件就是一种很典型的无结构文件。那由于这种结构没有很明显的这种结构特性,所以我们也不必要来讨论它的这种逻辑结构的问题。
因此我们重点关注的是有结构文件。它由一组相似的记录组成,又称为记录式文件。而每条记录又由若干个数据项组成,比如说像我们的数据库表文件。那一般来说每条记录可以有一个数据项作为关键字。像这个数据库表当中,就可以用学号这个数据项作为关键字来作为识别不同记录的一个ID。那每个学生会对应一条记录,每条记录又由若干个数据项组成。其中的学号可以作为记录的关键字。那这是有结构文件它由一系列的记录组成。
根据各条记录的长度,也就是占用的存储空间是否相等又可以把这些记录分为定长记录和可变长记录两种。
所以既然每一个数据项的长度都相同,那么每一条记录的这个总长度也是相同的,也就是128个字节。并且在定长记录当中,每个数据项在记录当中所处的位置都是相同的。
不过可变长记录的这种有结构文件其实才是咱们生活当中最常见的一种有结构文件。因此由于每个记录的特长这个数据项长度是不确定的,有的特别多,有的又特别少,有的甚至没有。所以像这些记录,它就是一种可变长的记录。那像之前的那个例子当中,专业这个数据项用几个字就可以描述,所以即使我们给各个学生的专业那个数据项分配了总共固定60个字节的这种长度,那即使有存储空间的浪费,也不至于特别明显。但是像这个例子当中,如果我们给每个学生的特长这个数据项都分配一个巨长、巨大的一个存储空间的话,那显然有的学生对这个数据项的存储空间利用是很不充分的。那这样的话就会存储空间利用率极低的一个问题。所以在这个例子当中,最好是让这个有结构文件的这些记录是可变长的记录。
那接下来我们需要再讨论,这些记录应该在逻辑上怎么被组织起来的问题,也就是有结构文件的逻辑结构的问题,分为顺序文件、索引文件和索引顺序文件这三种。
我们首先来看顺序文件这种逻辑结构。如果是顺序文件结构的话,那显然记录到底是定长的还是可变长的,这些记录是否按照关键字有序地排列,另外这些记录在物理上到底是顺序存储还是链式存储,所有的这些区别都会影响顺序文件到底能不能实现某一些操作的功能。
那接下来我们会重点讨论两个问题。也就是说是否能实现这些记录的随机存取、随机访问这件事情呢?
我们直接给出结论。假设一个顺序文件在物理上是采用链式存储的方式来实现的话,那么无论这个顺序文件它是定长记录还是可变长记录,也无论它是串结构还是顺序结构,它肯定都无法实现随机存取。每次只能从第一个记录开始依次往后寻找,那这个和我们的链表很类似。如果我们要在一个链表当中找到某一个元素的话,那么我们每次都只能从链头开始依次往后寻找。因此各个元素之间的存储位置它并没有规律,它都是离散的。所以我们并不能直接计算出某一个元素在物理上的存放地址。因此,如果我们采用的是链式存储的话,那么对这个顺序文件的记录的检索、查询都是不太方便的。那如果说采用的是顺序存储这样的物理结构的话,是不是会有所不同呢。假设采用的是顺序存储的物理结构,并且这个文件是可变长记录的文件,那它依然是无法实现随机存取的,每次只能从第一个记录开始依次往后查找。
来看一下为什么。由于这个文件的记录是可变长的,也就是每个记录的长度不一样,所以必须在每个记录之前用一定的存储空间来表示这个记录的长度。
假设这个记录长度可以用一个字节来表示,那第0条记录它的逻辑地址是0,第2条记录就应该是第0条记录的长度L0再加上它的这个记录长度这个字段占用的字节数1个字节,所以L0+1这是第一个记录对应的逻辑地址。那相应的应该是之前所有的这些记录的长度之和再加上所有的这些记录的这个长度字段所占用的字节,每一个占用1个字节。那前面总共有i个记录的话,总共就会占用i个字节。所以由于这个文件中的记录是可变长的,L0、L1、L2这些数据并不会呈现出某种规律性。因此我们想要找到某一条记录对应的地址的话,那么只能从第一条记录开始依次往后寻找。因此,如果说这个文件是可变长记录的文件,那么即使它采用了顺序存储的这种方式,依然无法实现随机存取。
那如果说这个文件是一个定长记录的顺序文件,并且在物理上是采用顺序存储的方式的话,情况就不一样了。这就可以实现随机存取。假设每个记录的长度固定为L,并且这些记录在物理上是顺序存储的,那就意味着各个逻辑上相邻的记录在物理上也是相邻地存储的,所以第0号记录的这个逻辑地址为0的话,那么i号记录的逻辑地址直接可以用i*L就可以直接得到。因此如果是定长记录的顺序文件,并且在物理上也是顺序存储的话,那么是可以实现随机存取的功能的。那么如果说这个顺序文件是串结构,也就是说这些记录的顺序和它们的关键字顺序是没有关系的话,那么这就无法快速地找到某个关键字对应的记录。因为它并不是按关键字的顺序来排列的,所以我们每次只能从头开始依次往后遍历地寻找这个关键字对应的记录。但是如果说这个定长记录的顺序文件采用的是顺序结构的话,那么也就意味着这些记录是按照关键字的顺序来排列的。那这样的话我们就可以用像折半查找啊之类的一些方法来快速地找到某一个关键字对应的记录。
那到这个地方我们就回答了之前提出的两个问题,关于是否可以实现对各个记录的随机存取这件事,我们得出的结论是,如果说物理上采用的是链式存储,那么肯定是无法实现记录的随机存取的。而如果说物理上采用的是顺序存储的话,那么可变长记录的顺序文件是无法实现随机存取的。而定长记录的这个顺序文件是可以实现随机存取的。如果说在这个基础上,再能保证这个定长记录的顺序文件它是一种顺序结构,也就是按照关键字的顺序来排列的话,那么我们就可以实现快速地检索某一个记录的这个功能。
那一般来说考题当中所指的顺序文件,默认的是指这个物理上采用顺序存储的这种顺序文件,所以我们不需要考虑各个记录链式存储的这种情况。在之后我们的讲解当中,再提到顺序文件的时候,也是默认如此。那显然,在顺序表那种数据结构当中,要增加或者删除一个元素是比较困难的。
同样的,如果采用的是顺序存储的这种结构的话,那么它要删除或者增加一个记录也是比较困难的。不过如果采用的是串结构的话,那么由于不需要保证各个记录按照关键字来排序,因此,对于串结构的这个顺序文件来说,增加和删除一个记录相对来说要简单一些。我们只需要很简单地把要增加的那个记录插到这个文件的末尾就可以了。那在实际应用当中,为了减少磁盘的I/O次数,一般来说系统、操作系统会管理一个日志文件,用这个日志文件来记录对于这个文件当中的各个记录进行修改的一些信息。然后每隔一段比较长的时间,再把这些信息统一地合并到外存当中的这个文件数据当中。比如说每隔一个小时才合并一次,或者说每隔十分钟才合并一次,那这样的话就可以减少对于顺序存储的顺序文件进行增删改所带来的一些开销了。不过这个知识点只是简单地提一提,在考试当中并不会进行考查。那以上的这些内容是顺序文件这种逻辑结构当中需要特别注意的一些知识点。特别是定长记录可以实现随机存取,而可变长记录不可以实现随机存取这两件事。
接下来我们再来看第二种逻辑结构,索引文件。通过之前的讲解我们知道,如果说一个顺序文件它是可变长记录的话,那么要找到第i个记录就必须先顺序地查找前面的i-1个记录。但是在实际生活当中,又有很多应用场景又必须使用可变长记录。能不能解决可变长记录的这种查找速度慢的问题呢?能否让可变长记录的文件也实现可以随机访问的这个功能呢?基于这个需求,人们提出了索引文件这种逻辑结构。
每一个文件会建立一张索引表。
并且每一个索引表的表项会对应这个文件的一条记录,文件的这些记录在物理上不需要连续存放。但是索引表的各个表项在物理上是需要连续存放的。另外,每一个索引表的表项的这个大小都是相等的。比如说,索引号、长度、指针这三个字段分别占4个字节,那么1个索引表的表项总共就需要占12字节的长度。
因此索引表本身也可以理解成是一种定长记录的顺序文件。那经过之前的讲解我们也知道,定长记录的顺序文件是可以支持随机访问的,所以我们可以快速地找到第i个记录对应的索引表项到底是存放在什么地方。
另外呢,如果我们把这个关键字作为索引号的内容,并且让索引表当中的这些表项按照关键字的顺序来排列的话,那么我们就可以让这个索引文件支持折半查找。这样的话就可以大幅度地提升索引文件的检索速度。
不过既然每一个文件的记录会对应一个索引表项,那么我们要增加或者删除一个记录的时候,当然也需要把对应的这个表项进行相应的处理。那要增加一个记录的时候,也需要增加一个相应的索引表项。要删除一个记录的时候,也需要把它对应的这个索引表项也给删除。那通过之前的讲解我们发现,索引文件这种逻辑结构可以支持很快的检索速度,所以这种逻辑结构主要是用在对于信息处理的及时性要求很高的那种场合。
另外呢,除了用这个关键字作为索引号之外,我们还可以用别的不同的数据项作为索引号来为一个文件建立多个索引表。如果学过数据库的同学就知道,在SQL语言当中,就可以用一条SQL语句就完成为某一个字段建立一张索引表的这个功能了。那这是索引文件。
接下来我们再来看最后一种逻辑结构————索引顺序文件。索引顺序文件它是一种索引文件和顺序文件思想的结合。与索引文件类似的是,索引顺序文件当中同样会为一个文件建立一张索引表。但是与索引文件不同的是,
索引顺序文件当中,并不会为每一个记录建立一个对应的索引表项。而是会给这些记录进行分组,然后每一个分组对应一个索引表项。
比如说在这个例子当中,就是按照学生的姓氏把学生记录进行了一个分组,
然后每一个分组会对应一个索引顺序文件的索引项,每一个索引项记录了这个分组的名字还有这个分组存放的一个位置。那从这个地方可以看到,索引顺序文件的索引项并不需要按照关键字的顺序来排列,那这样的话是可以更方便我们对新表项的插入操作的。也就是说索引顺序文件的索引表它其实是一个定长记录的串结构的顺序文件。另外,这样的一个分组就是一个顺序文件。那可以看到,采用索引顺序文件这种逻辑结构之后,索引表的表项是少了很多的。
所以我们完成了刚才提出的那个需求,也就是对索引表进行了瘦身、减肥的工作。那如果采用这样的策略的话,会不会出现检索速度慢的问题呢?
我们来分析一下。那我们要找到这个关键字对应的记录的话,就平均需要查找5000个记录。
但是如果我们把这个文件改造成这种索引顺序文件的这种逻辑结构的话,我们可以把这10000个记录平均分为100组,然后每组100个记录。这样的话我们要查询某一个关键字对应的记录,首先是需要顺序地查找这个索引表,找到这个关键字对应的那个分组,那由于这个索引表只有100个索引项,因此平均只需要查找50次就可以找到一个关键字对应的分组了。那相应的,找到了它对应的分组之后,在这个分组内有100个记录,因此接下来我们需要顺序地查找这100个记录,那平均只需要查找50次就可以找到这个关键字对应的记录存放的位置了。所以其实采用了这种索引顺序文件结构之后,平均的查找次数就减少为了50+50,总共100次。因此这种逻辑结构也是具有比较好的检索性能的。
那同样的道理,如果说一个文件有10^6个记录,那我们可以把它分为1000组,每个分组是1000条记录,根据关键字来检索一个记录,就平均需要500+500总共1000次查询。那这1000次其实查找的次数依然是很多的,那怎么解决这个问题呢?
那我们可以建立多级索引。对于刚才所说的这个例子来说,我们可以为这个文件先建立一张低级索引表,那每一个索引表对应100个表项,每一个表项又对应一个分组,每一个分组当中又是100个记录。那不难算出,我们总共会有100张低级索引表。因此,我们又可以为这100张低级索引表再建立一个*的索引表,这样的话就形成了两级索引顺序文件。那*索引表中总共有100个表项,低级索引表中每个索引表中也是有100个表项,那每一个表项又会对应一个分组,每一个分组中又会有100个记录。
因此,如果我们按照这样分组的话,那根据一个关键字来检索一个记录平均需要查找的次数就是150次。
无结构文件由二进制流或者字符流组成,没有明显的逻辑结构,所以这个地方我们不展开探讨。我们需要重点掌握的是有结构文件的这几种逻辑结构,分为顺序文件、索引文件和索引顺序文件。而有结构文件它是由记录组成的,分为定长记录和可变长记录。
那我们在考试中遇到的所谓的顺序文件,指的是默认各个记录在物理上顺序存储的这种物理结构。
需要重点注意的是,对于可变长记录的顺序文件来说,它是无法实现随机存取的,但是定长记录可以。可变长记录的顺序文件每一次在查询的时候只能从头开始依次往后查找。
第二个需要注意的点是定长记录、顺序结构的顺序文件可以快速检索。所谓的顺序结构就是指这些记录按照关键字的顺序依次排列。所以由于这些记录排列的顺序是有规律可循的,因此我们可以用像折半查找之类的方法来快速地找到一个关键字对应的记录。
那在索引文件这种逻辑结构中我们需要注意的是,索引表本身就是一种定长记录的顺序文件。
而如果说索引表按照关键字的顺序排列,那它同样也可以像之前所说的这样支持快速检索的功能,也就是根据某一个关键字来快速找到某一个记录的功能。那由于快速检索的这个功能和特性,因此索引文件也经常被使用在那些对于检索速度要求很高的那些场景当中。
那在最后我们介绍的索引顺序文件当中,除了要理解它的这一些原理之外,我们还需要会动手计算平均查找次数。
文件目录这个特别重要的知识点。
对文件目录的使用其实是很频繁的。比如说像Windows操作系统,我们随便打开一个逻辑磁盘,比如说D盘,里面就有很多各种各样的文件夹或者文件目录,并且在这个D盘下面它也会有一系列的普通的文件。那打开其中的某一个文件夹、文件目录之后,里面还会有更深一级的文件目录还有一些普通的文件。那像这种一层一层的目录结构,对于我们用户来说有什么好处呢?可以使整个文件的存放组织结构非常清晰,易于我们查找。另外,如果有编程经验的同学,应该也写过操作文件相关的一些函数。比如说像打开文件这个操作,它其实就是用我们提供的文件路径名作为参数,然后根据这个路径一层一层往下去找到我们想要让这个程序控制的那个文件的。所以采用这样的目录结构的话,可以让用户很轻松地实现按名存取这件事情。
其实就是用一个所谓的目录表来表示这个目录下面它存放了哪些东西。那在D盘当中的每一个文件,每一个文件夹都会对应这个目录表当中的一个表项。
所以其实这些一条一条的目录项,本身就是一个一条一条的记录。所以目录文件其实本身就是一种有结构的文件,它是由一条一条的记录组成的。而每一条记录会对应在这个目录下面的一个文件。因此我们在这个地方看到的目录其实它也是一种特殊的文件。那可以看到,在这个表当中,标识了文件的文件名是什么,还标识了文件类型。比如说像照片这个文件,它其实是一个目录文件,所以它的类型标识是目录。那像对账单这个文件,它的类型就是.txt文件。另外呢,在这个地方,我们还需要注意一个很重要的信息就是,在这个表项当中,记录了这个文件在外存当中存放的物理地址,放在外存中的什么位置。
所以其实我们双击打开目录的时候,操作系统在背后做的事情是,它会来查询这个根目录,D盘这个根目录的目录文件,然后找到照片这个文件对应的目录项,之后根据这个目录项当中记录的文件的存放位置,从外存当中读入照片这个目录文件的数据,这样的话就可以知道照片这个目录下面还有哪些内容。那这些内容就可以显示展示在我们用户面前了。
那么同样的,照片这个目录对应的目录文件它也是由一条一条的目录项组成的。每个目录项会对应其中的一个文件。
那其实目录文件当中的这样一条记录,它就是一个文件控制块,英文缩写是FCB。
所以其实这些FCB的有序结合,就是所谓的文件目录。而一个FCB,就是一个文件目录项。那很显然,每一个文件都会对应一个FCB。
另外,从这个例子中我们可以看到,FCB中包含了一个文件的基本信息,包括文件名、物理地址还有文件的逻辑结构、物理结构等等一系列的基本信息。另外还会有文件存取权限,存取控制相关的一些信息,包括文件是否可读、是否可写等等。那除了这些之外,还会有文件的一些使用信息,包括文件是在什么时候建立,上次修改的时间是多少等等。
不过所有的这些信息当中,最重要的还是文件名还有文件的存放的物理地址这两个信息。
因为FCB,存在的最重要的一个作用其实是为了实现让用户按名存取,就是按照文件的名字来操作一个文件这样的事情。所以FCB它必须建立起文件名到文件实际存放的物理地址这样的一个映射关系。因此,最重要、最基本的,应该是文件名还有物理地址这样的两个数据。那除了这个地方提到的这些信息以外,其实咱们之前提到过的文件的各种各样的那些属性也可以存放在文件对应的FCB文件控制块当中。
那接下来我们再来看一下,我们需要对文件目录进行哪些操作呢?首先,为了让用户能够实现按名存取,那为了实现这件事,肯定是需要有一个对目录搜索的过程。当用户需要使用一个文件的时候,系统需要根据这个文件名来搜索目录,然后找到这个文件名对应的目录项。
第二,当我们创建一个文件的时候,这个文件肯定是放在某一个目录当中的,所以在这个文件所属的目录当中,就需要增加一个目录项。
那与创建文件相反,当我们删除一个文件的时候,除了删除文件数据本身之外,也需要删除这个文件在目录当中对应的这个目录项。
另外呢,系统还需要提供显示目录的功能。因为用户在查看各级目录的时候,肯定是需要知道这个目录下一级到底还有哪一些文件的一些目录,所以这个功能也是必须实现的。那在显示目录的时候,可以显示与这些文件相关的一些属性,这一点大家可以在自己的Windows电脑上具体去看一下。
那最后我们需要知道的是,文件的各种属性是保存在目录当中的,所以如果说文件的某些属性发生变化的时候,那相应的肯定需要修改与它对应的那个目录项的内容。比如说我们对一个文件重命名,那么我们就需要把这个文件对应目录项的文件名这个信息给修改掉。那这就是一般来说需要对目录进行的一些操作。那通过之前的讲解我们知道,由文件控制块的有序集合,就组成了文件的目录。
在操作系统发展的过程当中,出现了各种各样的目录结构。在刚开始出现的叫做单级目录结构。早期的操作系统它只会在整个系统当中建立一张目录表,每个文件会占用一个目录表的目录项。
那这种单级目录结构是支持按名存取的,因为这个FCB当中其实也是包含文件名这个关键字。但是单级目录最大的一个缺点就是,不允许文件重名。可以想象一下,各个目录项的关键字是文件名,那么如果说出现了重名文件的话,比如说有一个文件叫A,另一个文件也叫A,那么当我们告诉操作系统,我们要按照文件名A来存取一个文件的时候,那操作系统到底应该选择哪一个文件呢?因此在单级目录结构当中,是不允许文件重名的。
所以我们在创建一个文件的时候,首先需要检查这个目录表当中到底有没有重名的文件,确定不重名之后,才允许建立新文件,并且把新文件对应的那个目录项,也就是FCB,插入到这个目录表当中。
那如果说这个计算机有很多用户在使用的话,那显然不同的用户的文件名是很容易重复的。因此,单级目录结构不适用于多用户操作系统。
那为了解决这个问题,后来人们提出了两级目录结构。在这种目录结构当中,会把目录分为两级。一级是主文件目录,英文缩写是MFD和用户文件目录,英文缩写是UFD。
那主文件目录就是记录了用户名还有这个用户名对应的用户文件目录存放的位置。
而一个用户文件目录又由这个用户的那些文件对应的FCB组成。
那由于不同的用户文件是存放在不同的用户文件目录下的,所以在这种情况下,不同用户的文件是允许重名的。比如说像User1这个用户,它有1个文件名叫demo的文件。User2这个用户它也有一个文件名叫做demo的文件。不过虽然它们的文件名是相同的,但是它们实际对应的文件数据是不同的两个数据。
那除了允许不同用户的文件重名之外,在采用了两级目录结构之后,也可以通过目录来实现访问限制。比如说User1想要访问User2的这个用户文件目录的话,那操作系统可以验证一下User1和User2这两个名字是否匹配。那发现它不是User2,就可以拒绝让它访问User2对应的这个用户文件目录。因此采用了这种两级目录结构之后,还是很方便实现这种对于访问的限制的。但是采用这种目录结构的缺点就是,用户不可以把自己的文件进行分类。
因此人们又提出了多级目录结构,又叫树形目录结构。那这也是现代操作系统当中很常用的一种目录结构。每一个目录下面可以有更低一级的目录,同时在各个目录下面也可以有一些不同的文件,并且不同目录下的文件是可以重名的。那和刚才一样,不同目录下的文件虽然说名字是相同的,但是它们实际对应的文件并不是同一个。
那如果说采用的是多级目录结构的话,用户或者用户进程想要访问某一个文件的时候,就需要用文件的路径名/标识符来让操作系统根据这个文件路径名找到这个文件存放的位置。那各级目录之间一般来说是用这个小斜线来隔开的。从根目录出发的路径,称为绝对路径。
比如说,像自拍.jpg这个文件的绝对路径,也就是从根目录出发的路径,就应该是根目录下面的照片这个目录,然后照片这个目录下面的2015-08这个目录。在20150-08这个目录下面,才可以找到自拍这个文件对应的目录项。
那用户或者说用户进程想要访问这个文件的时候,就需要把这个路径名告诉系统,然后操作系统会根据这个绝对路径一层一层地往下找下一级的目录。刚开始它会从外存当中调入根目录对应的这个目录表也就是这个目录文件,然后找到照片这个目录存放的位置,然后又从外存当中调入照片这个目录文件,然后再从这个目录当中找到2015-08这个目录在外存当中存放的位置。于是还需要从外存调入2015-08这个目录对应的目录表文件。那最后,再查询这个目录表才能找到自拍.jpg这个文件存放的实际位置。所以如果从根目录这个地方开始一层一层往下寻找的话,那整个过程需要3次读取磁盘的I/O操作。
不过在实际生活当中,用户经常会访问同一个目录内的多个文件。比如说一个用户它在连续地查看2015-08这个目录下面的各种各样的照片,那就意味着这个目录当中的那些内容是经常会被访问的。但是如果每一次访问2015-08当中的文件,都需要从根目录开始一层一层地往下寻找的话,那显然每一次都需要3次读磁盘操作,这是很低效的。
所以我们可以设置一个当前目录。比如说此时我们已经打开了照片这个目录文件,也就是说照片这个目录文件已经是从外存调入内存当中的了。那我们可以把照片这个目录设置为当前目录。当用户想要访问某个文件的时候,就可以从当前目录出发,然后找到自己想要的那个文件。那从当前目录出发的这种文件路径就叫做“相对路径”。
比如说像Linux系统当中,如果说当前目录是照片这个目录,然后我们想要用相对路径来表示自拍.jpg这个文件的话,那么它的相对路径就是当前目录下面的2015-08这个目录下面的自拍.jpg这个文件。所以如果从当前目录出发的话,那么想要找到自拍.jpg存放的位置,我们只需要先查询内存当中当前目录的这个目录表,找到2015-08这个目录文件在外存当中的存放位置,然后把这个目录调入内存。于是再从这个目录当中找到自拍.jpg存放的位置。
那这样的话整个过程只需要经过一次读磁盘操作就可以知道自拍.jpg存放的位置了。所以可以看到,在引入了当前目录和相对路径这种机制之后,磁盘I/O的次数减少了,这就提升了访问文件的效率。
不过树形目录结构也并不是万能的。它可以很方便地对文件进行分类,层次结构清晰,也可以很方便地对文件进行管理和保护。但是树形结构不便于实现文件的共享。那树形目录结构不便于实现文件共享这个知识点也是经常在选择题当中进行考查的。那为了解决这个问题,人们又提出了无环图目录结构。
其实无环图目录结构和树形目录结构也比较相似,只不过是在树形目录结构的基础上增加了这样的一些指向同一个节点的有向边,使整个目录的结构看起来是成为了一个有向无环图。那有向无环图相关的知识点是在数据结构当中进行学习的。那这样的话就很方便地可以实现多个用户间的文件共享。
那这个地方大家肯定也会发现,可以用不同的文件名指向同一个文件。也就是说User1这个用户可以用demo这个文件名找到这个文件,而User2这个用户可以用Mydemo这个文件名找到这个文件。它们指向的都是同一个,这个文件是被它俩所共享的。那除了共享一个文件之外,甚至可以共享同一个目录。因为目录其实本身也是一个特殊的文件。在引入了共享功能之后,对于文件的删除就不能像以前那么简单。只要一个用户让删除一个文件,就把这个文件的实际数据给删除,因为这个文件有可能是被多个用户使用的。
所以为了解决这个问题,可以为每一个这种共享节点设置一个共享计数器。比如说此时这个文件是正在被两个用户共享的,那么共享计数器就应该是2。
那此时如果用户1提出要删除文件这个请求的话,那其实操作系统只是会把User1对应的这个目录项给删除,并且让共享计数器减1。而这个文件实际的内容并不会被直接删除。
只有这个共享计数器的值减为0的时候,就意味着这个文件不再被任何用户所使用。那在这个时候才可以把这个共享文件真正地删除。
那大家要注意的是,共享文件和复制文件其实并不一样的。如果说User1只是复制了一个User2的这个文件的话,那其实它们俩所拥有的这个文件并不是同一个文件。当User1对自己的这个文件副本进行修改的时候,原来的这个文件的数据并不会被改变。而如果说这个文件是被两个用户所共享的话,那么由于它们指向的其实都是同一份文件的数据,因此只要其中的一个用户对这个文件数据进行更改,那另一个用户那边也是可以看到这个文件数据的变化的。那以上就是我们需要掌握的四种目录结构,单级目录结构,两级目录结构,树形目录结构和无环图目录结构。
那最后我们来介绍一下什么是索引结点。这其实是对FCB这种数据结构的一种改进。通过之前的学习我们知道,由一系列的FCB也就是文件控制块组成了一个一个的文件目录。但是其实操作系统在查找各级目录的过程当中,只需要使用文件名这个信息就可以了。而其他的这些冗余的信息暂时不需要。那只有文件名匹配的时候才需要去关心这个文件存放的物理位置。所以我们可以考虑让这个目录表进行一个简化,来提升这个搜索的效率。那由于按照文件名来搜索目录的时候,并不需要关心除了文件名之外其他的所有的信息。
因此可以把其他的这些信息放到另外一个地方,也就是索引结点当中。那除了文件名之外,像文件的类型,文件存放的物理位置等等这些信息,都会放在文件对应的索引结点当中。每一个文件都会有一个唯一的索引结点。而采用了索引结点这种机制之后,目录当中只包含文件名还有指向索引结点的指针这样的两个信息。那这样的话,这个目录表所需要占用的空间就会小很多。那我们来看一下采用这种方式到底是怎么加快我们查找一个文件的效率的呢?
我们假设一个文件控制块是64个字节,一个磁盘块的大小是1KB。那么每个磁盘块只能存放16个FCB。也就是说,每个磁盘块只能有16个目录项。因此,如果一个文件目录当中,总共有640个目录项的话,那么这么多的目录项,总共需要40个磁盘块才能存储。那在这种情况下,我们按照文件名来检索这个目录,平均需要查找320个目录项。那由于320个目录项需要20个磁盘块才可以存放的下,所以平均就需要启动磁盘20次,因为每次启动磁盘的读操作都只能读入一个磁盘块的内容。
但是如果我们采用的是索引结点机制的话,假设文件名只占14个字节,然后索引结点的指针只占两个字节,那么每个磁盘块就可以存放64个目录项,于是我们根据文件名按顺序来检索这个目录的时候,平均需要查询320个目录项,而320个目录项,只需要5个磁盘块就可以存放了。所以如果采用的是这种方式的话,那么只需要5次启动磁盘的操作。那由于I/O操作都是比较耗时的,所以启动磁盘的次数减少了很多,那么在检索文件的时候速度也会提升很多。所以这是把其他的这些冗余信息全部放到索引结点当中所带来的一个好处,就是让文件的检索速度更加的快捷。
那系统根据文件名,找到它所对应的索引结点之后,需要把这个索引结点调入内存。之后再根据这个索引结点当中保存的文件存放位置就可以找到这个文件了。
那在外存当中的索引结点称为磁盘索引结点。当索引结点放入内存之后就称为内存索引结点。相比于磁盘索引结点来说,内存索引结点需要再增加一些信息。比如说记录这个文件是否被修改,或者记录此时到底有几个进程正在访问这个文件等等这一系列的信息。
大家需要理解文件、FCB、目录项还有目录之间的一个组成的关系。
另外,大家也需要理解并记住在单级、两级还有多级目录结构当中,最主要的这个问题、缺点到底是什么。那其实每一种目录结构都是解决了上一种目录结构留下的这个最主要的问题。
那在多级目录结构当中,大家需要注意绝对路径、相对路径还有当前目录这样的几个概念。
并且需要能够理解为什么根据相对路径来检索文件可以减少磁盘I/O的次数。那其实背后的原因在于,每查询下一级的目录的时候,都需要启动磁盘I/O,把下一级目录对应的目录文件从外存调入内存。
那多级目录结构当中不方便实现文件的共享,但是无环图目录结构很方便地可以实现文件共享。但是需要注意的是,每个共享结点会有一个共享计数器。只有这个共享计数器的数值为0的时候,才可以真正地删除这个共享结点。那最后我们介绍了文件目录的一个优化方式,除了文件名之外的所有信息都放到索引结点当中。这样的话就可以让目录表的表项就大幅度地减小,从而每个磁盘块可以存放更多的目录项。因此,我们根据文件名来检索文件的时候就可以有更少的磁盘I/O的次数。
那为什么磁盘I/O的次数会更少,这一点大家也需要理解。那么这个小节的内容十分重要,很容易在选择题当中进行考查。那大家还需要通过课后习题进行进一步的巩固。
开始学习文件的物理结构也就是文件分配方式相关的一系列问题。那么这个小节的内容十分重要,在历年的考试当中都经常出现2-3题的这种选择题,甚至经常会在大题当中进行考查,所以这个小节的内容我们会讲的比较细致,会补充一些课本上没有提到的点。
那么在之前的学习当中我们知道,操作系统它作为最接近硬件的一个软件层次,需要对硬件进行管理,包括外存,也就是磁盘进行管理。那么操作系统对磁盘的管理主要是需要做这样两件事。第一,是需要对非空闲磁盘块进行管理。那么非空闲磁盘块也就是存放了文件数据的那些磁盘块。那这就是这个小节要重点探讨的文件物理结构、文件分配方式的问题。另外呢,还需要对空闲的磁盘块,也就是暂时还没有存放任何数据的那些磁盘块进行管理。那这是咱们之后的小节会探讨的文件存储空间管理的问题。那这个小节我们先看上面这个问题。
其实文件物理结构或者说文件分配方式要探讨的就是,文件的数据到底应该怎样存放在外存中。这些数据应该被怎样组织起来的一个问题。那总共被分为这样的三个模式,连续分配、链接分配和索引分配。那其中链接分配又可以进一步细分为隐式链接和显式链接这样的两种。在这个视频当中,我们首先探讨连续分配和链接分配这两种方式。那索引分配会在下个小节中再进行讲解。
那在正式开始之前我们需要先补充几个比较重要的知识点。在之前我们简单地提到过,类似于内存分页,磁盘中的这些存储单元会被分为一个个大小相等的块。那这些块也可以称为磁盘块或者物理块。相应的,系统也会为这些磁盘块进行一个编号。那像这个是0号块,这个1号块,依此类推。另外,在很多操作系统当中,磁盘块的大小会设置的和内存块、内存页面的大小相同。
那这么做是有好处的。因为内存和外存之间的数据交换,也就是对磁盘的读写操作,都是以块为单位进行的。每次读入一块,或者每次写出一块。那如果说能够保证外存的一个磁盘块和内存的一个内存块的大小是相等的,那么进行这种数据交换的时候就会很方便。
那在之前内存管理那个章节中我们学习过,进程的逻辑地址空间会被划分为一个一个的页面,这样可以方便操作系统对进程进行管理。那相应的,在外存管理当中,为了方便对文件数据的管理,文件的逻辑地址空间,也会被划分成一个一个大小相等的文件快。因此,文件的逻辑地址也可以表示为逻辑块号和块内地址的形式,那这就类似于一个进程的逻辑地址可以表示为页号和页内地址的形式。
就像这个样子。那假设,这个系统当中一个物理块的大小是1KB,那么1个1M字节大小的文件就可以分割为1K个块,所以这个文件的逻辑块号应该是从0到1023,总共1K个块号。并且每一块的大小都是1KB这么大,因此用户在操作自己的文件的时候可以用逻辑块号还有块内地址这样的形式就可以定位到自己文件当中的任何一个位置。
那这个文件的数据被分为一个一个的逻辑块之后,操作系统为文件分配存储空间,也都是以块为单位进行的。
比如说,逻辑块号为0的这个块,被放到了物理块号为4的这个磁盘块当中。然后逻辑块号为1的这个块,放到这个位置。逻辑块号为1023,放到这个位置。不过用户对于自己的文件的这些各个逻辑块到底存放到了什么地方,这个信息用户是不可知的。
因此,用户在操作自己的文件的时候,是使用的是逻辑块号和块内地址这样的形式。于是操作系统就需要负责把用户提供的这个逻辑块号和块内地址转换为这个文件块实际存放的物理块号和块内地址,那这也是文件的分配方式,文件物理结构这个部分需要重点关注的核心问题——怎么把逻辑块号映射为物理块号。
那我们首先来看第一种文件分配方式——连续分配。连续分配方式的思想很简单,要求每个文件在磁盘上占用一组连续的块。比如说一个文件“aaa”它在逻辑上可以分为这样的三个块。
那如果说采用连续分配方式的话,那这些逻辑上相邻的块在物理上也必须相邻,也必须是占用一组连续的块,并且依然需要保持这些块之间的这种相对顺序。比如说0号逻辑块放到了4号物理块当中,1号逻辑块放到了5号物理块当中,2号逻辑块放到6号物理块当中。
那接下来我们要注意的问题是,如果采用的是这种方式的话,那么操作系统应该如何实现从逻辑块号到物理块号的这种映射和转变呢?那首先我们需要明确的是,用户在操作自己的文件的时候,使用的是逻辑块号、块内地址这样的一个逻辑地址。那其实块内地址是不需要转变,因此操作系统只需要关心逻辑块号到物理块号的映射就可以了。
那为了实现这个地址映射的功能,在文件的目录表当中,必须记录两个文件的属性。第一是一个文件存放的起始块号,第二是这个文件的长度,也就是它总共占用了都少个块。比如说像“aaa”这个文件,它的起始块号是4,并且它占用了连续的3个块,因此它的长度是3。而“bbb”另外一个文件,它的起始块号是10,然后它占用了连续的4个块,因此它的长度又是4。那有了这些信息,操作系统就可以完成这个地址转换的事情了。
假设用户给出了他想要访问的逻辑块号,那么操作系统需要先找到这个用户想要访问的文件对应的目录项,也就是FCB。那在这个FCB当中,就可以知道文件存放的起始块号,再用起始块号加上用户提供的逻辑块号就可以得到这个块存放的实际的物理块号了。比如说这个用户想要访问“aaa”这个文件的逻辑块号为2的这个块,那么逻辑块号2加上它的起始块号4就得到物理块号6。那么从这个图当中我们也可以看到,逻辑块号为2的这个块确实是存放在物理块号为6的这个位置的。
当然,操作系统还需要验证用户提供的这个逻辑块号是否合法,它是否已经超过了这个文件的实际长度。
所以通过刚才的这个分析我们会发现,如果采用的是连续分配方式的话,那么只要用户给出了自己想要访问的逻辑块号,操作系统就可以直接根据逻辑块号算出它对应的物理块号到底是多少。因此,我们说连续分配方式是支持顺序访问和直接访问,也就是随机访问的。那所谓的顺序访问就是指,如果我要访问逻辑块号2的话,那么我必须先顺序地访问逻辑块号0和逻辑块号1,之后才能找到逻辑块号2,这是顺序访问。而所谓的直接访问或者说随机访问就是指,如果我要访问逻辑块号2的话,那我并不需要访问其他的这些块,我可以直接找到逻辑块号2存放的位置,所以这是顺序访问和直接访问的含义。那支持直接访问或者说随机访问也是连续分配方式最大的一个优点。
那我们再来分析连续分配方式的第二个优点,这个地方我们需要用到一个现在暂时还没有讲过的知识点。磁盘这种硬件想要读取一个磁盘块的时候,需要把磁头放到那个磁盘块相应的位置。而访问不同的磁盘块,是需要移动磁头的。并且如果两个磁盘块的距离越远,那么移动磁头所需要的时间就越长。比如说,我现在需要连续地读这三个黄色的块,那么首先是把磁头放到第一个块的位置,之后移动到第二块的位置,再之后再移动到第三个块的位置。所以由于这三个块它们之间在物理上是相邻的,也就是它们相距距离是最短的,因此这个移动的过程其实磁头的移动距离也是最短的。而假如我们此时想要读取的是紫色的这三个磁盘块的话,那么首先需要把磁头移动到第一个这个块对应的位置,读完了第一个块之后,需要移动磁盘而移动到第二个位置。而读完第二个磁盘块之后,也需要再移动磁盘,然后把它移动到第三个块的位置。因此如果这些块不相邻的话,那么读取一个块和下一块之间所需要的移动磁头的时间就会更长。
因此基于这个分析我们知道,如果一个文件采用的是连续分配的方式的话,那么我们在顺序地读取这个文件的各个块的时候,所需要的读写时间是最短的,读写速度是最快的。因为整个过程所需要的磁头移动距离是最短的。那这个知识点咱们还会在之后再进行进一步的探讨。这个地方我们只需要理解,连续分配的文件在顺序读写的时候速度最快,只需要理解这一点就可以了。那这是连续分配方式的第二个优点。
接下来我们来看一下连续分配方式有哪些缺点。
那进行数据迁移的这个过程其实是需要耗费很大的那个开销的,所以我们得出一个结论:由于连续分配方式要求文件在磁盘上占有的必须是一组连续的块,因此对文件的拓展是很不方便的。那这是它的一个缺点。
再来看第二个缺点。假设此时在这个图当中,
而这些磁盘碎片就有点类似于咱们在之前内存管理当中讲过的那种外部碎片。那处理这些碎片的方式,也和处理外部碎片的思想是一样的。可以用紧凑的方式来解决。不过紧凑就意味着需要移动大量的这些磁盘块的内容,那这个过程是需要花费很大的时间代价的。因此会产生磁盘碎片这个问题,也是连续分配方式一个很明显的缺点。
所以连续分配方式要求每个文件在磁盘上占用一组连续的块。它的优点是支持顺序访问和直接访问也就是随机访问,并且连续分配的文件在顺序访问时速度是最快的。但是它也有一些缺点,就是不方便文件的拓展,存储空间利用率低,会产生磁盘碎片。那这是连续分配方式我们需要掌握的一些知识点。
那接下来我们再来看第二种文件分配方式——链接分配。链接分配采取的是离散分配的这种思想,可以为文件分配离散的磁盘块,然后用指针链接的方式把它们、把这些磁盘块都给串联起来。而链接分配方式又可以进一步分为隐式链接和显式链接两种。
我们首先来看隐式链接这种方式。如果采用的是这种方式的话,那么在文件的目录项,也就是文件的FCB当中,需要记录这个文件的起始块号还有结束块号。另外,各个块之间都会有一定的存储空间存储指向下一块的这个链接指针,当然最后一个块是没有指向下一块的链接指针的。而这些指针对于用户来说是透明的。
那采用这种方式的话,怎么实现逻辑块号到物理块号的转变呢?用户首先给出它要访问的逻辑块号i,然后操作系统会根据文件名找到它对应的FCB这个目录项,找到这个文件的起始块号。
于是操作系统可以先读入这个文件的起始块,而这个块就是逻辑上的0号块。因为只有把这个块读入内内存之后,才可以知道这个块指向下一块的指针的数据到底是多少。那只有这样才可以再调入下一个块。于是下一个块调入内存之后,也可以再继续知道再下一个块存放的位置,于是再读入再下一个块,然后依次类推。
因此,如果说我们想要访问逻辑块号为i的这个逻辑块的话,那操作系统首先需要先依次地读入0号到i-1号逻辑块才可以找到i号块存放的位置。因此,访问i号逻辑块总共需要i+1次磁盘I/O操作。
因此,我们得到的结论是,如果采用的是链接分配(隐式链接)的这种方式的话,那么这种文件它只支持顺序访问不支持随机访问。也就是说如果想要访问i号逻辑块的话,那我只能先顺序地访问0到i-1号块才可以。因此,这种方式带来的问题就是,查找的效率很低。另外呢,每一个块当中也需要耗费一定少量的存储空间来存放指向下一个块的这个指针内容。
那接下来我们考虑下一个问题。像这种方式是否方便拓展文件呢?假设此时这个文件需要拓展,也就是需要再给它分配一个新的磁盘块。
那由于这个文件的这些块可以是离散分配的,因此只需要从这个磁盘中随便找出一个空闲的块把它挂到这个文件的链尾就可以了。比如说此时我们给文件再新分配了一个8号块,于是把8号块挂到这个链的链尾,
然后操作系统会修改这个文件FCB的结束块号这个内容,结束块号设为8号。
所以可以看到,采用这种方式的话,那么文件的拓展是很方便的,并且这种方式不会产生碎片问题,外存的利用率很高。
那么经过刚才的这个分析,我们知道了隐式链接的链接分配有这样的一些优点和缺点。这就是刚才咱们得到的结论,这儿就不再赘述。
接下来我们再来看显式链接的链接分配方式。这种方式会把用于链接各个文件物理块的指针显式地存放在一张表当中,这个表就被称为文件分配表,英文缩写是FAT(File Allocation Table)。磁盘当中的各个块的先后顺序都是统一记录在文件分配表当中的。所以一个磁盘它有多少个块,那在这个文件分配表当中就相应地会有多少个表项。
假设现在有一个新创建的文件,文件名是“aaa”,它依次占用了2、5、0、1这样的几个物理块。
那么,在“aaa”这个文件的FCB,也就是目录项当中,需要记录这个文件“aaa”它存放的起始块号,起始块号是2号块。另外呢,在这个文件分配表当中,会显式地记录文件“aaa”它这几个物理块的一个链接关系。
比如说2号块是起始块,2号块的下一块是5号块,所以FAT的这个表项,2号块的下一个块就应该记录为5。那这样的话就完成了2、5、0、1这样一个顺序的记录。
那么“bbb”这个文件对应的FCB当中,需要记录它的起始块号是4号块。然后4号块的下一个块是23,
那这样的话也完成了“bbb”这个文件4->23->3这样的一个顺序的记录。所以这种方式把每个文件的各个盘块的链接信息显式地统一地放在了这样的一个文件分配表FAT当中,所以这是它为什么称作显式链接的一个原因。
那从这个地方我们也可以看到,一个磁盘仅需要设置一张FAT就够了。每个文件的这些盘块的先后顺序都是统一地存放在这张表当中的。并且在我们开机的时候,FAT文件分配表它会被读入内存,并且会一直常驻内存。而FAT的这些一个一个的表项,在物理上是需要连续存储的,并且每一个表项的长度相同。比如说下一块这个数据我们可以用四个字节来表示,所以物理块号这个字段可以是隐含的。
那接下来我们再来看一下,采用这种方式的话怎么实现文件逻辑块号到物理块号的一个转变呢?一个用户给出他想要访问的逻辑块号i,然后操作系统首先需要找到这个文件对应目录项也就是它的FCB,找到这个文件的起始块号,起始物理块号。那比如说,一个用户想要访问的是文件“aaa”的2号逻辑块,那么操作系统首先找到“aaa”这个文件的0号逻辑块存放的物理块号是2,那接下来系统会查询内存当中的文件分配表。那找到这个表项就可以知道,0号逻辑块的下一个块,也就是1号逻辑块,应该是存放在5号物理块当中的。那接下来会继续查询这个表项,发现1号逻辑块的下一块也就是2号逻辑块它是存放在0号物理块当中的。于是查询到这个位置,就可以知道这个用户想要访问的“aaa”这个文件的2号逻辑块存放的物理块号了。那由于FAT它是常驻内存的,因此查询这个FAT的过程并不需要读磁盘操作。
所以通过这个分析我们得出这样的结论,采用显式链接的链接分配方式的话,那么这种文件它既然支持顺序访问,也支持随机访问。我们想要访问i号逻辑块,并不需要顺序地依次访问0-i-1号逻辑块,我们可以直接通过FAT表查到i号逻辑块存放的地址。并且由于这个查询的过程并不需要访问磁盘,所以相比于隐式链接的那种方式来说,采用显式链接访问速度会快很多。
那显然,这种方式也不会产生外部碎片。并且可以很方便地实现对文件的扩展。