BUAA-OS-lab5
lab5 实验报告
思考题
Thinking 5.1
缓存机制的设计是为了提高效率,数据在发生改变时不立即写入内存,而是在 Cache 发生替换时才写入。这对于需要实时交互的外设来说会带来问题,因为如果写入 kseg0 部分,数据可能很久都不被真正写入内存中,引发错误。
这种错误对于串口设备来说出现的可能性大,磁盘相对来说较小。
Thinking 5.2
一个磁盘块的大小为 4KB,一个文件控制块的大小为 256B,一个磁盘块中最多有 $(2^{12}/2^8=2^4=16)$ 个文件控制块。
一个目录下最多指向 $10+1024-10=1024$ 个磁盘块,每一个磁盘块有最多有 $16$ 个文件控制块,所以一个目录最多有 $1024\times 16=16K$ 个文件。
单个文件系统支持的最大文件为 $4096\times4096/4=2^{22}B=4MB$。
Thinking 5.3
#define DISKMAX 0x40000000
最大磁盘大小为1GB。
Thinking 5.4
#define SECT2BLK (BLOCK_SIZE / SECT_SIZE) /* sectors to a block */
实现 sect 号到 block 号的转换。
1
2
3
4
5
6
// 一个block里有多少个文件
#define FILE2BLK (BLOCK_SIZE / sizeof(struct File))
// File types
#define FTYPE_REG 0 // Regular file
#define FTYPE_DIR 1 // Directory
文件类型主要用在 struct File
中,用来标记这个文件属于一个文件还是目录。
malta.h
中出现了较多的宏定义,主要都是操作系统和外设交互的地址。
Thinking 5.5
会共享。
1
2
3
4
5
6
7
8
fdnum = open("/test", O_RDWR);
if((r = fork()) == 0) { //子进程
n = read(fdnum, buf, 5);
debugf("child : \"%s\"\n", buf);
} else {
n = read(fdnum, buf, 5);
debugf("father : \"%s\"\n", buf);
}
文件 /test
中的内容为 0123456789
.
输出为:
child : 01234
father : 56789
因此文件指针和文件描述符会共享。
Thinking 5.6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// File结构体表示一个文件或目录的元数据,既存在于磁盘上的物理结构,也存在于文件系统服务进程的内存中。
struct File {
char f_name[MAXNAMELEN]; // 文件名或目录名的字符串数组
uint32_t f_size; // 文件或目录占据的存储空间大小,以字节为单位
uint32_t f_type; // 文件的类型标识,用于区分文件和目录
uint32_t f_direct[NDIRECT]; // 直接指针数组,指向包含文件内容的磁盘块地址
uint32_t f_indirect; // 间接指针,指向一个磁盘块,该磁盘块存储了指向文件内容的其他磁盘块地址
struct File *f_dir; // 指向该文件所属目录的指针,此字段仅在内存中表示有效
char f_pad[BY2FILE - MAXNAMELEN - (3 + NDIRECT) * 4 - sizeof(void *)]; // 用于字节对齐的填充字段,确保结构体大小为256字节
} __attribute__((aligned(4), packed));
// Fd结构体代表一个文件描述符,它存储在内存中,包含了关于文件操作的上下文信息。
struct Fd {
u_int fd_dev_id; // 设备标识符,用于识别文件所属的外设类型
u_int fd_offset; // 文件偏移量,表示当前文件操作的读写位置
u_int fd_omode; // 文件打开模式,指定了文件的操作权限,如读写、只读、只写等
};
// Filefd结构体结合了文件描述符和文件元数据,提供了对文件操作的完整上下文。
struct Filefd {
struct Fd f_fd; // 文件描述符,包含设备ID、偏移量和打开模式
u_int f_fileid; // 文件标识符,用于唯一标识一个文件
struct File f_file; // 文件元数据,包含了文件名、大小、类型等属性
};
Thinking 5.7
在图5.7中,我们可以观察到两种不同类型的箭头,分别是实心箭头和虚线箭头。实心箭头用于表示同步消息,而虚线箭头则表示返回消息。
init 发出的 ENV_CREAT 箭头表示 init 进程创建 fs 和 user 两个进程。用户进程和文件系统之间的箭头表明二者之间存在交互,用户进程发出需求,文件系统实现。
通信的基本流程如下:首先,用户空间调用 user/lib/file.c
中提供的文件操作函数,如 open
。这些函数随后调用 user/lib/fsipc.c
的函数。传递所需的操作类型码,并进行 ipc_send
,向文件系统发送请求,并用 ipc_rcv
来接收结果。
文件系统接收到请求后,使用 serv.c
中的函数来处理请求。
实验难点分析
这次实验的代码填空总的来说其实不难,代码量并不大。然而,注意到 lab5 实际上新增了不少文件,主要是 file.c
和 fs.c
,并且这两个文件都很长,里面定义了很多函数,并且函数之间的调用比较频繁,它是一步一步向更深处调用函数的,一开始阅读很容易搞不明白函数的位置。注意到他们其实都是实现对文件系统的一系列操作,因此本质实际上是一样的,只是操作数量有些多罢了。我们只需要理解了用户进程和文件系统的交互机制,都很好理解。只是为了准备上机,所有的代码还是需要通读一遍比较好。
课下实验还有一部分内容是实现操作系统和外设磁盘的交互。在这一部分内容我曾出现了下面的疑问:
1
2
3
4
// Step 8: Read the data from device
for (int i = 0; i < SECT_SIZE / 4; i++) {
panic_on(syscall_read_dev(dst + offset + i * 4, MALTA_IDE_DATA, 4));
}
为什么这里的读取总是从同一个地址读?这样读出来的内容难道不会是一直一样的吗?
课后咨询助教才明白,这些 MALTA_*
的宏定义表示的地址,其实并不对应内存中的实际地址,只是表示操作系统和外设通信的一个接口。在先前对寄存器进行设置后,一直读取外设就行了。这个接口地址的知识也和上个学期计组 P7 的内容相似,这也让我对计算机体系结构有了更深刻的认知,有了醍醐灌顶的感觉。
实验体会
lab5 的上机完成的比较顺利,课上发现了一些课下的小 bug,比如非 void 型函数没给返回值啥的,不过看上去问题不大,都是一遍通过的。
OS 上机到此就结束了,虽然有的时候挺煎熬的,不过不得不承认,如果不是隔周的上机,我很难对整个小型操作系统的每一个部分都有这么清晰的认知,毕竟课下的任务其实不是很重,照着注释依葫芦画瓢的写代码,即使不求甚解,也可以通过课下,如果没有上机 push 我,我很难做到每周都抽一天时间来阅读代码并把整个架构都搞明白。
有的时候会思考,是否课下任务设置的太过于简单?但发现,如果课下要做的多了,或者注释提示的少了,一上来就这样也根本无从下手没法写。所以这种模式还是挺不错的,课下的机制让大家都能够完成课下并对代码有一个基本的认知,然后再用上机 push 一下,加深认知,感觉挺好的。