Lab5实验报告

Yunge HuOS

Lab5实验报告

1. 思考题

1.1 Thinking 5.1

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

对于写入操作,在采用Write-Back刷新策略时,写入数据只有在Cache被换出时才会进行写回,导致后面的操作覆盖了前面的操作,只进行最后一次操作;对于串口设备,只有Cache刷新后才能看到输出,且只能看到最后一个字符;类似的,对于IDE磁盘可能只会写入最后一个扇区。

对于读取操作,任何一种策略都可能会读取到过时的数据,因此产生错误。

1.2 Thinking 5.2

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

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

// Bytes per file system block - same as page size
#define BLOCK_SIZE PAGE_SIZE
#define BLOCK_SIZE_BIT (BLOCK_SIZE * 8)

// Maximum size of a filename (a single path component), including null
#define MAXNAMELEN 128

// Maximum size of a complete pathname, including null
#define MAXPATHLEN 1024

// Number of (direct) block pointers in a File descriptor
#define NDIRECT 10
#define NINDIRECT (BLOCK_SIZE / 4)

#define MAXFILESIZE (NINDIRECT * BLOCK_SIZE)

#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));

include/mmu.h 中有如下定义:

#define PAGE_SIZE 4096

所以一个磁盘块最多能存储的文件控制块数量为 $\frac{BLOCK_SIZE}{FILE_STRUCT_SIZE}=\frac{4096}{256}=16$ 。

一个目录,也就是一个 File 结构体最多指向 1024 个磁盘块,一个磁盘块中最多有 16 个文件控制块,一个目录下最多有 $1024\times16=16384$ 个文件。

同样的,一个 File 结构体最多指向 1024 个磁盘块,一个磁盘块的大小为 4096 字节,所以单个文件最大为 $1024\times4096=2^{22}B=4MB$ 。

1.3 Thinking 5.3

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

fs/serv.h 中有如下代码:

/* Maximum disk size we can handle (1GB) */
#define DISKMAX 0x40000000

所以我们能支持的最大磁盘大小为 $1GB$ 。

1.4 Thinking 5.4

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

首先是 fs/serv.h 文件:

#define PTE_DIRTY 0x0004 // 文件系统块缓存被修改

#define SECT_SIZE 512 // 每磁盘块扇区字节数
#define SECT2BLK (BLOCK_SIZE / SECT_SIZE) // 扇区对应磁盘块

#define DISKMAP 0x10000000 // 块缓存映射起始地址

#define DISKMAX 0x40000000 // 能够支持的最大磁盘容量

然后是 user/include/fs.h 文件:

// 每个文件系统块的大小——和页面大小相同
#define BLOCK_SIZE PAGE_SIZE
#define BLOCK_SIZE_BIT (BLOCK_SIZE * 8)

// 文件名的最大长度
#define MAXNAMELEN 128

// 路径的最大长度
#define MAXPATHLEN 1024

// 文件控制块中直接块指针个数
#define NDIRECT 10
// 文件控制块中非直接块指针个数
#define NINDIRECT (BLOCK_SIZE / 4)

// 最大文件大小
#define MAXFILESIZE (NINDIRECT * BLOCK_SIZE)

// 文件控制块结构体大小
#define FILE_STRUCT_SIZE 256

// 文件转磁盘块
#define FILE2BLK (BLOCK_SIZE / sizeof(struct File))

// 文件类型
#define FTYPE_REG 0 // 普通文件
#define FTYPE_DIR 1 // 目录文件

// 文件系统超级块

#define FS_MAGIC 0x68286097 // 魔数

1.5 Thinking 5.5

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

编写如下的测试代码:

5-5-code

运行结果如下:

5-5-result

说明 fork 前后的父子进程会共享文件描述符和定位指针。

1.6 Thinking 5.6

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

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));

// file descriptor
struct Fd {
	u_int fd_dev_id; // 外设 id 即外设类型
	u_int fd_offset; // 读写偏移量
	u_int fd_omode; // 文件打开方式
};

// file descriptor + file
struct Filefd {
	struct Fd f_fd; // 文件描述符
	u_int f_fileid; // 文件自身 id
	struct File f_file; // 文件本身
};

1.7 Thinking 5.7

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

file-system

我们的进程通过和 file_server 这个进程的通信来操作文件。

