Skip to main content

OS Lab5 实验笔记

文件系统概述

Thinking 5.1

查阅资料,了解 Linux/Unix 的 /proc 文件系统是什么?有什么作用? Windows 操作系统又是如何实现这些功能的?proc 文件系统这样的设计有什么好处和可以改进的地方?

答:

/proc 文件系统是什么?

proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口,一般挂载在“/proc”目录,其中的大部分内容是只读的。

有什么作用?

proc文件系统提供了从用户空间通往内核空间的一个入口,通过读取其中的文件来动态地获得内核的状态。很多Linux系统命令,比如lsmod、ps,都是通过直接访问proc文件系统来获得数据。此外还可以通过修改某些文件的内容来改变内核参数。

Windows如何实现这些功能?

在windows平台下,微软为了保护操作系统的安全性和稳定性,把系统分为内核层和用户层,需要借助native API 和win32 API访问系统内核数据。

/proc文件系统这样的设计有什么好处和可以改进的地方?

  • 好处:可以将对虚拟文件的读写作为与内核中实体进行通信
  • 改进:sysfs比/proc有着更好的组织;对文件的修改在重启后会失效

在本次实验中,我们将要实现一个简单但结构完整的文件系统。整个文件系统包括以下几个部分:

  1. 外部储存设备驱动 通常,外部设备的操作需要通过按照一定操作序列读写特定的寄存器来实现。为了将这种操作转化为具有通用、明确语义的接口,我们必须实现相应的驱动程序。在本部分,我们实现了IDE磁盘的用户态驱动程序。
  2. 文件系统结构 在本部分,我们实现磁盘上和操作系统中的文件系统结构,并通过驱动程序实现文件系统操作相关函数。
  3. 文件系统的用户接口 在本部分,我们提供接口和机制使得用户程序能够使用文件系统,这主要通过一个用户态的文件系统服务来实现。同时,我们引入了文件描述符等结构使操作系统和用户程序可以抽象地操作文件而忽略其实际的物理表示。

接下来我们一一详细解读这些部分的实现。

IDE磁盘驱动

Thinking 5.2

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

答:

如果通过kseg0 访问设备,在写入时会缓存到Cache中,当数据块要被替换时才写回到空间中。

  • 对于如Console的串口设备,进行通信时是实时的,而借助Cache数据何时被写入外设并不确定,因此无法预测通信的结果
  • 对于IDE磁盘等,访问磁盘与访问内存的机制相似,可以使用Cache机制提高访问速度。

Exercise5.1

请根据lib/syscall_all.c中的说明,完成sys_write_dev函数和sys_read_dev 函数,并且在 user/lib.h,user/syscall_lib.c 中完成用户态的相应系统调用的接口。

编写这两个系统调用时需要注意物理地址、用户进程虚拟地址同内核虚拟地址 之间的转换。 同时还要检查物理地址的有效性,在实验中允许访问的地址范围为: console: [0x10000000, 0x10000020), disk: [0x13000000, 0x13004200), rtc: [0x15000000, 0x15000200),当出现越界时,应返回指定的错误码。

Exercise 5.2

参考内核态驱动,完成 fs/ide.c 中的 ide_write 函数,以及 ide_read 函数,实现对磁盘的读写操作。

Text Input with Hints and Feedback

详细阅读实验指导书, 回答以下问题:往磁盘写入数据, 写入内存的首地址为:

答:

Gxemul 提供的Simulated IDE disk 的地址是0x13000000。

注意,所有的地址操作都需要将物理地址转换成虚拟地址。此处设备基地址对应的kseg1 的内核虚拟地址是0xB3000000。

0x13000000加上0xA0000000,答案就是0xB3000000。

文件系统结构

Exercise 5.3

文件系统需要负责维护磁盘块的申请和释放,在回收一个磁盘块时,需 要更改位图中的标志位。如果要将一个磁盘块设置为 free,只需要将位图中对应的 位的值设置为 1 即可。请完成 fs/fs.c 中的 free_block 函数,实现这一功能。同时 思考为什么参数 blockno 的值不能为 0 ?

Thinking 5.3

一个磁盘块最多存储 1024 个指向其他磁盘块的指针,试计算,我们 的文件系统支持的单个文件的最大大小为多大?

答:

每个磁盘块的大小为4KB,一个磁盘块最多存储 1024 个指向其他磁盘块的指针,因此单个文件的最大大小为:

