操作系统-文件管理
文件管理
由于系统的内存有限并且不能长期保存,故平时总是把它们以文件的形式存放在外存中,需要时再将它们调入内存。如何高效的对文件进行管理是操作系统实现的目标。
文件是对磁盘的抽象,所谓文件是指一组带标识(标识即为文件名)的、在逻辑上有完整意义的信息项序列。
信息项:构成文件内容的基本单位(单个字节,或多个字节),各信息项之间具有顺序关系
文件内容的意义:由文件建立者和使用者解释
文件系统
操作系统中统一管理信息资源的一种软件,管理文件的存储、检索、更新,提供安全可靠的共享和保护手段,并且方便用户使用。
文件系统管理的对象有:文件(作为文件管理的直接对象),目录(为了方便用户对文件的存取和检索,在文件系统中配置目录,每个目录项中,必须含有文件名及该文件所在的物理地址,对目录的组织和管理是方便和提高对文件存取速度的关键),磁盘(磁盘)存储空间(文件和目录必定占用存储空间,对这部分空间的有效管理,不仅能提高外存的利用率,而且能提高对文件的存取速度)。
- 统一管理磁盘空间,实施磁盘空间的分配和回收
- 实现文件的按名存取(名字空间----映射--->磁盘空间)
- 实现文件信息的共享,并提供文件的保护、保密手段
- 向用户提供一个方便使用、易于维护的接口,并向用户提供有关统计信息
- 提供文件系统的性能
- 提供与I/O系统的统一接口
文件分类
按文件性质和用途分类(UNIX)
普通文件、目录文件、特殊文件(设备文件)、管道文件、套接字
- 普通文件:包含了用户的信息,一般为ASCII或二进制文件
- 目录文件:管理文件系统的系统文件
- 特殊文件:
- 字符设备文件:和输入输出有关,用于模仿串行I/O设备,例如终端,打印机,网卡等
- 块设备文件:磁盘
文件逻辑结构
从用户角度看文件,由用户的访问方式确定,是用户可以直接处理的数据及其结构,独立于文件的物理特性,又称为文件组织。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010191854522-710987103.png)
图4-2a中的文件是一种无结构的字节序列, 操作系统事实上不知道也不关心文件内容是什么, 操作系统所见到的就是字节, 其任何含义只在用户程序中解释。 在UNIX和Windows中都采用这种方法。把文件看成字节序列为操作系统提供了最大的灵活性。 用户程序可以向文件中加入任何内容, 并以任何方便的形式命名。 操作系统不提供任何帮助, 但也不会构成阻碍。 对于想做特殊操作的用户来说, 后者是非常重要的。 所有UNIX、 MS-DOS以及Windows都采用这种文件模型。
图4-2b表示在文件结构上的第一步改进。 在这个模型中, 文件是具有固定长度记录的序列, 每个记录都有其内部结构。 把文件作为记录序列的中心思想是: 读操作返回一个记录, 而写操作重写或追加一个记录。这里对“记录”给予一个历史上的说明, 几十年前, 当80列的穿孔卡片还是主流的时候, 很多(大型机) 操作系统把文件系统建立在由80个字符的记录组成的文件基础之上。 这些操作系统也支持132个字符的记录组成的文件, 这是为了适应行式打印机(当时的行式打印机有132列宽) 。 程序以80个字符为单位读入数据, 并以132个字符为单位写数据, 其中后面52个字符都是空格。 现在已经没有以这种方式工作的通用系统了, 但是在80列穿孔卡片和132列宽行式打印机流行的日子里, 这是大型计算机系统中的常见模式。
第三种文件结构如图4-2c所示。 文件在这种结构中由一棵记录树构成, 每个记录并不具有同样的长度,而记录的固定位置上有一个“键”字段。 这棵树按“键”字段进行排序, 从而可以对特定“键”进行快速查找。虽然在这类结构中取“下一个”记录是可以的, 但是基本操作并不是取“下一个”记录, 而是获得具有特定键的记录。 如图4-2c中的文件zoo, 用户可以要求系统取键为pony的记录, 而不必关心记录在文件中的确切位置。 进而, 可以在文件中添加新记录。 但是, 把记录加在文件的什么位置是由操作系统而不是用户决定的。 这类文件结构与UNIX和Windows中采用的无结构字节流明显不同, 但它在一些处理商业数据的大型计算机中获得广泛使用。
文件物理结构
由于磁盘具有可直接访问的特性,故当磁盘来存放文件时,具有很大的灵活性。而文件的物理结构与外存分配方式有关,在采用连续分配方式时的文件物理结构是顺序式的文件结构,在采用链接分配方式将形成链接式文件结构,而索引分配方式将形成索引式文件结构。
连续(顺序)分配
文件的信息存放在若干连续的物理块中。连续分配要求为每个文件分配一组相邻接的盘块,一组盘块地址定义了磁盘上的一段线性地址。采用连续分配方式时,可把逻辑文件中的记录顺序地存储到邻接的各物理盘块中,这样所形成的文件结构称为顺序文件结构,这种分配方式保证了逻辑文件中的记录顺序与存储器中文件占用盘块的顺序的一致性。下图为连续分配方式(假设记录与盘块一样大)。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010192216568-1778428104.png)
如同动态分配分区分配一样,随着文件建立时空间的分配和文件删除时的空间回收,将使磁盘空间被分割成许多小块,这些小块的连续去已难以存储文件,此即外存的碎片,同样,可以使用紧凑的方法,将盘上所有的文件紧靠在一起,把所有的碎片拼成一大片连续的存储空间。
连续分配的优点如下
① 顺序访问容易,访问一个占有连续空间的文件非常容易。
② 顺序访问速度快,因为由连续分配所装入的文件,其所占用的盘块可能是位于一条或几条相邻的磁道上,这是,磁头移动距离最少,这种对文件访问的速度使几种存储空间分配方式中最高的一种。
连续分配的缺点如下
① 要求又连续的存储空间,要为每个文件分配一段连续的存储空间,这样,便会产生许多外部碎片,严重地降低了外存空间利用率,定期紧凑会花费大量的机器时间。
② 必须实现知道文件的长度,事先知道文件的长度,然后根据其大小,在存储空间中找出一块其大小足够的存储区,将文件装入,对于动态增长的文件非常低效。
链接分配
如果将一个逻辑文件存储到外存上,并不要求为整个文件分配一块连续的空间,而是可以将文件装到多个离散的盘块中,这样就可以消除连续分配的缺点。采用链接分配方式时,可通过在每个盘块上的链接指针,将同属于一个文件的多个离散盘块链接成一个链表,把这样形成的物理文件称为链接文件。链接分配采取离散分配方式,消除了外部碎片,故而显著地提高了外存空间的利用率,并且对文件的增、删、改、查十分方便。链接方式可分为隐式链接和显示链接两种形式。
① 隐式链接, 在文件目录的每个目录项中,都须含有指向链接文件第一个盘块和最后一个盘块的指针。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010192400496-257017456.png)
说明:第9个盘块指向第16个盘块,第16个盘块指向第1个盘块,第1个盘块指向第10个盘块,第10个盘块指向第25个盘块(结束块)。
隐式链接分配的主要问题在于:其只适合于顺序访问,对随机访问的效率及其低效。寻道次数增加,寻道时间增加。链接指针会占用一定的空间。此外,其可靠性较差,任何一个指针出现问题,都会导致整个链的断开。可以将几个盘块组成一个簇,然后以簇为单位进行分配,会减少查找指定块的时间,但是会增加内部碎片。
② 显示链接,把用于链接文件各物理块的指针,显式的放在内存的一张链接表中,该表在整个磁盘仅设置一张。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010192500129-281468881.png)
说明:表的序号从0开始,直至N-1,N为盘块总数,在每个表项中存放链接指针,即下一个盘块号,在该表中,凡是属于某一文件的第一个盘块号,或者说是每一条链的链首指针所对应的盘块号,均作为文件地址被填入相应的文件的FCB(File Control Block)的物理地址字段中,由于查找记录的过程是在内存中进行的,因而提高了检索速度,减少了访问磁盘的次数,由于分配给文件的所有盘块号都在该表中,故把该表称为文件分配表FAT(File Allocation Table)。
链接分配的问题如下:不能支持高效的直接存储(要对一个较大的文件进行直接存取,须首先在FAT中顺序地查找很多盘块号);FAT需要占用较大的内存空间(由于一个文件所占用的盘块的盘块号是随机地分布在FAT中的,因而只有将整个FAT调入内存,才能保证FAT中找到一个文件的所有盘块号,当磁盘容量较大时,FAT占用的容量更大)
③ 索引分配,事实上,在打开某个文件时,只需要把该文件占用的盘块号的编号调入内存即可,完全没有必要把整个FAT调入内存,为此,应该将每个文件所对应的盘块号集中地放在一起,索引分配方式就是基于这种想法所形成的一种分配方式。其为每个文件分配一个索引块(表),再把分配给该文件的所有盘块号都记录在该索引块中,因而该索引块就是一个含有许多磁盘块号的数组。在建立一个文件时,只需要在位为之建立的目录项中填上指向该索引块的指针(单级索引)。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010192552109-2056663464.png)
说明:索引方式支持直接访问,可在索引块中找到第i个盘块,索引方式也不会产生外部碎片,当文件较大时,索引分配方式要优于链接分配方式。其主要问题在于:可能需要花费较多的外存空间,每当建立一个文件时,便须为之分配一个索引块,将分配给该文件的所有盘块号记录其中。对于小文件而言,索引块的利用率非常低。
当OS为一个大文件分配磁盘空间时,如果所分配的盘块的盘块号已经装满一个索引块时,OS便为该文件分配另一个索引块,用于将以后继续为之分配的盘块号记录于其中,以此类推,然后再通过链指针将各索引块按序链接起来,当文件太大时,索引块太多,效率是低效的。此时,应该为这些索引块再建立一级索引,称为第一级索引,还可再建立索引,称为第二级索引等等。称为多级索引分配。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010192746412-1980391091.png)
说明:在二级索引分配方式下,若每个盘块的大小为1KB,每个盘块号占4个字节,则在一个索引块可以存放256个盘块号,这样,在两级索引时,最多可以包括存放文件的盘块号总数为64K(256 * 256)个盘块号,所允许文件最大长度为64MB,若盘块号为4KB,则一级索引的最大文件大小为4MB,二级索引的最大文件大小为4GB。
④ 混合索引分配方式,将多种索引分配方式相结合而形成的一种分配方式,如直接地址(在索引结点中设置10个直接地址项,每项中所存放的是该文件数据所在盘块的盘块号,假如每个盘块大小为4KB,当文件不大于40KB时,可以直接从索引结点中读出该文件的全部盘号),一次间接地址(利用索引结点中的地址项来提供一次间接地址,其实质就是一级索引分配方式,在一次简直快中可存放1K个盘块号,允许最大文件为4MB),多次间接地址(当文件大于4MB + 40KB时,系统采用二次间址分配方式,其实质是两级索引分配方式,采用二次间址的最大文件大小为4GB,同理,可采用三次间接地址,允许文件最大大小为4TB)。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010192839443-41761796.png)
文件存储介质
典型的存储介质
磁盘(包括固态盘)SSD、磁带、光盘、U盘、。。。。
物理块(块block、簇cluster)
信息存储、传输、分配的独立单位,存储设备划分为大小相等的物理块,统一编号
磁盘
任何时刻只有一个磁头处于活动状态:输入输出数据流以位串形式出现
物理地址形式:磁头号(盘面号)、磁道号(柱面号)、扇区号
扇区:标题(10字节)、数据(512字节)、ECC纠错信息(12-16字节)
磁盘访问
一次访盘请求:读/写,磁盘地址(设备号、柱面号、磁头号、扇区号),内存地址(源/目)
过程:
- 寻道(时间):磁头移动定位到指定磁道
- 旋转延迟(时间):等待指定扇区从磁头下旋转经过
- 数据传输(时间):数据在磁盘与内存之间的实际传输
文件存储空间管理
数据结构
- 位图:用一串二进制位反映磁盘空间中分配使用情况,每一个物理块对应一位,分配的物理块为0,否则为1。申请物理块时,可以在位示图中查找为1的位,返回对应物理块号,归还时麻将对应位转置1。
- 空闲列表:将所有空闲块记录在一个表中,即空闲块表。主要两项内容:起始块号,块数。
- 空闲块链表:把所有空闲块链成一个链。扩展:成组链接法。
位示图法
利用二进制的一位表示磁盘中的一个盘块的使用情况,当其值为0时,表示对应的盘块空闲,为1时,表示已经分配,磁盘上的所有盘块都有一个二进制位与之对应,这样,由所有盘块所对应的位构成一个集合,称为位示图,通常可用m * n个位数来构成位示图,并使m * n等于磁盘的总块数。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010193213276-1082594456.png)
对于盘块的分配分为如下三步
① 顺序扫描位示图,从中找出一个或一组值为0的二进制位。
② 将所找到的一个或一组二进制位转换成与之赌赢的盘块号。
③ 修改位示图。
对于盘块的回收分为如下两步
① 将回收盘块的盘块号转换成位示图中的行号和列号。
② 修改位示图。
此方法的优点在于从位示图中很容易找到一个或一组相邻接的空闲盘块,此外,由于位示图很小,占用空间少,因而可将其保存在内存中,进而使在每次进行盘区分配时,无需首先把盘区分配表读入内存,节省磁盘启动时间。
空闲表法
空闲表法属于连续分配方式,它与内存的动态分配方式雷同,它为每个文件分配一块连续的存储空间,即系统也为外存上所有空闲区建立一张空闲表,每个空闲区对应于一个空闲表项,其中包括表项序号、该空闲区的第一个盘块号、该区的空闲盘块号等信息,再将所有空闲区按其起始盘块号递增排列。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010193316049-1819770772.png)
空闲盘区的分配与内存的动态分配类似,同样采用首次适应算法,循环首次适应算法等。系统在对用户所释放的存储空间进行回收时,也采取类似于内存回收的方法,即考虑回收区是否与空闲表中插入点的前区和后区相邻接,对相邻接者应该予以合并。当文件较小时,采用连续分配方式,当文件较大时,可采用离散分配方式。
空闲链表法
空闲链表法是将所有空闲盘区拉成一条空闲链。把链表分成两种形式,空闲盘块链和空闲盘区链。
① 空闲盘块链,这是将磁盘上的所有空闲空间,以盘块为单位拉成一条链,当用户因创建文件而请求分配存储空间时,系统从链首开始,依次摘下适当数目的空闲盘块分配给用户,当删除文件而释放空间时,系统将回收的盘块依次插入空闲盘块链的末尾,其优点是用于分配和回收一个盘块的过程简单,但在为文件分配盘块时,可能要重复操作多次。
② 空闲盘区链,这是将磁盘上的所有空闲盘区(每个盘区可包含若干个盘块)拉成一条链,在每个盘区上除了含有只是下一个空闲盘区的指针外,还应有能指明本盘区大小(盘块数)的信息。盘区分配与内存的动态分配类似,可采用首次适应算法,在回收盘区时,同样也要将回收区和相邻接的空闲盘区相合并,在采用首次适应算法时,可以采用显式链接法提高检索速度,在内存中为空闲盘区建立一张链表。
成组链接法
空闲表法和空闲链表法都不适用于大型系统,因为这会使空闲表或空闲链表很长,在UNIX采用的成组链接法,结合上述两种方法。
① 空线盘块的组织,空闲盘块栈用来存放当前可用的一组空闲盘块的盘块号(最多含100个号),以及栈中尚有的空闲盘块号数N,顺便指出,N兼做栈顶指针使用,栈是临界资源,系统设置一把锁供进程互斥访问。其中,S.free(0)是栈底,栈满时栈顶为S.free(99)。
② 文件区中的所有空闲盘块被分成若干个组,如每100个盘块作为一组。
③ 将每一组含有的盘块总数N和该组所有的盘块号记入其前一组的第一个盘块S.free(0)~S.free(99)中,这样,由各组的第一个盘块可链接成一条链。
④ 将第一组的盘块总数和所有的盘块号记入空闲盘块号栈中,作为当前可供分配的空闲盘块号。
⑤ 最末一组只有99个盘块,其盘块号分别记入其前一组的S.free(1)~S.free(99)中,而在S.free(0)中则存放0,作为空闲盘块链的结束。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010193430767-316883464.png)
文件卷的目录区中专门用一个磁盘块作为“超级块”,当系统启动时需要将超级块读入内存。并且要保证内存与外存中的“超级块”数据一致。当系统要为用户分配文件所需的盘块时,须调用盘块分配过程来完成。该过程首先检查空闲盘块号栈是否上锁,如未上锁,便从 栈顶取出一空闲盘块号,将与之对应的盘块分配给用户,然后将栈顶指针下移一格。若该盘块号已是栈底,即S.free(0),这是当前栈中最后一个可分配的 盘块号。由于在该盘块号所对应的盘块中记有下一组可用的盘块号,因此,须调用磁盘读过程,将栈底盘块号所对应盘块的内容读入栈中,作为新的盘块号栈的内 容,并把原栈底对应的盘块分配出去(其中的有用数据已读入栈中)。然后,再分配一相应的缓冲区(作为该盘块的缓冲区)。最后,把栈中的空闲盘块数减1并返回。
在系统回收空闲盘块时,须调用盘块回收过程进行回收。它是将回收盘块的盘块号记入空闲盘块号栈的顶部,并执行空闲盘块数加1操作。当栈中空闲盘块号数目已达100时,表示栈已满,便将现有栈中的100个盘块号,记入新回收的盘块中,再将其盘块号作为新栈底。
文件控制块及文件目录
文件控制块(File Control Block)
为管理文件而设置的用于描述和控制文件的数据结构,称之为文件控制块,简称PCB。保存管理文件所需的所有有关信息(文件属性或元数据)。文件管理程序可借助于文件控制块中的信息,对文件施加各种操作,文件与文件控制块一一对应,而人们把文件控制块的有序集合称为文件目录,一个文件控制块就是一个文件目录项。通常,一个文件目录也可被看成是一个文件,称为目录文件。文件控制块包含基本信息、存取控制信息、使用信息。
① 基本信息,包括文件名(标识一个文件的符号名,在每个系统中,每个文件都有唯一的名字,用户利用该名字进行存取);文件物理位置(指文件在外存上的存储位置,包括存放文件的设备名、文件在外村上的起始盘块号、指示文件所占用的盘块数或字节数的文件长度);文件逻辑结构(指示文件是流式文件还是记录式文件、记录数,文件是定长还是变长记录);文件物理结构(指示文件是顺序文件、链式文件还是索引文件)
② 存取控制信息,包括文件主的存取权限、核准用户的存取权限及一般用户的存取权限。
③ 使用信息,包括文件的建立日期和时间、文件上一次修改的日期和时间及当前使用信息(这项信息包括当前已打开该文件的进程数、是否被其他进程锁住、文件在内存中是否已被修改但尚未拷贝到盘上)
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010193557828-229759123.png)
索引结点
文件目录通常是存放在磁盘上的,当文件很多时,文件目录可能要占用大量的盘块,在查找的过程中,先将存放目录文件的第一个盘块中的目录调入内存,然后把用户所给定的文件名和目录项中的文件名逐一对比。若未找到指定文件,则再将下一个盘块中的目录项调入内存。在检索目录文件时,只用到了文件名,仅当找到一个目录项(即其中的文件名与指定要查找的文件名相匹配)时,才需要从该目录项中读出该文件的物理地址,而其他一些对该文件进行描述的信息,在检索目录时一概不用,显然,这些信息在检索目录时不需要调入内存。为此,在有的系统中,如UNIX系统,便采用了把文件名和文件描述信息分开的方法,亦即,使文件描述信息单独形成一个称为索引结点的数据结构,简称为i结点,在文件目录中的每个目录项由文件名和指向该文件所对应的i结点的指针所构成。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010193733029-690595979.png)
每个文件都有唯一的磁盘索引结点(磁盘索引结点信息与文件名等信息一起构成了FCB),其主要包括内容如下
① 文件主标识符,即拥有该文件的个人或小组的标识符。
② 文件类型,包括正规文件、目录文件或特别文件。
③ 文件存取权限,指各类用户对该文件的存取权限。
④ 文件物理地址,每个索引结点中含有13个地址项(混合索引方式),他们以直接或间接的方式给出数据文件所在的盘块的编号。
⑤ 文件长度,指以字节为单位的文件长度。
⑥ 文件连接计数,表明在本文件系统中所有指向该文件名的指针计数。
⑦ 文件存取时间,指本文件最近被进程存取的时间、最近被修改的时间及索引结点最近被修改的时间。
当文件被打开时,要将磁盘索引结点拷贝到内存索引结点中,便于以后使用,在内存索引结点中又增加了一下内容。
① 索引结点编号,用于标识内存索引结点。
② 状态,指示i结点是否上锁或被修改。
③ 访问计数,每当有进程要访问此i结点时,将访问计数加1,访问完再减1。
④ 文件所属文件系统的逻辑设备号。
⑤ 链接指针,设置有分别指向空闲链表和散列队列的指针。
文件属性:
文件名,文件号,文件大小,文件地址,创建时间,最后修改时间,最后访问时间,保护,口令创建者,当前拥有者,文件类型,共享计数,各种标志(只读,隐藏)等。
文件操作
① 创建文件,在创建一个新文件时,系统首先要为新文件分配必要的外存空间,并在文件系统的目录中,为之建立一个目录项,目录项中应该记录新文件的文件名及其在外存的地址等属性。
② 删除文件,当已不再需要某文件时,可将其从文件系统中删除,在删除时,系统应先从目录中找到要删除文件的目录项,使之成为空项,然后回收该文件所占用的存储空间。
③ 读文件,读文件时,须在相应系统调用中给出文件名和应读入的内存目标地址。此时,系统要查找目录,找到指定目录项,从中得到被读文件在外存中的位置。在目录项中,还有一个指针用于对文件进行读/写。
④ 写文件,写文件时,须在相应系统调用中给出文件名和其在内存源地址。此时,系统要查找目录,找到指定目录项,从再利用目录中的写指针进行写操作。
⑤ 截断文件,如果一个文件的内容已经陈旧而需要全部更新时,一种方法是将此文件删除,再重新创建一个新文件,但如果文件名和属性均无改变,则可采取截断文件的方法,其将原有的文件长度设置为0,放弃原有文件的内容。
⑥ 设置文件的读/写位置,用于设置文件读/写指针的位置,以便每次读/写文件时,不需要从始端开始而是从所设置的位置开始操作。可以改顺序存取为随机存取。
⑦ ,在使用文件之前,必须先打开文件。这个调用的目的是允许系统将属性和磁盘地址列表保存到主存中,用来以后的快速访问。
当前OS所提供的大多数对文件的操作,其过程大致都是这样两步:首先,检索文件目录来找到指定文件的属性及其在外存上的位置;然后,对文件实施相应的操作,如读/写文件等,当用户要求对一个文件实施多次读/写或其他操作时,每次都要从检索目录开始,为了避免多次重复地检索目录,在大多数OS中都引入了打开这一文件系统调用,当用户第一次请求对某文件系统进行操作时,先利用open系统调用将该文件打开。
打开文件
给出文件路径名,获得文件句柄(file handle)或文件描述符(file descriptor),需将该文件的目录项读到内存,fd=open
- 根据文件路径名查目录,找到目录项(或I节点号)
- 根据文件号查系统打开文件表,看文件是否已被打开;是的话,共享计数加1;否则,将目录项(或I节点)等信息填入系统打开文件表空表项,共享计数置为1;
- 根据打开方式、共享说明和用户身份检查访问合法性;
- 在用户打开文件表中获取一空表项,填写打开方式等,并指向系统打开文件表对应表项,返回信息:fd:文件描述符,是一个非负整数,用于以后读写文件。
指针定位
seek(fd,新指针位置)
系统为每个进程打开的每个文件维护一个读写指针,即相对于文件开头的偏移地址(读写指针指向每次文件读写的开始位置,在每次读写完成后,读写指针按照读写的数据量自动后移相应数值)
- 由fd查用户打开文件表,找到对应的表项;
- 将用户打开文件表中文件读写指针位置设为新指针的位置,供后续读写命令存取该指针处文件内容
读文件
read(文件描述符,读指针,要读的长度,内存目的地址)
- 根据打开文件时得到的文件描述符,找到相应的文件控制块(目录项),确定读操作的合法性
- 将文件的逻辑块号转为物理块号,根据参数中的读指针、长度与文件控制块的信息,确定块号、块数、块内位移
- 申请缓冲区
- 启动磁盘I/O操作,把磁盘块中的信息读入缓冲区,在送到指定的内存区(多次读盘)
- 反复执行3、4直至读出所需数量的数据或读至文件尾
文件目录
统一管理每个文件的元数据,以支持文件名到文件物理地址的转换,将所有文件的管理信息组织在一起,即构成文件目录。目录中有一个固定大小的目录项列表, 每个文件对应一项, 其中包含一个(固定长度) 文件名、 一个文件属性结构以及用以说明磁盘块位置的一个或多个磁盘地址(至某个最大值) 。
目录文件
将文件目录以文件的形式存放在磁盘上
目录项
构成文件目录的基本单元,目录项可以是FCB,文件目录是文件控制块的有序集合。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010194100600-1630352348.png)
对于采用i节点的系统, 还存在另一种方法, 即把文件属性存放在i节点中而不是目录项中。 在这种情形下, 目录项会更短: 只有文件名和i节点号。 这种方法参见图4-14b。 后面我们会看到, 与把属性存放到目录项中相比, 这种方法更好。 图4-14中的两种处理方法分别对应Windows和UNIX。
文件目录结构
目录结构的组织,关系到文件系统的存取速度,也关系到文件的共享性和安全性,目前常用的目录结构形式有单级目录、两级目录、多级目录。
① 单级目录结构,在整个系统中只建立一张目录表,每个文件占一个目录项,目录项中含文件名、文件扩展名、文件长度、文件类型、文件物理地址、状态位(表示目录项是否空闲)等。每当要建立一个新文件时,必须先检查所有的目录项,以保证新文件名在目录中是唯一的,然后再从目录表中找到一个空白目录项,填入新文件的文件名及其他说明信息,并置状态为1,删除文件时,先从目录中找到该文件的目录项,回收该文件所占用的存储空间,然后再清除该目录项。单级目录的有点是简单并且能够实现目录管理的基本功能-按名存取,但是查找速度慢(查找一个目录项要花费较多的时间),不允许重名(在一个目录表中的所有文件,都不能与另一个文件有相同的名字,这是难以避免的),不便于实现文件共享(每一个用户都有自己的名字空间或命名习惯,因此,应该允许不同用户使用不同的文件名来访问同一个文件)
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010194215315-602671042.png)
② 两级目录结构,为每个用户建立一个单独的用户文件目录UFD(User File Directory),这些文件目录具有相似的结构,由用户所有文件的文件控制块组成。此外,系统中还有一个主文件目录MFD(Master File Directory),在主文件目录中,每个用户目录文件都占有一个目录项,其目录项包括用户名和指向用户目录文件的指针。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010194259698-128267553.png)
两级目录结构客服了单级目录的缺点,具有如下优点:提高了检索目录的速度(如果在主目录中有n个子目录,每个用户目录最多为m个目录项,则为查找一指定的目录项,最多只需要检索n+m个目录项)。在不同的用户目录中,可以使用相同的文件名(只要在用户自己的UFD中,每个文件名都是唯一的,不同用户可以有文件名相同的文件)。不同用户还可使用不同的文件名来访问系统中同一个共享文件。但在多个用户需要合作完成一个大任务时,不便于用户之间共享文件。
③ 多级目录结构,对于大型文件系统,通常采用三级或三级以上的目录结构,以提高对目录的检索速度和文件系统的性能。多级目录结构又称为树形目录结构,主目录被称为根目录,把数据文件称为树叶,其他的目录均作为树的结点。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010194353126-1173206350.png)
说明:方框代表目录文件,圆圈代表数据文件,主目录中有是哪个用户总目录A、B、C,在B用户的总目录B中,又包括三个分目录F、E、D,其中每个分目录中又包含多个文件,为提高系统的灵活性,应该允许在一个目录文件中的目录项既是作为目录文件的FCB,又是数据文件的FCB,这一信息可用目录项中的一位来指示。如用户A总目录中,目录项A是目录文件FCB,而目录项B和D则是数据文件的FCB。
在树形目录结构中,从根目录到任何数据文件,都只有一条唯一的通路,在该路径上从树的根开始,把全部目录文件名和数据文件名依次用"/"连接起来,即构成该数据文件的路径名。系统中的每个文件都有唯一的路径名。例如,用户B访问文件J,则使用路径名/B/F/J来访问。
当一个文件系统含有很多级时,每访问一个文件,都要使用从树根开始直到树叶(数据文件)为止的、包含各中间节点(目录)的全路径名,这非常麻烦,可为每个进程设置一个当前目录,又称为工作目录,进程对各文件的访问都相对于当前目录而进行的。把从当前目录开始直到数据文件为止所构成的路径名称为相对路径名,而把从树根开始的路径名称为绝对路径名。
④ 增加和删除目录,在树形目录结构中,用户可为自己建立UFD,并可再创建子目录,在用户要创建一个新文件时,只需要查看自己的UFD及其子目录中有无与新建文件相同的文件名,若无,便可在UFD或其某个子目录中增加一个新目录项。在树形目录中,如何删除一个目录,应该视情况而定,若要删除的目录为空,则简单地将其删除,使它在其上一级目录中所对应的目录项为空,若不为空,可采用如下方法:不删除非空目录(当目录不为空时,为了删除一个非空目录,必须先删除目录中所有的文件,使之称为空目录,然后再删除,如果目录中包含有子目录,则应该递归调用方式删除),可删除非空目录(将目录中的所有文件和子目录同时删除)。
目录检索技术
当用户要访问一个已存在的文件时,系统首先利用用户提供的文件名对目录进行查询,找出该文件的文件控制块或对应索引结点,然后,根据FCB或索引结点中所记录的文件物理地址(盘块号),换算出文件在磁盘上的物理位置,最后,再通过磁盘驱动程序,将所需文件读入内存。目前常用的方式有线性检索法和Hash方法。
① 线性检索法,其又称为顺序检索法,在树形目录中,用户提供的文件名是由多个文件分量名组成的路径名,此时须对多级目录进行查找,假定用户给定的文件路径名为/usr/ast/mbox,则查找过程如下。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010201827164-1628467466.png)
说明:首先,系统应先读入第一个文件分量名usr,用它与根目录文件(或当前目录文件)中各目录项中的文件名顺序地进行比较,从中找到匹配者,并得到匹配项的索引结点号是6,再从6号索引结点中得到usr目录文件放在132号盘块中,将该盘块内容读入内存。接着,系统再将路径名中的第二个分量名ast读入,用它与放在132号盘块中的第二级目录文件中各目录项的文件名顺序进行比较,又找到匹配项,从中得到ast的目录文件放在26号索引结点中,再从26号索引结点中得知/usr/ast是存放在496号盘块中,再读入496号盘块。然后,将文件的第三个分量名mbox读入,用它与第三季目录文件/usr/ast中各目录项的文件名进行比较,最后得到/usr/ast/mbox的索引结点号为60,即在60号索引结点中存放了指定文件的物理地址,目录查询操作到此结束,如果在顺序查找过程中发现有一个文件分量名没有找到,则停止查找,并返回文件未找到信息。
② Hash方法,系统利用用户提供的文件名并将它转换为文件目录的索引值,再利用该索引值到目录中去查找,这将提高检索速度。
文件系统的实现
磁盘分区:把一个物理磁盘的存储空间划分为几个相互独立的部分,称为分区。
文件卷:磁盘的逻辑分区,由一个或多个物理块(簇)组成。
- 一个文件卷可以是整个磁盘或部分磁盘或跨盘(RAID)
- 同一个文件卷中使用同一份管理数据进行文件分配和磁盘空闲空间管理,不同的文件卷中的管理数据是相互独立的
- 一个文件卷上:包括文件系统信息、一组文件(用户文件目录文件)、未分配空间
- 块或簇:一个或多个(2的幂)连续的扇区,可寻址数据块
格式化:在一个文件卷上建立文件系统,即建立并初始化用于文件分配和磁盘空闲空间管理的管理数据。
磁盘上的内容:
- 主引导区:包括了从该卷引导操作系统所需要的信息,每个卷(分区)一个,通常为第一个扇区
- 卷(分区)信息:包括该卷(分区)的块(簇)数、块(簇)大小、空闲块(簇)数量和指针、空闲FCB数量和指针
- 目录文件(根目录文件及其他目录文件)
- 用户文件
磁盘文件系统的布局
UNIX文件系统布局:
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010202016901-1746713139.png)
MBR:主引导区
分区表:给出了每个分区的起始和结束地址。
windows的FAT文件系统布局和UNIX布局不同之处:磁盘分区不一样。windows的磁盘分区构造为:引导区+文件分配表1+分配表2+根目录+其他目录和文件
内存中的数据结构(以UNIX为例)
系统打开文件表:整个系统一张,放在内存,用于保存已打开文件的FCB
用户打开文件表:每个进程一张,进程的PCB中记录了用户打开文件表的位置
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010202355528-291003631.png)
文件目录实现
访问一个文件分为两个步骤:文件名------>文件目录-----FCB--->磁盘
- 目录检索:用户给出文件名,按照文件名查找到目录项(FCB)
- 文件寻址:目录项中提供了查找文件磁盘块所需要的信息。 因系统而异, 这些信息有可能是整个文件的磁盘地址(对于连续分配方案) 、 第一个块的编号(对于两种链表分配方案) 或者是i节点号。 无论怎样, 目录系统的主要功能是把ASCII文件名映射成定位文件数据所需的信息。
改进:
目录项分解法:把FCB分成两部分,符号目录项和基本目录项。符号目录项包括文件名和文件号,基本目录项包括除文件名外的所有字段。
UNIX文件系统
- FCB = 目录项+ i节点
- 目录项:文件名+i节点
- 目录文件由目录项构成
- i节点:描述文件的相关信息
- 每个文件由一个目录项、一个i节点和若干磁盘块构成
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010202436196-323450212.png)
一般长文件名的实现方式
在MS-DOS中, 文件有1~8个字符的基本名和1~3字符的可选扩展名。 在UNIX V7中文件名有1~14个字符, 包括任何扩展名。 但是, 几乎所有的现代操作系统都支持可变长度的长文件名。 那么它们是如何实现的呢?
最简单的方法是给予文件名一个长度限制, 典型值为255个字符, 然后使用下图中的一种设计,并为每个文件名保留255个字符空间。 这种处理很简单, 但是浪费了大量的目录空间, 因为只有很少的文件会有如此长的名字。 从效率考虑, 我们希望有其他的结构。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010202514820-1927340825.png)
一种替代方案是放弃“所有目录项大小一样”的想法。 这种方法中, 每个目录项有一个固定部分, 这个固定部分通常以目录项的长度开始, 后面是固定格式的数据, 通常包括所有者、 创建时间、 保护信息以及其他属性。 这个固定长度的头的后面是实际文件名, 可能是如图4-15a中的正序格式放置(如SPARC机器) [1] 。在这个例子中, 有三个文件, project-budget、 personnel和foo。 每个文件名以一个特殊字符(通常是0) 结束, 在图4-15中用带叉的矩形表示。 为了使每个目录项从字的边界开始, 每个文件名被填充成整数个字, 如图4-15中带阴影的矩形所示。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010202545623-784402139.png)
这个方法的缺点是, 当移走文件后, 就引入了一个长度可变的空隙, 而下一个进来的文件不一定正好适合这个空隙。 这个问题与我们已经看到的连续磁盘文件的问题是一样的, 由于整个目录在内存中, 所以只有对目录进行紧凑操作才可节省空间。 另一个问题是, 一个目录项可能会分布在多个页面上, 在读取文件名时可能发生页面故障。
处理可变长度文件名字的另一种方法是, 使目录项自身都有固定长度, 而将文件名放置在目录后面的堆中, 如图4-15b所示。 这一方法的优点是, 当一个文件目录项被移走后, 另一个文件的目录项总是可以适合这个空隙。 当然, 必须要对堆进行管理, 而在处理文件名时页面故障仍旧会发生。 另一个小优点是文件名不再需要从字的边界开始, 这样, 原先在图4-15a中需要的填充字符, 在图4-15b中的文件名之后就不再需要了。
到目前为止, 在需要查找文件名时, 所有的方案都是线性地从头到尾对目录进行搜索。 对于非常长的目录, 线性查找就太慢了。 加快查找速度的一个方法是在每个目录中使用散列表。 设表的大小为n。 在输入文件名时, 文件名被散列到1和n-1之间的一个值, 例如, 它被n除, 并取余数。 其他可以采用的方法有, 对构成文件名的字求和, 其结果被n除, 或某些类似的方法。
不论哪种方法都要对与散列码相对应的散列表表项进行检查。 如果该表项没有被使用, 就将一个指向文件目录项的指针放入, 文件目录项紧连在散列表后面。 如果该表项被使用了, 就构造一个链表, 该链表的表头指针存放在该表项中, 并链接所有具有相同散列值的文件目录项。
查找文件按照相同的过程进行。 散列处理文件名, 以便选择一个散列表项。 检查链表头在该位置上的所有表项, 查看要找的文件名是否存在。 如果名字不在该链上, 该文件就不在这个目录中。
使用散列表的优点是查找非常迅速。 其缺点是需要复杂的管理。 只有在预计系统中的目录经常会有成百上千个文件时, 才把散列方案真正作为备用方案考虑。
一种完全不同的加快大型目录查找速度的方法是, 将查找结果存入高速缓存。 在开始查找之前, 先查看文件名是否在高速缓存中。 如果是, 该文件可以立即定位。 当然, 只有在构成查找主体的文件非常少的时候, 高速缓存的方案才有效果。
【1】 处理机中的一串字符存放的顺序有正序(big-endian) 和逆序(little-endian) 之分。 正序存放就是高字节存放在前低字节在后, 而逆序存放就是低字节在前高字节在后。 例如, 十六进制数为A02B, 正序存放就是A02B, 逆序存放就是2BA0。
文件系统的可靠性
可靠性:抵御和预防各种物理性破坏和人为性破坏的能力。
文件系统备份
全量转储:定期将所有文件拷贝到后援存储器
增量转储:只转储修改过的文件,即两次备份之间的修改,减少系统的开销
物理转储:从磁盘第0块开始,将所有磁盘块按序输出到磁带
逻辑转储:从一个或几个指定目录开始,递归的转储自给定日期后所有更改的文件和目录
文件系统一致性
很多文件系统读取磁盘块, 进行修改后, 再写回磁盘。 如果在修改过的磁盘块全部写回之前系统崩溃, 则文件系统有可能处于不一致状态。 如果一些未被写回的块是i节点块、 目录块或者是包含有空闲表的块时, 这个问题尤为严重。
解决方案:
设计一个实用程序以检验文件系统的一致性。例如,UNIX有fsck, 而Windows用scandisk。 系统启动时, 特别是崩溃之后的重新启动, 可以运行该实用程序。下面我们介绍在UNIX中这个fsck实用程序是怎样工作的。 scandisk有所不同, 因为它工作在另一种文件系统上, 不过运用文件系统的内在冗余进行修复的一般原理仍然有效。 所有文件系统检验程序可以独立地检验每个文件系统(磁盘分区) 的一致性。
一致性检查分为两种: 块的一致性检查和文件的一致性检查。 在检查块的一致性时, 程序构造两张表,每张表中为每个块设立一个计数器, 都初始化为0。 第一个表中的计数器跟踪该块在文件中的出现次数, 第二个表中的计数器跟踪该块在空闲表中的出现次数。
接着检验程序使用原始设备读取全部的i节点, 忽略文件的结构, 只返回所有的磁盘块, 从0开始。 由i节点开始, 可以建立相应文件中采用的全部块的块号表。 每当读到一个块号时, 该块在第一个表中的计数器加1。 然后, 该程序检查空闲表或位图, 查找全部未使用的块。 每当在空闲表中找到一个块时, 就会使它在第二个表中的相应计数器加1。
如果文件系统一致, 则每一块或者在第一个表计数器中为1, 或者在第二个表计数器中为1, 如图4-27a所示。 但是当系统崩溃后, 这两张表可能如图4-27b所示, 其中, 磁盘块2没有出现在任何一张表中, 这称为块丢失。 尽管块丢失不会造成实际的损害, 但它的确浪费了磁盘空间, 减少了磁盘容量。 块丢失问题的解决很容易: 文件系统检验程序把它们加到空闲表中即可。
有可能出现的另一种情况如图4-27c所示。 其中, 块4在空闲表中出现了2次(只在空闲表是真正意义上的一张表时, 才会出现重复, 在位图中, 不会发生这类情况) 。 解决方法也很简单: 只要重新建立空闲表即可。
最糟的情况是, 在两个或多个文件中出现同一个数据块, 如图4-27d中的块5。 如果其中一个文件被删除, 块5会添加到空闲表中, 导致一个块同时处于使用和空闲两种状态。 若删除这两个文件, 那么在空闲表中这个磁盘块会出现两次。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010202758646-1951320075.png)
文件系统的写入策略
- 通写(write-through):内存中的修改立即写到磁盘,缺点:速度性能差,例如:FAT文件系统
- 延迟写(lazy-write):利用回写(write back)缓存的方法,缺点:可恢复性差
- 可恢复写(translation log):利用事务日志来实现文件系统的写入,既考虑安全性,页考虑速度性能,例如:NTFS
文件系统的性能问题
访问磁盘比访问内存慢得多。 读内存中一个32位字大概要10ns。 从硬盘上读的速度大约超过100MB/s,对32位字来说, 大约要慢4倍, 还要加上5~10ms寻道时间, 并等待所需的扇面抵达磁头下。 如果只需要一个字, 内存访问则比磁盘访问快百万数量级。 考虑到访问时间的这个差异, 许多文件系统采用了各种优化措施以改善性能。
方法:目录项分解、当前目录、磁盘碎片管理、块高速缓存、磁盘调度、提前读取、合理分配磁盘空间、信息的优化分布、RAID技术。。。
1.块高速缓存
又称文件缓存、磁盘高速缓存、缓冲区高速缓存。具体为:在内存中为磁盘块设置的一个缓冲区,保存了磁盘中某些块的副本。
管理高速缓存有不同的算法, 常用的算法是: 检查全部的读请求, 查看在高速缓存中是否有所需要的块。 如果存在, 可执行读操作而无须访问磁盘。 如果该块不在高速缓存中, 首先要把它读到高速缓存, 再复制到所需地方。 之后, 对同一个块的请求都通过高速缓存完成。
高速缓存的操作如图4-28所示。 由于在高速缓存中有许多块(通常有上千块) , 所以需要有某种方法快速确定所需要的块是否存在。 常用方法是将设备和磁盘地址进行散列操作, 然后, 在散列表中查找结果。 具有相同散列值的块在一个链表中连接在一起, 这样就可以沿着冲突链查找其他块。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010202850676-1874616641.png)
如果高速缓存已满, 则需要调入新的块, 因此, 要把原来的某一块调出高速缓存(如果要调出的块在上次调入以后修改过, 则要把它写回磁盘) 。 这种情况与分页非常相似, 所有常用的页面置换算法之前已经介绍, 例如FIFO算法、 第二次机会算法、 LRU算法等, 它们都适用于高速缓存。 与分页相比, 高速缓存的好处在于对高速缓存的引用不很频繁, 所以按精确的LRU顺序在链表中记录全部的块是可行的。
在图4-28中可以看到, 除了散列表中的冲突链之外, 还有一个双向链表把所有的块按照使用时间的先后次序链接起来, 近来使用最少的块在该链表的前端, 而近来使用最多的块在该链表的后端。 当引用某个块时, 该块可以从双向链表中移走, 并放置到该表的尾部去。 用这种方法, 可以维护一种准确的LRU顺序。
但是,存在一种情形, 这个问题与之前讨论的系统崩溃和文件一致性有关。 如果一个关键块(比如i节点块) 读进了高速缓存并做过修改, 但是没有写回磁盘, 这时, 系统崩溃会导致文件系统的不一致。 如果把i节点块放在LRU链的尾部, 在它到达链首并写回磁盘前, 有可能需要相当长的一段时间。
此外, 某一些块, 如i节点块, 极少可能在短时间内被引用两次。 基于这些考虑需要修改LRU方案, 并应注意如下两点:
1)这一块是否不久后会再次使用?
2)这一块是否关系到文件系统的一致性?
考虑以上两个问题时, 可将块分为i节点块、 间接块、 目录块、 满数据块、 部分数据块等几类。 把有可能最近不再需要的块放在LRU链表的前部, 而不是LRU链表的后端, 于是它们所占用的缓冲区可以很快被重用。 对很快就可能再次使用的块, 比如正在写入的部分满数据块, 可放在链表的尾部, 这样它们能在高速缓存中保存较长的一段时间。
第二个问题独立于前一个问题。 如果关系到文件系统一致性(除数据块之外, 其他块基本上都是这样)的某块被修改, 都应立即将该块写回磁盘, 不管它是否被放在LRU链表尾部。 将关键块快速写回磁盘, 将大大减少在计算机崩溃后文件系统被破坏的可能性。
系统采用两种方法解决这一问题。 在UNIX系统中有一个系统调用sync, 它强制性地把全部修改过的块立即写回磁盘。 系统启动时, 在后台运行一个通常名为update的程序, 它在无限循环中不断执行sync调用, 每两次调用之间休眠30s。 于是, 系统即使崩溃, 也不会丢失超过30秒的工作。
虽然目前Windows有一个等价于sync的系统调用——FlushFileBuffers, 不过过去没有。 相反, Windows采用一个在某种程度上比UNIX方式更好(有时更坏) 的策略。 其做法是, 只要被写进高速缓存, 就把每个被修改的块写进磁盘。 将缓存中所有被修改的块立即写回磁盘称为通写高速缓存(write-through cache) 。 同非通写高速缓存相比, 通写高速缓存需要更多的磁盘I/O。
若某程序要写满1KB的块, 每次写一个字符, 这时可以看到这两种方法的区别。 UNIX在高速缓存中保存全部字符, 并把这个块每30秒写回磁盘一次, 或者当从高速缓存删除这一块时, 写回磁盘。 在通写高速缓存里, 每写入一字符就要访问一次磁盘。 当然, 多数程序有内部缓冲, 通常情况下, 在每次执行write系统调用时并不是只写入一个字符, 而是写入一行或更大的单位。
采用这两种不同的高速缓存策略的结果是: 在UNIX系统中, 若不调用sync就移动(软) 磁盘, 往往会导致数据丢失, 在被毁坏的文件系统中也经常如此。 而在通写高速缓存中, 就不会出现这类情况。 选择不同策略的原因是, 在UNIX开发环境中, 全部磁盘都是硬盘, 不可移动。 而第一代Windows文件源自MSDOS, 是从软盘世界中发展起来的。 由于UNIX方案有更高的效率它成为当然的选择(但可靠性更差) , 随着硬盘成为标准, 它目前也用在Windows的磁盘上。 但是, NTFS使用其他方法(日志) 改善其可靠性, 这在前面已经讨论过。
一些操作系统将高速缓存与页缓存集成。 这种方式特别是在支持内存映射文件的时候很吸引人。 如果一个文件被映射到内存上, 则它其中的一些页就会在内存中, 因为它们被要求按页进入。 这些页面与在高速缓存中的文件块几乎没有不同。 在这种情况下, 它们能被以同样的方式来对待, 也就是说, 用一个缓存来同时存储文件块与页。
2.提前读取
思路:每次访问磁盘,多读入一些磁盘块
依据:程序执行的空间局部性原理
开销:较小(只有数据传输时间)
第二个明显提高文件系统性能的技术是: 在需要用到块之前, 试图提前将其写入高速缓存, 从而提高命中率。 特别地, 许多文件都是顺序读的。 如果请求文件系统在某个文件中生成块k, 文件系统执行相关操作且在完成之后, 会在用户不察觉的情形下检查高速缓存, 以便确定块k+1是否已经在高速缓存。 如果还不在, 文件系统会为块k+1安排一个预读, 因为文件系统希望在需要用到该块时, 它已经在高速缓存或者至少
马上就要在高速缓存中了。
当然, 块提前读策略只适用于顺序读取的文件。 对随机存取文件, 提前读丝毫不起作用。 相反, 它还会帮倒忙, 因为读取无用的块以及从高速缓存中删除潜在有用的块将会占用固定的磁盘带宽(如果有“脏”块的话, 还需要将它们写回磁盘, 这就占用了更多的磁盘带宽) 。 那么提前读策略是否值得采用呢? 文件系统通过跟踪每一个打开文件的访问方式来确定这一点。 例如, 可以使用与文件相关联的某个位协助跟踪该文件到底是“顺序存取方式”还是“随机存取方式”。 在最初不能确定文件属于哪种存取方式时, 先将该位设置成顺序存取方式。 但是, 查找一完成, 就将该位清除。 如果再次发生顺序读取, 就再次设置该位。 这样, 文件系统可以通过合理的猜测, 确定是否应该采取提前读的策略。 即便弄错了一次也不会产生严重后果, 不过是浪费一小段磁盘的带宽罢了。
windows的文件访问方式
- 不使用文件缓存
- 普通方式
- 通过windows提供的FlushFileBuffers函数实现
- 使用文件缓存
- 预读取。每次读取的块大小、缓冲区大小、置换方式
- 写回。写回时机选择、一致性问题
- 异步模式
- 不再等待磁盘操作的完成
- 使处理器和I/O并发工作
使用文件缓存
- 由windows的cache manager实现对缓存的控制
- 读取数据的时候预取
- 在cache满时,根据LRU原则清除缓存的内容
- 定期更新磁盘内容使其与cache一致(1s)
- write-back机制
- 在用户要对磁盘写数据时,只更改cache中的内容,由cache manager决定何时将更新反映到磁盘
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010203048238-235004000.png)
阴影部分为需要访问的数据,数据在磁盘、系统缓存和进程地址空间由三份拷贝,通常用户对数据的修改并不会直接反映到磁盘上,而是通过write-back机制由lazy-write定期更新到磁盘
3.合理分配磁盘空间
分配磁盘块时,把有可能顺序存取的块放在一起,尽量分配在同一柱面上,从而减少磁盘臂的移动次数和距离。
当写一个输出文件时, 文件系统就必须按照要求一次一次地分配磁盘块。 如果用位图来记录空闲块, 并且整个位图在内存中, 那么选择与前一块最近的空闲块是很容易的。 如果用空闲表, 并且链表的一部分存在磁盘上, 要分配紧邻着的空闲块就困难得
多。
不过, 即使采用空闲表, 也可以采用块簇技术。 这里用到一个小技巧, 即不用块而用连续块簇来跟踪磁盘存储区。 如果一个扇区有512个字节, 有可能系统采用1KB的块(2个扇区) , 但却按每2块(4个扇区) 一个单位来分配磁盘存储区。 这和2KB的磁盘块并不相同, 因为在高速缓存中它依然使用1KB的块, 磁盘与内存数据之间传送也是以1KB为单位进行, 但在一个空闲的系统上顺序读取文件, 寻道的次数可以减少一半,
从而使文件系统的性能大大改善。 若考虑旋转定位则可以得到这类方案的变体。 在分配块时, 系统尽量把一个文件中的连续块存放在同一柱面上。
在使用i节点或任何类似i节点的系统中, 另一个性能瓶颈是, 读取一个很短的文件也需要两次磁盘访问: 一次是访问i节点, 另一次是访问块。 通常情况下, i节点的放置如图4-29a所示。 其中, 全部i节点都放在靠近磁盘开始位置, 所以i节点和它指向的块之间的平均距离是柱面数的一半, 这将需要较长的寻道时间。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010203125758-263995213.png)
一个简单的改进方法是, 在磁盘中部而不是开始处存放i节点, 此时, 在i节点和第一块之间的平均寻道时间减为原来的一半。 另一种做法是: 将磁盘分成多个柱面组, 每个柱面组有自己的i节点、 数据块和空闲表(McKusick等人, 1984) , 见图4-29b。 在文件创建时, 可选取任一i节点, 但首先在该i节点所在的柱面组上查找块。 如果在该柱面组中没有空闲的块, 就选用与之相邻的柱面组的一个块。
4.磁盘调度
当有多个访盘请求等待时,采用一定的策略,对这些请求的服务顺序调整安排,降低平均磁盘服务时间,达到公平、高效。即一个I/O请求在有限时间内满足,减少设备机械运动带来的时间开销。
一次访盘时间=寻道时间+旋转延迟时间+传输时间
磁盘调度算法
例子:
磁盘访问序列:98,183,37,122,14,124,65,67
1.先来先服务算法
按访问请求到达的先后次序服务。优点:简单、公平,缺点:效率不高,相邻两次请求可能会造成最内到最外的柱面寻道,使磁头反复移动,增加服务时间,对机械也不利。
依据上面的例子:读写头起始位置:53,先到98,在到183,按照上面的顺序执行,总共移动了640磁道,平均80。
2.最短寻道时间优先算法
优先选择距当前磁头最近的访问请求进行服务,主要考虑寻道优先。优点:改善了磁盘平均服务时间,缺点:造成某些访问请求长期等待得不到服务。
依据上面的例子:读写头起始位置:53,先到65,在到67,按照距当前磁头最近顺序的执行,总共移动了236磁道,平均29.5。
3.扫描算法SCAN(电梯算法)
当设备无访问请求时,磁头不动;当有访问请求时,磁头按一个方向移动,在移动过程中对遇到的访问请求进行服务,然后判断该方向上是否还有访问请求,如果有则继续扫描;否则改变移动方向,并为经过的访问请求服务,如此反复。
依据上面的例子:读写头起始位置:53,先到37,在到14,按照距当前磁头最近顺序的执行,总共移动了218磁道,平均27.25。
4.单向扫描调度算法
总是从0号柱面开始向里扫描,按柱面(磁道)位置选择访问者,移动臂到达最后一个柱面后,立即带动读写磁头快速返回到0号柱面,返回时不为任何的等待访问者服务,返回后再次进行扫描。
5.N-STEP-SCAN算法
吧磁盘请求队列分成长度为N的子队列,每次用SCAN处理一个子队列,在处理某一队列时,新请求添加到其他子队列中,如果最后剩下的请求数小于N,则它们全都将在下一次扫描时处理。N值比较大时,其性能接近SCAN;当N=1时,即FIFO。
6.FSCAN算法
使用两个子队列,扫描开始时,所有请求都在一个队列中,而另一个队列为空。扫描过程中,所有新到的请求都放入另一个队列中,对新请求的服务延迟到处理完所有老请求之后。
7.旋转调度算法
旋转调度:根据延迟时间来决定执行次序的调度
三种情况:
- 若干等待访问者请求访问同一磁头上的不同扇区
- 若干等待访问者请求访问不同磁头上的不同编号扇区
- 若干等待访问者请求访问不同磁头上具有相同的扇区
对于前两种情况:总是让首先到达读写磁头位置下的扇区先进行传送操作
对于第三种情况:这些扇区同时到达读写磁头位置下,可任意选择一个读写磁头进行传送操作。
5.信息的优化分布
记录在磁道上的排列方式会影响输入输出操作的时间。
例子:处理程序要求顺序处理8个记录;磁盘旋转一周为20毫秒/周;花5毫秒对记录进行处理:
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010203237344-1052872129.png)
当读取完记录1时,需要花5毫秒处理,处理完记录1,此时,磁盘已经旋转到4。如果要读取记录2,需要等2.5*6毫秒。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010203258172-1961072688.png)
按照上图排列方式,则不需要多等待。
6.记录的成组与分解
记录的成组:吧若干个逻辑记录合成一组存放一块的工作,进行成组操作时必须使用内存缓冲区,缓冲区的长度等于逻辑记录长度乘以成组的块因子。
成组目的:提高了存储空间的利用率;减少了移动外设的次数,提高系统的工作效率
记录的分解:从一组逻辑记录中吧一个逻辑记录分离出来。
典型例子:目录文件
6.RAID技术(独立磁盘冗余阵列-Redundant Array of Independent Disk)
多块磁盘按照一定要求构成一个独立的存储设备
目标:提高可靠性和性能
- 通过把多个磁盘组织在一起,作为一个逻辑卷提供磁盘跨越功能
- 通过把数据分成多个数据块,并行写入/读出多个磁盘,以提高数据传输率(数据分条stripe)
- 通过镜像或校验操作,提供容错能力(冗余)
最简单的RAID组织方式:镜像
最复杂的RAID组织方式:块交错校验
RAID0-条带化
无冗余(即无差错控制),性能最佳
- 数据分布在阵列的所有磁盘上
- 有数据请求时,同时多个磁盘并行操作
- 充分利用总线带宽,数据吞吐率提高,驱动器负载均衡
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010203328780-1931259474.png)
RAID1-镜像
数据安全性最好
- 最大限度保证数据安全及可恢复性
- 所有数据同时存在于两块磁盘的相同位置
- 磁盘利用率50%
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211010203342469-894498658.png)