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的前半部分!!!!!