---- 整理自狄泰软件唐佐林老师课程
查看所有文章链接:(更新中)深入浅出操作系统 - 目录
文章目录
- 1. 问题
 - 1.1 硬盘上最最最简单的文件系统支持方式
 - 1.2 改进思路
 - 1.3 更多细节问题
 - 1.4 文件系统概要设计
 - 1.5 硬盘数据逻辑示意图
 - 1.6 硬盘数据物理组织示意
 
- 2. 问题
 - 2.1 硬盘基础概念
 - 2.2 课程中的解决方案
 - 2.3 数据结构设计
 - 2.4 格式化的关键
 - 2.5 编程实验:文件系统的初步实现
 
- 3. 问题
 - 3.1 根目录的本质
 - 3.2 根目录逻辑结构
 - 3.3 文件实现的基础:扇区管理
 - 3.4 扇区申请
 - 3.5 一些注意事项
 - 3.6 访问数据扇区对应的分配表项
 - 3.7 核心数据结构及数量关系
 - 3.8 需要实现的部分扇区管理函数
 - 3.9 编程实验:扇区管理函数的实现
 
- 4. 问题
 - 4.1 根目录本质回顾
 - 4.2 根目录中创建文件的流程
 - 4.2.1 在根目录中创建文件
 - 4.2.2 CheckStorage() - 写入数据前的检查
 - 4.2.3 在根目录中写入新文件信息
 - 4.2.4 编程实验:在根目录中创建文件
 - 4.2.5 思考
 
- 4.3 根目录中查找文件的流程
 - 4.3.1 文件查找的本质
 - 4.3.2 FindFileEntry() - 文件查找的核心
 - 4.3.3 在扇区中查找FileEntry
 - 4.3.4 编程实验:在根目录中查找文件
 - 4.3.5 思考
 
- 4.4 在根目录中删除文件
 - 4.4.1 实现思路
 - 4.4.2 根目录中删除文件的流程
 - 4.4.3 关键细节
 - 4.4.4 数据链表中的字节删除
 - 4.4.5 数据链表容量调整
 - 4.4.6 编程实验:在根目录中删除文件
 - 4.4.7 思考
 
- 4.5 重命名根目录中的文件
 - 4.5.1 解决方案
 - 4.5.2 根目录中重命名文件的流程
 - 4.5.3 文件重命名
 - 4.5.4 FileEntry写回硬盘
 - 4.5.5 编程实验:在根目录中重命名文件
 - 4.5.6 思考
 
- 4.6 读写已经存在的文件
 - 4.6.1 文件读写的常规流程
 - 4.6.2 需要深入思考的问题
 - 4.6.3 文件描述符数据结构设计
 - 4.6.4 文件打开与关闭函数的实现
 - 4.6.5 写文件时需要考虑的问题
 - 4.6.6 文件数据写入函数
 - 4.6.7 编程实验:文件数据写入
 - 4.6.8 思考
 
- 4.7 从文件中读取数据
 - 4.7.1 读文件时需要考虑的问题
 - 4.7.2 文件数据读取函数
 - 4.7.3 编程实验:文件数据读取
 - 4.7.4 文件系统中的辅助函数
 - 4.7.5 数据擦除函数的定义与实现
 - 4.7.6 文件读写指针移动函数
 - 4.7.7 FSeek & FTell
 - 4.7.8 编程实验:辅助函数的实现
 - 4.7.9 扩展思考
 
- 5. 系列完结
 
1. 问题
- 能够读写硬盘扇区是否就可以创建文件?
 - 对硬盘而言只有扇区的概念,没有文件的概念,文件是操作系统中的概念。
 - 文件是 有逻辑关联的数据集合,并且 数据之间有存储上的先后关系 。
 - 那么,硬盘和文件是如何联系在一起的呢?
 
1.1 硬盘上最最最简单的文件系统支持方式
- 一个扇区就是一个文件,则: 
  
- 文件名就是逻辑扇区号(LBA)
 - 扇区第0字节存储文件长度
 - 文件大小最多511字节
 
 

- 缺陷: 
  
- 文件数量太多:用不上那么多文件
 - 文件名不友好:使用不方便
 - 文件容量太小:完全不够用
 
 - 启示 
  
- 文件中有一个逻辑上的“指针”,用于标识读写位置
 - 文件背后有一个“缓冲区”,方便指定位置数据读写
 
 
1.2 改进思路
- 支持自定义文件名(仅限制文件名字符数量)
 - 使用硬盘固定位置 将 文件名 和 扇区号 做 映射
 - 将多个扇区组织在一起,共同存储属于同一文件的数据
 

 如:DELPHI.TXT文件名和第35号扇区做映射
