408 · 操作系统 · 第 4 章

文件管理

磁盘上只有编了号的块,用户要的是有名字的文件。文件系统要回答四件事:文件的描述信息放哪(FCB / inode)、按名字怎么找到它(目录)、它的数据放在哪些块上(物理分配)、哪些块还空着(空闲空间管理)。

找文件就是一条链路径名 → 目录项 → FCB / inode → 地址项 → 磁盘块。目录就是 FCB 的集合,一个目录项就是一个 FCB(或 文件名 + inode 号)
分配方式只看“第 i 块的地址怎么得到”连续分配直接算;链接分配顺着指针一块块走;索引分配查表。读盘次数、能否随机访问都由此推出
共享看共享的是什么硬链接共享 inode(靠计数);软链接只存一条路径(原文件没了就失效)
一 · 演进导图

每一步都在解决上一步留下的问题

文件管理对上:按名存取、共享、保护;对下:把文件落到磁盘块上,并管好空闲块
目录结构文件多了,怎么按名字快速、无冲突地找到它

单级目录

不能重名
做法
整个系统一张目录表,每个文件占一个目录项
新问题
所有用户的文件名不能重复;文件多时线性查找慢;不便共享

两级目录

用户间可重名
做法
主文件目录 MFD(每个用户一项)+ 用户文件目录 UFD
解决
不同用户的文件可以同名,用户之间相互隔离
新问题
同一用户的文件没法再分类;用户之间共享不方便

树形目录

现代 OS 主流
做法
多级目录,用路径名定位文件:绝对路径从根开始,相对路径从当前目录开始
解决
文件可以按目录分类;用相对路径能少读几层目录,减少读盘
新问题
每个文件只有一条从根出发的路径,不便共享

无环图目录

支持共享
做法
允许不同目录中的目录项指向同一个文件(或子目录)
代价
要给共享结点设共享计数 count:删除时 count − 1,减到 0 才真正删除文件
物理结构(文件分配方式)一个文件的数据放在哪些磁盘块上,怎么记下来

连续分配

外部碎片
做法
文件占一组相邻的块,FCB 记 起始块号 + 长度
好处
顺序访问、随机访问都快,磁头移动最少
新问题
有外部碎片;文件不好扩展(后面的块可能被别人占了)

链接分配 · 隐式

无外部碎片
做法
块可以分散,每块末尾存下一块的指针;FCB 记首块和末块
新问题
只能顺序访问(找第 i 块要读前 i 块);指针占空间;一个指针坏了后面全丢,可靠性差

链接分配 · 显式(FAT)

支持随机访问
做法
把所有块的“下一块”指针集中到一张文件分配表 FAT,整个磁盘一张,开机读入内存
解决
顺着 FAT 找第 i 块全在内存里完成,只读 1 次盘
代价
FAT 常驻内存,磁盘越大 FAT 越大

索引分配

随机访问 · 无外部碎片
做法
每个文件一个索引块,第 i 项就是逻辑块 i 的物理块号
新问题
索引块占空间(小文件也要一块);大文件一个索引块装不下

链接方案 / 多层索引 / 混合索引

做法
索引块串成链、或像多级页表一样分层;UNIX 的混合索引把直接地址和一、二、三级间接放在同一个 inode 里
好处
小文件走直接地址,1 次读盘;大文件走间接,最大长度大增

另一条支线 → 空闲空间管理:分配前得知道哪些块空着。空闲表、空闲链表、位示图、成组链接四种方法,见第七节。
下一章 → I/O 管理:文件系统算出磁盘块号后,交给磁盘驱动去读写;读盘次数之外,还有寻道时间和旋转延迟。

二 · 文件的基本概念

FCB 是文件的“身份证”,inode 把它瘦了身

文件 = 数据 + 描述它的信息。描述信息集中放在文件控制块 FCB 里,有了 FCB 才能按名存取。FCB 的有序集合就是目录,一个 FCB 就是一个目录项。

FCB 里有什么

基本信息文件名、标识符、文件类型
物理位置在哪个设备、起始块号 / 索引块号、文件长度
结构信息逻辑结构、物理结构
存取控制文件主、各类用户的读写执行权限
使用信息创建时间、上次修改时间、当前使用状态

