MIT 6.828 lab5

前言

lab5比较简单,相对于之前的lab来说。主要就是完成简单的文件系统。大部分代码mit都已经帮你搭好了。

Exercise 1

i386_init identifies the file system environment by passing the type ENV_TYPE_FS to your environment creation function, env_create. Modify env_create in env.c, so that it gives the file system environment I/O privilege, but never gives that privilege to any other environment

启动IO驱动器,设置一下标志位即可。

1
2
3
if(type == ENV_TYPE_FS){
e->env_tf.tf_eflags |= FL_IOPL_3;
}

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?

不需要。因为进程切换时会保存Trapframe

Exercise 2

Implement the bc_pgfault and flush_block functions in fs/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.

我们用3G的内存地址来映射磁盘。从DISKMAPDISKMAP+DISKMAX。但是不可能把整块磁盘的内容都加载到内存中来。所以我们采用类似于COW的方式,延迟到page fault时,再从磁盘中读取,这种方式抽象了整个磁盘都在内存中的假象,并且以有限的内存空间,支持更大的磁盘读取。

bc_pgfault 当产生page fault,说明这一页还未读取到内存中,则先分配一页,然后进行读取。

1
2
3
4
5
6
7
addr = ROUNDDOWN(addr, PGSIZE);
r = sys_page_alloc(0, addr, PTE_P | PTE_U | PTE_W);
if(r != 0)
panic("bc.c: paga_alloc failed %e", r);
r = ide_read(blockno * BLKSECTS, addr, BLKSECTS);
if(r != 0)
panic("bc.c: ide_read failed %e", r);

flush_block 内存中的内容被修改之后,需要写回到磁盘中。因为本质上它就是一块Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
if (!(va_is_mapped(addr) && va_is_dirty(addr)))
return;
addr = ROUNDDOWN(addr, PGSIZE);
if(va_is_mapped(addr) && va_is_dirty(addr)){
r = ide_write(blockno * BLKSECTS, addr, BLKSECTS);
if(r != 0)
panic("flush_block: ide_write failed %e", r);
r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL);
if(r != 0)
panic("flush_block: sys_page_map failed %e", r);
}
}

Exercise 3

Use free_block as a model to implement alloc_block in fs/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 with flush_block, to help file system consistency.

Josbitmap来跟踪空闲块,这里比较简单,模仿block_is_free写即可。

1
2
3
4
5
6
7
8
9
10
11
int alloc_block(void) {
int i = 0;
for (i = 0; i < super->s_nblocks; i++) {
if (block_is_free(i)) {
bitmap[i / 32] &= ~(1 << ( i %32));
flush_block(diskaddr(i + 2));
return i;
}
}
return -E_NO_DISK;
}

Exercise 4

Implement file_block_walk and file_get_block. file_block_walk maps from a block offset within a file to the pointer for that block in the struct File or the indirect block, very much like what pgdir_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.

file_block_walk查找文件的磁盘块。分为两种情况,一种是direct blocks,也就是前十个块,是直接存在File数据结构中的,而indirect block,是一个指针指向额外的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc) {
int blockno;
if (filebno > NDIRECT + NINDIRECT)
return -E_INVAL;
if(filebno < NDIRECT)
*ppdiskbno = &f->f_direct[filebno];
else{
if(!f->f_indirect) {
if(alloc){
blockno = alloc_block();
if(blockno < 0)
return -E_NO_DISK;
memset(diskaddr(blockno), 0, BLKSIZE);
f->f_indirect = blockno;
}
else
return -E_NOT_FOUND;
}
*ppdiskbno = &((uintptr_t *) diskaddr(f->f_indirect))[filebno - NDIRECT];
}
return 0;
}

file_get_block 是对file_block_walk的调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
file_get_block(struct File *f, uint32_t filebno, char **blk) {
uint32_t *ppdiskbno;
int r;
r = file_block_walk(f, filebno, &ppdiskbno, 1);
if(r < 0)
return r;
if(! (*ppdiskbno)){
r = alloc_block();
if(r < 0)
return -E_NO_DISK;
*ppdiskbno = r;
}
*blk = diskaddr(*ppdiskbno);
return 0;
}

Exercise 5

Implement serve_read in fs/serv.c。

本质上所谓的文件系统,其实就是一个独立的进程负责读写磁盘。所以当其他进程想要通过文件系统来进行读写,那么只能通过IPC。更详细的过程,MIT讲义上写的很清楚。