1.3 更多细节问题
- 如何区分硬盘上的哪些扇区空闲,哪些扇区被文件使用?
 - 硬盘上的哪个位置记录文件名和扇区的映射关系?
 - 文件内容超过512字节时,如何增加扇区并维护前后关系?
 
1.4 文件系统概要设计

1.5 硬盘数据逻辑示意图

1.6 硬盘数据物理组织示意

2. 问题
-  当目标文件需要保存的数据大于512字节时,如何获取更多的存储空间? 
  
- 选择一个(多个)空闲扇区分配给目标文件使用即可。
 
 -  如何高效的查询以及获取空闲扇区? 
  
- 将扇区分配表组织成不同的链表
 - 扇区分配表中的 每个链表 代表 一个文件
 - 未使用的扇区也组织成一个链表(即:空闲链表,每次从头部取扇区,O(1))
 - 文件管理的过程可看作扇区在不同链表中移动的过程
 
 
2.1 硬盘基础概念
- 硬盘分区 
  
- 对硬盘进行分割,分割成不同的硬盘区域
 
 - 格式化 
  
- 格式化是指对硬盘分区进行的一种初始化操作(设置扇区分配表的初始状态,比如说:在扇区分配表中将所有的扇区标记为空闲扇区)
 - 格式化会使得硬盘分区中的所有文件被清除
 
 
2.2 课程中的解决方案
- 将整个硬盘当作唯一分区使用
 - 对硬盘分区进行格式化 
  
- 建立 引导区,根目录区 和 扇区分配表
 
 - 根目录的本质就是一个文件 
  
- 目录(文件夹) 的 本质 就是 文件
 
 
2.3 数据结构设计

2.4 格式化的关键
- 计算扇区分配表的大小(map size):
 

- 构建空闲扇区链表: 
  
- 将扇区分配表中的所有成员全部组织到空闲链表中
 
 

2.5 编程实验:文件系统的初步实现
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 81
- qt上结果如下:
 

 
- 将qt下格式化的“hd.img”放入到D.T.OS路径下,结果如下:
 

 
3. 问题
- 如何在根目录区中创建文件?
 
3.1 根目录的本质
- 根目录 在文件系统中是一个特殊的文件
 - 根目录中存储了 文件的基本信息(FileEntry) 
  
- FileEntry包含了文件名,文件起始扇区,文件大小等信息
 
 

3.2 根目录逻辑结构

3.3 文件实现的基础:扇区管理
- 如何 获取 / 归还 空闲扇区?
 - 如何 查找当前扇区的后续扇区 ?
 -  如何为当前文件 增加 / 删除 一个扇区?
…… 
3.4 扇区申请

3.5 一些注意事项
- 扇区操作是一种 外存操作,因此需要仔细计算目标位置
 - 扇区管理时 使用 相对扇区地址,扇区读写时 使用 绝对地址

 
●关系如下:
 
3.6 访问数据扇区对应的分配表项
- 分配表项就是管理单元 
  
- 计算相对扇区地址:offset = si - mapSize - 2
(注释:即扇区分配表中的第几个扇区) - 计算目标扇区:sctOff = offset / MAP_ITEM_CNT
(注释:一个扇区包含MAP_ITEM_CNT个分配单元,这里的除法算完后即可得到目标在扇区分配表中是第几个扇区) - 计算目标扇区内偏移:idxOff = offset % MAP_ITEM_CNT
(注释:得到扇区分配表中的第几个扇区之后,计算出在该扇区内的偏移,即在该扇区中偏移了几个分配单元) 
 - 计算相对扇区地址:offset = si - mapSize - 2
 

3.7 核心数据结构及数量关系

3.8 需要实现的部分扇区管理函数

3.9 编程实验:扇区管理函数的实现
- 获取 / 归还空闲扇区
 
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 82 / 00获取归还空闲扇区

 
- NextSector测试
 
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 82 / 01testNextSector

 
- 所有扇区管理函数
 
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 82 / 02
4. 问题
- 如何在根目录区中 创建文件?
 - 申请扇区,写入文件信息数据(FileEntry)
 - 扇区申请成功后,还需要做哪些工作?
 
4.1 根目录本质回顾
根目录(文件) 中存储的是FileEntry类型 的值,每个FileEntry值表示一个硬盘上的文件。

4.2 根目录中创建文件的流程

4.2.1 在根目录中创建文件

4.2.2 CheckStorage() - 写入数据前的检查

4.2.3 在根目录中写入新文件信息

