思考题

Thinking 5.1

  • 引发问题:外设数据更新在缓存写入之后,因此若通过kseg0读入,会导致缓存部分数据在外设数据更新之后写入,导致数据丢失。
  • 设备区别:串口设备访问频繁,IDE磁盘访问频率低,更像cache和内存的区别。

Thinking 5.2

  • 文件结构体的定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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));

    磁盘块容量4KB,每个文件控制块f_pad[i]对齐为256B,因此一个磁盘有16个文件控制块。

  • 一个目录最多含1024个磁盘块,因此最多含16384个文件。

  • 一个文件最多含1024个磁盘块,一次最多含16384个文件。

Thinking 5.3

  • 块缓存地址范围是[0x10000000, 0x50000000),总共1GB。

Thinking 5.4

  • DISKMAP:磁盘起始地址
  • DISKMAX:磁盘存储最大大小
  • BLOCK_SIZE:块大小
  • SECT_SIZE:磁盘每个扇区大小
  • MAXNAMELEN:文件名最大长度
  • MAXPATHLEN:路径名最大长度
  • MAXFILESIZE:文件最大大小
  • NDIRECT:直接指针数量
  • NINDIRECT:间接指针数量
  • struct File:文件结构体定义

Thinking 5.5

  • 进程所有文件描述符和定位指针均存储在某一用户空间范围内,因此调用fork()后父子进程会共享文件描述符和定位指针。

  • 测试代码:(参考往届学长代码)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include "lib.h"
    #include "printk.h"

    void test()
    {
    int r, fdnum, n;
    char buf[200];
    fdnum = open("/newmotd", O_RDWR | O_ALONE);
    if ((r = fork()) == 0)
    {
    n = read(fdnum, buf, 5);
    printk("child processing's value is %s\n", buf);
    }
    else
    {
    n = read(fdnum, buf, 5);
    printk("father processing's value is %s\n", buf);
    }
    }

Thinking 5.6

  • 文件控制块结构体、文件描述符结构体、文件内容结构体代码定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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 *)];
    //为使一个磁盘块含有证书个文件结构体,填充剩余字节
    };

    struct Fd {
    u_int fd_dev_id; //文件对应设备编号
    u_int fd_offset; //文件指针指向的地址
    u_int fd_omode; //文件的打开方式
    };

    struct Filefd {
    struct Fd f_fd; //文件描述符
    u_int f_fileid; //文件全局编号
    struct File f_file; //文件控制块
    };
  • 文件控制块File:主要用于存储文件具体信息,如文件名、大小、类型等。

  • 文件描述符Fd:主要用于记录已打开文件当前的状态,方便直接对文件进行申请和操作等。

  • 文件Filefd:主要用于存储更多的文件信息,包含文件描述符和文件控制块。在使用中有时会将Fd*类型指针强制转化为Filefd*类型指针,从而获取更多的文件信息。

Thinking 5.7

  • 图中主要有两种箭头:

    • 黑三角黑实线箭头:同步消息
    • 开三角黑虚线箭头:返回消息
  • 具体流程为发送方发出同步消息,并暂停活动等待返回消息,接收者获得消息并处理无误后发送返回消息。不同进程间的消息发送接收主要通过IPC实现,具体是调用fsipc()函数。

难点分析

本次实验中,我遇到的难点主要有以下几点:

  • 读写设备信息:本次实验最重要的函数是设备信息的获取与修改。根据传入的信息长度len判断是否符合标准,不符合标准需要返回-E_INVAL报错;再判断当前准备访问的物理地址是否处于设备信息存储物理地址范围内;没有问题后使用iowrite()、ioread()memcpy()函数进行数据的更改。
  • 用户态下驱动内核态对磁盘读写:用户态下对磁盘读写操作首先需要设置temp,对NSECTLBALLBAMLBAHDEVICESTATUS寄存器进行更改,写入相应数值。再将MALTA_IDE_DATA写入对应的dst或读入对应的src位置。
  • 创造文件create_File()操作:本次实验中,需要实现创造文件的函数。这个函数本身实现难度并不是很大,主要是对于struct File结构体定义的理解全面性。具体操作包含将块序号blocknum赋予文件直接指针或间接指针对应的数据,根据文件名称是否为空判断当前地址的文件是否被使用。如果存在未使用的文件,就返回当前文件;如果不存在,就新申请一个未使用的块并返回。

实验体会

Lab5主要让我们学习有关文件创造与系统使用的问题,了解MIPS系统中虚拟地址与物理地址的对应关系、进行设备读写并尝试通过设备读写更改文件内容。本次实验涉及到的知识点很多很新,同时也有大量未知的需要自己阅读的宏定义和函数(在不知道使用目的与形参函数的含义情况下上手难度极大),继承前三个lab的函数与知识,逻辑清晰,但实现难度较大,也花费了我很长一段时间来完成。程序完成后的debug也非常痛苦,主要问题是不知道哪个函数出现了问题(最终通过了print大法找出),并且有一些地方发现多种看似矛盾的写法都可以正确通过测试。也十分感谢那些写博客的学长(虽然发现了两篇有所不同的博客,也没有尝试是否都是正确的),看了他们的博客后真的是茅塞顿开。实验越往后走,陌生的东西越多,还是要摆正心态,世上无难事,只要肯攀登。