bigfile
写之前对文件系统一直有害怕的感觉,写完之后还是感觉害怕,这个做的感觉太基础了
第一反应是递归,但是不是完全规则的 pattern,还是老老实实 iterative
理论上有很多重复的地方可以抽象成函数,但是原先的实现很不 pure,不是很敢动手
实现上,总的来说跟着中文文档走就行了,要点都已经给出来了,那个图也很清晰
设计上,还是第一次见区分了 inode
和 dinode
的,之前一直没想过可以分成一个 cache 一个 on disk version
.....panic: virtio_disk_intr status
写完之后一直报这个错,发现是这两行复制了上面的内容,但是操作的对象没改,少了个 + 1
,debug 了好久
if ((addr = ip -> addrs [NDIRECT + 1 ]) == 0 )
ip -> addrs [NDIRECT + 1 ] = addr = balloc (ip -> dev );
mkfs: mkfs/mkfs.c:85: main: Assertion `(BSIZE % sizeof(struct dinode)) == 0’ failed.
实现过程中的问题,根据报错找到 dinode
之后修改定义为 +2
同样的地方,还有一个名为 MAXFILE 的宏定义,表示当前文件系统所支持的数据 块数目,请查看此处并思考,当你要增加 xv6 文件系统对大文件的支持时,此宏 定义是否也需要修改为对应值?
当然需要,都增加比 MAXFILE 大的支持了,怎么不修改
特别是 grep 里面看到 test 里面用到了的时候
具体实现
// kernel/fs.h
#define NDIRECT 11
#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT * NINDIRECT)
// On-disk inode structure
struct dinode {
short type; // File type
short major; // Major device number (T_DEVICE only)
short minor; // Minor device number (T_DEVICE only)
short nlink; // Number of links to inode in file system
uint size; // Size of file (bytes)
uint addrs [NDIRECT + 2 ]; // Data block addresses
};
// kernel/file.h
// in-memory copy of an inode
struct inode {
uint dev; // Device number
uint inum; // Inode number
int ref; // Reference count
struct sleeplock lock; // protects everything below here
int valid; // inode has been read from disk?
short type; // copy of disk inode
short major;
short minor;
short nlink;
uint size;
uint addrs [NDIRECT + 2 ];
};
// kernel/fs.c
static uint
bmap ( struct inode * ip , uint bn )
{
uint addr, * a;
struct buf * bp;
if (bn < NDIRECT){
if ((addr = ip->addrs[bn]) == 0 )
ip->addrs[bn] = addr = balloc (ip->dev);
return addr;
}
bn -= NDIRECT;
// first indirect
if (bn < NINDIRECT){
// Load indirect block, allocating if necessary.
if ((addr = ip->addrs[NDIRECT]) == 0 )
ip->addrs[NDIRECT] = addr = balloc (ip->dev);
bp = bread (ip->dev, addr);
a = ( uint* )bp->data;
if ((addr = a [bn]) == 0 ){
a [bn] = addr = balloc (ip->dev);
log_write (bp);
}
brelse (bp);
return addr;
}
bn -= NINDIRECT;
// second indirect
if (bn < NINDIRECT * NINDIRECT) {
// Load indirect block, allocating if necessary.
if ((addr = ip->addrs[NDIRECT + 1 ]) == 0 )
ip->addrs[NDIRECT + 1 ] = addr = balloc (ip->dev);
bp = bread (ip->dev, addr);
a = ( uint* )bp->data;
if ((addr = a [bn / NINDIRECT]) == 0 ){
a [bn / NINDIRECT] = addr = balloc (ip->dev);
log_write (bp);
}
brelse (bp);
// Load second indirect block
bn %= NINDIRECT;
// No need to baaloc the dev again since we already allocated the block
bp = bread (ip->dev, addr);
a = ( uint* )bp->data;
if ((addr = a [bn]) == 0 ){
a [bn] = addr = balloc (ip->dev);
log_write (bp);
}
brelse (bp);
return addr;
}
panic ( "bmap: out of range" );
}
// Truncate inode (discard contents).
// Caller must hold ip->lock.
void
itrunc ( struct inode * ip )
{
int i, j;
struct buf * bp;
uint * a;
for (i = 0 ; i < NDIRECT; i ++ ){
if (ip->addrs[i]){
bfree (ip->dev, ip->addrs[i]);
ip->addrs[i] = 0 ;
}
}
if (ip->addrs[NDIRECT]){
bp = bread (ip->dev, ip->addrs[NDIRECT]);
a = ( uint* )bp->data;
for (j = 0 ; j < NINDIRECT; j ++ ){
if ( a [j])
bfree (ip->dev, a [j]);
}
brelse (bp);
bfree (ip->dev, ip->addrs[NDIRECT]);
ip->addrs[NDIRECT] = 0 ;
}
// second indirect
if (ip->addrs[NDIRECT + 1 ]) {
bp = bread (ip->dev, ip->addrs[NDIRECT + 1 ]);
a = ( uint* )bp->data;
for (i = 0 ; i < NINDIRECT; i ++ ) {
if ( a [i]) {
// Load second indirect block
struct buf * bp2 = bread (ip->dev, a [i]);
uint * a2 = ( uint* )bp2->data;
for (j = 0 ; j < NINDIRECT; j ++ ){
if ( a2 [j])
bfree (ip->dev, a2 [j]);
}
brelse (bp2);
bfree (ip->dev, a [i]);
}
}
brelse (bp);
bfree (ip->dev, ip->addrs[NDIRECT + 1 ]);
ip->addrs[NDIRECT + 1 ] = 0 ;
}
ip->size = 0 ;
iupdate (ip);
}
结果:
$ bigfile
..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
wrote 65803 blocks
bigfile done; ok
symbolic link
soft link 和普通文件一样,也占用一个 inode
实现 syscall 时,主要参考之前加了什么,看看都用到了哪里,还有参考 link 和 unlink
根据添加的 define 可以找到 sys_open
和 create
看到 create
就猜到大概可以怎么实现,创建一个类型为 T_SYMLINK
的文件即可。再根据提示写入 target 到 data block 里面,不需要 check target,根据其他实现给 ip
加了个锁,然后最后才发现在外面已经上锁了不能在里面上锁,不然测试时候会死锁,这是看网上实现才发现的
open
就顾名思义,更改遇到 symlink 时候 follow 能 follow 的,并且加一个上限就行
实现时候的锁不小心锁多了一个(复制剪切时候没复制全),再次死锁,但是通过 gdb 能解决
总的来说,不管是文件系统还是 DB 都会遇到死锁问题,以后还是得好好尝试怎么处理死锁
非常想念 golang 的 defer
,清理资源容易太多了
// kernel/fcntl.h
#define O_NOFOLLOW 0x 800
// kernel/sysfile.c
uint64 sys_symlink ( void ) {
char target [MAXPATH], path [MAXPATH];
struct inode * ip;
if ( argstr ( 0 , target, MAXPATH) < 0 || argstr ( 1 , path, MAXPATH) < 0 )
return - 1 ;
begin_op ();
ip = create (path, T_SYMLINK, 0 , 0 );
if (ip == 0 ) {
end_op ();
return - 1 ;
}
// ilock(ip);
if ( writei (ip, 0 , (uint64)target, 0 , strlen (target)) != strlen (target)) {
iunlockput (ip);
end_op ();
return - 1 ;
}
iunlockput (ip);
end_op ();
return 0 ;
}
uint64 sys_open ( void )
{
// ...
begin_op ();
if (omode & O_CREATE) {
ip = create (path, T_FILE, 0 , 0 );
if (ip == 0 ) {
end_op ();
return - 1 ;
}
} else {
int depth = 0 ;
while ( 1 ) {
if ((ip = namei (path)) == 0 ) {
end_op ();
return - 1 ;
}
ilock (ip);
if ((ip->type == T_SYMLINK) && (omode & O_NOFOLLOW) == 0 ) { // if symlink and can follow
if ( ++ depth > 10 ) {
iunlockput (ip);
end_op ();
return - 1 ;
}
if ( readi (ip, 0 , (uint64)path, 0 , MAXPATH) < 0 ) {
iunlockput (ip);
end_op ();
return - 1 ;
}
iunlockput (ip);
} else {
break ;
}
}
if (ip->type == T_DIR && omode != O_RDONLY) {
iunlockput (ip);
end_op ();
return - 1 ;
}
}
// ...
iunlock (ip);
end_op ();
return fd;
}
结果:
$ symlinktest
Start: test symlinks
test symlinks: ok
Start: test concurrent symlinks
test concurrent symlinks: ok
总结
真的很容易一下就死锁,像 golang 那样的 defer cleanup 真的很好用
函数命名真的挺重要的,像这种老牌 unix hacker 写的函数不加注释完全看不懂,加了也只能一知半解
熟悉了 side effect / pure function,通过实例了解 FP 对测试和重构的影响
这种大量 hard code constant 的做法可靠性存疑,容易改了一个漏改另一个
稍微熟悉了一点 fs,特别是 symbolic link 里面的递归 link