在用户进程中,通过 user/file.c 中的函数操作文件系统,在这些用户接口函数中,调用了 user/fsipc.c 中的函数,从而通过 user/fsipc.c 中的这些函数与文件系统进行了通信。在文件系统进程中,初始化完成后将运行 serve 函数,在这个函数中,调用了 ipc_recv ,通过返回值的不同,在 switch...case 语句块中跳转到不同的函数,从而完成通信。

2. 难点分析

本次实验代码量巨大,为理解其逻辑关系以及编写练习带来了巨大的困难。

这部分代码主要分为三部分:

  • 外设控制:设备读写系统调用,IDE磁盘驱动。
  • 文件系统:磁盘布局,文件系统数据结构,文件系统服务进程等。
  • 文件系统接口:一系列用户库函数。

这其中比较难理解的就是 tools/fsformat.c 这个文件,它会对磁盘镜像进行格式化,建立初始的文件系统,同时将宿主机的文件复制到这个磁盘镜像中,以便其他操作的继续进行。可以说,这个格式化程序是整个实验的基础。

在Lab5-2-exam中,需要实现一个递归复制某目录到另一个目录的功能,核心代码如下:

int copy_file_content(struct File *src, struct File *dst) {
	void *src_blk, *dst_blk;
	int r;
	int nblock;
	// Calculate the total number of blocks in the source file.
	nblock = ROUND(src->f_size, BLOCK_SIZE) / BLOCK_SIZE;
	for (u_int i = 0; i < nblock; i++) {
		// Lab 5-2-Exam: Your code here. (3/6)
		// debugf("OK");
		try(file_get_block(src, i, &src_blk));
		try(file_get_block(dst, i, &dst_blk));
		// debugf("OK");
		memcpy(dst_blk, src_blk, BLOCK_SIZE);
		file_dirty(dst, i * BLOCK_SIZE);
	}
	// Flush the changes to the destination file
	file_flush(dst);
	return 0;
}

int copy_directory_contents(struct File *src, struct File *dst) {
	struct File *dir_content;
	void *blk;
	int r;
	// Iterate over each block in the source directory
	for (u_int i = 0; i < ROUND(src->f_size, BLOCK_SIZE) / BLOCK_SIZE; i++) {
		if ((r = file_get_block(src, i, &blk)) < 0) {
			return r;
		}
		dir_content = (struct File *)blk;
		for (int j = 0; j < FILE2BLK; j++) {
			if (dir_content[j].f_name[0] == '\0') continue;
			struct File *dst_file;
			// Step1: Alloc dst_file using 'dir_alloc_file'
			// Lab 5-2-Exam: Your code here. (4/6)
			try(dir_alloc_file(dst, &dst_file));
			// Step2: Assign corresponding values of 'f_name', 'f_dir', 'f_size', 'f_type' to dst_file
			strcpy(dst_file->f_name, dir_content[j].f_name);
			dst_file->f_dir = dst;
			dst_file->f_size = dir_content[j].f_size;
			dst_file->f_type = dir_content[j].f_type;

			// Step3: Invoke either 'copy_directory_contents' or 'copy_file_content',
			// depending on the value of 'f_type'.
			// Lab 5-2-Exam: Your code here. (5/6)
			if (dst_file->f_type == FTYPE_DIR) {
				try(copy_directory_contents(&dir_content[j], dst_file));
			} else if (dst_file->f_type == FTYPE_REG) {
				try(copy_file_content(&dir_content[j], dst_file));
			}
		}
	}
	file_flush(dst);
	return 0;
}

int directory_copy(char *src_path, char *dst_path) {
	struct File *src_dir, *dst_dir;
	int r;
	if ((r = file_open(src_path, &src_dir)) < 0) {
		return r;
	}
	if ((r = file_create(dst_path, &dst_dir)) < 0) {
		return r;
	}
	dst_dir->f_type = FTYPE_DIR;
	return copy_directory_contents(src_dir, dst_dir);
}

在Lab5-2-extra中,需要为文件添加权限控制,并在需要的时候检测是否具有操作权限。同时,还需要实现修改文件权限的服务函数。由于本人这部分实验没有通过,且需要添加代码的位置分散且众多,这里就不再展示代码。

总的来说,这次实验难度很大,需要对整体框架和调用关系有清晰的认识。

3. 实验体会

本学期操作系统的上机实验到这里就要告一段落了,虽然说最后有一点小遗憾,但是整体结果还是不错的,就这样吧。

result

Last Updated 2024/9/22 16:35:40