/* proc.h */ structvma_t { // int fd; structfile *f; int length; int prot; int flags; int offset; int oldsz; uint64 addr; };
// Per-process state structproc { structspinlocklock; ... char name[16]; // Process name (debugging) structvma_tvmas[16];// VMAs helps the kernel to decide how to handle page faults };
// mmap doesn't allow read/write mapping of a file opened read-only with MAP_SHARED // "or", "/" also meaning "and" in english when use "doesn't", "not" and so on if(f->readable && !f->writable && (prot & PROT_READ) && (prot & PROT_WRITE) && (flags & MAP_SHARED)) return-1; structproc *p = myproc(); int oldsz = p->sz; // get va without anything by p->sz if(addr == 0){ uint64 va = p->sz; int is_same = 0; while(1){ // avoid: some VAs may get same addr, becase we use 'lazy alloc' int i ; for(i = 0; i < 16; i++){ if(p->vmas[i].addr <= va && va < (p->vmas[i].addr + p->vmas[i].length)) is_same = 1; } if(!is_same){ addr = va; p->sz = va + length; break; } // reset is_same is_same = 0; if(va >= MAXVA) return-1; va += PGSIZE; } }
for(int i = 0; i < 16; i++){ // case: maybe remove and rebuild a vmas[i] when slots full // not case in test // build a vams[i] if(p->vmas[i].addr == 0){ p->vmas[i].f = f; p->vmas[i].length = length; p->vmas[i].prot = prot; p->vmas[i].flags = flags; p->vmas[i].offset = offset; p->vmas[i].addr = addr; p->vmas[i].oldsz = oldsz;
// mmap should increase the file's reference count filedup(f); return addr; } }
// not find a vma, so ret -1 if(has_a_vma == 0){ return-1; }
pte_t *pte = walk(p->pagetable, addr, 0); int offset = p->vmas[i].f->off; // addr is beginning of vma, so use file->off if(p->vmas[i].oldsz != addr) // addr is't beginning of vma, so use p->vmas[i].offset offset = p->vmas[i].offset;
// If an unmapped page has been modified and the file is mapped MAP_SHARED, // write the page back to the file. if((*pte & PTE_V) && p->vmas[i].flags & MAP_SHARED){ begin_op(); ilock(p->vmas[i].f->ip); writei(p->vmas[i].f->ip, 1, addr, offset, length); iunlock(p->vmas[i].f->ip); end_op(); }
// If munmap removes all pages of a previous mmap, // it should decrement the reference count of the corresponding struct file. // we keep end of old addr by 'p->vmas[i].addr += length' and 'p->vmas[i].length -= length' // we check by 'addr < (p->vmas[i].addr + p->vmas[i].length' in sys_mmap() // so we can't mmap [length munmap] and we will mmap after [p->vmas[i].length] // figure: // ' p->vmas[i].addr // [process data][ p->vmas[i].length ] // [ p->sz ] // ==> // ' p->vmas[i].addr // [process data][ length munmap ][p->vmas[i].length] // [ p->sz ] if(length < p->vmas[i].length){ p->sz -= length; p->vmas[i].addr += length; p->vmas[i].length -= length; } elseif(length == p->vmas[i].length){ p->sz -= length; p->vmas[i].f->ref--; p->vmas[i].f = 0; p->vmas[i].addr = 0; p->vmas[i].prot = 0; p->vmas[i].flags = 0; p->vmas[i].length = 0; p->vmas[i].oldsz = 0; } else return-1;
if((*pte & PTE_V) == 0) // don't write and uvmunmap(), only change data of vma return0;
// uvmunmap() after writing // find the VMA for the address range and unmap the specified pages // note: free physical memory => 'do_free = 1' uvmunmap(p->pagetable, addr, length/PGSIZE, 1);
/* trap.c */ int mmap_lazyalloc(pagetable_t pagetable, uint64 va) { structproc *p = myproc(); structfile *f; int prot; int has_a_vam = 0; int perm = 0; char *mem; // find va between vma.addr and vma.addr+vma.lenght int i; for(i = 0; i < 16; i++){ if(p->vmas[i].addr <= va && va < (p->vmas[i].addr + p->vmas[i].length)){ has_a_vam = 1; f = p->vmas[i].f; prot = p->vmas[i].prot; break; } } // not find vma, ret -1 if(has_a_vam == 0){ return-1; } // PTE_U controls whether instructions in user mode are allowed to access the page; // if PTE_U is notset, the PTE can be used only in supervisor mode. perm |= PTE_U; // MAYBE sets PTE_R, PTE_W, PTE_X if(prot & PROT_READ){ perm |= PTE_R; } if(prot & PROT_WRITE){ perm |= PTE_W; } if(prot & PROT_EXEC){ perm |= PTE_X; }
// big bug: not alloc mem(4096) to all virtual addresses if((mem = kalloc()) == 0){ return-1; } // In mmaptest/makefile() // create a file to be mapped, containing // 1.5 pages of 'A' and half a page of zeros. // so we must set 0 of length after getting mem memset(mem, 0, PGSIZE);
// note: mem is new address of phycial memory if(mappages(pagetable, va, PGSIZE, (uint64)mem, perm) == -1){ kfree(mem); return-1; }
// we not set PTE_D, becasue we always directly wirite back to file in munmap()
// length is the number of bytes to map; it might not be the same as the file's length. // read data from file, then put data to va ilock(f->ip); if(readi(f->ip, 1, va, va - p->vmas[i].addr, PGSIZE) < 0){ // readi offset by 'va - p->vmas[i].addr' iunlock(f->ip); return-1; } iunlock(f->ip); p->vmas[i].offset += PGSIZE;
// success, ret 0 return0; }
void usertrap(void) { int which_dev = 0;
if((r_sstatus() & SSTATUS_SPP) != 0) panic("usertrap: not from user mode");
// send interrupts and exceptions to kerneltrap(), // since we're now in the kernel. w_stvec((uint64)kernelvec);
structproc *p = myproc(); // save user program counter. p->trapframe->epc = r_sepc(); if(r_scause() == 8){ // system call
if(p->killed) exit(-1);
// sepc points to the ecall instruction, // but we want to return to the next instruction. p->trapframe->epc += 4;
// an interrupt will change sstatus &c registers, // so don't enable until done with those registers. intr_on();
syscall();
// Fill in the page table lazily, in response to page faults. } elseif(r_scause() == 13){ uint64 fault_va = r_stval(); int is_alloc = mmap_lazyalloc(p->pagetable, fault_va); if(fault_va > p->sz || is_alloc == -1){ p->killed = 1; } } elseif((which_dev = devintr()) != 0){ // ok } else { printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid); printf(" sepc=%p stval=%p\n", r_sepc(), r_stval()); p->killed = 1; }
if(p->killed) exit(-1);
// give up the CPU if this is a timer interrupt. if(which_dev == 2) yield();
make[1]: Leaving directory '/home/duile/xv6-labs-2021' == Test running mmaptest == $ make qemu-gdb (3.9s) == Test mmaptest: mmap f == mmaptest: mmap f: OK == Test mmaptest: mmap private == mmaptest: mmap private: OK == Test mmaptest: mmap read-only == mmaptest: mmap read-only: OK == Test mmaptest: mmap read/write == mmaptest: mmap read/write: OK == Test mmaptest: mmap dirty == mmaptest: mmap dirty: OK == Test mmaptest: not-mapped unmap == mmaptest: not-mapped unmap: OK == Test mmaptest: two files == mmaptest: two files: OK == Test mmaptest: fork_test == mmaptest: fork_test: OK == Test usertests == $ make qemu-gdb usertests: OK (141.4s) == Test time == time: OK Score: 140/140