思考题

Thinking 5.1

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

  • 当外部设备产生中断信号或者更新数据时,此时Cache中之前旧的数据可能刚完成缓存,那么完成缓存的这一部分无法完成更新,则会发生错误的行为。
  • 对于串口设备来说,读写频繁,信号多,在相同的时间内发生错误的概论远高于IDE磁盘。

Thinking 5.2

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

在user/include/fs.h中,有如下定义

1
2
#define BLOCK_SIZE PAGE_SIZE
#define FILE_STRUCT_SIZE 256

从宏定义可以看出:磁盘块的大小和页大小一样,都是4KB;文件控制块的大小 FILE_STRUCT_SIZE 是256B;故==⼀个磁盘块最多能存储 4K / 256 = 16 个⽂件控制块==

目录文件也是一种文件,会对应一个文件控制块,这一个文件控制块会占用一个磁盘块,那么作为目录的文件控制块最多拥有 BLOCK_SIZE / 4 = 1024个指向磁盘块的指针,也就是管理1024个磁盘的空间;而每个磁盘最多可以存储 16 个文件控制块,则==一个目录最多可以容纳 1024 * 16 = 16K个文件==

单个文件最大与上述分析同理,一个文件控制块最多管理1024个磁盘,如果全部用于存储文件内容的话,文件的大小为:1024 * BLOCK_SIZE = 4M ,即==单个文件最大为4M==

Thinking 5.3

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

实验使用的内核将将 DISKMAP ~ DISKMAP+DISKMAX 这一段虚存地址空间作为缓冲区,缓冲区大小为DISKMAX,因此最⼤磁盘⼤⼩为 DISKMAX = 0x40000000 ,即 ==1GB== 。

Thinking 5.4

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

  • #define BLOCK_SIZE PAGE_SIZE

    磁盘块大小 4096B

  • #define NDIRECT 10

    直接索引磁盘块的数量 10

  • #define NINDIRECT (BLOCK_SIZE / 4)

    间接索引磁盘块的数量 1024

  • #define FILE_STRUCT_SIZE 256

    文件控制块大小 256

  • #define FILE2BLK (BLOCK_SIZE / sizeof(struct File))

Thinking 5.5

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

会共享文件描述符和定位指针

验证程序:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "lib.h"

static char *msg = "This is the NEW message of the day!\n\n";
static char *diff_msg = "This is a different massage of the day!\n\n";

void umain()
{
int r;
int fdnum;
char buf[512];
int n;

if ((r = open("/newmotd", O_RDWR)) < 0) {
user_panic("open /newmotd: %d", r);
}
fdnum = r;
writef("open is good\n");

if ((n = read(fdnum, buf, 511)) < 0) {
user_panic("read /newmotd: %d", r);
}
if (strcmp(buf, diff_msg) != 0) {
user_panic("read returned wrong data");
}
writef("read is good\n");

int id;

if ((id = fork()) == 0) {
if ((n = read(fdnum, buf, 511)) < 0) {
user_panic("child read /newmotd: %d", r);
}
if (strcmp(buf, diff_msg) != 0) {
user_panic("child read returned wrong data");
}
writef("child read is good && child_fd == %d\n",r);
struct Fd *fdd;
fd_lookup(r,&fdd);
writef("child_fd's offset == %d\n",fdd->fd_offset);
}
else {
if((n = read(fdnum, buf, 511)) < 0) {
user_panic("father read /newmotd: %d", r);
}
if (strcmp(buf, diff_msg) != 0) {
user_panic("father read returned wrong data");
}
writef("father read is good && father_fd == %d\n",r);
struct Fd *fdd;
fd_lookup(r,&fdd);
writef("father_fd's offset == %d\n",fdd->fd_offset);
}
}

