文件
文件是进程创建的信息逻辑单元.一个磁盘一般含有几千甚至几百万个文件,每个文件是独立于其他文件的.文件不仅仅被用来对磁盘建模,以替代对随机存储器(RAM)建模.事实上,每个文件都是一种地址空间.
文件是受操作系统管理的,有关文件的构造、命名、存取、使用、保护、实现和管理方法都是操作系统设计的主要内容。从总体上看,操作系统处理文件的部分称为文件系统。
文件命名
文件具体的命名规则在各个系统中是不同的,不过所有的现代操作系统都允许用1至8个字母组成的字符串作为合法文件名。通常文件名中也允许有数字和一些特殊字符。许多文件系统支持长达255个字符的文件名。
有的文件系统区分文件名大小写,有的不区分。UNIX是前一类,MS-DOS是后一类。
许多操作系统支持文件名用圆点隔开分为两部分,如文件名,prog.c.文件拓展名通常表示文件的一些信息.
在某些系统中(如UNIX),文件拓展名只是一种约定,操作系统不强迫采纳它.名为file.txt的文件也许是文本,这个文件名是给用户看的,而不是传递什么信息给计算机.
但是另一方面,C编译器可能要求它编译的文件以.c结尾,否则它会拒绝编译.
文件结构
文件有多种构造方式
- 字节序列
- 记录序列
- 树
所有UNIX,MS-DOS,Windows都采用字节序列的方式.
文件类型
UNIX和Windows都有普通文件和目录,UNIX还有字符特殊文件和块特殊文件.
普通文件一般分为ASCII文件和二进制文件.ASCII文件由多行正文组成.二进制文件通常有一定的内部结构,使用该文件的程序才了解这种结构.
文件存取
早期的操作系统只有一种文件存取方式:顺序存取.进程在这些系统中可从头顺序读取文件的全部字节或记录,但不能跳过某一些内容,也不能不按顺序读取.可以返回起点,需要时可以返回起点读多次.
当使用磁盘存储文件时,我们可以不按顺序地读取文件中的字节或记录,或者按照关键字而不是位置来存取记录.这种能够以任何次序读取其中字节或记录的文件称作随机存取文件.
文件属性
文件都有文件名和数据.另外,所有的操作系统还会保存其他与文件相关的信息,如文件创建的日期和时间、文件大小等.这些附加信息称为文件属性,有人称之为元数据.
目录
一级目录系统
目录系统最简单形式是在一个目录中包含所有的文件.这有时称为根目录,但是由于只有一个目录,所以名称不重要.早期的计算机中,这种系统很普遍.
层级目录系统
层次目录系统允许用户创建任意数量的子目录,这种能力为用户组织其工作提供了强大的结构化工具.因此几乎所有现代文件系统都是这个方式组织的
路径名
绝对路径名:由从根目录到文件的路径组成.不同系统路径中使用的分隔符不一样.
相对路径 : 使用.表示当前路径,使用..表示上一级目录
共享文件
共享动机
- 多用户操作系统中不同的用户间需要共享一些文件来共同完成任务;
- 网络上不同的计算机之间需要进行通信,需要远程文件系统的共享功能的支持。
基于索引节点的共享方式(硬链接)
- 实现方法:一个共享文件只有一个索引节点,如果不同文件名的目录项需要共享该文件,只需目录项中的指针都指向该索引节点即可。在索引节点中再增加一个计数值来统计指向该索引节点的目录项的个数,这样一来就需要删除该文件时可以判断计数值,只有计数值为1时才删除该索引节点,若计数值大于1,则把计数值减1即可。
- 优点:能够实现文件的异名共享。
- 缺点:当文件被多个用户共享时,文件拥有者不能删除文件。
利用符号链实现文件共享(软链接)
- 实现方法:新建一个链接文件,文件里面存储需要共享文件的路径名。
- 优点:解决了基于索引节点共享方法中文件拥有者不能删除共享文件的问题。
- 缺点:当其他用户要访问共享文件时,需要逐层查找目录,开销较大。
文件系统的实现
文件系统布局
文件系统存放在磁盘上.多数磁盘划分为一个或多个分区,每个分区中有一个独立的文件系统.
如图,磁盘0号扇区称为主引导记录(MBR),用来引导计算机.在MBR的结尾是分区表.该表给出了每个分区的起始和结束地址.表中的一个分区被标记为活动分区.在计算机被引导时,BIOS读入并执行MBR.MBR做的第一件事是确定活动分区,读入它的第一个块,称为引导块,并执行之.为统一起见,每个分区都从一个引导块开始,以便将来在这个分区放入操作系统.
除了引导块开始之外,磁盘分区的布局是随着文件系统的不同而变化的.文件系统经常包含有如图的一些项目.第一个是超级块,超级块包含文件系统的所有关键参数,在计算机启动时,或者该文件系统首次使用时,把超级块读入内存,超级块中的典型信息包括:
- 确定文件系统类型使用的魔数
- 文件系统中数据块数量以及其他重要的管理信息
- 文件系统空闲块的信息,可以用位图或者指针列表的形式给出
- 后面可能是一组i节点,这个一个数据结构数组,每个文件一个.
- 接着可能是根目录
- 最后,磁盘的其他部分存放了其他所有的目录和文件
文件实现
连续分配
连续分配的思路很简单,把文件按照盘块大小分割.存放在磁盘的一个连续区域.
连续分配有两大优点
- 实现简单,记录文件所占用的磁盘块现在只需要知道第一块的地址和总的磁盘块数即可.
- 读操作性能好.因为在单个操作中就可以从磁盘中读出整个文件.只需要一次寻找,之后不需要寻道和旋转延迟.
连续分配同样也有明显的不足:
随着时间的推移,磁盘会变得零碎.开始时,碎片不是一个问题,因为每个文件都可以在上一次写磁盘的末尾写入.但是,磁盘最终会写满,所以要么压缩磁盘,要么重新使用空洞之中的空闲空间.重新使用空洞中的空闲空间可以维护一个空洞列表,这是可行的.但是,当创建一个文件的时候,需要预测这个文件将要占用的空间
链表分配
第二种方案是给每个磁盘块的末尾都记录下一个磁盘块的地址.这一种方案可以充分利用每个磁盘块,不会因为磁盘碎片而浪费空间.
但另一方面在这种方案中,尽管顺序读取文件非常方便,但是随即存取却很难,每次都要从第一个块开始.
而且由于指针占用了一定字节,每个磁盘块存储的字节数不再是2的整数次幂.虽然无伤大雅,但是确实降低了速度,现在读取完整一个块,要从磁盘块中获得和拼接信息.
内存中采用表的链表分配
取出每个盘块的指针字,把它放到内存的一张表中,就可以解决上述链表的两个不足.
但是仍然有不足,缺点是必须把整个表放在内存中,对于大硬盘,可能会占用600m或者800m内存.
inode
给每个文件赋予一个称为i节点的数据结构,其中列出了文件属性和文件块的磁盘地址.
如图:
在i节点中,列出了所有磁盘块的地址.相对于内存中用表的方式而言,这种机制只有当文件打开时i节点才会读取进内存.
i节点的一个问题是,当文件过大,一个i节点放不下怎么办呢?一种解决方案是i节点最后一个磁盘地址不指向数据块,而是指向一个包含磁盘块地址的块的地址.如图所示,更高级的方案是可以有两个或更多个包含磁盘地址的块,或者指向其他存放地址的磁盘块的磁盘块.
目录实现
目录系统的主要功能是把ASCII文件名映射成定位文件数据所需的信息
一个要解决的问题是何处存放文件属性.每个文件系统维护诸如文件所有者以及创建时间等文件睡醒,它们必须被存储在某个地方.
在windows中,文件属性被存放在目录项中.
在UNIX中,文件属性直接存放在i节点中.
变长的文件名如何实现?
最简单的方法是给文件名一个限制,限制在255.但是这种方法会浪费大量存储空间.
另一种方法是每个目录项都变长.这种方法中,每个目录项有一个固定部分,这个固定部分通常以目录项的长度开始,后面是固定格式的数据,通常包括所有者,创建时间,保护信息等.这个固定长度的头后面是实际文件名.坑能耐是正序放置,如图:
每个文件名以一个特殊字符(通常是0)结束.为了是目录项正好充满边界,每个文件名被填充为整数个字.(图中阴影).
这种方法有两个问题
- 移走文件后,就留下一个大小可变的空隙,下一个进来的不一定正好填满.
- 另一个问题是,一个目录项坑内分布在不同页面上,读取文件名可能发生缺页故障.
另一个方法是每个目录项都有固定长度,文件名放在最后的堆中,目录项中用一个指针指向堆中文件名.
当一个文件目录项被移走时,另一个文件目录项总是可以填满这个空.当然还是要对堆进行管理,读取文件名时,还是可能发生缺页中断.
文件系统管理和优化
磁盘空间管理
块大小
块是对磁盘扇区的一层封装,扇区的基本大小是512Byte
越大的块意味着越大的磁盘空间浪费,因为文件末尾剩的都被浪费了.
但是越小的块意味着越多的寻道时间和越低的性能.
记录空闲块
两种方法广泛采用来记录空闲块
磁盘块链表
每个块中包含尽可能多的空闲磁盘块号.对于1KB大小的块和32位的磁盘号,空闲表中每个块包含有255个空闲块的块号.对于500g的磁盘,拥有488乘以10的6次方个块.为了在255块中存放全部这些地址,需要190万个块.通常情况下,采用空闲块存放空闲表.这样存储器基本是空的
空闲块位图
在位图方案中,在位图中,空闲块用1表示,已分配块用0表示.对于500GB磁盘,需要60000个1KB块存储.显然位图法更节省空间,因为每个块只用一个bit表示.而链表法用32bit.只有当磁盘快要被填满时,链表法才比位图法省.
文件系统性能
高速缓存
最常用的减少磁盘访问次数的方法是块高速缓存和缓冲区高速缓存,这里的高速缓存指的是一系列的块,他们逻辑上属于磁盘,但实际上基于性能考虑被保存在内存中.
管理高速缓存常用的算法是:检查所有的请求,查看在高速缓存中有没有对应的块.如果有就直接用缓存的,如果没有就把它调入缓存.
如果缓存已满,就要调出.调出的算法可以使用精确的LRU算法实现,因为对缓存的引用不像内存访问那么频繁.
块提前读
第二个明显提高文件系统性能的技术是:在需要用到块之前,师徒提前将其写入高速缓存,从而提高命中率.特别的,许多文件都是顺序读的.如果请求文件系统在某个文件中生成块k,文件系统执行相关操作且在完成之后,会在用户不察觉的情况下检查高速缓存,以便确定块k+1是否已经在高速缓存.如果还不在,文件系统会为块k+1安排一个阅读,因为文件系统希望在需要用到该块时,它已经在高速缓存中了.
当然,这项技术只适用于顺序读取的文件.对随机存取文件,提前读丝毫不起作用.相反,它还会帮倒忙,因为读取无用的块以及从高速缓存中删除潜在有用的块将会占用固定的磁盘带宽.
减少磁盘臂运动
另一种重要的技术是把可能顺序存取的块放在一起,当然最好是在同一柱面上,从而减少磁盘臂的移动次数.如果用位图记录空闲块,找到连续的空闲块是很容易的,如果使用空闲链表,则会困难很多.
不过,即使采用空闲表,也可以采用块簇技术.这里用到一个小技巧,即不用块而用连续块簇来跟踪磁盘存储区.如果一个扇区有512字节,有可能系统采用1KB的块(2个扇区),却按照每2块(4个扇区)一个单位来分配磁盘存储区.这和2KB的磁盘块并不相同,因为在高速缓存中它依然使用1KB的块,磁盘与内存数据之间传送也是以1KB为单位进行,但在一个空闲的系统上顺序读取文件,寻道的次数可以减少一半,从而使文件系统性能大大改善.
磁盘臂调度算法
磁盘臂调度算法主要有这么几种:
- 先来先服务(FCFS)算法 :FCFS算法是一种最简单的磁盘调度算法。该算法按进程请求访问磁盘的先后次序进行调度。该算法的特点是合理、简单,但未对寻道进行优化。
- 最短寻道时间优先(SSTF)算法 :SSTF算法选择与当前磁头所在磁道距离最近的请求作为下一次服务的对象。该算法的寻道性能比FCFS算法好,但不能保证平均寻道时间最短,并且可能会使某些进程的请求总被其他进程的请求抢占而长期得不到服务(这种现象称为“饥饿”)。
- 扫描算法(SCAN)或电梯调度算法 :SCAN算法在磁头当前移动方向上选择与当前磁头所在磁道距离最近的请求作为下一次服务的对象。由于这种算法中磁头移动的规律颇似电梯的运行,故也称为电梯调度算法。SCAN算法具有较好的寻道性能,又避免了“饥饿”现象,但其对两端磁道请求比较不公平(通常两端请求都是最后得到服务)。
- 循环扫描(CSCAN)算法 :CSCAN算法是对SCAN算法的改良,它规定磁头单向移动,例如,自里向外移动,当磁头移到最外磁道时立即返回到最里磁道,如此循环进行扫描。该算法消除了对两端磁道请求的不公平。
理解文件系统的几层抽象
第一层抽象
操作系统提供给上层线性编址的磁盘块而不是抽象的柱面,磁头,扇区这几个参数,这是操作系统提供的第一层抽象.linux中,两个扇区是一个磁盘块.
上层告诉操作系统需要第几个磁盘块.操作系统就会把磁盘块转换成对应的柱面和磁头等信息.
第二层抽象
多进程同时使用磁盘时,通过队列使用.操作系统通过一些调度算法对磁盘进行调度
第三层抽象
操作系统使用文件对磁盘进行第三层抽象.文件的本质是一个地址空间.建立了字符流到盘块的映射关系.
第四层抽象
文件系统抽象了整个磁盘,这是对磁盘的第四层抽象.操作系统使用目录树来抽象了整个磁盘.
以上就是操作系统对磁盘的所有抽象.