Lab5-Report

Lab5 Report

思考题

Thinking 5.1

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

    • 外设更新后对内存也进行了更新,但是 cache 中的没有被更新,如果我们通过 kseg0 来访问设备,那么就会导致总是优先通过 cache 获取缓存的数据,而缓存的数据没有更新,从而导致错误。
    • 磁盘作为块设备,总是以块为单位进行读写,而且会经常进行读写,写的时候相应的内存会标记为 dirty,从而写回,更新缓存,因此这种错误对磁盘出现概率较小;但是与之相反,串口设备等字符型设备就很容易发生这种错误。

Thinking 5.2

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fsformat.c:
    #define NBLOCK 1024 // The number of blocks in the disk.
    // 一个磁盘块中最多能存储 1024 个文件控制块
    fs.h:
    BY2FILE = 256,一个文件控制块为256B
    BY2BLK = 4096,一个磁盘块4KB。
    因此一个磁盘块中包含4KB / 256B = 16 个文件控制块。
    NINDIRECT = (BY2BLK / 4) = 1024
    一个目录包含 1024 个指向磁盘块的指针,即最多有 1024 * 16 = 16384 个文件。
    MAXFILESIZE = (NINDIRECT * BY2BLK) = 2^(10+12) = 4MB

Thinking 5.3

  • 块缓存所在的地址空间为[0x10000000, 0x50000000), 因此我们的内核能够支持的磁盘大小为 0x40000000, 也就是 1GB

Thinking 5.4

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

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      #define PTE_DIRTY 0x0002 // 文件系统 cache 修改标志位

      #define BY2SECT 512 // 一个磁盘扇区大小
      #define SECT2BLK (BY2BLK / BY2SECT) // 一个磁盘块中包含的扇区数

      /* Disk block n, when in memory, is mapped into the file system
      * server's address space at DISKMAP+(n*BY2BLK). */
      #define DISKMAP 0x10000000 // 磁盘映射到内存的起始地址

      /* Maximum disk size we can handle (1GB) */
      #define DISKMAX 0x40000000 // 磁盘最大容量
    • user/include/fs.h

      1
      2
      3
      4
      5
      6
      7
      #define BY2BLK BY2PG  // 磁盘块大小和页大小相同,4K
      #define NDIRECT 10 //10 个直接指针,用来记录文件的数据块在磁盘上的位置,最大表示 40KB 的文件
      #define NINDIRECT (BY2BLK / 4) // 存储指向文件内容的磁盘块中,最多储存 1024 个间接指针

      #define MAXFILESIZE (NINDIRECT * BY2BLK) // 一个文件最大大小,1024 * 4K = 4MB

      #define BY2FILE 256 // 一个文件控制块大小

Thinking 5.5

  • 在 Lab4“系统调用与 fork”的实验中我们实现了极为重要的 fork 函数。那么 fork 前后的父子进程是否会共享文件描述符和定位指针呢?请在完成上述练习的基础上 编写一个程序进行验证。
    • 会,一个进程所有的文件描述符都存储在[FDTABLE, FILEBASE)这一地址空间中。在 fork 函数执行时,会将这父进程页表中映射一部分地址的页表项拷贝到子进程的页表中,因此 fork 前后的父子进程会共享文件描述符和定位指针。

Thinking 5.6

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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; // 指向文件所属的文件目录
    char f_pad[BY2FILE - MAXNAMELEN - (3 + NDIRECT) * 4 - sizeof(void *)]; // 填充字节,对齐 256
    } __attribute__((aligned(4), packed));

    #define FILE2BLK (BY2BLK / sizeof(struct File)) // 一个磁盘块中最多能存储 16 个文件控制块

    struct Fd { // 文件描述符,在寻找访问,打开时需要使用
    u_int fd_dev_id; // 文件所在设备的设备号
    u_int fd_offset; // 文件偏移量
    u_int fd_omode; // 文件打开模式
    };

    struct Filefd { // 文件描述符和文件控制块的映射
    struct Fd f_fd;
    //Filefd 结构体的第一个成员就是 Fd,因此指向 Filefd 的指针同样指向这个 Fd 的起始位置,故可以进行强制转换。
    u_int f_fileid;
    struct File f_file; // 对应的文件控制块
    };

Thinking 5.7

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

    • ENV_CREATE(user_env)和 ENV_CREATE(fs_serv)都是异步消息,由 init()发出创建消息后,init()函数即可返回执行后续步骤,由 fs 和 user 线程执行自己的初始化工作。
    • fs 线程初始化 serv_init()和 fs_init()完成后,进入 serv()函数等待,直到收到 User 线程的 ipc_send(fsreq)请求被唤醒。User 线程向 fs 线程 ipc_send(fsreq)发送请求为同步消息,发送后自身进入阻塞 ENV_NOT_RUNNABLE 等待被唤醒的 fs 线程服务结束时 ipc_send(dst_va), 用户线程接收到数据后继续运行,此后 fs 线程进入阻塞,等待下次被用户唤醒。

难点分析

Exercise 5.1

  • sys_write_dev 和 sys_read_dev 中都要注意检查[pa, pa+len)全部范围的地址有效性
  • 注意 memcpy 的 src 和 dst, 设备地址永远为物理地址 +KSEG1,因为提前映射到 KSEG1 段虚拟地址

Exercise 5.3

  • 注意每次通信需要发送的内容和地址,以及数据结构需要的指针地址,提前设定临时变量

Exercise 5.4

  • 位图数据结构为 int 数组,一个 int 可以表示 32 个位,能存 32 个 block 是否空闲的信息,检查或修改某一个块的位图时,需要通过位运算找到 int 中对应位。

Exercise 5.7

  • 0x10000000-0x4fffffff 4G 虚拟内存,即缓存区
  • 通过 syscall_mem_alloc 与 syscall_mem_unmap 建立与取消虚拟地址和磁盘地址的映射关系
  • 注意取消映射时,如果块中有数据并且被修改,则需要在取消映射前将数据写回磁盘

Exercise 5.9

  • Filefd 结构体的第一个成员就是 Fd,因此指向 Filefd 的指针同样指向这个 Fd 的起始位置,故可以进行强制转换。
  • 在文件描述符链接上文件后,转换成 Filefd,完成对文件描述符的更多的描述,如链接的 f_file 和对应的 f_fileid

实验难点

  • 各个层级中函数调用以及使用 ipc 进行信息传递。比如打开文件函数 open 中调用 fsipc_open 调用 fsipc 再调用 ipc 传输
  • 多级目录文件结构的建立,直接指针,间接指针,以及其中位图、文件描述符的相应作用