Lab5 实验报告

Part1. 思考题

Thinking 5.1 设备操作与高速缓存

如果通过 kseg0 读写设备,那么对于设备的写入会缓存到 Cache 中。这是一种错误的行为,在实际编写代码的时候这么做会引发不可预知的问题。请思考:这么做这会引发什么问题?对于不同种类的设备(如我们提到的串口设备和 IDE 磁盘)、的操作会有差异吗?可以从缓存的性质和缓存更新的策略来考虑。

当系统崩溃或者断电时,Cache 中的数据可能会丢失或未被写入到设备上,这会导致数据不一致或丢失。另外当访问内核空间时,可能导致设备被错误地写入/读取数据。
对于串口设备,通常是直接进行读写操作,具有即时性且读写频繁,发生数据丢失或不一致的风险高于 IDE 磁盘。

Thinking 5.2 单个文件的最大体积、一个磁盘块最多存储的文件控制块及一个目录最多子文件

查找代码中的相关定义,试回答一个磁盘块中最多能存储多少个文件控制块?一个目录下最多能有多少个文件?我们的文件系统支持的单个文件最大为多大?

1
2
3
4
5
6
7
8
9
10
11
12
#define NDIRECT 10
#define FILE_STRUCT_SIZE 256
struct File {
char f_name[MAXNAMELEN]; // filename
uint32_t f_size; // file size in bytes
uint32_t f_type; // file type
uint32_t f_direct[NDIRECT];
uint32_t f_indirect;

struct File *f_dir; // the pointer to the dir where this file is in, valid only in memory.
char f_pad[FILE_STRUCT_SIZE - MAXNAMELEN - (3 + NDIRECT) * 4 - sizeof(void *)];
} __attribute__((aligned(4), packed));

指导书 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 磁盘最大容量

请思考,在满足磁盘块缓存的设计的前提下,我们实验使用的内核支持的最大磁盘大小是多少?

DISKMAPDISKMAP+DISKMAX这一段虚存地址空间(0x10000000-0x4FFFFFFF)作为缓冲区,因此最大为DISKMAX,即1GB

Thinking 5.4 文件系统进程中宏定义理解

在本实验中,fs/serv.h、user/include/fs.h 等文件中出现了许多宏定义, 试列举你认为较为重要的宏定义,同时进行解释,并描述其主要应用之处。

1
2
3
in fs/serv.h
#define DISKMAP 0x10000000 //磁盘块映射到内存的起点
#define DISKMAX 0x40000000 // 最大磁盘大小
1
2
3
4
5
6
in user/include/fs.h
#define BLOCK_SIZE PAGE_SIZE // 每个磁盘块的大小为4KB
#define NDIRECT 10 // 每个文件控制块中直接指针的数量
#define NINDIRECT (BLOCK_SIZE / 4) // 每个文件控制块中间接指针的数量
#define FILE_STRUCT_SIZE 256 // 文件控制块的大小
#define FS_MAGIC 0x68286097 // Everyone's favorite OS class 魔数 MIT 6.828

Thinking 5.5 文件描述符与 fork 函数

在 Lab4“系统调用与 fork”的实验中我们实现了极为重要的 fork 函数。那么 fork 前后的父子进程是否会共享文件描述符和定位指针呢?请在完成上述练习的基础上编写一个程序进行验证。

会。进程所有的文件描述符都存储在 [FDTABLE, FILEBASE) 的地址空间中,位于 USTACKTOP 以下。在fork()函数执行时,会调用duppage()函数将这父进程页表中的这部分页表项拷贝到子进程的页表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(){
int fd;
int r;
char buf[1000];
fd = open("/forkTest", 0);
if((r = fork()) < 0){
user_panic("Error with fork");
}else if(r == 0){
if((r = read(fd, buf, 30)) != 30){
user_panic("Error with child read forkTest");
}
debugf("child read is good with \"%s\"\n", buf);
}else{
if((r = read(fd, buf, 28)) != 28){
user_panic("Error with father read forkTest");
}
debugf("father read is good with \"%s\"\n", buf);
}
return 0;
}

// in forkTest file
we share the file descriptor and the file offset pointer.

输出结果:

1
2
child read is good with  "we share the file descriptor "
father read is good with "and the file offset pointer."

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. 难点分析

本实验的设计主要围绕以下几个难点展开:

  1. 跨设备统一接口设计
    MOS 通过统一的 Fd 结构将不同设备抽象为文件接口,实现了操作上的一致性。在 fd.c 中提供统一的文件操作函数,而 file.cconsole.c 等模块则分别实现各自设备的 Dev 抽象函数,确保上层调用对各种设备均适用。

  2. 文件服务进程与进程间通信
    为了解耦文件操作与设备驱动,实验中构建了专门的文件服务进程 serv.c。该进程利用 fsipc.c 模块实现与用户进程的 IPC 通信,有效分离了文件系统请求与具体设备交互的实现,提高了系统模块的灵活性和可靠性。

  3. 磁盘块管理与缓存优化
    文件服务进程通过 fs.c 模块实现文件级和磁盘块级的数据交互,并采用块缓存机制来优化磁盘 I/O 的性能。所有底层磁盘交互均通过系统调用在 KSEG1 段实现,并由 ide.c 模块支持块或扇区级的直接读写,这种设计大大提升了系统的工作效率。

Part3. 实验体会

在 Lab5 中,我们通过建立统一的数据结构和接口设计、模块化的进程间通信以及高效的磁盘访问策略,实现了一个通用高效的文件系统架构。