五个问题:
问题1:计算机中存放了各种各样的文件,一个文件有哪些树形?
问题2:文件内部的数据应该怎样组织起来?
问题3:文件之间又应该怎么组织起来?
问题4:从下往上看,OS应该提供哪些功能,才能方便用户、应用程序使用文件?
问题5:从上往下看,文件数据应该怎么存放在外存(磁盘)上呢?
文件名
:由创建文件的用户决定文件名,主要是为了方便用户找到文件,同一目录下不允许有重名文件。
标识符
:一个系统内各文件表示符唯一,对用户来说毫无可读性,一次标识符只是操作系统用于区分各个文件的一种内部名称。
类型
:指明文件的类型。
位置
:文件存放的路径(让用户使用)、在外存中的地址(操作系统使用,用户不可见)。
大小
:指明文件大小。
创建信息、上次修改时间文件所有者信息
。
保护信息
:对文件进行保护的访问控制信息
无结构文件
:如文本文件,由一些二进制或字符流组成,又称"流式文件"。
有结构文件
:如数据库表,由一组相似的记录组成,又称"记录式文件"。
下面是思维导图:
对于在有结构的文件当中,各个记录之间应该如何组织,是顺序存放还是索引表记录顺序?
windows中使用的是目录形式,最顶层是根目录:
实际举例四个系统调用:
①create系统调用:当我们在文件管理器中右击—新建-文件夹时,实际上时图形化交互进程在背后调用了"create系统调用"。
②read系统调用:当我我们打开一个文本文件的时候,此时就会将文件的数据读入内存,让CPU来进行处理。这个读的过程实际上时调用了read系统调用,将文件数据从外存读入到内存,并显示在屏幕上。
③write系统调用:可以"写文件",当我们在打开的文本文档中进行编辑时,实际上在内存中的副本进行了修改,当点击保存按钮时,实际上记事本应用会去通过操作系统提供的"写文件",即write系统调用,将文件数据从内存写回外存。
④delete系统调用:选中文件,右击点击删除,此时就会通过图形化交互进程通过操作系统提供的delete系统调用将文件数据从外存中删除。
上面时给出了操作系统对于文件的最基础功能,基于这些功能我们可以实现一些较为复杂的功能如复制文件、剪切等操作。
与内存一样,外存也是由一个个存储单元组成的,每个存储单元可以存储一定量的数据(如1B),每个存储单元对应一个物理地址。
外存被分为一个个磁盘块,操作系统是以"块"为单位为文件分配存储空间,因此即使一个文件大小只有10B,但它依然需要占用1KB的磁盘块,外存中的数据读入内存时同样以块来作为单位。
文件逻辑地址也可以分为:(逻辑块号,块内地址)。
操作系统同样需要将逻辑地址转换为外存的物理地址:(物理块号,块内地址)。
需要探讨的问题如下:文件数据是否放在连续磁盘块中?各个磁盘块之间的先后顺序?操作系统如何管理空闲磁盘块?
文件的物理结构探讨的是文件这些数据在物理上应该是怎么存放,怎么组织的问题,对于之前提到的文件的逻辑结构指的是文件的各个记录,在逻辑上应该是什么样的一种组织关系的问题。
文件共享
:使多个用户可以共享使用一个文件。
文件保护
:如何保证不同的用户对文件有不同的操作权限。
之后主要来结合widnows操作系统实际应用进行探讨。
名词解释:
实际举例:
按照文件是否有结构分类,可以分为无结构文件、有结构文件。
无结构文件
:文件内部的数据是一系列数据或字符流组成,又称"流式文件",例如windows系统中的.txt文件。
有结构文件
:
有结构文件:由一组相似的记录组成,又称"记录式文件"。每条记录由若干个数据项组成。如:数据库表我呢见,每条记录有一个数据项可作为关键字,例如下图中的学号可以识别不同记录的ID。
根据各条记录的长度(占用存储空间)是否相等,可分为:定长记录、可变长记录。
①定长记录例如下方,我们可对每个数据项来设置指定的长度:
②可变长记录:若是我们存储的记录中有部分字段是长度不确定的,此时就被称为可变长记录:
下面根据有结构文件中的各条记录在逻辑上如何组织分为三类:顺序文件、索引文件、索引顺序文件。
顺序文件:文件中的记录一个接一个顺序排列(逻辑上的),记录可以是定长的或者可变长的。各个记录在物理上可以顺序存储或链式存储。
根据这个记录是否按照关键字的顺序排序可以将顺序文件分为:串结构、顺序结构。
提出问题:此时若是已经知道了文件起始地址,也就是第一个记录存放的位置。
思考1:能够快速找到第i个记录对应的地址?也就是能够实现随机存取。
思考2:能够快速找到某个关键字对应的记录存放的位置?
可变长记录的特征:可以看到具体某个条件记录的位置需要根据之前的记录给出的记录长度后才能够推断得到
定长记录的特征:每条记录的长度都是固定的,所以我们可以直接进行随机访问
结论:
1、物理上采用链式存储,无法实现随机存储。
2、物理上采用顺序存储,可变长记录无法实现随机存取,定长记录可以实现随机存取,其中对于串结构无法快速定位某个关键字对应记录是因为排列顺序并不是按照关键字来进行排序的,所以我们无法定位某个关键字对应的记录,而对于顺序结构的是可以快速定位的,并且可以实现折半查找。
注意:对于考试来说"顺序文件",指的是就是物理上顺序存储的顺序文件,对于顺序文件增加/删除一个记录比较困难,串结构则相对比较简单。
出现的原因:对于可变长记录文件,若是想要找到第i个记录,那么就必须先查找前i-1记录,那么对于一些场景就是会使用到可变长记录,那么我们如何解决这个快速定位的问题?
解决方案:我们可以设计一张索引表,在索引表中我们维护一个索引号,来实现快速检索,长度m指的是指向逻辑文件对应记录的长度,还有一个字段是指针ptr用于指向对应的逻辑文件。
索引表本身就是定长记录的顺序文件,通过将其中的索引号作为关键字顺序排序,可以快速找到第i个记录对应的索引项,还饿可以支持按照关键字折半查找。
增/删功能:若是要增加/删除一个记录时,需要对索引表进行修改。
应用场景:由于索引文件有很快的检索速度,因此主要用于对信息处理的及时性要求很高的场合。
其他非单独创建索引表使用索引号作为关键字的设计:可以采用不同数据项合并来建立多个索引表。例如,学生表中可以用关键字"学号"建立一张索引表,或者姓名建立一个索引表(不太建议会重名)快速的检索文件了。
引出索引文件的缺点:由于索引文件对应每一条记录都有一个索引表项,那么对应一个记录表的索引表会很大。
思路:同样是采用一个索引表,但是这个索引表中并不是根据关键字来顺序排列然后对应逻辑文件中的每条记录了,而是根据逻辑文件中的特征来进行分组,每一个分组就是一个顺序文件,分组内的记录无需按照关键字排序。
索引顺序文件:是索引文件和顺序文件思想的结合。在索引顺序表中,同样会为文件建立一张索引表,但不同的是并不是每个记录对应一个索引表项,而是一组记录对应一个索引表项。
提出疑问:用这种策略确实可以让索引表瘦身,但是是否会让出现不定长记录的顺序文件检索速度慢的问题呢?
通过示例来进行证明:
此时提出问题:若是文件有106记录,分为1000个分组,每个分组1000个记录,那么根据关键字检索一个记录平均需要查找500+500=1000次,那么查找次数依旧很多,如何解决?
优化思路:可以实现一个二级索引表,对于106个记录文件,可以先为该文件建立一张低级索引表,每100个记录为一组,此时低级索引表中共有10000个表项,对这个10000个定长记录再次进行分组,每组100个建立顶级索引,此时顶级索引共有100个表项。
最终我们查询一个106的表,只需要150次即可查找到。
实现文件目录功能,需要有一个很关键的数据结构:文件控制块
。
随着操作系统的发展:出现了多种的结构目录
。
索引节点
:对文件控制块的一个优化。
首先当我们点击D盘时,可以看到有很多的文件目录、文本文档,实际这些都是在一个文件目录表中记录:
双击打开目录中照片文件夹时,操作系统会来查询该D盘根目录的目录文件,找到照片这个文件对应目录项后,根据这个目录项当中记录的文件存放位置,从外存当中读入照片,这个目录文件中的数据,这样就可以直到整个目录下有哪些内容显示。
同样对应照片对应的目录文件也是由一条一条目录项组成,下面则是"照片"目录对应的目录文件:
在目录文件中的一条记录就是一个"文件控制块(FCB)
"。
FCB包含内容:包含文件基本信息(文件名、物理地址、逻辑结构、物理结构等),存储控制信息(是否可读/可写、禁止访问的用户名单等),使用信息(如文件的建立时间、修改时间)。
FCB实现功能:实现了文件名和文件之间的映射,使用户(用户程序)可以实现"按名存取"。
包含有搜索、创建文件、删除文件、显示目录、修改目录的功能。
针对于一个目录我们进行相应的操作实际完成了哪些事情呢?
早期操作系统并不支持多级目录,整个系统中只建立一张目录表,每个文件占一个目录项。
在单机目录结构中时不允许文件重名的。
创建文件时:需要先检查目录表中有没有重名文件,确定不重名后才能允许建立文件,并将新文件对应的目录项插入目录表中。
问题:若是很多用户使用,那么文件名肯定会难免出现重复,所以单机目录并不适用于多用户操作系统。
早期的多用户操作系统:采用两级目录结构,分为主文件目录(MFD,Master File Directory)和用户文件目录(UFD,User File Directory)。
组成:用户文件目录由该用户的文件FCB组成。
提供的功能:
访问路径:用户(或用户进程)要访问某个文件时要用文件路径名标识文件,文件路径名是个字符串。各级目录之间用"/"隔开。从根目录出发的路径称为绝对路径。
查找过程:系统根据绝对路径一层一层地找到下一级目录。①刚开始从外存读入根目录的目录表;②找到"照片"目录的存放位置之后,从外存读入对应的目录表;③再找到"2015-08"目录的存放位置,再从外存读入对应目录表;最终才找到文件"自拍.jpg"的存放位置,整个过程需要3次读磁盘操作。
说明:在很多时候,用户会连续的访问同一目录内多个文件,例如读取"2015-08"目录内的多个照片文件,显然每次都会从根目录开始查找,会十分低效,因此可以设置一个当前目录
。
示例:例如当前已经在"照片"的目录文件,此时这个目录表已经调入内存,那么可以设置其为"当前目录"。当用户想要访问某个文件时,可以使用从当前目录出发的"相对路径"。
举例Linux系统中:可以使用"./“表示当前路径出发,只需要查询内存中的照片"目录表”,即可直到"2015-08"目录表的存放位置,从外存调入该目录,即可知道"自拍.jpg"存放的位置。
./2015-08/自拍.jpg
”好处:通过使用"当前目录"和"相对路径"后,磁盘I/O的次数减少了,此时提升了访问文件的效率。
存在的问题:虽然能够很方便实现文件分类,层次清晰,能够有效进行文件的管理和保护,但是,树型结构不便于实现文件的共享。
本质:多个用户的文件目录表中某个文件表项都直接指向了同一个文件(共享同一目录下的所有内容)。
删除共享文件如何实现呢,若是有多个用户文件指向?
**何时真正删除共享文件?**当共享计数器减为0时,才会删除结点。
注意:共享文件与复制文件并不相同,在共享文件中,各用户指向的时同一个文件,因此只要其中一个用户修改了文件数据,那么所有的用户都可以看到文件数据的变化。
文件目录实际上是由多个FCB块组成的,由于每个FCB中包含了多个信息,实际我们在去检索查找的时候,除了文件名是需要关注的,其他的并不关注,那么在查找过程中若是直接读取了整个目录表中的所有表项就会有很大的I/O损耗,我们是否能够去建立一个查询索引,通过索引定位之后再单独的找到整个文件表项读取其他信息?
索引结点
。索引结点:我们将除了文件名其他的冗余表项都放到这个表中。
流程:我们首先会读取相对应的文件索引表,其中记录着对应文件目录中的所有文件名以及索引结点指针,尝试匹配其中的所有文件名,若是匹配成功,那么则根据索引结点指针来去外存读取索引结点表,此时就能够在这个索引结点表中可以读取到所有该文件表项的其他冗余字段。
实际案例对比:
①不使用索引结点:假设一个FCB是64B,磁盘块的大小为1KB,那么每个磁盘块可以存放16个FCB,此时若是一个文件目录中共有640目录项,那么则会占用40个盘块,因此按照某文件名检索该目录,平均需要查询320个目录项(16x40/2=320),平均需要启动20次磁盘(320/16=20,每次磁盘I/O读入一块)。
②使用索引结点机制:文件名占14B,索引结点指针占2B,则每个盘块可存放64个目录项(文件名+索引结点=16B,1KB/16B=64),那么按照,一个文件目录中有640项,平均同样是需要查询320个目录项(16x40/2=320),那么就需要5个磁盘块(320/64=5)。
流程:当找到文件名对应的目录项时,才将索引结点调入内存,索引结点中记录了文件的各种信息,包括文件在外存中存放位置,最终根据索引结点里的物理位置找到文件。
结论:通过使用索引结点机制,每次读取的仅仅只是文件名+索引结点,大大减小了读取数据量,那么我们的磁盘IO也会更少,性能更好。
名词说明:
磁盘索引结点
:存放在外存中的索引结点。内存索引结点
:当索引结点放入内存后。在内存索引结点中增加了一些信息,如文件是否倍修改,此时有几个进程正在访问该文件等。主要讨论的就是文件数据应该怎么样存放在外存中?
首先来看一下外存结构,对于外存结构与主存类似,同样在磁盘中存储单元也会分为一个个块:
注意:在大部分操作系统中,磁盘块与内存块(页面)的大小相同。
实际内存与外存之间交互,都是以"块"为单位进行的,每次读/写一块:
在内存管理中,进程逻辑地址空间被分为一个个页面,在外存管理中,为了方便对文件数据的管理,文件的逻辑地址空间也被分为了一个一个的文件"块"。
文件的逻辑地址表示:(逻辑块,块内地址)
操作系统为文件分配存储空间单位:块
。
使用流程:用户操作自己文件使用的是(逻辑块号、块内地址),操作系统将用户提供的逻辑块号、块内地址转换为该文件块实际存放的物理块号和块内地址。
此时文件物理结构这个部分重点关注核心问题:怎么把逻辑块号映射为物理块号。
连续分配方式思想:要求每个文件在磁盘上占有一组连续的块。
例如要查找的逻辑块号为:(aaa,2),那么此时物理块号 = 4 + 2 = 6,此时就能够确定其物理块号地址。
查找过程: 用户给出要访问的逻辑块号,操作系统找到该文件的对应目录项(FCB),接着确定起始块号,之后将起始块号+块内地址即可确定物理地址。
优点1:连续分配方式支持顺序访问和直接访问(即随机访问)。
优点2:采用连续分配的文件在顺序读/写速度最快。
当我们读取黄色块时,由于是连续存放的,那么磁头移动的距离就很短花费的时间同样十分短;读取紫色块时,由于是不连续的,那么读取一个块就需要移动很长一段磁头距离,此时耗费的时间就更久。
缺点1:物理上采用连续分配的文件不方便扩展。
场景如下:如下三个黄色块若是想要扩展,由于连续分配这种方案策略必须是块要连续的,那么此时就只能够将这三块数据整体的迁移到绿色区域,此时才能够进行扩展1位。
缺点2:物理上采用连续分配,存储空间利用率低,会产生难以利用的磁盘碎片。
场景如下:此时磁盘中有两部分区域,橙色区域是非空闲的,绿色区域是空闲的,对于绿色区域中没有一个块是连续的,都是离散的,那么在这个场景下若是创建新文件需要3个块,此时无法分配足够的存储空间,只能够白白浪费几个区块。
解决方案:可以用紧凑来处理碎片,但是需要耗费很大 的时间代价。
连续分配总结:
链接分配方式并不支持随机读取的,而是只能够进行顺序依次读取,对应的链式链接如下图所示:
对应的文件目录表项会多出来一个结束块号字段,在隐式链接中会指明一个文件的起始块号以及结束块号,接着会从起始开号的磁盘块开始读取一直读到结束块号终止:
实现逻辑块号到物理块号转变:首先用户给出想要访问的逻辑块号,操作系统找到该文件对应的目录项(FCB),接着从目录项中找到起始块号,接着依次根据每一块指向的结束块号来进行顺序访问。
磁盘I/O次数:读入i号逻辑块,需要i+1次磁盘I/O。
结论:采用链式分配(隐式链接)方式的文件,只支持顺序访问,不支持随机访问,查找效率低。另外指向下一个盘块的指针也需要耗费少量的存储空间。
采用这种方式是否方便扩展文件?
举例:我们讲aaa文件扩展一个磁盘块到8,此时变化如下仅仅只需要讲对应块16的下一个地址修改以及文件目录记录项中的结束块号修改为8。
结论:采用隐式链接分配方式,很方便文件扩展。另外,所有的空闲磁盘块都可以被利用,不会有碎片问题,外存利用率高。
优点:很方便文件扩展,不会有碎片问题,外存利用率高。
缺点:只支持顺序访问,不支持随机访问,查找效率低,指向下一个盘块的指针也需要耗费少量的存储空间。
显式链接
:把用于链接文件各物理块的指针显式的存放在一个表中,即文件分配表(FAT,File Allocation Table)
。一个磁盘只会建立一张文件分配表,开机时文件分配表会放入内存,并常驻内存。
文件目录表项格式如下图所示,相对之前链接分配只包含起始块号:
举例1:假设某个新创建文件"aaa"依次存放磁盘块为2->5->0->1,那么对应的FAT分配表以及磁盘的状态如下图所示
举例2:假设某个新创建的文件"bbb"依次存放在磁盘块4->23->3中
文件分配表(FAT)
:通过一个文件分配表讲每个文件各个盘块的链接信息显示的同一放在一个文件分配表中。
注意:一个磁盘仅设置一张FAT,在开机时,将FAT读入内存,并常驻内存。FAT的各个表项在物理上连续存储,且每一个表项长度相同,因此**"物理块号"字段时可以隐含的**(默认是按照顺序0、1、2…开始的)。
如何实现文件的逻辑块号到物理块号转变?
结论:采用链式分配(显式分配)方式的文件,支持顺序访问,也支持随机访问(想要访问i号逻辑块时,并不需要依次访问之前的0-i-1号逻辑块),由于块号转换的过程无需访问磁盘,因此相比隐式链接来说,访问速度会快很多。
优点:显示链接不会产生外部碎片,也可以很方便的对文件进行扩展,外存利用率高,并且支持随机访问,相比隐式链接来说,地址转换时不需要访问磁盘,因此文件的访问效率高。
缺点:文件分配表需要占用一定的存储空间。
索引分配允许文件离散地分配在各个磁盘块中,系统会为每个文件建立一张索引表,索引表中记录了文件各个逻辑块对应的物理块。
名词:
索引块
:索引表存放的磁盘块。数据块
:文件数据存放的磁盘块。对于索引块与数据块的区别我们看下图,这里假设某个新创建的文件"aaa"的数据依次存放在磁盘块2->15->13->9,其中7号磁盘块就是"aaa"的索引块,在索引块中保存了索引表的内存也就是黑色横线对等的右边表格
对于数据块则是在索引表中指向的各个物理块号,对应物理块号的磁盘块则是数据块!
注意:在显示链接的链接分配方式中,文件分配表FAT时一个磁盘对应一张表。而显示索引分配表则是一个文件对应一张表。
物理块号的长度是否是固定的?
可以使用固定的长度表示物理块号,如:假设C盘总容量为1TB=240B,磁盘块大小为1KB,则共有230个磁盘块,那么想要表示所有磁盘块的序号,那么则需要30位表示,则可用4B来表示磁盘块号。
索引表中的磁盘块号是可以进行隐含的,因为对于物理块号是定长的,那么我们可以很容易的找到第i磁盘块号在索引表中的位置。
若是增加指定文件增加一个磁盘块,扩展难不难?
如何实现文件的逻辑块号到物理块号的转换?
优点:可见索引分配可以支持随机访问,文件扩展也很容易实现,只需要给文件分配一个空闲块,并增加一个索引表项即可。
缺点:索引表需要占用一定的存储空间。
问题:若是每个磁盘块1KB,一个索引表项4B,那么一个磁盘块只能够存放256个索引项,若是一个文件的大小超过了256块,那么一个磁盘块是装不下文件的整张索引表的,如何解决这个问题?
多种方案:①链接方案。②多层索引。③混合索引。
链接方案:如果索引表太大,一个索引块装不下,那么可以将多个索引块链接起来存放。
举例:假设磁盘大小为1KB,一个索引表项占4B,则一个磁盘块只能存放256个索引项。
链接方式:那么索引表中逻辑块号为0-255,我们在255位置设置一个指向下一个索引表的物理地址即可。
对应场景问题:若是此时我们只想要访问文件"aaa"的第256个物理块号,按照上面的逻辑寻找首先需要从7号索引块中依次从0读到255块,接着通过255块来去链接到256块上,此时才能够进行读取数据。
256*256KB
=65536KB=64MB,那么该文件共有256*256
个索引项,由于一磁盘块就需要256个索引项,那么这么大的索引表需要两块磁盘块才能够表示,此时我们就需要在第一个索引块中的最后一个255位设置下一个索引表的指针,那么我们若是想要读取第256个索引块,那么就需要先读入前255个索引块。评价:对于这种效率显示是很低的,如何解决呢?
多层索引
:建立多层索引(原理类似于页表)。使第一层索引指向第二层的索引块,还可根据文件大小要求再建立第三层,第四层索引块。
举例:假设磁盘块大小为1KB,一个索引表项为4B,那么一个磁盘块只能够存放256个索引项。若是文件能够采用两级索引,那么该文件最大长度可以达到256*256*1KB
=65536KB=64MB。
此时若是需要根据逻辑块号来找到索引表中的哪个表项,例如要访问1026号逻辑块:
1026/256=4
1026%256=2
可以先将一级索引表调入内存,查询4号表项,将其对应的二级索引表调入内存,再查询二级索引表的2号表项即可知道1026号逻辑块存放的磁盘块号了。
I/O次数:需要3次磁盘I/O。
注意点:若是采用多层索引,则各层索引表大小不能超过一个磁盘块。
若是采用三层索引,那么文件最大长度为256*256*256*1KB=16GB
,此时若是访问目标数据块,那么需要4次磁盘I/O。
结论:若是采用K层索引结构,且顶级索引表未调入内存,那么访问一个数据块需要k+1次读磁盘操作。
小小问题:若是一个文件本来就很小,数据块只有1KB,但由于物理上采用两层索引,那么读入这个文件1KB,依旧需要三次读磁盘操作,如何来解决这个问题呢?
混合索引
:多级索引分配结合。例如一个文件的顶级索引表中,既包含直接地址索引(直接指向数据块),又包含一级间接索引(指向单层索引表),还包含两级间接索引(指向两层索引表)。
上方的结构则是由八个直接地址索引,那么会直接指向5个数据块,一个一级间接索引会指向一个单层的索引表,而每个索引表只会对应256个数据块,那么相应的二级索引最多会指向256*256数据块。
上方结果最大数据块长度:8+256+256*256=65800。
接下来来计算访问不同逻辑块需要的I/O次数:
好处:对于小文件只需要较少的读磁盘次数就可以访问目标数据块(一般计算机中小文件更多)。
超级重要考点:
注意点:若是题目直接说的链接分配,那么默认就是隐式链接。
指的就是对空闲磁盘块的管理。
安装windows操作系统的时候,一个必经步骤就是为磁盘分区,如C盘、D盘、E盘等。
什么是文件卷?
文件卷
:将物理磁盘划分为一个个文件卷(逻辑卷、逻辑盘)。问题:文件卷中分为目录区和文件区,各自有什么作用,存放什么数据?
物理磁盘分为多个文件卷(实际就是分区),在每个文件卷中包含有目录区、数据区。
对于部分系统支持超大型文件,其中可支持多个物理磁盘组成一个文件卷。
适用于连续分配实现的。
**如何记录空闲区域?**可以看到下面有两个字段,第一个字段指的是第一个空闲盘块号,第二个字段指的是从第一个空闲盘块号开始连续几块。
如何分配磁盘块:与内存管理中的动态分区分配很类似,为一个文件分配连续的存储空间,同样可以采用首次适应、最佳适应、最坏适应算法来决定为文件分配哪个分区。
示例:若是采用首次适应分配算法,新创建的文件请求3个块:
**如何回收磁盘块?**与内存管理中的动态分区分配很类似,当回收某个存储时需要有四种情况。
1、回收区的前后都没有相邻空闲区:新增一个空闲区。
2、回收区的前后都是空闲区:前后以及当前位置会合并为一个空闲区,此时会减少一个空闲区。
3、回收区前面是空闲区:合并,不会导致空闲表数量改变。
4、回收区后面是空闲区:合并,不会导致空闲表数量改变。
示例:若是回收了占用的15、16、17号块。
空闲链表法分为两种方式:空闲盘块链
、空闲盘区链
。
区别:一个是以盘块作为单位,另一个是以盘区作为单位来进行链接的,如下图所示。
采用这两种方式对于磁盘块的分配与回收有什么区别?
在操作系统中保存着连头、链尾指针。
如何分配:若是某个文件申请K个盘块,则从链头上开始依次摘下K个空闲盘块分配,并修改空闲链的链头指针。
如何回收:回收的盘块依次挂到链尾,并修改空闲链的链尾指针。
使用场景:适用于离散分配的物理结构。
缺点:为文件分配多个盘块是可能需要重复多次操作。
操作系统中保存着链头、链尾指针。
如何分配:若某文件申请K个盘块,则可以采用首次适应、最佳适应等算法,从链头开始检索,按照算法规则找到一个大小符合要求的空闲盘区,分配给文件。若是没有合适的连续空闲块,那么也可以将不同盘区的盘块同时分配给一个文件。
如何回收:若回收区和某个空闲盘区相邻,则需要将回收区合并到空闲盘区中。若回收区没有和任何空闲区相邻,将回收区作为单独的一个空闲盘区挂到链尾。
适用场景:离散分配、连续分配都适用,为一个文件分配多个盘块时效率更高。
对比空闲盘块:比起空闲盘块链来说,给一个文件分配多个盘块时效率更高。
位示图
:每个二进制位都对应一个盘块,在本例中,"0"表示盘块空闲,"1"表示盘块已分配。
其中一般使用连续的"字"来表示,例如本例中一个字的字长是16位,字中每一位都对应一个盘块,因此可以使用(字号、位号)对应一个盘块号,对于部分题目中描述位(行号,列号)。
重要考点:如何将给出的(字号、位号)与盘块号进行互推算。
下面的都是按照磁盘块、字号、位号从0开始进行推算的:
公式:
b = ni + j
。实际相互转换的例题如下:
如何分配:若文件需要K个块,①顺序扫描位示图,找到K个相邻或不相邻的"0"。②根据字号、位号算出对应的盘块号,将相应盘块分配给文件。③将相应位设置为"1"。
如何回收:①根据回收的盘块号计算出对应的字号、位号。②将相应二进制位设置位"0"。
适用场景:连续分配、离散分配都适合。
出现原因:空闲表法、空闲链表法不适用于大型文件系统,因为空闲表或空闲链表可能会过大。UNIX系统中采用了成组链接法对磁盘空闲块进行管理。
成组链接法
:文件卷的目录中专门用一个磁盘块作为"超级块",当系统启动时需要将超级快读入内存,并且要保证内存和外存当中的"超级块"数据一致。
对应超级块的分布如下图所示:
在超级块中,第一个红色的记录了当前超级块中有多少块空余的盘块,实际指的就是盘块号201-300这100个都是空闲块号。对于201-300则都是表示指向的空闲块号。
注意点1:对于一个分组中的块号不需要连续,可以是离散指向不同的盘块的。
注意点2:若是没有下一组空闲块,此处设置为某特殊块,例如右上角的-1就表示无空闲块。
需求1:需要一个空闲块
做法:
需求2:分配100空闲块
做法:
回收案例1:假设每个分组最多为100个空闲块,此时第一个分组已经有99个块,还要再回收一块。
做法:此时会再超级块中的最后一个位置增加一个块,并且给快数+1。
回收案例2:假设每个分组最多为100个空闲块,此时第一个分组已经有100个块了,还要再回收一个块。
做法:需要将超级块中的数据复制到新回收的块中,并修改超级块中的内容,让新回收的块称为第一个分组。
例子:使用C语言来创建无结构文件
若是我们想要寻找第16个字符o,我们可以基于逻辑地址指向的第16个位置来进行读取出来这个o:
现象:对于用户看来所有的字符都是连续存放的,占用一片连续的逻辑地址空间,我们只需要提供我们想要访问的字符在文件中的逻辑地址就可以顺序找到任何一个数据。
一个磁盘块大小为1KB,会将一整个文件拆分成一个个逻辑块,逻辑块号依次为0、1、2、3,之后操作系统会根据它的文件管理策略来决定是用连续分配还是其他分配方式,上图咋磁盘中的效果则是采用了连续分配的方式,采用连续分配方式在物理结构上也是连续的。
我们来分析一下之前C语言使用库函数的底层原理:
fgetc
底层使用了Read系统调用,操作系统将(逻辑块号,块内偏移量)转换为(物理块号,块内偏移量)。
对于物理结构实际数据的存储也可以采用链接分配方式
,逻辑地址会转换为(逻辑块号,起始地址,结尾地址)来进行寻找:
同样也可以采用索引分配
的方式:操作系统为每一个文件都维护一张索引表,其中记录了逻辑块号->物理块号的映射关系。
注意:无论操作系统采用了什么样的分配方式,什么样的物理地址,只要我们提供了我们要访问的文件逻辑地址,操作系统总能够讲它转变为相应的物理块号和块内偏移量。
下面是创建结构体,将多个结构体数据写入到文件中:
其中fwrite(参数1,参数2,参数3,参数4)
函数解析:
在用户看来每一条记录都是连续存放的,并且每一条用户记录占用空间都相同。
接着我们来看下访问学生5如何使用C语言函数实现:
实际在物理结构上,操作系统会将物理地址按照块为单位存储数据:
对于最终数据存储的方案同样可以有多种:链接、索引、连续
作为用户视角,我们可以实现为顺序存储以及链式存储:
作为操作系统视角,对于实际的物理地址可以采用连续分配,也可以采用链式分配方式存储在磁盘当中:
PS:
这种索引文件都是由文件的创建者自己来决定的。
流程:首先将文件前的1M字节的索引项全部读出来,接着找到目标学生的索引项,根据这些索引项找到目标学生他的索引项,最终根据这个索引项的信息来确定目标学生存在哪一个逻辑地址中,接着再去实际物理位置读取信息即可。
物理结构:同样索引文件也可以采用多种分配方式进行分配空间
这里来举索引文件采用索引分配的例子:可以看到用户视角与操作系统视角都维护了索引表
要区分索引文件的索引表以及索引分配的索引表区别:
总结:作为用户我们自己可以在逻辑上建立我们自己定义的索引表,而在物理结构中操作系统若是采用索引分配那么也会根据实际数据来构建索引表,这两个点要能够进行区分。
创建系统调用:create系统调用
在进行create系统调用时需要提供三个参数:
操作系统在进行create系统调用时,主要做的事情:
删除文件系统调用:delete系统调用
发起Delete系统调用需要的参数:
操作系统在处理Delete系统调用时,主要做的几件事情:
打开文件系统调用:open系统调用
在很多操作系统中,在对文件进行操作之前,要求用户先使用open系统调用"打开文件",需要提供的几个主要参数:
操作系统在处理open系统调用的时候,主要做几件事:
1、根据文件存放路径找到相应的目录文件,从目录中找到文件名对应的目录项,并检查该用户是否有指定的操作权限。
2、若是用户有操作权限,此时会将目录项复制到内存中的"打开文件表"中,并将对应标目的编号返回给用户,之后用户使用打开文件表的编号来指明要操作的文件。
获取到对应内存中的打开文件表中的编号后,就需要每次都查目录,可以加快文件的访问速度。
介绍另一种打开文件表:系统打开表
系统打开文件表
,整个系统之后一张,在这个打开文件表中会记录所有正在被其他进程使用的文件的一些信息。另外,每个进程也会有自己的一个打开文件表,在表中记录了自己的进程打开的文件是哪一些。
系统表索引号
,该索引号指向了系统的打开文件表指定的表项。读写指针
:读/写指针记录了该进程对文件的读/写操作进行到的位置。访问权限
:如果进程打开文件时原本申明的是"只读",那么该进程不能够对文件进行写操作。在系统的打开文件表中包含有一个打开计数器
,若是多个进程打开了同一个文件,那么在指定的文件表项上就会进行计数器+1,如下图有两个进程打开同一个文件,此时打开计数器为2:
打开计数器作用:若是在打开计数器不为0的时候,我们进行删除,此时就会提示"暂时无法删除该文件",实际系统在背后做的事情就是先检查了系统打开文件表,根据打开计数器来确认此时是否有进程在使用该文件。
关闭系统调用:close系统调用
当进程使用完文件后,要"关闭文件",操作系统在处理Close系统调用时,主要做了下面几件事情:
1、将进程的打开文件表相应表项删除。例如将进程B的文件删除如下
2、回收分配给该文件的内存空间等资源。
3、系统打开文件表的打开计数器count-1,若是count=0,那么就才会删除系统的打开文件表表项!!!
读文件系统调用:read系统调用
效果:能够将文件数据读入内存,当双击某个文档时,就会触发系统的读文件功能,也就是read系统调用,将文件数据从外存读入内存,并显示在屏幕上。
例如:当双击test文本文档的时候,此时就会先找到对应的文件目录表项,此时会读取到"记事本"进程的"打开文件表"中。
进程使用read系统调用完成读操作:
1、此时需要①指明是哪个文件,还需要指明要②读入多少数据(如:读入1KB)、指明③读入的数据要放在内存中的什么位置。
2、在处理read系统调用时,会从读指针指向的外存中,将用户指定大小的数据读入用户指定的内存区域当中。
写文件系统调用:write系统调用
功能:可以将更改过的文件数据写回外存。
进程使用write系统调用完成写操作:
1、需要指明哪个文件(一般就是文件表的索引号)、写出多少数据(如1KB)、写回外存的数据放在内存的什么位置。
2、操作系统处理write系统调用时,会从用户指定的内存区域,将指定大小的数据写回写指针指向的外存。
打开文件与读文件比较:只有读文件才会把文件的数据真正的从外存读入内存,而对于读/写操作时,用户无需提供文件名、文件路径,只需要提供文件描述符也就是打开文件表中的索引号即可确定文件的物理地址。
索引结点
:是一种文件目录瘦身策略,由于检索文件时只需要使用到文件名,因此可以将除了文件名之外的其他信息放到索引结点中,这样目录项中就只需要包含文件名、索引结点指针。
链接计数器:索引结点中设置一个链接计数器count,用于表示链接到本索引结点上的用户目录项数。若count=2,说明此时有两个用户目录链接到该索引结点上或者说有两个用户在共享此文件。
删除链接:
当删除掉一个用户共享文件时如下:
基于符号链的共享方式来创建文件时,该文件类型并不是目标文件,而是一个Link类型的文件,记录了文件1的存放路径,如"C:/User1/aaa",类似于Windows操作系统的快捷方式:
这类Link方式的类似于windows中的快捷方式。
举例:当User3访问"ccc"时,操作系统判断文件"ccc"属于Link类型文件,于是会根据其中记录的路径层层寻找目录,最终找到User1的目录中"aaa"表项,于是就找到了文件1的索引结点。
实际windows案例:例如我们桌面的快捷图标
删除软链接文件:若是我们软链接指向的目录文件删除掉,那么当前的这个软链接文件就已经失效了!
示例:若是我们提前先将QQ的启动.exe删除,接着去双击打开对应的快捷方式,就会出现如下提示
注意:对于软链接这种方式每一次访问共享文件的时候都需要一层一层的去查询目录,这个过程是需要进行I/O磁盘操作,因此使用软链接方式去访问一个共享文件速度会比硬链接更慢。
方式:给文件设置一个"口令",如abc111232,用户请求访问该文件时必须提供"口令"。
原理:口令一般存放在文件对应的FCB或索引节点中,用户在访问文件前需要输入"口令",操作系统会将用户提供的口令以及FCB中存储的口令对比,若是正确,允许该用户访问文件。
优点:保存口令的开销不多,验证口令的时间开销很小。
缺点:正确的"口令"存放在系统内部,不够安全。
原理:使用某个密码对文件进行加密(例如密码5位,文件100位,那么每五位每五位进行加密),在访问文件时需要提供正确的"密码",才能够对文件进行正常的解密。
文件内容:实际内容时经过加密之后的。
实操过程:用户若是能够提供正确的密码,那么是可以将这个加密的文件给它解密成原有的数据形式。
案例1:使用正确的密码进行解密,最终得到的文件内容就是原始数据
案例2:使用错误的密码,导致最后解密得到的文件内容依旧有误
优点:保密性强,不需要再系统中存储"密码",用户想要查看文件的时候,使用对应的密码解密即可。
缺点:编码/译码,或者说加密/解密需要花费一定时间。
实现方式:在每个文件的FCB(或索引结点)中增加一个访问控制列表(Access-Control List,ACL),该表中记录了各个用户对该文件执行哪些操作。
过程原理:用户在对文件进行访问类型时,可以查询对应文件的访问控制列表来查看他是否具有权限,若是没有权限拒绝访问。
访问类型如下:
每个文件都有对应的访问控制表:
弊端:有的计算机可能会有很多用户,因此访问控制可能会很大,采用精简的访问列表来解决问题。
精简访问列表
:以"组"为单位,标记各"组"用户可以对文件执行哪些操作。
例如分组:系统管理员、文件主、文件主的伙伴、其他用户几个分组。
我们只需要为各个分组来设置相应的访问权限即可:
查询权限流程:当某用户想要访问文件时,系统会检查该用户所属的分组是否有相应的访问权限。
加密保护比口令保护安全性更高,但是开销更大。
访问控制则更加灵活,将访问权限分为多种类型。
注意:若是对某个目录进行了访问权限的控制,那么要对目录下的所有文件进行相同的访问权限控制。
文件系统从上至下来分成各个层次如下图:
用户接口【文件的基本操作章节】:文件系统需要向上层的用户提供一些简单易用的功能接口。这层就是用于处理用户发出的系统调用请求,例如Read、Write、Open、Close等系统调用。
文件目录系统【文件目录章节】:用户是通过文件路径来访问文件的,因此这一层需要根据用户给出的文件路径找到相应的FCB或索引结点。所有和目录、目录项相关的管理工作都是在本层完成,例如:管理活跃的文件目录表、管理打开文件等。
存取控制模块【文件保护小节】:为了保证文件数据的安全,还需要验证用户是否有访问权限。这一层主要完成了文件保护的相关功能。
逻辑文件系统与文件信息缓冲区【文件的逻辑结构】:用户指明想要访问的文件记录号,这一层需要将记录号转换为对应的逻辑地址。
物理文件系统【文件的逻辑结构小节】:这一层需要把上一层提供的文件逻辑地址转换为实际的物理地址。
辅助分配模块【文件的存储空间管理】:负责文件存储空间的管理,负责分配与回收存储空间。
设备管理模块【磁盘管理章节】:直接与硬件进行交互,负责和硬件直接相关的一些管理工作,如:分配设备、分配设备缓冲区、磁盘调度、启动设备、释放设备等。
案例:假设某用户请求删除文件"D:/工作目录/学生信息.xlsx"的最后100条记录。
流程:
磁盘刚刚被生产出来时,没有划分扇区。
物理格式化
:即低级格式化,用来划分扇区,检测坏扇区,并用备用扇区来替换坏扇区。
逻辑格式化
:将磁盘分区来进行划分分区,对于一个磁盘划分了多少分区,一个分区大小多少,地址范围,这些都是用分区表来进行记录。
在每一个分区中可以建立各自独立的文件系统,例如在C盘分区中,建立了一个unix的文件系统,对应的unix文件系统结构如下:
引导块
:负责开机时初始化操作系统。超级块
:通过超级块可以快速找到磁盘里所有的空闲块。【迅速找到若干个空闲盘块】空闲空间管理
:位示图可以快速判断特点的磁盘块目前是否为空闲。【快速判断一个磁盘块是否空闲】i节点区
:索引结点,每一个文件都有一个对应的索引节点,所有的索引节点都是放在这个节点区中,在该区域时连续存放的,并且一个索引节点的大小相同,可以通过索引节点快速定位地址(索引节点大小、下标)。根目录
:任何一个文件系统都必须从根目录出发来建立下一级的目录或者存储新的文件。对于超级块、空闲空间管理在功能上有重合性,但是在实际的使用中有一定的区别。
内存分为:用户区
、内核区
。
内核区
:
open系统调用打开文件的背后过程:
对于open函数调用结束后会获取一个文件描述符,这个文件描述符就是进程打开文件表的索引,通过这个索引可以定位到系统打开文件表中的目录项,从而最终可以直接定位到文件的物理地址。
read函数的执行流过程:
上面调用open函数后得到的fd文件描述符就是对应内核区中的进程打开文件表,进而又通过打开文件表索引找到对应的系统打开文件表的目录项,最终确定文件的物理地址来进行访问外存读取数据。【过程只有一次磁盘访问I/O】
整个过程如下图所示:
首先看一下普通的文件系统:
我们发现对于不同的文件系统若是发起打开文件函数调用时,例如UFS文件系统、NTFS文件系统、FAT文件系统,不同的文件系统相对应的函数各不相同。
对于不同的系统,我们想要完成同样功能的调用则需要编写不同的函数调用代码,一旦底层系统不一样就需要修改代码,如何解决这种问题呢?
vfs
。虚拟文件系统
:
虚拟文件的系统特点:
1、向上层用户进程提供统一标准的系统调用接口,屏蔽底层具体文件系统的实现差异。
2、VFS
要求下层的文件系统必须实现某些规定的函数功能,例如:open/write/read
,一个新的文件系统想要在某操作系统上被使用,那么就必须满足该操作系统VFS的要求。
3、每打开一个文件,VFS就在主存中新建一个vnode
,用统一的数据结构表示文件,无论该文件存储在哪个文件系统。
可以看到不同的文件系统表示的目录项各不相同,UFS文件系统的时inode
,FAT文件系统的目录项则是包含多种字段的:
那么最终实际上我们需要统一读取的目录项,在VFS中提供了一个vnode节点,其他对应的文件目录项属性都会统一的复制到这个节点的表项中,此时VFS可以用一个统一的数据结构vnode表示任何一个文件的信息。
每打开一个文件就会在主存中新建一个vnode。
4、在vnode
中还包含函数功能指针,它用于指向具体文件系统的函数功能列表。
包含这样的一个函数功能指针的好处:只要我们open打开了一个文件,那之后文件进行任何操作如read、write都可以先找到这个文件的vnode接着根据器记录的这个函数功能指针,再找到相对应文件系统的函数功能列表,执行具体的函数。
文件系统挂载(mounting)
:即文件系统安装/装载。
如何将一个文件系统挂载到操作系统中呢?
文件系统挂载要做的事情:
①在VFS中注册新挂载的文件系统。内存中的挂载表(mount table)
包含了每个文件系统的相关信息,包括文件类型、系统类型、容量大小等。
②新挂载的文件系统,要向VFS提供一个函数地址列表。
③将新文件系统加到挂载点(mount point),也就是将新文件系统挂载到某个父目录下。
举例:windows例如U盘插入电脑时,就需要挂载到操作系统的虚拟文件系统当中(实际会挂载对应新分配的盘符上);MAC的挂载点是根目录下的volume目录
整理者:长路 时间:2023.7.16-18