MIT6.828LAB5 (1)

LAB5: The File System


前言

记录一下自己的学习过程
实验内容翻译:
https://gitee.com/cherrydance/mit6.828
该翻译仅供参考

在开始之前记录一个问题,由于使用了-Werror选项,将警告视为错误进行处理。导致这里的警告变成错误,直接将GNUmakefile的-Werror选项移除
在这里插入图片描述

练习一

i386_init通过将ENV_TYPE_FS传递给你的环境创建函数env_create来识别文件系统环境。修改env.c中的env_create函数,以便为文件系统环境提供I/O特权,但从不将该特权授予其他任何环境。
确保您可以启动文件环境而不会引发常规保护错误。您应该通过"fs i/o"测试来通过make grade。

就是在env_create函数中判断创建的环境是不是文件系统环境,如果是则将EFLAGS寄存器中的IOPL位设置为最高权限。

	if(type == ENV_TYPE_FS){
		new_env->env_tf.tf_eflags |= FL_IOPL_3;
	}

问题1:在随后从一个环境切换到另一个环境时,您是否需要执行其他操作,以确保此I/O特权设置被正确保存和恢复?为什么?

不需要,因为在环境切换时,会保存EFLAGS寄存器的值,之后也会正常恢复该值。

练习2

练习2. 在fs/bc.c中实现bc_pgfault和flush_block函数。bc_pgfault是一个页面错误处理程序,与您在之前实验中为写时复制fork编写的处理程序类似,不同之处在于它的任务是在发生页面错误时从磁盘加载页面。在编写此代码时,请记住:(1) addr可能不对齐到块边界,(2) ide_read操作的单位是扇区而不是块。
flush_block函数应在需要时将块写入磁盘。如果块不在块缓存中(即页面没有映射),或者如果块不是脏的,则flush_block不执行任何操作。我们将使用VM硬件来跟踪磁盘块自上次从磁盘读取或写入以来是否已被修改。为了判断一个块是否需要写入,我们只需查看uvpt条目中是否设置了PTE_D(“dirty”)位。(处理器在对该页面进行写操作时会设置PTE_D位;请参阅386参考手册第5章的5.2.4.3节。)在将块写入磁盘后,flush_block应使用sys_page_map清除PTE_D位。
使用make grade测试您的代码。您的代码应通过"check_bc"、"check_super"和"check_bitmap"测试。

bc_pgfault函数:
需要写的部分注释说的很清楚,addr可能不是页对齐,先对齐,然后分配一个页面,从磁盘把内容读取到页面上。代码如下:

	addr = ROUNDDOWN(addr, PGSIZE);
	if((r = sys_page_alloc(0, addr, PTE_U | PTE_W | PTE_P)) < 0){
		panic("in bc_pgfault, sys_page_malloc: %e", r);
	}
	if((r = ide_read(blockno*BLKSECTS, addr, BLKSECTS))< 0){
     	panic("in bc_pgfault, ide_read: %e",r);
 	}

flush_block函数:
同样是先对齐,判断块不在块缓存中(即页面没有映射),或者如果块不是脏的。把页面写到磁盘中然后取消脏位。代码如下:

addr = ROUNDDOWN(addr, PGSIZE);
	if(va_is_mapped(addr) && va_is_dirty(addr)){
		if(ide_write(blockno*BLKSECTS, addr, BLKSECTS) < 0){
			panic("flush_block: ide_write failed");
		}
		if(sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL) < 0){
			panic("flush_block: sys_page_map failed");
		}
	}

运行结果如图:
在这里插入图片描述

练习3

使用free_block作为模型,在fs/fs.c中实现alloc_block函数。该函数应在位图中查找一个空闲的磁盘块,标记为已使用,并返回该块的编号。当你分配一个块时,应立即使用flush_block将修改后的位图块刷新到磁盘上,以帮助维护文件系统的一致性。
使用make grade来测试你的代码。你的代码现在应该通过"alloc_block"的测试。

alloc_block函数:
位图是操作系统的一个基本知识,即使用1bit来表示一个块是否使用。要找到一个空闲的块,直接从最开始便利找到第一个。代码如下:使用了block_is_free来判断一个块是否空闲。最后将修改的位图块flush到磁盘。

	for(size_t i = 0; i < super->s_nblocks; ++i){
		if (block_is_free(i)) {
			bitmap[i/32] &= ~(1<<(i%32));
			flush_block(&bitmap[i/32]);
			return i;
		}
	}

运行结果如下,alloc_block成功:
在这里插入图片描述

练习4

实现file_block_walk和file_get_block函数。file_block_walk函数将文件内的块偏移映射到struct File或间接块中该块的指针,类似于pgdir_walk函数对页表的操作。file_get_block函数进一步映射到实际的磁盘块,如果需要的话,还会分配一个新的磁盘块。
使用make grade来测试你的代码。你的代码应该通过"file_open"、“file_get_block”、"file_flush/file_truncated/file_rewrite"和"testfile"的测试。

