操作系统 - 文件系统
文件
文件内部的数据,有操作系统组织。
文件分为无结构文件和
无结构文件,由一些二进制或字符流组成,又称流式文件,如文本文件
有结构文件,由记录构成,记录由数据项构成。

有结构文件,可以类比excel里的数据。

文件与文件的关系的组织

可以。你现在最适合的方式不是直接去啃 inode、位图、索引分配,而是先把“文件管理”这一章使用的基本词汇体系建立起来。
下面我按教材的方式,从最底层的几个概念开始。暂时不要求你记实现细节,第一遍的目标只有一个:
看到教材里的这些名词时,知道它大概在说什么、处在计算机系统的哪一层。
第 1 章 文件系统的基本概念
1.1 为什么操作系统需要“文件”
先从最基本的问题开始。
CPU 执行程序的时候,数据主要放在:
$$
\text{内存 RAM}
$$
例如程序里:
int a = 10;
char name[20] = "Tom";
运行期间,这些数据可以存在内存里。
但是内存有一个重要特点:
断电后,普通 RAM 里的内容会消失。
所以我们还需要 SSD、硬盘等设备长期保存数据。
一个最直接的办法
假设 SSD 有大量存储空间,我们可以直接告诉程序:
把数据写到 SSD 的第 182736 个位置。
下一次再:
读取 SSD 的第 182736 个位置。
理论上可以。
但是这样使用极其困难。
因为程序员必须自己记:
照片 → 10000~15233
论文 → 15234~19000
游戏存档 → 27500~27600
还要自己考虑:
哪些位置已经用了?
哪些位置还是空的?
这个数据有多大?
谁可以访问?
文件删掉以后空间怎么办?
因此操作系统提供了一个更容易理解的抽象:
文件(File)。
于是程序员看到的是:
report.pdf
photo.jpg
save.dat
hello.txt
而不用考虑数据究竟位于 SSD 的哪个物理位置。
1.2 文件 File
定义
可以先把文件理解成:
由操作系统管理的一组具有名称和属性的持久化数据。
例如:
hello.txt
photo.jpg
movie.mp4
program.exe
都是文件。
但这里有一个非常重要的观念。
“文件”不是硬件本身存在的东西
SSD 硬件并不知道:
这是 hello.txt
这是 JPG
这是 Word 文档
SSD 更接近于知道:
这里有一些字节:
48 65 6C 6C 6F ...
是操作系统把某些数据组织起来,说:
这些数据属于一个文件,
名字叫 hello.txt。
所以:
操作系统提供的抽象
↓
┌─────────────────┐
│ hello.txt │
└─────────────────┘
↓
实际保存的一系列数据
↓
SSD / HDD
文件首先是一个操作系统抽象。
1.3 文件内容 Content
一个文件里面保存的是:
字节序列(sequence of bytes)。
例如文本文件:
Hello
在计算机看来实际上可能是:
48 65 6C 6C 6F
它们都是字节。
图片也是字节:
FF D8 FF E0 ...
视频也是字节。
程序也是字节。
因此从操作系统的角度看:
.txt
.jpg
.mp4
.exe
没有我们想象中那么大的区别。
操作系统很大程度上只是管理:
一串字节
至于这些字节代表:
文字?
图片?
音乐?
程序?
数据库?
通常由应用程序解释。
1.4 文件名 Filename
例如:
cat.jpg
这里:
cat.jpg
就是文件名。
但需要特别注意:
文件名通常不是文件本身。
可以把它想成:
张三
只是一个人的名字。
真正的人还有:
身份证
年龄
身高
住址
……
类似地:
cat.jpg
只是我们用来寻找文件的名字。
文件系统内部通常还有另一套用于标识文件的数据结构。
以后讲 Unix 文件系统时,你会遇到:
inode
现在只要先知道:
文件名 ≠ 文件本身
就够了。
1.5 文件扩展名 Extension
例如:
hello.txt
photo.jpg
movie.mp4
其中:
.txt
.jpg
.mp4
通常称为:
文件扩展名(File Extension)
它主要帮助人和应用程序判断文件类型。
例如:
.jpg → JPEG 图片
.mp4 → 视频容器
.txt → 文本
但是要注意:
扩展名通常只是文件名的一部分,并不是硬件属性。
例如你完全可以把:
photo.jpg
改名成:
photo.abc
文件里的字节并不会因为改名就发生变化。
1.6 文件属性 / 元数据 Metadata
文件除了“里面的数据”,还有很多关于文件的信息。
例如:
文件名:hello.txt
大小:8240 bytes
创建时间:……
修改时间:……
所有者:Tom
权限:可读、可写
文件类型:普通文件
其中:
Hello world...
属于文件内容。
而:
大小
权限
所有者
时间
类型
这些描述文件的信息称为:
元数据(Metadata)
可以理解为:
关于数据的数据。
例如一本书:
正文 → 数据
书名
作者
出版时间
页数 → 元数据
ISBN
文件系统也类似。
1.7 目录 Directory
我们不可能把计算机里的几百万个文件全堆在一起。
所以操作系统提供:
目录(Directory)
例如:
Documents/
Pictures/
Downloads/
目录的作用首先是:
组织和查找文件。
例如:
home
│
├── tom
│ ├── Documents
│ │ └── report.pdf
│ │
│ └── Pictures
│ └── cat.jpg
│
└── alice
这里:
home
tom
Documents
Pictures
都是目录。
一个很重要的观念
普通用户看到目录会觉得:
“这就是一个文件夹。”
没有问题。
但从操作系统实现角度:
目录本身也是文件系统保存的一种数据结构。
它里面的重要内容大致是在记录:
名称 对应哪个文件
report.pdf → 某个文件
cat.jpg → 某个文件
因此目录不是一个神秘的“盒子”。
它更像一个:
名字 → 文件
的映射表。
以后我们会专门展开。
1.8 路径 Path
如果文件很多,只知道:
report.pdf
可能不够。
因为不同目录里都可能存在:
report.pdf
所以需要完整描述它的位置。
例如 Unix/Linux:
/home/tom/Documents/report.pdf
这称为:
路径(Path)
可以拆开:
/
└── home
└── tom
└── Documents
└── report.pdf
路径的作用就是:
告诉操作系统沿着哪些目录去寻找目标文件。
1.9 根目录 Root Directory
Linux/Unix 文件系统的最顶部通常写作:
/
称为:
根目录(Root Directory)
例如:
/
├── home
├── bin
├── etc
├── usr
└── var
所以:
/home/tom/a.txt
第一个 / 表示:
从根目录开始找。
1.10 绝对路径与相对路径
假设当前目录是:
/home/tom
文件位于:
/home/tom/Documents/a.txt
可以写:
/home/tom/Documents/a.txt
这叫:
绝对路径(Absolute Path)
因为从根目录 / 开始。
也可以写:
Documents/a.txt
这叫:
相对路径(Relative Path)
意思是:
从当前目录开始寻找。
因此:
绝对路径
↓
从文件系统根部开始找
相对路径
↓
从当前工作目录开始找
1.11 当前工作目录 Current Working Directory
一个正在运行的进程通常有一个:
当前工作目录(Current Working Directory,CWD)
例如:
/home/tom
如果程序执行:
open("a.txt", ...);
操作系统就需要知道:
a.txt到底是哪里的 a.txt?
如果当前工作目录为:
/home/tom
那么:
a.txt
通常相当于:
/home/tom/a.txt
所以“当前目录”并不是 shell 独有的概念。
进程本身也有当前工作目录。
1.12 文件系统 File System
这是这一章最重要的名词之一。
英文:
File System
中文:
文件系统
它不是单独指“文件”。
而是:
操作系统组织、保存、查找和管理持久化数据的一整套规则和数据结构。
例如:
ext4
XFS
Btrfs
NTFS
FAT32
exFAT
APFS
都是具体的文件系统。
文件系统解决哪些问题?
例如:
问题 1
文件名怎么找到文件?
文件系统解决。
问题 2
文件的数据存在磁盘哪里?
文件系统解决。
问题 3
哪些磁盘空间已经占用?
文件系统解决。
问题 4
哪些磁盘空间还是空闲?
文件系统解决。
问题 5
文件有多大?
文件系统记录。
问题 6
谁能读取这个文件?
文件系统记录权限。
问题 7
突然断电怎么办?
现代文件系统还需要考虑一致性和恢复。
所以可以记住:
文件是文件系统提供给程序的一种抽象。
1.13 存储设备 Storage Device
文件最终还是要保存到硬件上。
常见的是:
HDD 机械硬盘
SSD 固态硬盘
USB盘
SD卡
它们统称:
存储设备(Storage Device)
从操作系统角度,还经常把这类设备称为:
块设备(Block Device)
这个词以后会非常频繁出现。
1.14 块 Block
这是文件系统里另一个非常重要的基础概念。
假设有一个很大的 SSD:
┌─────────────────────────────────────────┐
│ │
│ 很大的存储空间 │
│ │
└─────────────────────────────────────────┘
文件系统通常不会一个字节一个字节地管理整个磁盘。
而是分成一块一块:
┌──────┬──────┬──────┬──────┬──────┬──────┐
│块 0 │块 1 │块 2 │块 3 │块 4 │块 5 │ ...
└──────┴──────┴──────┴──────┴──────┴──────┘
每块例如:
4096 bytes = 4 KiB
这些单位通常称为:
块(Block)
于是一个文件可能存放在:
block 83
block 125
block 901
文件系统必须知道:
这个文件对应哪些块。
1.15 逻辑块与物理存储
这一点先知道概念,不必深入。
假设文件系统说:
读取 block 100
这不一定等于:
“SSD 某个闪存单元的绝对物理位置。”
现代 SSD 内部还有:
控制器
FTL
闪存页
擦除块
磨损均衡
等机制。
因此学习操作系统文件系统时,我们通常先站在操作系统的角度:
文件
↓
文件系统块
↓
块设备
↓
设备驱动
↓
SSD控制器
↓
Flash
现阶段重点学上半部分。
1.16 卷 Volume
教材有时候会突然出现:
volume
一般翻译成:
卷
可以暂时理解成:
可以建立一个文件系统的一块逻辑存储区域。
例如一块 1 TB SSD:
1 TB SSD
│
├── 300 GB
└── 700 GB
可能划分成两个区域。
每个区域可以分别建立文件系统:
300 GB → NTFS
700 GB → ext4
这里就会涉及:
partition
volume
file system
这些概念比较容易混淆。
1.17 分区 Partition
Partition = 分区。
就是把一个存储设备的地址空间划成若干区域。
例如:
一块 1 TB 硬盘
┌──────────────────────────────────────┐
│ │
│ Physical Disk │
│ │
├───────────────┬──────────────────────┤
│ Partition 1 │ Partition 2 │
│ 300 GB │ 700 GB │
└───────────────┴──────────────────────┘
然后:
Partition 1 → NTFS
Partition 2 → ext4
所以三个词不要混:
磁盘/SSD
↓
可以划分出 Partition
↓
在其中建立 File System
↓
File System 里面保存 File
1.18 格式化 Format
平时 Windows 会说:
“格式化磁盘”
文件系统角度,“格式化”的核心意思并不是简单地:
删除所有文件。
更准确地说,是:
在一块存储区域上建立文件系统所需要的数据结构。
例如选择:
NTFS
格式化之后,系统会建立 NTFS 需要的内部结构。
选择:
FAT32
则建立 FAT32 的结构。
所以:
裸存储空间
↓
格式化
↓
建立文件系统的数据结构
↓
现在才能方便地创建文件和目录
1.19 挂载 Mount
这是 Unix/Linux 中非常重要的词。
英文:
mount
中文:
挂载
假设你有一个 U 盘,里面有自己的文件系统。
Linux 不一定把它显示成:
E:
而是可以把它接到目录树中的某个位置:
/
├── home
├── usr
├── etc
└── mnt
└── usb
例如:
/mnt/usb
从这里开始,实际上访问的是:
U 盘上的文件系统。
所以:
Mount = 把一个文件系统接入当前目录树中的某个位置。
这个“接入点”称为:
Mount Point,挂载点。
例如:
/mnt/usb
1.20 文件操作 File Operations
操作系统通常提供一些基本文件操作:
create 创建
open 打开
read 读取
write 写入
seek 改变读写位置
close 关闭
delete 删除
在 Unix/Linux 中很多操作最终表现为:
系统调用(System Call)
例如:
open()
read()
write()
close()
这里要区分:
C语言函数
和:
操作系统系统调用
不过我们之后讲 open() 的全过程时再详细区分。
1.21 打开文件 Open File
这里是初学者很容易误解的地方。
磁盘上有:
a.txt
程序调用:
open("a.txt", ...);
之后,操作系统通常会在内存中建立一些用于管理此次打开操作的数据结构。
所以要区分:
磁盘里的文件
a.txt
长期存在。
“已经打开的文件”状态
这是操作系统运行期间保存在内存里的信息,例如:
当前读到哪里
用什么方式打开
哪些进程正在使用
因此:
打开文件 ≠ 把整个文件全部加载进内存。
这是非常重要的一点。
1.22 文件描述符 File Descriptor
Unix/Linux 中:
int fd = open("a.txt", O_RDONLY);
可能得到:
fd = 3
这个 3 称为:
文件描述符(File Descriptor,fd)
它只是一个整数。
例如:
进程
┌────────────────────┐
│ 文件描述符表 │
│ │
│ 0 → stdin │
│ 1 → stdout │
│ 2 → stderr │
│ 3 → a.txt │
│ 4 → b.txt │
└────────────────────┘
所以:
read(3, buf, 100);
不是:
从“编号为 3 的磁盘文件”读取。
而大致是:
到当前进程的文件描述符表里找到第 3 项,再找到对应的打开文件。
这是以后理解 Unix 文件系统非常重要的基础。
1.23 文件偏移量 File Offset
假设:
abcdefghij
程序:
read(fd, buf, 3);
第一次得到:
abc
为什么第二次:
read(fd, buf, 3);
会得到:
def
而不是再次得到:
abc
因为打开的文件通常有一个:
文件偏移量(File Offset)
第一次开始:
offset = 0
读取 3 字节:
abc
之后:
offset = 3
下一次从:
d
开始。
所以可以想成文件里有一个“游标”:
abcdefghij
↑
offset = 3
1.24 Seek
既然有:
offset
自然就会有一种操作:
改变 offset。
这就是:
seek
例如原来:
abcdefghij
↑
通过 seek 可以把位置移动到:
abcdefghij
↑
然后下一次 read() 从那里开始。
Unix 中常见:
lseek()
以后学习随机访问文件时会遇到。
1.25 普通文件 Regular File
Unix/Linux 中,“文件”这个词范围比 Windows 用户平时理解的更广。
有一种最普通的文件叫:
Regular File,普通文件
例如:
.txt
.jpg
.exe
.pdf
都可以属于普通文件。
除此之外,Unix 还有:
directory
device file
pipe
socket
symbolic link
等。
所以教材如果说:
“Unix 中一切皆文件”
它不是说所有东西都是 .txt。
而是在说很多资源都使用类似文件的接口来访问。
1.26 目录项 Directory Entry
这个词以后会非常常见。
英文:
Directory Entry
经常简称:
dirent
可以先理解成:
目录中记录一个名字及其对应文件的信息。
例如一个目录内部逻辑上可能有:
| 文件名 | 对应文件 |
|---|---|
| a.txt | 文件 A |
| cat.jpg | 文件 B |
| notes | 文件 C |
其中每一行可以理解为一个:
directory entry
以后到了 Unix 文件系统,它通常会和:
inode number
发生关系。
1.27 FCB:File Control Block
很多操作系统教材会突然出现一个缩写:
FCB
全称:
File Control Block
中文常译:
文件控制块
这个名字容易让人觉得很可怕。
实际上可以先理解为:
操作系统用于保存一个文件相关管理信息的数据结构。
里面可能包含:
文件大小
权限
所有者
时间
数据存放位置
……
不同操作系统实现不同。
因此 FCB 更多是一种教材里的通用概念。
1.28 inode
如果你学习 Unix、Linux、xv6,很快会遇到:
inode
它可以暂时理解为 Unix 风格文件系统中的一种:
文件元数据结构。
例如可能记录:
inode
├── 文件类型
├── 权限
├── 大小
├── link count
├── 数据块地址
└── ...
注意:
inode 通常并不直接保存文件名。
这是非常重要的一点。
文件名更多存在于:
directory entry
中。
因此会形成:
目录项
"a.txt"
│
▼
inode #17
│
├── size
├── permission
└── data blocks
这就是以后 Unix 文件系统最核心的结构之一。
现在不需要深入。
1.29 数据块 Data Block
inode 找到以后,最终还要找到文件真正的数据。
例如:
inode #17
│
├── block 100
├── block 105
└── block 203
这几个块里面才真正存着:
Hello world...
这些通常称为:
Data Block,数据块
因此可以初步形成一条主线:
文件名
↓
Directory Entry
↓
inode / FCB
↓
Data Block
↓
真正的文件内容
这一条链以后会反复出现。
1.30 空闲空间 Free Space
如果磁盘一共有:
1000000 个块
操作系统必须知道:
哪些块已经用了?
哪些块还可以使用?
例如:
Block 0 已使用
Block 1 已使用
Block 2 空闲
Block 3 空闲
Block 4 已使用
这种管理问题称为:
Free Space Management,空闲空间管理
常见方法有:
Bitmap
Free List
以后都会讲。
1.31 Bitmap 位图
位图的思想非常简单。
假设有 8 个磁盘块:
0 1 2 3 4 5 6 7
使用一个 bit 表示一个块:
1 1 0 0 1 0 1 0
例如约定:
1 = 已使用
0 = 空闲
那么:
block 0 → 已使用
block 1 → 已使用
block 2 → 空闲
block 3 → 空闲
……
这就是:
Bitmap,位图
它并不是文件系统特有的算法。
本质上就是:
用 bit 集合记录一大批“是/否”状态。
1.32 缓存 Cache
文件系统还经常出现:
buffer cache
page cache
先不要区分两者。
首先理解为什么需要缓存。
SSD 比 RAM 慢很多。
如果每一次:
read()
都真正访问 SSD,效率会很低。
因此操作系统会把刚刚读取的数据暂时留在 RAM:
RAM
┌────────────┐
│ File Cache │
└────────────┘
↑
│
│
SSD
下一次再读取同一数据:
程序
↓
Cache
可能直接从 RAM 获得。
所以:
文件缓存不是文件本身,它是磁盘数据在内存中的临时副本。
1.33 权限 Permission
操作系统还需要控制:
谁能使用哪些文件。
Unix 中经常看到:
r = read
w = write
x = execute
例如:
rw-r--r--
表示不同用户对文件具有不同权限。
所以权限属于:
文件的元数据
而不是文件内容。
1.34 Link 链接
这个词现在只需要认识,不需要完全理解。
Unix 中会有:
Hard Link
Symbolic Link
中文:
硬链接
符号链接 / 软链接
它们都与:
“文件名”和“真正的文件”并不是同一个东西
有关。
等 inode 学清楚以后,再学 link 会简单很多。
现在先跳过实现。
1.35 删除 Delete / Unlink
普通用户理解:
删除 a.txt
就是:
文件消失了。
但 Unix 文件系统内部通常没有这么简单。
你以后会发现 Unix 经常使用:
unlink()
这个名字很有意思。
不是:
destroy_file()
而是:
unlink
原因正与:
目录项
文件名
inode
link count
有关。
所以等我们学完 inode 后,会专门解释:
删除一个文件究竟删掉了什么?
这个问题非常值得学。
1.36 文件系统这一章真正有哪些层次?
现在把这些名词放在一张“地图”里。
【应用程序】
│
│ open/read/write
▼
┌───────────────┐
│ 文件描述符 fd │
└───────┬───────┘
│
▼
【打开文件状态】
offset / flags
│
▼
【文件系统】
│
┌───────────┼────────────┐
│ │ │
▼ ▼ ▼
Directory inode Bitmap
目录 元数据 空闲块管理
│ │
└─────┬─────┘
▼
Data Block
数据块
│
▼
【块设备层】
│
▼
Driver
驱动
│
▼
NVMe / SATA 控制器
│
▼
SSD / HDD
现在教材里的词就不应该再全部混在一起了。
1.37 第一阶段最重要的 15 个词
第一遍学习,我建议你真正记住下面这些。
| 英文 | 中文 | 先记住的意思 |
|---|---|---|
| File | 文件 | 一组持久化数据的抽象 |
| Filename | 文件名 | 文件的名字 |
| Metadata | 元数据 | 描述文件的信息 |
| Directory | 目录 | 组织、查找文件 |
| Path | 路径 | 如何找到文件 |
| File System | 文件系统 | 管理文件的一整套机制 |
| Block | 块 | 文件系统管理磁盘空间的基本单位之一 |
| Partition | 分区 | 磁盘划出的区域 |
| Mount | 挂载 | 把文件系统接入目录树 |
| File Descriptor | 文件描述符 | 进程用于引用打开文件的整数 |
| Offset | 文件偏移量 | 当前读写到哪里 |
| Directory Entry | 目录项 | 名字与文件之间的记录 |
| FCB | 文件控制块 | 教材中的通用文件管理结构 |
| inode | 索引节点 | Unix 风格文件元数据结构 |
| Data Block | 数据块 | 真正保存文件数据的块 |
现在不需要把:
journaling
VFS
extent
superblock
indirect block
dentry cache
writeback
全部塞进脑子。
这些属于下一层。
1.38 最后建立一个最基本的心智模型
以后看到:
/home/tom/a.txt
不要只看到一个 Windows 那种“文件图标”。
应该逐渐想到:
/home/tom/a.txt
│
│ 路径
▼
逐级查目录
│
▼
Directory Entry
│
▼
inode / 文件元数据
│
▼
找到文件的数据块
│
▼
Block 100
Block 203
Block 417
│
▼
SSD / HDD
而程序访问它的时候:
open()
│
▼
fd
│
▼
read()
│
▼
文件系统
│
▼
数据块
│
▼
RAM中的buf
这两张图,就是整个“文件管理”章节的骨架。
下一课适合讲什么
下一步最值得讲的是教材里非常核心、而且也最容易混淆的一组概念:
“文件名、目录项、inode、打开文件对象、文件描述符,究竟是五个什么不同的东西?”
我可以继续完全按照这种教材式排版,用一个真实的:
fd = open("/home/tom/a.txt", O_RDONLY);
read(fd, buf, 100);
从 / 开始,一层一层画出操作系统到底查了哪些数据结构。这样等以后看到 xv6 的 namei()、inode、file、fd 时,就不会觉得是突然冒出来的名词。
浙公网安备 33010602011771号