目录
建议先看:
http://t.csdnimg.cn/59glP-----操作系统(11)--内存管理
http://t.csdnimg.cn/RAzgW-----操作系统(12)--页面分配策略
一.内存映射文件
内存映射文件----操作系统向上层程序员提供的功能(系统调用)。
•方便程序员访问文件数据
•方便多个进程共享同一个文件
传统的文件访问方式:
某文件需要存放到磁盘中,磁盘的存储空间以块为单位,则文件会被拆分为相应的大小相等的块,接着放入磁盘中。
若进程想要访问这一文件的数据,首先,该进程需要打开这一文件。
接着使用seek系统调用来指明需访问文件的哪部分数据,操作系统会用读写指针来记录这一位置。
接着进程会使用read系统调用来指明从这个位置往后要读入多少数据,例如读入10个字节,20个字节。
若现在进程读入的数据,在磁盘中的2号块中,那么操作系统就会将这部分数据读入内存。
当然也可以修改这一数据,如果想要这一修改被保存,则使用write系统调用,把内存中已经修改的数据写回磁盘。
总结,需要用到以下系统调用:
open 系统调用--打开文件
seek 系统调用--将读写指针移到某个位置read 系统调用--从读写指针所指位置读入若干数据(从磁盘读入内存)
write 系统调用--将内存中的指定数据,写回磁盘(根据读写指针确定要写回什么位置)
内存映射文件:
内存映射文件与传统文件访问方式的区别:
若进程想要访问某一文件,就需要先通过open系统调用打开文件,再用mmap系统调用,让操作将文件映射到进程的虚拟地址空间中。这一系统调用会给程序员返回一个指针,这个指针指向刚才映射区域的起始地址。
接下来就可以用访问内存的方式访问文件数据了,就是可以通过操作系统返回的起始地址指针得到起始地址,再加上某地址的偏移量,访问某特定内存区域。
但这里注意,操作系统只是建立了和内存之间的映射关系,但并没有把文件数据读入内存,相当于缺页的状态。例如:
若进程想要访问的是第2块,操作系统发现这一块数据没有调入内存,那么操作系统就会自动将磁盘中的这一数据读入内存。
也就是说程序员不需要进行read系统调用,read是由操作系统自动完成的。
若进程想要关闭文件,进程可以使用close系统调用来关闭文件,当关闭文件后,操作系统会自动将被修改的数据写回磁盘。
也就是说程序员不需要用write系统调用将被修改的数据写回磁盘,这一步是操作系统自动完成的。
总结:
open 系统调用--打开文件
mmap 系统调用--将文件映射到进程的虚拟地址空间。
close 系统调用---关闭文件
与传统的文件访问方式的区别:
① 以访问内存的方式访问② 文件数据文件数据的读入、写出由操作系统自动完成
③ 进程关闭文件时,操作系统自动将文件被修改的数据写回磁盘
内存映射文件能够使程序员更方便地使用文件数据,也可以实现文件的共享。
文件共享的实现:
对于磁盘中存放的某文件的数据,可以被进程1用系统调用的方式映射到自己的虚拟地址空间中,同理,也可以被进程2用系统调用的方式映射到自己的虚拟地址空间中。此时两个进程的虚拟地址空间是相互独立的。
操作系统会把这两个独立的虚拟地址空间映射到相同的物理内存中,实际操作就是,操作系统只需要修改两进程的页表,让对应的页面映射到相同的物理页框中。这样就能实现两个进程共享同一个文件的数据。
若进程1改变了2号块的数据,那么进程2会通过映射的物理内存,看到2号块数据的改变,所以
多个进程可以映射同一个文件,实现共享
在物理内存中,一个文件对应同一份数据,当一个进程修改文件数据时,另一个进程可以立马“看到”
内存映射文件的优点:
1.程序员编程更简单,已建立映射的文件,只需按访问内存的方式读写即可
2.文件数据的读入/写出完全由操作系统负责,I/O效率可以由操作系统负责优化
二.文件的属性
1.文件名
由创建文件的用户决定文件名,主要是为了方便用户找到文件,同一目录下不允许有重名文件。
2.标识符
一个系统内的各文件标识符唯一,对用户来说毫无可读性因此标识符只是操作系统用于区分各个文件的一种内部名称。
3.类型
指明文件的类型。
4.位置
文件存放的路径(让用户使用)、在外存中的地址(操作系统使用,对用户不可见)
5.大小
指明文件大小
6.创建时间、上次修改时间、文件所有者信息
7.保护信息
对文件进行保护的访问控制信息
三.文件的逻辑结构
文件的“逻辑结构”,就是指在用户看来,文件内部的数据应该是如何组织起来的。而物理结构”指的是在操作系统看来,文件的数据是如何存放在外存中的(这个下面会探讨)。
类似于数据结构的“逻辑结构”和“物理结构”。
如“线性表”就是一种逻辑结构,在用户角度看来,线性表就是一组有先后关系的元素序列,如:a,b,c, d,e 。
“线性表”这种逻辑结构可以用不同的物理结构实现,如:顺序表/链表。顺序表的各个元素在逻辑上相邻,在物理上也相邻;而链表的各个元素在物理上可以是不相邻的。因此,顺序表可以实现“随机访问”,而“链表”无法实现随机访问。
可见,算法的具体实现与逻辑结构、物理结构都有关(文件也一样,文件操作的具体实现与文件的逻辑结构、物理结构都有关)。
文件的逻辑结构如下:
1.无结构文件
由一些二进制或字符流组成,又称为“流式文件”。例如,windows操作系统中的文本文件(txt)。
2.有结构文件
由一组相似的记录组成,又称“记录式文件”。每条记录又若干个数据项组成,数据项是文件系统中最基本的数据单位。。如:数据库表文件。一般来说,每条记录有一个数据项可作为关键字(作为识别不同记录的ID)
在本例中,数据表的每一行就是一条记录,记录的是一组相关数据项的集合。学号”即可作为各个记录的关键字。
根据各条记录的长度(占用的存储空间)是否相等,又可分为定长记录和可变长记录两种。
定长记录:
这个有结构文件由定长记录组成,每条记录的长度都相同(共128 B)。各数据项都处在记录中相同的位置,具有相同的顺序和长度(前32B一定是学号,之后32B一定是姓名)
可变长记录:
这个有结构文件由可变长记录组成,由于各个学生的特长存在很大区别,因此“特长”这个数据项的长度不确定,这就导致了各条记录的长度也不确定。当然,没有特长的学生甚至可以去掉“特长”数据项。
那么有结构文件中的各记录又该如何组织起来呢?
顺序文件:
文件中的记录一个接一个地顺序排列(逻辑上),记录可以是定长的或可变长的。各个记录在物理上可以顺序存储或链式存储。
顺序存储---逻辑上相邻的记录在物理上也相邻(类似于顺序表)
链式存储---逻辑上相邻的记录物理上不一定相邻(类似于链表)
根据记录是否按照关键字的顺序进行排列,又可分为串结构和顺序结构:
串结构:指的是记录之间的顺序与关键字无关
顺序结构:指的是记录之间的顺序按关键字顺序排列
假设顺序文件在物理上采用链式存储的方式:
无论是定长/可变长记录,都无法实现随机存取,每次只能从第一个记录开始依次往后查找(因为各个元素之间存放的位置是离散的,没有规律,所以不能通过计算得到某元素的存放位置)
假设顺序文件在物理上采用顺序存储的方式:
1.若该文件是可变长记录的文件:
无法实现随机存取。每次只能从第一个记录开始依次往后查找。
由于记录内容不同,所以记录长度不同,需要显式地给出记录长度,假设用1字节表示记录长度,这些记录也是不规律的,因此如果想要找到某一条记录对应的地址,那么就需要从第一个记录依次往后查找。
所以若是可变长记录的文件,即使采用顺序存储,也无法实现顺序存储。
2.若采用定长记录:
可实现随机存取。记录长度为L,则第i个记录存放的相对位置是i*L
(1)若记录采用串结构,即记录之间的顺序与关键字无关,那么无法快速找到某关键字对应的记录,所以只能从头开始寻找指定关键字对应的记录
(2)若记录采用顺序结构,即记录之间的顺序按关键字顺序排列,那么可以快速找到某关键字对应的记录(如折半查找)。
所以:定长记录的顺序文件,若物理上采用顺序存储,则可实现随机存取;若能再保证记录的顺序结构,则可实现快速检索即根据关键字快速找到对应记录)
注:一般来说,考试题目中所说的“顺序文件”指的是物理上顺序存储的顺序文件。顺序文件的缺点是增加/删除一个记录比较困难(如果是串结构则相对简单,因为串结构不需要保证记录按照关键字排序)。
实际上,为了减少磁盘的I/O次数,操作系统会管理一个日志文件,用日志文件记录对文件记录进行修改的信息,隔一段时间后再将这些信息统一地合并到外存的文件数据中。
总结:
索引文件:
对于可变长记录文件,要找到第i个记录,必须先顺序第查找前i-1个记录,但是很多应用场景中又必须使用可变长记录。所以要使用到索引文件。
索引文件结构如下,每一个文件会记录一张索引表以加快检索速度,每个索引表项会对应文件的一条记录,文件中的这些记录在物理上可以离散地存放。但索引表的各个表项是需要连续存放的。另外,每个索引表的表项大小相同。
索引表本身是定长记录的顺序文件。因此可以快速找到第i个记录对应的索引项。可将关键字作为索引号内容,若按关键字顺序排列,则索引表还可以支持按照关键字折半查找。
每当要增加/删除一个记录时,需要对索引表进行修改。由于索引文件有很快的检索速度,因此主要用于对信息处理的及时性要求比较高的场合。
另外,可以用不同的数据项建立条个索引表。如:学生信息表中,可用关键字“学号”建立一张索引表。也可用“姓名”建立一张索引表。这样就可以根据“姓名”快速地检索文件了。(例如:SQL就支持根据某个数据项建立索引的功能)
索引顺序文件:
对于索引文件,每个记录对应一个索引表项:因此索引表可能会很大。比如:文件的每个记录平均只占8B,而每个索引表项占32个字节,那么索引表都要比文件内容本身大4倍,这样对存储空间的利用率就太低了。所以引出了索引顺序文件。
索引顺序文件是索引文件和顺序文件思想的结合。索引顺序文件中,同样会为文件建立一张索引表,但不同的是:并不是每个记录对应一个索引表项,而是一组记录对应一个索引表项。
在下面例子中,学生记录按照学生姓名的开头字母进行分组。每个分组就是一个顺序文件,分组内的记录不需要按关键字排序
索引顺序文件的索引项也不需要按关键字顺序排列,这样可以极大地方便新表项的插入。并且索引顺序表的表项也少了许多。
所以索引文件是定长记录的顺序结构的顺序文件,而索引顺序文件是定长记录的串结构的顺序文件。
用这种策略确实可以让索引表“瘦身”,但是是否会出现不定长记录的顺序文件检索速度慢的问题呢?
一个顺序文件有10000个记录,则根据关键字检索文件,只能从头开始顺序查找(这里指的并不是定长记录、顺序结构的顺序文件),平均须查找 5000 个记录。
若采用索引顺序文件结构,可把10000个记录分为
=100 组,每组 100个记录。则需要先顺序查找索引表找到分组(共100个分组,因此索引表长度为100,平均需要查50次),找到分组后,再在分组中顺序查找记录(每个分组100个记录,因此平均需要查50次)。可见,采用索引顺序文件结构后,平均查找次数减少为 50+50=100 次。
同理,若文件共有 10^6个记录,则可分为1000个分组,每个分组1000个记录。根据关键字检索一个记录平均需要查找 500+500=1000次。这个查找次数依然很多,如何解决呢?
多级索引顺序文件:
为了进一步提高检索效率,可以为顺序文件建立多级索引表。例如,对于一个含 106个记录的文件,可先为该文件建立一张低级索引表,每100个记录为一组,故低级索引表中共有10000个表项(即10000个定长记录),再把这10000个定长记录分组,每组100个,为其建立顶级索引表,故顶级索引表中共有100个表顶。
所以顶级索引表中有100个表项,低级索引表每个索引表也有100个表项,每个表项对应多个分组,每个分组中又会对应100个记录。此时,检索一个记录平均需要查找
50+50+50=150 次
四.文件之间应该怎样组织起来
下图的目录就是“文件夹”,用户可以自己创建一层一层的目录,各层目录中存放相应的文件。系统中的各个文件就通过一层一层的目录合理有序的组织起来了。
目录其实也是一种特殊的有结构文件(由记录组成),那么如何实现目录呢?、
五.文件的基本操作
文件的基本操作,也就是操作系统可以向上提供的功能:
1.创建文件(create系统调用)
可以“创建文件”,(点击新建后,图形化交互进程在背后调用了“create 系统调用”)
进行 Create 系统调用时,需要提供的几个主要参数:
1.所需的外存空间大小(如:一个盘块,即1KB)
2.文件存放路径(“D:/Demo”)
3.文件名(这个地方默认为"新建文本文档.txt")
操作系统在处理 Create 系统调用时,主要做了两件事:
1.在外存中找到文件所需的空间(结合空闲链表法、位示图、成组链接法等管理策略,找到空闲空间)
2.根据文件存放路径的信息找到该目录对应的目录文件(此处就是D:/Demo目录),在目录中创建该文件对应的目录项。目录项中包含了文件名、文件在外存中的存放位置等信息。
2.删除文件(delete系统调用)
可以“删除文件”(点了“删除”之后,图形化交互进程通过操作系统提供的“删除文件”功能,即 delete 系统调用,将文件数据从外存中删除)
进行 Delete 系统调用时,需要提供的几个主要参数:
1.文件存放路径(“D:/Demo”
2.文件名(“test.txt”)
操作系统在处理 Delete 系统调用时,主要做了几件事:
1.根据文件存放路径找到相应的目录文件,从目录中找到文件名对应的目录项。
2.根据该目录项记录的文件在外存的存放位置、文件大小等信息,回收文件占用的磁盘块。(回收磁盘块时,根据空闲表法、空闲链表法位图法等管理策略的不同,需要做不同的处理)3.从目录表中删除文件对应的目录项。
3.读文件(read系统调用)
可以“读文件”,将文件数据读入内存,才能让CPU处理(双击后,“记事本”应用程序通过操作系统提供的“读文件”功能,即 read 系统调用,将文件数据从外存读入内存,并显示在屏幕上)
进行 read 系统调用时:
1.需要指明是哪个文件(在支持“打开文件”操作的系统中,只需要提供文件在打开文件表中的索引号即可)。
2.还需要指明要读入多少数据(如:读入1KB)。
3.指明读入的数据要放在内存中的什么位置。
操作系统在处理 read 系统调用时,会从读指针指向的外存中,将用户指定大小的数据读入用户指定的内存区域中。
4.写文件(write系统调用)
可以“写文件”,将更改过的文件数据写回外存(我们在“记事本”应用程序中编辑文件内容,点击“保存”后,“记事本”应用程序通过操作系统提供的“写文件”功能,即 write 系统调用,将文件数据从内存写回外存)
进行 write 系统调用时:
1.需要指明是哪个文件(在支持“打开文件”操作的系统中,只需要提供文件在打开文件表中的索引号即可)
2.还需要指明要写出多少数据(如:写出1KB)
3.写回外存的数据放在内存中的什么位置
操作系统在处理 write 系统调用时,会从用户指定的内存区域中,将指定大小的数据写回写指针指向的外存。
注:对于"读/写文件"用“文件描述符”(即这一文件在内存中的打开文件表的索引号)即可指明文件,不再需要用到“文件名。
5.打开文件(open系统调用)
可以“打开文件”(读/写文件之前,需要“打开文件”,即open系统调用)这和用户双击触发的read系统调用不同。
只有读文件时才会将文件数据从外存读入内存,继续看就知道了。
进行 open 系统调用时,需要提供的几个主要参数:
1.文件存放路径(“D:/Demo”)
2.文件名(“test.txt”)
3.要对文件的操作类型(如:r只读;rw 读写等)
操作系统在处理open系统调用时,主要做了几件事:
1.根据文件存放路径找到相应的目录文件,从目录中找到文件名对应的的目录项,同时不同用户对文件的操作权限不同,这些信息记录在目录项中,系统会根据目录项检查该用户是否有指定的操作权限。
若用户有指定的操作权限,则将目录项复制到内存中的“打开文件表”中(打开文件时并不会把文件数据直接读入内存,而是将目录项复制到内存的“打开文件表”)。系统会将文件的索引号(这个“索引号”,也称“文件描述符”)返回给用户。之后用户使用打开文件表的索引号来指明要操作的文件,即之后用户进程再操作文件就不需要每次都重新查目录了,这样可以加快文件的访问速度。
注:这里有两种"打开文件表"
1.系统的打开文件表(整个系统只有一张),这个表中记录了所有被进程使用的文件。
2.另外,每个进程都有自己的一张"打开文件表",这个表中记录了每个进程打开的文件。这一表中记录了系统索引号。这一索引号对应了系统"打开文件表"的编号。
•系统的打开文件表中记录了"打开计数器",记录此时有多少个进程打开了此文件。
•用户进程的打开文件表中记录了"读写指针",记录了该进程对文件的读/写操作进行到的位置。
•用户进程的打开文件表中记录了"访问权限",如果打开文件时声明的是只读”,则该进程不能对文件进行写操作。
不同进程对同一文件的读写指针(读/写进行到的位置)不同,访问权限不同。
为什么需要在系统中设置打开文件表的总表?
可以方便实现某些文件管理的功能。例如:在Windows系统中,我们尝试删除某个txt文件,如果此时该文件已被某个“记事本”进程打开,则系统会提示我们“暂时无法删除该文件”。其实系统在背后做的事就是先检查了系统打开文件表,确认此时是否有进程正在使用该文件。
读文件与打开文件的联系与区别:
联系:
都会使用到内存中的“文件打开表”,系统会将文件的索引号(这个“索引号”,也称“文件描述符”)返回给用户。之后用户使用打开文件表的索引号来指明要操作的文件,即之后用户进程再操作文件就不需要每次都重新查目录了,这样可以加快文件的访问速度。
区别:
open(打开文件)不会把文件数据直接读入内存,而是将目录项复制到内存的“打开文件表”)
read(读文件)会将文件数据从外存读入内存。
6.关闭文件(close系统调用)
6.可以“关闭”文件(读/写文件结束之后,需要“关闭文件”,即close系统调用)这和用户点击关闭按钮触发的delete系统调用不同。
操作系统在处理Close系统调用时,主要做了几件事:
1.将进程的打开文件表相应表项删除。
2.回收分配给该文件的内存空间等资源。
3.系统打开文件表的打开计数器count减1,若count=0,则删除对应表项。例如,在打开文件示例中的打开计数器由2变为1。
注:可用几个基本操作完成更复杂的操作,比如:“复制文件”:先创建一个新的空文件,再把源文件读入内存,再将内存中的数据写到新文件中。
六.文件的物理结构
1.对非空闲磁盘块的管理
外存与内存相同,外存也是由一个个存储单元组成的,每个存储单元可以存储一定量的数据(如 1B)。每个存储单元对应一个物理地址。
类似于内存分为一个个“内存块”,外存会分为一个个“块/磁盘块/物理块”。每个磁盘块的大小是相等的,每块一般包含2的整数幂个地址( 如本例中,一块包含 2^10 个地址,即 1KB)。
同样类似的是,在内存管理中,进程的逻辑地址空间被分为一个一个页面,在外存管理中,为了方便对文件数据的管理,文件的逻辑地址空间也被分为了一个一个的文件“块”。
于是文件的逻辑地址也可以表示为(逻辑块号,块内地址)的形式。操作系统同样需要将逻辑地址转换为外存的物理地址(物理块号,块内地址)的形式(该物理地址用户是不可知的,用户只能通过逻辑地址来操作自己的文件,操作系统要负责实现从逻辑地址到物理地址的映射)。块内地址的位数取决于磁盘块的大小。(例如这里,一块物理块包含2^10个地址,那么块内地址的位数为10位)。
注:很多操作系统中,磁盘块的大小与内存块、页面的大小相同,那么内存与磁盘之间的数据交换(即读/写操作、磁盘I/O)都是以“块”为单位进行的。即每次读入一块,或每次写出一块。
外存被分为一个个磁盘块,那么该如何存放文件数据呢?这就是我们要探讨的文件的物理结构,即文件分配方式
(1)连续分配
连续分配方式要求每个文件在磁盘上占有一组连续的块。也就是逻辑相邻的块在物理上也必须相邻。并且依然要保持块间的相对顺序。
用户通过逻辑地址来操作自己的文件,操作系统如何实现从逻辑地址到物理地址的映射?
逻辑地址(逻辑块号,块内地址)→物理地址(物理块号,块内地址)。只需转换块号就行,块内地址保持不变就行了。为了实现地址映射,文件目录表记录了起始块号和长度(占用多少个块),例如"aaa"文件起始块号为4,占用了连续的3个块。
用户给出要访问的逻辑块号,操作系统找到该文件对应的目录项(FCB),通过FCB就得到起始块号,用该起始块号加上逻辑块号
物理块号=起始块号+逻辑块号
例如上图,想要访问"aaa"文件的逻辑块号2,再加上起始块号4,得到物理块号=2+4=6
当然,还需要检查用户提供的逻辑块号是否合法(逻辑块号
长度就不合法)
所以,只要用户提供逻辑块号,可以直接算出逻辑块号对应的物理块号,因此连续分配支持顺序访问和直接访问(即随机访问)
顺序访问,即,想要访问"aaa"的逻辑块号2,就必须先访问逻辑块号0,1
直接访问,即,想要访问"aaa"的逻辑块号2,不需要访问0,1
连续分配支持顺序访问和直接访问(即随机访问),这是连续分配的一大优点,还有就是:
读取某个磁盘块时,需要移动磁头。访问的两个磁盘块相隔越远,移动磁头所需时间就越长。
由于连续分配的磁盘块在物理上是相邻的,连续分配的文件在顺序读/写时速度最快。
连续分配的缺点:
1.物理上采用连续分配的文件不方便拓展
如下图所示,黄色区域表示物理上连续分配的文件A占用了连续的三个块,橙色区域为其他文件已经占用的磁盘块,绿色区域为空闲磁盘块。
若此时文件A要拓展,需要再增加一个磁盘块(总共需要连续的4个磁盘块)。由于采用连续结构,因此文件A占用的磁盘块必须是连续的。而文件A后面相邻的块已经被其他文件占有,若要拓展文件A,那么必须把文件A迁移到有连续4个空闲磁盘块的区域中。而数据迁移需要很大开销。
所以,物理上采用连续分配的文件不方便拓展。
2.物理上采用连续分配的文件存储空间利用率低
如下图所示,橙色区域为非空闲块,绿色区域为空闲磁盘块。若此时创建的新文件大小为3个块,而连续分配需要连续的3个磁盘块,那么无法为其分配足够的存储空间。
所以物理上采用连续分配,存储空间利用率低,会产生难以利用的磁盘碎片。可以用紧凑来处理碎片,但是需要耗费很大的时间代价。
(2)链接分配
链接分配采取离散分配的方式,可以为文件分配离散的磁盘块。分为隐式链接和显式链接两种。
隐式链接:
目录中记录了文件存放的起始块号和结束块号。当然,也可以增加一个字段来表示文件的长度。除了文件的最后一个磁盘块之外,每个磁盘块中都会保存指向下一个盘块的指针,这些指针对用户是透明的。
如何实现逻辑块号到物理块号?
用户给出要访问的逻辑块号i,操作系统找到该文件对应的目录项(FCB),从目录项中找到起始块号(即0号块),将0号逻辑块读入内存,只有将0号块读入内存后,才能得到0号块指向1号块的数据,由此知道1号逻辑块存放的物理块号,于是读入1号逻辑块,再找到2号逻辑块的存放位置.………..以此类推。
因此,读入i号逻辑块,总共需要i+1次磁盘I/O。
所以,采用链式分配(隐式链接)方式的文件,只支持顺序访问,不支持随机访问,查找效率低。另外,指向下一个盘块的指针也需要耗费少量的存储空间。
采用隐式链接,是否方便拓展文件?
由于文件的块可以离散存放,若此时要拓展文件,则可以随便找一个空闲磁盘块,挂到文件的磁盘块链尾,并修改文件的FCB。
例如下图,要新增8号块,那么将8号块放到磁盘块链尾。
所以,采用隐式链接的链接分配方式,很方便文件拓展。另外,所有的空闲磁盘块都可以被利用,不会有碎片问题外存利用率高。
总结:
隐式链接---除文件的最后一个盘块之外,每个盘块中都存有指向下一个盘块的指针。文件目录包括文件第一块的指针和最后一块的指针。
优点:很方便文件拓展,不会有碎片问题,外存利用率高。
缺点:只支持顺序访问,不支持随机访问,查找效率低,指向下一个盘块的指针也需要耗费少量的存储空间。
显式链接:
把用于链接文件各物理块的指针显式地存放在一张表中。即文件分配表(FAT, File Allgcation Table)
假设某个新创建的文件“aaa”依次存放在磁盘块2-->5-->0-->1,那么在"aaa"这一文件的FCB(目录项)中,需要记录这一文件存放的起始块号,目录中只需记录文件的起始块号。
而FAT(文件分配表)中,会显式记录文件各个块的链接关系,例如0号物理块下一块为5号块,1号块下一块设置为特殊的值,例如-1,表示1号块是文件的最后一块……
假设某个新创建的文件“bbb”依次存放在磁盘块4-->23-->3
注意:一个磁盘仅设置一张FAT,每个文件都统一存放在这一个FAT表中。开机时,将FAT读入内存,并常驻内存。FAT的各个表项在物理上连续存储,且每一个表项长度相同,因此“物理块号”字段可以是隐含的。
采用显式链接,如何实现文件的逻辑块号到物理块号的转变?
用户给出要访问的逻辑块号i,操作系统找到该文件对应的目录项(FCB),从目录项中找到起始块号,若i>0,则查询内存中的文件分配表FAT往后找到i号逻辑块对应的物理块号。
例如,访问文件"aaa",0号逻辑块对应的物理块号为2号物理块,2号物理块对应的下一块是5号物理块(1号逻辑块),依次类推……
逻辑块号转换成物理块号的过程不需要读磁盘操作。
所以,采用链式分配(显式链接)方式的文件,支持顺序访问,也支持随机访问(想访问i号逻辑块时,并不需要依次访问之前的0~i-1号逻辑块,可以直接通过FAT表查询i号逻辑块存放的地址),由于块号转换的过程不需要访问磁盘,因此相比于隐式链接来说,访问速度快很多。
显然,显式链接也不会产生外部碎片,也可以很方便地对文件进行拓展。
总结:
显式链接---把用于链接文件各物理块的指针显式地存放在一张表中,即文件分配表(FAT,FileAllocation Table)。一个磁盘只会建立一张文件分配表。开机时文件分配表放入内存,并常驻内存。
优点:很方便文件拓展,不会有碎片问题,外存利用率高,并且支持随机访问。相比于隐式链接来说,地址转换时不需要访问磁盘,因此文件的访问效率更高。
缺点:文件分配表的需要占用一定的存储空间。
注:考试题目中遇到未指明隐式/显式的“链接分配”,默认指的是隐式链接的链接分配。
(3)索引分配
索引分配允许文件离散地分配在各个磁盘块中,系统会为每个文件建立一张索引表,索引表中记录了文件的各个逻辑块对应的物理块(索引表的功能类似于内存管理中的页表--建立逻辑页面到物理页之间的映射关系)。索引表存放的磁盘块称为索引块。文件数据存放的磁盘块称为数据块。
目录中需要记录文件的索引块是几号磁盘块。
假设某个新创建的文件“aaa”的数据依次存放在磁盘块2-->5-->13-->9。
7号磁盘块作为“aaa”文件的索引块,索引块中保存了索引表的内容。
如图所示,“aaa”文件的索引表存放在7号磁盘块中,而实际数据存放在2,5,13,9这些磁盘块中。
注:在显式链接的链式分配方式中,文件分配表FA 是一个磁盘对应一张。而索引分配方式中,索引表是一个文件对应一张。
可以用固定的长度表示物理块号(如:假设磁盘总容量为1TB=2^40B,磁盘块大小为1KB,则共有 2^30个磁盘块,则可用4B(4个字节,4*8的32个磁盘块)表示磁盘块号),因此,索引表中的“逻辑块号”可以是隐含的。
所以我们可以看到,索引表的表项每一行占有4个字节,所以只要知道索引表的起始位置,再根据逻辑块号,就可以知道该逻辑块号对应的物理块号的地址。
如何实现文件的逻辑块号到物理块号的转换?
用户给出要访问的逻辑块号i,操作系统找到该文件对应的目录项(FCB),从中找到这一文件的索引块,再从这一索引块中读出索引表,将索引表从外存读入内存,并查找索引表即可知 i号逻辑块在外存中的存放位置。 而不需要先访问0~i-1号块。
可见,索引分配方式可以支持随机访问。
文件拓展也很容易实现(只需要给文件分配一个空闲块,并增加一个索引表项即可)
但是索引表需要占用一定的存储空间
若每个磁盘块1KB,一个索引表项4B,则一个磁盘块只能存放 256 个索引项。
如果一个文件的大小超过了256块,那么一个磁盘块是装不下文件的整张索引表的,如何解决这个问题?有三种方案①链接方案,②多层索引,③混合索引
①链接方案
如果索引表太大,一个索引块装不下,那么可以将多个索引块链接起来存放。
我们为文件分配多个索引块,并且在每个索引块中分配一定空间用于指向下一个索引块, 若采用链接方式,索引表中只需要记录第一个索引块的块号即可。
如下图, 若想找到逻辑块号为256对应的物理块号,由于各个索引块中是用链接的方式连接起来,为了找到第二个索引块的块号,需要把第一个索引块读入内存,再根据第一个索引块中的指针,找到第二个索引块的块号,把第二个索引块读入内存,才能得到逻辑块号256对应的物理块号。
假设磁盘块大小为1KB、一个索引表项占4B,则一个磁盘块只能存放256个索引项。若一个文件大小为 256*256KB=65,536 KB=64MB
该文件共有 256*256个块,也就对应256*256个索引项,也就需要256个索引块来存储256个索引表,每个索引表对应256个索引项,这些索引块用链接方案连起来。
若想要访问文件的最后一个逻辑块,就必须找到最后一个索引块(第256个索引块),而各个索引块之间是用指针链接起来的,因此必须先顺序地读入前255个索引块。
这一方案显然很低效,所以提出了多层索引方案。
②多层索引
多层索引会建立多层索引(原理类似于多级页表)。使第一层索引块指向第二层的索引块。还可根据文件大小的要求再建立第三层、第四层索引块。
假设磁盘块大小为1KB,一个索引表项占4B,则一个磁盘块只能存放256个索引项。在索引表中,只需要存放顶级索引表对应的索引块即可。
若采用多层索引,则各层索引表大小不能超过一个磁盘块,若某文件采用两层索引,则该文件的最大长度可以到256(第一个索引表最多有256个索引项,即分别指向256个索引表)*256(第二个索引表最多有256个索引项)*1KB(每个索引项又分别指向一个数据块,数据块的大小为1KB)=65,536KB=64MB
怎么实现逻辑块号到物理块号?
可根据逻辑块号算出应该查找索引表中的哪个表项。
如:要访问1026号逻辑块,则1026/256=4(1024号逻辑块存放在4号二级索引表中,操作系统只需要在一级索引表中找到4号索引项对应的物理块号即可),1026%256=2(表示需要查看4号二级索引表中的2号表项,2号表项就是1024号逻辑块对应的物理块)
因此可以先将一级索引表调入内存,查询4号表项,将其对应的二级索引表调入内存,再查询二级索引表的2号表项即可知道1026号逻辑块存放的磁盘块号了。
访问目标数据块,需要3次磁盘I/O。
同理,若采用三层索引,则文件的最大长度为256*256*256*1KB=16GB。
类似的,访问目标数据块,需要4次磁盘I/O。
总结:
采用K层索引结构,且顶级索引表未调入内存,则访问一个数据块只需要K+1次读磁盘操作
③混合索引
混合索引是指多种索引分配方式的结合。例如,一个文件的顶级索引表中,既包含直接地址索引(直接指向数据块),又包含一级间接索引(指向单层索引表)、还包含两级间接索引(指向两层索引表)。
如下图所示,8个直接地址索引对应8个数据块
一个一级间接索引会指向一个单层索引表,每个索引表最多有256个表项,所以这个索引表对应256个数据块
同理,一个二级间接索引对应256*256=65,536块。
那么如图的这种结构的索引支持的最大文件长度为65800KB。
假设想访问某个逻辑块,那么需要几次I/O操作。
若顶级索引表还没读入内存
① 访问0~7号逻辑块(直接地址),需要两次读磁盘,第一次是读入顶级索引表,第二次是通过顶级索引表中的直接地址找到目标数据块存放的位置。
注:对于小文件,只需较少的读磁盘次数就可以访问目标数据块(一般计算机中小文件更多)。
② 访问8~263(一级间接索引),需要三次读磁盘,第一次是读入顶级索引表,第二次是通过一级间接索引找到下一级索引表,将这一索引表读入内存,第三次是通过这一索引表的索引项找到目标数据块的位置,再将目标数据块读入内存。
③ 同理,访问264~65799(二级间接索引),需要四次读磁盘。
总结:
①链接方案:如果索引表太大,一个索引块装不下,那么可以将多个索引块链接起来存放。缺点:若文件很大,索引表很长,就需要将很多个索引块链接起来。想要找到i号索引块,必须先依次读入0~i-1号索引块,这就导致磁盘I/0次数过多,查找效率低下。
②多层索引:建立多层索引(原理类似于多级页表)。使第一层索引块指向第二层的索引块。还可根据文件大小的要求再建立第三层、第四层索引块。采用K层索引结构,且顶级索引表未调入内存,则访问一个数据块只需要K+1次读磁盘操作。缺点:即使是小文件,访问一个数据块依然需要K+1次读磁盘。
⑧混合索引:多种索引分配方式的结合。例如,一个文件的顶级索引表中,既包含直接地址索引(直接指向数据块),又包含一级间接索引(指向单层索引表)、还包含两级间接索引(指向两层索引表)。优点:对于小文件来说,访问一个数据块所需的读磁盘次数更少。
重点:
①要会根据多层索引、混合索引的结构计算出文件的最大长度(Key:各级索引表最大不能超过一个块);
②要能自己分析访问某个数据块所需要的读磁盘次数(Key:FCB中会存有指向顶级索引块的指针,因此可以根据FCB读入顶级索引块。每次读入下一级的索引块都需要一次读磁盘操作。另外,要注意题目条件--顶级索引块是否已调入内存,若没有则需要增加1次I/O操作)
三种分配方案表格如下:
2.对空闲磁盘块的管理
(1)存储空间的划分与初始化
安装 Windows 操作系统的时候,一个必经步骤就是为磁盘分区(C盘、D盘、E盘等),也就是将物理磁盘划分为一个个文件卷(逻辑卷、逻辑盘)。
初始化就是将各个文件卷划分为目录区、文件区。
目录区主要存放文件目录信息(FCB)、用于磁盘存储空间管理的信息。
文件区用于存放文件数据。
平常使用的电脑都是将一个物理磁盘划分为多个逻辑磁盘,但是在某些系统中,支持超大型文件,可支持由多个物理磁盘组成一个文件卷(一个逻辑磁盘)。
(2)管理方法
空闲表法:
磁盘存放情况如下:
用空闲表来记录第一个空闲盘号,以及空闲盘块数,下图是磁盘对应的空闲表:
空闲表法适用于"连续分配方式"。
那么应该如何分配磁盘块?
与内存管理中的动态分区分配很类似,为一个文件分配连续的存储空间。同样可采用首次适应、最佳适应、最坏适应等算法来决定要为文件分配哪个区间。
例如,新建的文件请求3个块,如果使用首次适应算法的话,操作系统会依次检查空闲表中的各个表项,找到第一个能满足连续3个块空闲的空闲区域。由上图可知,第一个空闲块号为10的空闲区域满足。
最佳适应算法:找到最小的能够满足空闲块数的空闲区域进行分配最坏适应算法:找到最大的能够满足空闲块数的空闲区域进行分配
如何回收磁盘块?
与内存管理中的动态分区分配很类似,当回收某个存储区时需要有四种情况
① 回收区的前后都没有相邻空闲区;(会在空闲表中新增一个表项)
②回收区的前后都是空闲区;(将回收区与前后空闲区合并,那么表项的数量会减少1)
③回收区前面是空闲区;(与前面空闲区合并)
④回收区后面是空闲区。(与后面空闲区合并)
③④两种情况不会导致表项减少。总之,回收时需要注意表项的合并问题
空闲链表法:
空闲链表法又分为空闲盘块链和空闲盘区链,空闲盘块链以盘块为单位组成一条空闲链,空闲盘曲链以盘区为单位组成一条空闲链。
空闲盘块链:
空闲盘块种存储着下一个空闲盘块的指针。
如何分配磁盘块?
若使用空闲盘块链,操作系统会保存链头和链尾指针。如上图,链头为20号磁盘块,链尾为0号磁盘块。若某文件申请K个盘块,则从链头开始依次摘下K个盘块分配,并修改空闲链的链头指针。
如何回收磁盘块?
回收的盘块依次挂到链尾,并修改空闲链的链尾指针。
适用于离散分配的物理结构。为文件分配多个盘块时可能要重复多次操作。
空闲盘区链:
连续的空闲盘块组成一个空闲盘区。空闲盘区中的第一个盘块内记录了盘区的长度、下一个盘区的指针。
如何分配磁盘块?
若使用空闲盘区链,操作系统保存着链头、链尾指针。若某文件申请K个盘块,则可以采用首次适应、最佳适应等算法,从链头开始检索,按照算法规则找到一个大小符合要求的空闲盘区分配给文件。若没有合适的连续空闲块,也可以将不同盘区的盘块同时分配给一个文件,注意分配后可能要修改相应的链指针、盘区大小等数据。
如何回收磁盘块?
若回收区和某个空闲盘区相邻,则需要将回收区合并到空闲盘区中。若回收区没有和任何空闲区相邻,将回收区作为单独的一个空闲盘区挂到链尾。如下图,若此时要回收的是17,18盘区,那么将这一空闲盘区挂到链尾。
空闲盘区链适用于离散分配、连续分配都适用。并且相比于空闲盘块链而言,为一个文件分配多个盘块时效率更高。
位示图法:
对于位示图,每个二进制位对应一个盘块。在本例中,“0”代表盘块空闲,“1”代表盘块已分配。位示图一般用连续的“字”来表示,如本例中一个字的字长是16位,字中的每一位对应一个盘块。因此可以用(字号,位号)对应一个盘块号。当然有的题目中也描述为(行号,列号)
在考试中,要能自己推出盘块号与(字号,位号)相互转换的公式。
注意题目条件:盘块号、字号、位号到底是从0开始还是从1开始
如本例中盘块号、字号、位号从0开始,若n表示字长,则:
(字号,位号)=(i,j)的二进制位对应的盘块号b=ni+j若字号、位号从1开始,n表示字长,则:
b=n(i-1)+j
b号盘块对应的字号i=b/n,位号j=b%n
若字号、位号从1开始,n表示字长,则:
i=(b-1)DIV n +1
j=(b-1)MOD n +1
如何分配磁盘块?
若文件需要K个块:
①顺序扫描位示图,找到K个相邻或不相邻的“0”;
②根据字号、位号算出对应的盘块号,将相应盘块分配给文件;
③将相应位设置为“1”。
如何回收磁盘块?
①根据回收的盘块号计算出对应的字号、位号;
②将相应二进制位设为“0”。
若采用位示图法,则连续分配、离散分配都适用。
成组链表法:
空闲表法、空闲链表法不适用于大型文件系统,因为空闲表或空闲链表可能过大。UNIX系统中采用了成组链接法对磁盘空闲块进行管理。
文件卷的目录区中专门用一个磁盘块作为“超级块”,当系统启动时需要将超级块读入内存。并且要保证内存与外存中的“超级块”数据一致。
在超级块中,记录了下一组空闲盘块的数量以及空闲盘块号,通过这些盘块号就可以依次找到下一个分组的各个盘块。
例如下图,300磁盘块,作为下一个分组的第一个磁盘块,这一磁盘块会记录下一个分组的空闲磁盘块的信息,100表示下一个分组的空闲盘块数的块数,301~400表示下一个分组的空闲块号。
若已经没有下一组空闲块,则表示下一组空闲盘块数的磁盘块的下一个磁盘块用某特殊值表示,说明这是最后一组空闲块。所以最后一个分组块数为99(比其他分组的盘块数少1),其中一个用来存放特殊值,如下图,特殊值为-1。
注:一个分组中的块号不需要连续,此处只是为了更方便看出各个分组的数量。
如何分配磁盘块?
例如,现在需要1个空闲块
① 检查第一个分组的块数是否足够。1<100,因此是足够的。
② 分配第一个分组中的1个空闲块,并修改相应数据。(如下图,会将分组中的最后一个磁盘块201分配出去,磁盘块分配出去后,就要修改超级表的数据,将这一空闲磁盘块删除,并且将块数由100改为99)
注:由于超级块已经读入内存,所以只需要根据超级块中的数据进行检查即可。
又例如,需要100个空闲块
①检查第一个分组的块数是否足够。100=100,是足够的。
②分配第一个分组中的100个空闲块。但是由于300号磁盘块内存放了再下一组的信息,因此如果直接把300号磁盘块分配给文件,那么和下一组链接的信息就断了,所以在300号空闲磁盘块分配给文件之前,需要将300号磁盘块中的数据复制到超级块中。
这样就能保证虽然分组全部分配给文件,但是下一个分组的链接信息依然保存在超级块中。
如何回收磁盘块?
(1)若第一个分组没满,可以把回收的空闲块插到第一个分组中。
例如,假设每个分组最多为100个空闲块,此时第一个分组已有99个块,还要再回收一块,我们首先将该块的链接信息放到超级块中,再将下一组空闲盘块数由99变为100。
(2)若第一个分组已满,则把回收的空闲块作为一个新分组。
例如,假设每个分组最多为100个空闲块,此时第一个分组已有100个块,还要再回收一块。由于第一个分组满,所以不能将这一回收的空闲块放到第一个分组中。所以我们可以把这一回收的空闲块作为一个新的分组。但是我们也要将超级块的内容复制到这一新分组中。那么这一新回收的块即可作为新的分组,拥有了下一分组的链接指针。
并且超级块的数据也需要进行修改,使其指向新分组,也就是新的回收的空闲块组成的新分组,由于新分组中只有一个空闲块,所以超级块中表示下一分组的块数的磁盘块中的内容为1
七.文件的逻辑结构与物理结构的对比
逻辑结构与物理结构分类如下:
在用户看来,整个文件占用的是连续一片的逻辑地址空间,但是从操作系统的视角看,只负责把文件拆分成一个个逻辑块,再根据分配方式(连续分配,连接分配,索引分配)将文件的数据块分配到磁盘中。
对于顺序文件,各记录可以顺序存储或链式存储。
(1)顺序存储:各条记录相邻着存放
对于顺序存储,我们可以直接确定第i条记录的逻辑地址,即支持随机访问。
(2)链式存储:各条记录离散着存放,用指针表示先后关系
而对于链式存储,要确定i条记录的逻辑地址,则必须先读入0~i-1条记录
① 链式存储的顺序文件,可以采用连续分配:逻辑上相邻的块,物理上也相邻。
② 链式存储的顺序文件,也可以采用链接分配,这里区分链式存储和链接分配:
文件内部各条记录链式存储:由创建文件的用户自己设计的
文件整体用链接分配:由操作系统决定,操作系统会将整个文件拆分成一个个相等的逻辑块
例如上图,逻辑块1中是没有数据的,但是操作系统不会管这些,他只管将整个文件都拆分成一个个逻辑块。再将这些逻辑块放到磁盘中。
在磁盘中存放这些逻辑块时,会用链接分配的方式记录这些逻辑块的存放顺序:
对于索引文件:
索引文件从用户视角来看,整个文件依然是连续存放的。如:前1MB存放索引项,后续部分存放记录。可以先将索引项调入内存,查询索引项,找到目标学生的索引项,根据索引项得到目标学生信息的逻辑地址,最后读取信息即可。
索引文件可以采用连续分配,链接分配,也可以采用索引分配,也就是操作系统会维护一个索引表,索引表会记录逻辑块号到物理块号的映射关系。
索引文件的索引表:用户自己建立的,映射:关键字--->记录存放的逻辑地址
索引分配的索引表:操作系统建立的,映射:逻辑块号--->物理块号
总结:
不是采用链式存储的顺序文件对应链接分配,索引文件对应索引分配,顺序文件和索引文件对应的是逻辑结构,在用户看来,整个文件占用连续的逻辑地址空间。文件内部的信息如何组织,由用户自己决定,操作系统并不很关心。
链接分配和索引分配是操作系统将逻辑块分配到外存(磁盘块中)的方式,即实现逻辑块号到物理块号的映射关系,是操作系统负责的。
所以,顺序文件可以使用3种分配方式,索引文件同理,两者是没有必然联系的。用户只需要提供想要访问的文件的逻辑地址,由操作系统负责将文件以不同的方式存储起来,并且将用户提供的逻辑地址转为物理地址。
八.文件目录
1.文件控制块(实现文件目录的关键数据结构)
目录文件用于记录目录下存放的文件,例如,下图就是根目录(D盘)的目录文件。
目录本身就是一种有结构文件,由一条条记录组成。每条记录对应一个在该目录下的文件。
当我们双击“照片”后,操作系统会在这个目录表中找到关键字“照片”对应的目录项(也就是记录),然后从外存中将“照片”目录的信息读入内存,于是,“照片”目录中的内容就可以显示出来了。
同样,照片对应的目录文件也是由一条条记录组成,每个记录对应其中一个文件。目录文件中的一条文件记录就是一个文件控制块(FCB)
FCB的有序集合称为“文件目录”,一个FCB就是一个文件目录项。FCB 中包含了文件的基本信息(文件名、物理地址、逻辑结构、物理结构等),存取控制信息(是否可读/可写、禁止访问的用户名单等),使用信息(如文件的建立时间、修改时间等)。
最重要,最基本的还是文件名、文件存放的物理地址
因为FCB 实现了文件名和文件之间的映射。使用户(用户程序)可以实现“按名存取。
可以对目录进行哪些操作?
搜索:当用户要使用一个文件时,系统要根据文件名搜索目录,找到该文件对应的目录项
创建文件:创建一个新文件时,需要在其所属的目录中增加一个目录项
删除文件:当删除一个文件时,需要在目录中删除相应的目录项
显示目录:用户可以请求显示目录的内容,如显示该目录中的所有文件及相应属性
修改目录:某些文件属性保存在目录中,因此这些属性变化时需要修改相应的目录项(如:文件重命名)
2.目录结构
(1)单级目录结构:
早期操作系统并不支持多级目录,整个系统中只建立一张目录表,每个文件占一个目录项。
单级目录实现了“按名存取”,但是不允许文件重名,所以在创建一个文件时,需要先检查目录表中有没有重名文件,确定不重名后才能允许建立文件,并将新文件对应的目录项插入目录表中。
显然,单级目录结构不适用于多用户操作系统。
(2)两级目录结构:
早期的多用户操作系统,采用两级目录结构。分为主文件目录(MFD,MasterFileDirectory)和用户文件目录(UFD,User Flie Directory)
主文件目录记录用户名及相应用户文件目录的存放位置
用户文件目录由该用户的文件FCB组成
而不同用户的文件可以重名,即允许不同用户的文件重名。文件名虽然相同,但是对应的其实是不同的文件。
两级目录结构允许不同用户的文件重名,也可以在目录上实现实现访问限制(检查此时登录的用户名是否匹配)。例如,user1想要访问user2的用户文件目录,那么操作系统就可以检查user1和user2登录的用户名是否匹配,若匹配,则可以访问。
但是两级目录结构依然缺乏灵活性,用户不能对自己的文件进行分类。
(3)多级目录结构(树形目录结构):
用户(或用户进程)要访问某个文件时要用文件路径名标识文件,文件路径名是个字符串。各级目录之间用“/”隔开。从根目录出发的路径称为绝对路径。例如,自拍.jpg 的绝对路径是"/照片/2015-08/自拍.jpg"系统根据绝对路径一层一层地找到下一级目录。刚开始 从外存读入根目录的目录表;找到“照片”目录的存放位置后, 从外存读入对应的目录表;再找到“2015-08”目录的存放位置,再从 外存读入对应目录表;最后才找到文件“自拍jpg”的存放位置。 整个过程需要3次读磁盘I/O操作。
很多时候,用户会连续访问同一目录内的多个文件(比如:接连査看“2015-08”目录内的多个照片文件),显然,每次都从根目录开始查找,是很低效的。因此可以设置一个“当前目录”。
例如,此时已经打开了“照片”的目录文件,也就是说,这张目录表已调入内存,那么可以把它设置为“当前目录”。当用户想要访问某个文件时,可以使用从当前目录出发的“相对路径”
在Linux中,表示当前目录,因此如果“照片”是当前目录,则"自拍.jpg"的相对路径为:“./2015-08/自拍.jpg”。从当前路径出发,只需要查询内存中的“照片”目录表,即可知道"2015-08"目录表的存放位置,从外存调入该目录,即可知道“自拍.jpg”存放的位置了,就是只需要经过一次I/O操作。
可见,引入“当前目录”和“相对路径”后,磁盘I/O的次数减少了。这就提升了访问文件的效率。
(4)无环图目录结构:
树形目录结构可以很方便地对文件进行分类,层次结构清晰,也能够更有效地进行文件的管理和保护。但是,树形结构不便于实现文件的共享。为此,提出了“无环图目录结构”。
在树形目录结构的基础上,增加一些指向同一节点的有向边,使整个目录成为一个有向无环图。可以更方便地实现多个用户间的文件共享。
可以用不同的文件名指向同一个文件,甚至可以指向同一个目录(共享同一目录下的所有内容,即共享一个目录,因为目录本身也是一个特殊的文件)。例如user1用户可以用demo文件名指向某文件,而user2也可以用Mydemo文件名指向该文件。
因为文件可能是被多个用户使用的,所以不能一个用户执行删除操作就进行删除,所以需要为每个共享结点设置一个共享计数器,用于记录此时有多少个地方在共享该结点。用户提出删除结点的请求时,只是删除该用户的FCB、并使共享计数器减1,并不会直接删除共享结点。只有共享计数器减为0时,才删除结点。
例如上图,user1想删除demo,那么只会对demo这个目录项进行删除,并且共享计数器减1,而这个文件对应的内容不会被直接删除。
注意:共享文件不同于复制文件。在共享文件中,由于各用户指向的是同一个文件,因此只要其中一个用户修改了文件数据,那么所有用户都可以看到文件数据的变化。而复制文件时,对副本进行修改,原来文件的数据不会变化。
3.索引结点(对文件控制块的优化)
文件目录是由一个个FCB的集合组成,但是在查找各级目录的过程中只需要用到“文件名”这个信息,只有文件名匹配时,才需要读出文件的其他信息。因此可以考虑让目录表“瘦身”来提升效率。
我们将除了文件名以外,其他冗余的数据放到索引结点中。每个文件都会对应唯一的索引结点,采用索引结点后,目录中只包含文件名和各个文件索引结点对应的指针的信息,由于目录项长度减小,因此每个磁盘块可以存放更多个目录项,因此检索文件时磁盘I/O的次数就少了很多,并且目录文件占用的空间也会少很多。
假设一个FCB是64B,磁盘块的大小为1KB,则每个盘块中只能存放16个FCB。若一个文件目录中共有640个目录项,则共需要占用640/16=40个盘块。因此按照某文件名检索该目录,平均需要查询320个目录项,平均需要启动磁盘20次(每次磁盘I/O读入一块)。
若使用索引结点机制,文件名占14B,索引结点指针占2B,则每个盘块可存放64个目录项,那么按文件名检索目录平均只需要读入320/64=5个磁盘块。显然,这将大大提升文件检索速度。
当找到文件名对应的目录项时,才需要将索引结点调入内存,索引结点中记录了文件的各种信息,包括文件在外存中的存放位置,根据“存放位置”即可找到文件。
存放在外存中的索引结点称为“磁盘索引结点”,当索引结点放入内存后称为“内存索引结点”相比之下内存索引结点中需要增加一些信息,比如:文件是否被修改、此时有几个进程正在访问该文件等。
九.其他需要由操作系统实现的文件管理功能
1.文件共享
操作系统为用户提供文件共享功能,可以让多个用户共享地使用同一个文件。
注:多个用户共享同一个文件,意味着系统中只有“一份”文件数据。并且只要某个用户修改了该文件的数据,其他用户也可以看到文件数据的变化。
如果是多个用户都“复制”了同一个文件,那么系统中会有“好几份”文件数据。其中一个用户修改了自己的那份文件数据,对其他用户的文件数据并没有影响。
(1)基于索引结点的共享方式(硬链接)
索引结点是一种文件目录瘦身策略。由于检索文件时只需用到文件名,因此可以将除了文件名之外的其他信息放到索引结点中。这样目录项就只需要包含文件名、索引结点指针。
索引结点中设置一个链接计数变量 count,用于表示链接到本索引结点上的用户目录项数。
若count=2,说明此时有两个用户目录项链接到该索引结点上,或者说是有两个用户在共享此文件。若使用这种方式,删除文件需要注意一点:
若某个用户决定“删除”该文件,则只是要把用户目录中与该文件对应的目录项删除,且索引结点的count值减 1。
若count>0,说明还有别的用户要使用该文件,暂时不能把文件数据删除,否则会导致指针悬空。
当count=0时,系统负责删除文件。
(2)基于符号链的共享方式(软链接:符号链接)
如下图所示,若user3想要用软链接的方式,共享文件1,那么user3会建立一个文件,这个文件是Link 类型的文件(Link 类型的文件名可以不同),记录了文件1的存放路径“C:/User1/aaa”,层层查找目录,最终找到 User1的目录表中的“aaa”表项,于是就找到了文件1的索引结点。类似于 Windows 操作系统的“快捷方式”。
若此时user1和user2不需要使用文件1,由于此时count值变为0,那么文件1以及其对应的索引结点都会被删除。此时user3再访问link型的文件---文件2,操作系统会查找"aaa"的存放路径,尝试找到"aaa"对应文件的目录项,但是这一目录项已经被删除了,通过这一路径已经找不到文件1了。那么这一软链接也失效了。
由于软链接需要一级一级查询目录,即要查询多级目录,会有多次磁盘I/O操作,因此软链接的共享方式比硬链接慢。
2.文件保护
操作系统需要保护文件数据的安全。
(1)口令保护
为文件设置一个“口令”(如:abc112233),用户请求访问该文件时必须提供“口令”。
口令一般存放在文件对应的 FCB 或索引结点中。用户访问文件前需要先输入“口令”,操作系统会将用户提供的口令与FCB中存储的口令进行对比,如果正确,则允许该用户访问文件。
优点:保存口令的空间开销不多,验证口令的时间开销也很小(用户想访问文件,必然要查找FCB,所以口令保存在FCB或索引结点中,验证口令的时间开销小)。
缺点:正确的“口令”存放在系统内部,不够安全。
(2)加密保护
使用某个“密码”对文件进行加密,在访问文件时需要提供正确的“密码”才能对文件进行正确的解密。
例如:一个最简单的加密算法--异或加密,假设用于加密/解密的“密码”为“01001”
系统中保存的不是文件的原始数据,而是加密后的数据,所以用户想要访问这一文件的话,必须解密才行。
通过对比可以发现,如果加密密码和解密密码一致,那么解密结果和原始数据是一致的。
假设用户用错误的密码进行解密,那么解密结果和原始数据是不一样的。
优点:保密性强,不需要在系统中存储“密码”。
缺点:编码/译码,或者说加密/解密要花费一定时间。
口令保护与加密保护的区别在于:
口令保护的口令保存在系统中,由系统验证口令是否正确。如果正确,才能允许用户访问该文件。
加密保护的密码不需要保存在系统之中,密码只有用户自己知道。用一个“密码”对文件加密,用户想要访问文件时,需要提供相同的“密码"才能正确的解密。
所以加密保护的安全性比口令保护的安全性更高,但是加密/解密的开销需要耗费一定时间。
(3)访问控制
在每个文件的FCB(或索引结点)中增加一个访问控制列表(Access-Control List,ACL),该表中记录了各个用户可以对该文件执行哪些操作。各个系统可对文件进行的操作是不一样的。
某文件的访问控制列表如下所示,有的计算机可能会有很多个用户,因此访问控制列表可能会很大,可以用精简的访问列表解决这个问题。
精简的访问列表:以“组”为单位,标记各“组”用户可以对文件执行哪些操作,每个分组对文件的访问权限是不同的。如:分为系统管理员、文件主、文件主的伙伴、其他用户几个分组。
当某用户想要访问文件时,系统会检查该用户所属的分组是否有相应的访问权限。所以系统也需要管理分组的信息。
当“其他用户”想要读取文件,只需要把该用户放入“文件主的伙伴”这个分组即可。
对于口令保护和加密保护,只需要正确的口令或密码就可以使用户对文件进行所有类型的操作,但是对于访问控制,可以使用户对文件进行不同类型的操作:读/写/执行/删除等。实现灵活,可以实现复杂的文件保护功能。
注:如果对某个目录进行了访问权限的控制,那也要对目录下的所有文件进行相同的访问权限控制。