lab5——文件系统
思考题
Thinking 5.1
查阅资料,了解 Linux/Unix 的 /proc 文件系统是什么?有什么作用? Windows 操作系统又是如何实现这些功能的?proc 文件系统这样的设计有什么好处和可以改进的地方?
/proc文件系统是一种特殊的,由软件创建的(伪)文件系统,内核使用它向外界导出信息,/proc系统只存在内存当中,而不占用外存空间。/proc下面的每个文件都绑定于一个内核函数,用户读取文件时,该函数动态地生成文件的内容。
与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系 统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变 内核的运行状态。
Windows,分盘,每个驱动器有自己的根目录,形成的是多个树并列的结构。
Linux,只有一个根目录 / ,所有东西都是从这开始
Thinking 5.2
如果我们通过 kseg0 读写设备,我们对于设备的写入会缓存到 Cache 中。通过 kseg0 访问设备是一种错误的行为,在实际编写代码的时候这么做会引发不可预知的问题。请你思考:这么做这会引起什么问题?对于不同种类的设备(如 我们提到的串口设备和 IDE 磁盘)的操作会有差异吗?可以从缓存的性质和缓存刷新的策略来考虑。
因为内核被放在kseg0区域,一般通过cache访问;如果对设备的写入缓存到cache中,就会导致以后想访问内核时却错误访问了写入设备的内容。
Thinking 5.3
一个磁盘块最多存储 1024 个指向其他磁盘块的指针,试计算,我们 的文件系统支持的单个文件的最大大小为多大?
4KB*1024=4MB
Thinking 5.4
查找代码中的相关定义,试回答一个磁盘块中最多能存储多少个文件控制块?一个目录下最多能有多少个文件?
1个磁盘块中最多能存储16个文件控制块,一个目录下最多能有1024*16=16384个文件
Thinking 5.5
请思考,在满足磁盘块缓存的设计的前提下,我们实验使用的内核支持的最大磁盘大小是多少?
0x6f3fd000
Thinking 5.6
如果将 DISKMAX 改成 0xC0000000, 超过用户空间,我们的文件系统还能正常工作吗?为什么?
不能,因为缓存磁盘块的时候可能会把内核的内容覆盖掉,导致系统运行异常
Thinking 5.7
阅读 user/file.c ,你会发现很多函数中都会将一个 struct Fd * 型的 指针转换为 struct Filefd * 型的指针,请解释为什么这样的转换可行。
因为在结构体Filefd中储存的第一个元素就是struct Fd*,因而对于相匹配的一对struct Fd和struct Filefd,他们的指针实际上指向了相同的虚拟地址,所以可以通过指针转化访问struct Filefd中的其他元素
Thinking 5.8
请解释 Fd, Filefd, Open 结构体及其各个域的作用。比如各个结构体 会在哪些过程中被使用,是否对应磁盘上的物理实体还是单纯的内存数据等。说明 形式自定,要求简洁明了,可大致勾勒出文件系统数据结构与物理实体的对应关系 与设计框架。
Fd结构体用于表示文件描述符,fd_dev_id表示文件所在设备的id,fd_offset表示读或者写文件的时候,距离文件开头的偏移量,fd_omode用于描述文件打开的读写模式。它主要用于在打开文件之后记录文件的状态,以便对文件进行管理/读写,不对应物理实体,只是单纯的内存数据。
Filefd结构体是文件描述符 和 文件的组合形式,f_fd记录了文件描述符,f_fileid记录了文件的id,f_file则记录了文件控制块,包含文件的信息以及指向储存文件的磁盘块的指针,对应了磁盘的物理实体,也包含内存数据。
Open结构体在文件系统进程用用于储存文件相关信息,o_file指向了对应的文件控制块,o_fileid表示文件id用于在数组opentab中查找对应的Open,o_mode记录文件打开的状态,o_ff指向对应的Filefd结构体。
Thinking 5.9 阅读serv.c/serve函数的代码,我们注意到函数中包含了一个死循
环for (;😉 {...},为什么这段代码不会导致整个内核进入panic 状态?
因为再调用ipc_recv的时候这个进程会等待其他进程向他发送请求文件系统调用再继续执行,如果没有接收到请求就会在这里一直等待,因此不会自己执行死循环
难点
Exercise5.1
请根据lib/syscall_all.c中的说明,完成sys_write_dev函数和sys_read_dev 函数,并且在 user/lib.h,user/syscall_lib.c 中完成用户态的相应系统调用的接口。
编写这两个系统调用时需要注意物理地址、用户进程虚拟地址同内核虚拟地址 之间的转换。 同时还要检查物理地址的有效性,在实验中允许访问的地址范围为: console: [0x10000000, 0x10000020), disk: [0x13000000, 0x13004200), rtc: [0x15000000, 0x15000200)
,当出现越界时,应返回指定的错误码。
重点注意要覆盖所有非法情况,但也不要把合法的边界误判了!合法之后直接bcopy即可
(exp:dst+len = 0x10000020是合法的 并没有使用0x10000020的地址写东西)
Exercise 5.2
参考内核态驱动,完成 fs/ide.c 中的 ide_write 函数,以及 ide_read 函数,实现对磁盘的读写操作
注意理解这里需要我们进行哪些操作!每0x200大小的读写操作都需要把所有流程执行一遍!
读取磁盘的时候:
写入磁盘号,写入磁盘读取位置偏移,写入0表示开始读取,读出操作结果(是否读取成功),从缓冲区读取数据
写磁盘的时候:
写入磁盘号,写入磁盘写入位置偏移,数据写入缓冲区,写入1表示开始写,读出操作结果
Exercise 5.3
文件系统需要负责维护磁盘块的申请和释放,在回收一个磁盘块时,需 要更改位图中的标志位。如果要将一个磁盘块设置为 free,只需要将位图中对应的 位的值设置为 1 即可。请完成 fs/fs.c 中的 free_block 函数,实现这一功能。同时 思考为什么参数 blockno 的值不能为 0 ?
每32个磁盘的标志位存在bitmap上的一个32位数上,所以根据blockno除以32求出对应数组的第几位,再对32取模得到是32位上的第几位并且置1即可
Exercise 5.4
请参照文件系统的设计,完成 fsformat.c 中的 create_file函数,并按 照个人兴趣完成 write_directory 函数(不作为考察点),实现将一个文件或指定 目录下的文件按照目录结构写入到 fs/fs.img 的根目录下的功能。关于如何创建二 进制文件的镜像,请参考 fs/Makefile。 在实现的过程中,你可以将你的实现同我们给出的参考可执行文件tools/fsformat 进行对比。具体来讲,你可以通过 Linux 提供的 xxd 命令将两个 fsformat 产生的二 进制镜像转化为可阅读的文本文件,手工进行查看或使用 diff 等工具进行对比。
分类讨论多种情况
如果还没有一个block被使用,创建一个新的
如果已经有了,那么遍历寻找一个空的文件控制块位置(注意直接和间接访问的区别)
如果遍历之后没有找到,创建一个新的
Exercise 5.5
fs/fs.c 中的 diskaddr 函数用来计算指定磁盘块对应的虚存地址。完成 diskaddr 函数,根据一个块的序号 (block number),计算这一磁盘块对应的 512 bytes 虚存的起始地址。(提示:fs/fs.h 中的宏 DISKMAP 和 DISKMAX 定义了磁盘映射虚存的地址空间)。
直接用DISKMAP + BY2BLK * blockno即可
Exercise 5.6
实现 map_block 函数,检查指定的磁盘块是否已经映射到内存,如果没有,分配一页内存来保存磁盘上的数据。对应地,完成 unmap_block 函数,用于解除磁盘块和物理内存之间的映射关系,回收内存。(提示:注意磁盘虚拟内存地址 空间和磁盘块之间的对应关系)。
map中:用block_is_mapped(blockno)检查是否已经映射到内存并得到虚拟内存va,如果没有则调用syscall_mem_alloc分配相应空间
unmap中:用block_is_mapped(blockno)检查是否已经映射到内存并得到虚拟内存va,如果块不是free的并且被修改了,write_block(blockno),写回。调用syscall_mem_unmap(0,va);解除映射关系
Exercise 5.7
补全 dir_lookup 函数,查找某个目录下是否存在指定的文件。(提示: 使用file_get_block可以将某个指定文件指向的磁盘块读入内存)。
先计算目录下有多少个块,然后依次遍历用file_get_block(dir,i,&blk)得到的每个块,查看里面的文件控制块并对比文件名字是否是要查找的,如果是返回0
Exercise 5.8
完成 user/file.c 中的 open 函数。(提示:若成功打开文件则该函数返 回文件描述符的编号)。
分配一个新的文件描述符Fd,然后根据path用fsipc_open打开对应文件。从中读取文件相关的信息,之后分配内存加载文件的内容,最后返回文件描述符的标号
Exercise 5.9
参考 user/fd.c 中的 write 函数,完成 read 函数
完全仿照write即可,通过fd_lookup和dev_lookup查找对应fd和dev,然后检查mode是否合格,根据debug决定是否输出,从seek标记的位置开始读取文件,之后修改文件中的标记位置。最后给读出的字符串加上结束的\0
Exercise5.10
文件user/fsipc.c中定义了请求文件系统时用到的IPC操作,user/file.c 文件中定义了用户程序读写、创建、删除和修改文件的接口。完成 user/fsipc.c 中的 fsipc_remove函数、user/file.c中的remove函数,以及fs/serv.c中的serve_remove 函数,实现删除指定路径的文件的功能。*
fsipc_remove先检查path是否合格,然后创建Fsreq_remove结构体并传递path进去,最后用fsipc发送请求
serve_remove复制出来path之后调用file_remove,把对应的结果用ipc发送回去
remove函数直接调用fsipc_remove
体会与感想
相比起lab4的地狱难度,lab5的填写工作确实要简单一些,不过这并不代表着lab5就更容易理解了。只是很多相关调用都是已经写好的,想要看懂整个机制还是需要阅读大量的代码进行自己的思考。虽然lab5课下测试拿了满分,代码也研究了很长时间,但是还是不敢说就把文件系统的整个过程弄明白了。
感觉助教写的补充指导书对于帮助理解还是起了至关重要的作用。仅仅依靠大指导书的内容,对于该填写什么,填写的东西是干什么用的完全一头雾水。感谢助教付出的辛苦劳动。