OS-lab5实验报告

lab5实验报告

一、实验思考题

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

  • Linux系统上的/proc目录是一种文件系统,即proc文件系统。与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统)。
  • /proc存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。
  • Windows系统通过Win32 API的函数调用来实现这些功能。
  • 这样的设计将对内核信息的访问交互抽象成了对文件的访问修改,简化了交互过程。缺点在于其长期驻留内存,占据内存空间。

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

  • 缓存内存中映射的外部设备内容时,可能外部设备更新后对内存进行了更新,但已经被缓存的部分没有被更新。
  • 这种错误对于磁盘概率较小,串口设备则很容易出现。

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

  • 4KB * 1024 = 4MB

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

  • 一个磁盘块4KB,一个文件控制块256B,一个磁盘块中最多能够存储4KB / 256B = 16个文件控制块。
  • 一个目录最多能够指向1024个磁盘块,每个磁盘块能够存储16个文件控制块,一个目录下最多能够有1024 * 16 = 16K个文件。

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

  • 最大磁盘大小为DISKMAX = 0x40000000,即1GB。

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

  • 不能正常工作
  • 因为可能出现写磁盘块时覆盖掉内核的内容的情况,导致操作系统运行异常。

Thinking 5.7
阅读 user/file.c,思考文件描述符和打开的文件分别映射到了内存的哪一段空间。

  • 文件描述符被fd_alloc函数映射到FDTABLEFILEBASE之间的空间内。

  • 文件描述符与打开的文件保存在同一个Filefd当中,位于文件描述符所在的页面。

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

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

  • Filefd结构体中第一个元素就是一个Fd结构体,所以做这样的强制转换,就相当于对一个Filefd里面的Fd进行赋值。

  • 按照C语言的内存分配,只要能够从指针指向的地址取出合法的元素,就可以进行强制类型转换。

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

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

  • Fd结构体

    // file descriptor
    struct Fd {				//文件描述符,用来描述一个文件的基本信息,独占一页
    	u_int fd_dev_id;	//外设id,表明外设类型
    	u_int fd_offset;	//表示读或者写文件的时候,距离文件开头的偏移量
    	u_int fd_omode;		//描述文件打开的读写模式,如只读,只写,读写
    };
    
  • Filefd结构体

    // file descriptor + file
    struct Filefd {			//描述一个文件的详细信息,位置就在Fd独占的一页
    	struct Fd f_fd;		//文件描述符
    	u_int f_fileid;		//文件的id
    	struct File f_file;	//对应文件的文件控制块
    };
    
  • Open结构体

    struct Open {
    	struct File *o_file;	//指向该文件的文件控制块的指针
    	u_int o_fileid;			//文件的id
    	int o_mode;				//描述文件打开的读写模式,如只读,只写,读写
    	struct Filefd *o_ff;	//打开位置的偏移量
    };
    

Thinking 5.10
阅读serv.c/serve函数的代码,我们注意到函数中包含了一个死循环for (;😉 {...},为什么这段代码不会导致整个内核进入 panic 状态?

  • 这个serve是一个用户进程,相当于提供服务的“后台进程”,只有接收到请求才会执行死循环,如果没有接收到请求就会在这里一直等待,因此不会自己执行死循环,不会导致整个内核进入panic状态。

Thinking 5.11

观察 user/fd.h 中结构体 Dev 及其调用方式。

struct Dev {
	int dev_id;
	char *dev_name;
	int (*dev_read)(struct Fd *, void *, u_int, u_int);
	int (*dev_write)(struct Fd *, const void *, u_int, u_int);
	int (*dev_close)(struct Fd *);
	int (*dev_stat)(struct Fd *, struct Stat *);
	int (*dev_seek)(struct Fd *, u_int);
};

综合此次实验的全部代码,思考这样的定义和使用有什么好处。

  • 这样的定义和使用可以把对文件的操作抽象到一个结构体当中,使用时只需要把相应操作的函数指针赋值不同的函数即可。

  • //  user/fd.c
    struct Dev devcons = {
    	.dev_id =	'c',
    	.dev_name =	"cons",
    	.dev_read =	cons_read,
    	.dev_write =	cons_write,
    	.dev_close =	cons_close,
    	.dev_stat =	cons_stat,
    };
    //  user/file.c
    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,
    };
    //  user/pipe.c
    struct Dev devpipe = {
    	.dev_id =	'p',
    	.dev_name =	"pipe",
    	.dev_read =	piperead,
    	.dev_write =	pipewrite,
    	.dev_close =	pipeclose,
    	.dev_stat =	pipestat,
    };
    

