lab5 实验报告 [BUAA-OS]
Lab5 实验报告
Part1. 思考题
Thinking 5.1 设备操作与高速缓存
如果通过 kseg0 读写设备,那么对于设备的写入会缓存到 Cache 中。这是一种错误的行为,在实际编写代码的时候这么做会引发不可预知的问题。请思考:这么做这会引发什么问题?对于不同种类的设备(如我们提到的串口设备和 IDE 磁盘)、的操作会有差异吗?可以从缓存的性质和缓存更新的策略来考虑。
当系统崩溃或者断电时,Cache 中的数据可能会丢失或未被写入到设备上,这会导致数据不一致或丢失。另外当访问内核空间时,可能导致设备被错误地写入/读取数据。
对于串口设备,通常是直接进行读写操作,具有即时性且读写频繁,发生数据丢失或不一致的风险高于 IDE 磁盘。
Thinking 5.2 单个文件的最大体积、一个磁盘块最多存储的文件控制块及一个目录最多子文件
查找代码中的相关定义,试回答一个磁盘块中最多能存储多少个文件控制块?一个目录下最多能有多少个文件?我们的文件系统支持的单个文件最大为多大?
1 |
|
指导书 P114 有如下描述:
f_direct[NDIRECT]为文件的直接指针,每个文件控制块设有10个直接指针,用来记录文件的数据块在磁盘上的位置。每个磁盘块的大小为4KB,也就是说,这十个直接指针能够表示最大40KB的文件,而当文件的大小大于40KB时,就需要用到间接指针。f_indirect指向一个间接磁盘块,用来存储指向文件内容的磁盘块的指针。为
了简化计算,我们不使用间接磁盘块的前十个指针。f_dir指向文件所属的文件目录。f_pad则是为了让整数个文件结构体占用一个磁盘块,填充结构体中剩下的字节。
查看user/include/fs.h
文件中的定义和指导书的说明,我们可以知道这是理论课上所说的多级间接索引结构:
- 一个磁盘块的大小为4KB(4096字节)。
- 每个文件控制块(File)占用256字节。
- 每个文件控制块中有10个直接指针,每个指针占用4字节。
- 每个磁盘块可以存储的文件控制块数量为:
4096 / 256 = 16
。 - f_indirect指向的间接磁盘块可以存储 4KB / 4B = 1K 个指针,所以一个目录文件下最多可以有 16 * 1024 = 16384 个文件。
- 一个文件最多可以使用 10 + 1K - 10(间接磁盘块的前10个指针) = 1024 个磁盘块,所以一个文件最大可以有 1024 * 4KB = 4MB。
Thinking 5.3 磁盘最大容量
请思考,在满足磁盘块缓存的设计的前提下,我们实验使用的内核支持的最大磁盘大小是多少?
DISKMAP
到DISKMAP+DISKMAX
这一段虚存地址空间(0x10000000-0x4FFFFFFF)作为缓冲区,因此最大为DISKMAX
,即1GB。
Thinking 5.4 文件系统进程中宏定义理解
在本实验中,fs/serv.h、user/include/fs.h 等文件中出现了许多宏定义, 试列举你认为较为重要的宏定义,同时进行解释,并描述其主要应用之处。
1 | in fs/serv.h |
1 | in user/include/fs.h |
Thinking 5.5 文件描述符与 fork 函数
在 Lab4“系统调用与 fork”的实验中我们实现了极为重要的 fork 函数。那么 fork 前后的父子进程是否会共享文件描述符和定位指针呢?请在完成上述练习的基础上编写一个程序进行验证。
会。进程所有的文件描述符都存储在 [FDTABLE, FILEBASE) 的地址空间中,位于 USTACKTOP 以下。在fork()
函数执行时,会调用duppage()
函数将这父进程页表中的这部分页表项拷贝到子进程的页表中。
1 | int main(){ |
输出结果:
1 | child read is good with "we share the file descriptor " |
Thinking 5.6 文件系统用户接口中的结构体
请解释 File, Fd, Filefd 结构体及其各个域的作用。比如各个结构体会在哪些过程中被使用,是否对应磁盘上的物理实体还是单纯的内存数据等。说明形式自定,要求简洁明了,可大致勾勒出文件系统数据结构与物理实体的对应关系与设计框架。
…existing code…
Thinking 5.6 文件系统用户接口中的结构体
请解释 File, Fd, Filefd 结构体及其各个域的作用。比如各个结构体会在哪些过程中被使用,是否对应磁盘上的物理实体还是单纯的内存数据等。说明形式自定,要求简洁明了,可大致勾勒出文件系统数据结构与物理实体的对应关系与设计框架。
-
File 结构体(磁盘数据 + 内存缓存)
• f_name:文件名,用于路径解析
• f_size:文件大小(字节),磁盘元信息
• f_type:文件类型(普通/目录/设备),磁盘元信息
• f_direct[N]:直接数据块号数组,指向磁盘上的数据块
• f_indirect:间接块号,指向存放额外块号的磁盘块(多级索引)
• f_dir:内存指针,指向父目录的 File 对象,仅存在内存中
• f_pad:对齐填充,确保 File 结构固定 256 字节 -
Fd 结构体(纯内存描述符)
• fd_dev_id:设备 ID,记录文件所在设备
• fd_offset:当前读写偏移量,仅内存维护,不写回磁盘
• fd_omode:打开模式(只读/写/读写),运行时控制访问权限 -
Filefd 结构体(内存封装 + 缓存)
• f_fd:内存 Fd 结构,用于进程间传递和系统调用
• f_fileid:文件 ID,对应 opentab 索引
• f_file:文件控制块
以上结构共同构成用户接口到磁盘数据的桥梁:
Fd 管理进程级别的打开状态,Filefd 将 Fd 与磁盘数据 File 关联,File 在内存与磁盘间同步文件信息与数据块索引。
Thinking 5.7 解释时序图,思考进程间通信
图5.9中有多种不同形式的箭头,请解释这些不同箭头的差别,并思考我们的操作系统是如何实现对应类型的进程间通信
查阅 OO第四单元手册,了解时序图中的箭头类型及其含义如下:
箭头类型 | 说明 |
---|---|
黑三角箭头搭配黑实线 | 表示同步消息,消息的发送者把进程控制传递给消息的接收者,然后暂停活动,等待消息接收者的回应消息 |
开三角箭头搭配黑色实线 | 表示异步消息,消息的发送者把消息发送给消息的接收者,不用等待回应的消息,即可开始另一个活动。 |
用开三角箭头搭配黑色虚线 | 表示返回关系,返回消息和同步消息结合使用,异步消息不进行等待,无需要知道返回值。 |
- 首先由init程序调用
init()
函数执行ENV_CREATE(user_env)
和ENV_CREATE(fs_serv)
函数创建第0个和第1个线程,来启动用户进程 user_env 和文件系统 fs_serv (必须为第1个线程)。 - fs_serv 进入 fs/serv.c,调用
serv_init()
和fs_init()
函数完成初始化,随后进入serv()
函数,被ipc_receive()
阻塞,等待 IPC。 - user_env 经过
open()
->fsipc_open()
->fsipc()
->ipc_send(fsreq)
的函数调用向 fs 进程发送请求,文件系统 fs_serv 执行完后使用ipc_send(dst_va)
返回,等待下一次服务wait_for_next_serv
。
Part2. 难点分析
本实验的设计主要围绕以下几个难点展开:
-
跨设备统一接口设计
MOS 通过统一的 Fd 结构将不同设备抽象为文件接口,实现了操作上的一致性。在fd.c
中提供统一的文件操作函数,而file.c
、console.c
等模块则分别实现各自设备的 Dev 抽象函数,确保上层调用对各种设备均适用。 -
文件服务进程与进程间通信
为了解耦文件操作与设备驱动,实验中构建了专门的文件服务进程serv.c
。该进程利用fsipc.c
模块实现与用户进程的 IPC 通信,有效分离了文件系统请求与具体设备交互的实现,提高了系统模块的灵活性和可靠性。 -
磁盘块管理与缓存优化
文件服务进程通过fs.c
模块实现文件级和磁盘块级的数据交互,并采用块缓存机制来优化磁盘 I/O 的性能。所有底层磁盘交互均通过系统调用在KSEG1
段实现,并由ide.c
模块支持块或扇区级的直接读写,这种设计大大提升了系统的工作效率。
Part3. 实验体会
在 Lab5 中,我们通过建立统一的数据结构和接口设计、模块化的进程间通信以及高效的磁盘访问策略,实现了一个通用高效的文件系统架构。