讲解 xv6 文件系统实现
“正常” 的项目都有遵循正常人能理解的代码组织
你可以问出很多问题
make -nB qemu | grep mkfs
make -nB qemu | grep fs\.img
mkfs 和 kernel 共享了一部分代码
磁盘上的数据结构
[ boot | superblock | log | inodes | free bitmap | data blocks ]
参数
BSIZE
, ROOTINO
(/
的 inode 编号), LOGSIZE
, NINODE
, ...重要的数据结构
struct superblock
(magic
, size
, ...), mkfs 填入struct dinode
(type
, nlink
, size
, addrs
)NDIRECT
(12) 个直接索引,一个间接索引struct dirent
(inum
, name
)彩蛋:static_assert
(C++11/C11)
奇怪的 xint
(does nothing)
uint xint(uint x) {
uint y;
uchar *a = (uchar*)&y;
a[0] = x; a[1] = x >> 8; a[2] = x >> 16; a[3] = x >> 24;
return y;
}
清零文件系统
for(i = 0; i < FSSIZE; i++) wsect(i, zeroes);
初始化 superblock
(真的都是数据结构操作)
ialloc
: 分配 inode (从 1 开始编号)
ialloc()
返回的是 /
的 inodeiappend
: 向 inode 对应的文件 (包括目录文件) 写入数据
.
” 和 “..
” 对应的 dirent
,都指向 rootino
(1)足够了
每次访问 block 的一部分都读一次、写一次磁盘 = 性能崩盘。
Buffer Cache
核心数据结构:struct buf
(buf.h, bio.c)
sys_write
begin_op();
ilock(f->ip);
if ((r = writei(f->ip, 1, addr + i, f->off, n1)) > 0)
f->off += r;
iunlock(f->ip);
end_op();
begin_op
和 end_op
包裹的代码
begin_op
不会导致 log 空间用完,就可以继续end_op
提交后唤醒bwrite
仅 log.c
调用)例子:写入数据
bp = bread(ip->dev, bmap(ip, off/BSIZE));
log_write(bp);
begin_op
和 end_op
之间所有的写入都仅发生在内存end_op
时才发生 commitstatic void commit() {
if (log.lh.n > 0) {
write_log(); // Write modified blocks from cache to log
write_head(); // Write header to disk -- the real commit
install_trans(0); // Now install writes to home locations
log.lh.n = 0;
write_head(); // Erase the transaction from the log
}
}
两个看似简单的规则
refcnt > 0
refcnt == 0
维护这一不变式的例子:log_write
i == log.lh.n
时才执行 bpin(b)
增加引用计数典型的阅读路径
sys_open
)create
, 被 sys_open
, sys_mkdir
调用)sys_link
)xv6 文件系统实现
Take-away messages