我们在Minix3文件系统中编写代码来读取inode,然后读取指向与给定inode相关的数据的直接、间接、双重间接和三重间接指针。但是,我们需要执行一次统计,以查看我们需要获取哪些inode。显然,这不是文件系统实际工作的方式。相反,我们希望按名称搜索给定的inode。
回想一下,在Minix3中,我们有一个目录条目结构,在Rust中如下所示。
上面的代码显示我们有一个4字节的inode和一个60字节的名称。这就是我们将名称链接到inode的方式。事实上,我们可以将多个名称链接到同一inode-这些名称称为硬链接。这也是我们没有删除文件系统功能的原因。取而代之的是,它被称为取消链接。当我们取消链接时,我们将名称断开为信息节点链接。只要没有剩余的链接,该文件就被视为已删除。
我们在哪里可以找到这些DirEntry结构?这相当简单。回想一下,每当我们读取文件时,我们都会转到10个“区域”指针。对于常规文件,当我们转到每个指针的末尾时,我们会找到属于该文件的实际数据。但是,如果有问题的文件是一个目录,我们可以通过查看inode的模式了解到这一点,那么这些指针指向一组DirEntry结构。
对于1024字节的块大小,我们有\(1024/64=16\)个DirEntry总数。回想一下,我们有一个4字节的inode和一个60字节的名称,这就是每个结构占用64字节的原因。
我们遇到的另一个问题是搜索目录项。这是一项非常昂贵的任务,因为我们每个块只有16个条目。因此,我们需要缓存inode。换句话说,我们需要在内存中放入一些东西,以便更快地进行查找。幸运的是,这相当简单。
我们的策略是在文件系统中查找所有目录项,并以某种查找速度较快的格式存储它们。我们并不真正关心插入或删除时间,但我们将进行大量查找。因此,让我们添加一些代码来缓存inode。我们将在每次初始化文件系统时启动。我们将获得支持文件系统的数据块设备。这样,我们就可以使用virtio块驱动程序对其进行读写。
静态mut mfs_inode_cache:[option<;BTreeMap<;string,inode>;>;;8]=[无,无];
我们还没有一个虚拟文件系统,所以我们做的很像是一个基于DOS的分配系统。因此,本质上我们是block device:,例如8:(还记得在DOS中,我们有C:?)。然后,每当我们进入该数据块设备时,我们就有了我们的底层文件系统。目前,我们不能只将一个文件系统挂载到另一个文件系统的目录结构中。但是,使用VFS时,我们将使用单个映射来存储整个文件系统。我们不是将字符串映射到inode,而是需要将字符串映射到某种结构,该结构告诉我们要转到哪个块设备,然后是inode或某个标识值。
请注意,我在这里使用的是BTreeMap。这是内置到Rust的alloc::Collection库中的,然后该库挂接到我们很久以前编写的全局分配器。
我们将按路径存储条目-这是我们的密钥。然后,我们将该条目的inode存储为值。这应该会给我们需要的所有信息。
因此,现在我们需要一个策略来实际缓存inode。首先,我们需要了解目录结构。回想一下,在Minix3中,我们只知道DirEntry结构中给定文件的名称。我们不知道完整的路径,所以这是我们需要跟上的东西。下面是Rust中的init()函数。
pub FN init(BDEV:usize){如果不安全{MFS_INODE_CACH[BDEV-1].is_None()}{let mut btm=BTreeMap::new();let CWD=string::from(";/";);//让&/让我们查看根(inode#1)self::cache_at(&;mut BTM,&;CWD,1,}}Else{println!(";kernel:已初始化已初始化的\文件系统{}";,BDEV);}}。
正如您在上面看到的,我们分配了一个字符串(string::from(“/”)),它是根目录。然后,我们查看根目录中的每个目录条目。如果我们命中另一个目录,我们递归地做同样的事情,直到没有更多的目录。因此,我们的缓存将存储目录和常规文件-它们的路径和索引节点,而不是实际数据。
作为附注,Rust要求我们添加不安全的{},因为这是一个可变的静态变量,这意味着它是全局的并且可以更改,因此,除非我们添加锁定,否则它不是线程安全的。
那么,让我们来看看实际执行工作的函数。这就是您在上面看到的self::cache_at函数。
fn cache_at(btm:&;mut BTreeMap<;string,inode>;,cwd:&;string,inode_num:u32,bdev:usize){let ino=self::get_inode(bdev,inode_num).unwire();let mut buf=buffer::new(ino.size+block_size-1)&;!block_size)as usize)。ino,buf.get_mut(),block_size,0);让num_dirents=sz作为usize/size_of::<;DirEntry>;();//我们从2开始,因为前两个条目是。还有..。for i in 2..num_dirents{unsafe{let ref d=*dirents.add(I);let d_ino=self::get_inode(bdev,d.inode).unwire();let mut new_cwd=string::with_acity(120);for i in cwd.bytes(){new_cwd.ush(I As Char);}//在该inode和下一个inode之间添加目录分隔符。//如果我们是根(Inode 1),我们不想将//前斜杠加倍,因此只对非根执行此操作。如果inode_num!=1{new_cwd.ush(';/';);}for i in 0..60{if d.name[i]==0{Break;}new_cwd.ush(d.name[i]as char);}new_cwd.ink_to_fit();如果d_ino.mode&;S_IFDIR!=0{//这是一个目录,请缓存这些文件。这是一个递归调用,我不太喜欢。self::cache_at(btm,&;new_cwd,d.inode,bdev);}Else{btm.insert(new_cwd,d_ino);}。
我们在这里所做的是查看最初提供的inode,它记住根是数字1。我们首先读取给定的inode-给定的inode应该是目录的inode,因为我们不对常规文件调用cache_at。回想一下,这些inode中的每一个都有用DirEntry结构(64字节)填充的块。
如果我们找到一个目录,我们用新的inode和新的路径递归调用cache_at。这将再次搜索所有目录条目,如果还有更多目录,它将再次递归查找所有目录。否则,如果我们找到一个文件,我们只存储路径,而不是递归。
请不要介意我试图弄清楚如何让Rust将字符数组(更准确地说是U8数组)视为字符串。因此,我必须创建一个字符串,然后逐个字符地将数组中的每个字符推送到字符串上。
现在我们已经缓存了inode,是时候让它做点什么了。幸运的是,由于BTreeMap是Rust内置的,我们可以只使用它的.get()成员函数。如果此函数找到键,它将返回一个One(Inode)。否则,它将返回NONE。我们可以使用它来查看文件是否存在。因此,让我们向open()文件系统函数添加一些代码。
pub FN open(BDEV:usize,path:&;str)->;result<;inode,FsError&>;{if let Some(Cache)=unsafe{MFS_INNODE_CACH[BDEV-1].Take()}{let ret;if let Some(Inode)=cache.get(Path){ret=OK(*inode);}Else{ret=err(FsError::FileNonode[BDEV-1].Take()}{let ret;if let Some(Inode)=cache.get(Path){ret=OK(*inode);}Else{ret=err。}ret}否则{err(FsError::FileNotFound)}}。
我们在这里所做的就是获取对缓存的独占访问,查找提供的路径,然后返回对缓存的访问。如果我们找到了inode,我们会复制它并将其封装在OK()中返回。否则,我们将错误原因包装在err()中。请注意,如果我们无法访问缓存,则返回FileNotFound。或许这让FNF变得模棱两可。
现在,为了初始化缓存,我们在进程上下文中运行init()。然后,我们可以使用open()获取inode。最后,如果我们想要读取其中的数据,可以使用read()。请注意,read()fs函数已更改为接受inode引用。这有助于让事情变得更干净一些。这也给了我们一个流程:我们缓存inode,我们打开一个文件来获得它的inode结构,我们根据这个inode结构读取。目前,Close不做任何事情,因为实际上没有什么要关闭的。对于阅读,我们不在乎。对于写入,关闭将执行一些操作,例如确保所有写入都已完成。