BUAA OS Lab5-2分析

BUAA OS Lab5-2分析

前言

说实话,当我做完Lab5-1,我以为Lab5仅仅如此,后面又看到Lab5-2的指导书部分内容比较少,使我更坚信了Lab5是一个轻松的Lab,然而当我读完指导书,对后面几个exercise无从入手时,我才知道我错了。。。

Lab5的代码填写量非常少,但是需要自己阅读数量巨大的代码,同时还要理解各个文件的关系。我尝试着梳理了一下Lab5中几个文件和主要函数的调用关系,尽量从一个比较宏观的视角来看待整个文件系统,希望能够对大家理解Lab5有帮助。

当然,其中很多内容仅仅是基于我自己的理解,可能有错误之处,希望能够谅解并指正!

一、总览

我们实现的文件系统主要由三部分组成:用户进程部分user、服务进程部分serv、底层部分fs。这些部分之间具有明显的抽象层次,每一部分都是对较低一个部分的抽象和封装。其中,用户进程部分是最高级的抽象。每个部分由于其抽象程度不同,文件的抽象形式也不同,因此需要比较清楚地理解文件在各个部分的抽象形式。我们从高级抽象一步步向下展开。

(一)用户进程 user

用户进程部分封装了一系列常见的操作文件的接口(user/fd.c 和部分user/file.c),如open、close、read、write、seek等,这些是最高级的抽象。实际上,这些操作的对象都是文件描述符,因为在用户进程中,文件的抽象形式就是文件描述符Fd。这些顶级抽象接口需要依赖低级一些的抽象(user/file.c),因为他们会调到对应的文件控制符Fd中的dev_id的dev的对应函数,在文件系统中也就是file_close、file_write等等。

需要注意的是,该部分文件的抽象形式就是文件描述符Fd(user的文件描述符Fd来源于serv通过ipc的页面共享ffd),因此内存空间中有专门的地址存放文件描述符,也就是FDTABLE,这里可以看成一个数组,每个Fd占一个Page大小,可以直接用fdnum作为下标获得;此外,文件的具体数据存在FILEBASE部分的内存空间,每个文件的数据最多占PDMAP大小,也可以实现fdnum到文件数据的一一映射(FILEBASE + PDMAP * fdnum)。

struct Fd {
    u_int fd_dev_id;
    u_int fd_offset; // 定位指针
    u_int fd_omode; // 打开形式
};

struct Dev devfile = {
    .dev_id =   'f',
    .dev_name = "file",
    .dev_read = file_read,
    .dev_write =    file_write,
    .dev_close =    file_close,
    .dev_stat = file_stat,
};

而这些file_xxx中,file_open和file_close需要涉及到和文件服务进程的交互。因为当打开一个文件时,需要从服务进程中读取相应的文件描述符到用户空间,此时要调用fsipc_open;而当file_close时,需要fsipc_close来写回服务进程和磁盘。当然在其他的file_xxx,如file_remove,file_sync中,由于这些函数直接涉及了文件内容的修改,因此需要写回,从而同样要调用fsipc_xxx来与服务进程交互。

那么,fsipc_xxx又干什么呢?fsipc_xxx其实就是找到相应的文件描述符Fd(毕竟这时还在用户进程中),然后构造相应类别的请求req,再通过ipc将该req发送给服务进程(其实就是把req的页面共享给服务进程),最后通过ipc_recv从服务进程中收到相应的结果,以及一些额外的页面映射,如对于fsipc_open,会将服务进程中的文件描述符共享映射给用户空间的文件描述符;fsipc_map则会将服务进程中相应的Block映射给用户进程。

此外,虽然文件的抽象形式是Fd,但是在与serve交互时,会把整个Filefd映射过来,因此可以直接用一个Filefd的指针指过去,从而获取更多Filefd的信息。

(二)服务进程 serve

首先我们需要明确,文件在服务进程抽象存在形式是Open,其中有一个Filefd的指针,也存储了文件的相关信息。而在fs/serv.c中,有一个Open的数组opentable,这正是服务进程中的关键。

其次需要明确,文件的数据在服务进程中是以块缓存的形式存储的(也就是Block和va的映射)。

struct Open {
    struct File *o_file;    // 对应的文件控制块
    u_int o_fileid;     // 文件id(可以看做当前打开的文件,每个都有个id)
    int o_mode;     // 打开形式
    struct Filefd *o_ff;    // Filefd
};
struct Filefd {
    struct Fd f_fd; // 文件描述符
    u_int f_fileid; // 文件id(同上)
    struct File f_file; // 文件控制块
};
struct Fd {
    u_int fd_dev_id;
    u_int fd_offset; // 定位指针
    u_int fd_omode; // 打开形式
};

[lab5-快缓存示意图]

服务进程中除了对Open最基本的操作,如open_alloc和open_lookup外,最主要的部分就是各个serve_xxx函数。这些正是接收到用户进程请求时需要进行的操作。serve_xxx首先是从请求req中提取有关信息,如fileid等,从而找到对应的Open(如果是serve_open则是分配Open)。找到对应的Open,就相当于找到了服务进程中对应的文件,这时候就可以调用底层的函数(fs/fs.c)中来把相应的Block从磁盘读入服务进程的内存空间中(也就是上图的内存分布)或对相应的Block进行操作。

(三)底层部分 fs

上面我们提到,服务进程需要通过调用底层函数来操作文件对应的Block。而在fs/fs.c中,也就是本文说的底层部分,这里封装了许多可以直接根据文件修改对应的Block的函数,如file_get_block、file_flush、file_set_size等,这些使得我们可以很方便地迅速获得文件的某一Block,或直接对该文件的Block进行操作,以及和磁盘的交互(如磁盘的读取与回写)。

在这一部分,文件的抽象形式是文件控制块File,文件数据则是存在Block中。

需要注意的是,这部分其实也属于文件系统的服务进程,只是在抽象层面上划分开了,因此这部分实现的Block的读入等同样修改的是服务进程的内存空间。

二、函数关系

lab5涉及到的函数数量巨大,这里只给出部分主要函数之间的调用关系以及对应的功能。从整个文件系统的更高层面上看待这些函数之间的协作、封装,可以避免纠结于某个文件或者某个函数。或许先从宏观上了解了整个结构,再去了解具体的细节实现,能够更好地理解文件系统。

当然,如果要深入理解每个函数的功能,还是需要好好读代码的(尽管一下子读这么多代码确实挺恶心的

该图的结构参考了博客:https://hyggge.github.io/2022/05/30/OS-Lab5函数解读/

其中,箭头上的注释主要表示调用这个函数的主要作用是什么,或许可以对理解函数的功能起到一定帮助。

三、其他

这里整理了一些可能比较难理解的点(慢慢补充)

  1. 在user空间中,为什么一个Fd占一个Page大小:因为serv在通过ipc给user共享映射Fd时只能映射一整个Page
  2. 在fsipc_map通过ipc共享页面的时候,共享的是Block还是Page:既是Block也是Page,因为一个Block的大小和一个Page的大小一样(BY2BLK = BY2PG = 4KB)
  3. user在用户空间修改了文件内容比如write,serv和磁盘怎么同步:文件数据是user和serv共享映射的(在user/file.c的open函数中就通过fsipc_map共享了),自然serv能同步;serv是一个缓存,flush时再写回磁盘
  4. user中文件内容在哪:在FILEBASE段,每个文件有PDMAP的空间,同上,是和serv共享映射的
posted @ 2022-06-07 10:20  Booooomb  阅读(263)  评论(0编辑  收藏  举报