serve_read没什么难度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
struct OpenFile *o;
int r;
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 in fs/serv.c and devfile_write in lib/file.c.

serve_write 参照着serve_read写就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.
struct OpenFile *o;
int r;
if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0)
return r;
r = file_write(o->o_file, req->req_buf, req->req_n, o->o_fd->fd_offset);
if(r < 0)
return r;
o->o_fd->fd_offset += r;
return r;
}

devfile_write也一样。注意fsipcbuf是存放各种读写操作的联合体。

1
2
3
4
5
6
7
8
9
10
11
static ssize_t
devfile_write(struct Fd *fd, const void *buf, size_t n) {
int r;
fsipcbuf.write.req_fileid = fd->fd_file.id;
fsipcbuf.write.req_n = n < PGSIZE ? n: PGSIZE;
memmove(fsipcbuf.write.req_buf, buf, fsipcbuf.write.req_n);
r = fsipc(FSREQ_WRITE, NULL);
return r;
}

Exercise 7

spawn relies on the new syscall sys_env_set_trapframe to initialize the state of the newly created environment. Implement sys_env_set_trapframe in kern/syscall.c(don’t forget to dispatch the new system call in syscall()).

这里我们简单来看下spawn函数,它的主要工作就是把从磁盘上加载用户与程序,并运行它。相当于把forkexec结合到一个函数。

  1. 打开文件,获取Fd
  2. 读取ELF头,这一步和load_icode,只不过这里是从磁盘中读
  3. 创建子进程
  4. 把子进程的eip设置到程序的入口点
  5. 初始化栈
  6. 把所有需要加载的程序段加载到子程序的地址空间。到这里,文件操作完毕。
  7. 初始化进程状态

更详细的大家去看代码,这里不赘述。

sys_env_set_trapframe很简单

1
2
3
4
5
6
7
8
9
10
static int
sys_env_set_trapframe(envid_t envid, struct Trapframe *tf) {
struct Env *env;
int r = envid2env(envid, &env, 1);
if(r!=0)
return r;
env->env_tf = *tf;
env->env_tf.tf_eflags |= FL_IF;
return 0;
}

Exercise 8

Change duppage in lib/fork.c to follow the new convention. If the page table entry has the PTE_SHARE bit set, just copy the mapping directly. (You should usePTE_SYSCALL, not 0xfff, to mask out the relevant bits from the page table entry. 0xfff picks up the accessed and dirty bits as well.)

Likewise, implement copy_shared_pages in lib/spawn.c. It should loop through all page table entries in the current process (just like fork did), copying any page mappings that have the PTE_SHARE bit set into the child process.

这里需要让Fd在进程之间共享。多添加了一个标志位,使其在fork时并不进行COW映射,而是直接映射,所以当page fault时,也不会进行copy.

duppage

1
2
3
4
5
if(uvpt[pn] & PTE_SHARE){
if ((r = sys_page_map(0, addr, envid, addr, uvpt[pn] & PTE_SYSCALL)) < 0)
return r;
return 0;
}

copy_shared_pages

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int
copy_shared_pages(envid_t child) {
uintptr_t i;
for (i = 0; i < USTACKTOP; i += PGSIZE)
{
if ((uvpd[PDX(i)] & PTE_P) &&
(uvpt[PGNUM(i)] & PTE_P) &&
(uvpt[PGNUM(i)] & PTE_SHARE))
{
sys_page_map(0, (void*)i,
child, (void*)i,
(uvpt[PGNUM(i)] & PTE_SYSCALL));
}
}
return 0;
}

Exercise 9

In your kern/trap.c, call kbd_intr to handle trap IRQ_OFFSET+IRQ_KBD and serial_intr to handle trap IRQ_OFFSET+IRQ_SERIAL.

不解释

1
2
3
4
5
6
7
8
9
if (tf->tf_trapno == IRQ_OFFSET + IRQ_KBD) {
kbd_intr();
return;
}
if (tf->tf_trapno == IRQ_OFFSET + IRQ_SERIAL) {
serial_intr();
return;
}

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.

这个也不解释,在xv6那本书第一章中就有提到过实现。

1
2
3
4
5
6
7
8
if ((fd = open(t, O_RDONLY)) < 0) {
cprintf("open %s for read: %e", t, fd);
exit();
}
if (fd != 0) {
dup(fd, 0);
close(fd);
}