思考题

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

当外部设备自身更新数据时,如果此时 CPU 写入外设的数据还只在缓存中,则缓存的那部分数据就只能在外设自身更新后再写入外设(只有缓存块将要被新进入的数据取代时,缓存数据才会被写入内存),这样就会发生错误的行为。

对于串口这类实时交互设备,问题表现为明显的 I/O 延迟和丢失。对于IDE 磁盘这类块设备,问题可能导致命令执行失败、磁盘状态混乱,最严重的是静默数据损坏,危害性极大且难以排查。

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

user/include/fs.h 中,磁盘块大小 BLOCK_SIZE 定义为 PAGE_SIZE,文件控制块的大小 STRUCT_FILE_SIZE 定义为 256,可知一个磁盘块最多存储 PAGE_SIZE / 256 = 16 个文件控制块。一个目录下最多容纳 NINDIRECT 即 1024 个磁盘块,因此一个目录下最多有 1024 _ 256 = 218 个文件。MOS 系统支持的单个文件大小为 NINDIRECT _ BLOCK_SIZE = 1024 \* 4096 KB = 4 MB 大小。

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

DISKMAPDISKMAP + DISKMAX 这一段虚拟地址空间(0x1000_0000-0x4FFF_FFFF)作为缓冲区,故 MOS 支持的最大磁盘大小为 0x4000_0000 即 1GB。

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

fs/serv.h 中:

  • SECT_SIZE:表示扇区大小。
  • SECT2BLK:表示一个磁盘块中包含的扇区数量。
  • DISKMAPDISKMAX:分别表示磁盘映射到内存空间的起始地址和所映射的空间大小。

user/include/fs.h 中:

  • BLOCK_SIZEBLOCK_SIZE_BIT:分别表示磁盘块大小和一个磁盘块位数。
  • NDIRCTNINDIRECT:分别表示文件控制块中直接指针的数量和间接指针的数量。
  • FILE2BLK:表示一个磁盘块中存储文件控制块的最大数量,用于对目录的某个磁盘块进行文件控制块的遍历。

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

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

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 *)];
    } __attribute__((aligned(4), packed));
    • f_type 为文件类型,有普通文件(FTYPE_REG)和目录(FTYPE_DIR)两种。
    • f_direct 存储磁盘块的直接指针(实际为磁盘块的块号),f_indirect 存储磁盘块的间接指针。为便于计算,f_indirect 指向的磁盘块中的前 NDIRECT 个指针不使用,因此一个文件最多有 NINDIRECT 即 1024 个磁盘块。
    • f_dir 为指向所属目录的指针,仅在文件控制块被载入内存后才有效。
    • f_pad 是用于填充结构体中剩下的字节(File 结构体大小固定为 STRUCT_FILE_SIZE,为了让整数个文件结构体占用一个磁盘块)。

    文件控制块存在于目录文件下指向的磁盘块中,因此存在于磁盘上的物理实体。

  • Fd:文件描述符

    1
    2
    3
    4
    5
    6
    // user/include/fd.h
    struct Fd {
    u_int fd_dev_id;
    u_int fd_offset;
    u_int fd_omode;
    };
    • fd_dev_id 为外设 id,决定去哪找文件。
    • fd_offset 为读写的偏移量,指出现在读到何处了,在 seek()write() 等操作中会被修改。
    • fd_omode 为打开权限,有 0-R, 1-W, 2-RW 三种。

    文件描述符并不是磁盘上的实体,只是内存上文件系统进程的一个结构。

  • Filefd:给文件描述符增加了一些描述

    1
    2
    3
    4
    5
    6
    // user/include/fd.h
    struct Filefd {
    struct Fd f_fd;
    u_int f_fileid;
    struct File f_file;
    };
    • f_fileid 描述已经打开的文件。

    是内存数据而非磁盘数据。

  • 补充 Open:文件打开记录表的表项。

    1
    2
    3
    4
    5
    6
    7
    // 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
    };
    • o_file

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

箭头表示 含义
image-20250529104527013 同步消息(阻塞)
image-20250529104816379 返回消息
image-20250529104922510 生命线

我们的操作系统是通过 IPC 来实现进程间通信的,这种方式传递的信息本质上是一种同步消息。具体流程是:发送方先调用 ipc_send 函数,该函数通过一个死循环来不断向接收方信息。当接收方成功接收到消息时,ipc_send 函数跳出循环并结束,这时发送方再调用 ipc_recv 函数主动放弃 CPU,等待接收返回信息。

难点分析

  1. 文件结构(以下文件树仅展示部分与本 Lab 相关的文件)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    tools/
    ├── fsformat
    └── fsformat.c
    fs/
    ├── fs.c
    ├── ide.c
    ├── serv.c
    └── serv.h
    user/lib/
    ├── fd.c
    ├── file.c
    ├── fsipc.c
    └── ipc.c
    user/include/
    ├── fd.h
    ├── fs.h
    ├── fsreq.h
    └── lib.h
    • tools 目录中存放的是构建 MOS 时辅助工具的代码,本 Lab 中实现的 fsformat 工具用于创建磁盘镜像。注意,tools 目录下的代码仅用于 MOS 的构建,在宿主 Linux 环境(而非 MIPS 模拟器)中运行,也不会被编译进 MOS 的内核、用户库或用户程序中。
    • fs 目录中存放的是文件系统服务进程的代码。在 fs.c 中实现文件系统的基本功能函数,在 ide.c 中通过系统调用与磁盘镜像进行交互。该进程的主干函数在 serv.c 中,通过 IPC 通信与用户进程 user/lib/fsipc.c 内的通信函数进行交互。
    • user/lib 目录下存放了用户程序的库函数。在本 Lab 中,该目录下的 fsipc.c 实现了与文件系统服务进程的交互file.c 实现了文件系统的用户接口fd.c 中实现了文件描述符,允许用户程序使用统一的接口,抽象地操作磁盘文件系统中的文件,以及控制台和管道等虚拟的文件。
  2. 块缓存

  3. 用户进程与文件系统服务进程的交互(以 remove 文件为例)

心得体会

本 Lab 新增的内容很多,难点主要在于硬盘外设的结构设计、文件系统与设备的交互以及用户进程与文件系统服务进程的交互。要仔细研读各个部分的代码,厘清用户进程借助文件系统对文件操作的流程。

原创说明

思考题部分内容参考http://hyggge.github.io/2022/06/04/os/os-lab5-shi-yan-bao-gao/