diff options
Diffstat (limited to 'src/kernel/user/pager.c')
-rw-r--r-- | src/kernel/user/pager.c | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/src/kernel/user/pager.c b/src/kernel/user/pager.c new file mode 100644 index 0000000..c892599 --- /dev/null +++ b/src/kernel/user/pager.c @@ -0,0 +1,183 @@ +#include <string.h> + +#include <pager.h> +#include <vfs.h> +#include <process.h> +#include <frame.h> + +#define ENT_FRAME_SHIFT 12 + + +// ========== // +// SWAP PAGER // +// ========== // + +static void swap_page_in(pager_t *p, size_t offset, size_t len); +static void swap_page_out(pager_t *p, size_t offset, size_t len); +static void swap_page_release(pager_t *p, size_t offset, size_t len); + +static pager_ops_t swap_pager_ops = { + .page_in = swap_page_in, + .page_commit = 0, + .page_out = swap_page_out, + .page_release = swap_page_release, +}; + +pager_t *new_swap_pager(size_t size) { + pager_t *p = (pager_t*)malloc(sizeof(pager_t)); + if (p == 0) return 0; + + p->pages = create_hashtbl(id_key_eq_fun, id_hash_fun, 0); + if (p->pages == 0) goto error; + + p->ops = &swap_pager_ops; + p->size = size; + p->lock = MUTEX_UNLOCKED; + p->maps = 0; + + return p; + +error: + if (p) free(p); + return 0; +} + +void swap_page_in(pager_t *p, size_t offset, size_t len) { + ASSERT(PAGE_ALIGNED(offset)); + size_t npages = PAGE_ALIGN_UP(len) / PAGE_SIZE; + + void *region = region_alloc(PAGE_SIZE, "Page zero area", 0); + if (region == 0) return; + + for (size_t i = 0; i < npages; i++) { + size_t page = offset + (i * PAGE_SIZE); + if (hashtbl_find(p->pages, (void*)page) == 0) { + uint32_t frame = frame_alloc(1); + if (frame != 0) { + pd_map_page(region, frame, true); + memset(region, 0, PAGE_SIZE); + pd_unmap_page(region); + } + if (!hashtbl_add(p->pages, (void*)page, (void*)(frame << ENT_FRAME_SHIFT))) { + frame_free(frame, 1); + } + } + } + + region_free(region); +} + +void swap_page_out(pager_t *p, size_t offset, size_t len) { + // later : save to swap file... +} + +void swap_page_release(pager_t *p, size_t offset, size_t len) { + ASSERT(PAGE_ALIGNED(offset)); + size_t npages = PAGE_ALIGN_UP(len) / PAGE_SIZE; + + for (size_t i = 0; i < npages; i++) { + size_t page = offset + (i * PAGE_SIZE); + uint32_t frame = (uint32_t)hashtbl_find(p->pages, (void*)page) >> ENT_FRAME_SHIFT; + if (frame != 0) { + hashtbl_remove(p->pages, (void*)page); + frame_free(frame, 1); + } + } +} + +// ======================= // +// GENERIC PAGER FUNCTIONS // +// ======================= // + +void delete_pager(pager_t *p) { + ASSERT(p->maps == 0); + + pager_commit_dirty(p); + + mutex_lock(&p->lock); + + p->ops->page_release(p, 0, p->size); + delete_hashtbl(p->pages); + free(p); +} + +void pager_access(pager_t *p, size_t offset, size_t len, bool accessed, bool dirty) { + mutex_lock(&p->lock); + + ASSERT(PAGE_ALIGNED(offset)); + size_t npages = PAGE_ALIGN_UP(len) / PAGE_SIZE; + + for (size_t i = 0; i < npages; i++) { + size_t page = offset + (i * PAGE_SIZE); + uint32_t v = (uint32_t)hashtbl_find(p->pages, (void*)page); + if (v != 0) { + hashtbl_change(p->pages, (void*)page, + (void*)(v | (accessed ? PAGE_ACCESSED : 0) | (dirty ? PAGE_DIRTY : 0))); + } + } + + mutex_unlock(&p->lock); +} + +void pager_commit_dirty(pager_t *p) { + if (!p->ops->page_commit) return; + + mutex_lock(&p->lock); + + for (size_t i = 0; i < p->size; i += PAGE_SIZE) { + uint32_t v = (uint32_t)hashtbl_find(p->pages, (void*)i); + if (v & PAGE_DIRTY) { + p->ops->page_commit(p, i, PAGE_SIZE); + hashtbl_change(p->pages, (void*)i, (void*)(v & ~PAGE_DIRTY)); + } + + } + + mutex_unlock(&p->lock); +} + +int pager_get_frame(pager_t *p, size_t offset) { + mutex_lock(&p->lock); + + ASSERT(PAGE_ALIGNED(offset)); + + uint32_t v = (uint32_t)hashtbl_find(p->pages, (void*)offset); + if (v == 0) { + p->ops->page_in(p, offset, PAGE_SIZE); + } + + uint32_t ret = (uint32_t)hashtbl_find(p->pages, (void*)offset) >> ENT_FRAME_SHIFT; + + mutex_unlock(&p->lock); + + return ret; +} + +void pager_add_map(pager_t *p, user_region_t *reg) { + mutex_lock(&p->lock); + + reg->next_for_pager = p->maps; + p->maps = reg; + + mutex_unlock(&p->lock); +} + +void pager_rm_map(pager_t *p, user_region_t *reg) { + mutex_lock(&p->lock); + + if (p->maps == reg) { + p->maps = reg->next_for_pager; + } else { + for (user_region_t *it = p->maps; it->next_for_pager != 0; it = it->next_for_pager) { + if (it->next_for_pager == reg) { + it->next_for_pager = reg->next_for_pager; + break; + } + } + } + reg->next_for_pager = 0; + + mutex_unlock(&p->lock); +} + +/* vim: set ts=4 sw=4 tw=0 noet :*/ |