文章目录
- 一、Large files
- 简介
- 提示
- 实验代码
- 实验结果
- 二、Symbolic links
- 简介
- 提示
- 实验代码
- 实验结果
首先切换到 fs 分支
- $ git fetch
- $ git checkout fs
- $ make clean
一、Large files
简介
该实验需要我们增大xv6文件的最大大小,目前xv6的最大文件大小为 268个块,即 268 * BSIZE
。这是因为 xv6 包含12个直接块号 和 一个一级间接块号
,最大索引 12 + 256 = 268 个块
。
bigfile
命令将创建xv6允许的最大文件,并报告其大小,未进行实验前结果如图:
我们需要让xv6支持二级间接块,这样支持的索引块号最大为 11 + 256 + 256 * 256 = 65803
个,这样即可满足 bigfile
的测试要求。
我们的任务:修改 bmap() 函数让其可以支持二重间接块号,
提示
- 磁盘文件的
inode
定义由fs.h
的struct inode
定义 - 主目录中的
mkfs
程序用于创建 xv6 文件系统磁盘硬件并确定该磁盘总共多少数据块,磁盘的总大小由kernel/param.h
中的FSSIZE 控制,当前分支已经将磁盘的大小改为 20w 个块大小 - 在磁盘上查找文件数据的函数是
fs.c
的bmap()
,阅读它并确保知道该函数在做什么。该函数在读取和写入文件时都会被调用,写入时bmap()根据需要分配新的块来保存内容,并根据需要分配间接块来保存块地址。 - 可以将
bmap()
函数理解为将文件的逻辑块号映射为磁盘的物理块号,该函数的bn
参数表示逻辑块号。而ip->addrs[] 数组下标和 bread()
需要的都是物理块号,因此bmap()
需要将bn
转换为物理块号 - 如果修改
NDIRECT
的定义,应该同时修改file.h
中struct inode
的addrs[]
数组的大小,需要让他们保证元素数目一致 - 不要忘记在
bread()
后对读取的块使用brelse()
函数 - 确保
itrunc()
可以释放所有块,包括二级间接块
实验代码
本实验中磁盘的最大已经改为20w,因此无需通过mkfs
重新创建磁盘。我们只需要修改bmap()
即可。
- fs.h
#define NDIRECT 11
#define ONE_LEVEL_INDIRECT_IDX 11
#define TWO_LEVEL_INDIRECT_IDX 12
#define NINDIRECT (BSIZE / sizeof(uint))
#define N_TWO_INDIRECT NINDIRECT * NINDIRECT
#define MAXFILE (NDIRECT + NINDIRECT + N_TWO_INDIRECT)// On-disk inode structure
struct dinode {short type; // File typeshort major; // Major device number (T_DEVICE only)short minor; // Minor device number (T_DEVICE only)short nlink; // Number of links to inode in file systemuint size; // Size of file (bytes)uint addrs[NDIRECT+2]; // Data block addresses
};
- fs.c
struct {struct spinlock lock;struct inode inode[TWO_LEVEL_INDIRECT_IDX];
} itable;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;// 如果属于一级间接块的范围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 < N_TWO_INDIRECT){// 加载二级间接索引块,如果二级索引块还未分配,则分配一个数据块作为二级索引块// 如果二级索引块不存在,为其分配地址,并记录在 addrs 数组对应的位置if((addr = ip->addrs[TWO_LEVEL_INDIRECT_IDX]) == 0){ip->addrs[TWO_LEVEL_INDIRECT_IDX] = addr = balloc(ip->dev);}// 读取二级索引块,内容为一级索引块地址bp = bread(ip->dev, addr);a = (uint*)bp->data;// bn 必定是可以整除 NINDIRECT 的数,决定查询一级索引的第几项,如果此时一级索引块也未分配// 分配一级索引块,并将地址记录到对应的二级索引块中if((addr = a[bn / NINDIRECT]) == 0){a[bn / NINDIRECT] = addr = balloc(ip->dev);log_write(bp);}brelse(bp);// 读取一级索引块的内容bp = bread(ip->dev, addr);a = (uint*)bp->data;// 得到在一级索引块中实际数据块的idx,如果不存在,则分配if((addr = a[bn % NINDIRECT]) == 0){a[bn % NINDIRECT] = addr = balloc(ip->dev);log_write(bp);}brelse(bp);return addr;}panic("bmap: out of range");
}
实验结果
- usertests -q(该程序至少需要运行1分半)
- userstest -q
二、Symbolic links
简介
实现符号链接(软链接)
目标:实现 symlink(char *target, char *path)
系统调用,该系统调用在path
表示路径下创建一个新的符号链接,引用target
路径。最终通过symlinktest
测试。
提示
- 创建一个新的系统调用
symlink
,在user/usys.pl, user/user.h
添加对应的项,在kernel/sysfile.c
中实现sys_symlink()
函数 - 在
kernel/stat.h
中添加T_SYMLINK
文件类型,表示符号链接类型的文件 kernel/fcntl.h
添加一个新的flag标志位O_NOFOLLOW
,该标记位将在open()
系统调用中使用。注意flag
标记位在open()
中使用时需要使用按位或(or)
。- 实现
symlink
系统调用的逻辑,需要在一个地方先缓存target
路径,可以选择存储在inode
的数据块中,同时该系统调用应该在成功时返回0,失败时返回-1 - 修改
open()
系统调用,在打开一个符号链接文件时,如果其引用的对象不存在,则打开失败,如果存在,则打开其引用的对象 - 如果符号链接文件引用的文件依然是符号链接文件,那么需要递归的去引用,直到遇到一个非链接文件。如果递归变为循环递归,需要返回错误,且需要添加一个递归阈值(例如10)以防止无限制递归。
- 其他系统调用,操作符号链接文件本身时,就应该操作该文件本身(例如 link、unlink)。
- 无需处理对于目录的符号链接
实验代码
- kernel/fcntl.h
#define O_NOFOLLOW 0x004
- kernel/stat.h
#define T_SYMLINK 4 // symbol link
- user/user.h
int symlink(char *target, char *path);
- user/usys.pl
entry("symlink");
- makefile
$U/_symlinktest\
- kernel/syscall.h
#define SYS_symlink 22
- kernel/syscall.c
extern uint64 sys_symlink(void);static uint64 (*syscalls[])(void) = {
// ...
[SYS_close] sys_close,
[SYS_symlink] sys_symlink,
};
- kernel/sysfile.c:sys_symlink 可以参考 sys_link
uint64
sys_symlink(void){char target[MAXPATH];char path[MAXPATH];struct inode *ip;if((argstr(0, target, MAXPATH)) < 0 || argstr(1, path, MAXPATH) < 0)return -1;begin_op();// 如果是目录,返回错误if((ip = namei(target)) != 0){ilock(ip);if(ip->type == T_DIR){iunlock(ip);end_op();return -1;}iunlock(ip);}// 创建软链接文件,返回的是一个上锁的fdip = create(path, T_SYMLINK, 0, 0);if(ip == 0){end_op();return -1;}// 将 target 路径写入到软链接文件中if(writei(ip, 0, (uint64)target, 0, strlen(target) + 1) <= 0) {end_op();return -1;}iunlockput(ip);end_op();return 0;
}
- kernel/sysfile.c:修改 sys_open(),添加对于软链接文件的处理
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 {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_SYMLINK && !(omode & O_NOFOLLOW)) {int total = 10;while (ip->type == T_SYMLINK && total > 0) {// 读取软链接文件中存储的 target 路径if(readi(ip, 0, (uint64)path, 0, ip->size) != ip->size) {iunlockput(ip);end_op();return -1;}iunlockput(ip); //释放软链接文件的锁// ip重新赋值为软链接 target 指向的文件if((ip = namei(path)) == 0) {end_op();return -1;}// 锁住,进行下一轮判断ilock(ip);total--;}// 超过循环上限if (total == 0) {iunlockput(ip);end_op();return -1;}}}// ... 其他代码
}
实验结果
- symlinktest