Update chapter6_filesystem.md

pull/16/head
Zhiyuan Shao 2 years ago
parent 5acc78007d
commit 3b5efb7b28

@ -33,15 +33,15 @@
## 6.1 实验4的基础知识
本章我们将首先以Linux的文件系统为例介绍文件系统的基础知识接着讲述 riscv-pke操作系统内核的文件系统设计然后开始实验五的几个实验以加深读者对文件系统底层逻辑的理解。
本章我们将首先以Linux的文件系统为例介绍文件系统的基础知识接着讲述 riscv-pke操作系统内核的文件系统设计然后开始对PKE实验4的基础实验进行讲解以加深读者对文件系统底层逻辑的理解。
<a name="filesystem"></a>
### 6.1.1 文件系统概述
文件系统是操作系统用于存储、组织和访问计算机数据的方法它使得用户可以通过一套便利的文件访问接口来访问存储设备常见的是磁盘也有基于NAND Flash的固态硬盘或分区上的文件。操作系统中负责管理和存储文件信息的**软件机构**称为文件管理系统,简称**文件系统**。 文件系统所管理的最基本的单位,是有具体且完整逻辑意义的“**文件**”,文件系统实现了对文件的按名(即文件名)存取。为避免“重名”问题,文件系统的常用方法是采用树型目录,来辅助(通过加路径)实现文件的按名存取。存储在磁盘上,实现树型目录的数据结构(如目录文件等),往往被称为“**元数据**”meta-data。实际上文件系统中辅助实现对文件进行检索和存储的数据都被称为元数据。从这个角度来看文件系统中对磁盘空间进行管理的数据以及可能的缓存数据也都是重要的元数据。
文件系统是操作系统用于存储、组织和访问计算机数据的方法它使得用户可以通过一套便利的文件访问接口来访问存储设备常见的是磁盘也有基于NAND Flash的固态硬盘或分区上的文件。操作系统中负责管理和存储文件信息的**软件机构**称为文件管理系统,简称文件系统。 文件系统所管理的最基本的单位,是有具体且完整逻辑意义的“文件”,文件系统实现了对文件的按名(即文件名)存取。为避免“重名”问题,文件系统的常用方法是采用树型目录,来辅助(通过加路径)实现文件的按名存取。存储在磁盘上,实现树型目录的数据结构(如目录文件等),往往被称为“**元数据**”meta-data。实际上文件系统中辅助实现对文件进行检索和存储的数据都被称为元数据。从这个角度来看文件系统中对磁盘空间进行管理的数据以及可能的缓存数据也都是重要的元数据。
虽然功能都是对文件进行管理,但不同的文件系统往往采用不同种类的元数据来完成既定功能。对不同类型元数据的采用往往也区分了不同类型的文件系统。例如Linux中的ext系列文件系统Windows中广泛采用的fat系列文件系统,以及NTFS文件系统等。虽然完成的功能一样但它们在具体实现上却也存在巨大差异。由于PKE实验采用了spike模拟器它具有和主机进行交互的htif接口所以在实验四,我们将自然接触到一类很特殊的文件系统**hostfs**Host File System。它实际上是位于主机上的文件系统PKE通过定义一组接口使得在spike所构造的虚拟risc-v机器上运行的应用能够对主机上文件进行访问。另外我们将分配一段内存作为我们的“磁盘”即RAM Disk并在该磁盘上创建一个简单的名称为**RFS**Ramdisk File System的文件系统。
不同的文件系统往往采用不同种类的元数据来完成既定功能。对不同类型元数据的采用往往也区分了不同类型的文件系统。例如Linux中的ext系列文件系统Windows中广泛采用的fat系列文件系统NTFS文件系统等。虽然完成的功能一样但它们在具体实现上却也存在巨大差异。由于PKE实验采用了Spike模拟器它具有和主机进行交互的HTIF接口所以在PKE实验4我们将自然接触到一类很特殊的文件系统hostfsHost File System。它实际上是位于主机上的文件系统PKE通过定义一组接口使得在Spike所构造的虚拟RISC-V机器上运行的应用能够对主机上文件进行访问。另外我们将分配一段内存作为我们的“磁盘”即RAM Disk并在该磁盘上创建一个简单的名称为RFSRamdisk File System的文件系统。
由于有多个文件系统的存在且PKE需要同时对这两类文件系统进行支持。所以在PKE的实验四我们引入了**虚拟文件系统**Virtual File System有时也被称为Virtual Filesystem Switch简称都是**VFS**的概念。VFS也是构建现代操作系统中文件系统的重要概念通过实验四读者将对这一重要概念进行学习。在后面的讨论中我们将依次介绍[PKE的文件系统架构](#pke_fs)、了解[PKE文件系统对进程所提供的接口](#fs_interface)、[虚拟文件系统的构造](#vfs),以及我们自己定义的[RFS文件系统](#rfs)。目标是辅助读者构建对PKE文件系统代码的理解为完成后续的基础和挑战实验做好准备。
@ -49,14 +49,10 @@
### 6.1.2 PKE的文件系统架构
文件系统中存储数据的基本数据单位是文件,然而不同类型的文件系统对于文件在存储设备上的组织方式可能采用不同的物理结构,以及不同的访问接口。 文件系统的种类众多例如PKE系统中的hostfs和RFS而操作系统希望**对用户提供一个统一的接口,隐藏不同文件系统之间的差异**,于是在用户层与文件系统层引入了一个中间层,这个中间层就称为**虚拟文件系统简称VFS** 。
VFS 定义了一组所有文件系统都支持的数据结构和标准接口,这样程序员在访问文件系统时,不需要了解底层文件系统的工作原理,只需要通过 VFS 提供的统一接口进行访问即可。PKE的VFS存在于内核态用户在用户态使用open、read等函数操作文件时总是通过VFS操作处于具体文件系统的文件。
PKE文件系统架构如下图所示图中的RAM DISK在文件系统中的地位等价于磁盘设备在其上“安装”的文件系统就是RFS。特别要注意的一点是在后文讨论RFS时会存在“某种数据结构被保存在磁盘中”这样的表述这通常意味着这种数据结构实际上被保存在上图的RAM DISK中。除了RFS外PKE文件系统通过虚拟文件系统对主机文件系统hostfs进行了支持在riscv-pke操作系统内核上运行的应用程序能够对这两个文件系统中的文件进行访问。
<img src="pictures/lab4.pke_fs_architecture.png" alt="1660396457538" style="zoom: 70%;" />
PKE文件系统的基本架构与Linux的类似部分功能进行了简化对于外存磁盘部分为了教学方便以RAM内存来模拟替代磁盘对磁盘的相关操作都转化成对内存的相关操作。图中的RAM DISK在文件系统中的地位等价于磁盘设备。特别要注意的一点是在后文讨论RFS时会存在“某种数据结构被保存在磁盘中”这样的表述这通常意味着这种数据结构实际上被保存在上图的RAM DISK中。
在PKE系统启动时会在初始化阶段将这两个文件系统进行挂载见kernel/kernel.c文件中对S模式的启动代码的修改
```c
@ -224,7 +220,7 @@ PKE文件系统的基本架构与Linux的类似部分功能进行了简化
78 }process;
```
在进程定义的77行增加了一个proc_file_management指针类型的成员pfile。在实验四每当创建一个进程时都会调用kernel/proc_file.c中定义的函数init_proc_file_management(),来对该进程(将来)要打开的文件进行管理。该函数的定义如下:
我们看到在进程定义的77行增加了一个proc_file_management指针类型的成员pfile。这样每当创建一个进程时都会调用kernel/proc_file.c中定义的函数init_proc_file_management(),来对该进程(将来)要打开的文件进行管理。该函数的定义如下:
```c
41 proc_file_management *init_proc_file_management(void) {
@ -241,9 +237,7 @@ PKE文件系统的基本架构与Linux的类似部分功能进行了简化
52 }
```
可以看到该函数的作用是为将要管理的“打开文件”分配一个物理页面的空间初始当前目录cwd置为根目录初始化打开文件计数42--44行然后初始化所有“已打开”的文件的文件描述符fd46--49行
kernel/proc_file.c文件中还定义了一组接口用于进程对文件的一系列操作。这些接口包括文件打开do_open、文件关闭do_close、文件读取do_read、文件写do_write、文件读写定位do_lseek、获取文件状态do_stat甚至获取磁盘状态do_disk_stat。这些接口都是在应用程序发出对应的文件操作如open、close、read_u等通过user lib到达do_syscall并最后调用的。
该函数的作用是为将要管理的“打开文件”分配一个物理页面的空间初始当前目录cwd置为根目录初始化打开文件计数42--44行然后初始化所有“已打开”的文件的文件描述符fd46--49行。kernel/proc_file.c文件中还定义了一组接口用于进程对文件的一系列操作。这些接口包括文件打开do_open、文件关闭do_close、文件读取do_read、文件写do_write、文件读写定位do_lseek、获取文件状态do_stat甚至获取磁盘状态do_disk_stat。这些接口都是在应用程序发出对应的文件操作如open、close、read_u等通过user lib到达do_syscall并最后被调用的。
这里我们对其中比较典型的文件操作如打开和文件读进行分析。我们先来观察do_open的实现见kernel/proc_file.c
@ -287,7 +281,7 @@ kernel/proc_file.c文件中还定义了一组接口用于进程对文件的
我们看到这个函数会首先对发起进程读取文件的权限进行判断通过判断后会继续通过调用vfs_read来实现对文件的真正的读取动作。最后再将读取到的数据拷贝到参数buf中。
实际上PKE文件系统提供给进程的所有操作最终都是通过调用VFS这一层的功能来最终完成它们的功能的。这里我们鼓励读者继续阅读其他操作如关闭文件、写文件等来观察PKE文件系统的这一特点。接下来我们将对PKE的虚拟文件系统进行介绍。
实际上PKE文件系统提供给进程的所有操作最终都是通过调用VFS这一层的功能来最终完成它们的功能的。这里我们鼓励读者继续阅读其他操作如关闭文件、写文件等来观察PKE文件系统的这一特点。
<a name="vfs"></a>
@ -345,9 +339,9 @@ VFS对具体的文件系统进行了抽象构建出一个通用的文件系
126 };
```
其中需要重点关注的是inum、sb以及i_ops。下面分别介绍这几个字段。
其中需要重点关注的成员inum、sb以及i_ops。下面分别介绍这几个字段。
**inum**inum用于保存一个vinode所对应的存在于磁盘上的disk inode序号。对于存在disk inode的文件系统如RFS而言inum唯一确定了该文件系统中的一个实际的文件。而对于不存在disk inode的文件系统inum字段则不会被使用且该文件系统需要自行提供并维护一个vinode所对应的文件在该文件系统中的“定位信息”VFS必须能够根据该“定位信息”唯一确定此文件系统中的一个文件。这类“定位信息”由具体文件系统创建、使用通过viop函数后文会介绍和释放。在PKE的实现中hostfs就属于这种不存在inode的文件系统。实际上主机上的文件系统同样可能是一种存在disk inode的文件系统。但是由于我们通过HTIF接口对宿主机文件进行访问因此并不关注宿主机上实际文件系统的情况。也就是说我们并没有通过宿主机上的文件系统的disk inode对文件进行访问。在这种情况下hostfs中一个文件的“定位信息”就是一个spike_file_t结构体定义在spike_interface/spike_file.h中
**inum**inum用于保存一个vinode所对应的存在于磁盘上的disk inode序号。对于存在disk inode的文件系统如RFS而言inum唯一确定了该文件系统中的一个实际的文件。而对于不存在disk inode的文件系统如hostfsinum字段则不会被使用且该文件系统需要自行提供并维护一个vinode所对应的文件在该文件系统中的“定位信息”VFS必须能够根据该“定位信息”唯一确定此文件系统中的一个文件。这类“定位信息”由具体文件系统创建、使用通过viop函数后文会介绍和释放。实现上PKE为一个hostfs所设计的 “定位信息”就是一个spike_file_t结构体定义在spike_interface/spike_file.h中
```c
09 typedef struct file_t {
@ -356,17 +350,17 @@ VFS对具体的文件系统进行了抽象构建出一个通用的文件系
12 } spike_file_t;
```
保存了宿主机中一个打开文件的文件描述符,该spike_file_t结构体的地址会被保存在vinode的i_fs_info字段中。
该结构体保存了宿主机中一个打开文件的文件描述符,结构体的地址会被保存在vinode的i_fs_info字段中。
**sb**vinode中的sb用来记录inode所在的文件系统对应的超级块超级块唯一确定了vinode对应的文件系统。后文会对super_block对象进行更详细的介绍。
**sb**vinode中的sb字段是struct super_block类型用来记录inode所在的文件系统对应的超级块超级块唯一确定了vinode对应的文件系统。
**i_ops**i_ops是一组对vinode进行操作的函数调用。需要注意的是在VFS中仅仅提供了vinode的操作函数接口这些函数的具体实现会由下层的文件系统提供。6.1.4.3节会对其进行详细介绍。
**i_ops**i_ops字段是struct vinode_ops类型是一组对vinode进行操作的函数调用。需要注意的是在VFS中仅仅提供了vinode的操作函数接口这些函数的具体实现会由下层的文件系统提供。
例如,对于RFS而言vinode中的其他信息inum、size、type、nlinks、blocks以及addrs都是在VFS首次访问到某个磁盘文件或目录时从其保存在RAM DISK中的disk inode上直接复制得到的。而对于hostfsvinode中的这些数据并不会被使用而只是会设置i_fs_info字段。
对于RFS而言vinode中的其他信息inum、size、type、nlinks、blocks以及addrs都是在VFS首次访问到某个磁盘文件或目录时基于对应的disk inode内容产生的。而对于hostfsvinode中的这些数据并不会被使用而只是会设置i_fs_info字段。
* *dentry*
如果说vinode是文件访问和操作的核心对象那么dentry可以称为在VFS中对文件进行组织和索引的核心对象。dentry即directory entry是VFS中对目录项的抽象该对象在VFS承载了多种功能包括对vinode进行组织、提供目录项缓存以及快速索引支持。dentry同样定义在vfs.h文件中
dentry即directory entry是VFS中对目录项的抽象该对象在VFS承载了多种功能包括对vinode进行组织、提供目录项缓存以及快速索引支持,它的定义在vfs.h文件中
```c
38 struct dentry {
@ -380,15 +374,11 @@ VFS对具体的文件系统进行了抽象构建出一个通用的文件系
与vinode类似dentry同样是仅存在于内存中一种抽象数据类型且一个dentry对应一个vinode由dentry_inode成员记录。由于硬链接的存在一个vinode可能同时被多个dentry引用。另外需要注意的是在VFS中无论是普通文件还是目录文件都会存在相应的dentry。
为了支撑dentry的功能内存中的每一个dentry都存在于**目录树**与**路径哈希表**两种结构之中。
首先所有的dentry会按文件的树形目录结构组织成一颗目录树每个dentry中的parent数据项会指向其父dentry。该目录树结构将内存中存在的dentry及对应的vinode进行了组织便于文件的访问。
其次所有的dentry都存在于一个以父目录项地址和目录项名为索引的哈希链表dentry_hash_table中。将dentry存放于哈希链表中可以加快对目录的查询操作。关于VFS具体是如何利用dentry在内存中构建目录树可以参考下文6.1.4.4节的内容。
为了支撑dentry的功能内存中的每一个dentry都存在于目录树与路径哈希表两种结构之中。首先所有的dentry会按文件的树形目录结构组织成一颗目录树每个dentry中的parent数据项会指向其父dentry。该目录树结构将内存中存在的dentry及对应的vinode进行了组织便于文件的访问。其次所有的dentry都存在于一个以父目录项地址和目录项名为索引的哈希链表dentry_hash_table将dentry存放于哈希链表中可以加快对目录的查询操作。
* *super_block*
super_block对象用来保存一个已挂载文件系统的相关信息。在一个文件系统被挂载到系统中时其对应的super_block会被创建并读取磁盘上的super block信息若存在来对super_block对象进行初始化。对于RFS文件系统而言在访问其中的文件时需要从super_block中获取文件系统所在的设备信息且RFS中数据盘块的分配和释放需要借助保存在s_fs_info成员中的bitmap来完成。而对于hostfs而言由于它并不存super block所以其VFS层的super_block结构主要用来维护hostfs根目录信息。super_block结构定义在vfs.h文件中
super_block结构用来保存一个已挂载文件系统的相关信息。在一个文件系统被挂载到系统中时其对应的super_block会被创建并读取磁盘上的super block信息若存在来对super_block对象进行初始化。对于RFS文件系统而言在访问其中的文件时需要从super_block中获取文件系统所在的设备信息且RFS中数据盘块的分配和释放需要借助保存在s_fs_info成员中的bitmap来完成。而对于hostfs而言它并不存在物理上的super block其VFS层的super_block结构主要用来维护hostfs根目录信息。super_block结构定义在vfs.h文件中
```c
104 struct super_block {
@ -462,7 +452,7 @@ viop函数是VFS与具体文件系统进行交互的重要部分也是VFS能
169 };
```
上述函数接口实际上是一系列的函数指针在VFS中的vinode_ops结构体中限定了这些函数的参数类型和返回值并没有给出函数的实际定义。而在具体文件系统中需要根据vinode_ops中提供的函数接口来定义具体的函数实现并将函数地址赋值给vinode_ops结构中相应的函数指针。在VFS中则通过vinode_ops的函数指针调用实际的文件系统操作函数。以RFS为例我们可以在kernel/rfs.c中找到如下一个vinode_ops类型变量rfs_i_ops的定义以下完整代码来自lab4_3_hardlink分支
上述函数接口实际上是一系列的函数指针在VFS中的vinode_ops结构体中限定了这些函数的参数类型和返回值并没有给出函数的实际定义。而在具体文件系统中需要根据vinode_ops中提供的函数接口来定义具体的函数实现并将函数地址赋值给vinode_ops结构中相应的函数指针。在VFS中则通过vinode_ops的函数指针调用实际的文件系统操作函数。以RFS为例我们可以在kernel/rfs.c中找到如下一个vinode_ops类型变量rfs_i_ops的定义以下完整代码来自lab4_3_hardlink分支
```c
22 const struct vinode_ops rfs_i_ops = {
@ -571,9 +561,7 @@ viop函数是VFS与具体文件系统进行交互的重要部分也是VFS能
vfs_open函数即为前文中提到的VFS层对上层应用提供的接口之一该函数完成根据文件路径和访问标志位打开一个文件的功能。vfs_open在第135行和179行分别调用了两个viop接口函数viop_create“viop_create(node, name)”形式是为了调用方便而定义的宏其展开为“node->i_ops->viop_create(node, name)”其他viop接口函数也有类似的宏定义见kernel/vfs.h和viop_hook_open。通过阅读该函数实现我们可以发现vfs_open函数在发现打开的文件不存在且调用时具有creatable标志位时会调用viop_create对该文件进行创建。而该viop_create则由具体文件系统提供实现比如RFS中的rfs_create。在这个过程中我们注意到vfs_open函数要想完成打开文件的操作其必须依赖底层文件系统提供一个能够完成“创建新文件并返回其vinode”功能的viop_create函数。换句话说除了钩子函数以外的其他viop函数如viop_create在其具体实现中需要完成什么样的功能是在VFS层规定好的底层文件系统提供的这些viop函数实现必须完成VFS层所预期的功能如在磁盘中创建一个文件、读写文件或创建硬链接等
接下来我们再来看vfs_open的177--182行对钩子函数即viop_hook_open的调用。代码实现的功能是若viop_hook_open函数指针不为NULL则调用它通过条件判断。可以看到从VFS层的角度来说即使viop_hook_open不完成任何实际功能甚至没有定义则不会被调用也不会影响vfs_open函数完成打开文件的功能。换句话来讲VFS层对这些钩子函数没有任何的“期待”仅仅只是在正确的位置打开文件、关闭文件、打开目录和关闭目录调用它们而已。
既然从VFS层的角度来看这类钩子函数不需要完成任何功能为什么还要有钩子函数的存在呢实际上在VFS运行过程中的一些关键时刻钩子函数为具体文件系统提供一个执行自定义代码的“机会”。比如上文中提到过hostfs中的spike_file_t结构体包含主机端打开文件描述符hostfs需要将其地址保存在文件对应vinode的i_fs_info中而该操作显然需要在打开文件时完成否则后续对该文件的读写操作则无法顺利进行。但是这段与hostfs自身细节高度相关的代码显然不应该直接放入vfs_open函数中而是应该由hostfs自己提供vfs_open负责为其提供一个执行“机会”。
接下来我们再来看vfs_open的177--182行对钩子函数即viop_hook_open的调用。代码实现的功能是若viop_hook_open函数指针不为NULL则调用它通过条件判断。可以看到从VFS层的角度来说即使viop_hook_open不完成任何实际功能甚至没有定义则不会被调用也不会影响vfs_open函数完成打开文件的功能。换句话来讲VFS层对这些钩子函数没有任何的“期待”仅仅只是在正确的位置打开文件、关闭文件、打开目录和关闭目录调用它们而已。既然从VFS层的角度来看这类钩子函数不需要完成任何功能为什么还要有钩子函数的存在呢实际上在VFS运行过程中的一些关键时刻钩子函数为具体文件系统提供一个执行自定义代码的“机会”。比如上文中提到过hostfs中的spike_file_t结构体包含主机端打开文件描述符hostfs需要将其地址保存在文件对应vinode的i_fs_info中而该操作显然需要在打开文件时完成否则后续对该文件的读写操作则无法顺利进行。但是这段与hostfs自身细节高度相关的代码显然不应该直接放入vfs_open函数中而是应该由hostfs自己提供vfs_open负责为其提供一个执行“机会”。
为了验证这一点我们可以查看kernel/hostfs.c中hostfs_i_ops的定义以下完整代码来自lab4_3_hardlink分支
@ -617,9 +605,7 @@ vfs_open函数即为前文中提到的VFS层对上层应用提供的接口之一
249 }
```
由此可见hostfs_hook_open函数确实完成了打开一个主机端文件并将其spike_file_t结构地址保存在i_fs_info的功能。
VFS层一共定义了四个钩子函数分别是viop_hook_open、viop_hook_close、viop_hook_opendir和viop_hook_closedir它们分别在打开文件vfs_open、关闭文件vfs_close、打开目录vfs_opendir和关闭目录vfs_closedir时被调用。
由此可见hostfs_hook_open函数确实完成了打开一个主机端文件并将其spike_file_t结构地址保存在i_fs_info的功能。VFS层一共定义了四个钩子函数分别是viop_hook_open、viop_hook_close、viop_hook_opendir和viop_hook_closedir它们分别在打开文件vfs_open、关闭文件vfs_close、打开目录vfs_opendir和关闭目录vfs_closedir时被调用。
hostfs实现了前两种钩子函数函数名称以及完成的功能如下
@ -697,7 +683,7 @@ RFS实现了后两种钩子函数函数名称以及完成的功能如下
103 }
```
为了简化文件系统挂载过程PKE的文件系统并不支持将一个设备中的文件系统挂载到任意目录下而是提供了两种固定的挂载方式”挂载为根目录“或”挂载为根目录下的子目录“。vfs_mount的第78--83行对应将一个设备挂载为根目录的情况。第79行将VFS目录树的根目录指向该设备上文件系统的根目录第82行则将根目录的dentry加入哈希链表中加快后续的目录搜索过程。第83--98行对应将一个设备挂载为根目录下的子目录的情况。第91行将设备上文件系统的根目录对应的dentry名称修改为设备名第94行将其链接到VFS根目录下作为VFS根目录下的一个子目录。注意该步骤相当于在VFS的根目录下创建了一个虚拟目录虚拟目录的名称为被挂载设备的设备名。例如若将设备“DEVICE0”以`MOUNT_DEFAULT`方式挂载则会在VFS根目录下创建一个虚拟的子目录仅存在于内存中不会在磁盘中创建目录文件`/DEVICE0`作为所挂载设备的挂载点。在挂载完成后,`/DEVICE0`在逻辑上等价于所挂载文件系统的根目录。最后第97行同样将这个新加入VFS目录树的dentry插入到哈希链表中加快后续的目录搜索。实际上任何新加入VFS目录树的dentry都会被同步插入哈希链表中。
为了简化文件系统挂载过程PKE的文件系统并不支持将一个设备中的文件系统挂载到任意目录下而是提供了两种固定的挂载方式“挂载为根目录”或“挂载为根目录下的子目录”。vfs_mount的第78--83行对应将一个设备挂载为根目录的情况。第79行将VFS目录树的根目录指向该设备上文件系统的根目录第82行则将根目录的dentry加入哈希链表中加快后续的目录搜索过程。第83--98行对应将一个设备挂载为根目录下的子目录的情况。第91行将设备上文件系统的根目录对应的dentry名称修改为设备名第94行将其链接到VFS根目录下作为VFS根目录下的一个子目录。注意该步骤相当于在VFS的根目录下创建了一个虚拟目录虚拟目录的名称为被挂载设备的设备名。例如若将设备“DEVICE0”以MOUNT_DEFAULT方式挂载则会在VFS根目录下创建一个虚拟的子目录仅存在于内存中不会在磁盘中创建目录文件/DEVICE0作为所挂载设备的挂载点。在挂载完成后/DEVICE0在逻辑上等价于所挂载文件系统的根目录。最后第97行同样将这个新加入VFS目录树的dentry插入到哈希链表中加快后续的目录搜索。实际上任何新加入VFS目录树的dentry都会被同步插入哈希链表中。
在上述VFS层文件系统挂载过程中我们应当注意到另一个事实当一个新文件系统被挂载时并不会立刻读取其磁盘上保存的完整目录结构到VFS目录树中而只是将具体文件系统的根目录添加到VFS目录树中。显然这样做既能节约时间和内存消耗也更加贴近于真实的文件系统运行情况。但是VFS如何处理后续对该新挂载文件系统下子目录或子文件的访问呢通过阅读kernel/vfs.c中定义的lookup_final_dentry函数可以找到答案
@ -794,9 +780,7 @@ RFS实现了后两种钩子函数函数名称以及完成的功能如下
#### 6.1.4.5 VFS层的哈希缓存
VFS通过哈希链表的形式实现了对dentry与vinode两种结构的缓存cache和快速索引它们都采用util/hash_table.h中定义的通用哈希链表类型hash_table实现并提供各自的key类型、哈希函数以及key的等值判断函数。
在kernel/vfs.c中能找到这两个哈希链表的定义
VFS通过哈希链表的形式实现了对dentry与vinode两种结构的缓存cache和快速索引它们都采用util/hash_table.h中定义的通用哈希链表类型hash_table实现并提供各自的key类型、哈希函数以及key的等值判断函数。在kernel/vfs.c中能找到这两个哈希链表的定义
```c
16 struct hash_table dentry_hash_table;
@ -867,9 +851,7 @@ VFS通过哈希链表的形式实现了对dentry与vinode两种结构的缓存
194 };
```
vinode的key值由disk inode号与文件系统对应的超级块构成。disk inode号在一个文件系统中具有唯一性因此一个disk inode号加上一个文件系统超级块便能够唯一确定一个vinode。vinode的等值判断函数与哈希函数同样定义在kernel/vfs.c中这里不再罗列请读者自行阅读。
另外由于hostfs对应的vinode不存在disk inode号所以其不会被加入到上述vinode哈希链表中hostfs不存在硬链接也不需要回写vinode数据因此无需担心上述异常情况
vinode的key值由disk inode号与文件系统对应的超级块构成。disk inode号在一个文件系统中具有唯一性因此一个disk inode号加上一个文件系统超级块便能够唯一确定一个vinode。vinode的等值判断函数与哈希函数同样定义在kernel/vfs.c中这里不再罗列请读者自行阅读。另外由于hostfs对应的vinode不存在disk inode号所以其不会被加入到上述vinode哈希链表中hostfs不存在硬链接也不需要回写vinode数据因此无需担心上述异常情况
<a name="rfs"></a>
@ -1127,6 +1109,17 @@ RFS给每个文件都分配了一个硬链接数nlinks在rfs_dinode的定义
- 先提交lab3_3的答案然后切换到lab4_1继承lab3_3中所做的修改并make后的直接运行结果
```
//切换到lab4_1
$ git checkout lab4_1_file
//继承lab4_1以及之前的答案
$ git merge lab3_3_rrsched -m "continue to work on lab4_1"
//重新构造
$ make clean; make
//运行构造结果
$ spike ./obj/riscv-pke ./obj/app_file
In m_start, hartid:0
HTIF is available!
(Emulated) memory size: 2048 MB
@ -1179,6 +1172,7 @@ System is shutting down with exit code -1.
实验完成后的运行结果:
```
$ spike ./obj/riscv-pke ./obj/app_file
In m_start, hartid:0
HTIF is available!
(Emulated) memory size: 2048 MB
@ -1437,6 +1431,17 @@ $ git commit -a -m "my work on lab4_1 is done."
```
//切换到lab4_2
$ git checkout lab4_2_directory
//继承lab4_2以及之前的答案
$ git merge lab4_1_file -m "continue to work on lab4_2"
//重新构造
$ make clean; make
//运行构造结果
$ spike ./obj/riscv-pke ./obj/app_directory
In m_start, hartid:0
HTIF is available!
(Emulated) memory size: 2048 MB
@ -1488,6 +1493,7 @@ System is shutting down with exit code -1.
实验完成后的运行结果:
```
$ spike ./obj/riscv-pke ./obj/app_directory
In m_start, hartid:0
HTIF is available!
(Emulated) memory size: 2048 MB
@ -1821,6 +1827,17 @@ $ git commit -a -m "my work on lab4_2 is done."
```c
//切换到lab4_3
$ git checkout lab4_3_hardlink
//继承lab4_2以及之前的答案
$ git merge lab4_2_directory -m "continue to work on lab4_3"
//重新构造
$ make clean; make
//运行构造结果
$ spike ./obj/riscv-pke ./obj/app_hardlink
In m_start, hartid:0
HTIF is available!
(Emulated) memory size: 2048 MB
@ -1865,6 +1882,7 @@ System is shutting down with exit code -1.
实验完成后的运行结果:
```
$ spike ./obj/riscv-pke ./obj/app_hardlink
In m_start, hartid:0
HTIF is available!
(Emulated) memory size: 2048 MB

Loading…
Cancel
Save