#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" #include "uaccess.h" #include "posix.h" #include "pciuser.h" extern int mappages(pml4e_t *pgdir, void *va, uint64 size, uint64 pa, int perm); /* MAP_* and PROT_* now live in include/posix.h, so userspace and the kernel compare the same numbers. */ // 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; } // Anonymous memory -- pages this process owns outright, with no segment or // device behind them. These are the only mappings that may be merged, trimmed // or split, because for them the page table is the whole truth: fork copies // and munmap frees whatever is present in a range and skips the holes. static int vma_anon(const struct vma *v) { return v->used && !v->no_free && v->shmid < 0; } static struct vma * vma_free_slot(struct proc *p) { for (int i = 0; i < NVMA; i++) if (!p->vma[i].used) return &p->vma[i]; return 0; } // Record [va, va+len) as anonymous memory. // // Every mapping used to take a slot of its own, so a process could hold NVMA // of them and no more -- and zsh takes one per 16 KB heap block, so splitting // 30000 words into an array failed after 4 MB of heap with a silent exit 1. // Addresses come from a bump allocator, so a new mapping almost always starts // where the last one ended, and it extends that one instead. Linux merges // neighbouring anonymous mappings for the same reason. static int vma_note_anon(struct proc *p, uint64 va, uint64 len) { for (int i = 0; i < NVMA; i++) { struct vma *v = &p->vma[i]; if (vma_anon(v) && v->addr + v->length == va) { v->length += len; return 0; } } struct vma *v = vma_free_slot(p); if (!v) return -ENOMEM; v->addr = va; v->length = len; v->used = 1; v->no_free = 0; v->shmid = -1; return 0; } 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; } // A slot of its own, for the mappings that cannot be merged: a segment, // device memory, the framebuffer. Anonymous memory goes through // vma_note_anon() instead and usually needs none. // // -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. struct vma *v = vma_free_slot(myproc()); // 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; // A memfd. The pages already exist and belong to the segment, so this maps // them rather than allocating -- which is the entire point: the other // process that maps the same segment gets the same physical pages, and a // store by one is a load by the other because there is only one copy. // // MAP_PRIVATE is refused rather than given a copy. Every caller of this is // sharing on purpose, and a private mapping of an anonymous object is an // expensive way to ask for MAP_ANONYMOUS. if (f && f->type == FD_MEMFD) { int id = f->memfd_seg; uint64 have = memfd_size(id); if (!(flags & MAP_SHARED)) return -EINVAL; if (offset % PGSIZE) return -EINVAL; // Mapping past the end is not an error on Linux -- the pages simply // fault -- but there is no fault handler here to make that true, so // it is refused with the errno that says the range is wrong. if (offset + length > have) return -EINVAL; if (!v) return -ENOMEM; shm_ref(id); for (uint64 o = 0; o < length; o += PGSIZE) { char *pg = shm_page(id, (int)((offset + o) / PGSIZE)); if (!pg || mappages(myproc()->pgdir, (void*)(va + o), PGSIZE, V2P((uint64)pg), PTE_W | PTE_U) < 0) { vm_unmap_norelease(myproc()->pgdir, va, o); shm_detach(id); return -ENOMEM; } } v->addr = va; v->length = length; v->used = 1; v->no_free = 1; // the segment owns these pages v->shmid = id; return va; } // 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) { vm_unmap_range(myproc()->pgdir, va, a - va); return -ENOMEM; } memset(mem, 0, PGSIZE); if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0) { kfree(mem); vm_unmap_range(myproc()->pgdir, va, a - va); 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); // A private copy, so from here it is anonymous memory like any other. if (vma_note_anon(myproc(), va, length) < 0) { vm_unmap_range(myproc()->pgdir, va, length); return -ENOMEM; } return va; } // /dev/pci: the offset is the physical address of a BAR. // // Checked against the BARs of the devices *this process has claimed*, // which is what stops it being /dev/mem with extra steps -- without that // test a process could map the kernel's own memory and call it a driver. if (f && f->type == FD_INODE && f->ip->type == T_DEV && f->ip->major == PCIUSER_MAJOR) { uint64 phys = PGROUNDDOWN(offset); uint64 len = PGROUNDUP(length); if (!pciuser_mmio_ok(phys, len)) return -EACCES; if (!v) return -ENOMEM; // PTE_PWT: device registers are not memory, and a cached write to a // doorbell arrives whenever the cache feels like it, which for a // driver is indistinguishable from hardware that ignores you. if (mappages(myproc()->pgdir, (void*)va, len, phys, PTE_W | PTE_U | PTE_PWT) < 0) return -ENOMEM; v->addr = va; v->length = len; v->used = 1; v->no_free = 1; // device memory is not ours to free 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 (!v) return -ENOMEM; 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) { vm_unmap_range(myproc()->pgdir, va, a - va); return -ENOMEM; } memset(mem, 0, PGSIZE); if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0) { kfree(mem); vm_unmap_range(myproc()->pgdir, va, a - va); return -ENOMEM; } } if (vma_note_anon(myproc(), va, length) < 0) { vm_unmap_range(myproc()->pgdir, va, length); return -ENOMEM; } return va; } 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; struct proc *p = myproc(); if (argaddr(0, &addr) < 0 || argaddr(1, &length) < 0) return -1; length = PGROUNDUP(length); if (bad_user_range(addr, length) || (addr % PGSIZE)) return -EINVAL; uint64 end = addr + length; // A segment or device mapping goes only as a whole, by its own address. // Part of one is refused: unmapping it the anonymous way below would kfree // pages that are the framebuffer's or another process's. for (int i = 0; i < NVMA; i++) { struct vma *v = &p->vma[i]; if (!v->used || vma_anon(v)) continue; if (v->addr == addr) { if (v->shmid >= 0) shm_detach(v->shmid); if (v->no_free) vm_unmap_norelease(p->pgdir, addr, v->length); else vm_unmap_range(p->pgdir, addr, v->length); v->used = v->addr = v->length = 0; v->no_free = 0; v->shmid = -1; return 0; } if (addr < v->addr + v->length && end > v->addr) return -EINVAL; } // Anonymous memory: trim every mapping the range touches, splitting one // it falls in the middle of. With no slot for the split, the record keeps // covering the hole, which is harmless -- fork and munmap skip pages that // are not there, and the bump allocator never hands the addresses out // again. for (int i = 0; i < NVMA; i++) { struct vma *v = &p->vma[i]; if (!vma_anon(v)) continue; uint64 vs = v->addr, ve = v->addr + v->length; if (end <= vs || addr >= ve) continue; if (addr <= vs && end >= ve) { v->used = v->addr = v->length = 0; } else if (addr <= vs) { v->addr = end; v->length = ve - end; } else if (end >= ve) { v->length = addr - vs; } else { struct vma *w = vma_free_slot(p); if (w) { w->addr = end; w->length = ve - end; w->used = 1; w->no_free = 0; w->shmid = -1; v->length = addr - vs; } } } vm_unmap_range(p->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); } // --- memfd ------------------------------------------------------------------ // // memfd_create(2): a shared memory object reached by *descriptor* rather than // by name or by System V key. That is the whole difference and the whole // reason it exists -- two processes share memory by passing the fd over a // socket (SCM_RIGHTS, which already works), so neither has to agree on a path // or race to invent a key. // // It is the same segment machinery underneath, which is exactly what // docs/plan/foundations.md proposed: shmget()'s pages, refcounts, no_free // VMAs and the fork/exit accounting are all already right. What memfd adds is // a segment that starts empty and is sized by ftruncate(), and a file that // owns one reference to it. // // This is narrower than a page cache and deliberately so. Writable MAP_SHARED // of a *real file* still returns ENODEV, because nothing here could make its // contract true. An anonymous object has no such contract to break. // Grow or shrink a segment. Growing allocates zeroed pages; shrinking frees // from the end. // // Only legal while nothing has it mapped. Linux allows shrinking a mapped // memfd and faults the truncated pages out from under the mapper, which needs // a page fault handler that can say "this page is gone"; here there is no such // handler and a freed page would simply stay mapped to memory somebody else // now owns. Refusing is the honest version. static int shm_resize(int id, uint64 newsize) { struct shmseg *sg; int want, i; if (id < 0 || id >= NSHM) return -EINVAL; newsize = PGROUNDUP(newsize); want = (int)(newsize / PGSIZE); if (want > SHM_MAXPAGES) return -ENOMEM; shm_init_once(); acquire(&shmlock); sg = &shm[id]; if (!sg->used) { release(&shmlock); return -EINVAL; } // nattach is 1 for the file itself; anything above that is a live mapping. if (sg->nattach > 1 && want < sg->npages) { release(&shmlock); return -EBUSY; } release(&shmlock); // Allocate outside the lock: kalloc takes its own, and a page allocator // called with a spinlock held is how the two orders meet. for (i = sg->npages; i < want; i++) { char *pg = kalloc(); if (!pg) { // Undo this call's own allocations. Anything from an earlier // successful resize stays: the object is still that size. for (int j = sg->npages; j < i; j++) { kfree(sg->pages[j]); sg->pages[j] = 0; } return -ENOMEM; } memset(pg, 0, PGSIZE); sg->pages[i] = pg; } for (i = want; i < sg->npages; i++) { if (sg->pages[i]) { kfree(sg->pages[i]); sg->pages[i] = 0; } } acquire(&shmlock); sg->npages = want; sg->size = newsize; release(&shmlock); return 0; } // One page of a segment, by index. Used by the mmap path; the caller has // already taken a reference, so the page cannot go away underneath it. char * shm_page(int id, int pg) { char *p; if (id < 0 || id >= NSHM || pg < 0) return 0; shm_init_once(); acquire(&shmlock); p = (shm[id].used && pg < shm[id].npages) ? shm[id].pages[pg] : 0; release(&shmlock); return p; } uint64 memfd_size(int id) { uint64 n; if (id < 0 || id >= NSHM) return 0; shm_init_once(); acquire(&shmlock); n = shm[id].used ? shm[id].size : 0; release(&shmlock); return n; } int memfd_truncate(int id, uint64 len) { return shm_resize(id, len); } // read()/write() on a memfd, so it is a file rather than a token that only // mmap understands. Wayland clients use ftruncate and mmap, but a descriptor // that refuses read() is a surprise nobody needs. int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing) { struct shmseg *sg; int done = 0; if (id < 0 || id >= NSHM || n <= 0) return 0; shm_init_once(); sg = &shm[id]; if (!sg->used) return -EINVAL; if (off >= sg->size) return 0; // EOF, or a write past the end if (off + (uint64)n > sg->size) n = (int)(sg->size - off); while (done < n) { uint64 pos = off + (uint64)done; int pg = (int)(pos / PGSIZE); int inpg = (int)(pos % PGSIZE); int want = PGSIZE - inpg; if (want > n - done) want = n - done; char *p = sg->pages[pg] + inpg; if (writing) { if (user) { if (copy_from_user(p, buf + done, want) < 0) return -EFAULT; } else memmove(p, (char*)(buf + done), want); } else { if (user) { if (copy_to_user(buf + done, p, want) < 0) return -EFAULT; } else memmove((char*)(buf + done), p, want); } done += want; } return done; } addr_t sys_memfd_create(void) { addr_t namep; int flags, id = -1, fd; struct file *f; argaddr(0, &namep); argint(1, &flags); // The name is for /proc and for debuggers. There is no /proc//fd // here, so it is read for validation and dropped -- which is honest, and // better than storing a string nothing will ever show. if (namep) { char *nm; if (fetchstr(namep, &nm) < 0) return -EFAULT; } shm_init_once(); acquire(&shmlock); for (int i = 0; i < NSHM; i++) if (!shm[i].used) { memset(&shm[i], 0, sizeof(shm[i])); shm[i].used = 1; shm[i].key = IPC_PRIVATE; shm[i].size = 0; shm[i].npages = 0; shm[i].nattach = 1; // the file holds this one id = i; break; } release(&shmlock); if (id < 0) return -ENFILE; if ((f = filealloc()) == 0) { shm_detach(id); return -ENFILE; } if ((fd = fdalloc(f)) < 0) { fileclose(f); return -EMFILE; } f->type = FD_MEMFD; f->memfd_seg = id; f->readable = 1; f->writable = 1; f->off = 0; return fd; } 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. These returned a flat 0 for as long as the scheduler had // no notion of priority to report; it has one now (kernel/proc/proc.c), and a // stub that says "everything is nice 0" is a lie the moment `renice` works. // // PRIO_PROCESS is 0, and `who` of 0 means the caller -- both from POSIX. #define PRIO_PROCESS 0 addr_t sys_getpriority(void) { int which, who, nice = 0, r; argint(0, &which); argint(1, &who); if (which != PRIO_PROCESS) return -EINVAL; if ((r = proc_getnice(who, &nice)) < 0) return r; // getpriority(2) returns the nice value *offset by 20*, because -1 is a // legal nice level and also how a syscall says "error". libc subtracts the // 20 back off. Returning the raw nice value here makes `renice -n -1` look // like a failure to every caller that checks. return (addr_t)(20 - nice); } addr_t sys_setpriority(void) { int which, who, nice; argint(0, &which); argint(1, &who); argint(2, &nice); if (which != PRIO_PROCESS) return -EINVAL; return (addr_t)proc_setnice(who, nice); }