#include <kernel/kernel.h> #include <kernel/memory.h> #include <kernel/vmm.h> #include <kernel/proc.h> #include <kernel/thread.h> #include <malloc.h> extern process_t proc_idle; void* vmmMap(process_t* proc, size_t pages, addr_t start, void *dest, unsigned type, dword flags) { vm_area_t *area, *collide; //addr_t oldEnd; area = malloc(sizeof(vm_area_t)); if (!area) return NULL; collide = NULL; if (start == NULL && (flags & MEM_LITERAL) == 0) { start = proc->vmm_end; collide = vmmArea(proc, (void*) start); assert(collide == NULL); while ((collide = vmmArea(proc, (void*) start))) { wprintf(L"vmmAlloc: new block at %08x(%x) collides with block at %08x(%x)\n", start, pages * PAGE_SIZE, (addr_t) collide->start, collide->pages * PAGE_SIZE); start = (addr_t) collide->start + collide->pages * PAGE_SIZE; } } else if ((collide = vmmArea(proc, (void*) start))) { wprintf(L"vmmAlloc: block at %08x collides with block at %08x\n", start, (addr_t) collide->start); return NULL; } if (collide) wprintf(L"vmmAlloc: moved block to %08x\n", start); //wprintf(L"vmmAlloc: allocating %d page(s) at %x\n", pages, start); area->next = NULL; area->start = (void*) start; area->owner = proc; area->pages = pages; //area->phys = NULL; area->flags = flags; area->prev = proc->last_vm_area; //area->is_committed = false; area->type = type; switch (type) { case VM_AREA_NORMAL: break; case VM_AREA_MAP: area->dest.phys_map = (addr_t) dest; break; case VM_AREA_SHARED: area->dest.shared_from = NULL; break; case VM_AREA_FILE: area->dest.file = (file_t*) dest; break; } semAcquire(&proc->sem_vmm); if (proc->last_vm_area) proc->last_vm_area->next = area; if (!proc->first_vm_area) proc->first_vm_area = area; proc->last_vm_area = area; //oldEnd = proc->vmm_end; while ((collide = vmmArea(proc, (void*) proc->vmm_end))) proc->vmm_end = (addr_t) collide->start + collide->pages * PAGE_SIZE; //if (oldEnd != proc->vmm_end) //wprintf(L"vmmAlloc: vmm_end adjusted from %08x to %08x\n", oldEnd, proc->vmm_end); semRelease(&proc->sem_vmm); if (flags & MEM_COMMIT) vmmCommit(area, NULL, (size_t) -1); return (void*) start; } //! Allocates an area of memory in the specified process's address space. /*! * The vmmArea() function can be used to get a vm_area_t structure from * a pointer. * * The memory can only be accessed from with in the specified process's * context. * * \param proc The process which will access the memory * \param pages The number of 4KB pages to allocate * \param start The virtual address at which the block will start. If this * is NULL an address will be chosen (unless flags includes the * MEM_LITERAL flag). * * \return A pointer to the start of the block, or NULL if the block could * not be allocated. */ void* vmmAlloc(process_t* proc, size_t pages, addr_t start, dword flags) { return vmmMap(proc, pages, start, NULL, VM_AREA_NORMAL, flags); } void* vmmShare(vm_area_t* area, process_t* proc, addr_t start, dword flags) { vm_area_t *new_area, *collide; if (start == NULL) start = (addr_t) area->start; if ((collide = vmmArea(proc, (void*) start))) { wprintf(L"vmmShare: block at %08x collides with block at %08x\n", start, (addr_t) collide->start); return NULL; } new_area = malloc(sizeof(vm_area_t)); new_area->next = NULL; new_area->start = (void*) start; new_area->owner = proc; new_area->pages = area->pages; //area->phys = NULL; new_area->flags = flags; new_area->prev = proc->last_vm_area; //new_area->is_committed = false; new_area->type = VM_AREA_SHARED; new_area->dest.shared_from = area; return new_area->start; } void* vmmMapFile(process_t *proc, addr_t start, size_t pages, file_t *file, dword flags) { flags &= ~(MEM_COMMIT | MEM_ZERO); return vmmMap(proc, pages, start, file, VM_AREA_FILE, flags); } //! Frees an area of memory allocated by the vmmAlloc() function. /*! * \param proc The process which contains the area * \param area The area of memory to be freed */ void vmmFree(vm_area_t* area) { if (area == NULL) return; //wprintf(L"vmmFree: %d => %x...", area->pages, area->start); if (area->start != NULL) vmmUncommit(area); semAcquire(&area->owner->sem_vmm); if (area->prev) area->prev->next = area->next; if (area->next) area->next->prev = area->prev; if (area->owner->first_vm_area == area) area->owner->first_vm_area = NULL; if (area->owner->last_vm_area == area) area->owner->last_vm_area = NULL; semRelease(&area->owner->sem_vmm); free(area); //wprintf(L"done\n"); } bool vmmDoMapFile(vm_area_t *area, addr_t start, size_t pages) { IMAGE_DOS_HEADER *dos; IMAGE_PE_HEADERS *pe; wprintf(L"vmmDoMapFile: area = %x(%d) mapping file at %x(%d)\n", area->start, area->pages, start, pages); if (area->flags & MEM_FILE_IMAGE && start != 0) { halt(0); dos = (IMAGE_DOS_HEADER*) area->start; pe = (IMAGE_PE_HEADERS*) (area->start + dos->e_lfanew); } fsSeek(area->dest.file, start - (addr_t) area->start); fsRead(area->dest.file, (void*) start, pages * PAGE_SIZE); return true; } //! Commits an area of memory, by mapping and initialising it. /*! * This function is called by vmmAlloc() if the MEM_COMMIT flag is specified. * Otherwise, it will be called when a page fault occurs in the area. * * Physical storage is allocated (unless the MEM_LITERAL flag is specified) and * it is mapped into the address space of the specified process. If the * process is the one currently executing, that area of the address space * is invalidated, allowing it to be accessed immediately. If the MEM_ZERO * flag was specified then the area is zeroed; otherwise, the contents of * the area are undefined. * * \param proc The process which contains the area * \param area The area to be committed * \return true if the area could be committed, false otherwise. */ bool vmmCommit(vm_area_t* area, addr_t start, size_t pages) { dword f; //if (area->is_committed) //vmmUncommit(area); if (start == NULL) start = (addr_t) area->start; if (pages == (size_t) -1) pages = area->pages; semAcquire(&area->owner->sem_vmm); //wprintf(L"vmmCommit: committing %dKB: ", (area->pages * PAGE_SIZE) / 1024); f = PRIV_PRES; if ((area->flags & 3) == 3) f |= PRIV_USER; else f |= PRIV_KERN; if (area->flags & MEM_READ) f |= PRIV_RD; if (area->flags & MEM_WRITE) f |= PRIV_WR; if (area->flags & MEM_LITERAL) memMap(area->owner->page_dir, (addr_t) area->start, (addr_t) area->start, area->pages, f); else { int page; addr_t virt, phys; virt = start; if (area->type == VM_AREA_MAP) phys = area->dest.phys_map; else phys = NULL; for (page = 0; page < pages; page++) { if (area->type == VM_AREA_NORMAL || area->type == VM_AREA_FILE) { phys = memAlloc(); if (!phys) { semRelease(&area->owner->sem_vmm); return false; } } memMap(area->owner->page_dir, virt, phys, 1, f); virt += PAGE_SIZE; if (area->type == VM_AREA_MAP) phys += PAGE_SIZE; } } if (area->owner == current->process) { vmmInvalidate(area, start, pages); if (area->type == VM_AREA_FILE) vmmDoMapFile(area, start, pages); else if (area->flags & MEM_ZERO) i386_lmemset32(start, 0, PAGE_SIZE * pages); } else { assert((area->flags & MEM_ZERO) == 0); assert(area->type != VM_AREA_FILE); } //area->is_committed = true; semRelease(&area->owner->sem_vmm); return true; } //! Removes an area of memory from the address space of the specified process. /*! * The physical storage associated with the area is deallocated and the area is * un-mapped from the address space of the process. * * \param proc The process which contains the area * \param area The area to be uncommitted */ void vmmUncommit(vm_area_t* area) { int i; byte* virt; addr_t phys; //wprintf(L"vmmUncommit: %d => %x...", area->pages, area->start); //if (area->is_committed) { //memFree(area->phys, area->pages); semAcquire(&area->owner->sem_vmm); virt = area->start; for (i = 0; i < area->pages; i++) { if ((area->flags & MEM_LITERAL) == 0) { phys = memTranslate(area->owner->page_dir, virt) & -PAGE_SIZE; if (phys) memFree(phys); } memMap(area->owner->page_dir, (addr_t) virt, 0, 1, 0); virt += PAGE_SIZE; } semRelease(&area->owner->sem_vmm); //area->is_committed = false; } //wprintf(L"done\n"); } //! Updates the processor's page table cache associated with the specified area. /*! * It is necessary to invalidate pages after their linear-to-physical address * mapping has changed if they are part of the current address space. * * \param area The area to be invalidated */ void vmmInvalidate(vm_area_t* area, addr_t start, size_t pages) { addr_t virt; if (start == NULL) start = (addr_t) area->start; if (pages == (size_t) -1) pages = area->pages; for (virt = 0; virt < pages * PAGE_SIZE; virt += PAGE_SIZE) invalidate_page((byte*) start + virt); } //! Retrieves the vm_area_t structure associated with the specified address. /*! * \param proc The process which contains the area * \param ptr The linear address which will be contained within the area * returned * \return A vm_area_t structure describing the area of memory around the * address, or NULL if the address had not been allocated. */ vm_area_t* vmmArea(process_t* proc, const void* ptr) { vm_area_t* area; for (area = proc->first_vm_area; area; area = area->next) if ((addr_t) ptr >= (addr_t) area->start && (addr_t) ptr < (addr_t) area->start + area->pages * PAGE_SIZE) return area; return NULL; }