二、实验难点图示

难点一:IDE磁盘驱动——驱动程序编写

这里比较难以理解的是我们都需要执行哪些操作。

读取磁盘的过程:

  • 写入磁盘号
  • 写入磁盘读取位置偏移
  • 写入read_value(0)表示开始读取
  • 读出操作结果status
  • 从缓冲区读取数据

写入磁盘的过程:

  • 写入磁盘号
  • 写入磁盘写入位置偏移
  • 数据写入缓冲区
  • 写入write_value(1)表示开始写入
  • 读出操作结果status

需要注意的是,每0x200大小的读写操作都需要走一遍全部的流程。

难点二:文件系统结构——诸多结构体的含义

梦回Lab2。

在实验思考题部分已经写明了Fd, Filefd, Open三个结构体的含义,此处不再赘述。

struct Block {
    uint8_t data[BY2BLK];
    uint32_t type;
} disk[NBLOCK];

Block 结构体也就是磁盘中的磁盘块,data是磁盘块中存储的数据,type则是磁盘块的类型。而这个disk[NBLOCK]就是磁盘块数组,其实也就是磁盘。

struct Super {
   u_int s_magic;		// Magic number: FS_MAGIC
   u_int s_nblocks;	// Total number of blocks on disk
   struct File s_root;	// Root directory node
};

Super结构体是磁盘块的一种——超级块,s_magic是魔数,一个常量,用于识别该文件系统,s_nblocks记录本文件系统有多少个磁盘块,具体到实验中即1024,s_root是根目录。

struct File {
   u_char f_name[MAXNAMELEN];	// filename
   u_int f_size;			// file size in bytes
   u_int f_type;			// file type
   u_int f_direct[NDIRECT];
   u_int f_indirect;

   struct File *f_dir;		// the pointer to the dir where this file is in, valid only in memory.
   u_char f_pad[BY2FILE - MAXNAMELEN - 4 - 4 - NDIRECT * 4 - 4 - 4];
};

File结构体即文件结构体,f_name是文件名,f_size是文件大小,f_type是文件类型,f_direct是文件的直接指针,存储了NDIRECT(10)个磁盘块的编号,f_indirect是文件的间接指针,存储了1个磁盘块编号,而这个编号对应的磁盘块存储了NINDIRECT(1024)个磁盘块的编号,f_dir是文件所在的文件夹。

难点三:文件系统的用户接口——文件系统服务

这部分很难以理解的点在于如何通过IPC连接起用户进程和文件系统。

Open操作为例,整个流程的流程图如下(不一定理解的正确Orz)


三、体会与感想

因为上次实验被背刺,所以这次没有敢掉以轻心,并且做好了十足的心理准备,但是还是很难啊Orz。虽然本次实验在代码填写方面没有那么困难,但是在代码理解方面的难度更大了。

之所以说代码的理解难度更大,是因为对代码的理解不仅限于对文件系统这一部分的理解,还包括对之前几次实验的理解,尤其是Lab4IPC部分,IPC怎样连接起用户进程和文件系统,我到现在也不觉得我真正搞明白了,函数之间的调用太过复杂,很头疼。反正是稀里糊涂地就通过了课下测试。

完成了Lab5感觉长出了一口气,不知不觉OS实验也就要结束了,终于可以不再被这一堆谜语代码折磨了。

posted @ 2021-06-02 10:19  神樂坂清清  阅读(474)  评论(0)    收藏  举报