4.2.4 编程实验:在根目录中创建文件
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 83 / 00在根目录中创建文件

4.2.5 思考
- 如何判断在根目录中是否存在指定文件?
 - 直接查找根目录的数据链表,如果文件存在,那么必然能够查找到对应的FileEntry。
 
4.3 根目录中查找文件的流程

4.3.1 文件查找的本质
- 在数据链表中查找FileEntry值
 

4.3.2 FindFileEntry() - 文件查找的核心

4.3.3 在扇区中查找FileEntry

4.3.4 编程实验:在根目录中查找文件
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 83 / 01在根目录中查找文件

4.3.5 思考
- 如何在根目录中删除文件?
 
4.4 在根目录中删除文件
4.4.1 实现思路
根据 文件名 在根目录的数据链表中查找FileEntry值
 当查找成功时:从数据链表中删除FileEntry值
- 关键问题:
FileEntry值的实际位置是在硬盘上,那么如何从硬盘上抹除这个值呢? - 解决方案: 
  
- 判断目标文件是否打开(只有关闭状态能被删除)
 - 根据名字查找目标FileEntry的位置(如:FileEntry 2)
 - 将数据链表中最后一个FileEntry值复制到FileEntry的位置
 - lastBytes = lastBytes - sizeof(FileEntry)
 
 

4.4.2 根目录中删除文件的流程

4.4.3 关键细节
-  
FileEntry移动时需要保留原inSctIdx和inSctOff的值

 -  
抹除最后一个FileEntry所占用的空间,即:修改lastBytes,使其减少64字节
 -  
扩展问题:
- 删除最后一个FileEntry之后,如果lastBytes的值为0,那么应该做什么样的操作?
 - 归还扇区的操作
 
 
4.4.4 数据链表中的字节删除

4.4.5 数据链表容量调整

4.4.6 编程实验:在根目录中删除文件
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 84 / 00在根目录中删除文件

 
4.4.7 思考
- 如何重命名根目录中的文件?
 
4.5 重命名根目录中的文件
4.5.1 解决方案
- 判断目标文件是否已经打开(只有关闭状态能重命名)
 - 根据名字查找目标FileEntry的位置(如:FileEntry 4)
 - 查找新名字是否已经被占用 
  
- 如果没有占用,修改目标FileEntry的name成员
 - 如果已经占用,返回失败
 
 
4.5.2 根目录中重命名文件的流程

4.5.3 文件重命名

4.5.4 FileEntry写回硬盘

4.5.5 编程实验:在根目录中重命名文件
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 84 / 01在根目录中重命名文件

 
4.5.6 思考
- 如何 读写 已经存在的文件?
 
4.6 读写已经存在的文件
4.6.1 文件读写的常规流程

4.6.2 需要深入思考的问题
- 打开一个文件意味着什么?
 - 如何高效的标识已打开的文件?
 - 读写文件数据时必须操作硬盘吗?
 - 如何指定读写文件的位置?
 - ……
 
4.6.3 文件描述符数据结构设计
文件描述符 需要反映当前文件的状态,如:文件名,读写位置,文件长度,等。
4.6.4 文件打开与关闭函数的实现
- FOpen:
 

- FClose:
 

4.6.5 写文件时需要考虑的问题
-  需要写入的数据量与文件读写指针的关系
如:当缓冲区满时,需要如何处理? -  写入数据的同时,文件数据链表是否发生变化?
如:对刚创建的文件写入数据时会发生什么? 
4.6.6 文件数据写入函数

- 缓冲区的准备
 

- 缓冲区数据写入
 

4.6.7 编程实验:文件数据写入
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 85

 
4.6.8 思考
- 如何从文件中读取数据?
 
4.7 从文件中读取数据
4.7.1 读文件时需要考虑的问题
-  需要读取的数据量与文件读写指针的关系
如:文件数据总量小于读取需求量,该如何处理? - 读取数据时,文件数据数据缓冲区如何变化?即:是否需要使用数据缓冲区?
 
4.7.2 文件数据读取函数

- 缓冲区数据读取
 

- 辅助函数的实现
 

4.7.3 编程实验:文件数据读取
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 86 / 00文件数据读取

 
4.7.4 文件系统中的辅助函数

4.7.5 数据擦除函数的定义与实现

4.7.6 文件读写指针移动函数

4.7.7 FSeek & FTell

4.7.8 编程实验:辅助函数的实现
【参看链接】:81-82-83-84-85-86 - 文件系统设计与实现 / 86 / 01辅助函数的实现

 
4.7.9 扩展思考
- 有了文件系统后,内核可以发生怎样的变化?
 



