1024×4KB=4MB1024×4KB=4MB

Thinking 5.4

查找代码中的相关定义,试回答一个磁盘块中最多能存储多少个文件控制块?一个目录下最多能有多少个文件?

答:

一个磁盘块中最多能存储FILE2BLK个文件控制块,即16个文件控制块。

FILE2BLK=BY2BLKBY2FILE=16FILE2BLK = \frac{BY2BLK}{BY2FILE}=16

Exercise 5.4

请参照文件系统的设计,完成 fsformat.c 中的 create_file函数,并按 照个人兴趣完成 write_directory 函数(不作为考察点),实现将一个文件或指定 目录下的文件按照目录结构写入到 fs/fs.img 的根目录下的功能。关于如何创建二 进制文件的镜像,请参考 fs/Makefile。 在实现的过程中,你可以将你的实现同我们给出的参考可执行文件tools/fsformat 进行对比。具体来讲,你可以通过 Linux 提供的 xxd 命令将两个 fsformat 产生的二 进制镜像转化为可阅读的文本文件,手工进行查看或使用 diff 等工具进行对比。

Thinking 5.5

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

答:

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

我们实验使用的内核支持的最大磁盘大小是DISKMAX,大小为1G。

Thinking 5.6

如果将 DISKMAX 改成 0xC0000000, 超过用户空间,我们的文件系统还能正常工作吗?为什么?

答:

不能。我们的文件系统将DISKMAP到DISKMAP+DISKMAX 这一段虚存地址空间作为缓冲区,如果DISKMAX 改成 0xC0000000,那么缓冲区就包括了内核空间。用户态进程若是直接读写内核虚拟地址将会由处理器引发一个地址错误,因此不能正常工作。

Exercise 5.5

fs/fs.c 中的 diskaddr 函数用来计算指定磁盘块对应的虚存地址。完成 diskaddr 函数,根据一个块的序号 (block number),计算这一磁盘块对应的 512 bytes 虚存的起始地址。(提示:fs/fs.h 中的宏 DISKMAP 和 DISKMAX 定义了磁盘映射虚存的地址空间)。

Exercise 5.6

实现 map_block 函数,检查指定的磁盘块是否已经映射到内存,如果没有,分配一页内存来保存磁盘上的数据。对应地,完成 unmap_block 函数,用于解除磁盘块和物理内存之间的映射关系,回收内存。(提示:注意磁盘虚拟内存地址 空间和磁盘块之间的对应关系)。

Exercise 5.7

补全 dir_lookup 函数,查找某个目录下是否存在指定的文件。(提示: 使用file_get_block可以将某个指定文件指向的磁盘块读入内存)。

Thinking 5.7

阅读 user/file.c ,你会发现很多函数中都会将一个 struct Fd 型的 指针转换为 struct Filefd 型的指针,请解释为什么这样的转换可行。

答:

在user/fd.h中可以找到Fd和Filefd的定义:

// file descriptor
struct Fd {
u_int fd_dev_id;
u_int fd_offset;
u_int fd_omode;
};

// file descriptor + file
struct Filefd {
struct Fd f_fd;
u_int f_fileid;
struct File f_file;
};

可以看到Filefd结构体的第一个成员就是Fd结构体,从指针角度, struct Fd 型的指针与struct Filefd 型的指针所指向的数据的起始部分是相同的,转换后可以增加Filefd中的新属性。

Thinking 5.8

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

答:

Fd结构体:

// file descriptor
struct Fd {
u_int fd_dev_id; // 设备id:用于校验等操作
u_int fd_offset; // 文件指针偏移:在write/read时标记当前读写位置
u_int fd_omode; // 文件权限:在进行文件的write操作时需要校验是否可写
// 低两位00表示只读,01表示只写,10表示读写均可
};

Filefd结构体:

// file descriptor + file
struct Filefd {
struct Fd f_fd; // 文件描述符:同上
u_int f_fileid; // 文件id:用于fsipc_close等根据id修改文件的操作
struct File f_file; // 文件控制块:对应磁盘上的物理实体
};

Open结构体:

struct Open {
struct File *o_file; // 映射文件控制块的指针:对应磁盘上的物理实体
u_int o_fileid; // 文件id:同上
int o_mode; // 文件读写权限:同上
struct Filefd *o_ff; // 文件描述符:同上
};
note

这是一篇从Hexo迁移的文章,创建于2020-05-24 23:16:36