考试最常问:FCB 里有文件名、物理地址、存取权限;没有文件内容。

为什么要引入索引结点 inode

按名查找时只比较文件名,FCB 里其他信息在找到之前用不上,却也要跟着一起读盘。于是把它们拆出去放进 inode,目录项只留 文件名 + inode 号。目录项变小,一个盘块能放更多目录项,目录占的块就少,查找时读盘次数也就少。

文件名14 B
inode 号2 B

UNIX 的目录项只有 16 B,其余信息(类型、权限、长度、地址项、链接计数 count …)都在 inode 里。

算一算:读盘次数少了多少

设盘块 1 KB,目录下有 640 个文件,顺序查找平均要读一半的目录块。

不用 inode:一个 FCB 占 64 B
每块目录项数 = 1024 ÷ 64 = 16
目录占块数 = 640 ÷ 16 = 40
平均读盘 ≈ 40 ÷ 2 = 20 次
用 inode:目录项 16 B
每块目录项数 = 1024 ÷ 16 = 64
目录占块数 = 640 ÷ 64 = 10
平均读盘 ≈ 10 ÷ 2 = 5 次(找到后再读 1 次 inode)

精确地说,顺序查找 k 个目录块平均读 (k + 1) ÷ 2 块;选择题一般按 k ÷ 2 估算。放在磁盘上的叫磁盘索引结点,文件打开后复制到内存的叫内存索引结点,后者多了 inode 编号、状态、访问计数、逻辑设备号等字段。

三 · 文件的逻辑结构

用户眼里文件长什么样,决定了按关键字怎么查

逻辑结构是用户看到的组织方式,和数据在磁盘上怎么放(物理结构)是两回事。无结构文件(流式文件)就是一串字节,源程序、可执行文件都是;有结构文件由一条条记录组成,重点是下面几种。

类型怎么组织按关键字找一条记录优点缺点
无结构(流式)以字节为单位的字节流,没有记录只能穷举搜索简单,适合源程序、可执行文件不适合按记录查询
顺序文件 · 串结构记录按存入时间排列,与关键字无关从头顺序找,平均 N/2 次批量顺序处理效率高查找慢;增删记录难
顺序文件 · 顺序结构记录按关键字排序可折半查找,约 log2N 次查找比串结构快插入删除要移动记录(常配合日志文件,定期合并)
索引文件每条记录一个索引项(长度 + 指针),索引表按关键字排序查索引表(可折半),再直接定位记录变长记录也能随机访问每条记录一个索引项,索引表可能很大
索引顺序文件记录分组,每组第一条记录建一个索引项先查索引表,再在组内顺序找,约 √N 次索引表小得多,查找仍然快组内仍要顺序查找
直接 / 散列文件由关键字经散列函数直接算出地址无需顺序查找,最快存取速度快可能冲突

索引顺序文件为什么是 √N

N 条记录分成 √N 组,每组 √N 条。先在索引表(√N 项)里顺序找到组,平均 √N/2 次;再在组内顺序找,平均 √N/2 次。合计约 √N 次。

N = 106 条记录
顺序文件(串结构):N/2 = 500000 次
索引顺序文件:√N = 1000 次
二级索引顺序:(3/2)·∛N = 150 次

定长记录的顺序文件可以随机访问

每条记录长 L,第 i 条(从 0 编号)的地址 = 起始地址 + i × L,直接算出来。变长记录就算不出来了,只能从头数,这正是索引文件要解决的问题。

注意区分:这里的“随机访问”指按记录号定位,和物理结构里“能否直接找到第 i 个盘块”是两个层面的问题。

四 · 文件共享与文件保护

硬链接共享 inode,软链接只记路径

无环图目录允许多个目录项指向同一个文件。怎么指,有两种做法。

