diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3729dc6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,10 @@ +BasedOnStyle: LLVM +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +PointerAlignment: Left +DerivePointerAlignment: false +SortIncludes: false +ReflowComments: false +AlignConsecutiveMacros: true +ColumnLimit: 0 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..578af01 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "[c]": { + "editor.defaultFormatter": "ms-vscode.cpptools", + "editor.tabSize": 2, + "editor.insertSpaces": true + }, + "[cpp]": { + "editor.defaultFormatter": "ms-vscode.cpptools", + "editor.tabSize": 2, + "editor.insertSpaces": true + }, + "C_Cpp.clang_format_style": "file", + "C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: LLVM, IndentWidth: 2, UseTab: Never, PointerAlignment: Left }" +} \ No newline at end of file diff --git a/Makefile b/Makefile index 7dbd8e2..97532ee 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ SD_DMA_TICKS ?= 1 # number of emulator ticks per 4-byte SD DMA transfer # memory map TEXT_LOAD_ADDR := 0x10000 -DATA_LOAD_ADDR := 0x80000 +DATA_LOAD_ADDR := 0x90000 RODATA_LOAD_ADDR := 0xD0000 BSS_LOAD_ADDR := 0xE0000 @@ -502,6 +502,21 @@ define assemble_kernel_image echo "Kernel build error: section bases are not ordered text->rodata->data->bss->end." >&2; \ exit 1; \ fi; \ + text_zone_size=$$(( $(DATA_LOAD_ADDR) - $(TEXT_LOAD_ADDR) )); \ + data_zone_size=$$(( $(RODATA_LOAD_ADDR) - $(DATA_LOAD_ADDR) )); \ + rodata_zone_size=$$(( $(BSS_LOAD_ADDR) - $(RODATA_LOAD_ADDR) )); \ + if [ $$((rodata_base - text_base)) -gt $$text_zone_size ]; then \ + echo "Kernel build error: text section exceeds its reserved zone $(TEXT_LOAD_ADDR)-$(DATA_LOAD_ADDR)." >&2; \ + exit 1; \ + fi; \ + if [ $$((data_base - rodata_base)) -gt $$rodata_zone_size ]; then \ + echo "Kernel build error: rodata section exceeds its reserved zone $(RODATA_LOAD_ADDR)-$(BSS_LOAD_ADDR)." >&2; \ + exit 1; \ + fi; \ + if [ $$((bss_base - data_base)) -gt $$data_zone_size ]; then \ + echo "Kernel build error: data section exceeds its reserved zone $(DATA_LOAD_ADDR)-$(RODATA_LOAD_ADDR)." >&2; \ + exit 1; \ + fi; \ text_start_block=$$((text_base / $(KERNEL_BLOCK_SIZE))); \ rodata_start_block=$$((rodata_base / $(KERNEL_BLOCK_SIZE))); \ data_start_block=$$((data_base / $(KERNEL_BLOCK_SIZE))); \ diff --git a/docs/filesystem.md b/docs/filesystem.md index 9d81672..fb572aa 100644 --- a/docs/filesystem.md +++ b/docs/filesystem.md @@ -62,7 +62,7 @@ The filesystem uses several lock layers: - rwx permission enforcement - uid / gid - atime / mtime / ctime updates -- VFS layer or page cache +- VFS layer ### Tests - `ext_read.c` diff --git a/docs/kernel.md b/docs/kernel.md index ea11f0a..272d2ad 100644 --- a/docs/kernel.md +++ b/docs/kernel.md @@ -53,7 +53,7 @@ See `filesystem.md` for more details. ## Virtual Memory Uses a 2-level table, similar to x86 -TLB is software managed, so any miss invoked the tlb handler +TLB is software managed, so any miss invokes the tlb handler VMEM currently supports: - private anonymous diff --git a/docs/kernel_mem_map.md b/docs/kernel_mem_map.md index db8660b..da8a0d3 100644 --- a/docs/kernel_mem_map.md +++ b/docs/kernel_mem_map.md @@ -6,11 +6,11 @@ Interrupt Vector Table ### 0x400 - ... Where BIOS code is loaded (32KiB reserved). Can overwrite once kernel is entered. -### 0x10000 - 0x80000 -Kernel text (448KiB reserved for now) +### 0x10000 - 0x90000 +Kernel text (512KiB reserved for now) -### 0x80000 - 0xD0000 -Kernel data (320KiB) +### 0x90000 - 0xD0000 +Kernel data (256KiB) ### 0xD0000 - 0xE0000 Kernel rodata (64KiB) diff --git a/docs/page_cache.md b/docs/page_cache.md new file mode 100644 index 0000000..c3ddc3b --- /dev/null +++ b/docs/page_cache.md @@ -0,0 +1,61 @@ +## Page Cache + +The kernel page cache serves as a canonicalizing mapping for accessing files, as well as a cache for file-backed pages to provide faster access. + +### Structure + +The cache is a simple hash-table mapping from `(inode pointer, page offset)` to a cached page entry, which contains a pointer to the physical page data and the number of bytes from the file that belong to this page. The cache is protected by a single global blocking lock and uses chaining for collision handling. + +The cache key is the tuple of the node's cached inode pointer and the supplied page offset. The hash function is a simple XOR of those values modulo the hash table size. + +### Supported Page Cache Features + +#### Initialization +`page_cache_init()` allocates the hash table, stores the configured bucket count, initializes the cache lock, and clears all buckets to `NULL`. + +#### Lookup / Acquire +`page_cache_acquire()` returns the cached page entry, locking the backing page. + +If the entry is already cached. On a hit, it returns the existing entry. On a miss, it allocates a fresh physical page, inserts a new entry into the cache, locks the backing page, reads up to one frame from the backing node with `node_read_all()`, zero-fills any remaining bytes in the frame, and returns the newly created entry. +- On a hit, the page cache lock must be released in order to claim the page lock; this creates a window for eviction and necessitates revalidation +- On a miss, the freshly allocated frame is pinned, and thus not evictable + +The caller is expected to update the page's metadata (mostly the reverse mapping) and release the page lock when done. + +#### Release / Writeback +Page cache entries are released when the backing page is evicted by `page_evict()`. Dirty pages are written back first with `node_write_all()` using the entry's recorded `file_bytes` value. Clean pages are discarded without writeback. + +#### Eviction +`page_evict()` can be called on the metadata of a frame to free the frame and evict the cache entry. It may only be called if the backing frame is not pinned, and if the caller holds the page lock. + +Page eviction uses the reverse mapping (stored in `page->refs`) to find and invalidate all virtual mappings before reclaiming the physical frame. The pathway: +- Lock the INode (prevents re-caching of the page while eviction writeback is in-flight) +- Remove the entry from the cache +- For each `PageRef` in `page->refs`, invalidate the corresponding PTE and perform a TLB shootdown (see **Reverse Mapping** in vmem.md) +- If the page is dirty (checked via `page->flags`), write it back to the backing node with `node_write_all()` using the entry's recorded `file_bytes` value +- Free the backing frame via `physmem_free()` + +Eviction is currently invoked only by explicit caller requests; there is no automatic reclamation policy. // TODO: add an eviction policy + +### Data Stored Per Entry + +- cached inode pointer and page offset key +- pointer to the physical page data +- `file_bytes`, the number of bytes from the file that belong to this page +- next pointer for hash-chain collision handling + +### Locking + +- `page_cache_init()` initializes a single blocking lock for the whole cache +- `page_cache_acquire()` acquires that lock while it inspects or mutates the hash table +- `page_cache_lookup()` and `page_cache_insert()` do not lock on their own and are only safe to call while the cache lock is held + +#### Lock Ordering +Locks must be acquired in order of decreasing granularity (i.e. you cannot hold a coarser lock while contesting for a finer lock). Specifically, locks must be acquired in the following order: +- Page lock +- INode lock +- Global page cache lock + +### Not Yet Supported +- Page replacement policy +- Error handling for allocation or I/O failure \ No newline at end of file diff --git a/docs/physmem.md b/docs/physmem.md index 7d59c7e..3d0425f 100644 --- a/docs/physmem.md +++ b/docs/physmem.md @@ -25,6 +25,37 @@ one single top-level buddy tree. Instead, `physmem_init()` decomposes the arena into the largest aligned power-of-two blocks that fit and seeds one free list per order with that forest of top-level blocks. +### Page Metadata + +Metadata for all physical frames is stored as a flat array in `physmem_map`. A frame's metadata is stored in a `struct Page`, retrievable via `get_page(frame_address)`. This metadata tracks the frame's current +use across multiple subsystems: + +- `flags`: State bits for the page, drawn from `enum PageFlags` +- `ref_cnt`: Reference count equal to the length of the `refs` list; indicates how + many virtual mappings currently point to this frame +- `refs`: Head of a linked list of `struct PageRef` entries, each representing one + virtual mapping of this page (see **Reverse Mapping** in vmem.md) +- `cache_entry`: Pointer to the page-cache entry if this frame is file-backed (see + **Data Stored Per Entry** in page_cache.md); `NULL` for anonymous pages +- `lock`: Semaphore protecting this frame's metadata during modifications + +The metadata enables page reclamation. + +Currently defined `PageFlags` are: +- `PG_DIRTY`: the page has been written to and needs to be written back before eviction +- `PG_PINNED`: the page is pinned in memory and cannot be evicted until unpinned + - Note that only one thread may pin a page at a time, and the pinning thread is responsible for unpinning. + - A page may not be reclaimed while it is pinned + - Currently, a page is pinned if it is part of the physical memory map, if it is free and thus being managed by the physical memory allocator, if it is backing a private mapping, or if it is being written to from disk (during `page_cache_acquire()`) + - Pinning during `page_cache_acquire()` signals that the page data is not stable yet +- `PG_ACCESSED`: software managed accessed bit; not currently supported + +#### Concurrency / Invariants +- A frame must be pinned as it is being freed; a frame will be pinned when it is allocated by the physmem allocator +- A frame must not have any remaining references when it is freed +- A frame's lock must be held during any modifications to its metadata. Flags may be examined when the lock is not held, but are not guaranteed to be stable +- A frame's lock may still have waiters when it is freed, but they must release the lock in O(1) AND they must not modify the metadata. It is the responsibility of the waiter to detect that the page has been freed and fulfill this contract + ### Supported Physmem Features #### Initialization diff --git a/docs/vmem.md b/docs/vmem.md index 43d9466..13442ea 100644 --- a/docs/vmem.md +++ b/docs/vmem.md @@ -84,6 +84,41 @@ the matching TLB value in `rA`. `vmem_core_init()` flushes the local core's TLB and clears the active PID to 0 at boot. +`tlb_shootdown()` performs cross-core TLB shootdown on a (pid, virtual address) pair, blocking until all cores have performed the shootdown. + +### Reverse Mapping + +The kernel maintains a reverse mapping from physical pages to their virtual mappings +to support efficient invalidation during page eviction. This is currently only supported for file-backed pages in the page cache, but the same mechanism will be extended to anonymous pages once the kernel supports eviction for those as well. + +#### PageRef Structure + +Each virtual mapping of a physical page is represented by a `struct PageRef` entry +containing: + +- `pid`: The page-directory ID (physical address of the page directory) of the mapping +- `virtual_address`: The virtual address of the mapping +- `next`: Pointer to the next `PageRef` in the list + +#### Reverse Mapping List + +When a thread installs a PTE pointing to a physical frame, it creates a new `PageRef` +and inserts it at the head of `page->refs` (the linked list in the frame's `struct Page` +metadata). The `page->ref_cnt` field maintains a count of these entries. + +When a thread later unmaps the page (via `munmap()` or during address-space teardown), +it removes the corresponding `PageRef` from the list and decrements `page->ref_cnt`. + +#### Eviction Invalidation + +During page eviction (see **Eviction** in page_cache.md), the page-cache code walks +the `page->refs` list to find all virtual mappings and invalidates them: + +- For each `PageRef`, construct a TLB shootdown request containing the `pid` and `virtual_address` +- Submit the requests to other cores for TLB shootdown (via `tlb_shootdown()`) +- Initiate the shootdown (via `send_ipi()`) and wait for acknowledgments from all cores +- Once all cores have flushed the TLB entries, the physical page is safe to free + ### Supported VM Features #### Global Initialization @@ -234,15 +269,20 @@ Current behavior: The ISA provides TLB-miss vector at `0x82` / `0x208`, and the kernel registers it to `tlb_miss_handler()`. -For a tlb miss, it: - -- finds the containing VME in the current thread's `vme_list` -- allocates a page table if the enclosing PDE is still invalid -- allocates or acquires the required backing page depending on the VME type -- installs a PTE with the requested permissions -- writes the resolved translation into the TLB - +For a tlb miss, it finds the PTE (allocates a page table if the enclosing PDE is invalid). If the PTE is "sufficient" to handle the fault (i.e. it maps the address and has the requested permissions), the translation is written to the TLB. + +Otherwise, the `page_fault_handler()` is called: +- Finds the containing VME in the current thread's `vme_list` +- If the page is mapped but there was a permission fault: + - Panics if the VME does not permisison for the attempted operation + - Otherwise (no write permission for VME allowing writes), updates the dirty bit and adds requested permission to the PTE + - This operation revalidates the PTE once the page lock is acquired (necessary to ensure eviction did not occur); if revalidation fails, falls thrugh to the "not mapped" case of the page fault handler + - Other permission faults may exist in the future but are not currently expected/supported +- If the page is not mapped, allocates or acquires the required backing page depending on the VME type + - If the page is mapped file-backed and shared, it is mapped with read-only permissions so the dirty bit can be set on first write +- Writes the resolved translation into the TLB If no containing VME exists, the kernel panics. +The page fault handler is responsible for updating the reverse mapping of the backing page. So far this is only a concern for file-backed & shared mappings. ### Address-Space Teardown @@ -269,10 +309,15 @@ Current VM code assumes: - one address space is active on only one core at a time That last point matters because `munmap()` invalidates TLB entries only on the -current core. There is no cross-core TLB shootdown mechanism yet. +current core. // TODO cross-core TLB shootdown exists, we should use it in munmap -Shared file-backed page sharing is implemented with the global page cache, which -has its own lock and reference counts. +Shared file-backed page sharing is implemented with the global page cache. + +Current VM code assumes: +- a thread's PTEs may be modified by another thread only during address space teardown or page reclaimation +- these modifications will take the form of invalidation, fully zeroing the PTE +When accessing metadata of a frame through the PTE, the PTE must be revalidated after the page lock is acquired to ensure that eviction hs not occurred. +- Once the page lock is acquired, the PTE will be stable ### Current Limitations diff --git a/kernel/countdown_latch.c b/kernel/countdown_latch.c new file mode 100644 index 0000000..ff1606f --- /dev/null +++ b/kernel/countdown_latch.c @@ -0,0 +1,21 @@ +#include "countdown_latch.h" +#include "atomic.h" +#include "threads.h" + +void countdownlatch_init(struct CountDownLatch* latch, unsigned count) { + latch->count = count; +} + +void countdownlatch_sync(struct CountDownLatch* latch) { + while (latch->count > 0) { + yield(); + } +} + +void countdownlatch_down(struct CountDownLatch* latch) { + __atomic_fetch_add((int*)&latch->count, -1); +} + +void countdownlatch_up(struct CountDownLatch* latch) { + __atomic_fetch_add((int*)&latch->count, 1); +} diff --git a/kernel/countdown_latch.h b/kernel/countdown_latch.h new file mode 100644 index 0000000..4a27820 --- /dev/null +++ b/kernel/countdown_latch.h @@ -0,0 +1,15 @@ +struct CountDownLatch { + unsigned count; +}; + +// initialize the barrier with the given count of threads +void countdownlatch_init(struct CountDownLatch* latch, unsigned count); + +// block until the count reaches 0 +void countdownlatch_sync(struct CountDownLatch* latch); + +// Non blocking; increment or decrement the count. If the count reaches 0, all waiting threads will be woken. +void countdownlatch_down(struct CountDownLatch* latch); +void countdownlatch_up(struct CountDownLatch* latch); + +// Destroying the latch while threads are still waiting on it causes undefined behavior \ No newline at end of file diff --git a/kernel/ext.c b/kernel/ext.c index c020767..7c0de44 100644 --- a/kernel/ext.c +++ b/kernel/ext.c @@ -2414,7 +2414,7 @@ unsigned node_read_all(struct Node* node, unsigned offset, unsigned size, char* return cnt; } -unsigned node_write_all(struct Node* node, unsigned offset, unsigned size, char* src){ +unsigned node_write_all_locked(struct Node* node, unsigned offset, unsigned size, char* src){ if (size == 0) return 0; unsigned block_size = ext2_get_block_size(node->filesystem); @@ -2422,11 +2422,6 @@ unsigned node_write_all(struct Node* node, unsigned offset, unsigned size, char* unsigned end_block = (offset + size - 1) / block_size; unsigned bytes_copied = 0; - // Serialize the full write path for one inode so block growth, inode writeback, - // and data writes observe one consistent per-file state without re-entering - // inode_lock through icache_set(). - blocking_lock_acquire(&node->cached->lock); - assert(node_is_file(node) || node_is_symlink(node), "node_write_all: can only write to regular files or symlinks.\n"); // Host-built ext2 images may encode a trailing run of all-zero file blocks as @@ -2451,11 +2446,20 @@ unsigned node_write_all(struct Node* node, unsigned offset, unsigned size, char* bytes_copied += copy_size; } - blocking_lock_release(&node->cached->lock); return size; } +unsigned node_write_all(struct Node* node, unsigned offset, unsigned size, char* src){ + // Serialize the full write path for one inode so block growth, inode writeback, + // and data writes observe one consistent per-file state without re-entering + // inode_lock through icache_set(). + blocking_lock_acquire(&node->cached->lock); + size = node_write_all_locked(node, offset, size, src); + blocking_lock_release(&node->cached->lock); + return size; +} + bool node_shrink(struct Node* node, unsigned target_size){ assert(node != NULL, "node_shrink: node is NULL.\n"); assert(node_is_file(node), "node_shrink: can only shrink regular files.\n"); diff --git a/kernel/ext.h b/kernel/ext.h index 12332aa..932fc86 100644 --- a/kernel/ext.h +++ b/kernel/ext.h @@ -197,6 +197,9 @@ void node_write_block(struct Node* node, unsigned block_num, char* src, unsigned // requested write size on success. unsigned node_write_all(struct Node* node, unsigned offset, unsigned size, char* src); +// Like node_write_all but the caller must already hold the inode lock +unsigned node_write_all_locked(struct Node* node, unsigned offset, unsigned size, char* src); + // Shrinks a regular file to `target_size` bytes and writes the smaller inode // size back to disk. does not reclaim any blocks or clear truncated bytes. bool node_shrink(struct Node* node, unsigned target_size); diff --git a/kernel/kernel_entry.c b/kernel/kernel_entry.c index 9707002..b6936de 100644 --- a/kernel/kernel_entry.c +++ b/kernel/kernel_entry.c @@ -21,7 +21,7 @@ #include "audio.h" unsigned HEAP_START = 0x100000; -unsigned HEAP_SIZE = 0x700000; +unsigned HEAP_SIZE = 0x700000; extern void kernel_main(void); extern void boot_ipi_handler_(void); @@ -32,7 +32,7 @@ int start_barrier = 0; // Core 0 performs global initialization, creates the first runnable kernel_main // thread while no other core can contend for heap locks, then all cores // bootstrap their idle-thread scheduler context and enter event_loop(). -void kernel_entry(void){ +void kernel_entry(void) { int me = get_core_id(); int num_cores = CONFIG.num_cores; @@ -41,7 +41,7 @@ void kernel_entry(void){ static int awake_cores = 0; __atomic_fetch_add(&awake_cores, 1); - if (me == 0){ + if (me == 0) { register_spurious_handlers(); vga_init(); uart_init(); @@ -66,7 +66,7 @@ void kernel_entry(void){ vmem_global_init(); say("| Initializing PIT...\n", NULL); - pit_init(3000); // trigger interrupts at 3,000Hz + pit_init(3000); // trigger interrupts at 3,000Hz // when running on emulator, this will actually be a much lower frequency say("| Initializing threads...\n", NULL); @@ -120,12 +120,12 @@ void kernel_entry(void){ say("| Core %d enabling interrupts...\n", &me); interrupts_restore(DEFAULT_INTERRUPT_MASK); - + // wait for all cores to be awake and set up say("| Core %d waiting at start barrier...\n", &me); spin_barrier_sync(&start_barrier); event_loop(); - panic("event loop returned"); + panic("event loop returned"); } diff --git a/kernel/machine.h b/kernel/machine.h index 08fae45..aa62917 100644 --- a/kernel/machine.h +++ b/kernel/machine.h @@ -45,6 +45,9 @@ extern unsigned get_efg(void); // Return the TLB miss address register (cr7) value extern unsigned get_tlb_addr(void); +// Return the mailbox in register (cr10) value +extern unsigned get_mbi(void); + // Read the TLB entry for the given virtual address, returning the physical address it maps to // returns 0 if there is no TLB entry for the given virtual address extern unsigned tlb_read(void* vaddr); diff --git a/kernel/machine.s b/kernel/machine.s index cd17a0b..8eaa4b9 100644 --- a/kernel/machine.s +++ b/kernel/machine.s @@ -76,6 +76,12 @@ get_tlb_addr: mov r1, tlba ret +# Return the TLB miss flags register (cr10) value + .global get_mbi +get_mbi: + mov r1, mbi + ret + # Read the TLB entry for the virtual address in r1, and put the result in r1 .global tlb_read tlb_read: diff --git a/kernel/page_cache.c b/kernel/page_cache.c index 0d1f3b3..5fe29b0 100644 --- a/kernel/page_cache.c +++ b/kernel/page_cache.c @@ -4,25 +4,26 @@ #include "print.h" #include "debug.h" +struct PageCache page_cache; + // initialize the page cache -void page_cache_init(struct PageCache* cache, unsigned hash_map_size){ +void page_cache_init(struct PageCache* cache, unsigned hash_map_size) { cache->hash_map = leak(sizeof(struct PageCacheEntry*) * hash_map_size); cache->hash_map_size = hash_map_size; blocking_lock_init(&cache->lock); - for(unsigned i = 0; i < hash_map_size; i++){ + for (unsigned i = 0; i < hash_map_size; i++) { cache->hash_map[i] = NULL; } } -// lookup a page in the page cache by inode and page index, incrementing its reference count if found -// does not lock the cache, -static struct PageCacheEntry* page_cache_lookup(struct PageCache* cache, struct Node* node, unsigned offset){ +// lookup a page in the page cache by inode and page index +// does not lock the cache, +static struct PageCacheEntry* page_cache_lookup(struct PageCache* cache, struct Node* node, unsigned offset) { unsigned hash = ((unsigned)(node->cached) ^ offset) % cache->hash_map_size; struct PageCacheEntry* entry = cache->hash_map[hash]; // iterate linked list until we find a match - while (entry){ - if(entry->key.inode == node->cached && entry->key.offset == offset){ - entry->refcount++; + while (entry) { + if (entry->key.inode == node->cached && entry->key.offset == offset) { return entry; } entry = entry->next; @@ -32,15 +33,16 @@ static struct PageCacheEntry* page_cache_lookup(struct PageCache* cache, struct // insert a page into the page cache. should not be called if the page may already exist // in the cache. does not acquire cache lock -static struct PageCacheEntry* page_cache_insert(struct PageCache* cache, struct Node* node, - unsigned offset, unsigned file_bytes, void* page_data){ +static struct PageCacheEntry* page_cache_insert(struct PageCache* cache, struct Node* node, + unsigned offset, unsigned file_bytes, void* page_data) { unsigned hash = ((unsigned)(node->cached) ^ offset) % cache->hash_map_size; struct PageCacheEntry* new_entry = malloc(sizeof(struct PageCacheEntry)); + + // Keep the inode cache entry alive while this page-cache entry exists. + node->cached->refcount += 1; new_entry->key.inode = node->cached; new_entry->key.offset = offset; new_entry->page_data = page_data; - new_entry->refcount = 1; - new_entry->flags = 0; new_entry->file_bytes = file_bytes; new_entry->next = cache->hash_map[hash]; @@ -50,82 +52,165 @@ static struct PageCacheEntry* page_cache_insert(struct PageCache* cache, struct } // lookup a page if it is in the cache, insert into cache if not -struct PageCacheEntry* page_cache_acquire(struct PageCache* cache, struct Node* node, unsigned offset, unsigned file_bytes){ - blocking_lock_acquire(&cache->lock); - - struct PageCacheEntry* entry = page_cache_lookup(cache, node, offset); - if (entry){ - blocking_lock_release(&cache->lock); - return entry; - } +// TODO should we maybe just return the ppn or a page object? should the page object contain a PPN +struct PageCacheEntry* page_cache_acquire(struct PageCache* cache, struct Node* node, unsigned offset, unsigned file_bytes) { + while (true) { + blocking_lock_acquire(&cache->lock); + + struct PageCacheEntry* entry = page_cache_lookup(cache, node, offset); + if (entry != NULL) { + // Lock page + void* page_data = entry->page_data; + struct Page* page = get_page(page_data, "get page - cache acquire hit"); + blocking_lock_release(&cache->lock); // Can't hold the cache lock while acquiring a page lock + physmem_page_lock(page); + + // Revalidate + blocking_lock_acquire(&cache->lock); + struct PageCacheEntry* verify = page_cache_lookup(cache, node, offset); + if ((verify != NULL) && (verify->page_data == page_data) && !(page->flags & PG_PINNED)) { // Success! + // say("Released cache lock - revalidate success\n", NULL); + blocking_lock_release(&cache->lock); + assert(verify == page->cache_entry, "page cache mismatch"); + + // Update file_bytes + struct CachedInode* inode = verify->key.inode; + if (file_bytes > verify->file_bytes) { + verify->file_bytes = file_bytes; + } + blocking_lock_acquire(&inode->lock); + if (offset + file_bytes > inode->inode.size) { + inode->inode.size = offset + file_bytes; + } + blocking_lock_release(&inode->lock); - void* page_data = physmem_alloc(); // allocate a new page + return verify; + } else { // Fail (it got evicted, or it's still being paged in) + blocking_lock_release(&cache->lock); + physmem_page_unlock(page); + continue; // If revalidation failed, loop again + } + } - // load the page from disk into the newly allocated page_data - unsigned bytes_read = node_read_all(node, offset, FRAME_SIZE, page_data); + // Insert into the page cache + void* page_data = physmem_alloc(); // allocate a new page (pinned) + entry = page_cache_insert(cache, node, offset, file_bytes, page_data); + struct Page* page = get_page(page_data, "get page - cache acquire miss"); + page->cache_entry = entry; + blocking_lock_release(&cache->lock); // Release the cache lock while acquiring other locks + // We're safe from eviction because the page is pinned + physmem_page_lock(page); + + // Update file_bytes + struct CachedInode* inode = entry->key.inode; + blocking_lock_acquire(&inode->lock); + if (offset + file_bytes > inode->inode.size) { + inode->inode.size = offset + file_bytes; + } + blocking_lock_release(&inode->lock); - // zero remaining bytes - for (int i = bytes_read; i < FRAME_SIZE; i++){ - ((char*)page_data)[i] = 0; + // load the page from disk into the newly allocated page_data + unsigned bytes_read = node_read_all(node, offset, FRAME_SIZE, page_data); + // zero remaining bytes + for (int i = bytes_read; i < FRAME_SIZE; i++) { + ((char*)page_data)[i] = 0; + } + physmem_clear_page_flags(page, PG_PINNED); + return entry; } +} - entry = page_cache_insert(cache, node, offset, file_bytes, page_data); +// Looks up the page if it is in the page cache; returns NULL if not +struct PageCacheEntry* page_cache_acquire_if_present(struct PageCache* cache, struct Node* node, unsigned offset) { + while (true) { + blocking_lock_acquire(&cache->lock); - blocking_lock_release(&cache->lock); + struct PageCacheEntry* entry = page_cache_lookup(cache, node, offset); - return entry; -} + if (entry == NULL) { + blocking_lock_release(&cache->lock); + return NULL; + } -void page_cache_mark_dirty(struct PageCache* cache, struct Node* node, unsigned offset){ - unsigned hash = ((unsigned)(node->cached) ^ offset) % cache->hash_map_size; + // Lock page + void* page_data = entry->page_data; + struct Page* page = get_page(page_data, "get page - cache acquire hit"); + blocking_lock_release(&cache->lock); // Can't hold the cache lock while acquiring a page lock + physmem_page_lock(page); - blocking_lock_acquire(&cache->lock); - struct PageCacheEntry* entry = cache->hash_map[hash]; - while (entry){ - if (entry->key.inode == node->cached && entry->key.offset == offset){ - entry->flags |= PAGE_DIRTY; + // Revalidate + blocking_lock_acquire(&cache->lock); + struct PageCacheEntry* verify = page_cache_lookup(cache, node, offset); + if ((verify != NULL) && (verify->page_data == page_data) && !(page->flags & PG_PINNED)) { // Success! blocking_lock_release(&cache->lock); - return; + assert(verify == page->cache_entry, "page cache mismatch"); + return verify; + } else { // Fail (it got evicted, or it's still being paged in) + blocking_lock_release(&cache->lock); + physmem_page_unlock(page); + continue; // If revalidation failed, loop again } - entry = entry->next; } - blocking_lock_release(&cache->lock); - - panic("page_cache_mark_dirty: missing cache entry for dirty page.\n"); } -// release a page from the page cache -// decrementing its reference count and freeing it if the count reaches zero -void page_cache_release(struct PageCache* cache, struct Node* node, unsigned offset){ - unsigned hash = ((unsigned)(node->cached) ^ offset) % cache->hash_map_size; +void page_cache_remove(struct PageCache* cache, struct PageCacheEntry* entry) { + unsigned hash = ((unsigned)(entry->key.inode) ^ entry->key.offset) % cache->hash_map_size; blocking_lock_acquire(&cache->lock); - struct PageCacheEntry* entry = cache->hash_map[hash]; + struct PageCacheEntry* curr = cache->hash_map[hash]; struct PageCacheEntry* prev = NULL; - while (entry){ - if (entry->key.inode == node->cached && entry->key.offset == offset){ - if (entry->refcount > 1){ - // still live reference, just decrement refcount - entry->refcount--; + while (curr) { + if (curr == entry) { + // remove from hash map and free + if (prev) { + prev->next = entry->next; } else { - // no more references, remove from hash map and free - if (prev){ - prev->next = entry->next; - } else { - cache->hash_map[hash] = entry->next; - } - - // no need to write back clean pages - if (entry->flags & PAGE_DIRTY){ - node_write_all(node, offset, entry->file_bytes, entry->page_data); - } - - physmem_free(entry->page_data); - free(entry); + cache->hash_map[hash] = entry->next; } break; } - prev = entry; - entry = entry->next; + prev = curr; + curr = curr->next; } blocking_lock_release(&cache->lock); } + +void page_cache_destroy(struct PageCache* cache) { + blocking_lock_acquire(&cache->lock); + for (unsigned i = 0; i < cache->hash_map_size; i++) { + struct PageCacheEntry* entry = cache->hash_map[i]; + while (entry != NULL) { + // TODO check dirty bits and write back + struct PageCacheEntry* to_delete = entry; + entry = entry->next; + icache_release(&fs.icache, to_delete->key.inode); + physmem_free(to_delete->page_data); + free(to_delete); + } + } + + blocking_lock_release(&cache->lock); +} + +// NOT SYNCHRONIZED; testing purposes only +void page_cache_flush_all(struct PageCache* cache) { + blocking_lock_acquire(&cache->lock); + + for (unsigned i = 0; i < cache->hash_map_size; i++) { + struct PageCacheEntry* entry = cache->hash_map[i]; + while (entry != NULL) { + struct Page* page = get_page(entry->page_data, "get page - cache flush all"); + physmem_page_lock(page); + if (page->flags & PG_DIRTY) { + struct Node* node = malloc(sizeof(struct Node)); + node_init(node, entry->key.inode, EXT2_BAD_INO, &fs); // hopefully we don't actually need the parent inumber... + node_write_all(node, entry->key.offset, entry->file_bytes, entry->page_data); + free(node); + // say("flushed 1 entry\n", NULL); + } + physmem_page_unlock(page); + entry = entry->next; + } + } + + blocking_lock_release(&cache->lock); +} \ No newline at end of file diff --git a/kernel/page_cache.h b/kernel/page_cache.h index 2dac4ba..1ab5edd 100644 --- a/kernel/page_cache.h +++ b/kernel/page_cache.h @@ -10,15 +10,10 @@ struct PageCacheKey { unsigned offset; }; -#define PAGE_DIRTY 0x1 - // metadata for the page cache entry struct PageCacheEntry { struct PageCacheKey key; - void* page_data; - - unsigned refcount; - unsigned flags; + void* page_data; // Pointer to frame // how many bytes of the file this page actually contains unsigned file_bytes; @@ -34,20 +29,22 @@ struct PageCache { struct BlockingLock lock; }; +extern struct PageCache page_cache; + // initialize the page cache void page_cache_init(struct PageCache* cache, unsigned hash_map_size); // lookup a page if it is in the cache, insert into cache if not -struct PageCacheEntry* page_cache_acquire(struct PageCache* cache, struct Node* node, - unsigned offset, unsigned file_bytes); +struct PageCacheEntry* page_cache_acquire(struct PageCache* cache, struct Node* node, + unsigned offset, unsigned file_bytes); + +// Looks up the page if it is in the page cache; returns NULL if not +struct PageCacheEntry* page_cache_acquire_if_present(struct PageCache* cache, struct Node* node, unsigned offset); -// Conservatively mark one cached page dirty. Shared writable mappings call this -// when they expose a cache page directly to userspace because the ISA does not -// currently provide a hardware dirty bit for later writeback decisions. -void page_cache_mark_dirty(struct PageCache* cache, struct Node* node, unsigned offset); +// remove a page from the page cache +void page_cache_remove(struct PageCache* cache, struct PageCacheEntry* entry); -// release a page from the page cache -// decrementing its reference count and freeing it if the count reaches zero -void page_cache_release(struct PageCache* cache, struct Node* node, unsigned offset); +void page_cache_destroy(struct PageCache* cache); +void page_cache_flush_all(struct PageCache* cache); #endif // PAGE_CACHE_H diff --git a/kernel/per_core.h b/kernel/per_core.h index 55b968c..fc70791 100644 --- a/kernel/per_core.h +++ b/kernel/per_core.h @@ -6,6 +6,7 @@ #include "queue.h" #include "config.h" #include "physmem.h" +#include "vmem.h" // Stores all core-local data struct PerCore { @@ -25,6 +26,9 @@ struct PerCore { // allocator struct PhysmemLocalCache physmem_cache; + + // Shootdown requests + struct GenericSpinQueue shootdown_requests; // TODO replace with something more O(1) - maybe funky queues? }; extern struct PerCore per_core_data[MAX_CORES]; diff --git a/kernel/physmem.c b/kernel/physmem.c index 46598cc..59607d3 100644 --- a/kernel/physmem.c +++ b/kernel/physmem.c @@ -6,6 +6,9 @@ #include "blocking_lock.h" #include "per_core.h" #include "threads.h" +#include "heap.h" +#include "vmem.h" +#include "interrupts.h" static struct BlockingLock physmem_lock; @@ -20,6 +23,8 @@ static int order_allocs[PHYS_FRAME_MAX_ORDER_PLUS_ONE]; static int order_frees[PHYS_FRAME_MAX_ORDER_PLUS_ONE]; static int order_leaks[PHYS_FRAME_MAX_ORDER_PLUS_ONE]; +static struct Page* physmem_map; // Metadata for each physical page + // sanity check that something could be a frame address static bool physmem_is_frame_address(unsigned phys_addr) { if (phys_addr < FRAMES_ADDR_START || phys_addr >= FRAMES_ADDR_END) { @@ -28,8 +33,12 @@ static bool physmem_is_frame_address(unsigned phys_addr) { return (phys_addr & (FRAME_SIZE - 1)) == 0; } -unsigned frame_index_from_address(unsigned phys_addr) { - assert(physmem_is_frame_address(phys_addr), "physmem: invalid frame address.\n"); +unsigned frame_index_from_address(unsigned phys_addr, char* source) { + if (!physmem_is_frame_address(phys_addr)) { + int args[2] = {phys_addr, (int)source}; + say("tried to get index of frame address 0x%X (called from %s)\n", args); + panic("physmem: invalid frame address.\n"); + } return (phys_addr - FRAMES_ADDR_START) / FRAME_SIZE; } @@ -55,9 +64,9 @@ static void free_list_push(struct FreePageNode* block_addr, int order) { assert(order >= 0 && order <= PHYS_FRAME_MAX_ORDER, "physmem: invalid block order.\n"); assert(physmem_is_frame_address((unsigned)block_addr), "physmem: invalid block address.\n"); - unsigned block_index = frame_index_from_address((unsigned)block_addr); - assert((block_index & ((1u << order) - 1)) == 0, - "physmem: block address is not aligned to its size.\n"); + unsigned block_index = frame_index_from_address((unsigned)block_addr, "free list push"); + assert((block_index & ((1u << order) - 1)) == 0, + "physmem: block address is not aligned to its size.\n"); struct FreePageNode* node = block_addr; node->prev = NULL; @@ -88,7 +97,7 @@ static struct FreePageNode* free_list_pop(int order) { node->next = NULL; node->prev = NULL; - unsigned block_index = frame_index_from_address((unsigned)node); + unsigned block_index = frame_index_from_address((unsigned)node, "free list pop"); mark_block_allocated(block_index); return node; @@ -96,7 +105,7 @@ static struct FreePageNode* free_list_pop(int order) { // remove a specific block from free list for given order static struct FreePageNode* free_list_remove(struct FreePageNode* node) { - unsigned block_index = frame_index_from_address((unsigned)node); + unsigned block_index = frame_index_from_address((unsigned)node, "free list remove"); unsigned order = node->free_order; assert(order >= 0 && order <= PHYS_FRAME_MAX_ORDER, "physmem: invalid block order.\n"); @@ -121,11 +130,11 @@ static struct FreePageNode* free_list_remove(struct FreePageNode* node) { } // add all frames to free lists, coalescing into larger blocks as much as possible -void physmem_init(void){ - assert((FRAMES_ADDR_END - FRAMES_ADDR_START) / FRAME_SIZE == PHYS_FRAME_COUNT, - "physmem init: frame count does not match address range.\n"); - assert((PHYS_FRAME_COUNT + 7) / 8 == FREE_PAGE_BITMAP_SIZE, - "physmem init: free page bitmap size is incorrect.\n"); +void physmem_init(void) { + assert((FRAMES_ADDR_END - FRAMES_ADDR_START) / FRAME_SIZE == PHYS_FRAME_COUNT, + "physmem init: frame count does not match address range.\n"); + assert((PHYS_FRAME_COUNT + 7) / 8 == FREE_PAGE_BITMAP_SIZE, + "physmem init: free page bitmap size is incorrect.\n"); blocking_lock_init(&physmem_lock); for (int i = 0; i < PHYS_FRAME_MAX_ORDER_PLUS_ONE; i++) { @@ -163,11 +172,53 @@ void physmem_init(void){ per_core_data[i].physmem_cache.pages[j] = NULL; } } + + // init physmem map + unsigned map_bytes = sizeof(struct Page) * PHYS_FRAME_COUNT; + unsigned map_pages = (map_bytes + FRAME_SIZE - 1) / FRAME_SIZE; + unsigned map_order = 0; + unsigned map_page_count = 1; + while (map_page_count < map_pages) { + map_page_count <<= 1; + map_order++; + } + physmem_map = physmem_leak_order(map_order); + + // Initialize metadata for every physical frame. + // Ensure member offsets are as expected + assert((unsigned)(physmem_map + 1) - (unsigned)physmem_map == 40, "Page not sized as expected\n"); + assert((unsigned)&physmem_map->flags - (unsigned)physmem_map == 0, "flags not at offset 0\n"); + assert((unsigned)&physmem_map->ref_cnt - (unsigned)physmem_map == 4, "ref_cnt not at offset 4\n"); + assert((unsigned)&physmem_map->refs - (unsigned)physmem_map == 8, "refs not at offset 8\n"); + assert((unsigned)&physmem_map->cache_entry - (unsigned)physmem_map == 12, "cache_entry not at offset 12\n"); + assert((unsigned)&physmem_map->lock - (unsigned)physmem_map == 16, "lock not at offset 16\n"); + assert((unsigned)&physmem_map->lock.lock.the_lock - (unsigned)physmem_map == 16, "lock.lock.the_lock not at offset 16\n"); + assert((unsigned)&physmem_map->lock.lock.interrupt_state - (unsigned)physmem_map == 20, "lock.lock.interrupt_state not at offset 20\n"); + assert((unsigned)&physmem_map->lock.count - (unsigned)physmem_map == 24, "lock.count not at offset 24\n"); + assert((unsigned)&physmem_map->lock.wait_queue - (unsigned)physmem_map == 28, "lock.wait_queue not at offset 28\n"); + assert((unsigned)&physmem_map->lock.wait_queue.head - (unsigned)physmem_map == 28, "lock.wait_queue.head not at offset 28\n"); + assert((unsigned)&physmem_map->lock.wait_queue.tail - (unsigned)physmem_map == 32, "lock.wait_queue.tail not at offset 32\n"); + assert((unsigned)&physmem_map->lock.wait_queue.size - (unsigned)physmem_map == 36, "lock.wait_queue.size not at offset 36\n"); + /* Initialization logic + for (unsigned i = 0; i < PHYS_FRAME_COUNT; i++) { + // physmem_map[i].flags = PG_INIT_FLAGS; + // physmem_map[i].ref_cnt = 0; + // physmem_map[i].refs = NULL; + // physmem_map[i].cache_entry = NULL; + // sem_init(&physmem_map[i].lock, 1); + } + */ + physmem_metadata_init(physmem_map, PHYS_FRAME_COUNT, PG_PINNED); + + // init per core shootdown queues + for (int i = 0; i < MAX_CORES; i++) { + generic_spin_queue_init(&per_core_data[i].shootdown_requests); + } } // allocate a physical page of given order // Panics if no free frames remain -void* physmem_alloc_order(int order){ +void* physmem_alloc_order(int order) { assert(order >= 0 && order <= PHYS_FRAME_MAX_ORDER, "physmem alloc: invalid order.\n"); blocking_lock_acquire(&physmem_lock); @@ -191,7 +242,7 @@ void* physmem_alloc_order(int order){ while (current_order > order) { current_order--; unsigned buddy_addr = address_from_frame_index( - frame_index_from_address((unsigned)node) + (1u << current_order)); + frame_index_from_address((unsigned)node, "physmem alloc order") + (1u << current_order)); struct FreePageNode* buddy = (struct FreePageNode*)buddy_addr; free_list_push(buddy, current_order); @@ -200,38 +251,45 @@ void* physmem_alloc_order(int order){ blocking_lock_release(&physmem_lock); assert( - physmem_is_frame_address((unsigned)node), - "physmem alloc: free list returned an invalid frame address.\n" - ); + physmem_is_frame_address((unsigned)node), + "physmem alloc: free list returned an invalid frame address.\n"); return node; } -void* physmem_leak_order(int order){ +void* physmem_leak_order(int order) { void* page = physmem_alloc_order(order); __atomic_fetch_add(&order_leaks[order], 1); return page; } // free a physical page of given order -void physmem_free_order(void* page, int order){ +void physmem_free_order(void* page, int order) { + unsigned phys_addr = (unsigned)page; + + for (unsigned i = 0; i < 1 << order; i++) { + struct Page* metadata = get_page((void*)(phys_addr + (i * FRAME_SIZE)), "get page - physmem free order"); + metadata->cache_entry = NULL; + assert(metadata->refs == NULL, "freeing a page that's still referenced"); + physmem_set_page_flags(metadata, PG_PINNED); // TODO more flags? Locking? + } + assert(page != NULL, "physmem free: page is NULL.\n"); assert( - physmem_is_frame_address(phys_addr), - "physmem free: page is not a valid allocatable frame.\n" - ); + physmem_is_frame_address(phys_addr), + "physmem free: page is not a valid allocatable frame.\n"); assert(order >= 0 && order <= PHYS_FRAME_MAX_ORDER, "physmem free: invalid order.\n"); - assert((frame_index_from_address(phys_addr) & ((1u << order) - 1)) == 0, - "physmem free: page address is not aligned to its size.\n"); + assert((frame_index_from_address(phys_addr, "physmem free order") & ((1u << order) - 1)) == 0, + "physmem free: page address is not aligned to its size.\n"); blocking_lock_acquire(&physmem_lock); __atomic_fetch_add(&order_frees[order], 1); // coalesce with buddy blocks if possible - unsigned block_index = frame_index_from_address(phys_addr); + unsigned block_index = frame_index_from_address(phys_addr, "physmem free order"); while (order < PHYS_FRAME_MAX_ORDER) { unsigned buddy_index = block_index ^ (1u << order); if (buddy_index >= PHYS_FRAME_COUNT) { @@ -268,7 +326,7 @@ void physmem_free_order(void* page, int order){ } // allocate a physical page from core-local cache -void* physmem_alloc(void){ +void* physmem_alloc(void) { enum CoreAffinity prev = core_pin(); struct PerCore* per_core = get_per_core(); @@ -281,7 +339,8 @@ void* physmem_alloc(void){ if (per_core->physmem_cache.count == 0) { // refill cache for (int i = 0; i < LOCAL_CACHE_SIZE; i++) { - per_core->physmem_cache.pages[i] = physmem_alloc_order(0); + void* frame = physmem_alloc_order(0); + per_core->physmem_cache.pages[i] = frame; } per_core->physmem_cache.count = LOCAL_CACHE_SIZE; } @@ -293,17 +352,18 @@ void* physmem_alloc(void){ blocking_lock_release(&per_core->physmem_cache.lock); core_unpin(prev); - + assert(page != 0, "ALLOCATING PAGE 0?"); + assert(get_page(page, "get page - physmem alloc")->flags & PG_PINNED, "ALLOCATING A FRAME THAT'S NOT PINNED"); return page; } -void* physmem_leak(void* page){ +void* physmem_leak(void* page) { __atomic_fetch_add(&frames_leaked, 1); return physmem_alloc(); } // free a physical page -void physmem_free(void* page){ +void physmem_free(void* page) { enum CoreAffinity prev = core_pin(); struct PerCore* per_core = get_per_core(); @@ -311,9 +371,13 @@ void physmem_free(void* page){ blocking_lock_acquire(&per_core->physmem_cache.lock); __atomic_fetch_add(&frames_freed, 1); - + // push to local cache if there is room, otherwise free to global pool if (per_core->physmem_cache.count < LOCAL_CACHE_SIZE) { + struct Page* metadata = get_page(page, "get page - physmem free"); + metadata->cache_entry = NULL; + assert(metadata->refs == NULL, "freeing a page that's still referenced"); + physmem_set_page_flags(metadata, PG_PINNED); // TODO more flags? Locking? per_core->physmem_cache.pages[per_core->physmem_cache.count] = page; per_core->physmem_cache.count++; @@ -327,7 +391,7 @@ void physmem_free(void* page){ } } -void physmem_check_leaks(void){ +void physmem_check_leaks(void) { bool all_good = true; if (frames_alloced != frames_freed + frames_leaked) { @@ -344,8 +408,71 @@ void physmem_check_leaks(void){ all_good = false; } } - + if (all_good) { say("| No physmem leaks detected\n", NULL); } } + +struct Page* get_page(void* frame, char* source) { + return &physmem_map[frame_index_from_address((unsigned)frame, source)]; +} + +void physmem_set_page_flags(struct Page* page, unsigned flags) { + page->flags |= flags; +} + +void physmem_clear_page_flags(struct Page* page, unsigned flags) { + page->flags &= ~flags; +} + +void physmem_page_lock(struct Page* page) { + sem_down(&page->lock); +} + +bool physmem_page_trylock(struct Page* page) { + return sem_try_down(&page->lock); +} + +void physmem_page_unlock(struct Page* page) { + sem_up(&page->lock); +} + +void physmem_page_addRef(struct Page* page, unsigned virtual_addr) { + // Create new page ref + struct PageRef* ref = malloc(sizeof(struct PageRef)); + ref->pid = (unsigned)get_pid(); + ref->virtual_address = virtual_addr; + + // Add to linked list + ref->next = page->refs; + page->refs = ref; + + // Update ref count + page->ref_cnt++; +} + +void physmem_page_removeRef(struct Page* page, unsigned virtual_addr, unsigned pid) { + struct PageRef* prev = NULL; + struct PageRef* curr = page->refs; + + while (curr != NULL) { + if (curr->pid == pid && curr->virtual_address == virtual_addr) { + if (prev == NULL) { + page->refs = curr->next; + } else { + prev->next = curr->next; + } + curr->next = NULL; + free(curr); + assert(page->ref_cnt != 0, "removing a ref from page with refcount 0"); + page->ref_cnt--; + return; + } + + prev = curr; + curr = curr->next; + } + + panic("tried to remove a ref that doesn't exist!"); +} \ No newline at end of file diff --git a/kernel/physmem.h b/kernel/physmem.h index a83ac69..975086c 100644 --- a/kernel/physmem.h +++ b/kernel/physmem.h @@ -2,11 +2,12 @@ #define PHYSMEM_H #include "blocking_lock.h" +#include "semaphore.h" #define FRAME_SIZE 4096 #define FRAMES_ADDR_START 0x800000 -#define FRAMES_ADDR_END 0x7FB8000 +#define FRAMES_ADDR_END 0x7FB8000 #define PHYS_FRAME_COUNT 30648 @@ -26,8 +27,8 @@ struct PhysmemLocalCache { // free pages store metadata to form a linked list struct FreePageNode { - struct FreePageNode *prev; - struct FreePageNode *next; + struct FreePageNode* prev; + struct FreePageNode* next; unsigned free_order; }; @@ -35,7 +36,7 @@ struct FreePageNode { void physmem_init(void); // get the frame index corresponding to a physical address (first frame is index 0) -unsigned frame_index_from_address(unsigned phys_addr); +unsigned frame_index_from_address(unsigned phys_addr, char* source); // get the physical address corresponding to a frame index (first frame is index 0) unsigned address_from_frame_index(unsigned frame_index); @@ -44,6 +45,9 @@ unsigned address_from_frame_index(unsigned frame_index); // Panics if no free frames remain void* physmem_alloc_order(int order); +// allocate a physical page of given order and count it as intentionally leaked for leak reporting +void* physmem_leak_order(int order); + // free a physical page of given order void physmem_free_order(void* page, int order); @@ -57,4 +61,41 @@ void physmem_free(void* page); // check for physical memory leaks void physmem_check_leaks(void); +enum PageFlags { + PG_PINNED = 0x1, // Non-evictable: either is being managed by physmem, or not able to be evicted (but owned by exactly one thing) + PG_ACCESSED = 0x2, // Software-managed access bit + PG_DIRTY = 0x4, // Has been written to since last writeback +}; + +struct PageRef; +struct PageCacheEntry; +struct VME; + +struct Page { + unsigned flags; + unsigned ref_cnt; // ref_cnt == len(refs) + struct PageRef* refs; + struct PageCacheEntry* cache_entry; + struct Semaphore lock; +}; + +// static_assert(sizeof(struct Page) == 64, "Page is unexpected size; physmem assembly will be sad"); + +// Get the metadata from a frame physical address +struct Page* get_page(void* frame, char* source); + +// Set and clear flags (does not acquire lock) +void physmem_set_page_flags(struct Page* page, unsigned flags); +void physmem_clear_page_flags(struct Page* page, unsigned flags); + +// Lock and unlock page +void physmem_page_lock(struct Page* page); +bool physmem_page_trylock(struct Page* page); +void physmem_page_unlock(struct Page* page); + +// Add and remove PageRefs (defaults to current process; does not acquire lock) +void physmem_page_addRef(struct Page* page, unsigned virtual_addr); +void physmem_page_removeRef(struct Page* page, unsigned virtual_addr, unsigned pid); + +extern void physmem_metadata_init(struct Page* physmem_map, unsigned frame_count, unsigned pg_init_flags); #endif // PHYSMEM_H diff --git a/kernel/physmem.s b/kernel/physmem.s new file mode 100644 index 0000000..a3b9ac7 --- /dev/null +++ b/kernel/physmem.s @@ -0,0 +1,66 @@ + .text + .align 4 + + .global physmem_metadata_init + #extern void physmem_metadata_init(struct Page* physmem_map, unsigned frame_count + # unsigned flags); + #// Initialize metadata for every physical frame. + #for (unsigned i = 0; i < frame_count; i++) { + # physmem_map[i].flags = flags; + # physmem_map[i].ref_cnt = 0; + # sem_init(&physmem_map[i].lock, 1); + # physmem_map[i].refs = NULL; + # physmem_map[i].cache_entry = NULL; + #} + # r1 = physmem_map + # r2 = frame_count + # r3 = flags +physmem_metadata_init: + mov r4, r0 # i = 0 + add r6, r0, 1 # r6 = 1 + add r7, r0, 40 # r7 = sizeof(struct Page) = 40 + mov r5, r1 # r5 = physmem_map + +physmem_metadata_init_loop: + cmp r4, r2 # compare i with frame_count + bae physmem_metadata_init_end # if i >= frame_count, exit loop + + swa r3, [r5] # physmem_map[i].flags = flags + + # init ref_cnt + swa r0, [r5, 4] # physmem_map[i].ref_cnt = 0 + + # init refs + swa r0, [r5, 8] # physmem_map[i].refs = NULL + + # init cache_entry + swa r0, [r5, 12] # physmem_map[i].cache_entry = NULL + + # init semaphore + # struct Semaphore { + # struct SpinLock lock { + # bool the_lock; (init to 0) + # int interrupt_state; (init to 0) + # }; + # int count; (init to 1) + # struct Queue wait_queue { + # struct TCB* head; (init to NULL) + # struct TCB* tail; (init to NULL) + # int size; (init to 0) + # }; + # }; + # }; + + swa r0, [r5, 16] # physmem_map[i].lock.lock.the_lock = 0 + swa r0, [r5, 20] # physmem_map[i].lock.lock.interrupt_state = 0 + swa r6, [r5, 24] # physmem_map[i].lock.count = 1 + swa r0, [r5, 28] # physmem_map[i].lock.wait_queue.head = NULL + swa r0, [r5, 32] # physmem_map[i].lock.wait_queue.tail = NULL + swa r0, [r5, 36] # physmem_map[i].lock.wait_queue.size = 0 + + add r5, r5, r7 # move to the next Page struct + add r4, r4, 1 # i++ + jmp physmem_metadata_init_loop + +physmem_metadata_init_end: + ret \ No newline at end of file diff --git a/kernel/sd_driver.c b/kernel/sd_driver.c index c3aebcf..e55ff0a 100644 --- a/kernel/sd_driver.c +++ b/kernel/sd_driver.c @@ -215,9 +215,10 @@ static int sd_wait_done(enum SdDrive drive, int was){ int status; int err; - if (__atomic_load_n(&bootstrapping)) { + if (__atomic_load_n(&bootstrapping) || __atomic_load_n(&shutting_down)) { // During bootstrapping, we don't have threads or interrupts set up yet, // so we have to busy wait + // During shut down, we're running in an idle thread, so we can't block interrupts_restore(was); do { if (drive == SD_DRIVE_0) { diff --git a/kernel/sys.c b/kernel/sys.c index 261b6b0..d109abb 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -18,6 +18,7 @@ #include "ext.h" #include "string.h" #include "scheduler.h" +#include "page_cache.h" #define INITIAL_USER_STACK_SIZE 0x4000 #define SYSCALL_MAX_PATH_BYTES 1024 @@ -588,7 +589,7 @@ int handle_read(int fd, char* buf, unsigned count){ char* kbuf = malloc(bytes_to_read); unsigned rounded_offset = (unsigned)offset & ~(FRAME_SIZE - 1); - unsigned rounded_bytes = (bytes_to_read + ((unsigned)offset - rounded_offset) + FRAME_SIZE - 1) & ~(FRAME_SIZE - 1); + unsigned rounded_bytes = bytes_to_read + ((unsigned)offset - rounded_offset); char* mmapped_file = mmap(rounded_bytes, file_node, rounded_offset, MMAP_READ | MMAP_SHARED); memcpy(kbuf, mmapped_file + ((unsigned)offset - rounded_offset), @@ -867,10 +868,61 @@ int handle_truncate(int fd, unsigned size){ return -1; } + unsigned original_size = descriptor->file->cached->inode.size; + if (!node_shrink(descriptor->file, size)){ return -1; } + // Note that node_shrink does not deallocate or overwrite blocks on disk, but reads from the file *should* be clamped to the right size + + // Zero the new tail + unsigned rounded_tail_offset = size & ~(FRAME_SIZE - 1); // Rounded down to page boundary + unsigned bytes_in_tail = size - rounded_tail_offset; + struct PageCacheEntry* tail_entry = page_cache_acquire_if_present(&page_cache, descriptor->file, rounded_tail_offset); // Locks the page + if (tail_entry != NULL) { + struct Page* tail_page = get_page(tail_entry->page_data, "handle_truncate - tail"); + assert(!(tail_page->flags & PG_PINNED), "truncation would remove a pinned page (rounded tail)"); + page_shootdown(tail_page); // Remove all references + memset((void*)((unsigned)tail_entry->page_data + bytes_in_tail), 0, FRAME_SIZE - bytes_in_tail); + physmem_page_unlock(tail_page); // Unlock the page + // Page should now still be able to be acquired from the page cache + } + + // Shootdown truncated page cache pages + unsigned offset = rounded_tail_offset + FRAME_SIZE; + while (offset < original_size) { + struct PageCacheEntry* entry = page_cache_acquire_if_present(&page_cache, descriptor->file, offset); // Locks the page + if (entry == NULL) { + offset += FRAME_SIZE; + continue; + } + + struct Page* page = get_page(entry->page_data, "handle_truncate"); + assert(!(page->flags & PG_PINNED), "truncation would remove a pinned page\n"); + void* frame = entry->page_data; + + // Evict from page cache + page_cache_remove(&page_cache, entry); + + // Remove references + page_shootdown(page); + + // No need to write back + + // Clean up metadata + entry->key.inode->refcount--; + free(entry); + + // Free page + page->cache_entry = NULL; + physmem_set_page_flags(page, PG_PINNED); + physmem_page_unlock(page); + physmem_free(frame); + + offset += FRAME_SIZE; + } + return 0; } diff --git a/kernel/threads.c b/kernel/threads.c index 27745f7..8ea5d4e 100644 --- a/kernel/threads.c +++ b/kernel/threads.c @@ -26,6 +26,7 @@ #include "ps2.h" #include "scheduler.h" #include "vmem.h" +#include "page_cache.h" #include "sys.h" #include "promise.h" @@ -35,16 +36,17 @@ struct SpinQueue reaper_queue; int n_active = 0; int n_active_others = 0; // number of running threads not counted in n_active bool bootstrapping = true; +bool shutting_down = false; int shutdown_barrier = 0; -unsigned DEFAULT_INTERRUPT_MASK = - GLOBAL_INT_ENABLE | - SD_0_INT_ENABLE | SD_1_INT_ENABLE | - PIT_INT_ENABLE | - PS2_INT_ENABLE | - IPI_INT_ENABLE | - AUDIO_INT_ENABLE; +unsigned DEFAULT_INTERRUPT_MASK = + GLOBAL_INT_ENABLE | + SD_0_INT_ENABLE | SD_1_INT_ENABLE | + PIT_INT_ENABLE | + PS2_INT_ENABLE | + IPI_INT_ENABLE | + AUDIO_INT_ENABLE; static void free_fun(struct Fun* fun) { if (fun->arg != NULL) { @@ -56,10 +58,10 @@ static void free_fun(struct Fun* fun) { static void free_tcb(struct TCB* tcb) { assert(tcb != NULL, "trying to free resources of a NULL TCB.\n"); assert(tcb->stack != NULL, "TCB stack is already NULL.\n"); - + free(tcb->stack); free_fun(tcb->thread_fun); - + vmem_destroy_address_space(tcb); free_vme_list(tcb->vme_list); @@ -88,10 +90,10 @@ static void free_tcb(struct TCB* tcb) { } // reaper thread that runs forever and frees resources of threads that have been stopped -static void reaper(void){ - while (true){ +static void reaper(void) { + while (true) { struct TCB* tcb = spin_queue_remove_all(&reaper_queue); - while (tcb != NULL){ + while (tcb != NULL) { struct TCB* prev = tcb; tcb = tcb->next; free_tcb(prev); @@ -105,7 +107,7 @@ static void reaper(void){ // defaults to: preemption enabled, not pinned, normal priority // If init_stdio is false, leave the descriptor tables empty so kernel-only // daemon threads do not allocate stdio descriptors they can never consume. -static struct TCB* make_tcb(bool is_daemon){ +static struct TCB* make_tcb(bool is_daemon) { struct TCB* tcb = is_daemon ? leak(sizeof(struct TCB)) : malloc(sizeof(struct TCB)); tcb->flags = 0; @@ -149,15 +151,14 @@ static struct TCB* make_tcb(bool is_daemon){ } // create a thread to run the given function, and add it to the global ready queue -void thread(struct Fun* thread_fun){ +void thread(struct Fun* thread_fun) { thread_(thread_fun, NORMAL_PRIORITY, ANY_CORE); } - // create a thread to run the given function, and add it to the global ready queue // allows specifying the thread's priority and the core affinity -void thread_(struct Fun* thread_fun, - enum ThreadPriority priority, enum CoreAffinity core_affinity){ +void thread_(struct Fun* thread_fun, + enum ThreadPriority priority, enum CoreAffinity core_affinity) { struct TCB* tcb = make_tcb(false); __atomic_fetch_add(&n_active, 1); __atomic_store_n(&bootstrapping, false); @@ -170,9 +171,9 @@ void thread_(struct Fun* thread_fun, tcb->stack = the_stack; tcb->psr = 1; // kernel mode - tcb->ksp = (unsigned)(&the_stack[TCB_STACK_SIZE / sizeof (unsigned) - 1]); - tcb->bp = (unsigned)(&the_stack[TCB_STACK_SIZE / sizeof (unsigned) - 1]); - + tcb->ksp = (unsigned)(&the_stack[TCB_STACK_SIZE / sizeof(unsigned) - 1]); + tcb->bp = (unsigned)(&the_stack[TCB_STACK_SIZE / sizeof(unsigned) - 1]); + tcb->priority = priority; tcb->core_affinity = core_affinity; tcb->mlfq_level = LEVEL_ZERO; @@ -186,7 +187,7 @@ void thread_(struct Fun* thread_fun, // used to make stuff like reaper threads that won't count as active threads // and leave the system in the bootstrapping phase // leaks mem because it assumes these threads run forever -void setup_thread(struct Fun* thread_fun, enum ThreadPriority priority, enum CoreAffinity core_affinity){ +void setup_thread(struct Fun* thread_fun, enum ThreadPriority priority, enum CoreAffinity core_affinity) { struct TCB* tcb = make_tcb(true); __atomic_fetch_add(&n_active_others, 1); @@ -199,8 +200,8 @@ void setup_thread(struct Fun* thread_fun, enum ThreadPriority priority, enum Cor tcb->stack = the_stack; tcb->psr = 1; // kernel mode - tcb->ksp = (unsigned)(&the_stack[TCB_STACK_SIZE / sizeof (unsigned) - 1]); - tcb->bp = (unsigned)(&the_stack[TCB_STACK_SIZE / sizeof (unsigned) - 1]); + tcb->ksp = (unsigned)(&the_stack[TCB_STACK_SIZE / sizeof(unsigned) - 1]); + tcb->bp = (unsigned)(&the_stack[TCB_STACK_SIZE / sizeof(unsigned) - 1]); tcb->priority = priority; tcb->core_affinity = core_affinity; tcb->mlfq_level = LEVEL_ZERO; @@ -210,13 +211,13 @@ void setup_thread(struct Fun* thread_fun, enum ThreadPriority priority, enum Cor } // initialize thread structures; should only be called once on one core -void threads_init(void){ +void threads_init(void) { scheduler_init(); shutdown_barrier = CONFIG.num_cores; - struct Fun* reaper_fun = leak(sizeof (struct Fun)); - reaper_fun->func = (void (*)(void *))reaper; + struct Fun* reaper_fun = leak(sizeof(struct Fun)); + reaper_fun->func = (void (*)(void*))reaper; reaper_fun->arg = NULL; setup_thread(reaper_fun, LOW_PRIORITY, ANY_CORE); @@ -229,7 +230,7 @@ void threads_init(void){ // If false, they are enabled after the callback returns // Assumes callback doesn't modify the 'next' TCB // Preconditions: interrupts are disabled; current thread is core->current_thread -void block(unsigned was, void (*func)(void *), void *arg, bool run_with_interrupts) { +void block(unsigned was, void (*func)(void*), void* arg, bool run_with_interrupts) { struct PerCore* core = get_per_core(); struct TCB* me = core->current_thread; struct TCB* idle = &core->idle_thread; @@ -251,11 +252,10 @@ void thread_entry(void) { // Catch corrupted thread trampoline state before an indirect branch can jump to 0x0. if (thread_fun->func == NULL) { int args[4] = { - get_core_id(), - (int)current_tcb, - (int)thread_fun, - (int)thread_fun->arg - }; + get_core_id(), + (int)current_tcb, + (int)thread_fun, + (int)thread_fun->arg}; say("| thread_entry null func core=%d tcb=0x%X fun=0x%X arg=0x%X\n", args); panic("thread_entry: thread_fun->func is NULL.\n"); } @@ -274,33 +274,36 @@ void thread_entry(void) { static void nothing(void* unused) {} // cleanup and shutdown the system -void kernel_shutdown(void){ +void kernel_shutdown(void) { interrupts_disable(); // move from interrupt-based keyboard handling to polling // wait for other cores to finish what they are doing spin_barrier_sync(&shutdown_barrier); - // Core 0 will print results and shut down the system, + // Core 0 will print results and shut down the system, // other cores will wait for this to happen if (get_core_id() == 0) { + __atomic_store_n(&shutting_down, true); // all cores are now in shutdown, so heap operations should not block struct GenericQueueElement* keys = blocking_queue_remove_all(&ps2_queue); - while (keys != NULL){ + while (keys != NULL) { // free existing keyboard events struct GenericQueueElement* next = keys->next; free(keys); keys = next; } + page_cache_destroy(&page_cache); + ext2_destroy(&fs); say("| Finished in %d jiffies\n", (int*)¤t_jiffies); - + check_leaks(); physmem_check_leaks(); - if (CONFIG.use_vga){ + if (CONFIG.use_vga) { say("| Press Q to exit...\n", NULL); // Wait for a full 'q' key cycle (make then break). @@ -317,21 +320,24 @@ void kernel_shutdown(void){ continue; } - if (saw_q_make && (key == 'q' || key == 'Q')) break; + if (saw_q_make && (key == 'q' || key == 'Q')) + break; saw_q_make = 0; } } else { say("| Halting...\n", NULL); } - while (true) shutdown(); + while (true) + shutdown(); } else { - while (true) pause(); + while (true) + pause(); } } // idle thread loop -// calls to block() context switch to here, +// calls to block() context switch to here, // where we decide which thread to run next and switch to it void event_loop(void) { /* only the idle thread can enter this function */ @@ -381,10 +387,10 @@ void event_loop(void) { } // set up thread context for the first thread on this core (which is now the idle thread) -void bootstrap(void){ +void bootstrap(void) { int imr = get_imr(); - assert((imr & GLOBAL_INT_ENABLE) == 0, - "interrupts should be disabled when bootstrapping thread context.\n"); + assert((imr & GLOBAL_INT_ENABLE) == 0, + "interrupts should be disabled when bootstrapping thread context.\n"); int me = get_core_id(); struct PerCore* core = get_per_core(); @@ -401,7 +407,7 @@ void bootstrap(void){ tcb->r26 = 0; tcb->r27 = 0; tcb->r28 = 0; - + tcb->next = NULL; tcb->can_preempt = false; tcb->core_affinity = me; @@ -424,7 +430,7 @@ void bootstrap(void){ } // voluntarily yield the CPU and re-queue the current thread -void yield(void){ +void yield(void) { unsigned was = interrupts_disable(); struct TCB* tcb = get_current_tcb(); scheduler_charge_yield(tcb); @@ -432,12 +438,12 @@ void yield(void){ } // add a thread to the reaper queue to have its resources freed by the reaper thread -void reap_tcb(void* tcb){ +void reap_tcb(void* tcb) { spin_queue_add(&reaper_queue, (struct TCB*)tcb); } // block the current thread until a target jiffy count is reached -void sleep(unsigned jiffies){ +void sleep(unsigned jiffies) { unsigned was = interrupts_disable(); struct TCB* tcb = get_current_tcb(); struct PerCore* core = get_per_core(); @@ -447,7 +453,7 @@ void sleep(unsigned jiffies){ block(was, sleep_queue_add, (void*)args, true); } -// terminate the current thread and +// terminate the current thread and // place it on the reaper queue to eventually free its resources void stop(unsigned rc) { unsigned was = interrupts_disable(); @@ -480,7 +486,7 @@ void stop(unsigned rc) { } // disable preemption and return whether it was previously enabled or not -bool preemption_disable(void){ +bool preemption_disable(void) { int was = interrupts_disable(); struct TCB* tcb = get_current_tcb(); @@ -497,7 +503,7 @@ bool preemption_disable(void){ } // restore preemption to the given value -void preemption_restore(bool was){ +void preemption_restore(bool was) { int intrs = interrupts_disable(); struct TCB* tcb = get_current_tcb(); @@ -510,7 +516,7 @@ void preemption_restore(bool was){ } // pin a thread to the current core, preventing it from being scheduled on other cores -enum CoreAffinity core_pin(void){ +enum CoreAffinity core_pin(void) { int was = interrupts_disable(); unsigned me = get_core_id(); struct TCB* tcb = get_current_tcb(); @@ -529,7 +535,7 @@ enum CoreAffinity core_pin(void){ } // allow a thread to be scheduled on any core -void core_unpin(enum CoreAffinity prev){ +void core_unpin(enum CoreAffinity prev) { int was = interrupts_disable(); unsigned me = get_core_id(); struct TCB* tcb = get_current_tcb(); diff --git a/kernel/threads.h b/kernel/threads.h index e9f07c6..5caefa6 100644 --- a/kernel/threads.h +++ b/kernel/threads.h @@ -34,6 +34,9 @@ extern unsigned DEFAULT_INTERRUPT_MASK; // after which we consider the system to be done with bootstrapping and fully operational extern bool bootstrapping; +// false until core 0 starts destroying things in kernel_shutdown +extern bool shutting_down; + // initialize thread structures; should only be called once on one core void threads_init(void); diff --git a/kernel/vmem.c b/kernel/vmem.c index 1d32525..7e9b9b2 100644 --- a/kernel/vmem.c +++ b/kernel/vmem.c @@ -11,22 +11,20 @@ #include "string.h" #include "ivt.h" -struct PageCache page_cache; - -static unsigned vmem_range_start(unsigned flags){ +static unsigned vmem_range_start(unsigned flags) { return (flags & MMAP_USER) ? USER_VMEM_START : KERNEL_VMEM_START; } -static unsigned vmem_range_end(unsigned flags){ +static unsigned vmem_range_end(unsigned flags) { return (flags & MMAP_USER) ? USER_VMEM_END : KERNEL_VMEM_END; } // VMEs store an exclusive 32-bit end address. For the user half, the logical // exclusive end would be 0x100000000, which is not representable, so the // highest page-aligned exclusive end we can encode today is 0xFFFFF000. -static unsigned vmem_range_topdown_limit(unsigned flags){ +static unsigned vmem_range_topdown_limit(unsigned flags) { unsigned range_end = vmem_range_end(flags); - if (range_end == UINT_MAX){ + if (range_end == UINT_MAX) { return UINT_MAX - (FRAME_SIZE - 1); } return range_end + 1; @@ -35,75 +33,79 @@ static unsigned vmem_range_topdown_limit(unsigned flags){ // VMEs use an exclusive end address, so the selected range must both contain // the requested bytes and allow `start + rounded_size` to remain representable. static bool vmem_range_can_hold(unsigned start, unsigned rounded_size, - unsigned range_start, unsigned range_end){ - if (start < range_start || start > range_end){ + unsigned range_start, unsigned range_end) { + if (start < range_start || start > range_end) { return false; } - if (start > UINT_MAX - rounded_size){ + if (start > UINT_MAX - rounded_size) { return false; } return (start + rounded_size - 1) <= range_end; } -void vmem_global_init(void){ +void vmem_global_init(void) { register_handler(tlb_miss_handler_, (void*)TLB_MISS_IVT_ENTRY); page_cache_init(&page_cache, 4096); } -void vmem_core_init(void){ +void vmem_core_init(void) { tlb_flush(); set_pid(0); } -void tlb_invalidate_range(unsigned start, unsigned end){ +void tlb_invalidate_range(unsigned start, unsigned end) { // invalidate entries on the current core's TLB - for (unsigned va = start; va < end; va += FRAME_SIZE){ + for (unsigned va = start; va < end; va += FRAME_SIZE) { tlb_invalidate((void*)va); } } // allocate a new page directory for a thread -unsigned create_page_directory(void){ +unsigned create_page_directory(void) { unsigned* pd = (unsigned*)physmem_alloc(); - for (int i = 0; i < 1024; i++){ + for (int i = 0; i < 1024; i++) { pd[i] = 0; // mark all entries invalid } return (unsigned)pd; } -unsigned create_page_table(void){ +unsigned create_page_table(void) { unsigned* pt = (unsigned*)physmem_alloc(); - for (int i = 0; i < 1024; i++){ + for (int i = 0; i < 1024; i++) { pt[i] = 0; // mark all entries invalid } return (unsigned)pt; } -unsigned create_zeroed_page(void){ +unsigned create_zeroed_page(void) { // TODO: does this need to lock? unsigned* page = (unsigned*)physmem_alloc(); - for (int i = 0; i < FRAME_SIZE / sizeof(unsigned); i++){ + for (int i = 0; i < FRAME_SIZE / sizeof(unsigned); i++) { page[i] = 0; } return (unsigned)page; } +void* pte_phys_addr(unsigned pte) { + return (void*)(pte & ~(FRAME_SIZE - 1)); +} + struct VME* vme_create(unsigned start, unsigned end, unsigned size, - struct Node* file, unsigned file_offset, unsigned flags, unsigned paddr){ + struct Node* file, unsigned file_offset, unsigned flags, unsigned paddr) { struct VME* vme = (struct VME*)malloc(sizeof(struct VME)); assert(start % FRAME_SIZE == 0, "vme create: start address must be page aligned.\n"); assert(end % FRAME_SIZE == 0, "vme create: end address must be page aligned.\n"); assert(end > start, "vme create: end address must be greater than start address.\n"); assert(file == NULL || (file_offset % FRAME_SIZE) == 0, - "vme create: file-backed mmap offset must be page aligned.\n"); + "vme create: file-backed mmap offset must be page aligned.\n"); - if (paddr != 0){ - assert(file == NULL, "vme create: cannot specify both a physical address and a backing file.\n"); + if (paddr != 0) { + assert(file == NULL, "vme create: cannot specify both a physical address and a backing file.\n"); } - + vme->start = start; vme->end = end; vme->flags = flags; @@ -111,13 +113,13 @@ struct VME* vme_create(unsigned start, unsigned end, unsigned size, vme->file_offset = file_offset; vme->size = size; vme->paddr = paddr; - + return vme; } // Insert one VME into a thread's sorted, non-overlapping VME list -void vme_insert(struct TCB* tcb, struct VME* prev, struct VME* vme){ - if (prev){ +void vme_insert(struct TCB* tcb, struct VME* prev, struct VME* vme) { + if (prev) { vme->next = prev->next; prev->next = vme; } else { @@ -127,10 +129,10 @@ void vme_insert(struct TCB* tcb, struct VME* prev, struct VME* vme){ } // free all VMEs in the given list -void free_vme_list(struct VME* vme){ - while (vme){ +void free_vme_list(struct VME* vme) { + while (vme) { struct VME* next = vme->next; - if (vme->file != NULL){ + if (vme->file != NULL) { node_free(vme->file); } free(vme); @@ -139,7 +141,7 @@ void free_vme_list(struct VME* vme){ } // unmap all physical pages backing this VME and invalidate PTE entries -void unmap_vme(unsigned* pd, struct VME* vme){ +void unmap_vme(unsigned* pd, struct VME* vme) { // free any physical pages backing this VME and invalidate PTE entries unsigned prev_page_dir_index = UINT_MAX; unsigned* prev_pt = NULL; @@ -148,38 +150,45 @@ void unmap_vme(unsigned* pd, struct VME* vme){ unsigned page_table_index = (va >> 12) & 0x3FF; unsigned pde = pd[page_dir_index]; - if (!(pde & VMEM_VALID)) continue; + if (!(pde & VMEM_VALID)) + continue; unsigned* pt = (unsigned*)(pde & ~(FRAME_SIZE - 1)); unsigned pte = pt[page_table_index]; - if (!(pte & VMEM_VALID)) continue; - - if (vme->flags & MMAP_SHARED){ + if (!(pte & VMEM_VALID)) + continue; + + if (vme->flags & MMAP_SHARED) { assert(vme->file != NULL, "cannot yet handle shared anonymous pages\n"); - // shared mapping, release from page cache - page_cache_release(&page_cache, vme->file, (vme->file_offset + (va - vme->start))); - } else if (vme->paddr != 0){ + // shared mapping, remove the ref + struct Page* page = get_page((void*)pte_phys_addr(pte), "get page - unmap VME"); + physmem_page_lock(page); + if (pt[page_table_index] == pte) { // Revalidate - ensure page didn't get evicted between finding the page and locking it + physmem_page_removeRef(page, va, (unsigned)pd); + } + physmem_page_unlock(page); + } else if (vme->paddr != 0) { // Direct physmem mappings borrow an existing MMIO/physical window. The // unmap path must only drop the translation, not return that backing page // to the physmem allocator. } else { // private mapping, just free the physical page - physmem_free((void*)(pte & ~(FRAME_SIZE - 1))); + physmem_free((void*)pte_phys_addr(pte)); } - + pt[page_table_index] = 0; // only check if the page table is empty when we move to a new page directory entry - if (page_dir_index != prev_page_dir_index && prev_pt != NULL){ + if (page_dir_index != prev_page_dir_index && prev_pt != NULL) { // if page table is now empty, free it and invalidate the PDE bool empty = true; - for (int i = 0; i < 1024; i++){ - if (prev_pt[i] & VMEM_VALID){ + for (int i = 0; i < 1024; i++) { + if (prev_pt[i] & VMEM_VALID) { empty = false; break; } } - if (empty){ + if (empty) { physmem_free(prev_pt); pd[prev_page_dir_index] = 0; } @@ -190,15 +199,15 @@ void unmap_vme(unsigned* pd, struct VME* vme){ } // if page table is now empty, free it and invalidate the PDE - if (prev_pt != NULL){ + if (prev_pt != NULL) { bool empty = true; - for (int i = 0; i < 1024; i++){ - if (prev_pt[i] & VMEM_VALID){ + for (int i = 0; i < 1024; i++) { + if (prev_pt[i] & VMEM_VALID) { empty = false; break; } } - if (empty){ + if (empty) { physmem_free(prev_pt); pd[prev_page_dir_index] = 0; } @@ -212,17 +221,17 @@ void unmap_vme(unsigned* pd, struct VME* vme){ // the mapped page cache entry so writes can extend the file through mmap(). // This helper mirrors the shared-file fault path when it decides how many bytes // of file data one cached page represents. -static unsigned shared_vme_page_bytes(struct VME* vme, unsigned va){ +static unsigned shared_vme_page_bytes(struct VME* vme, unsigned va) { assert(vme != NULL, "shared_vme_page_bytes: VME must not be NULL.\n"); assert(vme->file != NULL, - "shared_vme_page_bytes: shared VME must be file-backed.\n"); + "shared_vme_page_bytes: shared VME must be file-backed.\n"); assert(va >= vme->start && va < vme->end, - "shared_vme_page_bytes: virtual address must fall inside the VME.\n"); + "shared_vme_page_bytes: virtual address must fall inside the VME.\n"); unsigned vme_offset = va - vme->start; - if (vme->size > vme_offset){ + if (vme->size > vme_offset) { unsigned bytes_remaining = vme->size - vme_offset; - if (bytes_remaining < FRAME_SIZE){ + if (bytes_remaining < FRAME_SIZE) { return bytes_remaining; } } @@ -231,12 +240,12 @@ static unsigned shared_vme_page_bytes(struct VME* vme, unsigned va){ } // copy a thread's page dir/page tables and vme_list from src to dst -void vmem_fork(struct TCB* src, struct TCB* dst){ +void vmem_fork(struct TCB* src, struct TCB* dst) { dst->vme_list = NULL; // copy vme list to dst tcb struct VME* prev_vme = NULL; - for (struct VME* vme = src->vme_list; vme != NULL; vme = vme->next){ + for (struct VME* vme = src->vme_list; vme != NULL; vme = vme->next) { struct VME* new_vme = vme_create(vme->start, vme->end, vme->size, vme->file, vme->file_offset, vme->flags, vme->paddr); @@ -249,22 +258,25 @@ void vmem_fork(struct TCB* src, struct TCB* dst){ unsigned* src_pd = (unsigned*)src->pid; unsigned* dst_pd = (unsigned*)dst->pid; - for (struct VME* vme = dst->vme_list; vme != NULL; vme = vme->next){ - if (! (vme->flags & MMAP_USER)) continue; + for (struct VME* vme = dst->vme_list; vme != NULL; vme = vme->next) { + if (!(vme->flags & MMAP_USER)) + continue; - for (unsigned va = vme->start; va < vme->end; va += FRAME_SIZE){ + for (unsigned va = vme->start; va < vme->end; va += FRAME_SIZE) { unsigned page_dir_index = (va >> 22) & 0x3FF; unsigned page_table_index = (va >> 12) & 0x3FF; unsigned pde = src_pd[page_dir_index]; - if (!(pde & VMEM_VALID)) continue; + if (!(pde & VMEM_VALID)) + continue; unsigned* src_pt = (unsigned*)(pde & ~(FRAME_SIZE - 1)); unsigned pte = src_pt[page_table_index]; - if (!(pte & VMEM_VALID)) continue; + if (!(pte & VMEM_VALID)) + continue; unsigned* dst_pt; - if (dst_pd[page_dir_index] & VMEM_VALID){ + if (dst_pd[page_dir_index] & VMEM_VALID) { dst_pt = (unsigned*)(dst_pd[page_dir_index] & ~(FRAME_SIZE - 1)); } else { dst_pt = (unsigned*)create_page_table(); @@ -272,13 +284,13 @@ void vmem_fork(struct TCB* src, struct TCB* dst){ } unsigned paddr = pte & ~(FRAME_SIZE - 1); - if (vme->flags & MMAP_SHARED){ + if (vme->flags & MMAP_SHARED) { unsigned page_offset = vme->file_offset + (va - vme->start); unsigned page_bytes = shared_vme_page_bytes(vme, va); struct PageCacheEntry* page = page_cache_acquire(&page_cache, - vme->file, page_offset, page_bytes); + vme->file, page_offset, page_bytes); assert((unsigned)page->page_data == paddr, - "vmem_fork: shared source PTE must point at the page cache page.\n"); + "vmem_fork: shared source PTE must point at the page cache page.\n"); dst_pt[page_table_index] = pte; } else { unsigned* dst_page = physmem_alloc(); @@ -289,7 +301,7 @@ void vmem_fork(struct TCB* src, struct TCB* dst){ } } -// free all physical pages mapped by the given address space, +// free all physical pages mapped by the given address space, // and free the page directory and page tables void vmem_destroy_address_space(struct TCB* tcb) { unsigned* pd = (unsigned*)tcb->pid; @@ -301,7 +313,7 @@ void vmem_destroy_address_space(struct TCB* tcb) { // free any page tables and invalidate PDE entries for (unsigned page_dir_index = 0; page_dir_index < 1024; page_dir_index++) { if (pd[page_dir_index] & VMEM_VALID) { - physmem_free((void*)(pd[page_dir_index] & ~(FRAME_SIZE - 1))); + physmem_free((void*)pte_phys_addr(pd[page_dir_index])); pd[page_dir_index] = 0; } } @@ -311,8 +323,9 @@ void vmem_destroy_address_space(struct TCB* tcb) { } // Make a VME with the given parameters and add it to the current thread's list of VMEs -void* mmap(unsigned size, struct Node* file, unsigned file_offset, unsigned flags){ - if (size == 0) return NULL; +void* mmap(unsigned size, struct Node* file, unsigned file_offset, unsigned flags) { + if (size == 0) + return NULL; // round up size to the nearest page boundary unsigned rounded_size = (size + FRAME_SIZE - 1) & ~(FRAME_SIZE - 1); @@ -323,25 +336,25 @@ void* mmap(unsigned size, struct Node* file, unsigned file_offset, unsigned flag struct TCB* tcb = get_current_tcb(); interrupts_restore(was); - // Skip any mappings that end before the selected kernel/user half, + // Skip any mappings that end before the selected kernel/user half, // then do first-fit within that half struct VME* prev = NULL; struct VME* curr = tcb->vme_list; - while (curr && curr->end <= range_start){ + while (curr && curr->end <= range_start) { prev = curr; curr = curr->next; } unsigned last_end = range_start; - while (curr){ + while (curr) { assert(curr->start >= last_end, - "mmap: VME list must stay sorted, non-overlapping, and stay within one address-space half.\n"); + "mmap: VME list must stay sorted, non-overlapping, and stay within one address-space half.\n"); - if (curr->start > range_end){ + if (curr->start > range_end) { break; } - if (curr->start - last_end >= rounded_size){ + if (curr->start - last_end >= rounded_size) { break; } @@ -350,8 +363,8 @@ void* mmap(unsigned size, struct Node* file, unsigned file_offset, unsigned flag curr = curr->next; } - if (!vmem_range_can_hold(last_end, rounded_size, range_start, range_end)){ - if (flags & MMAP_USER){ + if (!vmem_range_can_hold(last_end, rounded_size, range_start, range_end)) { + if (flags & MMAP_USER) { panic("mmap: requested mapping would exceed user virtual memory range!\n"); } else { panic("mmap: requested mapping would exceed kernel virtual memory range!\n"); @@ -372,15 +385,16 @@ void* mmap(unsigned size, struct Node* file, unsigned file_offset, unsigned flag // Reserve an anonymous user stack using a top-down first-fit search inside the // user half. The returned pointer is the stack base; callers still compute the // initial SP from the top word in the reserved range. -void* mmap_stack(unsigned size, unsigned flags){ - if (size == 0) return NULL; +void* mmap_stack(unsigned size, unsigned flags) { + if (size == 0) + return NULL; - if (!(flags & MMAP_USER)){ + if (!(flags & MMAP_USER)) { panic("mmap_stack: user stacks must be allocated in the user virtual memory range!\n"); return NULL; } - if (flags & MMAP_SHARED){ + if (flags & MMAP_SHARED) { panic("mmap_stack: shared anonymous stacks are not supported.\n"); return NULL; } @@ -398,7 +412,7 @@ void* mmap_stack(unsigned size, unsigned flags){ // the stack. The list stays globally sorted; we only consider the user half. struct VME* prev = NULL; struct VME* curr = tcb->vme_list; - while (curr && curr->end <= range_start){ + while (curr && curr->end <= range_start) { prev = curr; curr = curr->next; } @@ -408,22 +422,22 @@ void* mmap_stack(unsigned size, unsigned flags){ struct VME* stack_prev = NULL; bool found = false; - while (curr){ + while (curr) { assert(curr->start >= gap_start, - "mmap_stack: VME list must stay sorted, non-overlapping, and stay within one address-space half.\n"); + "mmap_stack: VME list must stay sorted, non-overlapping, and stay within one address-space half.\n"); unsigned gap_end = curr->start; - if (gap_end > range_limit){ + if (gap_end > range_limit) { gap_end = range_limit; } - if (gap_end >= gap_start && (gap_end - gap_start) >= rounded_size){ + if (gap_end >= gap_start && (gap_end - gap_start) >= rounded_size) { stack_start = gap_end - rounded_size; stack_prev = prev; found = true; } - if (curr->start >= range_limit){ + if (curr->start >= range_limit) { break; } @@ -432,13 +446,13 @@ void* mmap_stack(unsigned size, unsigned flags){ curr = curr->next; } - if (gap_start <= range_limit && (range_limit - gap_start) >= rounded_size){ + if (gap_start <= range_limit && (range_limit - gap_start) >= rounded_size) { stack_start = range_limit - rounded_size; stack_prev = prev; found = true; } - if (!found || !vmem_range_can_hold(stack_start, rounded_size, range_start, range_end)){ + if (!found || !vmem_range_can_hold(stack_start, rounded_size, range_start, range_end)) { panic("mmap_stack: requested stack would exceed the representable user virtual memory range!\n"); return NULL; } @@ -451,16 +465,17 @@ void* mmap_stack(unsigned size, unsigned flags){ } // Make a VME with the given parameters and add it to the current thread's list of VMEs -struct VME* mmap_at(unsigned size, struct Node* file, unsigned file_offset, unsigned flags, unsigned vaddr){ - if (size == 0) return NULL; +struct VME* mmap_at(unsigned size, struct Node* file, unsigned file_offset, unsigned flags, unsigned vaddr) { + if (size == 0) + return NULL; // round up size to the nearest page boundary unsigned rounded_size = (size + FRAME_SIZE - 1) & ~(FRAME_SIZE - 1); unsigned range_start = vmem_range_start(flags); unsigned range_end = vmem_range_end(flags); - if (!vmem_range_can_hold(vaddr, rounded_size, range_start, range_end)){ - if (flags & MMAP_USER){ + if (!vmem_range_can_hold(vaddr, rounded_size, range_start, range_end)) { + if (flags & MMAP_USER) { panic("mmap_at: requested mapping falls outside the user virtual memory range!\n"); } else { panic("mmap_at: requested mapping falls outside the kernel virtual memory range!\n"); @@ -479,12 +494,12 @@ struct VME* mmap_at(unsigned size, struct Node* file, unsigned file_offset, unsi // If that VME begins before `end`, the fixed-address mapping overlaps it. struct VME* prev = NULL; struct VME* curr = tcb->vme_list; - while (curr && curr->end <= start){ + while (curr && curr->end <= start) { prev = curr; curr = curr->next; } - if (curr != NULL && curr->start < end){ + if (curr != NULL && curr->start < end) { panic("mmap_at: requested mapping overlaps an existing VME.\n"); return NULL; } @@ -497,8 +512,9 @@ struct VME* mmap_at(unsigned size, struct Node* file, unsigned file_offset, unsi } // Make a VME with the given parameters and add it to the current thread's list of VMEs -void* mmap_physmem(unsigned size, unsigned paddr, unsigned flags){ - if (size == 0) return NULL; +void* mmap_physmem(unsigned size, unsigned paddr, unsigned flags) { + if (size == 0) + return NULL; // round up size to the nearest page boundary unsigned rounded_size = (size + FRAME_SIZE - 1) & ~(FRAME_SIZE - 1); @@ -509,25 +525,25 @@ void* mmap_physmem(unsigned size, unsigned paddr, unsigned flags){ struct TCB* tcb = get_current_tcb(); interrupts_restore(was); - // Skip any mappings that end before the selected kernel/user half, + // Skip any mappings that end before the selected kernel/user half, // then do first-fit within that half struct VME* prev = NULL; struct VME* curr = tcb->vme_list; - while (curr && curr->end <= range_start){ + while (curr && curr->end <= range_start) { prev = curr; curr = curr->next; } unsigned last_end = range_start; - while (curr){ + while (curr) { assert(curr->start >= last_end, - "mmap: VME list must stay sorted, non-overlapping, and stay within one address-space half.\n"); + "mmap: VME list must stay sorted, non-overlapping, and stay within one address-space half.\n"); - if (curr->start > range_end){ + if (curr->start > range_end) { break; } - if (curr->start - last_end >= rounded_size){ + if (curr->start - last_end >= rounded_size) { break; } @@ -536,8 +552,8 @@ void* mmap_physmem(unsigned size, unsigned paddr, unsigned flags){ curr = curr->next; } - if (!vmem_range_can_hold(last_end, rounded_size, range_start, range_end)){ - if (flags & MMAP_USER){ + if (!vmem_range_can_hold(last_end, rounded_size, range_start, range_end)) { + if (flags & MMAP_USER) { panic("mmap: requested mapping would exceed user virtual memory range!\n"); } else { panic("mmap: requested mapping would exceed kernel virtual memory range!\n"); @@ -555,7 +571,7 @@ void* mmap_physmem(unsigned size, unsigned paddr, unsigned flags){ return (void*)start; } -void munmap(void* p){ +void munmap(void* p) { int was = interrupts_disable(); struct TCB* tcb = get_current_tcb(); interrupts_restore(was); @@ -563,22 +579,22 @@ void munmap(void* p){ struct VME* prev = NULL; struct VME* curr = tcb->vme_list; - while (curr){ + while (curr) { // find VME corresponding to p - if ((void*)curr->start == p){ - if (prev){ + if ((void*)curr->start == p) { + if (prev) { prev->next = curr->next; } else { tcb->vme_list = curr->next; } - assert(!((curr->flags & MMAP_SHARED) && (curr->file == NULL)), - "munmap: cannot yet unmap shared anonymous VME\n"); + assert(!((curr->flags & MMAP_SHARED) && (curr->file == NULL)), + "munmap: cannot yet unmap shared anonymous VME\n"); // free any physical pages backing this VME unmap_vme((unsigned*)tcb->pid, curr); - if (curr->file != NULL){ + if (curr->file != NULL) { node_free(curr->file); } free(curr); @@ -591,188 +607,387 @@ void munmap(void* p){ panic("munmap called with invalid address\n"); } -void vme_change_perms(struct VME* vme, unsigned new_flags){ +unsigned* vmem_get_pte(unsigned* pd, unsigned virtual_address, bool create) { + unsigned page_dir_index = (virtual_address >> 22) & 0x3FF; + unsigned page_table_index = (virtual_address >> 12) & 0x3FF; + unsigned pde = pd[page_dir_index]; + + if (!(pde & VMEM_VALID)) { + if (!create) { + panic("vmem_get_pte: missing page table for virtual address.\n"); + return NULL; + } + + unsigned pt_addr = create_page_table(); + pd[page_dir_index] = pt_addr | VMEM_VALID | VMEM_READ | VMEM_WRITE; + pde = pd[page_dir_index]; + } + + unsigned* pt = (unsigned*)(pde & ~(FRAME_SIZE - 1)); + return &pt[page_table_index]; +} + +void vme_change_perms(struct VME* vme, unsigned new_flags) { vme->flags = new_flags; // traverse the page tables corresponding to this VME and update the permissions - for (unsigned addr = vme->start; addr < vme->end; addr += FRAME_SIZE){ + for (unsigned addr = vme->start; addr < vme->end; addr += FRAME_SIZE) { unsigned page_dir_index = (addr >> 22) & 0x3FF; unsigned page_table_index = (addr >> 12) & 0x3FF; unsigned* pd = get_pid(); unsigned pde = pd[page_dir_index]; - if (!(pde & VMEM_VALID)) continue; + if (!(pde & VMEM_VALID)) + continue; unsigned* pt = (unsigned*)(pde & ~0xFFF); unsigned pte = pt[page_table_index]; - if (!(pte & VMEM_VALID)) continue; + if (!(pte & VMEM_VALID)) + continue; pte &= ~(VMEM_READ | VMEM_WRITE | VMEM_EXEC); - if (vme->flags & MMAP_READ) pte |= VMEM_READ; - if (vme->flags & MMAP_WRITE) pte |= VMEM_WRITE; - if (vme->flags & MMAP_EXEC) pte |= VMEM_EXEC; - if (vme->flags & MMAP_USER) pte |= VMEM_USER; + if (vme->flags & MMAP_READ) + pte |= VMEM_READ; + if (vme->flags & MMAP_WRITE) + pte |= VMEM_WRITE; + if (vme->flags & MMAP_EXEC) + pte |= VMEM_EXEC; + if (vme->flags & MMAP_USER) + pte |= VMEM_USER; pt[page_table_index] = pte; } tlb_invalidate_range(vme->start, vme->end); } -int tlb_miss_handler(void* vpn, unsigned flags, unsigned* epc_ptr, bool* return_to_user){ - // look up the VME corresponding to this faulting address - int was = interrupts_disable(); - struct TCB* tcb = get_current_tcb(); - interrupts_restore(was); +// Handles killing the user / returning to the kernel / panicking on genuinely invalid memory accesses +static int segfault_helper(struct TCB* tcb, unsigned* epc_ptr, bool* return_to_user, char* user_error_msg, char* kernel_error_msg) { // ISA `cr0` is the trap/exception nesting depth after entry. A value of 1 // means this miss interrupted user mode; values above 1 mean the core was // already in kernel mode and took a nested miss while handling that context. bool was_user = get_cr0() == 1; + + if (was_user) { + // User code touched a mapped page without sufficient permissions. Abort + // back to the kernel caller of `jump_to_user(...)`. + say("| User program killed due to access of mapped page without sufficient permissions\n", NULL); + *return_to_user = false; + return -1; + } else if (tcb->uaccess_active) { + // Kernel uaccess helpers recover by redirecting the faulting instruction + // stream to their local error path, then resuming kernel mode via rfe. + assert(tcb->uaccess_err_addr != NULL, "uaccess err addr not set"); + *epc_ptr = (unsigned)tcb->uaccess_err_addr; + return 0; + } else { + panic("vmem: kernel TLB miss due to invalid privileges\n"); + } +} + +// Walks the page table & checks the PTE. +// If the PTE is sufficient to handle the miss, updates the cache +// If the PTE is not sufficient to handle the miss (no PTE, PTE doesn't have enough permissions), calls the page fault handler +int tlb_miss_handler(void* vpn, unsigned flags, unsigned* epc_ptr, bool* return_to_user) { + *return_to_user = true; // default to resuming the faulting context via rfe - if (flags != 0){ - if (was_user){ - // User code touched a mapped page without sufficient permissions. Abort - // back to the kernel caller of `jump_to_user(...)`. - say("| User program killed due to access of mapped page without sufficient permissions\n", NULL); - *return_to_user = false; - return -1; - } else if (tcb->uaccess_active){ - // Kernel uaccess helpers recover by redirecting the faulting instruction - // stream to their local error path, then resuming kernel mode via rfe. - assert(tcb->uaccess_err_addr != NULL, "uaccess err addr not set"); - *epc_ptr = (unsigned)tcb->uaccess_err_addr; - return 0; - } else { - panic("vmem: kernel TLB miss due to invalid privileges\n"); + unsigned fault_addr = (unsigned)(vpn) << 12; + + unsigned* pd = get_pid(); + unsigned* pte = vmem_get_pte(pd, fault_addr, true); + unsigned was = interrupts_disable(); + unsigned pte_value = *pte; // Lock this value in so we know it won't change throughout handling + + // Does the PTE adequately handle the miss? + bool needs_fault = false; + if (flags == 0) { // True TLB miss + if (!(pte_value & VMEM_VALID)) + needs_fault = true; + } else { + if (flags & VMEM_READ) { + if (pte_value & VMEM_READ) { + flags &= ~VMEM_READ; + } else { + needs_fault = true; + } + } + if (flags & VMEM_WRITE) { + if (pte_value & VMEM_WRITE) { + flags &= ~VMEM_WRITE; + } else { + needs_fault = true; + } + } + if (flags & VMEM_EXEC) { + if (pte_value & VMEM_EXEC) { + flags &= ~VMEM_EXEC; + } else { + needs_fault = true; + } } } - unsigned fault_addr = (unsigned)(vpn) << 12; + if (!needs_fault) { + tlb_write(fault_addr, pte_value); + interrupts_restore(was); + return 0; + } + interrupts_restore(was); + + return page_fault_handler(fault_addr, flags, pte, epc_ptr, return_to_user); +} + +// Updates PTE & TLB +int page_fault_handler(unsigned fault_addr, unsigned flags, unsigned* pte, unsigned* epc_ptr, bool* return_to_user) { + int args[2] = {fault_addr, flags}; + + // look up the VME corresponding to this faulting address + int was = interrupts_disable(); + struct TCB* tcb = get_current_tcb(); + interrupts_restore(was); struct VME* curr = tcb->vme_list; - while (curr){ - if (fault_addr >= curr->start && fault_addr < curr->end){ + while (curr) { + if (fault_addr >= curr->start && fault_addr < curr->end) { break; } curr = curr->next; } - if (curr == NULL){ - if (was_user) { - // User code touched an unmapped address. Abort back to the kernel caller - // of `jump_to_user(...)`. - say("| User program killed due to access of unmapped address\n", NULL); - *return_to_user = false; - return -1; - } else if (tcb->uaccess_active){ - // Kernel uaccess helpers recover by redirecting the faulting instruction - // stream to their local error path, then resuming kernel mode via rfe. - assert(tcb->uaccess_err_addr != NULL, "uaccess err addr not set"); - *epc_ptr = (unsigned)tcb->uaccess_err_addr; - return 0; - } else { - int args[3] = {fault_addr, flags, (int)*epc_ptr}; - say("| vmem: tlb miss fault_addr=0x%X flags=0x%X epc=0x%X has no corresponding VME\n", args); - panic("vmem: TLB miss with no corresponding VME.\n"); - return -1; - } + if (curr == NULL) { + int args[3] = {fault_addr, flags, (int)*epc_ptr}; + say("| vmem: tlb miss fault_addr=0x%X flags=0x%X epc=0x%X has no corresponding VME\n", args); + return segfault_helper(tcb, epc_ptr, return_to_user, + "| User program killed due to access of unmapped address\n", + "vmem: TLB miss with no corresponding VME.\n"); } - if ((curr->flags & MMAP_SHARED) && (curr->file == NULL)){ + if ((curr->flags & MMAP_SHARED) && (curr->file == NULL)) { int args[3] = {fault_addr, flags, (int)*epc_ptr}; say("| vmem: tlb miss fault_addr=0x%X flags=0x%X epc=0x%X hit unsupported shared anonymous VME\n", args); panic("vmem: shared anonymous TLB miss not supported yet.\n"); return -1; } - unsigned page_dir_index = ((unsigned)vpn >> 10) & 0x3FF; - - unsigned* pd = get_pid(); - unsigned pde = pd[page_dir_index]; + unsigned pte_value = *pte; // Make sure value is constant throughout - if (!(pde & VMEM_VALID)) { - // need to create a new page table for this PDE - unsigned pt_addr = create_page_table(); - unsigned entry = pt_addr | VMEM_VALID | VMEM_READ | VMEM_WRITE; - pd[page_dir_index] = entry; - pde = entry; - } - - unsigned* pt = (unsigned*)(pde & ~0xFFF); - unsigned page_table_index = (unsigned)vpn & 0x3FF; - unsigned pte = pt[page_table_index]; - - if (!(pte & VMEM_VALID)) { - // need to allocate a physical page and update the PTE - unsigned phys_page = 0; - if (curr->file){ - if (curr->flags & MMAP_SHARED){ - // this is intentional, so mmap can be used to extend files - unsigned bytes_in_page = shared_vme_page_bytes(curr, fault_addr); - - // shared mapping points directly into page cache - struct PageCacheEntry* page = page_cache_acquire(&page_cache, curr->file, - (curr->file_offset + (fault_addr - curr->start)), bytes_in_page); - if (curr->flags & MMAP_WRITE){ - page_cache_mark_dirty(&page_cache, curr->file, - (curr->file_offset + (fault_addr - curr->start))); - } - phys_page = (unsigned)page->page_data; - } else { - unsigned file_page_offset = curr->file_offset + (fault_addr - curr->start); - unsigned current_size = node_size_in_bytes(curr->file); - unsigned bytes_remaining = 0; - if (current_size > file_page_offset){ - bytes_remaining = current_size - file_page_offset; - } + if ((flags != 0) && (pte_value & VMEM_VALID)) { // Permission fault + if (flags & VMEM_READ) { + return segfault_helper(tcb, epc_ptr, return_to_user, + "| User program killed due to access of mapped page without sufficient permissions\n", + "vmem: can't handle a read permission fault"); + } + if (flags & VMEM_WRITE) { + if (!(curr->flags & MMAP_WRITE)) { + return segfault_helper(tcb, epc_ptr, return_to_user, + "| User program killed due to access of mapped page without sufficient permissions\n", + "vmem: invalid privileges (write)"); + } + // PTE says read-only but VME says writable => first write + // Dirty bit tracking + assert(pte_value != 0, "PTE value should not be 0"); + struct Page* page = get_page(pte_phys_addr(pte_value), "get page - fault handler write"); + physmem_page_lock(page); + if (*pte == pte_value) { // Revalidate + physmem_set_page_flags(page, PG_DIRTY); + *pte |= VMEM_WRITE; + tlb_write(fault_addr, pte_value); // Must update tlb_write and pte value with no possibility for eviction between + physmem_page_unlock(page); + return 0; + } + physmem_page_unlock(page); + // If revalidation didn't work, fall through to invalid PTE handler + // TODO: potentially COW (in the future) + } + if (flags & VMEM_EXEC) { + return segfault_helper(tcb, epc_ptr, return_to_user, + "| User program killed due to access of mapped page without sufficient permissions\n", + "vmem: can't handle an exec permission fault"); + } + } + + // Missing PTE + assert(!(*pte & VMEM_VALID), "flags = 0 for existing PTE"); + + // Create flags for PTE entry + unsigned pte_flags = VMEM_VALID; + if (curr->flags & MMAP_READ) + pte_flags |= VMEM_READ; + if (curr->flags & MMAP_WRITE) + pte_flags |= VMEM_WRITE; + if (curr->flags & MMAP_EXEC) + pte_flags |= VMEM_EXEC; + if (curr->flags & MMAP_USER) + pte_flags |= VMEM_USER; + + bool allow_write = curr->flags & MMAP_WRITE; + + // Need to allocate a physical page, update the PTE, and update the TLB + unsigned phys_page = 0; + if (curr->file) { + if (curr->flags & MMAP_SHARED) { + // FILE-BACKED SHARED MAPPING + + // allows mmap can to extend files + unsigned bytes_in_page = shared_vme_page_bytes(curr, fault_addr); + + // shared mapping points directly into page cache + struct PageCacheEntry* cache_entry = page_cache_acquire(&page_cache, curr->file, (curr->file_offset + (fault_addr - curr->start)), bytes_in_page); // Locks the page + struct Page* page = get_page(cache_entry->page_data, "get page - file backed shared"); + physmem_page_addRef(page, fault_addr); + + pte_flags &= ~VMEM_WRITE; // Map as read-only so we can do dirty tracking on first write + pte_value = (unsigned)cache_entry->page_data | pte_flags; + *pte = pte_value; + tlb_write(fault_addr, pte_value); + physmem_page_unlock(page); + return 0; + } else { + // FILE-BACKED PRIVATE MAPPING + unsigned file_page_offset = curr->file_offset + (fault_addr - curr->start); + unsigned current_size = node_size_in_bytes(curr->file); + unsigned bytes_remaining = 0; + if (current_size > file_page_offset) { + bytes_remaining = current_size - file_page_offset; + } - unsigned bytes_in_vme = (curr->size - (fault_addr - curr->start)) > FRAME_SIZE ? - FRAME_SIZE : (curr->size - (fault_addr - curr->start)); - unsigned bytes_in_page = bytes_remaining < bytes_in_vme ? - bytes_remaining : bytes_in_vme; + unsigned bytes_in_vme = (curr->size - (fault_addr - curr->start)) > FRAME_SIZE ? FRAME_SIZE : (curr->size - (fault_addr - curr->start)); + unsigned bytes_in_page = bytes_remaining < bytes_in_vme ? bytes_remaining : bytes_in_vme; - // private mapping copies from page cache (TODO: COW) - struct PageCacheEntry* page = page_cache_acquire(&page_cache, curr->file, - (curr->file_offset + (fault_addr - curr->start)), bytes_in_page); - - phys_page = (unsigned)physmem_alloc(); - memcpy((void*)phys_page, page->page_data, FRAME_SIZE); + // private mapping copies from page cache (TODO: COW) + struct PageCacheEntry* cache_entry = page_cache_acquire(&page_cache, curr->file, + (curr->file_offset + (fault_addr - curr->start)), bytes_in_page); // Locks the page - page_cache_release(&page_cache, curr->file, - (curr->file_offset + (fault_addr - curr->start))); - } + phys_page = (unsigned)physmem_alloc(); // TODO: make it unpinned once we're done copying if we can evict things to swap + memcpy((void*)phys_page, cache_entry->page_data, FRAME_SIZE); + + // Release the old page once we're done copying + struct Page* source_page_metadata = get_page(cache_entry->page_data, "get page - fault handler file backed private"); + physmem_page_unlock(source_page_metadata); + + // TODO locking or something once swap eviction exists + pte_value = phys_page | pte_flags; + *pte = pte_value; + tlb_write(fault_addr, pte_value); + return 0; + } + } else { + assert(!(curr->flags & MMAP_SHARED), "cannot yet handle shared anonymous pages\n"); + + if (curr->paddr != 0) { + // Physmem mappings reserve one contiguous physical window. Each faulting + // virtual page must therefore advance through that window page-for-page + // instead of aliasing every VME page back onto the first physical page. + phys_page = curr->paddr + (fault_addr - curr->start); } else { - assert(!(curr->flags & MMAP_SHARED), "cannot yet handle shared anonymous pages\n"); - - if (curr->paddr != 0){ - // Physmem mappings reserve one contiguous physical window. Each faulting - // virtual page must therefore advance through that window page-for-page - // instead of aliasing every VME page back onto the first physical page. - phys_page = curr->paddr + (fault_addr - curr->start); - } else { - phys_page = create_zeroed_page(); - } + // ANONYMOUS PRIVATE MAPPING + phys_page = create_zeroed_page(); // TODO: unpin it once swap exists } - - unsigned entry = phys_page | VMEM_VALID; - - if (curr->flags & MMAP_READ) entry |= VMEM_READ; - if (curr->flags & MMAP_WRITE) entry |= VMEM_WRITE; - if (curr->flags & MMAP_EXEC) entry |= VMEM_EXEC; - if (curr->flags & MMAP_USER) entry |= VMEM_USER; - pt[page_table_index] = entry; - pte = entry; - } - - tlb_write(fault_addr, pte); - return 0; + // TODO locking once swap exists + pte_value = phys_page | pte_flags; + *pte = pte_value; + tlb_write(fault_addr, pte_value); + return 0; + } } -void ipi_handler(unsigned data){ +void ipi_handler(unsigned data) { mark_ipi_handled(); + struct ShootdownRequest* request = (struct ShootdownRequest*)generic_spin_queue_remove_all(&per_core_data[get_core_id()].shootdown_requests); + while (request != NULL) { + struct ShootdownRequest* next = request->next; + tlb_invalidate_other(request->pid, request->vaddr); // Handle + countdownlatch_down(request->latch); // Mark as handled (means element might get freed) + request = next; + } +} - int cid = get_core_id(); - int args[2] = {cid, data}; - say("| Received IPI on core %d with data %d\n", args); +// Block until all cores successfully shootdown this page ref +void tlb_shootdown(struct PageRef* ref) { + unsigned num_cores = CONFIG.num_cores; + struct ShootdownRequest** created_requests = malloc(sizeof(struct ShootdownRequest*) * num_cores); // So we can clean up the requests once everyone is done + struct CountDownLatch latch; + countdownlatch_init(&latch, num_cores); + for (int i = 0; i < num_cores; i++) { + // Create a request & give to a core + struct ShootdownRequest* request = malloc(sizeof(struct ShootdownRequest)); + request->latch = &latch; + request->pid = ref->pid; + request->vaddr = (void*)ref->virtual_address; + request->next = NULL; + generic_spin_queue_add(&per_core_data[i].shootdown_requests, (struct GenericQueueElement*)request); + + // Keep track of created requests + created_requests[i] = request; + } + // All cores IPI + send_ipi(0); // This should interrupt us as well + // Wait for all cores to finish shootdown + countdownlatch_sync(&latch); + // Clean up! + for (int i = 0; i < num_cores; i++) { + free(created_requests[i]); + } + free(created_requests); } + +// Block until all cores shootdown the linked list of page refs +// void tlb_shootdown_batch(struct PageRef* ref) { +// } + +// Helper function to handle invalidating all referencing PTEs and invlpg TLB caches +// Page must be locked +void page_shootdown(struct Page* page) { + // Remove PTEs & invlpg + struct PageRef* ref = page->refs; + while (ref != NULL) { + // Overwrite the PTE + *vmem_get_pte((unsigned*)ref->pid, ref->virtual_address, false) = 0; // TODO make sure no one can modify this pte before we do + // Shootdown on all cores + tlb_shootdown(ref); + + struct PageRef* to_delete = ref; + ref = ref->next; + free(to_delete); + page->ref_cnt--; + } + assert(page->ref_cnt == 0, "page refcount != 0"); + page->refs = NULL; +} + +// Page must be locked already +// Page must not be pinned +void page_evict(struct Page* page) { + assert(!(page->flags & PG_PINNED), "TRYING TO EVICT A PINNED PAGE\n"); + // Acquire the inode lock so no one can try to demand page it in before we finish writing back + struct PageCacheEntry* cache_entry = page->cache_entry; // TODO what if someone else tries to evict at the same time as us? + void* frame = cache_entry->page_data; + blocking_lock_acquire(&cache_entry->key.inode->lock); + + // Evict from page cache + page_cache_remove(&page_cache, cache_entry); + + page_shootdown(page); + + // Writeback + if (page->flags & PG_DIRTY) { + struct Node node; + node.cached = cache_entry->key.inode; + node.filesystem = &fs; + node.parent_inumber = EXT2_BAD_INO; + node_write_all_locked(&node, cache_entry->key.offset, FRAME_SIZE, cache_entry->page_data); // TODO does this make sense + physmem_clear_page_flags(page, PG_DIRTY); + } + blocking_lock_release(&cache_entry->key.inode->lock); + + // Clean up metadata + cache_entry->key.inode->refcount--; + free(cache_entry); + + // Free page + page->cache_entry = NULL; + physmem_set_page_flags(page, PG_PINNED); + sem_up(&page->lock); + physmem_free(frame); +} \ No newline at end of file diff --git a/kernel/vmem.h b/kernel/vmem.h index 56299fd..7d47a64 100644 --- a/kernel/vmem.h +++ b/kernel/vmem.h @@ -3,6 +3,8 @@ #include "constants.h" #include "ext.h" +#include "countdown_latch.h" +#include "physmem.h" // flags to pass into mmap #define MMAP_NONE 0x00 @@ -80,13 +82,21 @@ void free_vme_list(struct VME* vme); // copy a thread's page dir/page tables and vme_list from src to dst void vmem_fork(struct TCB* src, struct TCB* dst); -// free all physical pages mapped by the given address space, +// free all physical pages mapped by the given address space, // and free the page directory and page tables void vmem_destroy_address_space(struct TCB* tcb); +// Helper to get the pte from a virtual address +unsigned* vmem_get_pte(unsigned* pd, unsigned virtual_address, bool create); + +// Helper to extract the physical address from a pte +void* pte_phys_addr(unsigned pte); + void vme_change_perms(struct VME* vme, unsigned new_flags); -extern void tlb_miss_handler_(void); +extern int tlb_miss_handler_(void); + +int page_fault_handler(unsigned fault_addr, unsigned flags, unsigned* pte, unsigned* epc_ptr, bool* return_to_user); extern void ipi_handler_(void); @@ -94,4 +104,32 @@ extern void mark_ipi_handled(void); extern unsigned send_ipi(unsigned data); +// Describes how a page is being used +struct PageRef { + // struct TCB* thread; + unsigned pid; + unsigned virtual_address; + // struct VME* vme; + struct PageRef* next; +}; + +struct ShootdownRequest { + struct ShootdownRequest* next; + unsigned pid; + void* vaddr; + struct CountDownLatch* latch; // On handling a request, a thread will call down on this +}; + +// Block until all cores successfully shootdown this page ref +void tlb_shootdown(struct PageRef* ref); + +// Block until all cores shootdown the linked list of page refs +void tlb_shootdown_batch(struct PageRef* ref); + +void page_shootdown(struct Page* page); + +// Evicts & frees a page (unlocking it in the process) +// The page must be locked already +void page_evict(struct Page* page); + #endif // VMEM_H diff --git a/root/crt/dirent.h b/root/crt/dirent.h index c35bdd3..aee3560 100644 --- a/root/crt/dirent.h +++ b/root/crt/dirent.h @@ -1,23 +1,23 @@ -#ifndef DIRENT_H -#define DIRENT_H - -#define DT_UNKNOWN 0 -#define DT_FIFO 1 -#define DT_CHR 2 -#define DT_DIR 4 -#define DT_BLK 6 -#define DT_REG 8 -#define DT_LNK 10 -#define DT_SOCK 12 -#define DT_WHT 14 - -struct linux_dirent { - unsigned d_ino; // i-node number. - unsigned d_off; - unsigned short d_reclen; // Length of this record. - char d_name; // Filename (null-terminated array). - // char pad; - // char d_type; // Offset is (d_reclen - 1). -}; - -#endif // DIRENT_H +#ifndef DIRENT_H +#define DIRENT_H + +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 +#define DT_WHT 14 + +struct linux_dirent { + unsigned d_ino; // i-node number. + unsigned d_off; + unsigned short d_reclen; // Length of this record. + char d_name; // Filename (null-terminated array). + // char pad; + // char d_type; // Offset is (d_reclen - 1). +}; + +#endif // DIRENT_H diff --git a/root/shell/dirs.c b/root/shell/dirs.c index b777df8..ddf0cf5 100644 --- a/root/shell/dirs.c +++ b/root/shell/dirs.c @@ -1,383 +1,383 @@ -#include "dirs.h" -#include "../crt/string.h" -#include "../crt/sys.h" -#include "../crt/print.h" -#include "../crt/vga.h" - - -void *malloc(unsigned size); -void free(void* p); - -struct LinkedDirent* sbin_entries = 0; - -#define ENTRIES_PER_LINE 5 -#define SPACES_PER_TAB 8 - -#define PRINT_BUFFER_SIZE 256 - -struct LinkedDirent* create_linked_dirent(struct linux_dirent* dirent) { - struct LinkedDirent* entry = malloc(sizeof(struct LinkedDirent) + dirent->d_reclen - sizeof(struct linux_dirent)); - memcpy(&entry->dirent, dirent, dirent->d_reclen); - entry->d_type = *((char*)dirent + dirent->d_reclen - 1); - entry->next = 0; - return entry; -} - -void destroy_linked_dirents(struct LinkedDirent* head) { - struct LinkedDirent* current = head; - while (current != 0){ - struct LinkedDirent* next = current->next; - free(current); - current = next; - } -} - -struct LinkedDirent* read_directory_no_error(char* path) { - struct LinkedDirent* entries = read_directory(path); - if (entries == (struct LinkedDirent*) -1) { - return 0; - } - return entries; -} - -// Returns -1 on error. 0 represents empty. -struct LinkedDirent* read_directory(char* path) { - int fd = open(path); - if (fd < 0) { - return (struct LinkedDirent*) -1; - } - - char* buffer = malloc(1024); - struct LinkedDirent* head = 0; - struct LinkedDirent* tail = 0; - while (1) { - int n = getdents(fd, buffer, 1024); - if (n < 0) { - // Error. - close(fd); - destroy_linked_dirents(head); - free(buffer); - return (struct LinkedDirent*) -1; - } - if (n == 0) { - // No more entries. - break; - } - - int offset = 0; - while (offset < n) { - struct linux_dirent* dirent = (struct linux_dirent*) (buffer + offset); - struct LinkedDirent* new_entry = create_linked_dirent(dirent); - if (head == 0) { - head = new_entry; - tail = new_entry; - } else { - tail->next = new_entry; - tail = new_entry; - } - offset += dirent->d_reclen; - } - } - close(fd); - free(buffer); - return head; -} - -// Returns new index. If keep_together_length > 1, ensures first characters are printed together. -unsigned add_to_print_buffer(char* print_buffer, unsigned index, char* to_add, unsigned keep_together_length) { - // Print earlier if needed. - if (keep_together_length > 1 && index + keep_together_length >= PRINT_BUFFER_SIZE) { - print_buffer[index] = 0; - puts(print_buffer); - index = 0; - } - while (*to_add != 0) { - if (index >= PRINT_BUFFER_SIZE - 1) { - // Buffer full. Print and reset. - print_buffer[index] = 0; - puts(print_buffer); - index = 0; - } - // Add character. - print_buffer[index++] = *to_add; - to_add++; - } - return index; -} - -void print_print_buffer(char* print_buffer, unsigned index) { - print_buffer[index] = 0; - puts(print_buffer); -} - -void get_column_widths(struct LinkedDirent* head, unsigned entries_per_line, unsigned* longest, bool skip_current_and_parent) { - int count = 0; - - // Find longest names for formatting. - for (struct LinkedDirent* current = head; current != 0; current = current->next) { - char* name = ¤t->dirent.d_name; - - // Skip "." and ".." if not including. - if (skip_current_and_parent && name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { - continue; - } - // Skip "lost+found". - if (strcmp(name, "lost+found") == 0) { - continue; - } - - unsigned length = strlen(name); - if (length > longest[count % entries_per_line]) { - longest[count % entries_per_line] = length; - } - count++; - } - - for (int i = 0; i < entries_per_line - 1; i++) { - // Add one for a space, and then round up. - longest[i] = (longest[i] + SPACES_PER_TAB) & ~(SPACES_PER_TAB - 1); - } -} - -void print_directory(struct LinkedDirent* head, bool skip_current_and_parent) { - if (head == 0) { - return; - } - - unsigned longest_array[ENTRIES_PER_LINE] = {0}; - get_column_widths(head, ENTRIES_PER_LINE, longest_array, skip_current_and_parent); - - unsigned total_longest = 0; - unsigned one_line_width = 0; - - for (int i = 0; i < ENTRIES_PER_LINE; i++) { - one_line_width += longest_array[i]; - if (longest_array[i] > total_longest) { - total_longest = longest_array[i]; - } - } - - unsigned better_entries_per_line = TILE_ROW_WIDTH / total_longest; - unsigned entries_per_line = ENTRIES_PER_LINE; - - // Check if ideal space. - // Overflow or not overflow and better option. - unsigned* longest = longest_array; - if (one_line_width > TILE_ROW_WIDTH || better_entries_per_line > ENTRIES_PER_LINE) { - entries_per_line = better_entries_per_line; - if (entries_per_line == 0) { - entries_per_line = 1; - } - // Redistribute. - longest = malloc(sizeof(unsigned) * entries_per_line); - for (int i = 0; i < entries_per_line; i++) { - longest[i] = 0; - } - get_column_widths(head, entries_per_line, longest, skip_current_and_parent); - } - - int count = 0; - char print_buffer[PRINT_BUFFER_SIZE]; - unsigned buffer_index = 0; - for (struct LinkedDirent* current = head; current != 0; current = current->next) { - char* name = ¤t->dirent.d_name; - - // Skip "." and ".." if not including. - if (skip_current_and_parent && name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { - continue; - } - // Skip "lost+found". - if (strcmp(name, "lost+found") == 0) { - continue; - } - - // Colors I stole from bash. - char* color; - switch (current->d_type) { - case DT_FIFO: - color = "\x1b[49m"; - break; - case DT_CHR: - color = "\x1b[48m"; - break; - case DT_DIR: - color = "\x1b[34m"; - break; - case DT_BLK: - color = "\x1b[48m"; - break; - case DT_REG: - color = "\x1b[37m"; - break; - case DT_LNK: - color = "\x1b[36m"; - break; - case DT_SOCK: - color = "\x1b[35m"; - break; - case DT_WHT: - color = "\x1b[37m"; - break; - default: // DT_UNKNOWN or other type. - color = "\x1b[31m"; // Bright red. - break; - } - buffer_index = add_to_print_buffer(print_buffer, buffer_index, color, 5); - buffer_index = add_to_print_buffer(print_buffer, buffer_index, name, 0); - // Reset to white. - buffer_index = add_to_print_buffer(print_buffer, buffer_index, "\x1b[37m", 5); - - unsigned name_length = strlen(name); - unsigned padding = longest[count % entries_per_line] - name_length; - for (unsigned i = 0; i < padding; i++) { - buffer_index = add_to_print_buffer(print_buffer, buffer_index, " ", 0); - } - count++; - if (count % entries_per_line == 0) { - // Print at end of line. - buffer_index = add_to_print_buffer(print_buffer, buffer_index, "\n", 0); - print_print_buffer(print_buffer, buffer_index); - buffer_index = 0; - } - } - // Reset color. - buffer_index = add_to_print_buffer(print_buffer, buffer_index, "\x1b[37m", 5); - - // Final newline if we didn't end on one. - if (count % entries_per_line != 0) { - buffer_index = add_to_print_buffer(print_buffer, buffer_index, "\n", 0); - } - - // Print rest of buffer. - print_print_buffer(print_buffer, buffer_index); - if (longest != longest_array) { - free(longest); - } -} - -struct LinkedDirent* tab_complete_directory(char* prefix, bool include_commands) { - // Find last '/' in prefix. - int last_slash = -1; - for (int i = 0; prefix[i] != 0; i++) { - if (prefix[i] == '/') { - last_slash = i; - } - } - char* prefix_base = malloc(last_slash + 2); // For '/' and 0. - - struct LinkedDirent* head; - if (last_slash == -1) { - // No base path. - prefix_base[0] = 0; - head = read_directory_no_error("."); - } else { - memcpy(prefix_base, prefix, last_slash + 1); - prefix_base[last_slash + 1] = 0; - head = read_directory_no_error(prefix_base); - } - - if (include_commands) { - // Get from sbin if not already. - if (sbin_entries == 0) { - sbin_entries = read_directory_no_error("/sbin"); - // Skip ".", "..", and "lost+found". - struct LinkedDirent* current = sbin_entries; - struct LinkedDirent* previous = 0; - unsigned count = 0; - while (current != 0 && count < 3) { - char* name = ¤t->dirent.d_name; - if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) - || strcmp(name, "lost+found") == 0) { - count++; - if (previous == 0) { - sbin_entries = current->next; - } else { - previous->next = current->next; - } - struct LinkedDirent* to_free = current; - current = current->next; - free(to_free); - } else { - previous = current; - current = current->next; - } - } - // Built-in commands. - char* built_ins[11] = {"rmdir", "rm", "mv", "mkdir", "ls", "help", "exit", "cp", "clear", "cd", "cat"}; - for (int i = 0; i < 11; i++) { - unsigned name_length = strlen(built_ins[i]); - struct LinkedDirent* entry = malloc(sizeof(struct LinkedDirent) + name_length + 1); - entry->dirent.d_ino = 0; - entry->dirent.d_off = 0; - entry->dirent.d_reclen = sizeof(struct linux_dirent) + name_length + 1; - memcpy(&entry->dirent.d_name, built_ins[i], name_length + 1); - entry->d_type = DT_REG; - entry->next = 0; - if (sbin_entries == 0) { - sbin_entries = entry; - } else { - entry->next = sbin_entries; - sbin_entries = entry; - } - } - } - // Copy sbin entries. - struct LinkedDirent* copied_sbin = 0; - struct LinkedDirent* copied_sbin_tail = 0; - for (struct LinkedDirent* current = sbin_entries; current != 0; current = current->next) { - unsigned size = sizeof(struct LinkedDirent) + current->dirent.d_reclen - sizeof(struct linux_dirent); - struct LinkedDirent* new_entry = malloc(size); - memcpy(new_entry, current, size); - if (copied_sbin == 0) { - copied_sbin = new_entry; - copied_sbin_tail = new_entry; - } else { - copied_sbin_tail->next = new_entry; - copied_sbin_tail = new_entry; - } - } - if (copied_sbin_tail != 0) { - copied_sbin_tail->next = head; - head = copied_sbin; - } - } - - if (head == 0) { - // No matches. - free(prefix_base); - return 0; - } - - // Match rest of prefix. - char* prefix_rest = prefix + last_slash + 1; - unsigned prefix_rest_length = strlen(prefix_rest); - struct LinkedDirent* matches_head = 0; - struct LinkedDirent* matches_tail = 0; - for (struct LinkedDirent* current = head; current != 0;) { - bool match = 1; - char* name = ¤t->dirent.d_name; - for (unsigned i = 0; i < prefix_rest_length; i++) { - if (name[i] != prefix_rest[i] || name[i] == 0) { - match = 0; - break; - } - } - struct LinkedDirent* next = current->next; - current->next = 0; - if (match) { - if (matches_head == 0) { - matches_head = current; - matches_tail = current; - } else { - matches_tail->next = current; - matches_tail = current; - } - } else { - // No match. Free. - free(current); - } - current = next; - } - free(prefix_base); - return matches_head; -} +#include "dirs.h" +#include "../crt/string.h" +#include "../crt/sys.h" +#include "../crt/print.h" +#include "../crt/vga.h" + + +void *malloc(unsigned size); +void free(void* p); + +struct LinkedDirent* sbin_entries = 0; + +#define ENTRIES_PER_LINE 5 +#define SPACES_PER_TAB 8 + +#define PRINT_BUFFER_SIZE 256 + +struct LinkedDirent* create_linked_dirent(struct linux_dirent* dirent) { + struct LinkedDirent* entry = malloc(sizeof(struct LinkedDirent) + dirent->d_reclen - sizeof(struct linux_dirent)); + memcpy(&entry->dirent, dirent, dirent->d_reclen); + entry->d_type = *((char*)dirent + dirent->d_reclen - 1); + entry->next = 0; + return entry; +} + +void destroy_linked_dirents(struct LinkedDirent* head) { + struct LinkedDirent* current = head; + while (current != 0){ + struct LinkedDirent* next = current->next; + free(current); + current = next; + } +} + +struct LinkedDirent* read_directory_no_error(char* path) { + struct LinkedDirent* entries = read_directory(path); + if (entries == (struct LinkedDirent*) -1) { + return 0; + } + return entries; +} + +// Returns -1 on error. 0 represents empty. +struct LinkedDirent* read_directory(char* path) { + int fd = open(path); + if (fd < 0) { + return (struct LinkedDirent*) -1; + } + + char* buffer = malloc(1024); + struct LinkedDirent* head = 0; + struct LinkedDirent* tail = 0; + while (1) { + int n = getdents(fd, buffer, 1024); + if (n < 0) { + // Error. + close(fd); + destroy_linked_dirents(head); + free(buffer); + return (struct LinkedDirent*) -1; + } + if (n == 0) { + // No more entries. + break; + } + + int offset = 0; + while (offset < n) { + struct linux_dirent* dirent = (struct linux_dirent*) (buffer + offset); + struct LinkedDirent* new_entry = create_linked_dirent(dirent); + if (head == 0) { + head = new_entry; + tail = new_entry; + } else { + tail->next = new_entry; + tail = new_entry; + } + offset += dirent->d_reclen; + } + } + close(fd); + free(buffer); + return head; +} + +// Returns new index. If keep_together_length > 1, ensures first characters are printed together. +unsigned add_to_print_buffer(char* print_buffer, unsigned index, char* to_add, unsigned keep_together_length) { + // Print earlier if needed. + if (keep_together_length > 1 && index + keep_together_length >= PRINT_BUFFER_SIZE) { + print_buffer[index] = 0; + puts(print_buffer); + index = 0; + } + while (*to_add != 0) { + if (index >= PRINT_BUFFER_SIZE - 1) { + // Buffer full. Print and reset. + print_buffer[index] = 0; + puts(print_buffer); + index = 0; + } + // Add character. + print_buffer[index++] = *to_add; + to_add++; + } + return index; +} + +void print_print_buffer(char* print_buffer, unsigned index) { + print_buffer[index] = 0; + puts(print_buffer); +} + +void get_column_widths(struct LinkedDirent* head, unsigned entries_per_line, unsigned* longest, bool skip_current_and_parent) { + int count = 0; + + // Find longest names for formatting. + for (struct LinkedDirent* current = head; current != 0; current = current->next) { + char* name = ¤t->dirent.d_name; + + // Skip "." and ".." if not including. + if (skip_current_and_parent && name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { + continue; + } + // Skip "lost+found". + if (strcmp(name, "lost+found") == 0) { + continue; + } + + unsigned length = strlen(name); + if (length > longest[count % entries_per_line]) { + longest[count % entries_per_line] = length; + } + count++; + } + + for (int i = 0; i < entries_per_line - 1; i++) { + // Add one for a space, and then round up. + longest[i] = (longest[i] + SPACES_PER_TAB) & ~(SPACES_PER_TAB - 1); + } +} + +void print_directory(struct LinkedDirent* head, bool skip_current_and_parent) { + if (head == 0) { + return; + } + + unsigned longest_array[ENTRIES_PER_LINE] = {0}; + get_column_widths(head, ENTRIES_PER_LINE, longest_array, skip_current_and_parent); + + unsigned total_longest = 0; + unsigned one_line_width = 0; + + for (int i = 0; i < ENTRIES_PER_LINE; i++) { + one_line_width += longest_array[i]; + if (longest_array[i] > total_longest) { + total_longest = longest_array[i]; + } + } + + unsigned better_entries_per_line = TILE_ROW_WIDTH / total_longest; + unsigned entries_per_line = ENTRIES_PER_LINE; + + // Check if ideal space. + // Overflow or not overflow and better option. + unsigned* longest = longest_array; + if (one_line_width > TILE_ROW_WIDTH || better_entries_per_line > ENTRIES_PER_LINE) { + entries_per_line = better_entries_per_line; + if (entries_per_line == 0) { + entries_per_line = 1; + } + // Redistribute. + longest = malloc(sizeof(unsigned) * entries_per_line); + for (int i = 0; i < entries_per_line; i++) { + longest[i] = 0; + } + get_column_widths(head, entries_per_line, longest, skip_current_and_parent); + } + + int count = 0; + char print_buffer[PRINT_BUFFER_SIZE]; + unsigned buffer_index = 0; + for (struct LinkedDirent* current = head; current != 0; current = current->next) { + char* name = ¤t->dirent.d_name; + + // Skip "." and ".." if not including. + if (skip_current_and_parent && name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { + continue; + } + // Skip "lost+found". + if (strcmp(name, "lost+found") == 0) { + continue; + } + + // Colors I stole from bash. + char* color; + switch (current->d_type) { + case DT_FIFO: + color = "\x1b[49m"; + break; + case DT_CHR: + color = "\x1b[48m"; + break; + case DT_DIR: + color = "\x1b[34m"; + break; + case DT_BLK: + color = "\x1b[48m"; + break; + case DT_REG: + color = "\x1b[37m"; + break; + case DT_LNK: + color = "\x1b[36m"; + break; + case DT_SOCK: + color = "\x1b[35m"; + break; + case DT_WHT: + color = "\x1b[37m"; + break; + default: // DT_UNKNOWN or other type. + color = "\x1b[31m"; // Bright red. + break; + } + buffer_index = add_to_print_buffer(print_buffer, buffer_index, color, 5); + buffer_index = add_to_print_buffer(print_buffer, buffer_index, name, 0); + // Reset to white. + buffer_index = add_to_print_buffer(print_buffer, buffer_index, "\x1b[37m", 5); + + unsigned name_length = strlen(name); + unsigned padding = longest[count % entries_per_line] - name_length; + for (unsigned i = 0; i < padding; i++) { + buffer_index = add_to_print_buffer(print_buffer, buffer_index, " ", 0); + } + count++; + if (count % entries_per_line == 0) { + // Print at end of line. + buffer_index = add_to_print_buffer(print_buffer, buffer_index, "\n", 0); + print_print_buffer(print_buffer, buffer_index); + buffer_index = 0; + } + } + // Reset color. + buffer_index = add_to_print_buffer(print_buffer, buffer_index, "\x1b[37m", 5); + + // Final newline if we didn't end on one. + if (count % entries_per_line != 0) { + buffer_index = add_to_print_buffer(print_buffer, buffer_index, "\n", 0); + } + + // Print rest of buffer. + print_print_buffer(print_buffer, buffer_index); + if (longest != longest_array) { + free(longest); + } +} + +struct LinkedDirent* tab_complete_directory(char* prefix, bool include_commands) { + // Find last '/' in prefix. + int last_slash = -1; + for (int i = 0; prefix[i] != 0; i++) { + if (prefix[i] == '/') { + last_slash = i; + } + } + char* prefix_base = malloc(last_slash + 2); // For '/' and 0. + + struct LinkedDirent* head; + if (last_slash == -1) { + // No base path. + prefix_base[0] = 0; + head = read_directory_no_error("."); + } else { + memcpy(prefix_base, prefix, last_slash + 1); + prefix_base[last_slash + 1] = 0; + head = read_directory_no_error(prefix_base); + } + + if (include_commands) { + // Get from sbin if not already. + if (sbin_entries == 0) { + sbin_entries = read_directory_no_error("/sbin"); + // Skip ".", "..", and "lost+found". + struct LinkedDirent* current = sbin_entries; + struct LinkedDirent* previous = 0; + unsigned count = 0; + while (current != 0 && count < 3) { + char* name = ¤t->dirent.d_name; + if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) + || strcmp(name, "lost+found") == 0) { + count++; + if (previous == 0) { + sbin_entries = current->next; + } else { + previous->next = current->next; + } + struct LinkedDirent* to_free = current; + current = current->next; + free(to_free); + } else { + previous = current; + current = current->next; + } + } + // Built-in commands. + char* built_ins[11] = {"rmdir", "rm", "mv", "mkdir", "ls", "help", "exit", "cp", "clear", "cd", "cat"}; + for (int i = 0; i < 11; i++) { + unsigned name_length = strlen(built_ins[i]); + struct LinkedDirent* entry = malloc(sizeof(struct LinkedDirent) + name_length + 1); + entry->dirent.d_ino = 0; + entry->dirent.d_off = 0; + entry->dirent.d_reclen = sizeof(struct linux_dirent) + name_length + 1; + memcpy(&entry->dirent.d_name, built_ins[i], name_length + 1); + entry->d_type = DT_REG; + entry->next = 0; + if (sbin_entries == 0) { + sbin_entries = entry; + } else { + entry->next = sbin_entries; + sbin_entries = entry; + } + } + } + // Copy sbin entries. + struct LinkedDirent* copied_sbin = 0; + struct LinkedDirent* copied_sbin_tail = 0; + for (struct LinkedDirent* current = sbin_entries; current != 0; current = current->next) { + unsigned size = sizeof(struct LinkedDirent) + current->dirent.d_reclen - sizeof(struct linux_dirent); + struct LinkedDirent* new_entry = malloc(size); + memcpy(new_entry, current, size); + if (copied_sbin == 0) { + copied_sbin = new_entry; + copied_sbin_tail = new_entry; + } else { + copied_sbin_tail->next = new_entry; + copied_sbin_tail = new_entry; + } + } + if (copied_sbin_tail != 0) { + copied_sbin_tail->next = head; + head = copied_sbin; + } + } + + if (head == 0) { + // No matches. + free(prefix_base); + return 0; + } + + // Match rest of prefix. + char* prefix_rest = prefix + last_slash + 1; + unsigned prefix_rest_length = strlen(prefix_rest); + struct LinkedDirent* matches_head = 0; + struct LinkedDirent* matches_tail = 0; + for (struct LinkedDirent* current = head; current != 0;) { + bool match = 1; + char* name = ¤t->dirent.d_name; + for (unsigned i = 0; i < prefix_rest_length; i++) { + if (name[i] != prefix_rest[i] || name[i] == 0) { + match = 0; + break; + } + } + struct LinkedDirent* next = current->next; + current->next = 0; + if (match) { + if (matches_head == 0) { + matches_head = current; + matches_tail = current; + } else { + matches_tail->next = current; + matches_tail = current; + } + } else { + // No match. Free. + free(current); + } + current = next; + } + free(prefix_base); + return matches_head; +} diff --git a/root/shell/dirs.h b/root/shell/dirs.h index 868de33..2820ab5 100644 --- a/root/shell/dirs.h +++ b/root/shell/dirs.h @@ -1,23 +1,23 @@ -#ifndef DIRS_H -#define DIRS_H - -#include "../crt/dirent.h" -#include "../crt/stdbool.h" - -// Linked-list representation. -struct LinkedDirent { - struct LinkedDirent* next; - char d_type; - struct linux_dirent dirent; -}; - -struct LinkedDirent* create_linked_dirent(struct linux_dirent* dirent); -void destroy_linked_dirents(struct LinkedDirent* head); - -struct LinkedDirent* read_directory(char* path); -void print_directory(struct LinkedDirent* head, bool skip_current_and_parent); - -struct LinkedDirent* tab_complete_directory(char* prefix, bool include_commands); - -#endif // DIRS_H - +#ifndef DIRS_H +#define DIRS_H + +#include "../crt/dirent.h" +#include "../crt/stdbool.h" + +// Linked-list representation. +struct LinkedDirent { + struct LinkedDirent* next; + char d_type; + struct linux_dirent dirent; +}; + +struct LinkedDirent* create_linked_dirent(struct linux_dirent* dirent); +void destroy_linked_dirents(struct LinkedDirent* head); + +struct LinkedDirent* read_directory(char* path); +void print_directory(struct LinkedDirent* head, bool skip_current_and_parent); + +struct LinkedDirent* tab_complete_directory(char* prefix, bool include_commands); + +#endif // DIRS_H + diff --git a/tests/ipi_simple.c b/tests/ipi_simple.c index 3b59d4f..d23ae85 100644 --- a/tests/ipi_simple.c +++ b/tests/ipi_simple.c @@ -1,7 +1,19 @@ #include "../kernel/vmem.h" #include "../kernel/print.h" +#include "../kernel/ivt.h" +#include "../kernel/machine.h" -void kernel_main(void){ +void simple_ipi_handler() { + unsigned data = get_mbi(); + mark_ipi_handled(); + + int cid = get_core_id(); + int args[2] = {cid, data}; + say("| Received IPI on core %d with data %d\n", args); +} + +void kernel_main(void) { + register_handler((void*)simple_ipi_handler, (void*)IPI_IVT_ENTRY); say("***Sending test IPI with data 42\n", NULL); send_ipi(42); say("***Test IPI sent\n", NULL); diff --git a/tests/user_dir_syscalls.dir/sbin/dirs.c b/tests/user_dir_syscalls.dir/sbin/dirs.c index b9888c4..9856414 100644 --- a/tests/user_dir_syscalls.dir/sbin/dirs.c +++ b/tests/user_dir_syscalls.dir/sbin/dirs.c @@ -1,105 +1,105 @@ -#include "dirs.h" -#include "../../../root/crt/print.h" -#include "../../../root/crt/stdlib.h" -#include "../../../root/crt/sys.h" - -#define ENTRIES_PER_LINE 4 -#define SPACES_PER_TAB 8 - -// Combines with '/'. Adds null terminator. -char* combine_path(char* base_path, char* rest, unsigned base_length, unsigned rest_length) { - char has_base = base_length != 0; - char* full_path = (char*) malloc(base_length + rest_length + 1 + has_base); // 0 and '/'. - memcpy(full_path + base_length + has_base, rest, rest_length); - if (has_base) { - memcpy(full_path, base_path, base_length); - full_path[base_length] = '/'; - } - full_path[base_length + rest_length + has_base] = 0; - return full_path; -} - -struct LinkedDirent *create_linked_dirent(struct linux_dirent *dirent) { - struct LinkedDirent *entry = (struct LinkedDirent*) malloc(sizeof(struct LinkedDirent) + dirent->d_reclen - sizeof(struct linux_dirent)); - memcpy(&entry->dirent, dirent, dirent->d_reclen); - entry->d_type = *((char*)dirent + dirent->d_reclen - 1); - entry->next = 0; - return entry; -} - -void destroy_linked_dirents(struct LinkedDirent *head) { - struct LinkedDirent *current = head; - while (current != 0) { - struct LinkedDirent *next = current->next; - free(current); - current = next; - } -} - -struct LinkedDirent *read_directory(char* path) { - int fd = open(path); - if (fd < 0) { - return 0; - } - - char* buffer = (char*) malloc(BUFFER_SIZE); - struct LinkedDirent *head = 0; - struct LinkedDirent *tail = 0; - while (1) { - int n = getdents(fd, buffer, BUFFER_SIZE); - int argshi[1] = {(int) n}; - printf("***getdents returned %d.\n", argshi); - if (n < 0) { - close(fd); - destroy_linked_dirents(head); - free(buffer); - return 0; - } - if (n == 0) { - // No more entries. - break; - } - - int offset = 0; - while (offset < n) { - struct linux_dirent* dirent = (struct linux_dirent*) (buffer + offset); - struct LinkedDirent* new_entry = create_linked_dirent(dirent); - if (head == 0) { - head = new_entry; - tail = new_entry; - } else { - tail->next = new_entry; - tail = new_entry; - } - offset += dirent->d_reclen; - - int args[1] = {(int) &dirent->d_name}; - - printf("***Found entry: %s\n", args); - printf("*** Type: ", NULL); - switch (new_entry->d_type) { - case DT_REG: - printf("Regular file.\n", NULL); - break; - case DT_DIR: - printf("Directory.\n", NULL); - break; - case DT_LNK: - printf("Symbolic link.\n", NULL); - char link[BUFFER_SIZE]; - char* combined_path = combine_path(path, (char*) &dirent->d_name, strlen(path), strlen((char*) &dirent->d_name)); - readlink(combined_path, link, BUFFER_SIZE); - free(combined_path); - int args[1] = {(int) link}; - printf("*** To: %s\n", args); - break; - default: - printf("Other.\n", NULL); - break; - } - } - } - close(fd); - free(buffer); - return head; -} +#include "dirs.h" +#include "../../../root/crt/print.h" +#include "../../../root/crt/stdlib.h" +#include "../../../root/crt/sys.h" + +#define ENTRIES_PER_LINE 4 +#define SPACES_PER_TAB 8 + +// Combines with '/'. Adds null terminator. +char* combine_path(char* base_path, char* rest, unsigned base_length, unsigned rest_length) { + char has_base = base_length != 0; + char* full_path = (char*) malloc(base_length + rest_length + 1 + has_base); // 0 and '/'. + memcpy(full_path + base_length + has_base, rest, rest_length); + if (has_base) { + memcpy(full_path, base_path, base_length); + full_path[base_length] = '/'; + } + full_path[base_length + rest_length + has_base] = 0; + return full_path; +} + +struct LinkedDirent *create_linked_dirent(struct linux_dirent *dirent) { + struct LinkedDirent *entry = (struct LinkedDirent*) malloc(sizeof(struct LinkedDirent) + dirent->d_reclen - sizeof(struct linux_dirent)); + memcpy(&entry->dirent, dirent, dirent->d_reclen); + entry->d_type = *((char*)dirent + dirent->d_reclen - 1); + entry->next = 0; + return entry; +} + +void destroy_linked_dirents(struct LinkedDirent *head) { + struct LinkedDirent *current = head; + while (current != 0) { + struct LinkedDirent *next = current->next; + free(current); + current = next; + } +} + +struct LinkedDirent *read_directory(char* path) { + int fd = open(path); + if (fd < 0) { + return 0; + } + + char* buffer = (char*) malloc(BUFFER_SIZE); + struct LinkedDirent *head = 0; + struct LinkedDirent *tail = 0; + while (1) { + int n = getdents(fd, buffer, BUFFER_SIZE); + int argshi[1] = {(int) n}; + printf("***getdents returned %d.\n", argshi); + if (n < 0) { + close(fd); + destroy_linked_dirents(head); + free(buffer); + return 0; + } + if (n == 0) { + // No more entries. + break; + } + + int offset = 0; + while (offset < n) { + struct linux_dirent* dirent = (struct linux_dirent*) (buffer + offset); + struct LinkedDirent* new_entry = create_linked_dirent(dirent); + if (head == 0) { + head = new_entry; + tail = new_entry; + } else { + tail->next = new_entry; + tail = new_entry; + } + offset += dirent->d_reclen; + + int args[1] = {(int) &dirent->d_name}; + + printf("***Found entry: %s\n", args); + printf("*** Type: ", NULL); + switch (new_entry->d_type) { + case DT_REG: + printf("Regular file.\n", NULL); + break; + case DT_DIR: + printf("Directory.\n", NULL); + break; + case DT_LNK: + printf("Symbolic link.\n", NULL); + char link[BUFFER_SIZE]; + char* combined_path = combine_path(path, (char*) &dirent->d_name, strlen(path), strlen((char*) &dirent->d_name)); + readlink(combined_path, link, BUFFER_SIZE); + free(combined_path); + int args[1] = {(int) link}; + printf("*** To: %s\n", args); + break; + default: + printf("Other.\n", NULL); + break; + } + } + } + close(fd); + free(buffer); + return head; +} diff --git a/tests/user_dir_syscalls.dir/sbin/dirs.h b/tests/user_dir_syscalls.dir/sbin/dirs.h index 1ac7680..b1e01d4 100644 --- a/tests/user_dir_syscalls.dir/sbin/dirs.h +++ b/tests/user_dir_syscalls.dir/sbin/dirs.h @@ -1,20 +1,20 @@ -#ifndef DIRS_H -#define DIRS_H - +#ifndef DIRS_H +#define DIRS_H + #include "../../../root/crt/dirent.h" #include "../../../root/crt/string.h" - -#define BUFFER_SIZE 1024 - + +#define BUFFER_SIZE 1024 + struct LinkedDirent { - struct LinkedDirent *next; - char d_type; - struct linux_dirent dirent; -}; - -struct LinkedDirent *create_linked_dirent(struct linux_dirent *dirent); -void destroy_linked_dirents(struct LinkedDirent *head); - -struct LinkedDirent *read_directory(char *path); - -#endif // DIRS_H + struct LinkedDirent *next; + char d_type; + struct linux_dirent dirent; +}; + +struct LinkedDirent *create_linked_dirent(struct linux_dirent *dirent); +void destroy_linked_dirents(struct LinkedDirent *head); + +struct LinkedDirent *read_directory(char *path); + +#endif // DIRS_H diff --git a/tests/vmem_eviction_concurrency.c b/tests/vmem_eviction_concurrency.c new file mode 100644 index 0000000..48af480 --- /dev/null +++ b/tests/vmem_eviction_concurrency.c @@ -0,0 +1,172 @@ +/* + * Eviction concurrency stress test. + * + * 3 writer threads repeatedly write disjoint bytes into a shared, file-backed + * mapping while one evictor thread continuously evicts the page. After a + * fixed number of rounds we quiesce and verify the backing file contains the + * writers' last committed values. + * + * Checks that faulting and evicting at the same time still produces correct behavior; + * no deadlocks and no incoherence + */ + +#include "../kernel/vmem.h" +#include "../kernel/ext.h" +#include "../kernel/threads.h" +#include "../kernel/barrier.h" +#include "../kernel/heap.h" +#include "../kernel/print.h" +#include "../kernel/debug.h" +#include "../kernel/physmem.h" +#include "../kernel/machine.h" +#include "../kernel/page_cache.h" +#include "../kernel/barrier.h" + +#define WRITERS 3 +#define ROUNDS 64 +#define TEST_FILE_NAME "evict_conc.txt" +#define TEST_BYTES 512 + +static int finished = 0; +static int progress = 0; + +struct Barrier barrier; + +struct WriterArg { + int id; +}; + +// Copied over from page cache +static struct PageCacheEntry* page_cache_lookup(struct PageCache* cache, struct Node* node, unsigned offset) { + unsigned hash = ((unsigned)(node->cached) ^ offset) % cache->hash_map_size; + struct PageCacheEntry* entry = cache->hash_map[hash]; + // iterate linked list until we find a match + while (entry) { + if (entry->key.inode == node->cached && entry->key.offset == offset) { + return entry; + } + entry = entry->next; + } + return NULL; +} + +static void expect_file_contents(char* file_bytes) { + for (int row = 0; row < WRITERS; ++row) { + for (int i = 0; i < ROUNDS - 1; ++i) { + char expected = 'a' + (i % 26); + char got = file_bytes[(row * ROUNDS) + i]; + if (got != expected) { + int args[4] = {row, i, (int)got, (int)expected}; + say("***vmem eviction concurrency FAIL row=%d offset=%d got=0x%X expected=0x%X\n", args); + } + } + + char got = file_bytes[(row * ROUNDS) + (ROUNDS - 1)]; + if (got != '\n') { + int args[3] = {row, ROUNDS - 1, (int)got}; + say("***vmem eviction concurrency FAIL row=%d offset=%d got=0x%X expected=0xA\n", args); + panic("vmem eviction concurrency: missing row terminator\n"); + } + } +} + +static void writer_thread(void* arg) { + struct WriterArg* a = (struct WriterArg*)arg; + int id = a->id; + struct Node* file = node_find(&fs.root, TEST_FILE_NAME); + assert(file != NULL, "evict conc: failed to open fixture file\n"); + assert(file->cached != NULL, "NULL CACHED INODE\n"); + char* mapping = mmap(TEST_BYTES, file, 0, MMAP_READ | MMAP_WRITE | MMAP_SHARED); + node_free(file); + assert(mapping != NULL, "evict conc: mmap returned NULL\n"); + + barrier_sync(&barrier); + + for (int r = 0; r < ROUNDS - 1; ++r) { + // write one byte slot owned by this writer each round + unsigned offset = (id * ROUNDS) + r; // disjoint low-order bytes + mapping[offset] = 97 + (r % 26); + + __atomic_fetch_add(&progress, 1); + + // widen the race window; give eviction a chance to run + if ((r & 3) == 0) + yield(); + } + mapping[(id * ROUNDS) + (ROUNDS - 1)] = '\n'; + __atomic_fetch_add(&finished, 1); + + int args[1] = {id}; + say("writer %d finished\n", args); +} + +static void evictor_thread(void* _arg) { + // Evictor runs on one core and continuously finds the cached page and evicts + struct Node* file = node_find(&fs.root, TEST_FILE_NAME); + assert(file != NULL, "evict conc: failed to open fixture file\n"); + + while (finished < WRITERS) { + blocking_lock_acquire(&page_cache.lock); + // acquire page cache entry to find the page frame (if present) + struct PageCacheEntry* entry = page_cache_lookup(&page_cache, file, 0); // NOTE this is only safe because we're the only thing that can evict + blocking_lock_release(&page_cache.lock); + if (entry) { + say("z\n", NULL); + struct Page* page = get_page(entry->page_data, "get page - concurrency test"); + if (!(page->flags & PG_PINNED)) { + physmem_page_lock(page); + if (!(page->flags & PG_PINNED)) { + page_evict(page); + } else { + physmem_page_unlock(page); + } + } + } else { + yield(); + } + yield(); + } + + __atomic_fetch_add(&finished, 1); + node_free(file); + say("evictor finished\n", NULL); +} + +void kernel_main(void) { + say("***vmem eviction concurrency test start\n", NULL); + + struct Node* file = node_make_file(&fs.root, TEST_FILE_NAME); + assert(file != NULL, "evict conc: failed to create fixture file\n"); + + // spawn writers + barrier_init(&barrier, WRITERS); + for (int i = 0; i < WRITERS; ++i) { + struct WriterArg* a = malloc(sizeof(*a)); + a->id = i; + struct Fun* worker = malloc(sizeof(*worker)); + worker->func = writer_thread; + worker->arg = a; + thread(worker); + } + + // spawn evictor + struct Fun* evictor = malloc(sizeof(*evictor)); + evictor->func = evictor_thread; + evictor->arg = NULL; + thread(evictor); + + // wait for writers to finish + while (__atomic_load_n(&finished) != WRITERS + 1) { + sleep(50); + int args[2] = {progress, WRITERS * ROUNDS - WRITERS}; + say("progress: %d / %d\n", args); + } + + // verify backing file contains last writer values + char* file_bytes = mmap(TEST_BYTES, file, 0, MMAP_READ); + assert(file_bytes != NULL, "evict conc: mmap for verification returned NULL\n"); + expect_file_contents(file_bytes); + + node_free(file); + say("***vmem eviction concurrency test complete\n", NULL); +} diff --git a/tests/vmem_eviction_concurrency.dir/nothing.txt b/tests/vmem_eviction_concurrency.dir/nothing.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/vmem_eviction_concurrency.ok b/tests/vmem_eviction_concurrency.ok new file mode 100644 index 0000000..4b6c633 --- /dev/null +++ b/tests/vmem_eviction_concurrency.ok @@ -0,0 +1,2 @@ +***vmem eviction concurrency test start +***vmem eviction concurrency test complete diff --git a/tests/vmem_eviction_simple.c b/tests/vmem_eviction_simple.c new file mode 100644 index 0000000..5f38001 --- /dev/null +++ b/tests/vmem_eviction_simple.c @@ -0,0 +1,142 @@ +/* + * Concurrent shared file-backed eviction / TLB shootdown test. + * + * Validates: + * - multiple cores can map the same shared file page + * - page_evict() removes the live mapping for every thread holding that page + * - a stale TLB entry does not survive eviction and let a core keep reading + * the old physical page + * - after eviction, a reread of the same virtual address refaults from the + * backing file and sees the file's new contents + * + * How: + * - all threads map the same file-backed shared page and confirm an initial + * baseline string + * - the main thread locks the underlying page, calls page_evict(), then + * rewrites the backing file with a different string + * - every thread rereads the same virtual address; if TLB shootdown is wrong, + * a core can keep seeing the old page contents instead of faulting back in + * the rewritten file bytes + */ + +#include "../kernel/vmem.h" +#include "../kernel/ext.h" +#include "../kernel/threads.h" +#include "../kernel/barrier.h" +#include "../kernel/heap.h" +#include "../kernel/print.h" +#include "../kernel/debug.h" +#include "../kernel/physmem.h" +#include "../kernel/machine.h" + +#define WORKER_COUNT 3 +#define TEST_FILE_NAME "evict.txt" +#define TEST_BYTES 16 + +#define OLD_TEXT "OLD-COHERENCY-1!" +#define NEW_TEXT "NEW-COHERENCY-2!" + +static struct Barrier phase_barrier; +static int finished = 0; + +struct WorkerArg { + int id; +}; + +static void expect_bytes(char* got, char* expected, int thread_id, int phase) { + for (unsigned i = 0; i < TEST_BYTES; ++i) { + if (got[i] != expected[i]) { + int args[4] = {thread_id, phase, (int)i, ((int)got[i] << 8) | (unsigned char)expected[i]}; + say("***vmem evict tlb FAIL id=%d phase=%d offset=%d pair=0x%X\n", args); + panic("vmem evict tlb: mapping contents mismatch\n"); + } + } +} + +static struct Page* mapped_page(char* mapping) { + unsigned* pte = vmem_get_pte(get_pid(), (unsigned)mapping, false); + assert(pte != NULL, "vmem evict tlb: failed to look up PTE\n"); + assert(*pte & VMEM_VALID, "vmem evict tlb: expected mapping to be valid\n"); + return get_page(pte_phys_addr(*pte), "get page - eviction test"); +} + +static char* map_fixture_page(void) { + struct Node* file = node_find(&fs.root, TEST_FILE_NAME); + assert(file != NULL, "vmem evict tlb: failed to open fixture file\n"); + + char* mapping = mmap(TEST_BYTES, file, 0, MMAP_READ | MMAP_WRITE | MMAP_SHARED); + assert(mapping != NULL, "vmem evict tlb: mmap returned NULL\n"); + node_free(file); + return mapping; +} + +static void shared_evict_worker(void* arg) { + struct WorkerArg* worker = (struct WorkerArg*)arg; + int id = worker->id; + char* mapping = map_fixture_page(); + + expect_bytes(mapping, (char*)OLD_TEXT, id, 0); + // Phase 0: all threads have faulted in the original shared page. + barrier_sync(&phase_barrier); + + // Phase 1: wait for kernel_main() to evict the page and rewrite the file. + barrier_sync(&phase_barrier); + expect_bytes(mapping, (char*)NEW_TEXT, id, 1); + yield(); + expect_bytes(mapping, (char*)NEW_TEXT, id, 2); + + // Phase 2: all threads have observed the refaulted page contents. + barrier_sync(&phase_barrier); + munmap(mapping); + + __atomic_fetch_add(&finished, 1); +} + +void kernel_main(void) { + say("***vmem eviction simple test start\n", NULL); + + barrier_init(&phase_barrier, WORKER_COUNT + 1); + for (int i = 0; i < WORKER_COUNT; ++i) { + struct WorkerArg* arg = malloc(sizeof(struct WorkerArg)); + assert(arg != NULL, "vmem evict tlb: failed to allocate worker args\n"); + arg->id = i; + + struct Fun* fun = malloc(sizeof(struct Fun)); + assert(fun != NULL, "vmem evict tlb: failed to allocate thread metadata\n"); + fun->func = shared_evict_worker; + fun->arg = arg; + thread(fun); + } + + char* mapping = map_fixture_page(); + expect_bytes(mapping, (char*)OLD_TEXT, -1, 0); + + // Phase 0: workers have faulted in the original page and are paused. + barrier_sync(&phase_barrier); + + struct Page* page = mapped_page(mapping); + physmem_page_lock(page); + page_evict(page); // Definitely not pinned because writers have already faulted in the page + + struct Node* file = node_find(&fs.root, TEST_FILE_NAME); + assert(file != NULL, "vmem evict tlb: failed to reopen fixture file\n"); + unsigned wrote = node_write_all(file, 0, TEST_BYTES, (char*)NEW_TEXT); + assert(wrote == TEST_BYTES, "vmem evict tlb: failed to rewrite file bytes\n"); + node_free(file); + expect_bytes(mapping, (char*)NEW_TEXT, -1, 1); + + // Phase 1: release workers only after eviction and rewrite have completed. + barrier_sync(&phase_barrier); + + // Phase 2: all threads have observed the refaulted page contents. + barrier_sync(&phase_barrier); + munmap(mapping); + + while (__atomic_load_n(&finished) != WORKER_COUNT) { + yield(); + } + + barrier_destroy(&phase_barrier); + + say("***vmem eviction simple test complete\n", NULL); +} \ No newline at end of file diff --git a/tests/vmem_eviction_simple.dir/evict.txt b/tests/vmem_eviction_simple.dir/evict.txt new file mode 100644 index 0000000..083b1b8 --- /dev/null +++ b/tests/vmem_eviction_simple.dir/evict.txt @@ -0,0 +1 @@ +OLD-COHERENCY-1! \ No newline at end of file diff --git a/tests/vmem_eviction_simple.ok b/tests/vmem_eviction_simple.ok new file mode 100644 index 0000000..bae57aa --- /dev/null +++ b/tests/vmem_eviction_simple.ok @@ -0,0 +1,2 @@ +***vmem eviction simple test start +***vmem eviction simple test complete diff --git a/tests/vmem_private_file.c b/tests/vmem_private_file.c index db570dc..08d1aa2 100644 --- a/tests/vmem_private_file.c +++ b/tests/vmem_private_file.c @@ -30,17 +30,18 @@ #include "../kernel/print.h" #include "../kernel/debug.h" #include "../kernel/string.h" +#include "../kernel/page_cache.h" #define WORKER_COUNT 4 -#define ROUNDS 4 +#define ROUNDS 4 #define TEST_FILE_NAME "hello.txt" // File-backed mmap offsets are page-aligned; use the second 4 KiB page so the // first-page sentinel catches any offset-handling bug immediately. -#define TEST_FILE_OFFSET 4096 -#define TEST_FILE_SIZE 4107 +#define TEST_FILE_OFFSET 4096 +#define TEST_FILE_SIZE 4107 #define PRIVATE_FILE_BYTES 11 -#define PRIVATE_BASE_TEXT "PRIVATEmap\n" +#define PRIVATE_BASE_TEXT "PRIVATEmap\n" static struct Barrier phase_barrier; static int finished = 0; @@ -56,7 +57,7 @@ static char private_worker_byte(int id, int round) { static void read_file_bytes(char* dest) { struct Node* file = node_find(&fs.root, TEST_FILE_NAME); assert(file != NULL, - "vmem private file thread: failed to reopen fixture file.\n"); + "vmem private file thread: failed to reopen fixture file.\n"); unsigned size = node_size_in_bytes(file); if (size != TEST_FILE_SIZE) { @@ -76,16 +77,15 @@ static void read_file_bytes(char* dest) { } static void expect_bytes(char* got, char* expected, int worker_id, - int round, int phase) { + int round, int phase) { for (unsigned i = 0; i < PRIVATE_FILE_BYTES; ++i) { if (got[i] != expected[i]) { int args[5] = { - worker_id, - round, - phase, - (int)i, - ((int)got[i] << 8) | (unsigned char)expected[i] - }; + worker_id, + round, + phase, + (int)i, + ((int)got[i] << 8) | (unsigned char)expected[i]}; say("***vmem private file thread FAIL id=%d round=%d phase=%d offset=%d pair=0x%X\n", args); panic("vmem private file thread: byte contents mismatch.\n"); } @@ -105,12 +105,12 @@ static void private_file_worker(void* arg) { for (int round = 0; round < ROUNDS; ++round) { struct Node* file = node_find(&fs.root, TEST_FILE_NAME); assert(file != NULL, - "vmem private file thread: worker failed to open fixture file.\n"); + "vmem private file thread: worker failed to open fixture file.\n"); char* mapping = mmap(PRIVATE_FILE_BYTES, file, TEST_FILE_OFFSET, - MMAP_READ | MMAP_WRITE); + MMAP_READ | MMAP_WRITE); assert(mapping != NULL, - "vmem private file thread: mmap returned NULL.\n"); + "vmem private file thread: mmap returned NULL.\n"); node_free(file); memcpy(expected, (void*)PRIVATE_BASE_TEXT, PRIVATE_FILE_BYTES); @@ -146,12 +146,12 @@ void kernel_main(void) { for (int i = 0; i < WORKER_COUNT; ++i) { struct WorkerArg* arg = malloc(sizeof(struct WorkerArg)); assert(arg != NULL, - "vmem private file thread: failed to allocate worker args.\n"); + "vmem private file thread: failed to allocate worker args.\n"); arg->id = i; struct Fun* fun = malloc(sizeof(struct Fun)); assert(fun != NULL, - "vmem private file thread: failed to allocate thread metadata.\n"); + "vmem private file thread: failed to allocate thread metadata.\n"); fun->func = private_file_worker; fun->arg = arg; thread(fun); @@ -161,7 +161,8 @@ void kernel_main(void) { for (int round = 0; round < ROUNDS; ++round) { barrier_sync(&phase_barrier); barrier_sync(&phase_barrier); - + + page_cache_flush_all(&page_cache); read_file_bytes(file_bytes); expect_bytes(file_bytes, (char*)PRIVATE_BASE_TEXT, WORKER_COUNT, round, 2); @@ -169,6 +170,7 @@ void kernel_main(void) { barrier_sync(&phase_barrier); read_file_bytes(file_bytes); + page_cache_flush_all(&page_cache); expect_bytes(file_bytes, (char*)PRIVATE_BASE_TEXT, WORKER_COUNT, round, 3); } diff --git a/tests/vmem_shared_file.c b/tests/vmem_shared_file.c index 6b373a4..a381441 100644 --- a/tests/vmem_shared_file.c +++ b/tests/vmem_shared_file.c @@ -29,17 +29,18 @@ #include "../kernel/print.h" #include "../kernel/debug.h" #include "../kernel/string.h" +#include "../kernel/page_cache.h" #define WORKER_COUNT 4 -#define ROUNDS 4 +#define ROUNDS 4 #define TEST_FILE_NAME "hello.txt" // File-backed mmap offsets are page-aligned; use the second 4 KiB page so the // first-page sentinel catches any offset-handling bug immediately. -#define TEST_FILE_OFFSET 4096 -#define TEST_FILE_SIZE 4106 +#define TEST_FILE_OFFSET 4096 +#define TEST_FILE_SIZE 4106 #define SHARED_FILE_BYTES 10 -#define SHARED_BASE_TEXT "SHAREDmap\n" +#define SHARED_BASE_TEXT "SHAREDmap\n" static struct Barrier phase_barrier; static int finished = 0; @@ -66,7 +67,7 @@ static void build_shared_expected(char* dest, int round) { static void read_file_bytes(char* dest) { struct Node* file = node_find(&fs.root, TEST_FILE_NAME); assert(file != NULL, - "vmem shared file thread: failed to reopen fixture file.\n"); + "vmem shared file thread: failed to reopen fixture file.\n"); unsigned size = node_size_in_bytes(file); if (size != TEST_FILE_SIZE) { @@ -86,16 +87,15 @@ static void read_file_bytes(char* dest) { } static void expect_bytes(char* got, char* expected, int worker_id, - int round, int phase) { + int round, int phase) { for (unsigned i = 0; i < SHARED_FILE_BYTES; ++i) { if (got[i] != expected[i]) { int args[5] = { - worker_id, - round, - phase, - (int)i, - ((int)got[i] << 8) | (unsigned char)expected[i] - }; + worker_id, + round, + phase, + (int)i, + ((int)got[i] << 8) | (unsigned char)expected[i]}; say("***vmem shared file thread FAIL id=%d round=%d phase=%d offset=%d pair=0x%X\n", args); panic("vmem shared file thread: byte contents mismatch.\n"); } @@ -110,34 +110,35 @@ static void shared_file_worker(void* arg) { for (int round = 0; round < ROUNDS; ++round) { struct Node* file = node_find(&fs.root, TEST_FILE_NAME); assert(file != NULL, - "vmem shared file thread: worker failed to open fixture file.\n"); + "vmem shared file thread: worker failed to open fixture file.\n"); char* mapping = mmap(SHARED_FILE_BYTES, file, TEST_FILE_OFFSET, - MMAP_READ | MMAP_WRITE | MMAP_SHARED); + MMAP_READ | MMAP_WRITE | MMAP_SHARED); assert(mapping != NULL, - "vmem shared file thread: mmap returned NULL.\n"); + "vmem shared file thread: mmap returned NULL.\n"); node_free(file); build_shared_expected(expected, round - 1); expect_bytes(mapping, expected, id, round, 0); - barrier_sync(&phase_barrier); + barrier_sync(&phase_barrier); // Phase 0.0 mapping[id] = shared_worker_byte(id, round); if (((id + round) & 1) == 1) { yield(); } - barrier_sync(&phase_barrier); + barrier_sync(&phase_barrier); // Phase 0.1 build_shared_expected(expected, round); expect_bytes(mapping, expected, id, round, 1); - barrier_sync(&phase_barrier); + barrier_sync(&phase_barrier); // Phase 1.0 munmap(mapping); - barrier_sync(&phase_barrier); + barrier_sync(&phase_barrier); // Phase 1.1 + barrier_sync(&phase_barrier); // Phase 2.0 } __atomic_fetch_add(&finished, 1); @@ -151,12 +152,12 @@ void kernel_main(void) { for (int i = 0; i < WORKER_COUNT; ++i) { struct WorkerArg* arg = malloc(sizeof(struct WorkerArg)); assert(arg != NULL, - "vmem shared file thread: failed to allocate worker args.\n"); + "vmem shared file thread: failed to allocate worker args.\n"); arg->id = i; struct Fun* fun = malloc(sizeof(struct Fun)); assert(fun != NULL, - "vmem shared file thread: failed to allocate thread metadata.\n"); + "vmem shared file thread: failed to allocate thread metadata.\n"); fun->func = shared_file_worker; fun->arg = arg; thread(fun); @@ -165,14 +166,18 @@ void kernel_main(void) { char file_bytes[SHARED_FILE_BYTES]; char expected[SHARED_FILE_BYTES]; for (int round = 0; round < ROUNDS; ++round) { - barrier_sync(&phase_barrier); - barrier_sync(&phase_barrier); - barrier_sync(&phase_barrier); - barrier_sync(&phase_barrier); + barrier_sync(&phase_barrier); // Phase 0.0 + barrier_sync(&phase_barrier); // Phase 0.1 + barrier_sync(&phase_barrier); // Phase 1.0 + barrier_sync(&phase_barrier); // Phase 1.1 build_shared_expected(expected, round); + page_cache_flush_all(&page_cache); read_file_bytes(file_bytes); expect_bytes(file_bytes, expected, WORKER_COUNT, round, 2); + barrier_sync(&phase_barrier); // Phase 2.0 + int args[1] = {round}; + say("Finished round %x\n", args); } while (__atomic_load_n(&finished) != WORKER_COUNT) { diff --git a/tests/vmem_simple.c b/tests/vmem_simple.c index 753823f..1dd0362 100644 --- a/tests/vmem_simple.c +++ b/tests/vmem_simple.c @@ -7,7 +7,7 @@ #include "../kernel/physmem.h" #include "../kernel/ext.h" -void private_anonymous_test(void){ +void private_anonymous_test(void) { int* p = mmap(FRAME_SIZE, NULL, 0, MMAP_READ | MMAP_WRITE); say("*** mmap'd a page at virtual address 0x%X\n", &p); @@ -21,7 +21,7 @@ void private_anonymous_test(void){ say("*** munmap'd the page\n", NULL); } -void private_file_backed_test(void){ +void private_file_backed_test(void) { struct Node* file = node_find(&fs.root, "hello.txt"); assert(file != NULL, "could not find hello.txt in ext2 filesystem\n"); @@ -39,12 +39,12 @@ void private_file_backed_test(void){ say("*** munmap'd the file-backed page\n", NULL); } -void shared_anonymous_test(void){ +void shared_anonymous_test(void) { // might be hard to test without processes say("*** TODO: implement shared anonymous mmap test\n", NULL); } -void shared_file_backed_test(void){ +void shared_file_backed_test(void) { struct Node* file = node_find(&fs.root, "hello.txt"); assert(file != NULL, "could not find hello.txt in ext2 filesystem\n"); @@ -53,8 +53,12 @@ void shared_file_backed_test(void){ say("*** contents of hello.txt: %s\n", &p); + struct Page* page = get_page(pte_phys_addr(*vmem_get_pte(get_pid(), (unsigned)p, false)), "get page - simple test"); + assert(!(page->flags & PG_DIRTY), "Page should not be dirty\n"); + p[6] = '!'; say("*** modified contents of hello.txt: %s\n", &p); + assert(page->flags & PG_DIRTY, "Page should be dirty\n"); munmap(p); say("*** munmap'd the file-backed page\n", NULL); diff --git a/tests/vmem_simple.ok b/tests/vmem_simple.ok new file mode 100644 index 0000000..2c393d8 --- /dev/null +++ b/tests/vmem_simple.ok @@ -0,0 +1,19 @@ +***Hello from vmem_simple test! +***Running private anonymous mmap test... +*** mmap'd a page at virtual address 0x10000000 +*** wrote 42 to first int of mmap'd page +*** read 42 from first int of mmap'd page +*** munmap'd the page +***Running private file-backed mmap test... +*** mmap'd a file-backed page at virtual address 0x10000000 +*** contents of hello.txt: Hello! +*** modified contents of hello.txt: Hello!! +*** munmap'd the file-backed page +***Running shared anonymous mmap test... +*** TODO: implement shared anonymous mmap test +***Running shared file-backed mmap test... +*** mmap'd a file-backed page at virtual address 0x10000000 +*** contents of hello.txt: Hello! +*** modified contents of hello.txt: Hello!! +*** munmap'd the file-backed page +***vmem_simple test complete!