#include "types.h" #include "defs.h" #include "gfx.h" #include "param.h" #include "stat.h" #include "memlayout.h" #include "mmu.h" #include "spinlock.h" #include "sleeplock.h" #include "proc.h" #include "fs.h" #include "file.h" #include "syscall.h" #include "errno.h" extern int mappages(pml4e_t *pgdir, void *va, uint64 size, uint64 pa, int perm); #define MAP_SHARED 0x01 #define MAP_PRIVATE 0x02 #define MAP_FIXED 0x10 #define MAP_ANONYMOUS 0x20 #define PROT_NONE 0 #define PROT_WRITE 0x2 // Reject user mapping requests that fall outside user space. Without this, // MAP_FIXED at an arbitrary address could create user-accessible PTEs over // (or vm_unmap_range/kfree pages belonging to) the kernel half of the // address space. static int bad_user_range(uint64 va, uint64 length) { return length == 0 || va + length < va || va + length > KERNBASE; } addr_t sys_sbrk(void) { int n; if (argint(0, &n) < 0) return -1; addr_t addr = myproc()->sz; if (growproc(n) < 0) return -1; return addr; } addr_t sys_brk(void) { addr_t addr; if (argaddr(0, &addr) < 0 || addr == 0) return myproc()->sz; if (addr > myproc()->sz && growproc(addr - myproc()->sz) < 0) return myproc()->sz; return myproc()->sz; } addr_t sys_mmap(void) { uint64 addr, length, offset; int prot, flags, fd; struct file *f = 0; if (argaddr(0, &addr) < 0 || argaddr(1, &length) < 0 || argint(2, &prot) < 0 || argint(3, &flags) < 0 || argint(4, &fd) < 0 || argaddr(5, &offset) < 0) return -1; if (fd >= 0 && fd < NOFILE) f = myproc()->ofile[fd]; length = PGROUNDUP(length); uint64 va = addr; if (length == 0 || length > KERNBASE) return -EINVAL; if (flags & MAP_FIXED) { if (bad_user_range(va, length) || (va % PGSIZE)) return -EINVAL; vm_unmap_range(myproc()->pgdir, va, length); if (prot == PROT_NONE) return va; for (uint64 a = va; a < va + length; a += PGSIZE) { char *mem = kalloc(); if (!mem) return -1; memset(mem, 0, PGSIZE); if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0) return -1; } return va; } struct vma *v = 0; for (int i = 0; i < NVMA; i++) { if (!myproc()->vma[i].used) { v = &myproc()->vma[i]; break; } } // -ENOMEM, not -1. A bare -1 reaches musl as -EPERM, so a process that // had simply used all NVMA slots was told "operation not permitted" -- // which sends you looking for a permission model this system does not // have. malloc() turns either into NULL, but only one of them is a clue. if (!v) return -ENOMEM; // Ignore non-zero hint addresses (not MAP_FIXED); always pick a fresh VA. // Bump-allocated: each mapping gets its own disjoint, exactly-length-sized // range, so a single mmap() can never run into a neighboring live mapping // regardless of size (the old scheme assigned a fixed 8MB window per vma[] // slot index, which a single allocation bigger than 8MB — e.g. a large // realloc() growth — could map straight past, panicking mappages("remap") // when it hit an already-present PTE in the next slot's territory). // munmap()'d ranges are not reclaimed for reuse; that's fine at this scale. if (myproc()->mmap_next == 0) myproc()->mmap_next = 0x40000000; va = myproc()->mmap_next; if (bad_user_range(va, length)) return -ENOMEM; myproc()->mmap_next += length; int is_fb = 0; int shmid = -1; // File-backed mapping of a regular file. // // This used to be ignored entirely: any file mapping silently returned // zeroed anonymous pages, so a program that mmap'd a font or a config // file got a buffer full of nothing and no error to explain it. Reading // the contents in is not demand paging and never will be without a page // cache -- but it is the difference between a wrong answer and a right // one. // // MAP_SHARED that is also writable is refused rather than faked. Its // whole contract is that stores land in the file and other mappers see // them, and there is nothing here that could make that true; quietly // giving back a private copy would be the same lie in a new place. if (f && f->type == FD_INODE && f->ip->type != T_DEV) { if ((flags & MAP_SHARED) && (prot & PROT_WRITE)) return -ENODEV; if (!f->readable) return -EACCES; for (uint64 a = va; a < va + length; a += PGSIZE) { char *mem = kalloc(); if (!mem) return -ENOMEM; memset(mem, 0, PGSIZE); if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0) { kfree(mem); return -ENOMEM; } } // Read through the file's own offset-independent path, so this does // not disturb a descriptor the caller is also reading normally. ilock(f->ip); readi(f->ip, 0, va, offset, length); iunlock(f->ip); v->addr = va; v->length = length; v->used = 1; v->no_free = 0; v->shmid = -1; return va; } if (f && f->type == FD_INODE && f->ip->type == T_DEV && f->ip->major == 2) { // Map the framebuffer where it actually is. This used to map a // hardcoded 0xfd000000 for a hardcoded length, so a request for more // than the screen holds handed out user-writable PTEs to whatever // MMIO sits above the framebuffer, and any machine that put the LFB // somewhere else got a window onto the wrong device entirely. uint64 fb = gfx_fb_phys(); uint64 fbsz = PGROUNDUP(gfx_fb_size()); if (fb == 0 || fbsz == 0) return -ENODEV; if (length > fbsz) length = fbsz; if (mappages(myproc()->pgdir, (void*)va, length, fb, PTE_W | PTE_U) < 0) return -1; is_fb = 1; // From here the drawing happens with no further syscalls, so this is // the kernel's only chance to notice that the screen no longer belongs // to the text console. fbclose() puts it back. fb_mapped(); } else { for (uint64 a = va; a < va + length; a += PGSIZE) { char *mem = kalloc(); if (!mem) return -1; memset(mem, 0, PGSIZE); if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0) return -1; } } v->addr = va; v->length = length; v->used = 1; v->no_free = is_fb; v->shmid = shmid; return va; } addr_t sys_munmap(void) { uint64 addr, length; if (argaddr(0, &addr) < 0 || argaddr(1, &length) < 0) return -1; length = PGROUNDUP(length); if (bad_user_range(addr, length)) return -EINVAL; for (int i = 0; i < NVMA; i++) { if (myproc()->vma[i].used && myproc()->vma[i].addr == addr) { if (myproc()->vma[i].shmid >= 0) shm_detach(myproc()->vma[i].shmid); if (myproc()->vma[i].no_free) vm_unmap_norelease(myproc()->pgdir, addr, myproc()->vma[i].length); else vm_unmap_range(myproc()->pgdir, addr, myproc()->vma[i].length); myproc()->vma[i].used = myproc()->vma[i].addr = myproc()->vma[i].length = 0; myproc()->vma[i].no_free = 0; myproc()->vma[i].shmid = -1; return 0; } } // Partial / untracked unmap — free pages anyway vm_unmap_range(myproc()->pgdir, addr, length); return 0; } addr_t sys_mprotect(void) { return 0; } // --------------------------------------------------------------------------- // Shared memory // // Two processes mapping the same physical pages. This is what a display // server and its clients need: without it every frame is a copy through a // pipe, and the copy is the entire cost of drawing anything. // // Deliberately simple. Pages are allocated up front rather than on demand, // there is no swapping, and a segment lives until the last attachment goes // away. What it does have is the property that matters: a store by one // process is visible to the other, because it is the same page. // --------------------------------------------------------------------------- #define NSHM 16 // segments #define SHM_MAXPAGES 512 // 2 MB each -- a 800x600x32 surface is 1.9 MB #define IPC_PRIVATE 0 #define IPC_CREAT 01000 #define IPC_EXCL 02000 struct shmseg { int used; int key; uint64 size; // rounded up to a page int npages; int nattach; // live attachments across all processes char *pages[SHM_MAXPAGES]; // kernel virtual addresses }; static struct shmseg shm[NSHM]; static struct spinlock shmlock; static int shm_ready; static void shm_init_once(void) { if (!shm_ready) { initlock(&shmlock, "shm"); shm_ready = 1; } } static void shm_free(struct shmseg *s) { for (int i = 0; i < s->npages; i++) if (s->pages[i]) kfree(s->pages[i]); memset(s, 0, sizeof(*s)); } // fork() gives the child its own attachment to the same segment. Without // this the count would say one attachment while two processes had it mapped, // and the first to exit would free the pages under the other. void shm_ref(int id) { if (id < 0 || id >= NSHM) return; shm_init_once(); acquire(&shmlock); if (shm[id].used) shm[id].nattach++; release(&shmlock); } // Called from munmap and from exit, once per detached mapping. void shm_detach(int id) { if (id < 0 || id >= NSHM) return; shm_init_once(); acquire(&shmlock); if (shm[id].used && shm[id].nattach > 0) { if (--shm[id].nattach == 0) shm_free(&shm[id]); } release(&shmlock); } addr_t sys_shmget(void) { int key, flags; addr_t size; if (argint(0, &key) < 0 || argaddr(1, &size) < 0 || argint(2, &flags) < 0) return -EINVAL; size = PGROUNDUP(size); if (size == 0 || size > (uint64)SHM_MAXPAGES * PGSIZE) return -EINVAL; shm_init_once(); acquire(&shmlock); if (key != IPC_PRIVATE) { for (int i = 0; i < NSHM; i++) { if (shm[i].used && shm[i].key == key) { if ((flags & IPC_CREAT) && (flags & IPC_EXCL)) { release(&shmlock); return -EEXIST; } if (shm[i].size < size) { release(&shmlock); return -EINVAL; } release(&shmlock); return i; } } if (!(flags & IPC_CREAT)) { release(&shmlock); return -ENOENT; } } for (int i = 0; i < NSHM; i++) { if (shm[i].used) continue; struct shmseg *s = &shm[i]; s->npages = (int)(size / PGSIZE); for (int pg = 0; pg < s->npages; pg++) { s->pages[pg] = kalloc(); if (!s->pages[pg]) { for (int j = 0; j < pg; j++) kfree(s->pages[j]); memset(s, 0, sizeof(*s)); release(&shmlock); return -ENOMEM; } // Zeroed, because a fresh segment handing out the last tenant's // bytes would leak them between processes. memset(s->pages[pg], 0, PGSIZE); } s->used = 1; s->key = key; s->size = size; s->nattach = 0; release(&shmlock); return i; } release(&shmlock); return -ENOSPC; } addr_t sys_shmat(void) { int id, flags; addr_t addr; if (argint(0, &id) < 0 || argaddr(1, &addr) < 0 || argint(2, &flags) < 0) return -EINVAL; if (id < 0 || id >= NSHM) return -EINVAL; shm_init_once(); acquire(&shmlock); if (!shm[id].used) { release(&shmlock); return -EINVAL; } uint64 len = shm[id].size; int npages = shm[id].npages; shm[id].nattach++; // claimed before the lock is dropped release(&shmlock); struct vma *v = 0; for (int i = 0; i < NVMA; i++) if (!myproc()->vma[i].used) { v = &myproc()->vma[i]; break; } if (!v) { shm_detach(id); return -ENOMEM; } // Ignore the hint and take a fresh range, the same way mmap does. if (myproc()->mmap_next == 0) myproc()->mmap_next = 0x40000000; uint64 va = myproc()->mmap_next; if (bad_user_range(va, len)) { shm_detach(id); return -ENOMEM; } myproc()->mmap_next += len; for (int pg = 0; pg < npages; pg++) { if (mappages(myproc()->pgdir, (void*)(va + (uint64)pg * PGSIZE), PGSIZE, V2P((uint64)shm[id].pages[pg]), PTE_W | PTE_U) < 0) { vm_unmap_norelease(myproc()->pgdir, va, (uint64)pg * PGSIZE); shm_detach(id); return -ENOMEM; } } v->addr = va; v->length = len; v->used = 1; v->no_free = 1; // the segment owns these pages v->shmid = id; return va; } // Drop every mapping this process holds that it does not own the pages of, // before its page tables are torn down. // // freevm() frees any mapped page below PHYSTOP, which is right for the heap // and the stack and wrong for anything shared. The framebuffer survived only // because it sits above PHYSTOP and was skipped by accident; shared memory is // kalloc'd, sits below it, and would be handed to kfree while another process // was still using it -- the worst kind of bug, silent and remote. // // Called from exit() and from exec() before the old address space goes away. void proc_release_shared(struct proc *p) { for (int i = 0; i < NVMA; i++) { if (!p->vma[i].used || !p->vma[i].no_free) continue; vm_unmap_norelease(p->pgdir, p->vma[i].addr, p->vma[i].length); if (p->vma[i].shmid >= 0) shm_detach(p->vma[i].shmid); p->vma[i].used = 0; p->vma[i].no_free = 0; p->vma[i].shmid = -1; } } addr_t sys_shmdt(void) { addr_t addr; if (argaddr(0, &addr) < 0) return -EINVAL; for (int i = 0; i < NVMA; i++) { if (myproc()->vma[i].used && myproc()->vma[i].addr == addr && myproc()->vma[i].shmid >= 0) { vm_unmap_norelease(myproc()->pgdir, addr, myproc()->vma[i].length); shm_detach(myproc()->vma[i].shmid); myproc()->vma[i].used = myproc()->vma[i].addr = myproc()->vma[i].length = 0; myproc()->vma[i].no_free = 0; myproc()->vma[i].shmid = -1; return 0; } } return -EINVAL; } // Memory advice/hints — all no-ops; kernel decides on its own. addr_t sys_madvise(void) { return 0; } addr_t sys_msync(void) { return 0; } addr_t sys_mincore(void) { return 0; } addr_t sys_mlock(void) { return 0; } addr_t sys_munlock(void) { return 0; } addr_t sys_mlockall(void) { return 0; } addr_t sys_munlockall(void){ return 0; } addr_t sys_fadvise64(void) { return 0; } // mremap: we don't support region relocation; return ENOMEM. addr_t sys_mremap(void) { return -ENOMEM; } // Resource limits: always report unlimited. addr_t sys_setrlimit(void) { return 0; } // Process priority: always report 0. addr_t sys_getpriority(void) { return 0; } addr_t sys_setpriority(void) { return 0; }