硬链接:两个目录项,同一个 inode 目录 /A a.txt │ inode 17 目录 /B b.txt │ inode 17 inode 17 count = 2 地址项 → 数据 文件数据块 删 /A/a.txt:只删这个目录项,count 变 1, /B/b.txt 照常能用。count 减到 0 才回收 inode 和数据块。 软链接:链接文件里存的是路径 目录 /C c │ inode 42 inode 42 · LINK 内容:"/A/a.txt" inode 17(原文件) 按路径从根逐级查目录 每级目录都可能要读盘 原文件被删后,路径指向的东西不存在了, 链接还在但访问失败(悬空链接)。原 count 不受影响。
对比项硬链接(基于索引结点)软链接(符号链接)
实现新建一个目录项,指向原文件的 inode新建一个 LINK 类型的文件,内容是原文件的路径名
是否新占 inode否,原 inode 的 count + 1是,链接文件有自己的 inode;原文件 count 不变
删除原文件名后count − 1,不为 0 时其他链接照常访问原文件真的删了,链接失效
访问开销小:目录项直接给出 inode 号大:先读链接文件拿到路径,再按路径逐级查目录,多次读盘
跨文件系统不能(inode 号只在本文件系统内有意义)能,甚至可以跨网络
链接目录一般不允许(可能形成环)可以

文件保护:防止文件被未授权访问

口令保护

建文件时设一个口令,存在 FCB 或系统里;访问时要输入口令。

优点:时间和空间开销小。缺点:口令存在系统内部,不够安全。

加密保护

用密钥把文件内容编码存放,读时解码;密钥不存在系统里。

优点:保密性强,不用存口令。缺点:编码解码要花时间。

访问控制

访问控制矩阵:行是用户(域),列是文件,格子里是权限(读、写、执行、添加、删除、列表)。矩阵太大太稀疏,所以按列存成每个文件一张访问控制表 ACL,按行存就是能力表。

精简做法:只分 文件主 / 同组用户 / 其他用户 三类,UNIX 的 rwxr-x--- 就是这样。

五 · 文件的物理结构

同一个 4 块的文件,四种放法

下面是同一块磁盘(编号 0–15),文件 F 有 4 个逻辑块。看每种方式 FCB 里记了什么、要找逻辑块 2 该怎么走。

FAT 表有多大

FAT 为磁盘上每个盘块设一个表项,表项里存下一块的块号(−1 表示文件结束,−2 表示空闲,所以 FAT 同时也管了空闲块)。

FAT 大小 = 表项数 × 表项长度
表项数 = 盘块总数
表项位数 = ⌈log2(盘块总数)⌉
例:磁盘 4 GB,盘块 4 KB
盘块数 = 4 GB ÷ 4 KB = 220
表项 20 位 = 2.5 B
FAT = 220 × 2.5 B = 2.5 MB

题目若要求表项按字节对齐,20 位要取 3 B,FAT = 3 MB。看清题目的对齐要求。

大文件的索引块怎么组织

  • 链接方案:一个索引块装不下,就把多个索引块链起来。找靠后的块要先顺序读前面的索引块。
  • 多层索引:和多级页表一样,第一层索引块指向第二层索引块。设每块能放 n 个地址,两层最多 n2 块;k 层索引访问一个数据块要读 k + 1 次盘(索引块都不在内存时)。
  • 混合索引(UNIX inode):若干直接地址 + 一级间接 + 二级间接 + 三级间接。小文件只用直接地址,快;大文件用间接地址,容量大。下一节的计算器专门算它。

四种分配方式对比

方式FCB 里记什么随机访问外部碎片文件扩展访问第 i 块读盘次数主要代价
连续分配起始块号、长度支持有难1外部碎片,要预知文件大小
隐式链接首块号、末块号不支持无易i + 1只能顺序访问;指针占空间;可靠性差
显式链接(FAT)首块号支持无易1FAT 常驻内存,占内存
索引分配(单层)索引块号支持无易2索引块占空间,小文件浪费

约定:逻辑块号 i 从 0 开始;FCB 已在内存(文件已打开);索引分配的索引块不在内存,所以要先读 1 次索引块。若题目说索引块已调入内存,就只要 1 次。

六 · 动手算 · 混合索引

最大文件长度和“读第 k 个字节要读几次盘”

改盘块大小、地址项长度和各级地址项个数,下面实时算出各级能管多少块、单个文件最大多长;再输入一个文件内的字节偏移,看它落在哪一级、要读几次盘、每层索引块里用第几个下标。