输出结果

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
26
27
28
29
30
31
32
33
34
35
36
37
main.c: main is start ...
init.c: mips_init() is called
Physical memory: 65536K available, base = 65536K, extended = 0K
to memory 80401000 for struct page directory.
to memory 80431000 for struct Pages.
pmap.c: mips vm init success
pageout: @@@___0x7f3fe000___@@@ ins a page
pageout: @@@___0x40d000___@@@ ins a page
FS is running
FS can do I/O
pageout: @@@___0x7f3fe000___@@@ ins a page
pageout: @@@___0x407000___@@@ ins a page
superblock is good
diskno: 0
diskno: 0
read_bitmap is good
diskno: 0
alloc_block is good
file_open is good
file_get_block is good
file_flush is good
file_truncate is good
diskno: 0
file rewrite is good
serve_open 00000400 ffff000 0x2
open is good
read is good
father read is good && father_fd == 0
father_fd's offset == 41
[00000400] destroying 00000400
[00000400] free env 00000400
i am killed ...
child read is good && child_fd == 0
child_fd's offset == 41
[00001402] destroying 00001402
[00001402] free env 00001402
i am killed ...

Thinking 5.6

请解释File,Fd,Filefd结构体及其各个域的作用。比如各个结构体会在哪些过程中被使用,是否对应磁盘上的物理实体还是单纯的内存数据等。说明形式自定,要求简洁明了,可大致勾勒出文件系统数据结构与物理实体的对应关系与设计框架。

File 文件

1
2
3
4
5
6
7
8
9
10
11
// user/include/fs.h 中
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 *)]; // 将结构体大小扩充到 FILE_STRUCT_SIZE
} __attribute__((aligned(4), packed));

用处:作为文件控制块,记录的文件的信息

对应了磁盘的物理实体

Fd 文件描述符

1
2
3
4
5
6
7
// user/include/fd.h 中
// file descriptor
struct Fd {
u_int fd_dev_id; //文件对应的设备
u_int fd_offset; //文件读写的偏移量
u_int fd_omode; //文件读写的模式
};

用处:用于存储文件的基本信息和用户进程中关于文件的状态

不对应物理实体,是单纯的内存数据

FileFd 文件+描述符

1
2
3
4
5
6
// file descriptor + file
struct Filefd {
struct Fd f_fd; // 文件描述符fd
u_int f_fileid; // 文件id
struct File f_file; // 文件File
};

对应了磁盘的物理实体,也包含内存数据

Thinking 5.7

图 5.9 中有多种不同形式的箭头,请解释这些不同箭头的差别,并思考我们的操作系统是如何实现对应类型的进程间通信的。

![image-20240528212533809](C:\Users\86199\Documents\A blog\myblog\source_posts\image-20240528212533809.png)

起点为圆点的实线箭头表示进程创建;起点没有点的实线箭头表示用户进程向文件系统服务进程发送信息,虚线箭头表示文件服务进程向用户进程发送信息

  • ENV_CREATE(user_env)和ENV_CREATE(fs_serv)都是异步消息,由init()发出创建消息后,init()函数即可返回执行后续步骤,由fs和user线程执行自己的初始化工作。
  • fs线程初始化serv_init()和fs_init()完成后,进入serv()函数,被ipc_receive()阻塞为ENV_NOT_RUNNABLE,直到收到User线程的ipc_send(fsreq)被唤醒。
  • User线程向fs线程ipc_send(fsreq)发送请求为同步消息,发送后自身进入阻塞ENV_NOT_RUNNABLE等待被唤醒的fs线程服务结束时ipc_send(dst_va),用户线程接收到数据后继续运行,此后fs线程进入阻塞,等待下次被用户唤醒。

实验难点

磁盘空间

![image-20240516120636111](C:\Users\86199\Documents\A blog\myblog\source_posts\image-20240516120636111.png)

结构整理

File相关见上方思考题

Open

1
2
3
4
5
6
7
// user/fs/serv.c
struct Open {
struct File *o_file; // mapped descriptor for open file
u_int o_fileid; // file id
int o_mode; // open mode
struct Filefd *o_ff; // va of filefd page
};

实验心得

虽然课下实现的代码量减小了,但是要理解整个文件系统要阅读的代码量真的很大 x

磁盘的引入也让我重新审视了自己对内存空间的理解,文件系统服务进程和用户进程之间的ipc通信也让我更加深入的理解了ipc的机制

爆夸指导书绘图,拯救了我糊糊的层次结构理解

不过如果能对课下以外的代码多加入一些讲解就好了