Lab5_实验报告

一、思考题

Thinking 5.1

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

1.访问内核时错误读取到设备内容
2.外设可能随时更新,如果在上一次缓存过后,设备的值已经发生了改变,但系统读取外设时先在 Cache 中查找到设备后返回缓存的值,那么通过缓存会访问到落后内容。

串口设备由于即时性与高使用频率,会比IDE磁盘更容易出现这样的错误

Thinking 5.2

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

1
2
3
4
5
6
7
8
9
10
11
12
#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));

(1)每个文件控制块都被数组f_pad强制对齐为256B

一个磁盘块的容量为4KB,最多能存储16个文件控制块(FILE2BLK

NINDIRECT 文件控制块中间接块指针可以管理的块数量上限为 (BLOCK_SIZE / 4) = 1024

f_direct[NDIRECT]包含10个文件指针,同时f_indirect指向一个间接磁盘块,用来存储指向文件内容的磁盘块的指针,一个指针4B,一个磁盘块大小4KB,可以,存放1024个指针(不使用间接磁盘块的前十个指针)。

(2)一个目录文件最多可以使用1024个磁盘块存储数据,因此一个目录下最多1024*16 = 16384个文件

(3)一个文件最多可以使用1024个磁盘块存储数据,因此一个文件最大容量为1024*4KB = 4MB

Thinking 5.3

请思考,在满足磁盘块缓存的设计的前提下,我们实验使用的内核支持的最大磁盘大小是多少
块缓存所在的地址空间为[0x10000000, 0x50000000) 所以内核能够支持的磁盘大小为0x40000000,也就是1GB。

Thinking 5.4

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

  • DISKMAP 0x10000000 磁盘块映射到内存的起点 fs/serv.h

  • DISKMAX 0x40000000 最大磁盘大小 fs/serv.h

  • BLOCK_SIZE 4096 user/include/fs.h

  • *BLOCK_SIZE_BIT 40968 ** user/include/fs.h

  • NDIRECT 10 user/include/fs.h

文件控制块中直接块指针的数量,其值为10

  • NINDIRECT (BLOCK_SIZE / 4) user/include/fs.h

文件控制块中间接块指针可以管理的块数量上限(一个指针4B)

  • **FILE2BLK ** user/include/fs.h

一个block块可以容纳File指针的数量

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

简单计算可以知道,该宏值为16,即,一个block块可以储存16个File指针

Thinking 5.5

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

会。一个进程所有的文件描述符都存储在[FDTABLE, FILEBASE)这一地址空间中。在fork函数执行时,会将这父进程页表中映射一部分地址的页表项拷贝到子进程的页表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main(){
int fd;
int r;
char buf[512];
fd = open("/test",O_RDONLY);
r = fork();
if(r < 0){
user_panic("fork error");
}else if(r == 0){
if((r = read(fd,buf,3))!=3){
user_panic("read error");
}
debugf("child read: \"%s\"\n",buf);
}else{
if((r = read(fd,buf,3))!=3){
user_panic("read error");
}
debugf("father read: \"%s\"\n",buf);
}
return 0;
}

test文件内容

1
2
abcde fghi
jkcbshsciowscwevk

输出

1
2
father read: "abcd"
child read: "e fg"

Thinking 5.6

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

  • File结构体定义及各个域的作用
1
2
3
4
5
6
7
8
9
10
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[FILE_STRUCT_SIZE - MAXNAMELEN - (3 + NDIRECT) * 4 - sizeof(void *)];//对齐512字节
} __attribute__((aligned(4), packed));

对应磁盘的物理实体,文件系统进行实际操作的对象void file_*(struct File **file)

  • Fd结构体定义及各个域的作用
1
2
3
4
5
struct Fd {
u_int fd_dev_id;//文件对应的设备id
u_int fd_offset;//文件读写的偏移量
u_int fd_omode;//文件读写的模式
};

仅为内存数据,主要是用户使用文件描述符对文件进行操作

  • Filefd的结构体定义及各个域的作用
1
2
3
4
5
struct Filefd {
struct Fd f_fd;//文件描述符结构体
u_int f_fileid;//文件id,表示该文件在opentab中的位置
struct File f_file; //文件控制块
};

