diff --git a/include/aarch64/vm_param.h b/include/aarch64/vm_param.h index 379002031d..380d483780 100644 --- a/include/aarch64/vm_param.h +++ b/include/aarch64/vm_param.h @@ -3,6 +3,7 @@ #define PAGESIZE 4096 #define SUPERPAGESIZE (1 << 21) /* 2 MB */ +#define PAGE_SHIFT 12 #define KERNEL_SPACE_BEGIN 0xffff000000000000L #define KERNEL_SPACE_END 0xffffffffffffffffL diff --git a/include/mips/vm_param.h b/include/mips/vm_param.h index a5e3fe49fc..06665a5fb3 100644 --- a/include/mips/vm_param.h +++ b/include/mips/vm_param.h @@ -12,6 +12,7 @@ #define PAGESIZE 4096 #define SUPERPAGESIZE (1 << 22) /* 4 MB */ +#define PAGE_SHIFT 12 #define VM_PHYSSEG_NMAX 16 diff --git a/include/sys/vm_amap.h b/include/sys/vm_amap.h new file mode 100644 index 0000000000..9bd7f33528 --- /dev/null +++ b/include/sys/vm_amap.h @@ -0,0 +1,68 @@ +#ifndef _SYS_UVM_AMAP_H_ +#define _SYS_UVM_AMAP_H_ + +#ifndef _KERNEL +#error "Do not use this header file outside kernel code!" +#endif + +#include + +typedef struct vm_anon vm_anon_t; +typedef struct vm_aref vm_aref_t; +typedef struct vm_amap vm_amap_t; + +struct vm_aref { + int ar_pageoff; /* page offset into amap we start */ + vm_amap_t *ar_amap; /* pointer to amap */ +}; + +/* + * The upper layer of UVM’s two-layer mapping scheme. + * A vm_amap describes an area of anonymous memory. + * + * Field markings and the corresponding locks: + * (@) vm_amap::am_lock + */ +struct vm_amap { + mtx_t am_lock; + int am_ref; /* (@) reference counter */ + int am_nslot; /* (@) number of allocated slots */ + int am_nused; /* (@) number of used slots */ + int *am_slot; /* (@) slots of used anons - refers to am_bckptr */ + int *am_bckptr; /* (@) stack of used anons - refers to am_anon & am_slots */ + vm_anon_t **am_anon; /* (@) anons in that map */ +}; + +/* + * amap interface + */ + +/* Allocate a new amap. */ +vm_amap_t *vm_amap_alloc(void); +/* Acquire amap->am_lock. */ +void vm_amap_lock(vm_amap_t *amap); +/* Release amap->am_lock. */ +void vm_amap_unlock(vm_amap_t *amap); + +/* Increase reference counter. + * + * Must be called with amap:am_lock held. */ +void vm_amap_hold(vm_amap_t *amap); + +/* Decrement reference counter and destroy amap if it has reached 0. + * + * Must be called with amap:am_lock held. + * Releases amap:am_lock. */ +void vm_amap_drop(vm_amap_t *amap); +/* Lookup an anon at offset in amap. */ +vm_anon_t *vm_amap_lookup(vm_aref_t *aref, vaddr_t offset); +/* Add an annon at offset in amap. */ +void vm_amap_add(vm_aref_t *aref, vm_anon_t *anon, vaddr_t offset); +/* Remove an annon from an amap. */ +void vm_amap_remove(vm_aref_t *aref, vaddr_t offset); + +/* TODO: ppref */ + +/* TODO: aref interface */ + +#endif /* !_SYS_UVM_AMAP_H_ */ diff --git a/sys/kern/Makefile b/sys/kern/Makefile index 525434d323..cef36571e7 100644 --- a/sys/kern/Makefile +++ b/sys/kern/Makefile @@ -68,6 +68,7 @@ SOURCES = \ uart_tty.c \ uio.c \ ustack.c \ + vm_amap.c \ vfs.c \ vfs_name.c \ vfs_readdir.c \ diff --git a/sys/kern/vm_amap.c b/sys/kern/vm_amap.c new file mode 100644 index 0000000000..6195b561bc --- /dev/null +++ b/sys/kern/vm_amap.c @@ -0,0 +1,157 @@ +#define KL_LOG KL_VM +#include +#include +#include +#include +#include +#include + +#define AMAP_SLOT_EMPTY (-1) + +static POOL_DEFINE(P_AMAP, "vm_amap", sizeof(vm_amap_t)); + +static inline int vm_amap_slot(vm_aref_t *aref, vaddr_t offset) { + assert(offset % PAGESIZE == 0); + return (int)(aref->ar_pageoff + (offset >> PAGE_SHIFT)); +} + +vm_amap_t *vm_amap_alloc(void) { + vm_amap_t *amap = pool_alloc(P_AMAP, M_ZERO); + mtx_init(&amap->am_lock, 0); + return amap; +} + +void vm_amap_lock(vm_amap_t *amap) { + mtx_lock(&amap->am_lock); +} + +void vm_amap_unlock(vm_amap_t *amap) { + mtx_unlock(&amap->am_lock); +} + +void vm_amap_hold(vm_amap_t *amap) { + assert(mtx_owned(&amap->am_lock)); + amap->am_ref++; +} + +/* TODO: revisit after vm_anon implementation. */ +static void vm_anon_free(vm_anon_t *anon) { + assert(anon != NULL); +} + +static void vm_amap_free(vm_amap_t *amap) { + for (int i = 0; i < amap->am_nused; ++i) { + int slot = amap->am_bckptr[i]; + vm_anon_free(amap->am_anon[slot]); + } + + kfree(M_TEMP, amap->am_slot); + kfree(M_TEMP, amap->am_bckptr); + kfree(M_TEMP, amap->am_anon); + + pool_free(P_AMAP, amap); +} + +void vm_amap_drop(vm_amap_t *amap) { + assert(mtx_owned(&amap->am_lock)); + amap->am_ref--; + + vm_amap_unlock(amap); + if (amap->am_ref == 0) + vm_amap_free(amap); +} + +static vm_anon_t *vm_amap_lookup_nolock(vm_amap_t *amap, int slot) { + if (amap->am_nslot <= slot) + return NULL; + return amap->am_anon[slot]; +} + +vm_anon_t *vm_amap_lookup(vm_aref_t *aref, vaddr_t offset) { + vm_amap_t *amap = aref->ar_amap; + int slot = vm_amap_slot(aref, offset); + + SCOPED_MTX_LOCK(&amap->am_lock); + return vm_amap_lookup_nolock(amap, slot); +} + +static void __vm_amap_extend(vm_amap_t *amap, int size) { + /* new entries in slot & bckptr will be set to AMAP_SLOT_EMPTY */ + int *slot = kmalloc(M_TEMP, sizeof(int) * size, 0); + int *bckptr = kmalloc(M_TEMP, sizeof(int) * size, 0); + /* new entries in anon will be set to NULL */ + vm_anon_t **anon = kmalloc(M_TEMP, sizeof(vm_anon_t *) * size, M_ZERO); + + /* old unused entires will be set to AMAP_SLOT_EMPTY */ + memcpy(slot, amap->am_slot, sizeof(int) * amap->am_nslot); + memcpy(bckptr, amap->am_bckptr, sizeof(int) * amap->am_nslot); + + /* copy pointers only for allocated anons */ + for (int i = 0; i < amap->am_nused; ++i) { + int bslot = amap->am_bckptr[i]; + anon[bslot] = amap->am_anon[bslot]; + } + + /* set new entries to AMAP_SLOT_EMPTY */ + for (int i = amap->am_nslot; i < size; ++i) { + slot[i] = AMAP_SLOT_EMPTY; + bckptr[i] = AMAP_SLOT_EMPTY; + } + + kfree(M_TEMP, amap->am_slot); + kfree(M_TEMP, amap->am_bckptr); + kfree(M_TEMP, amap->am_anon); + + amap->am_slot = slot; + amap->am_bckptr = bckptr; + amap->am_anon = anon; + + amap->am_nslot = size; +} + +static void vm_amap_extend(vm_amap_t *amap, int size) { + int capacity = max(amap->am_nslot, 1); + while (capacity < size) + capacity *= 2; + __vm_amap_extend(amap, capacity); +} + +static void vm_amap_add_nolock(vm_amap_t *amap, vm_anon_t *anon, int slot) { + if (slot >= amap->am_nslot) + vm_amap_extend(amap, slot); + + assert(amap->am_anon[slot] == NULL); + amap->am_anon[slot] = anon; + amap->am_slot[slot] = amap->am_nused; + amap->am_bckptr[amap->am_nused++] = slot; +} + +void vm_amap_add(vm_aref_t *aref, vm_anon_t *anon, vaddr_t offset) { + vm_amap_t *amap = aref->ar_amap; + int slot = vm_amap_slot(aref, offset); + + SCOPED_MTX_LOCK(&amap->am_lock); + vm_amap_add_nolock(amap, anon, slot); +} + +static void vm_amap_remove_nolock(vm_amap_t *amap, int slot) { + assert(amap->am_nslot > slot); + assert(amap->am_anon[slot] != NULL); + + vm_anon_free(amap->am_anon[slot]); + amap->am_anon[slot] = NULL; + + int bslot = amap->am_slot[slot]; + amap->am_slot[slot] = AMAP_SLOT_EMPTY; + amap->am_bckptr[bslot] = AMAP_SLOT_EMPTY; + swap(amap->am_bckptr[bslot], amap->am_bckptr[amap->am_nused - 1]); + amap->am_nused--; +} + +void vm_amap_remove(vm_aref_t *aref, vaddr_t offset) { + vm_amap_t *amap = aref->ar_amap; + int slot = vm_amap_slot(aref, offset); + + SCOPED_MTX_LOCK(&amap->am_lock); + vm_amap_remove_nolock(amap, slot); +} diff --git a/sys/tests/Makefile b/sys/tests/Makefile index 11b37a6a6d..1ea43dc04e 100644 --- a/sys/tests/Makefile +++ b/sys/tests/Makefile @@ -30,6 +30,7 @@ SOURCES = \ turnstile_propagate_many.c \ uiomove.c \ utest.c \ + vm_amap.c \ vm_map.c \ devclass.c \ vfs.c \ diff --git a/sys/tests/vm_amap.c b/sys/tests/vm_amap.c new file mode 100644 index 0000000000..95672cc0a3 --- /dev/null +++ b/sys/tests/vm_amap.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include + +/* Test internal state of amap. */ +static int test_amap_simple(void) { + vm_amap_t *amap = vm_amap_alloc(); + assert(amap != NULL); + + vm_aref_t aref = {.ar_pageoff = 0, .ar_amap = amap}; + + vm_amap_add(&aref, (vm_anon_t *)1, 3 * PAGESIZE); + vm_amap_add(&aref, (vm_anon_t *)2, 7 * PAGESIZE); + vm_amap_add(&aref, (vm_anon_t *)3, 2 * PAGESIZE); + vm_amap_add(&aref, (vm_anon_t *)4, 14 * PAGESIZE); + + vm_amap_lock(amap); + assert(amap->am_nslot == 16); + assert(amap->am_nused == 4); + + assert(amap->am_anon[3] == (vm_anon_t *)1); + assert(amap->am_anon[7] == (vm_anon_t *)2); + assert(amap->am_anon[2] == (vm_anon_t *)3); + assert(amap->am_anon[14] == (vm_anon_t *)4); + + assert(amap->am_bckptr[0] == 3); + assert(amap->am_bckptr[1] == 7); + assert(amap->am_bckptr[2] == 2); + assert(amap->am_bckptr[3] == 14); + + assert(amap->am_slot[3] == 0); + assert(amap->am_slot[7] == 1); + assert(amap->am_slot[2] == 2); + assert(amap->am_slot[14] == 3); + vm_amap_unlock(amap); + + assert(vm_amap_lookup(&aref, 7 * PAGESIZE) == (vm_anon_t *)2); + vm_amap_remove(&aref, 7 * PAGESIZE); + assert(amap->am_nused == 3); + assert(vm_amap_lookup(&aref, 7 * PAGESIZE) == NULL); + + vm_amap_lock(amap); + assert(amap->am_slot[7] == -1); + assert(amap->am_bckptr[1] == 14); + assert(amap->am_bckptr[3] == -1); + assert(amap->am_anon[7] == NULL); + + vm_amap_drop(amap); + + return KTEST_SUCCESS; +} + +KTEST_ADD(vm_amap_simple, test_amap_simple, 0);