现代操作系统::文件系统
文件系统
- 文件
- 文件命名
- 文件是一种抽象机制,它提供了一种在磁盘上保存信息而且方便以后读取的方法。这种方法可以使用户不必了解存储信息的方法、位置和实际磁盘工作方式等有关细节。
- 文件的具体命名规则在各个系统中是不同的,不过所有的现代操作系统都允许用 1 至 8 个字母组成的字符串作为合法的文件名。
- 有些文件系统区分大小写字母,有些则不区分。 UNIX 属于前一类,老的文件系统 MS-DOS 则属于后一类。
- 许多操作系统支持文件名用圆点隔开分为两部分,如文件名 prog.c。圆点后面的部分称为 文件扩展 名 (file extension)
- 在某些系统中(如所有 UNIX 版本),文件扩展名只是一种约定,操作系统井不强迫采用它。Windows 关注扩展名且对其赋予了含义。
- 文件结构
- 字节序列,一种无结构的字节序列,只对特定程序有意义,目前主流操作系统采取的方案。
- 记录序列,文件是具有固定长度记录的序列,每个记录都有其内部结构。把文件作为记录序列的中心思想是:读操作返回一个记录,而写操作重写或追加一个记录。穿孔卡片采用的方案。
- 树,这种结构中由一棵记录树构成,每个记录不必具有相同的长度,记录的固定位置上有一个键字段。可以根据特定字段找到记录的位置,在一些处理商业数据的服务器上采用。
- 文件类型
- 普通文件,含有包含用户信息的文件。
- ASCII 文件,有多行正文组成,可以显示打印和用编辑器编辑。
- 二进制文件,有特定结构,只对使用该结构的程序有意义。
- 目录,管理文件系统结构的系统文件。
- 字符特殊文件,与输入/输出有关,用于串行 I/O 类设备,如终端、打印机、网络等
- 块特殊文件用于磁盘类设备
- 普通文件,含有包含用户信息的文件。
- 文件访问
- 顺序访问,从头按顺序读取文件的全部字节或记录,但不能跳过某一些内容,也不能不按顺序读取。
- 随机访问文件,能够以任何次序读取其中字节或记录。
- 文件属性
- 名字
- 权限
- 创建时间
- 加锁标记
- 等等
- 文件操作
- create。创建不包含任何数据的文件。该调用的目的是表明文件即将建立,井设置文件的一些属性。
- delete。当不再需要某个文件时,必须删除该文件以释放磁盘空间。任何文件系统总有一个系统调用用来删除文件。
- open。在使用文件之前,必须先打开文件。open 调用的目的是:把文件属性和磁盘地址表装入内存,便于后续调用的快速访问。
- close。访问结束后,不再需要文件属性和磁盘地址,这时应该关闭文件以释放内部表空间。很多系统限制进程打开文件的个数,以鼓励用户关闭不再使用的文件。磁盘以块为单位写入,关闭文件时,写入该文件的最后一块,即使这个块还没有满。
- read。在文件中读取数据。 一般地,读取的数据来自文件的当前位置。调用者必须指明需要读取多少数据,并且提供存放这些数据的缓冲区。
- write。向文件写数据,写操作一般也是从文件当前位置开始。如果当前位置是文件末尾,文件长度增加。如果当前位置在文件中间,则现有数据被覆盖,并且永远丢失。
- append。此调用是 write 的限制形式,它只能在文件末尾添加数据。若系统只提供最小系统调用集合,则通常没有 append。很多系统对同一操作提供了多种实现方法,这些系统中有时有 append 调用.
- seek。对干随机访问文件,要指定从何处开始获取数据,通常的方法是用 seek 系统调用把当前位置指针指向文件中特定位置。 seek 调用结束后,就可以从该位置开始读写数据了。
- get attributes。进程运行常需要读取文件属性。例如, UNIX 中 make 程序通常用于管理由多个源文件组成的软件开发项目。在调用 make 时,它会检查全部源文件和目标文件的修改时间,实现最小编译,使得全部文件都为最新版本。为达到此目的,需要查找文件的某一些属性,即修改时间。
- set attributes。某些属性是可由用户设置的,甚至是在文件创建之后,实现该功能的是 set attributes 系统调用。保护模式信息是一个典型的例子,大多数标志也属于此类属性。
- rename。用户常常要改变已有文件的名字, rename 系统调用用于这一目的。严格地说, rename 系统调用不是必需的,因为先把文件复制到一个新文件中,然后删除原来的文件,就可以达到同样的目的。
- 文件命名
- 目录
- 一级目录系统
- 目录系统的最简单形式是在一个目录中包含所有的文件。这有时称为根目录
- 层次目录系统
- 用户可以创建任意数量的子目录,每个目录又可以继续创建子目录。
- 路径名
- 绝对路径名,路径名从根目录开始,不同路径之间用’/‘分割
- 相对路径名,从当前工作目录开始,‘.’ 表示当前路径,‘..‘表示父目录。
- 目录操作
- create。创建目录。除了目录项".“和” ..“外,目录内容为空。目录项”.“和 “..“是系统自动放在目录中的(有时通过 mkdir 程序完成) 。
- delete。 删除目录。只有空目录可删除。只包含目录项”.“和”..“的目录被认为是空目录,这两个目录项通常不能删除。
- opendir。目录内容可被读取。例如,为列出目录中全部文件,程序必须先打开该目录,然后读其中全部文件的文件名。与打开和读文件相同,在读目录前,必须打开目录。
- closedir。读目录结束后,应关闭目录以释放内部表空间。
- readdir。系统调用 readdir 返回打开目录的下一个目录项。以前也采用 read 系统调用来读目录,但这方法有一个缺点:程序员必须了解和处理目录的内部结构。相反,不论采用哪一种目录结构,readdir 总是以标准格式返回一个目录项。
- rename。在很多方面目录和文件都相似。文件可换名,目录也可以 。
- link。链接技术允许在多个目录中出现同一个文件。这个系统调用指定一个存在的文件和一个路径名,并建立从该文件到路径所指名字的链接。这样,可以在多个目录中出现同一个文件。这种类型的链接增加了该文件的 i 节点 (i-node) 计数器的计数(记录含有该文件的目录项数目),有时称为硬链接(hard link)。
- unlink。删除目录项。如果被解除连接的文件只出现在一个目录中(通常情况),则将它从文件系统中删除。如果它出现在多个目录中,则只删除指定路径名的连接,依然保留其他路径名的连接。在 UNIX 中,用于删除文件的系统调用(前面已有论述)实际上就是 unlink.
- 符号链接
- 不同于使用两个文件名指向同一个内部数据结构来代表一个文件
- 符号链接的优点在于它能够跨越磁盘的界限
- 一级目录系统
- 文件系统的实现
- 文件系统布局
- 文件系统存放在磁盘上。多数磁盘划分为一个或多个分区,每个分区中有一个独立的文件系统
- 磁盘的 0 号扇区称为主引导记录,用来引导计算机,MBR 后面是分区表
- 一个可能的文件系统布局包括:引导块、超级块、空闲空间管理、i 节点、根目录、文件和目录
- 文件的实现
- 连续分配:最简单的分配方案是把每个文件作为一连串连续数据块存储在磁盘上,实现简单,读操作性能好,有磁盘碎片问题。
- 链表分配:存储文件的第二种方法是为每个文件构造磁盘块链表。每个块的第一个字作为指向下一块的指针,块的其他部分存放数据。可以充分利用磁盘块,顺序访问块,随机访问慢,需要从头开始遍历。因为指针占据了部分空间,导致发生额外的拼接和复制。
- 使用内存中的表进行链表分配
- 如果取出每个磁盘块的指针字,把它们放在内存的一个表中,就可以解决上述链表的两个不足。内存中存在这样一个表格叫做 FAT。仍然需要顺着链表查找给定的偏移量。缺点是需要把整张表存在内存中。
- i 节点
- 每个文件赋予一个称为 i 节点的数据结构,其中列出了文件属性和文件块的磁盘地址。对比 FAT 的优势,只有在打开该文件时才加载该数据结构。
- 目录的实现
- 目录中有一个固定大小的目录项列表,每个文件对应一项,其中包含一个(固定长度)文件名、一个文件属性的结构体以及用以说明磁盘块位置的一个或多个磁盘地址(至某个最大值)。
- 对于采用 i 节点的系统,把文件属性存放在 i 节点中而不是目录项中。
- 采用散列表可以加速查找文件名,另一种方式是采用高速缓存。
- 共享文件
- 当几个用户同在一个项目里工作时,他们常常需要共享文件。
- 如果 C 的一个文件现在也出现在 B 的目录下。 B 的目录与该共享文件的联系称为一个链接 (link)。
- 共享的实现
- 磁盘块不列入目录,而是列入一个与文件本身关联的小型数据结构中 。目录将指向这个小型数据结构。这是 UNIX 系统中 所采用的方法
- 通过让系统建立一个类型为 LINK 的新文件,井把该文件放在 B 的目录下,使得 B 与 C 的一个文件存在链接。新的文件中只包含了它所链接的文件的路径名。当 B 读该链接文件时,操作系统查看到要读的文件是 LINK 类型,则找到该文件所链接的文件的名字,并且去读那个文件。与传统(硬)链接相对比起来,这一方法称为符号链接
- 日志结构文件系统
- 将整个磁盘结构化为一个日志,每隔一段时间,或是有特殊需要时,被缓冲在内存中的所有未决的写操作都被放到一个单独的段中,作为在日志末尾的一个邻接段写入磁盘。这个单独的段可能会包括 i 节点、目录块、数据块或者都有。 每一个段的开始都是该段的摘要,说明该段中都包含哪些内容。
- i 节点分布在日志中,查找比较困难,需要维护额外的信息帮助查找 i 节点。
- 总而言之,所有的写操作最初都被缓冲在内存中,然后周期性地把所有已缓冲的写作为一个单独的段,在日志的末尾处写入磁盘 .要打开一个文件,则首先需要从 i 节点图中找到文件的 i 节点 。一旦 i 节点定位之后就可以找到相应的块的地址。所有的块都放在段中,在日志的某个位置上。
- LFS 有一个清理线程,该清理线程周期地扫描日志进行磁盘压缩。整个磁盘抽象成一个大的环形缓冲区。LFS 处理大量零碎的读写性能比 UNIX 好一个数量级,在读写大块的性能方面也不逊色。
- 日志文件系统
- 对于需要几个步骤完成的操作。
- 日志文件系统则先写一个日志项,列出几个将要完成的动作。然后日志项被写人磁盘(并且为了良好地实施,可能从磁盘读回来验证它的完整性)。只有当日志项已经被写人,不同的操作才可以进行。 当所有的操作成功完成后,擦除日志项。如果系统这时崩溃.系统恢复后,文件系统可以通过检查日志来查看是不是有未完成的操作。如果有,可以煎新运行所有未完成的操作直到文件被正确地删除。直到操作完成。
- 日志的写入需要幂等
- 文件系统可以引入原子事务的概念。
- 虚拟文件系统
- 尝试将多种文件系统统一成一个有序的结构。关键的思想就是抽象出所有文件系统都共有的部分,并且将这部分代码放在单独 的一层,该层调用底层的实际文件系统来具体管理数据。
- 所有和文件相关的系统调用在最初的处理上都指向虚拟文件系统。这些来自用户进程的调用,都是标准的 POSIX 系统调用,比如 open、 read、 write 和 lseek 等。因此,VFS 对用户进程有 一 个“上层“接口,它就是著名的 POSIX 接口。
- 文件系统布局
- 文件系统管理和优化
- 磁盘空间管理
- 存储一个有 n 个字节的文件可以有两种策略:分配 n 个字节的连续磁盘空间,或者把文件分成很多个连续(或井不一定连续)的块。
- 几乎所有的文件系统都把文件分割成固定大小的块来存储,各块之间不一定相邻。
- 块大小
- 需要从传输速度和磁盘利用率综合考虑
- 记录空闲块
- 磁盘块链表,链表的每个块中包含尽可能多的空闲磁盘块号。
- 位图,空闲块用 1 表示,已分配块用 0 表示(或者反之)。
- 磁盘配额
- 多用户操作系统常常提供一种强制性磁盘配额机制。其思想是系统管理员分给每个用户拥有文件和块的最大数量、操作系统确保每个用户不超过分给他们的配额。
- 当用户打开一个文件时,系统找到文件属性和磁盘地址,井把它们送入内存中的打开文件表。其中一个属性告诉文件所有者是谁。任何有关该文件大小的增长都记到所有者的配额上,以防止一个用户垄断所有 i 节点。
- 文件系统备份
- 增量转储,周期性地(每周一次或每月一次)做全面的转储(备份),而每天只对从上一次全面转储起发生变化的数据做备份。
- 物理转储,磁盘的第 0 块开始,将全部的磁盘块按序输出到磁带上,直到最后一块复制完毕。
- 逻辑转储,从一个或几个指定的目录开始,递归地转储其自给定基准日期(例如,最近一次增量转储或全面系统转储的日期)后有所更改的全部文件和目录。所以,在逻辑转储中,转储磁带上会有一连串精心标识的目录和文件,这样就很容易满足恢复特定文件或目录的请求。
- 文件系统的一致性
- 块的一致性检查
- 文件的一致性检查
- 文件系统性能
- 高速缓存,检查全部的读请求,查看在高速缓存中是否有所需要的块。如果存在,可执行读操作而无须访问磁盘。如果该块不在高速缓存中,首先要把它读到高速缓存,再复制到所需地方。之后,对同一个块的请求都通过高速缓存完成。
- 块提前读,在需要用到块之前,试图提前将其写入高速缓存,从而提高命中率
- 减少磁盘臂运动,把有可能顺序访问的块放在一起,当然最好是在同一个柱面上,从而减少磁盘臂的移动次数。
- 磁盘碎片整理
- 移动文件使它们相邻,并把所有的(至少是大部分的)空闲空间放在一个或多个大的连续的区域内。
- 固态硬盘并不受磁盘碎片的影响。事实上,在固态硬盘上做磁盘碎片整理反倒是多此一举,不仅没有提高性能,反而磨损了固态硬盘。所以碎片整理只会缩短固态硬盘的寿命 。
- 磁盘空间管理
- 有关文件系统的研究
- 备份
- 高速缓存
- 安全删除数据
- 文件压缩
- Flash 文件系统
- 性能
- 可靠性和错误恢复
- 用户及文件系统
- 版本文件系统
- 安全