接下来我们通过查看XV6中的代码,更进一步的了解文件系统。因为我们前面已经分配了inode,我们先来看一下这是如何发生的。sysfile.c中包含了所有与文件系统相关的函数,分配inode发生在sys_open函数中,这个函数会负责创建文件。
在sys_open函数中,会调用create函数。
create函数中首先会解析路径名并找到最后一个目录,之后会查看文件是否存在,如果存在的话会返回错误。之后就会调用ialloc(inode allocate),这个函数会为文件x分配inode。ialloc函数位于fs.c文件中。
以上就是ialloc函数,与XV6中的大部分函数一样,它很简单,但是又不是很高效。它会遍历所有可能的inode编号,找到inode所在的block,再看位于block中的inode数据的type字段。如果这是一个空闲的inode,那么将其type字段设置为文件,这会将inode标记为已被分配。函数中的log_write就是我们之前看到在console中有关写block的输出。这里的log_write是我们看到的整个输出的第一个。
以上就是第一次写磁盘涉及到的函数调用。这里有个有趣的问题,如果有多个进程同时调用create函数会发生什么?对于一个多核的计算机,进程可能并行运行,两个进程可能同时会调用到ialloc函数,然后进而调用bread(block read)函数。所以必须要有一些机制确保这两个进程不会互相影响。
让我们看一下位于bio.c的buffer cache代码。首先看一下bread函数
bread函数首先会调用bget函数,bget会为我们从buffer cache中找到block的缓存。让我们看一下bget函数
这里的代码还有点复杂。我猜你们之前已经看过这里的代码,那么这里的代码在干嘛?
学生回答:这里遍历了linked-list,来看看现有的cache是否符合要找的block。
是的,我们这里看一下block 33的cache是否存在,如果存在的话,将block对象的引用计数(refcnt)加1,之后再释放bcache锁,因为现在我们已经完成了对于cache的检查并找到了block cache。之后,代码会尝试获取block cache的锁。
所以,如果有多个进程同时调用bget的话,其中一个可以获取bcache的锁并扫描buffer cache。此时,其他进程是没有办法修改buffer cache的(注,因为bacche的锁被占住了)。之后,进程会查找block number是否在cache中,如果在的话将block cache的引用计数加1,表明当前进程对block cache有引用,之后再释放bcache的锁。如果有第二个进程也想扫描buffer cache,那么这时它就可以获取bcache的锁。假设第二个进程也要获取block 33的cache,那么它也会对相应的block cache的引用计数加1。最后这两个进程都会尝试对block 33的block cache调用acquiresleep函数。
acquiresleep是另一种锁,我们称之为sleep lock,本质上来说它获取block 33 cache的锁。其中一个进程获取锁之后函数返回。在ialloc函数中会扫描block 33中是否有一个空闲的inode。而另一个进程会在acquiresleep中等待第一个进程释放锁。
学生提问:当一个block cache的refcnt不为0时,可以更新block cache吗?因为释放锁之后,可能会修改block cache。
Frans教授:这里我想说几点;首先XV6中对bcache做任何修改的话,都必须持有bcache的锁;其次对block 33的cache做任何修改你需要持有block 33的sleep lock。所以在任何时候,release(&bcache.lock)之后,b->refcnt都大于0。block的cache只会在refcnt为0的时候才会被驱逐,任何时候refcnt大于0都不会驱逐block cache。所以当b->refcnt大于0的时候,block cache本身不会被buffer cache修改。这里的第二个锁,也就是block cache的sleep lock,是用来保护block cache的内容的。它确保了任何时候只有一个进程可以读写block cache。
如果buffer cache中有两份block 33的cache将会出现问题。假设一个进程要更新inode19,另一个进程要更新inode20。如果它们都在处理block 33的cache,并且cache有两份,那么第一个进程可能持有一份cache并先将inode19写回到磁盘中,而另一个进程持有另一份cache会将inode20写回到磁盘中,并将inode19的更新覆盖掉。所以一个block只能在buffer cache中出现一次。你们在完成File system lab时,必须要维持buffer cache的这个属性。
学生提问:如果多个进程都在使用同一个block的cache,然后有一个进程在修改block,并通过强制向磁盘写数据修改了block的cache,那么其他进程会看到什么结果?
Frans教授:如果第一个进程结束了对block 33的读写操作,它会对block的cache调用brelse(block cache release)函数。
这个函数会对refcnt减1,并释放sleep lock。这意味着,如果有任何一个其他进程正在等待使用这个block cache,现在它就能获得这个block cache的sleep lock,并发现刚刚做的改动。
假设两个进程都需要分配一个新的inode,且新的inode都位于block 33。如果第一个进程分配到了inode18并完成了更新,那么它对于inode18的更新是可见的。另一个进程就只能分配到inode19,因为inode18已经被标记为已使用,任何之后的进程都可以看到第一个进程对它的更新。
这正是我们想看到的结果,如果一个进程创建了一个inode或者创建了一个文件,之后的进程执行读就应该看到那个文件。