mit 6.828学习笔记不知道几--lab5
exercise1:
在文件inc/mmu.h中定义了
#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask
x86处理器使用EFLAGS寄存器中的IOPL位来确定是否允许保护模式代码执行特殊的设备I/O指令,比如IN和OUT指令。
我们只需要给文件系统环境提供“I/O privilege”即可。
if(type== ENV_TYPE_FS) e->env_tf.tf_eflags |= FL_IOPL_MASK;
make grade时通过“fs i/o” test。
exercise 2 :
在文件fs/ide.c 中,有磁盘读写函数
int ide_read(uint32_t secno, void *dst, size_t nsecs) { ... return 0; } int ide_write(uint32_t secno, const void *src, size_t nsecs) { ... return 0; }
bc_pgfault 这个函数处理页面错误,首先分配一个页面,然后从磁盘读取数据到页面中。
flush_block 这个函数把包含va的块的内容flush到磁盘中去
static void bc_pgfault(struct UTrapframe *utf) { ... // LAB 5: you code here:
//addr首先要页面对齐 addr = ROUNDDOWN(addr, PGSIZE); //分配一个页面 if ((r=sys_page_alloc(0, addr, PTE_U | PTE_P | PTE_W)) != 0) panic("bc_pgfault:sys_page_alloc filed:%e\n,r"); //读取磁盘的内容 if ((r = ide_read(blockno * BLKSECTS, addr, BLKSECTS))!=0) panic("bc_pgfault:ide_read filed: %e\n", r); ... }
void flush_block(void *addr) { uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE; if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE)) panic("flush_block of bad va %08x", addr); // LAB 5: Your code here. int r; // LAB 5: Your code here. addr = ROUNDDOWN(addr, PGSIZE); if (va_is_mapped(addr) && va_is_dirty(addr)){ if ((r = ide_write(blockno * BLKSECTS, addr, BLKSECTS)) < 0) panic("flush_block:ide_write filed: %e\n", r); if ((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0) panic("flush_block:sys_page_map filed: %e\n", r); } //panic("flush_block not implemented"); }
使用 make grade 结果如下:
提到的函数
// Check to see if the block bitmap indicates that block 'blockno' is free. // Return 1 if the block is free, 0 if not. bool block_is_free(uint32_t blockno) { if (super == 0 || blockno >= super->s_nblocks) return 0; if (bitmap[blockno / 32] & (1 << (blockno % 32))) return 1; return 0; }
exercise 3:
首先看看 free_block:
// Mark a block free in the bitmap void free_block(uint32_t blockno) { // Blockno zero is the null pointer of block numbers. if (blockno == 0) panic("attempt to free zero block"); bitmap[blockno/32] |= 1<<(blockno%32); }
我们要借鉴的是将bitmap某一位设置为1的代码
关于这个bitmap要讲两句:
首先bitmap作为一个指针,指向了一块连续的区域,这个区域中的每一位都对应着一个块。
也可以将bitmap视为一个数组,数组中的每一个偏移量都是一个unit32_t的数据,是32位的。
简单画一个图:
所以对于一个blockno,首先要得出他在bitmap[?],这个通过bitmap[blockno / 32]得到
然后就是针对这一位进行检查,使用1 << (blockno % 32) 即可
例如:
至于为什么不是块1、块2...这样排列,这与1左移的方法有关。
如果按照jos的代码来说,就应该是如上排列。
如果采用1<<(31-blocno%32),就可以是块1、块2...这样的排列。
只要标记为占用或者空闲时使用的左移的方法一样,就都可。
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. uint32_t blockno; for (blockno = 0; blockno < super->s_nblocks; blockno++) { if (block_is_free(blockno)) { bitmap[blockno / 32] ^= 1 << (blockno % 32); flush_block(bitmap); return blockno; } } return -E_NO_DISK; //panic("alloc_block not implemented"); return -E_NO_DISK; }
make grade 应该通过 alloc——block
exercise 4:
可能会用到的几个函数:
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)//寻找一个文件结构f中的第fileno个块指向的磁盘块编号放入ppdiskbno。如果filebno小于NDIRECT,则返回属于f.direct[NDIRECT]中的相应链接,否则返回f_indirect中查找的块。如果alloc为真且相应磁盘块不存在,则分配1个。 dir_lookup(struct File *dir, const char *name, struct File **file)//在目录dir中查找名字为name的文件,如果找到则让file指向该文件结构体。 dir_alloc_file(struct File *dir, struct File **file)//在dir对应的File结构体中分配1个File的指针连接给file,用于添加文件的操作。 skip_slash(const char *p)//用于路径中的字符串处理,跳过斜杠。 walk_path(const char *path, struct File **pdir, struct File **pf, char *lastelem)//path为从绝对路径的文件名,如果成功找到该文件,则把相应的文件结构体赋值给pf,其所在目录的文件结构体赋值给pdir,lastlem为失效时最后剩下的文件名。 file_free_block(struct File *f, uint32_t filebno)//释放1个文件中的第filebno个磁盘块。此函数在file_truncate_blocks中被调用。 file_truncate_blocks(struct File *f, off_t newsize)//将文件设置为缩小后的新大小,清空那些被释放的物理块。
具体实现:
static int file_block_walk(struct File* f, uint32_t filebno, uint32_t** ppdiskbno, bool alloc) { // LAB 5: Your code here. int r; //-E_INVAL if filebno is out of range (it's >= NDIRECT + NINDIRECT). if (filebno >= NDIRECT + NINDIRECT) return -E_INVAL; //如果他是直接块 //fileno相当于偏移量 if (filebno < NDIRECT) { if (ppdiskbno) *ppdiskbno = f->f_direct + filebno; return 0; } //-E_NOT_FOUND if the function needed to allocate an indirect block, but //alloc was 0. if (!alloc && !f->f_indirect) return -E_NOT_FOUND; //如果要分配,但是没有空闲的间接块了 if (!f->f_indirect) { //尝试分配一个块,分配失败, if ((r = alloc_block()) < 0) return -E_NO_DISK; //分配成功,加入链表。记得清零 f->f_indirect = r; memset(diskaddr(r), 0, BLKSIZE); flush_block(diskaddr(r)); } //要分配并且有间接块 if (ppdiskbno) *ppdiskbno = (uint32_t*)diskaddr(f->f_indirect) + filebno - NDIRECT; return 0; //panic("file_block_walk not implemented"); }
int file_get_block(struct File* f, uint32_t filebno, char** blk) { // LAB 5: Your code here. //-E_INVAL if filebno is out of range int r; uint32_t* ppdiskbno; if (filebno >= NDIRECT + NINDIRECT) return -E_INVAL; //先调用file_walk_block函数找到文件中的目标块 if ((r = file_block_walk(f, filebno, &ppdiskbno, 1)) < 0) return r; if (*ppdiskbno == 0) { if ((r = alloc_block()) < 0) return -E_NO_DISK; *ppdiskbno = r; memset(diskaddr(r), 0, BLKSIZE); flush_block(diskaddr(r)); } *blk = diskaddr(*ppdiskbno); return 0; //panic("file_get_block not implemented"); }
make grade,应该通过“file_open”、“file_get_block”、“file_flush/file_truncated/file rewrite”和“testfile”。
exercise 5 :
Openfile结构体的定义
//fs/server.c
struct OpenFile { uint32_t o_fileid; // file id struct File* o_file; // mapped descriptor for open file int o_mode; // open mode struct Fd* o_fd; // Fd page };
union Fsipc 定义:
//inc/fs.h
union Fsipc { struct Fsreq_open { char req_path[MAXPATHLEN]; int req_omode; } open; struct Fsreq_set_size { int req_fileid; off_t req_size; } set_size; struct Fsreq_read { int req_fileid; size_t req_n; } read; struct Fsret_read { char ret_buf[PGSIZE]; } readRet; struct Fsreq_write { int req_fileid; size_t req_n; char req_buf[PGSIZE - (sizeof(int) + sizeof(size_t))]; } write; struct Fsreq_stat { int req_fileid; } stat; struct Fsret_stat { char ret_name[MAXNAMELEN]; off_t ret_size; int ret_isdir; } statRet; struct Fsreq_flush { int req_fileid; } flush; struct Fsreq_remove { char req_path[MAXPATHLEN]; } remove; // Ensure Fsipc is one page char _pad[PGSIZE]; };
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: struct OpenFile* o; int r, req_n; // Look up an open file for envid. if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0) return r; //因为ipc最多只能传一个页面 所以取max{PGSIZE,req_req_n} req_n = req->req_n > PGSIZE ? PGSIZE : req->req_n; if ((r = file_read(o->o_file, ret->ret_buf, req_n, o->o_fd->fd_offset)) < 0) return r; //读入了数据,偏移量要增加 o->o_fd->fd_offset += r; return r; }
其实真正读的工作是由file_read完成的,server_read只是提供了一个rpc接口
make grade应该通过“serve_open/file_stat/file_close”和“file_read”获得70/150的分数。
exercise 6:
这个函数的实现十分类似于serve_write()。
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, req_n; // Look up an open file for envid. if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0) return r; 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; //panic("serve_write not implemented"); }
这个函数的实现类似于上面的 devfile_read()。
但是此处有一个问题:
static ssize_t devfile_read(struct Fd* fd, void* buf, size_t n) { ... if ((r = fsipc(FSREQ_READ, NULL)) < 0) return r; ... memmove(buf, fsipcbuf.readRet.ret_buf, r); ... }
这两行代码在devfile_read()中需要交换位置。为什么
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 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); if ((r = fsipc(FSREQ_WRITE, NULL)) < 0) return r; assert(r <= n); assert(r <= PGSIZE); //memmove(fsipcbuf.write.req_buf, buf, n); return r; //panic("devfile_write not implemented"); }
exercise 7:
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! struct Env *e; int r; if ((r = envid2env(envid, &e, true)) < 0) return -E_BAD_ENV; user_mem_assert(e, tf, sizeof(struct Trapframe), PTE_U);
tf->tf_eflags |= FL_IF;
tf->tf_eflags &= ~FL_IOPL_MASK; //普通进程不能有IO权限
tf->tf_cs |= 3;
e->env_tf = *tf;
return 0; //panic("sys_env_set_trapframe not implemented"); }
记得修改syscall.c的分支
case (SYS_env_set_trapframe): return sys_env_set_trapframe(a1,(struct Trapframe *)a2);
以及init.c中
#if defined(TEST) // Don't touch -- used by grading script! ENV_CREATE(TEST, ENV_TYPE_USER); #else // Touch all you want. //ENV_CREATE(user_icode, ENV_TYPE_USER); //ENV_CREATE(user_primes, ENV_TYPE_USER); ENV_CREATE(user_spawnhello, ENV_TYPE_USER); /*ENV_CREATE(user_yield, ENV_TYPE_USER); ENV_CREATE(user_yield, ENV_TYPE_USER); ENV_CREATE(user_yield, ENV_TYPE_USER);*/ //ENV_CREATE(user_dumbfork, ENV_TYPE_USER); #endif // TEST*
make grade 通过 spawn via spawnhello
exercise 8:
记得修改init.c恢复原状
static int duppage(envid_t envid, unsigned pn) { //这个函数用来复制映射关系 //对于UTOP下面地址空间中的每个可写页面或写时复制页面, //父类(1)要调用duppage, 写时复制的页面映射到子进程的地址空间, // (2) 在自己的地址空间中重新映射写时复制的页面。 int r; void* addr = (void*)(pn << 12);//address pn*PGSIZE envid_t fu_id = sys_getenvid(); //Lab 5 code here: //如果页表条目设置了PTE_SHARE位,那么直接复制映射即可。 if (uvpt[pn] & PTE_SHARE) { if ((r = sys_page_map(sys_getenvid(), addr, envid, addr, uvpt[pn] & PTE_SYSCALL)) < 0) { panic("duppage: page mapping failed: %e", r); return r; } } else { if (uvpt[pn] & (PTE_W | PTE_COW)) { // If the page is writable or copy-on-write, // the new mapping must be created copy-on-write, //父进程的地址空间映射给了子进程 r = sys_page_map(fu_id, (void*)addr, envid, (void*)addr, PTE_COW | PTE_U); if (r != 0) return r; //父进程这里要重新映射一遍,区别是之前有可能是writable,现在只能是COW,so? //难道是因为现在父子进程都映射着同一个物理页,如果父进程还是可写的话,就会影响子进程 r = sys_page_map(fu_id, (void*)addr, fu_id, (void*)addr, PTE_COW | PTE_U); if (r != 0) return r; } else { r = sys_page_map(fu_id, (void*)addr, envid, (void*)addr, uvpt[pn] & PTE_SYSCALL); if (r != 0) return r; } } return 0; }
static int copy_shared_pages(envid_t child) { int i,r; // LAB 5: Your code here. //类似与fork.c中的fork(),将父进程的地址空间复制给子进程 //它应该循环遍历当前进程中的所有页表条目(就像fork所做的那样) //将设置了PTE_SHARE位的任何页映射到子进程 //这里不用duppage,因为我们是直接共享了地址空间,所以添加映射即可 //而dupage只是复制了到了子进程里面,但是全局没有注册 for (i = 0; i < PGNUM(USTACKTOP); i++) { if ((uvpd[i / 1024] & PTE_P) && (uvpt[i] & PTE_P) && (uvpt[i] & PTE_SHARE)) { //相比于fork(),这里多了一个判断条件, uvpt[i] & PTE_SHARE) if ((r = sys_page_map(0, PGADDR(i / 1024, i % 1024, 0), child, PGADDR(i / 1024, i % 1024, 0), uvpt[i] & PTE_SYSCALL)) < 0) return r; } } return 0; }
make run-testpteshare:应该看到“fork handle PTE_SHARE right、spawn handle PTE_SHARE right
make run-testfdsharing:应该看到read in child succeeded、read in parent succeeded
exercise 9:
这个联系很简单,在trap_dispatch中添加分支即可:
// Handle keyboard and serial interrupts. // LAB 5: Your code here. else if (tf->tf_trapno == IRQ_OFFSET + IRQ_KBD) { kbd_intr(); return; } else if (tf->tf_trapno == IRQ_OFFSET + IRQ_SERIAL) { serial_intr(); return; }
运行make run-testkbd测试代码,并输入一些字符,按下回车,他应该显示你刚刚输入的字符。
exercise 10:
// LAB 5: Your code here. if ((fd = open(t, O_RDONLY)) < 0) { cprintf("open %s for read: %e", t, fd); exit(); } if (fd != 0) { dup(fd, 0); close(fd); }
make grade