操作系统lab5

LAB5实验报告

思考题

Thinking1

  • 如果使用kseg0进行设备读写,在写时将写入cache,而设备只能读内存中的数据,此时内存中的数据可能还未更新,设备将无法读到正确的数据。
  • 对于串口设备由于其读写频率较高,发生上述错误的概率可嫩较高。

Thinking2

  • 由定义可知,文件结构体大小为\(256B\),一个磁盘块大小为\(4KB\),易得一个磁盘块中有\(16\)个文件结构体。 图片

  • 目录中的各个文件结构体对应各个文件,因此目录中能得到的所有文件结构体数量即为一个目录下文件数量的最大值,一个磁盘块有\(16\)个文件结构体,目录中有\(1024\)个文件结构体,因此最多\(4K\)个文件。

  • 单个文件中的指向的磁盘块存储文件内容,单个文件有\(1024\)个磁盘块,每个磁盘块\(4KB\),因此单个文件最大为\(4GB\)

Thinking3

DISKMAX为文件系统进程地址空间中用于映射磁盘内容的域的大小,其定义如下:

1
#define DISKMAX 0x40000000

可知其最大支持\(1GB\)的磁盘内容。

Thinking4

fs/serv.h中有如下宏定义:

1
2
#define DISKMAP 0x10000000  // 磁盘内容映射起始地址,可以通过此起始地址和偏移量访问磁盘内容
#define DISKMAX 0x40000000 // 磁盘内容映射空间大小,在访问磁盘内容时可以用于检查是否越界

user/include/fs.h中有如下宏定义:

1
2
3
4
#define BLOCK_SIZE PAGE_SIZE	// 定义了磁盘块的大小,其大小正好为一页的大小,便于磁盘块映射
#define FILE_STRUCT_SIZE 256 // 定义了文件结构体大小,磁盘块大小为其整数倍,使一个磁盘块中有整数个文件结构体
#define FTYPE_REG 0
#define FTYPE_DIR 1 // 定义了两种文件类别,目录和普通文件,在使用文件时可根据此判断其类别并进行对应操作

Thinking5

代码如下:

1
2
3
4
5
6
7
8
9
10
11
int num = open("Makefile",O_RDWR);
if (fork() == 0) {
struct Fd *fd;
fd_lookup(num,&fd);
debugf("!@this is child,num:%d,addr:%d\n", num, fd->fd_offset);
return 0;
}
struct Fd *fd;
fd_lookup(num,&fd);
debugf("!@this is father,num:%d,addr:%d\n", num, fd->fd_offset);
return 0;

运行后结果如下:

1
2
!@this is father,num:0,addr:0
!@this is child,num:0,addr:0

由此可知,其文件描述符和定位指针会共享。

Thinking6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Fd {
u_int fd_dev_id; // 外设id,用户调用fd.c中的接口程序时,将根据此id选择相应的外设进行服务
u_int fd_offset; // 记录文件读写位置的偏移量
u_int fd_omode; // 文件打开方式
};
struct Filefd{
struct Fd f_fd; // 文件描述符结构体,即struct Fd
u_int f_fileid; // 文件id
struct File f_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[FILE_STRUCT_SIZE-MAXNAMELEN-(3 + NDIRECT)* 4-sizeof(void *)];// 站位符,使整数个文件控制块占用一个磁盘块
}__attribute__((aligned(4),packed));

Thinking7

图片
  • 实线箭头表示异步消息,执行结束后继续执行自身操作,虚线箭头为同步消息,执行结束后陷入阻塞状态,知道收到相应消息后才可以继续被调度执行。
  • fs文件系统进程进入serve函数后执行ipc_recv函数,然后在接收到user进程的信号之前陷入阻塞状态,等待信号。user进程执行ipc_send函数向文件系统发送信号,fs进入就绪态,被调度执行后将返回的信息发送给user进程,然后再次执行ipc_recv函数陷入阻塞状态。

难点分析

本次实验难点主要在于对于磁盘块,文件控制块结构的理解,使用各级指针查找文件控制块的位置。具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int nblk = dirf->f_size / BLOCK_SIZE;
for (int i = 0; i < nblk; ++i) { // 遍历该文件的所有磁盘块
int bno;
if (i<10) { // 如果个数小于10说明为直接索引,直接得到磁盘块
bno = dirf->f_direct[i];
} else { // 否则需要先找到间接索引磁盘块的起始地址,然后通过偏移量找到磁盘块
bno = *(((int *)(disk[dirf->f_indirect].data))+i);
}
struct File *blk = (struct File *)(disk[bno].data);
for (struct File *f = blk; f < blk + FILE2BLK; ++f) { // 遍历磁盘块,需要注意一个磁盘块中有FILE2BLK个文件控制块
if (f->f_name == NULL) {
return f;
}
}
}

此处只有理解了磁盘块和文件控制块的逻辑结构关系才可以实现上述代码。

实验体会

本次实验中,我理解了用户,操作系统,设备之间的协调配合,以文件系统为例,文件系统进程为运行在用户态的进程,其通过系统调用进入内核态,在内核态中调用内核函数读写内存中的特定区域对设备进行控制,由设备完成具体的设备服务。这个过程充分体现了对于硬件逐层的抽象。

原创说明

参考了往年学长的博客:https://yanna-zy.github.io/2023/05/19/BUAA-OS-5/