对应磁盘的物理实体,也包含内存数据,将Fd*强制转换为Filefd*从而获取到文件控制块,获得更多文件信息

Thinking 5.7

//懒得放图

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

UML时序图中有以下几种箭头

消息类型 表示
同步消息 用黑三角箭头搭配黑实线表示
异步消息 用两条小线的开箭头和黑色实线表示
返回消息 用黑三角箭头搭配黑色虚线表示
创建消息 用开三角箭头搭配黑实线表示,其下面特别注明 <<create>>
摧毁消息 用黑三角箭头搭配黑实线表示,其下面特别注明 <<destroy>>
Lost and Found Message 用一个黑色实心的点和黑色实心三角箭头黑实线表示

通过IPC来实现进程间通信的,发送方先调用ipc_send函数,当接收方成功接收到消息时,ipc_send函数跳出循环结束,这时发送方再调用ipc_recv函数等待接返回信息。

1
2
3
4
5
6
static int fsipc(u_int type, void *fsreq, void *dstva, u_int *perm) {
u_int whom;
// Our file system server must be the 2nd env.
ipc_send(envs[1].env_id, type, fsreq, PTE_D);
return ipc_recv(&whom, dstva, perm);
}

//懒得放图

请求消息,用户向文件系统发送请求

fsipc(FSREQ_*, 0, 0),通过发送特定调用号FSREQ_*的值,使得文件系统可以获取serve_table[req]中对于请求类型,进入相应处理函数中处理

//懒得放图

返回消息,文件系统对于用户请求提供返回值

二、实验难点

1.用户进程和文件系统服务进程的交互

fs/serve.c

对请求分发处理

fsipc()—send—>serve()->serve_*()

处理请求并发送响应

serve_*()中调用

  • file_*()

    fs.c中的文件服务函数

  • ipc_send()—send—>fsipc()

user/lib/fd.c

有一个static struct Dev *devtab[]

用户接口 close()、write()、read()、fstat()

调用dev->dev_*

1
2
3
//居然是等价的
(*dev->dev_stat)(fd, stat)
dev->dev_stat(fd, stat)

user/lib/file.c

1
2
3
4
5
6
7
8
9
10
11
12
13
static int file_close(struct Fd *fd);
static int file_read(struct Fd *fd, void *buf, u_int n, u_int offset);
static int file_write(struct Fd *fd, const void *buf, u_int n, u_int offset);
static int file_stat(struct Fd *fd, struct Stat *stat);

struct Dev devfile = {
.dev_id = 'f',
.dev_name = "file",
.dev_read = file_read,
.dev_write = file_write,
.dev_close = file_close,
.dev_stat = file_stat,
};

file_read、file_write、file_stat

只在内存空间行动

调用用户库函数,读写文件内容或文件信息

open、remove、file_close

用户进程请求文件服务 调用fsipc_*

user/lib/fsipc.c

fsipc_*

会返回调用fsipc(FSREQ_*, req, void *dstva, u_int *perm)

1
2
3
4
5
6
static int fsipc(u_int type, void *fsreq, void *dstva, u_int *perm) {
u_int whom;
// Our file system server must be the 2nd env.
ipc_send(envs[1].env_id, type, fsreq, PTE_D);
return ipc_recv(&whom, dstva, perm);
}

2.文件系统的地址空间

文件服务进程的块缓存所在的地址空间为[DISKMAP, DISKMAP + DISKMAX)

用户进程所有文件描述符所在的地址空间为 [FILEBASE - PDMAP, FILEBASE)

用户进程文件描述符对应文件内容整体的映射区间为 [FILEBASE, FILEBASE+1024*PDMAP)

三、实验体会

本单元文件系统内容之多、难度之深、时间之紧让我叹服,还好只上一次机,如果有机会,期末考试结束了会回头再看看的……

宜将剩勇追穷寇,不可沽名学霸王

Lab5上机(5.29)、航概考试(6.2)、OS理论考试(6.7)、挑战性任务(十八周)

哥们来了!!

笑死了extra又挂了!!!!!!!!!!!!!!!!!!!!!!!!!!!!