姓名:贾昊龙
学号:18307130049
PartⅠ:运行结果
Part Ⅱ:问题回答
- 请回答Exercise 1后的Question 1,Do you have to do anything else to ensure that this I/O privilege setting is saved and restored properly when you subsequently switch from one environment to another? Why?
不需要,当进程陷入中断后,所有寄存器信息会被保存到进程的 env_tf 中,在之前的lab当中已经实现过了
- 详细描述JOS 中文件存储的结构、打开文件的过程以及往文件中写入数据的过程。
在JOS中,文件系统是一个运行在userspace的进程,而其他进程通过类似Clinet-Server的方式来与文件系统进程进行进程间通信,从而实现文件操作。
存储结构定义在fs.h
当中
而文件读写过程可以参考问题5的回答
- 对于此JOS,一个磁盘有多少个扇区?
JOS的磁盘总大小为定义在fs.h中的 DISKSIZE ,值为3GB;而每个扇区的大小为 SECTSIZE , 值为 512B。因此JOS总共有:3 * 2^21个扇区
- 请详细阐述,JOS中superblock的概念,以及superblock的布局和结构。
超级块指的是文件系统保存文件系统元数据的数据块
JOS中文件系统的第1块(块号为0)是磁盘块,用来用作保存bootloader和分区表,而第2块(块号为1)就是超级块,用来保存文件系统元信息的,布局如下:
- 以open文件为例,阐述regular环境访问磁盘的流程
以磁盘读为例,当一个进程在发起一次磁盘读取请求的时候,首先会调用JOS提供的库函数 read()
,而 read()
函数会调用 devfile_read()
函数,这个函数是进程端的磁盘读接口,它会继续将用户的读取请求递交给 fsipc()
函数,之后通过进程间通信IPC机制,将读取请求发送给接收端的文件系统进程。
文件系统进程serve
会不断的进行 ipc_recv()
来 检查是否有进程发起读写请求。在接收到进程读文件的 IPC信息后,serve会将这个信息发送给 serve_read()
,最后调用file_read()
函数完成真正的文件读取过程,而之前的过程都是在进行信息传递。
- 画出对应的流程图
- 5-c fd page是什么时候设置好的?
fd page在JOS中的作用类似于文件描述符,它是OpenFile结构体中的一项,结构如下:
当文件系统进程接收到文件读写请求时,会调用server_open()
函数打开文件,并写入fd page信息。
Part A:the file system
Exercise 1
i386_init
identifies the file system environment by passing the typeENV_TYPE_FS
to your environment creation function,env_create
. Modifyenv_create
inenv.c
, so that it gives the file system environment I/O privilege, but never gives that privilege to any other environment.Make sure you can start the file environment without causing a General Protection fault. You should pass the “fs i/o” test in make grade.
env_create()
只需要在文件系统进程创建的时候给予访问文件的权限即可(设置eflags寄存器的IOPL标志位)
void
env_create(uint8_t *binary, enum EnvType type)
{
// LAB 3: Your code here.
// If this is the file server (type == ENV_TYPE_FS) give it I/O privileges.
struct Env *e;
int r;
if ((r = env_alloc(&e, 0) != 0)) {
panic("create env failed\n");
}
// LAB 5: Your code here.
if (type == ENV_TYPE_FS)
{
e->env_tf.tf_eflags = e->env_tf.tf_eflags | FL_IOPL_MASK;
}
load_icode(e, binary);
e->env_type = type;
}
Exercise 2
IUse
free_block
as a model to implementalloc_block
infs/fs.c
, which should find a free disk block in the bitmap, mark it used, and return the number of that block. When you allocate a block, you should immediately flush the changed bitmap block to disk withflush_block
, to help file system consistency.Use make grade to test your code. Your code should now pass “alloc_block”.mplement the
bc_pgfault
andflush_block
functions infs/bc.c
.bc_pgfault
is a page fault handler, just like the one your wrote in the previous lab for copy-on-write fork, except that its job is to load pages in from the disk in response to a page fault. When writing this, keep in mind that (1)addr
may not be aligned to a block boundary and (2)ide_read
operates in sectors, not blocks.The
flush_block
function should write a block out to disk if necessary.flush_block
shouldn’t do anything if the block isn’t even in the block cache (that is, the page isn’t mapped) or if it’s not dirty. We will use the VM hardware to keep track of whether a disk block has been modified since it was last read from or written to disk. To see whether a block needs writing, we can just look to see if thePTE_D
“dirty” bit is set in theuvpt
entry. (ThePTE_D
bit is set by the processor in response to a write to that page; see 5.2.4.3 in chapter 5 of the 386 reference manual.) After writing the block to disk,flush_block
should clear thePTE_D
bit usingsys_page_map
.Use make grade to test your code. Your code should pass “check_bc”, “check_super”, and “check_bitmap”
bc_pgfault
该函数的作用是文件系统进程的缺页处理,将磁盘块数据读取到相应的内存位置
// LAB 5: you code here:
addr = (void *)ROUNDDOWN(addr, BLKSIZE);
if((r = SYS_page_alloc(0, addr, PTE_U | PTE_W | PTE_P)) < 0)
panic("in bc_pgfault, sys_page_alloc: %e", r);
if ((r = (ide_read(blockno*BLKSECTS, addr, BLKSECTS))) < 0)
panic("in bc_pgfault, sys_page_alloc: %e", r);
// Clear the dirty bit for the disk block page since we just read the
// block from disk
flush_block
这个函数将缓存的block写回到磁盘中,如果数据块没有被写过,则不需要做任何事。通过 PTE_D 标志位可以判断数据块是否被写入过。
void
flush_block(void *addr)
{
uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE;
int r;
if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE))
panic("flush_block of bad va %08x", addr);
// LAB 5: Your code here.
// panic("flush_block not implemented");
addr = (void*)ROUNDDOWN(addr, BLKSIZE);
if(!va_is_mapped(addr) || !va_is_dirty(addr)) return;
if((r = ide_write(blockno*BLKSECTS, addr, BLKSECTS)) < 0)
panic("in flush_block, ide_write: %e", r);
if ((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0)
panic("in flush_block, sys_page_map: %e", r);
}
Exercise 3
Use
free_block
as a model to implementalloc_block
infs/fs.c
, which should find a free disk block in the bitmap, mark it used, and return the number of that block. When you allocate a block, you should immediately flush the changed bitmap block to disk withflush_block
, to help file system consistency.Use make grade to test your code. Your code should now pass “alloc_block”.
alloc_block
函数的作用是从 bitmap 中找到一个空的数据块,并给调用函数的进程分配它。
int
alloc_block(void)
{
// The bitmap consists of one or more blocks. A single bitmap block
// contains the in-use bits for BLKBITSIZE blocks. There are
// super->s_nblocks blocks in the disk altogether.
// LAB 5: Your code here.
for (uint32_t i = 2 ; i < super->s_nblocks ; i++)
{
if (block_is_free(i))
{
bitmap[i>>5] &= ~(1 << (i % 32));
flush_block(diskaddr(i));
return i;
}
}
// panic("alloc_block not implemented");
return -E_NO_DISK;
}
Exercise 4
Implement
file_block_walk
andfile_get_block
.file_block_walk
maps from a block offset within a file to the pointer for that block in thestruct File
or the indirect block, very much like whatpgdir_walk
did for page tables.file_get_block
goes one step further and maps to the actual disk block, allocating a new one if necessary.Use make grade to test your code. Your code should pass “file_open”, “file_get_block”, and “file_flush/file_truncated/file rewrite”, and “testfile”.
file_block_walk
这个函数的功能是找到文件 f 中第 filebno 号数据块的数据块号slot并保存到 ppdiskbno 中。
// Hint: Don't forget to clear any block you allocate.
static int
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)
{
// LAB 5: Your code here.
if(filebno>= NDIRECT + NINDIRECT) return -E_INVAL;
if(filebno<NDIRECT){
*ppdiskbno = &(f->f_direct[filebno]);
return 0;
}
else if (f->f_indirect == 0){
if(alloc) {
int blockno = alloc_block();
if(blockno < 0) return -E_NO_DISK;
memset(diskaddr(blockno), 0, BLKSIZE);
f->f_indirect = blockno;
}
else return -E_NOT_FOUND;
}
uint32_t * addr = (uint32_t*)diskaddr(f->f_indirect);
*ppdiskbno = &addr[filebno];
return 0;
// panic("file_block_walk not implemented");
}
file_get_block
这个函数的功能是查找文件 f 第 filebno 个数据块对应的虚拟地址 addr ,并将其保存到给定的地 址 blk 处。根据提示实现即可
// Hint: Use file_block_walk and alloc_block.
int
file_get_block(struct File *f, uint32_t filebno, char **blk)
{
// LAB 5: Your code here.
// panic("file_get_block not implemented");
uint32_t *ppdiskbno;
int r = file_block_walk(f,filebno, &ppdiskbno,1);
if(r < 0) return r;
if((*ppdiskbno) == 0)
{
if((*ppdiskbno = alloc_block()) < 0)
{
return -E_NO_DISK;
}
}
*blk = diskaddr(*ppdiskbno);
return 0;
}
Exercise 5
Implement
serve_read
infs/serv.c
.
serve_read
‘s heavy lifting will be done by the already-implementedfile_read
infs/fs.c
(which, in turn, is just a bunch of calls tofile_get_block
).serve_read
just has to provide the RPC interface for file reading. Look at the comments and code inserve_set_size
to get a general idea of how the server functions should be structured.Use make grade to test your code. Your code should pass “serve_open/file_stat/file_close” and “file_read” for a score of 70/150.
serve_read
这个函数是文件系统进程的服务端接口,调用 file_read()
来实现真正的文件读取
// Read at most ipc->read.req_n bytes from the current seek position
// in ipc->read.req_fileid. Return the bytes read from the file to
// the caller in ipc->readRet, then update the seek position. Returns
// the number of bytes successfully read, or < 0 on error.
int
serve_read(envid_t envid, union Fsipc *ipc)
{
struct Fsreq_read *req = &ipc->read;
struct Fsret_read *ret = &ipc->readRet;
if (debug)
cprintf("serve_read %08x %08x %08x\n", envid, req->req_fileid, req->req_n);
// Lab 5: Your code here:
int r;
struct OpenFile *o;
if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0) return r;
r = file_read(o->o_file, ret->ret_buf, req->req_n, o->o_fd->fd_offset);
if (r < 0) return r;
o->o_fd->fd_offset += r;
return r;
}
Exercise 6
Implement
serve_write
infs/serv.c
anddevfile_write
inlib/file.c
.Use make grade to test your code. Your code should pass “file_write”, “file_read after file_write”, “open”, and “large file” for a score of 90/150.
serve_write
与之前的 serve_read() 实现方法基本一样
// Write req->req_n bytes from req->req_buf to req_fileid, starting at
// the current seek position, and update the seek position
// accordingly. Extend the file if necessary. Returns the number of
// bytes written, or < 0 on error.
int
serve_write(envid_t envid, struct Fsreq_write *req)
{
if (debug)
cprintf("serve_write %08x %08x %08x\n", envid, req->req_fileid, req->req_n);
// LAB 5: Your code here.
// panic("serve_write not implemented");
struct OpenFile* o = NULL;
int r = openfile_lookup(envid, req->req_fileid, &o);
if(r < 0) return r;
r = file_write(o->o_file, req->req_buf, req->req_n, o->o_fd->fd_offset);
if(r > 0) o->o_fd->fd_offset += r;
return r;
}
devfile_write
是客户端进程函数,通过调用 fsipc() 将传入的参数发送给文件系统进程。
// Write at most 'n' bytes from 'buf' to 'fd' at the current seek position.
//
// Returns:
// The number of bytes successfully written.
// < 0 on error.
static ssize_t
devfile_write(struct Fd *fd, const void *buf, size_t n)
{
// Make an FSREQ_WRITE request to the file system server. Be
// careful: fsipcbuf.write.req_buf is only so large, but
// remember that write is always allowed to write *fewer*
// bytes than requested.
// LAB 5: Your code here
// panic("devfile_write not implemented");
fsipcbuf.write.req_fileid = fd->fd_file.id;
fsipcbuf.write.req_n = MIN(n, PGSIZE);
memmove(fsipcbuf.write.req_buf, buf, fsipcbuf.write.req_n);
int r = fsipc(FSREQ_WRITE, NULL);
return r;
}
Part B:Spawning Processes
Exercise 7
spawn
relies on the new syscallsys_env_set_trapframe
to initialize the state of the newly created environment. Implementsys_env_set_trapframe
inkern/syscall.c
(don’t forget to dispatch the new system call insyscall()
).Test your code by running the
user/spawnhello
program fromkern/init.c
, which will attempt to spawn/hello
from the file system.Use make grade to test your code.
sys_env_set_trapframe
函数作用是将进程号为 envid 的进程的 TrapFrame 设为 tf ,并且在设置前需要检查 envid 是否存 在
// Set envid's trap frame to 'tf'.
// tf is modified to make sure that user environments always run at code
// protection level 3 (CPL 3), interrupts enabled, and IOPL of 0.
//
// Returns 0 on success, < 0 on error. Errors are:
// -E_BAD_ENV if environment envid doesn't currently exist,
// or the caller doesn't have permission to change envid.
static int
sys_env_set_trapframe(envid_t envid, struct Trapframe *tf)
{
// LAB 5: Your code here.
// Remember to check whether the user has supplied us with a good
// address!
// panic("sys_env_set_trapframe not implemented");
int r;
struct Env * e;
if((r=envid2env(envid, &e, 1))<0) return r;
memmove(&e->env_tf,tf,sizeof(struct Trapframe));
return 0;
}
Part C:The Shell
Exercise 10
The shell doesn’t support I/O redirection. It would be nice to run sh <script instead of having to type in all the commands in the script by hand, as you did above. Add I/O redirection for < to
user/sh.c
.Test your implementation by typing sh <script into your shell
Run make run-testshell to test your shell.
testshell
simply feeds the above commands (also found infs/testshell.sh
) into the shell and then checks that the output matchesfs/testshell.key
.
修改user/sh.c
中的runcmd()
来支持重定向
// LAB 5: Your code here.
// panic("< redirection not implemented");
if ((fd = open(t, O_RDONLY)) < 0)
{
cprintf("open %s for write: %e", t, fd);
exit();
}
if (fd != 0)
{
dup(fd, 0);
close(fd);
}
break;
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!