file_block_walk函数:
函数目的为获取文件f的filebno所在的位置指针。

1,判断基本参数的合法性
2,判断filebno是否在直接块,否则继续
3,判断是否有间接块,有间接块则通过间接块获取地址
4,没有间接块看alloc是否要分配新块做间接块
5,分配新块要先清零。
   if(filebno >= NDIRECT + NINDIRECT){
			return -E_INVAL;
	   }
	   if(filebno <= NDIRECT){
			*ppdiskbno = &f->f_direct[filebno];
	   }else{
			if(f->f_indirect){
				*ppdiskbno = ((uint32_t*)diskaddr(f->f_indirect) + filebno - NDIRECT);
			}else{
				if(alloc){
					int ret = alloc_block();
					if(ret < 0){
						return -E_NO_DISK;
					}
					memset(diskaddr(ret),0,BLKSIZE);
					flush_block(diskaddr(ret));
					f->f_indirect = ret;
					*ppdiskbno = ((uint32_t)diskaddr(f->f_indirect) + filebno - NDIRECT);
				}else{
					return -E_NOT_FOUND;
				}
			}
	   }
		return 0;

file_get_block函数:
这个函数才是真获取到实际磁盘块的地址。

1,判断参数合法性
2,用file_block_walk函数为获取文件f的filebno所在的位置指针,但存在这个块不存在的可能
3,处理这个可能,即分配一块内存在这个filebno

代码如下:

 	size_t ret;
	if(filebno >= NDIRECT + NINDIRECT){
	return -E_INVAL;
    }
    uint32_t *ppdiskbno;
	ret = file_block_walk(f, filebno, &ppdiskbno, 1);
	if(ret < 0){
		return -ret;
    }
	if(*ppdiskbno == 0){
		ret = alloc_block();
		if(ret < 0){
			return ret;
		}
		*ppdiskbno = ret;
		memset(diskaddr(ret), 0, BLKSIZE);
		flush_block(diskaddr(ret));
	 }
	*blk = diskaddr(*ppdiskbno);
	return 0;

练习5

在fs/serv.c中实现serve_read。
serve_read的重要工作已由已实现的fs/fs.c中的file_read完成(而file_read本身只是一系列对file_get_block的调用)。serve_read只需提供文件读取的RPC接口。查看serve_set_size中的注释和代码,以了解服务器函数应如何组织的一般思路。
使用make grade来测试你的代码。你的代码应通过“serve_open/file_stat/file_close”和“file_read”测试,得分为70/150。

查看server_set_size函数,发现他使用了一个叫openfile_lookup的函数获取一个OpenFile结构体,跟踪这个结构体得到该结构体的作用:
struct OpenFile链接Struct File和Struct FD,并且对文件服务器保持私有。服务器维护一个所有打开文件的数组,按照“文件ID”进行索引。(同时最多可以打开MAXOPEN个文件。)客户端使用文件ID与服务器进行通信。文件ID与内核中的环境ID非常相似。使用openfile_lookup将文件ID转换为struct OpenFile。

serve_read函数

1,使用openfile_lookup获取OpenFile结构体
2,读取req_n字节到ret_buff
3,更新寻址位置,返回读取的字节数
	struct OpenFile *o;
	int r;
	if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0){
		return r;
	}
	if((r = file_read(o->o_file, ret->ret_buf, req->req_n, o->o_fd->fd_offset)) < 0){
		return r;
	}
	o->o_fd->fd_offset += r;
	return r;

运行结果:
在这里插入图片描述

练习6

练习6. 在fs/serv.c中实现serve_write,并在lib/file.c中实现devfile_write。
使用make grade来测试你的代码。你的代码应通过“file_write”、“file_read after file_write”、“open”和“large file”测试,得分为90/150。

server_write函数:
几乎与server_read一样,唯一需要考虑的就是写进去的内容req_n不能大于PGSIZE,下面的devfile_write也一样,注释中页提示了“写操作始终可以写入少于请求的字节数”

	struct OpenFile *o;
	int r;
	if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0){
		return r;
	}
	int req_n = req->req_n > PGSIZE?PGSIZE:req->req_n;
	if((r = file_write(o->o_file, req->req_buf, req_n, o->o_fd->fd_offset)) < 0){
		return r;
	}
	o->o_fd->fd_offset += r;
	return r;

devfile_write函数:
与devfile_read函数相似

	int r;
	if (n > sizeof(fsipcbuf.write.req_buf)){
		n = sizeof(fsipcbuf.write.req_buf);
	}
	fsipcbuf.write.req_fileid = fd->fd_file.id;
	fsipcbuf.write.req_n = n;
	memmove(fsipcbuf.write.req_buf, buf, n);
	return fsipc(FSREQ_WRITE, NULL);

运行结果如下:
在这里插入图片描述

总结

完成了lab5的前半部分!!!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值