概念引入
xv6的文件系统分为七层,自底向上的顺序是硬盘(Disk)
,缓冲区高速缓存(Buffer cache)
,日志(Logging)
,索引节点(Inode)
,目录(Directory)
,路径名(Pathname)
,文件描述符(File descriptor)
。
inode
inode是一个文件或者目录的元数据,inode包含了关于文件的所有元数据,包括文件类型、权限、大小和指向文件数据块的指针。inode里面存在着许多block,指向数据块真正存在的地方。但是这样会产生一个问题,一个inode里的block没被用完,这样就会导致空间浪费。这被称为内部碎片(internal fragmentation)。
dinode和inode的关系
dinode是inode储存在磁盘上的版本,简化了一些只在操作inode时需要的字段。相应的,inode时dinode被加载到内存中的版本,多了一些可以辅助inode操纵的字段,比如锁lock,引用计数ref等
buffercache区和内存中的inode
当文件系统需要获取某个文件的 inode 时,iget 函数会通过 bread 从磁盘读取包含该 inode 的磁盘块,然后从中提取出 inode 的信息,并将其加载到内存中的 inode 结构里。
例如,假设我们需要读取 inode 编号为 5 的 inode,它在磁盘上位于 inode 区的一个块中。iget 会通过 bread 从磁盘中读取包含 inode 5 的整个块,这个块会被缓存到 buffercache 中。然后,iget 从缓冲区中的数据提取出 dinode 信息,并填充到内存中的 inode 结构中(也就是 struct inode)。
这一步的具体流程如下:
-
iget 根据 inum 计算出 inode 所在的磁盘块。
-
调用 bread,从 buffercache 中获取包含该 inode 的块(如果 buffercache 中没有该块,就从磁盘读取并缓存)。
-
从 buffercache 返回的缓冲块中提取 inode 信息,填充到内存中的 inode 结构中。
关于inode并发问题
暂存。。。以后来补
file descriptor与inode
inode
和 file
的关系
inode
是文件的底层数据结构,描述文件的属性和位置,是整个系统中对文件的唯一标识。file
是对进程而言的高层次文件描述符,记录每个进程对文件的访问信息。file
结构体包含一个ip
指针,用于指向文件的inode
,从而通过inode
获取文件内容。- 一个文件可以有多个
file
结构体,但它们会共享同一个inode
。- 例如,如果两个进程同时打开同一个文件,每个进程会有自己的
file
结构(包含独立的偏移量等信息),但它们都指向同一个inode
。
- 例如,如果两个进程同时打开同一个文件,每个进程会有自己的
Large files(moderate)
完成这个task首先要理解数据块是如何存储在inode中的,这里直接给出示意图。
#define FSMAGIC 0x10203040
#define NDIRECT 11 // 修改这里,因为我们减少一个直接快,新增一个二级间接块
#define NINDIRECT (BSIZE / sizeof(uint))
#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 总大小依旧是13
};
//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;
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;
}
// new code START
bn -= NINDIRECT;
if(bn < NINDIRECT * NINDIRECT) {
// Allocate double indirect block if necessary
if((addr = ip->addrs[NDIRECT+1]) == 0)
ip->addrs[NDIRECT+1] = addr = balloc(ip->dev);
// Load the first level indirect block
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
uint first_index = bn / NINDIRECT; // Calculate first level index
if((addr = a[first_index]) == 0) {
a[first_index] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
// Load the second level indirect block
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
bn = bn % NINDIRECT; // Calculate second level index
if((addr = a[bn]) == 0) {
a[bn] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
// new code END
panic("bmap: out of range");
}
//kernel/fs.c
void
itrunc(struct inode *ip)
{
int i, j;
struct buf *bp;
uint *a, *b;
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;
}
// new code START
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]) {
struct buf *bp2 = bread(ip->dev, a[i]);
b = (uint*)bp2->data;
for(j = 0; j < NINDIRECT; j++) {
if(b[j])
bfree(ip->dev, b[j]);
}
brelse(bp2);
bfree(ip->dev, a[i]);
}
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT + 1]);
ip->addrs[NDIRECT + 1] = 0;
}
// new code END
ip->size = 0;
iupdate(ip);
}
Symbolic links(moderate)
sys_link,sys_symlink 和带有O_CREATE 的open的区别。
Sys_link
为现有inode创建一个新名称。函数create
(kernel/sysfile.c:242)为新inode创建一个新名称。它是三个文件创建系统调用的泛化:带有O_CREATE
标志的open
生成一个新的普通文件
一些函数的分析过程
理解了这些可能会对做lab有些帮助。
sys_link()
分析过程
uint64
sys_link(void)
{
char name[DIRSIZ], new[MAXPATH], old[MAXPATH];
struct inode *dp, *ip;
if(argstr(0, old, MAXPATH) < 0 || argstr(1, new, MAXPATH) < 0)
return -1;
begin_op(); // 开始事务
if((ip = namei(old)) == 0){ // namei的时候就会增加inode的引用了,在 namei 函数调用后,返回的 inode ip 的引用计数已经被增加了。这是 namei 的内部机制:每次成功找到并返回一个 inode 时,namei 都会调用 iget 函数来增加引用计数,确保这个 inode 在使用过程中不会被其他操作删除。
end_op();
return -1;
}
ilock(ip); // 开始修开inode,要先加锁
if(ip->type == T_DIR){ // 目录的话,就失效
iunlockput(ip); // 释放ip锁,并减少引用
end_op();
return -1;
}
ip->nlink++;
iupdate(ip); // 更新数据到磁盘
iunlock(ip); // 释放inode锁
if((dp = nameiparent(new, name)) == 0) // 找到当前inode的父目录,并且找到其名字赋给name
goto bad;
ilock(dp); // 接下来要操作dp,所以先加锁
if(dp->dev != ip->dev || dirlink(dp, name, ip->inum) < 0){ // 判断是否操作的是一个设备,是否能够将新的硬链接条目添加到父目录 dp 中。dirlink(dp, name, ip->inum) 的作用是将 name 作为一个新的目录项插入到目录 dp 中,并将其指向文件 ip->inum(即原文件的 inode 号)
iunlockput(dp);
goto bad;
}
iunlockput(dp); // 是对父目录 inode 的操作,解锁并减少其引用计数,因为已经完成了所有操作
iput(ip); // 是对被链接的目标文件 inode 的操作,减少其引用计数,我们已经完成了对inode的所有操作
end_op(); // 结束事务
return 0;
bad: // 恢复之前进行过的操作
ilock(ip);
ip->nlink--;
iupdate(ip);
iunlockput(ip);
end_op();
return -1;
}
sys_open()
分析过程。这个函数在本次lab中非常重要。但是后面我们还会对这个函数做改动,所以留到后面一起分析。
namex()
分析过程
static struct inode*
namex(char *path, int nameiparent, char *name)
{
struct inode *ip, *next;
if(*path == '/') // 判断是否是绝对路径
ip = iget(ROOTDEV, ROOTINO);
else
ip = idup(myproc()->cwd); // ip指向当前目录
while((path = skipelem(path, name)) != 0){ // 进入循环,一层一层解析。skipelem 函数从路径中提取一个元素,并将剩余的路径返回。例如,对路径 /a/b/c 第一次调用 skipelem 会提取 a,并将 path 设置为 "/b/c",将 name 设置为 "a"
ilock(ip);
if(ip->type != T_DIR){
iunlockput(ip);
return 0;
}
if(nameiparent && *path == '\0'){ // 如果nameiparent为真且path当前没有解析到结尾
// Stop one level early.
iunlock(ip);
return ip;
}
if((next = dirlookup(ip, name, 0)) == 0){ // 在当前目录中寻找匹配的名字
iunlockput(ip);
return 0;
}
iunlockput(ip); // 对ip的使用结束,释放所,引用-1
ip = next; // 更新ip
}
if(nameiparent){
iput(ip);
return 0;
}
return ip;
}
接下来我们正式开始做lab
先按照添加系统调用的方法,注册好symlink()
系统调用。然后根据提示加上一些定义。
//kernel/stat.h
#define T_DIR 1 // Directory
#define T_FILE 2 // File
#define T_DEVICE 3 // Device
#define T_SYMLINK 4 // 新加的!
//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/sysfile.c
uint64
sys_symlink(void){
char target[MAXPATH], path[MAXPATH];
struct inode* i;
if(argstr(0, target,MAXPATH) < 0 || argstr(1, path,MAXPATH) < 0) {
return -1;
}
// printf("target is %s \n", target);
// printf("path is %s \n", path);
begin_op();
i = create(path, T_SYMLINK,0, 0); // 创建一个软连接类型的文件
if(i == 0) {
end_op();
return -1;
}
if (writei(i,0,(uint64)target,0,strlen(target)) < 0) { // 往文件里面写路径,待会我们读的时候就要利用写进去的路径读
end_op();
return -1;
};
iunlockput(i);
end_op();
return 0;
}
因为我们添加了一个新的文件类型,所以我们也要修改open函数来支持这个新的文件类型。
uint64
sys_open(void)
{
char path[MAXPATH]; // 存储文件路径的字符串
int fd, omode; // 文件描述符(fd)和打开模式(omode)
struct file *f; // 文件结构体指针
struct inode *ip; // inode 指针
int n;
// 获取系统调用的参数:文件路径和打开模式
if((n = argstr(0, path, MAXPATH)) < 0 || argint(1, &omode) < 0)
return -1; // 获取失败则返回 -1
begin_op(); // 开始文件系统事务,以确保文件系统操作的原子性
// 如果打开模式中包含 O_CREATE 标志,则创建文件
if(omode & O_CREATE){
ip = create(path, T_FILE, 0, 0); // 调用 create 创建新文件的 inode
if(ip == 0){ // 创建失败则结束事务并返回 -1
end_op();
return -1;
}
} else {
// 如果不需要创建文件,则尝试查找文件路径对应的 inode
if((ip = namei(path)) == 0){ // 如果文件不存在,则返回 -1
end_op();
return -1;
}
ilock(ip); // 锁定 inode,以确保并发访问安全
// 如果文件类型为目录且打开模式不是只读,则返回错误
if(ip->type == T_DIR && omode != O_RDONLY){
iunlockput(ip); // 解锁并释放 inode
end_op();
return -1;
}
}
// 处理符号链接(symbolic link)
if(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)) { // 若符号链接未被禁止跟随
int MAX_SYMLINK_DEPTH = 10; // 设置符号链接的最大跟随深度
for(int i = 0; i < MAX_SYMLINK_DEPTH; ++i) {
// 读取符号链接指向的路径
if(readi(ip, 0, (uint64)path, 0, MAXPATH) != MAXPATH) {
iunlockput(ip); // 读取失败则解锁并释放 inode
end_op();
return -1;
}
iunlockput(ip); // 释放当前符号链接 inode 锁
ip = namei(path); // 获取符号链接指向的路径的 inode
if(ip == 0) { // 若符号链接路径不存在则返回错误
end_op();
return -1;
}
ilock(ip); // 锁定新的 inode
if(ip->type != T_SYMLINK) // 如果找到的文件不是符号链接,停止递归
break;
}
// 如果超过最大深度仍然是符号链接,返回错误
if(ip->type == T_SYMLINK) {
iunlockput(ip);
end_op();
return -1;
}
}
// 检查是否为有效的设备文件,若设备号无效,则返回错误
if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){
iunlockput(ip);
end_op();
return -1;
}
// 分配文件结构(file struct)和文件描述符
if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
if(f) // 若分配了文件结构体但失败,则关闭该文件结构体
fileclose(f);
iunlockput(ip); // 解锁并释放 inode
end_op();
return -1;
}
// 初始化文件结构体(file struct)中的信息
if(ip->type == T_DEVICE){ // 若文件类型为设备
f->type = FD_DEVICE;
f->major = ip->major; // 记录设备号
} else { // 若文件类型为普通文件或目录
f->type = FD_INODE;
f->off = 0; // 文件偏移设置为 0(从文件头开始)
}
f->ip = ip; // 设置文件的 inode 指针
f->readable = !(omode & O_WRONLY); // 如果不是只写,则设置为可读
f->writable = (omode & O_WRONLY) || (omode & O_RDWR); // 设置是否可写
// 如果打开模式包含 O_TRUNC,并且文件类型是普通文件,则截断文件
if((omode & O_TRUNC) && ip->type == T_FILE){
itrunc(ip); // 调用 itrunc 截断文件内容
}
iunlock(ip); // 解锁 inode
end_op(); // 结束文件系统事务
return fd; // 返回文件描述符
}