Lec11: CrashRecovery学习笔记
-
Block划分情况:
名称 索引 作用 BootBlock 0 存放操作系统启动的代码 SuperBlock 1 存放文件系统的元数据信息,例如:构成文件系统的Block数量 Log Block [2, 31] 其大小由SuperBlock进行定义,主要作用是CrashRecovery Inode Block [32,44] 一个Inode是64字节,一个Inode可以唯一标识一个文件,因此,操作系统中所有文件的Inode信息都保存在这里。 BitMap Block 45 标示Data Block是否空闲 DataBlock [46, xxx) 真正保存数据的区域 -
Inode简介:
- 包含Type字段,表明它是文件还是目录;
- 包含nlink字段,表明当前有多少文件指向它(包含软连接);
- 数据文件所存放的Block,一般分为两种类型,Direct Block Number和Indirect Block Number,Direct类型直接指向数据真实存放的Block,而Indirect类型需要一次跳转,因为Indirect一般指向一个跳转Block,该跳转Block包含256个BlockNumber(1k/4),从跳转的这个Block里才能真正的找到数据存放的位置。
-
目录和文件的查找路径。
-
SpinLock和SleepLock的区别?
- SpinLock的限制比较多,其加锁时必须关中断;因此,如果一个系统只有一个CPU核,当我们持有SpinLock时,就永远不能从磁盘上收到数据(因为磁盘);持有SpinLock期间,不能调用Sleep函数;
- SleepLock可以在持有锁的过程中不关闭中断;因此,可以在I/O操作过程中持有Sleep Lock;
-
File System Logging: Logging机制主要用来实现Crash Recovery,进而保证文件系统的一致性。
-
核心思想:将磁盘分为两部分,一部分为写Log,一部分写文件系统的数据。所有要更新文件系统的操作,都会先在Log中进行记录。(在下图中,Log主要存储在Log Block区域,Log Block的第一个区域为Log Header Block,用于存放此次WAL的一些元数据信息)
-
所有写文件系统的操作将分为四步:
1.log writes
: 将所有更新文件系统的操作落盘至Log Block。(例如,用户调用echo "hi" > x
,则该过程会涉及多个步骤:创建Inode、更新BitMap、写Data Block、更新Inode的size字段等操作,即所有这些更新文件系统的操作必须是原子的,要么全部执行,要么全部不执行,才能保证文件系统最后的状态是一致的。在该步骤中,我们会将这些操作记录下来,并落盘至Log Block中。)
2.commit log
: commit是将上一步中落盘的的操作数(假设为5)记录下来,并写入commit header block中。注意:在向磁盘的block或者sector写数据时,系统能保证要么数据全部写入成功,要么数据全部失败,因此,不会出现部分写入的情况。
3.install
log:该过程是将写入Log的操作真正的执行到文件系统中,且该操作是幂等的,即可以多次执行。
4.clean log
:将已经执行的操作删除,其实就是在Log中记录当前某个文件系统的待执行数为0 -
系统在四个步骤中的任何时间点Crash,都能保证文件系统的一致性。
-
本人之前一直有一个疑问,为什么在文件系统之前要先在Log中记录一遍,Log占用的不也是磁盘的一部分么?这不是一个死循环么,写Log的过程中也可能会产生系统的Crash,不也会导致Log的不一致么?仔细想想之后,觉得是这样的:(1)我们的目标是保证文件系统的一致性,Log只是我们为实现这个一致性的中转工具,通过Log,我们能保证一系列的操作要么在文件系统中全部执行,要么全部失败。(2)Log的一致性,其实主要体现在
log write
过程的落盘,如果在这个过程中系统Crash了,我们只能自认倒霉,即相关更新文件系统操作全部失效,就像从没调用过文件系统的接口一样,这是可以接受的。(3)Crash Recovery的过程在重启之后会自动执行,因此,通过Log这个中转工具,可以保证文件系统在正常使用之前肯定是被修复好的。
-
-
函数Log_write,该函数是在内存的Logging相关数据结构中记录对block cache的更改,方便之后将对block的更改记录到Log中,并进行commit、install、clean等一些列操作。
- 每个对文件系统的操作,都包含在
begin_op()
和end_op()
之间,这两个函数类似于数据库中的事务。只有在end_op
调用之后,才会进行将数据写入Log,并进行commit、install、clean等操作。而在两个函数之间,只会进行磁盘数据或者内存数据对应的数据结构(在内存里)的更新,也就是block cache的更新。 - 因为写数据的过程中,我们在Log和文件系统上执行共两次,所以相应操作的效率会下降一半。
- 每个对文件系统的操作,都包含在
实验链接
https://pdos.csail.mit.edu/6.S081/2020/labs/fs.html
实验
Large Files
没改代码之前,XV6只支持最大写入大小为258(256 + 12)*1K的文件大小,本实现的目的是更改代码使得文件系统支持更大的文件大小。如上图所示,该实验要求文件系统支持最大(11 + 256 + 256乘以256)乘1K大小的文件。这里将Inode指向文件数据Block的索引分为三类。
- Direct Block:直接索引,可指向11个Block;
- Singly Indirect Block:只跳转一次的间接索引,可指向256个Block;
- Doubly Indirect Block: 跳转两次的间接索引,可指向256乘以256个Block。
具体实现如下:
-
文件
kernel/fs.h
xxxx #define NDIRECT 11 // 改为11个,因为要留出来一个给跳转两次的索引 #define NINDIRECT (BSIZE / sizeof(uint)) #define NDOUBLE ((NINDIRECT) * (NINDIRECT)) // 两次跳转索引可增加的Block引用数量 #define MAXFILE (NDIRECT + (NINDIRECT) + (NDOUBLE)) // 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) // 这里改为加2,一个给singly indirect索引,一个给doubly indirect索引 uint addrs[NDIRECT+2]; // Data block addresses }; xxx
-
文件
kernel/file.h
xxxxx // 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; // 这里改为加2,一个给singly indirect索引,一个给doubly indirect索引 uint addrs[NDIRECT+2]; }; xxxxx
-
文件
kernel/fs.c
,按照提示,主要修改bmap函数和itrunc函数。// Inode content // // The content (data) associated with each inode is stored // in blocks on the disk. The first NDIRECT block numbers // are listed in ip->addrs[]. The next NINDIRECT blocks are // listed in block ip->addrs[NDIRECT]. // Return the disk block address of the nth block in inode ip. // If there is no such block, bmap allocates one. static uint bmap(struct inode *ip, uint bn) { uint addr, *a, *a1; struct buf *bp, *bp1; if(bn < NDIRECT){ if((addr = ip->addrs[bn]) == 0) ip->addrs[bn] = addr = balloc(ip->dev); return addr; } bn -= NDIRECT; 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; // 主要更改的位置,即两次跳转才能找到索引 if (bn < NDOUBLE) { if ((addr = ip->addrs[NDIRECT + 1]) == 0) ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev); bp = bread(ip->dev, addr); a = (uint*)bp->data; int index = bn / NINDIRECT; int remain = bn % NINDIRECT; // 一级跳转 if ((addr = a[index]) == 0) { a[index] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); // 二级跳转 bp1 = bread(ip->dev, addr); a1 = (uint*)bp1->data; if ((addr = a1[remain]) == 0) { a1[remain] = addr = balloc(ip->dev); log_write(bp1); } brelse(bp1); return addr; } panic("bmap: out of range"); } // Truncate inode (discard contents). // Caller must hold ip->lock. void itrunc(struct inode *ip) { int i, j, k; struct buf *bp, *bp1; uint *a, *a1; 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; } if (ip->addrs[NDIRECT + 1]) { bp = bread(ip->dev, ip->addrs[NDIRECT + 1]); a = (uint*)bp->data; // 一级跳转 for (j = 0; j < NINDIRECT; j++) { if (a[j]) { bp1 = bread(ip->dev, a[j]); a1 = (uint*)bp1->data; // 二级跳转 for (k = 0; k < NINDIRECT; k++) { if (a1[k]) { bfree(ip->dev, a1[k]); } } brelse(bp1); bfree(ip->dev, a[j]); } } brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT + 1]); ip->addrs[NDIRECT + 1] = 0; } ip->size = 0; iupdate(ip); }
-
执行结果1
-
执行结果2
Lab: Symbolic links
实验目标是添加关于软连接的系统调用,软连接与硬连接的区别如下:
软连接 | 硬连接 |
---|---|
也叫符号连接,是指该连接只保存原文件的位置信息。如果原文件被删除了,则该软连接就失效了。 | 一个文件可以有多个名字,但是该文件只有一个inode编号,即文件名与inode编号是多对一的关系。如果一个文件有多个硬连接,则只有所有硬连接都删除了,该文件才被删除,否则,删除任意一个硬连接,该文件还是真实存在的。 |
可以创建不存在文件或者目录的软连接 | 不能创建不存在文件的硬连接 |
可以在不同的文件系统中创建软连接 | 只能在相同的文件系统中创建硬连接,即硬连接不能跨操作系统 |
可以创建文件或者目录的软连接 | 只能创建文件的硬连接,不能创建目录的硬连接 |
除此之外,该实验还要求在open文件时,如果是软连接,且用户要求循环查找,则需要循环查找该连接的原文件,直到查找路径深度超过10。
代码如下:
-
文件
Makefile
,可执行文件生成symlinktest
:UPROGS=\ $U/_cat\ $U/_echo\ $U/_forktest\ $U/_grep\ $U/_init\ $U/_kill\ $U/_ln\ $U/_ls\ $U/_mkdir\ $U/_rm\ $U/_sh\ $U/_stressfs\ $U/_usertests\ $U/_grind\ $U/_wc\ $U/_zombie\ $U/_symlinktest\ // --add
-
文件
kernel/fcntl.h
:#define O_RDONLY 0x000 #define O_WRONLY 0x001 #define O_RDWR 0x002 #define O_CREATE 0x200 #define O_TRUNC 0x400 #define O_NOFOLLOW 0x800 // ++++++
-
文件
kernel/stat.h
,增加类型软连接。#define T_DIR 1 // Directory #define T_FILE 2 // File #define T_DEVICE 3 // Device #define T_SYMLINK 4 // Symbol link ++++ struct stat { int dev; // File system's disk device uint ino; // Inode number short type; // Type of file short nlink; // Number of links to file uint64 size; // Size of file in bytes };
-
文件
kernel/syscall.h
,增加系统调用编号#define SYS_symlink 22
-
文件
user/user.h
,增加用户态的调用接口int symlink(const char*, const char*);
-
文件
user/usys.pl
,增加perl语言自动生成asm的entryentry("symlink");
-
文件
kernel/syscall.c
,更改如下:xxxx extern uint64 sys_symlink(void); //+++++++++ static uint64 (*syscalls[])(void) = { [SYS_fork] sys_fork, [SYS_exit] sys_exit, [SYS_wait] sys_wait, [SYS_pipe] sys_pipe, [SYS_read] sys_read, [SYS_kill] sys_kill, [SYS_exec] sys_exec, [SYS_fstat] sys_fstat, [SYS_chdir] sys_chdir, [SYS_dup] sys_dup, [SYS_getpid] sys_getpid, [SYS_sbrk] sys_sbrk, [SYS_sleep] sys_sleep, [SYS_uptime] sys_uptime, [SYS_open] sys_open, [SYS_write] sys_write, [SYS_mknod] sys_mknod, [SYS_unlink] sys_unlink, [SYS_link] sys_link, [SYS_mkdir] sys_mkdir, [SYS_close] sys_close, [SYS_symlink] sys_symlink, //+++++++++ }; xxxx
-
文件
kernel/sysfile.c
中,增加函数sys_symlink
,并修改函数sys_open
。xxxxxx 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(); // create 函数会对inode加锁 ip = create(path, T_SYMLINK, 0, 0); if (ip == 0) { end_op(); return -1; } // 将目标路径写入inode的data block if (writei(ip, 0, (uint64)target, 0, MAXPATH) != MAXPATH) { return -1; } // iput类似于对Inode的ref减一 iunlockput(ip); end_op(); return 0; } xxxxxx uint64 sys_open(void) { char path[MAXPATH]; int fd, omode; struct file *f; struct inode *ip; int n; if((n = argstr(0, path, MAXPATH)) < 0 || argint(1, &omode) < 0) return -1; begin_op(); if(omode & O_CREATE){ ip = create(path, T_FILE, 0, 0); if(ip == 0){ end_op(); return -1; } } else { if((ip = namei(path)) == 0){ end_op(); return -1; } ilock(ip); if(ip->type == T_DIR && omode != O_RDONLY){ iunlockput(ip); end_op(); return -1; } } if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){ iunlockput(ip); end_op(); return -1; } // // ++++++++begin++++++++++++++++ if (ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)) { int cnt = 0; while (ip->type == T_SYMLINK) { if (readi(ip, 0, (uint64)&path, 0, MAXPATH) == -1) { iunlockput(ip); end_op(); return -1; } iunlockput(ip); if ((ip = namei(path)) == 0) { end_op(); return -1; } cnt++; if (10 == cnt) { end_op(); return -1; // 超过最大的深度 } ilock(ip); //namei不会对inode加锁,所以这里需要手动加锁 } } // -----------end----------- if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){ if(f) fileclose(f); iunlockput(ip); end_op(); return -1; } if(ip->type == T_DEVICE){ f->type = FD_DEVICE; f->major = ip->major; } else { f->type = FD_INODE; f->off = 0; } f->ip = ip; f->readable = !(omode & O_WRONLY); f->writable = (omode & O_WRONLY) || (omode & O_RDWR); if((omode & O_TRUNC) && ip->type == T_FILE){ itrunc(ip); } iunlock(ip); end_op(); return fd; }
-
执行结果1
-
执行结果2
结果
$ make grade
提交结果
$ git commit -m "lab fs"
$ make handin
查看结果
登录网站https://6828.scripts.mit.edu/2020/handin.py/student
,可以看到提交的结果。
参考链接
https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081
https://pdos.csail.mit.edu/6.S081/2020/schedule.html
Github
源码: https://github.com/aerfalwl/mit-xv6-labs-2020