You should implement enough mmap and munmap functionality to make the mmaptest test program work. If mmaptest doesn’t use a mmap feature, you don’t need to implement that feature.
这个实验还是比较复杂的,用gdb调了很多次才最终通过所有测试,当然通过测试不代表程序就是对的。
hints
首先简要地翻译一下hints
Start by adding _mmaptest to UPROGS, and mmap and munmap system calls, in order to get user/mmaptest.c to compile. For now, just return errors from mmap and munmap. We defined PROT_READ etc for you in kernel/fcntl.h. Run mmaptest, which will fail at the first mmap call.
补全mmap和munmap两个系统调用路径,并将_mmaptest加入用户程序
Fill in the page table lazily, in response to page faults. That is, mmap should not allocate physical memory or read the file. Instead, do that in page fault handling code in (or called by) usertrap, as in the lazy page allocation lab. The reason to be lazy is to ensure that mmap of a large file is fast, and that mmap of a file larger than physical memory is possible.
Keep track of what mmap has mapped for each process. Define a structure corresponding to the VMA (virtual memory area) described in Lecture 15, recording the address, length, permissions, file, etc. for a virtual memory range created by mmap. Since the xv6 kernel doesn’t have a memory allocator in the kernel, it’s OK to declare a fixed-size array of VMAs and allocate from that array as needed. A size of 16 should be sufficient.
Implement mmap: find an unused region in the process’s address space in which to map the file, and add a VMA to the process’s table of mapped regions. The VMA should contain a pointer to a struct file for the file being mapped; mmap should increase the file’s reference count so that the structure doesn’t disappear when the file is closed (hint: see filedup). Run mmaptest: the first mmap should succeed, but the first access to the mmap-ed memory will cause a page fault and kill mmaptest.
Add code to cause a page-fault in a mmap-ed region to allocate a page of physical memory, read 4096 bytes of the relevant file into that page, and map it into the user address space. Read the file with readi, which takes an offset argument at which to read in the file (but you will have to lock/unlock the inode passed to readi). Don’t forget to set the permissions correctly on the page. Run mmaptest; it should get to the first munmap.
Implement munmap: find the VMA for the address range and unmap the specified pages (hint: use uvmunmap). If munmap removes all pages of a previous mmap, it should decrement the reference count of the corresponding struct file. If an unmapped page has been modified and the file is mapped MAP_SHARED, write the page back to the file. Look at filewrite for inspiration.
Ideally your implementation would only write back MAP_SHARED pages that the program actually modified. The dirty bit (D) in the RISC-V PTE indicates whether a page has been written. However, mmaptest does not check that non-dirty pages are not written back; thus you can get away with writing pages back without looking at D bits.
页表项中记录了该物理页是否被修改,最好的解决方案应该只在该页被修改时才进行文件写入操作。
Modify exit to unmap the process’s mapped regions as if munmap had been called. Run mmaptest; mmap_test should pass, but probably not fork_test.
修改exit函数,在进程退出时unmap所有已经映射的内存区域
Modify fork to ensure that the child has the same mapped regions as the parent. Don’t forget to increment the reference count for a VMA’s struct file. In the page fault handler of the child, it is OK to allocate a new physical page instead of sharing a page with the parent. The latter would be cooler, but it would require more implementation work. Run mmaptest; it should pass both mmap_test and fork_test.
// Exit the current process. Does not return. // An exited process remains in the zombie state // until its parent calls wait(). voidexit(int status) { structproc *p = myproc();
if (p == initproc) panic("init exiting");
// Close all open files. for (int fd = 0; fd < NOFILE; fd++) { if (p->ofile[fd]) { structfile *f = p->ofile[fd]; fileclose(f); p->ofile[fd] = 0; } }
// 释放所有内存区域 free_all_area(p);
begin_op(); iput(p->cwd); end_op(); p->cwd = 0;
acquire(&wait_lock);
// Give any children to init. reparent(p);
// Parent might be sleeping in wait(). wakeup(p->parent);
acquire(&p->lock);
p->xstate = status; p->state = ZOMBIE;
release(&wait_lock);
// Jump into the scheduler, never to return. sched(); panic("zombie exit"); }
intkernel_fileread(struct file *f, uint64 addr, int offset, int size) { ilock(f->ip); if ((readi(f->ip, 0, addr, offset, size)) <= 0) { printf("read file error\n"); iunlock(f->ip); return-1; } iunlock(f->ip); return0; }
intkernel_filewrite(struct file *f, uint64 addr, int offset, int n) { // write a few blocks at a time to avoid exceeding // the maximum log transaction size, including // i-node, indirect block, allocation blocks, // and 2 blocks of slop for non-aligned writes. // this really belongs lower down, since writei() // might be writing a device like the console. int r, ret = 0; int max = ((MAXOPBLOCKS - 1 - 1 - 2) / 2) * BSIZE; int i = 0; f->off = offset; while (i < n) { int n1 = n - i; if (n1 > max) n1 = max;
begin_op(); ilock(f->ip); if ((r = writei(f->ip, 0, addr + i, f->off, n1)) > 0) f->off += r; iunlock(f->ip); end_op();
if (r != n1) { // error from writei break; } i += r; } ret = (i == n ? n : -1); return ret; }
if (!holding(&p->lock)) panic("sched p->lock"); if (mycpu()->noff != 1) panic("sched locks"); if (p->state == RUNNING) panic("sched running"); if (intr_get()) panic("sched interruptible");