预设
级别地址项可寻址块数容量逻辑块号范围读盘次数
换一个偏移

    约定:文件已打开,inode 已在内存,各级索引块都不在内存。读盘次数 = 要读的索引块数 + 1 次数据块。若题目说 inode 不在内存(文件还没打开,要先把 inode 读进来),再 +1。

    七 · 空闲空间管理

    哪些块空着,四种记账方法

    管理的对象是整个文件卷(分区)上的空闲块。前两种和内存动态分区很像,位示图和成组链接是计算题高发区。

    方法怎么记分配回收特点
    空闲表法每个连续空闲区一项:第一个空闲块号、空闲块数首次适应、最佳适应等,和内存动态分区一样与相邻空闲区合并(四种情况)适合连续分配
    空闲链表 · 盘块链所有空闲块串成一条链,以块为单位从链头摘 k 块挂到链尾简单;但一次分配或回收多块时要重复多次,效率低
    空闲链表 · 盘区链每个连续空闲区(盘区)为一个结点,结点里记区大小首次适应等要与相邻盘区合并分配回收效率高,但更复杂
    位示图每块一位:0 空闲、1 已分配(以题目为准)扫描找 0 位 → 算出块号 → 置 1由块号算出行列 → 置 0位示图小,可放内存;换算公式是考点
    成组链接法UNIX:空闲块分组,每组的信息记在上一组的某一块里,第一组的信息在超级块见下见下空闲块表不占额外连续空间,适合大文件系统

    位示图换算器

    编号起点
    方向

      点网格里任意一格,可直接把它设为当前盘块。灰底格是 1(已分配),白底是 0(空闲)。混合编号(例如盘块号从 0、行列从 1)时,先把行列各减 1 化成全从 0,再套左边的公式:b = n(i − 1) + (j − 1)。

      成组链接法(UNIX)

      超级块(内存)空闲块数 = 3
      [300, 201, 202]
      栈底 300 是组长块
      300 里记着 →
      块 300空闲块数 = 100
      [400, 301 … 399]
      400 里记着 →
      块 400下一组 …
      最后一组以结束标志收尾

      分配一块

      1. 检查超级块是否已上锁,上锁则等待(互斥访问)。
      2. 空闲块数 > 1:直接分配栈顶的块(上例先给 202,再给 201),空闲块数 − 1。
      3. 空闲块数 = 1:剩下的是组长块 300,它里面记着下一组。先把 300 的内容读入超级块,再把 300 分配出去。

      回收一块

      1. 超级块未满(< 100):把块号压入栈顶,空闲块数 + 1。
      2. 超级块已满(= 100):把超级块现有内容写进这个新回收的块,它成为新组的组长;超级块清空,只记这一块,空闲块数 = 1。

      超级块在文件系统挂载时读入内存,平时分配回收都在内存里改,只有换组时才读写一次盘。

      八 · 文件操作与打开文件表

      open 一次,之后凭文件描述符访问

      按路径查目录要一层层读盘,很慢。open 把这件事只做一次:查到 FCB(inode)后放进内存的打开文件表,返回一个编号(文件描述符 fd);之后 read / write 都用 fd,不再按路径查目录。

      操作系统做了什么
      create① 为文件分配外存空间;② 在目录中新建目录项(文件名、位置等)
      delete① 按路径找到目录项;② 回收文件占用的磁盘块;③ 删除目录项。有硬链接时只是 count − 1
      open① 按路径查目录找到目录项;② 检查权限;③ 把 FCB(inode)复制进内存打开文件表;④ 返回 fd。不读文件数据
      close① 删除进程打开文件表中的表项;② 系统打开文件表的打开计数 − 1,减到 0 删除该表项(FCB 有修改就写回外存)
      read给出 fd、读入内存的位置、读多少;从读写指针处开始读,读完指针后移
      write给出 fd、内存中数据的位置、写多少;从读写指针处写

      两级打开文件表

      进程 A 的打开文件表
      fd 3:指针 0,只读 → #1
      进程 B 的打开文件表
      fd 5:指针 4096,读写 → #1
      →
      系统打开文件表
      #1:inode 17 副本
      打开计数 = 2
      ↔
      磁盘
      目录项 a.txt │ 17
      inode 17 · 数据块

      进程级表:每个进程一张,记这个进程自己的读写指针、访问权限,以及指向系统表项的指针。两个进程读同一个文件,各读各的位置,所以指针放在这里。

      系统级表:整个系统一张,每个已打开文件一项,放 FCB / inode 副本、打开计数、磁盘位置等。第二个进程再 open 同一文件时,只把计数 + 1,不再重复读 FCB。

      九 · 文件系统层次结构 · VFS · 挂载

      一层一层把“按名字读”变成“读第几块”

      文件系统层次结构

      应用程序(系统调用 open / read / write)
      逻辑文件系统管理元数据:目录结构、FCB、按文件名找到 FCB;负责保护与安全
      文件组织模块知道文件的物理结构,把逻辑块号换算成物理块号;包含空闲空间管理
      基本文件系统向驱动发出“读写第几块”的一般命令;管理内存缓冲区和缓存
      I/O 控制设备驱动程序和中断处理程序,在内存和磁盘之间传数据
      设备(磁盘)

      磁盘上的布局:整个磁盘有主引导记录 MBR 和分区表;每个分区依次放 引导块、超级块、空闲空间管理信息、inode 区、根目录,然后是其余文件和目录。

      虚拟文件系统 VFS

      用户进程:统一的 open / read / write
      ↓
      VFS:向上提供统一接口,屏蔽各种文件系统的差异
      ↓
      ext4FAT32NFS

      每种文件系统都要实现 VFS 规定的函数。VFS 定义了四类对象:超级块对象(一个已挂载的文件系统)、索引结点对象(一个文件)、目录项对象(路径中的一项,只存在于内存)、文件对象(一个被进程打开的文件)。

      文件系统挂载

      1. 在 VFS 中注册新文件系统,内存里的挂载表记下它的类型、容量等信息;
      2. 新文件系统向 VFS 提供它实现的函数地址列表;
      3. 把它挂到已有目录树的某个目录(挂载点)上,之后访问这个目录就进入新文件系统。
      十 · 易错速记 & 分配方式速查

      选择题直接对表

      分配方式随机访问外部碎片访问第 i 块读盘一句话原因
      连续分配支持有1起始块号 + i 直接算出物理块号
      隐式链接不支持无i + 1下一块的地址藏在上一块里,只能一块块读过去
      显式链接 FAT支持无1指针链在内存里的 FAT 中走完,只读数据块
      单层索引支持无2读索引块查第 i 项,再读数据块
      k 层索引支持无k + 1每层索引块各读一次
      混合索引支持无1 / 2 / 3 / 4直接 / 一级 / 二级 / 三级间接(inode 已在内存)
      硬链接的两个“不能”不能跨文件系统(inode 号只在本文件系统内唯一),一般不能链接目录(会成环)。软链接两样都可以。
      删有硬链接的文件只删自己的目录项,count − 1;count 减到 0 才回收 inode 和数据块。软链接不改原文件的 count。
      open 读的是 FCB,不是数据open 把 FCB(inode)从外存读进内存的打开文件表,返回 fd;之后 read / write 不再按路径查目录。
      FAT 为什么能随机访问FAT 常驻内存,顺着 FAT 找第 i 块不用读盘,最后只读 1 次数据块。隐式链接的指针在盘块里,所以不行。
      索引分配访问第 i 块单层索引 2 次(索引块 + 数据块);k 层索引 k + 1 次;混合索引按落在哪一级算,inode 不在内存再 + 1。
      每块能放几个地址= 盘块大小 ÷ 地址项长度。1 KB ÷ 4 B = 256,这个数是所有间接计算的基础。
      位示图先看编号起点全从 0:b = n·i + j;全从 1:b = n(i − 1) + j。题目三者起点可能不同,先统一。
      目录项 ≠ inode引入 inode 后目录项只有 文件名 + inode 号,目录变小、查找读盘少;找到后还要再读一次 inode。
      读写指针在进程级表打开计数在系统级表。两个进程同时读一个文件,各自的位置互不影响。
      逻辑结构 ≠ 物理结构顺序文件 / 索引文件说的是逻辑结构;连续 / 链接 / 索引分配说的是物理结构。“索引文件”和“索引分配”不是一回事。