SchOS/kernel/sys/sys_mem.c
Nexoniarz a2d987db5f sched: fair share by virtual runtime, with nice
The scheduler was xv6's round-robin: run everything RUNNABLE in slot
order and yield on every timer tick. Slot order is not fairness, a
process alone on a CPU paid a full context switch a hundred times a
second to be handed straight back to itself, and there was no way to
say that one thing mattered more than another.

Every process now carries vruntime, the CPU time it has used divided
by its weight, and the scheduler runs the runnable process with the
smallest. Fairness and interactivity both fall out of that one rule;
nice is a weight, 1.25x per level as UNIX has always had it. A slice
is 20 ms shared among the runnable processes, never below 2 ms, and
the timer tick preempts only once it is spent. A process waking from
a long sleep is brought up to min_vruntime less one period, so it is
scheduled promptly without being owed everything it slept through;
fork inherits vruntime, so forking does not reset a debt.

getpriority/setpriority are real, with Linux's offset of 20 on the
return; /proc/<pid>/schedstat and the ctxt_switches lines of status
report what the scheduler did. docs/architecture/scheduler.md has the
reasoning, including why the run queue is still a scan of ptable.

This was written in an earlier session and left uncommitted. Finished
here: a signal waking a sleeper goes through the same vruntime floor
as wakeup() does, and sched_yield() yields -- it returned 0 doing
nothing, harmless while every tick was a yield and a spin of a whole
slice once ticks stopped being one.

Checked under QEMU on 1 and 4 CPUs: a nice 10 loop beside a nice 0
one gets about a tenth of a shared CPU, and a command typed meanwhile
answers in 26 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:01:27 +02:00

879 lines
28 KiB
C

#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/<pid>/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);
}