diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 768269b..a291160 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,9 +32,10 @@ test_userprog: extends: .pintos_tests variables: DIR: userprog + IGNORE: (tests/userprog/no-vm/multi-oom) test_vm: extends: .pintos_tests variables: DIR: vm - IGNORE: (tests/vm/pt-grow-stack|tests/vm/pt-grow-pusha|tests/vm/pt-big-stk-obj|tests/vm/pt-overflowstk|tests/vm/pt-write-code2|tests/vm/pt-grow-stk-sc|tests/vm/page-linear|tests/vm/page-parallel|tests/vm/page-merge-seq|tests/vm/page-merge-par|tests/vm/page-merge-stk|tests/vm/page-merge-mm|tests/vm/mmap-read|tests/vm/mmap-close|tests/vm/mmap-overlap|tests/vm/mmap-twice|tests/vm/mmap-write|tests/vm/mmap-exit|tests/vm/mmap-shuffle|tests/vm/mmap-clean|tests/vm/mmap-inherit|tests/vm/mmap-misalign|tests/vm/mmap-null|tests/vm/mmap-over-code|tests/vm/mmap-over-data|tests/vm/mmap-over-stk|tests/vm/mmap-remove) + IGNORE: (tests/vm/pt-grow-stack|tests/vm/pt-grow-pusha|tests/vm/pt-big-stk-obj|tests/vm/pt-overflowstk|tests/vm/pt-write-code2|tests/vm/pt-grow-stk-sc|tests/vm/page-linear|tests/vm/page-parallel|tests/vm/page-merge-seq|tests/vm/page-merge-par|tests/vm/page-merge-stk|tests/vm/page-merge-mm|tests/vm/mmap-over-stk) diff --git a/src/Makefile.build b/src/Makefile.build index 7778f57..12419be 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -64,7 +64,9 @@ userprog_SRC += userprog/tss.c # TSS management. # Virtual memory code. vm_SRC += vm/frame.c # Frame table manager. vm_SRC += vm/page.c # Page table manager. +vm_SRC += vm/mmap.c # Memory-mapped files. vm_SRC += devices/swap.c # Swap block manager. +#vm_SRC = vm/file.c # Some other file. # Filesystem code. filesys_SRC = filesys/filesys.c # Filesystem core. diff --git a/src/filesys/inode.c b/src/filesys/inode.c index 3463563..ec36af3 100644 --- a/src/filesys/inode.c +++ b/src/filesys/inode.c @@ -1,6 +1,7 @@ #include "filesys/inode.h" #include #include +#include #include #include #include "filesys/filesys.h" diff --git a/src/threads/init.c b/src/threads/init.c index 69ac6af..c507d53 100644 --- a/src/threads/init.c +++ b/src/threads/init.c @@ -33,6 +33,7 @@ #endif #ifdef VM #include "vm/frame.h" +#include "vm/page.h" #include "devices/swap.h" #endif #ifdef FILESYS @@ -104,6 +105,7 @@ main (void) paging_init (); #ifdef VM frame_init (); + shared_file_pages_init (); #endif /* Segmentation. */ diff --git a/src/threads/thread.c b/src/threads/thread.c index 91a12b5..5af7da3 100644 --- a/src/threads/thread.c +++ b/src/threads/thread.c @@ -18,6 +18,10 @@ #ifdef USERPROG #include "userprog/process.h" #include "userprog/syscall.h" +#include "vm/page.h" +#endif +#ifdef VM +#include "vm/mmap.h" #endif /* Random value for struct thread's `magic' member. @@ -261,10 +265,24 @@ thread_create (const char *name, int priority, #ifdef USERPROG /* Initialize the thread's file descriptor table. */ t->fd_counter = MINIMUM_USER_FD; + bool success = hash_init (&t->open_files, fd_hash, fd_less, NULL); + if (success) + { + success = hash_init (&t->child_results, process_result_hash, + process_result_less, t); + if (!success) + hash_destroy (&t->open_files, NULL); +#ifdef VM + else + { + success = init_pages (&t->pages); + if (!success) + hash_destroy (&t->child_results, NULL); + } +#endif + } - if (!hash_init (&t->open_files, fd_hash, fd_less, NULL) - || !hash_init (&t->child_results, process_result_hash, - process_result_less, t)) + if (!success) { palloc_free_page (t); free (t->result); @@ -272,6 +290,10 @@ thread_create (const char *name, int priority, } #endif +#ifdef VM + mmap_init (t); +#endif + /* Prepare thread for first run by initializing its stack. Do this atomically so intermediate values for the 'stack' member cannot be observed. */ diff --git a/src/threads/thread.h b/src/threads/thread.h index 4a88577..fe7f362 100644 --- a/src/threads/thread.h +++ b/src/threads/thread.h @@ -135,6 +135,12 @@ struct thread /* Shared between thread.c and synch.c. */ struct list_elem elem; /* List element. */ + struct hash pages; /* Table of open user pages. */ + + /* Memory mapped files for user virtual memory. */ + struct hash mmap_files; /* List of memory mapped files. */ + unsigned int mmap_counter; /* Counter for memory mapped files. */ + #ifdef USERPROG /* Owned by userprog/process.c. */ uint32_t *pagedir; /* Page directory. */ @@ -143,6 +149,8 @@ struct thread struct hash open_files; /* Hash Table of FD -> Struct File. */ #endif + void *curr_esp; + /* Owned by thread.c. */ unsigned magic; /* Detects stack overflow. */ }; diff --git a/src/userprog/Make.vars b/src/userprog/Make.vars index e4dbb08..23bae3d 100644 --- a/src/userprog/Make.vars +++ b/src/userprog/Make.vars @@ -1,7 +1,7 @@ # -*- makefile -*- -kernel.bin: DEFINES = -DUSERPROG -DFILESYS -KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys +kernel.bin: DEFINES = -DUSERPROG -DFILESYS -DVM +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys vm TEST_SUBDIRS = tests/userprog tests/userprog/no-vm tests/filesys/base GRADING_FILE = $(SRCDIR)/tests/userprog/Grading SIMULATOR = --qemu diff --git a/src/userprog/exception.c b/src/userprog/exception.c index 0a20b53..db07db9 100644 --- a/src/userprog/exception.c +++ b/src/userprog/exception.c @@ -1,9 +1,20 @@ #include "userprog/exception.h" #include #include +#include "stdbool.h" #include "userprog/gdt.h" #include "threads/interrupt.h" #include "threads/thread.h" +#ifdef VM +#include "vm/frame.h" +#include "vm/page.h" +#include "devices/swap.h" +#include "threads/vaddr.h" +#include "userprog/pagedir.h" +#endif + +#define MAX_STACK_SIZE (8 * 1024 * 1024) // 8MB +#define MAX_STACK_OFFSET 32 // 32 bytes offset below stack pointer (ESP) /* Number of page faults processed. */ static long long page_fault_cnt; @@ -11,6 +22,10 @@ static long long page_fault_cnt; static void kill (struct intr_frame *); static void page_fault (struct intr_frame *); +static bool is_valid_stack_access (const void *fault_addr, const void *esp); +static bool grow_stack (void *upage); +bool fetch_page (void *upage, bool write); + /* Registers handlers for interrupts that can be caused by user programs. @@ -145,6 +160,34 @@ page_fault (struct intr_frame *f) write = (f->error_code & PF_W) != 0; user = (f->error_code & PF_U) != 0; + /* Select the appropriate stack pointer based on the context of the fault. */ + void *esp = user ? f->esp : thread_current()->curr_esp; + + /* If the fault address is in a user page that is not present, then it might + be just that the stack needs to grow or that it needs to be lazily loaded. + So we attempt to grow the stack. If this does not work, we check our SPT to + see if the page is expected to have data loaded in memory. */ + void *upage = pg_round_down (fault_addr); + if (not_present && is_user_vaddr (upage) && upage != NULL) + { + if (fetch_page (upage, write)) + return; + + if (is_valid_stack_access (fault_addr, esp)) + if (grow_stack (upage)) + return; + } + + /* If the page fault occurred in kernel mode, then we intentionally indicate + a fault (for get_user() etc). */ + if (!user) + { + f->eip = (void *)f->eax; + f->eax = 0xffffffff; + return; + } + + /* To implement virtual memory, delete the rest of the function body, and replace it with code that brings in the page to which fault_addr refers. */ @@ -156,3 +199,107 @@ page_fault (struct intr_frame *f) kill (f); } +/* Validates whether the fault address is a valid stack access. Access is a + valid stack access under the following two conditions: + 1. The fault address must be within MAX_STACK_OFFSET (32) bytes below + the current stack pointer. (Accounts for both PUSH and PUSHA instructions) + 2. Growing this stack to this address does not cause it to exceed the + MAX_STACK_SIZE (8MB) limit. + + Returns true if both conditions are met, false otherwise. + + Pre: fault_addr is a valid user virtual address (so also not NULL). */ +static bool +is_valid_stack_access (const void *fault_addr, const void *esp) +{ + uint32_t new_stack_size = PHYS_BASE - pg_round_down (fault_addr); + + uint32_t *lowest_valid_push_addr = (uint32_t *)esp - MAX_STACK_OFFSET; + bool is_within_push_range = (uint32_t *)fault_addr >= lowest_valid_push_addr; + + return is_within_push_range && new_stack_size <= MAX_STACK_SIZE; +} + +/* Attempts to grow the stack by allocating and mapping a new page. + This involves: + 1. Allocating a zeroed page from the user pool + 2. Installing it into the page table with write permissions + + Returns true if the stack was successfully grown, false if either + allocation or installation fails. + + Pre: upage is a valid page-aligned address (so also not NULL). */ +static bool +grow_stack (void *upage) +{ + /* Allocate new page for stack */ + void *new_page = frame_alloc (PAL_ZERO, upage, thread_current ()); + if (new_page == NULL) + return false; + + /* Install the page into user page table */ + if (!pagedir_set_page (thread_current ()->pagedir, upage, new_page, true)) + { + frame_free (new_page); + return false; + } + + return true; +} + +bool +fetch_page (void *upage, bool write) +{ + /* Check if the page is in the supplemental page table. That is, it is a page + that is expected to be in memory. */ + struct page_entry *page = page_get (thread_current (), upage); + if (page == NULL) + return false; + + /* Check if the non-present user page is in the swap partition. + If so, swap it back into main memory, updating the PTE for + the faulted virtual address to point to the newly allocated + frame. */ + struct thread *t = thread_current (); + if (page_in_swap (t, upage)) + { + /* NOTE: This code should be refactored and moved into helper functions + within 'page.c'.*/ + void *kpage = frame_alloc (0, upage, t); + lock_acquire (&page->lock); + + size_t swap_slot = page_get_swap (t, upage); + swap_in (kpage, swap_slot); + + lock_release (&page->lock); + + bool writeable = pagedir_is_writable (t->pagedir, upage); + + /* TODO: When this returns false we should quit the page fault, + but currently we continue and check the stack conditions in the + page fault handler. */ + return pagedir_set_page (t->pagedir, upage, kpage, writeable); + } + + /* An attempt to write to a non-writeable should fail. */ + if (write && !page->writable) + return false; + + /* Load the page into memory based on the type of data it is expecting. */ + bool success = false; + switch (page->type) { + case PAGE_MMAP: + case PAGE_FILE: + case PAGE_SHARED: + success = page_load_file (page); + break; + default: + return false; + } + + if (success && page->writable && + !pagedir_is_writable(thread_current()->pagedir, upage)) + pagedir_set_writable(thread_current()->pagedir, upage, true); + + return success; +} diff --git a/src/userprog/exception.h b/src/userprog/exception.h index f83e615..663db4b 100644 --- a/src/userprog/exception.h +++ b/src/userprog/exception.h @@ -1,6 +1,8 @@ #ifndef USERPROG_EXCEPTION_H #define USERPROG_EXCEPTION_H +#include + /* Page fault error code bits that describe the cause of the exception. */ #define PF_P 0x1 /* 0: not-present page. 1: access rights violation. */ #define PF_W 0x2 /* 0: read, 1: write. */ @@ -8,5 +10,7 @@ void exception_init (void); void exception_print_stats (void); +bool +try_fetch_page (void *upage, bool write); #endif /* userprog/exception.h */ diff --git a/src/userprog/pagedir.c b/src/userprog/pagedir.c index ef5bbff..1d79b38 100644 --- a/src/userprog/pagedir.c +++ b/src/userprog/pagedir.c @@ -2,12 +2,14 @@ #include #include #include +#include "devices/swap.h" #include "threads/init.h" #include "threads/pte.h" #include "threads/palloc.h" +#include "vm/frame.h" +#include "vm/page.h" static uint32_t *active_pd (void); -static void invalidate_pagedir (uint32_t *); /* Creates a new page directory that has mappings for kernel virtual addresses, but none for user virtual addresses. @@ -40,8 +42,14 @@ pagedir_destroy (uint32_t *pd) uint32_t *pte; for (pte = pt; pte < pt + PGSIZE / sizeof *pte; pte++) - if (*pte & PTE_P) - palloc_free_page (pte_get_page (*pte)); + { + if (page_is_shared_pte (pte)) + continue; + else if (page_in_swap_pte (pte)) + swap_drop (page_get_swap_pte (pte)); + else if (*pte & PTE_P) + frame_free (pte_get_page (*pte)); + } palloc_free_page (pt); } palloc_free_page (pd); @@ -53,7 +61,7 @@ pagedir_destroy (uint32_t *pd) on CREATE. If CREATE is true, then a new page table is created and a pointer into it is returned. Otherwise, a null pointer is returned. */ -static uint32_t * +uint32_t * lookup_page (uint32_t *pd, const void *vaddr, bool create) { uint32_t *pt, *pde; @@ -278,7 +286,7 @@ active_pd (void) This function invalidates the TLB if PD is the active page directory. (If PD is not active then its entries are not in the TLB, so there is no need to invalidate anything.) */ -static void +void invalidate_pagedir (uint32_t *pd) { if (active_pd () == pd) diff --git a/src/userprog/pagedir.h b/src/userprog/pagedir.h index 06e45d2..6b8fd26 100644 --- a/src/userprog/pagedir.h +++ b/src/userprog/pagedir.h @@ -6,6 +6,7 @@ uint32_t *pagedir_create (void); void pagedir_destroy (uint32_t *pd); +uint32_t *lookup_page (uint32_t *pd, const void *vaddr, bool create); bool pagedir_set_page (uint32_t *pd, void *upage, void *kpage, bool rw); void *pagedir_get_page (uint32_t *pd, const void *upage); void pagedir_clear_page (uint32_t *pd, void *upage); @@ -16,5 +17,6 @@ void pagedir_set_accessed (uint32_t *pd, const void *upage, bool accessed); bool pagedir_is_writable (uint32_t *pd, const void *upage); void pagedir_set_writable (uint32_t *pd, const void *upage, bool writable); void pagedir_activate (uint32_t *pd); +void invalidate_pagedir (uint32_t *pd); #endif /* userprog/pagedir.h */ diff --git a/src/userprog/process.c b/src/userprog/process.c index aa5091c..ca99aee 100644 --- a/src/userprog/process.c +++ b/src/userprog/process.c @@ -24,6 +24,8 @@ #include "threads/vaddr.h" #include "threads/synch.h" #include "devices/timer.h" +#include "vm/page.h" +#include "vm/mmap.h" #ifdef VM #include "vm/frame.h" #endif @@ -118,7 +120,7 @@ process_execute (const char *cmd) static void *get_usr_kpage (enum palloc_flags flags, void *upage); static void free_usr_kpage (void *kpage); -static bool install_page (void *upage, void *kpage, bool writable); +bool install_page (void *upage, void *kpage, bool writable); static bool process_init_stack (char *cmd_saveptr, void **esp, char *file_name); static void *push_to_stack (void **esp, void *data, size_t data_size); @@ -362,9 +364,15 @@ process_exit (void) struct thread *cur = thread_current (); uint32_t *pd; + /* Unmap all memory mapped files */ + mmap_destroy (); + /* Clean up all open files */ hash_destroy (&cur->open_files, fd_cleanup); + /* Clean up the thread's supplemental page table. */ + hash_destroy (&cur->pages, page_cleanup); + /* Close the executable file, implicitly allowing it to be written to. */ if (cur->exec_file != NULL) { @@ -621,7 +629,9 @@ load (const char *file_name, void (**eip) (void), void **esp) done: /* We arrive here whether the load is successful or not. */ +#ifndef VM file_close (file); +#endif lock_release (&filesys_lock); return success; } @@ -689,58 +699,29 @@ validate_segment (const struct Elf32_Phdr *phdr, struct file *file) or disk read error occurs. */ static bool load_segment (struct file *file, off_t ofs, uint8_t *upage, - uint32_t read_bytes, uint32_t zero_bytes, bool writable) + uint32_t read_bytes, uint32_t zero_bytes, bool writable) { ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0); ASSERT (pg_ofs (upage) == 0); ASSERT (ofs % PGSIZE == 0); - file_seek (file, ofs); - while (read_bytes > 0 || zero_bytes > 0) + while (read_bytes > 0 || zero_bytes > 0) { /* Calculate how to fill this page. We will read PAGE_READ_BYTES bytes from FILE and zero the final PAGE_ZERO_BYTES bytes. */ size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE; size_t page_zero_bytes = PGSIZE - page_read_bytes; - - /* Check if virtual page already allocated */ - struct thread *t = thread_current (); - uint8_t *kpage = pagedir_get_page (t->pagedir, upage); - - if (kpage == NULL){ - - /* Get a new page of memory. */ - kpage = get_usr_kpage (0, upage); - if (kpage == NULL){ - return false; - } - - /* Add the page to the process's address space. */ - if (!install_page (upage, kpage, writable)) - { - free_usr_kpage (kpage); - return false; - } - - } else { - - /* Check if writable flag for the page should be updated */ - if(writable && !pagedir_is_writable(t->pagedir, upage)){ - pagedir_set_writable(t->pagedir, upage, writable); - } - - } - /* Load data into the page. */ - if (file_read (file, kpage, page_read_bytes) != (int) page_read_bytes){ - return false; - } - memset (kpage + page_read_bytes, 0, page_zero_bytes); + /* Add the page metadata to the SPT to be lazy loaded later on */ + if (page_insert_file (file, ofs, upage, page_read_bytes, page_zero_bytes, + writable, PAGE_FILE) == NULL) + return false; /* Advance. */ read_bytes -= page_read_bytes; zero_bytes -= page_zero_bytes; + ofs += PGSIZE; upage += PGSIZE; } return true; @@ -782,6 +763,7 @@ get_usr_kpage (enum palloc_flags flags, void *upage) return NULL; else page = frame_alloc (flags, upage, t); + pagedir_set_accessed (t->pagedir, upage, true); #else page = palloc_get_page (flags | PAL_USER); #endif @@ -809,7 +791,7 @@ free_usr_kpage (void *kpage) with palloc_get_page(). Returns true on success, false if UPAGE is already mapped or if memory allocation fails. */ -static bool +bool install_page (void *upage, void *kpage, bool writable) { struct thread *t = thread_current (); diff --git a/src/userprog/process.h b/src/userprog/process.h index 688cd2a..7cf3df4 100644 --- a/src/userprog/process.h +++ b/src/userprog/process.h @@ -8,4 +8,6 @@ int process_wait (tid_t); void process_exit (void); void process_activate (void); +bool install_page (void *upage, void *kpage, bool writable); + #endif /* userprog/process.h */ diff --git a/src/userprog/syscall.c b/src/userprog/syscall.c index 3efe7b5..8275d51 100644 --- a/src/userprog/syscall.c +++ b/src/userprog/syscall.c @@ -10,11 +10,16 @@ #include "threads/synch.h" #include "userprog/process.h" #include "userprog/pagedir.h" +#include "vm/frame.h" +#include "vm/page.h" +#include "vm/mmap.h" #include +#include #include #define MAX_SYSCALL_ARGS 3 #define EXIT_FAILURE -1 +#define MMAP_FAILURE -1 struct open_file { @@ -44,10 +49,20 @@ static int syscall_write (int fd, const void *buffer, unsigned size); static void syscall_seek (int fd, unsigned position); static unsigned syscall_tell (int fd); static void syscall_close (int fd); +static mapid_t syscall_mmap (int fd, void *addr); +static void syscall_munmap (mapid_t mapping); static struct open_file *fd_get_file (int fd); -static void validate_user_pointer (const void *start, size_t size); -static void validate_user_string (const char *str); +static void validate_user_ptr (const void *start, size_t size, + bool write); +static void validate_and_pin_user_ptr (const void *start, size_t size, + bool write); +static void validate_and_pin_user_str (const char *ptr); +static void unpin_user_ptr (const void *start, size_t size); + +static void unpin_user_str (const char *ptr); +static int get_user (const uint8_t *); +static bool put_user (uint8_t *, uint8_t); /* A struct defining a syscall_function pointer along with its arity. */ struct syscall_arguments @@ -73,6 +88,8 @@ static const struct syscall_arguments syscall_lookup[] = [SYS_SEEK] = {(syscall_function) syscall_seek, 2}, [SYS_TELL] = {(syscall_function) syscall_tell, 1}, [SYS_CLOSE] = {(syscall_function) syscall_close, 1}, + [SYS_MMAP] = {(syscall_function) syscall_mmap, 2}, + [SYS_MUNMAP] = {(syscall_function) syscall_munmap, 1} }; /* The number of syscall functions (i.e, number of elements) within the @@ -96,8 +113,9 @@ static void syscall_handler (struct intr_frame *f) { /* First, read the system call number from the stack. */ - validate_user_pointer (f->esp, sizeof (uintptr_t)); - uintptr_t syscall_number = *(int *) f->esp; + validate_user_ptr (f->esp, sizeof (uintptr_t), false); + uintptr_t syscall_number = *(int *)f->esp; + thread_current ()->curr_esp = f->esp; /* Ensures the number corresponds to a system call that can be handled. */ if (syscall_number >= LOOKUP_SIZE) @@ -106,12 +124,11 @@ syscall_handler (struct intr_frame *f) struct syscall_arguments syscall = syscall_lookup[syscall_number]; /* Next, read and copy the arguments from the stack pointer. */ - validate_user_pointer (f->esp + sizeof (uintptr_t), - syscall.arity * sizeof (uintptr_t)); - - uintptr_t args[MAX_SYSCALL_ARGS] = {0}; + validate_user_ptr (f->esp + sizeof (uintptr_t), + syscall.arity * sizeof (uintptr_t), false); + uintptr_t args[MAX_SYSCALL_ARGS] = { 0 }; for (int i = 0; i < syscall.arity && i < MAX_SYSCALL_ARGS; i++) - args[i] = *(uintptr_t *) (f->esp + sizeof (uintptr_t) * (i + 1)); + args[i] = *(uintptr_t *)(f->esp + sizeof (uintptr_t) * (i + 1)); /* Call the function that handles this system call with the arguments. When there is a return value it is stored in f->eax. */ @@ -140,10 +157,11 @@ syscall_exit (int status) static pid_t syscall_exec (const char *cmd_line) { - /* Validate the user string before executing the process. */ - validate_user_string (cmd_line); + validate_and_pin_user_str (cmd_line); + pid_t pid = process_execute (cmd_line); + unpin_user_str (cmd_line); - return process_execute (cmd_line); /* Returns the PID of the new process */ + return pid; } /* Handles the syscall of wait. Effectively a wrapper for process_wait as the @@ -160,14 +178,15 @@ syscall_wait (pid_t pid) static bool syscall_create (const char *file, unsigned initial_size) { - /* Validate the user string before creating the file. */ - validate_user_string (file); + validate_and_pin_user_str (file); /* Acquire the file system lock to prevent race conditions. */ lock_acquire (&filesys_lock); bool status = filesys_create (file, initial_size); lock_release (&filesys_lock); + unpin_user_str (file); + /* Return the status of the file creation. */ return status; } @@ -178,14 +197,15 @@ syscall_create (const char *file, unsigned initial_size) static bool syscall_remove (const char *file) { - /* Validate the user string before removing the file. */ - validate_user_string (file); + validate_and_pin_user_str (file); /* Acquire the file system lock to prevent race conditions. */ lock_acquire (&filesys_lock); bool status = filesys_remove (file); lock_release (&filesys_lock); + unpin_user_str (file); + /* Return the status of the file removal. */ return status; } @@ -197,14 +217,15 @@ syscall_remove (const char *file) static int syscall_open (const char *file) { - /* Validate the user string before opening the file. */ - validate_user_string (file); + validate_and_pin_user_str (file); /* Acquire the file system lock to prevent race conditions. */ lock_acquire (&filesys_lock); struct file *ptr = filesys_open (file); lock_release (&filesys_lock); + unpin_user_str (file); + /* If the file could not be opened, return failure. */ if (ptr == NULL) return EXIT_FAILURE; @@ -264,11 +285,11 @@ syscall_read (int fd, void *buffer, unsigned size) if (fd < STDIN_FILENO || fd == STDOUT_FILENO) return EXIT_FAILURE; - /* Validate the user buffer for the provided size before reading. */ - validate_user_pointer (buffer, size); - if (fd == STDIN_FILENO) { + /* Validate the user buffer. */ + validate_user_ptr (buffer, size, true); + /* Reading from the console. */ char *write_buffer = buffer; for (unsigned i = 0; i < size; i++) @@ -286,13 +307,19 @@ syscall_read (int fd, void *buffer, unsigned size) if (file_info == NULL) return EXIT_FAILURE; + /* Validate the user buffer, and pin the pages to prevent eviction. */ + validate_and_pin_user_ptr (buffer, size, true); + /* Acquire the file system lock to prevent race-conditions. */ lock_acquire (&filesys_lock); - int bytes_written = file_read (file_info->file, buffer, size); + int bytes_read = file_read (file_info->file, buffer, size); lock_release (&filesys_lock); + /* Unpin the pages to allow eviction. */ + unpin_user_ptr (buffer, size); + /* Return the number of bytes read. */ - return bytes_written; + return bytes_read; } } @@ -308,11 +335,11 @@ syscall_write (int fd, const void *buffer, unsigned size) if (fd <= 0) return 0; - /* Validate the user buffer for the provided size before writing. */ - validate_user_pointer (buffer, size); - if (fd == STDOUT_FILENO) { + /* Validate the user buffer. */ + validate_user_ptr (buffer, size, false); + /* Writing to the console. */ putbuf (buffer, size); @@ -328,13 +355,19 @@ syscall_write (int fd, const void *buffer, unsigned size) if (file_info == NULL) return 0; + /* Validate the user buffer, and pin the pages to prevent eviction. */ + validate_and_pin_user_ptr (buffer, size, false); + /* Acquire the file system lock to prevent race conditions. */ lock_acquire (&filesys_lock); - int bytes = file_write (file_info->file, buffer, size); + int bytes_written = file_write (file_info->file, buffer, size); lock_release (&filesys_lock); + /* Unpin the pages to allow eviction. */ + unpin_user_ptr (buffer, size); + /* Return the number of bytes written. */ - return bytes; + return bytes_written; } } @@ -393,6 +426,83 @@ syscall_close (int fd) } } +/* Handles the syscall for memory mapping a file. */ +static mapid_t +syscall_mmap (int fd, void *addr) +{ + /* Ensure the FD is for a file in the filesystem (not STDIN or STDOUT). */ + if (fd == STDOUT_FILENO || fd == STDIN_FILENO) + return MMAP_FAILURE; + + /* Validate that there is a file associated with the given FD. */ + struct open_file *file_info = fd_get_file (fd); + if (file_info == NULL) + return MMAP_FAILURE; + + /* Ensure that the address is page-aligned and it's neither NULL nor zero. */ + if (addr == 0 || addr == NULL || pg_ofs (addr) != 0) + return MMAP_FAILURE; + + /* Reopen the file to obtain a separate and independent reference to the file + for the mapping. */ + struct file *file = file_reopen (file_info->file); + if (file == NULL) + return MMAP_FAILURE; + + /* Get the size of the file. Mmap fails if the file is empty. */ + off_t file_size = file_length (file); + if (file_size == 0) + return MMAP_FAILURE; + + /* ensures the page for mmap does not overlap with the stack */ + if (addr >= (thread_current ()->curr_esp - PGSIZE)) + return MMAP_FAILURE; + + /* Check and ensure that there is enough space in the user virtual memory to + hold the entire file. */ + for (off_t ofs = 0; ofs < file_size; ofs += PGSIZE) + { + if (page_get (thread_current (), addr + ofs) != NULL) + return MMAP_FAILURE; + } + + /* Map the file data into the user virtual memory starting from addr. */ + for (off_t ofs = 0; ofs < file_size; ofs += PGSIZE) + { + off_t read_bytes = file_size - ofs < PGSIZE ? file_size - ofs : PGSIZE; + off_t zero_bytes = PGSIZE - read_bytes; + + if (page_insert_file (file, ofs, addr + ofs, read_bytes, zero_bytes, true, + PAGE_FILE) == NULL) + return MMAP_FAILURE; + } + + /* Create a new mapping for the file. */ + struct mmap_entry *mmap = mmap_insert (file, addr); + if (mmap == NULL) + return MMAP_FAILURE; + + + return mmap->mapping; +} + +/* Handles the syscall for unmapping a memory mapped file. + + Pre: mapping is a valid mapping identifier returned by mmap syscall. */ +static void +syscall_munmap (mapid_t mapping) +{ + /* Get the mmap entry from the mapping identifier. */ + struct mmap_entry *mmap = mmap_get (mapping); + + /* Delete the mmap entry from the hash table. */ + hash_delete (&thread_current ()->mmap_files, &mmap->elem); + + /* Unmap the mmap entry: free the pages and write back to the file if + necessary. NOTE. freeing and cleaning up is also handled by mmap_unmap. */ + mmap_unmap (mmap); +} + /* Hashing function needed for the open_file table. Returns a hash for an entry, based on its FD. */ unsigned @@ -451,63 +561,193 @@ fd_get_file (int fd) return hash_entry (e, struct open_file, elem); } -/* Validates if a block of memory starting at START and of size SIZE bytes is - fully contained within user virtual memory. Kills the thread (by exiting with - failure) if the memory is invalid. Otherwise, returns (nothing) normally. - If the size is 0, the function does no checks and returns the given ptr. */ +/* Helper function that validates a block of memory and optionally pins frames. + thread_exit() if the memory is invalid. Used only by the two helper functions + validate_user_ptr and validate_and_pin_user_ptr. See the comments for those + functions for more details on each. */ static void -validate_user_pointer (const void *start, size_t size) +validate_user_ptr_helper (const void *start, size_t size, bool write, bool pin) { - /* If the size is 0, we do not need to check anything. */ if (size == 0) return; - const void *end = start + size - 1; - - /* Check if the start and end pointers are valid user virtual addresses. */ - if (start == NULL || !is_user_vaddr (start) || !is_user_vaddr (end)) + /* ptr < ptr + size - 1, so sufficient to check that (ptr + size -1) is a + valid user virtual memory address. */ + void *end = start + size - 1; + if (!is_user_vaddr (end)) syscall_exit (EXIT_FAILURE); - /* We now need to check if the entire memory block is mapped to physical - memory by the page table. */ for (const void *ptr = pg_round_down (start); ptr <= end; ptr += PGSIZE) - if (pagedir_get_page (thread_current ()->pagedir, ptr) == NULL) - syscall_exit (EXIT_FAILURE); -} - -/* Validates if a string is fully contained within user virtual memory. Kills - the thread (by exiting with failure) if the memory is invalid. Otherwise, - returns (nothing) normally. */ -static void -validate_user_string (const char *str) -{ - /* Check if the string pointer is a valid user virtual address. */ - if (str == NULL || !is_user_vaddr (str)) - syscall_exit (EXIT_FAILURE); - - /* Calculate the offset of the string within the (first) page. */ - size_t offset = (uintptr_t) str % PGSIZE; - - /* We move page by page, checking if the page is mapped to physical memory. */ - for (;;) { - void *page = pg_round_down (str); + int result; - /* If we reach addresses that are not mapped to physical memory before the - end of the string, the thread is terminated. */ - if (!is_user_vaddr(page) || - pagedir_get_page (thread_current ()->pagedir, page) == NULL) + /* Check read access to pointer. */ + if ((result = get_user (ptr)) == -1) syscall_exit (EXIT_FAILURE); - while (offset < PGSIZE) - { - if (*str == '\0') - return; /* We reached the end of the string without issues. */ + /* Check write access to pointer (if required). */ + if (write && !put_user ((uint8_t *)ptr, result)) + syscall_exit (EXIT_FAILURE); - str++; - offset++; + /* If pin is set, pin the frame to prevent eviction. */ + if (pin) + { + void *kpage = pagedir_get_page(thread_current()->pagedir, ptr); + if (kpage == NULL) + { + // If it was evicted, try to load it back in. + ptr -= PGSIZE; + continue; } - offset = 0; /* Next page will start at the beginning. */ + frame_pin(kpage); + } } } + +/* Validates if a block of memory starting at PTR and of size SIZE bytes is + fully contained within valid user virtual memory. thread_exit () if the + memory is invalid. + If the size is 0, the function does no checks and returns PTR. */ +static void +validate_user_ptr (const void *start, size_t size, bool write) +{ + validate_user_ptr_helper (start, size, write, false); +} + +/* Validates if a block of memory starting at PTR and of size SIZE bytes is + fully contained within valid user virtual memory. thread_exit () if the + memory is invalid. The function also checks if the memory is writable if + WRITE flag is set. + + The function attempts to preload the pages in case they are not in memory + yet (e.g., in a swap, lazy loading). If this is successful, the frame pages + are pinned to prevent eviction prior to access. + + As such, a call to this function MUST be followed by a call to + unpin_user_ptr (START, SIZE) to unpin the pages and allow eviction. + + If the size is 0, the function does no checks and returns PTR. */ +static void +validate_and_pin_user_ptr (const void *start, size_t size, bool write) +{ + validate_user_ptr_helper (start, size, write, true); +} + +/* Unpins all the pages containing a block of memory starting at START and of + size SIZE bytes. + + Pre: The pages were previously pinned by validate_and_pin_user_ptr (START, + SIZE). */ +static void +unpin_user_ptr (const void *start, size_t size) +{ + void *end = start + size - 1; + + /* We don't need to do any checks as this function is always called after + validate_and_pin_user_ptr. */ + /* Go through all pages in the block range, unpinning the frames. */ + for (void *ptr = pg_round_down (start); ptr <= end; ptr += PGSIZE) + { + void *kpage = pagedir_get_page (thread_current ()->pagedir, ptr); + ASSERT (kpage != NULL); + + frame_unpin (kpage); + } +} + +/* Validates of a C-string starting at ptr is fully contained within valid + user virtual memory. thread_exit () if the memory is invalid. */ +static void +validate_and_pin_user_str (const char *ptr) +{ + size_t offset = (uintptr_t) ptr % PGSIZE; + + for (;;) + { + if (!is_user_vaddr (ptr)) + syscall_exit (EXIT_FAILURE); + + if (get_user ((const uint8_t *)ptr) == -1) + syscall_exit (EXIT_FAILURE); + + /* Pin the frame to prevent eviction. */ + void *page = pg_round_down (ptr); + void *kpage = pagedir_get_page (thread_current ()->pagedir, page); + if (kpage == NULL) + { + // If it was evicted, attempt to reload. + ptr -= PGSIZE; + continue; + } + + frame_pin (kpage); + + while (offset < PGSIZE) + { + if (*ptr == '\0') + return; /* We reached the end of the string without issues. */ + + ptr++; + offset++; + } + + offset = 0; + } +} + +/* Unpins all the pages containing a C-string starting at PTR. + + Pre: The pages were previously pinned by validate_and_pin_user_str (PTR). + PTR points to a valid C string that ends with '\0'. */ +static void +unpin_user_str (const char *ptr) +{ + size_t offset = (uintptr_t)ptr % PGSIZE; + const char *str_ptr = ptr; + + for (;;) + { + void *page = pg_round_down(str_ptr); + void *kpage = pagedir_get_page(thread_current()->pagedir, page); + ASSERT(kpage != NULL); + frame_unpin (kpage); + + /* Scan until end of string or page */ + while (offset < PGSIZE) + { + if (*str_ptr == '\0') + return; /* Found end of string */ + str_ptr++; + offset++; + } + + offset = 0; + } +} + +/* PROVIDED BY SPEC. + Reads a byte at user virtual address UADDR. + UADDR must be below PHYS_BASE. + Returns the byte value if successful, -1 if a segfault occurred. */ +static int +get_user (const uint8_t *uaddr) +{ + int result; + asm ("movl $1f, %0; movzbl %1, %0; 1:" : "=&a"(result) : "m"(*uaddr)); + return result; +} + +/* PROVIDED BY SPEC. + Writes BYTE to user address UDST. + UDST must be below PHYS_BASE. + Returns true if successful, false if a segfault occurred. */ +static bool +put_user (uint8_t *udst, uint8_t byte) +{ + int error_code; + asm ("movl $1f, %0; movb %b2, %1; 1:" + : "=&a"(error_code), "=m"(*udst) + : "q"(byte)); + return error_code != -1; +} diff --git a/src/vm/frame.c b/src/vm/frame.c index cdb141c..4730558 100644 --- a/src/vm/frame.c +++ b/src/vm/frame.c @@ -2,14 +2,14 @@ #include #include #include - #include "frame.h" #include "page.h" +#include "filesys/file.h" #include "threads/malloc.h" #include "threads/vaddr.h" #include "userprog/pagedir.h" +#include "userprog/syscall.h" #include "threads/synch.h" -#include "devices/swap.h" /* Hash table that maps every active frame's kernel virtual address to its corresponding 'frame_metadata'.*/ @@ -36,7 +36,9 @@ struct frame_metadata { void *frame; /* The kernel virtual address holding the frame. */ void *upage; /* The user virtual address pointing to the frame. */ - struct thread *owner; /* Pointer to the thread that owns the frame. */ + struct list owners; /* List of threads that own the frame. */ + bool pinned; /* Indicates wheter the frame should be + considered as an eviction candidate.*/ struct hash_elem hash_elem; /* Tracks the position of the frame metadata within 'frame_table', whose key is the kernel virtual address of the frame. */ @@ -50,7 +52,10 @@ hash_less_func frame_metadata_less; static struct list_elem *lru_next (struct list_elem *e); static struct list_elem *lru_prev (struct list_elem *e); +static struct frame_metadata *frame_metadata_get (void *frame); static struct frame_metadata *get_victim (void); +static void free_owners (struct list *owners); +static struct frame_metadata *frame_metadata_find (void *frame); /* Initialize the frame system by initializing the frame (hash) table with the frame_metadata hashing and comparison functions, as well as initializing @@ -76,7 +81,7 @@ frame_alloc (enum palloc_flags flags, void *upage, struct thread *owner) { struct frame_metadata *frame_metadata; flags |= PAL_USER; - + lock_acquire (&lru_lock); void *frame = palloc_get_page (flags); @@ -92,9 +97,28 @@ frame_alloc (enum palloc_flags flags, void *upage, struct thread *owner) struct frame_metadata *victim = get_victim (); ASSERT (victim != NULL); /* get_victim () should never return null. */ - /* 2. Swap out victim into disk. */ - size_t swap_slot = swap_out (victim->frame); - page_set_swap (victim->owner, victim->upage, swap_slot); + /* 2. Handle victim page writing based on its type. */ + struct page_entry *victim_page = page_get (thread_current (), victim->upage); + if (victim_page != NULL && victim_page->type == PAGE_MMAP) + { + /* If it was a memory-mapped file page, we just write it back + to the file if it was dirty. */ + if (pagedir_is_dirty(owner->pagedir, victim->upage)) + { + lock_acquire (&filesys_lock); + file_write_at (victim_page->file, victim->upage, + victim_page->read_bytes, victim_page->offset); + lock_release (&filesys_lock); + } + } + else + { + /* Otherwise, insert the page into swap. */ + page_insert_swapped (victim->upage, victim->frame, &victim->owners); + } + + /* Free victim's owners. */ + free_owners (&victim->owners); /* If zero flag is set, zero out the victim page. */ if (flags & PAL_ZERO) @@ -115,6 +139,8 @@ frame_alloc (enum palloc_flags flags, void *upage, struct thread *owner) /* Must own lru_lock here, as otherwise there is a race condition with next_victim either being NULL or uninitialized. */ frame_metadata = malloc (sizeof (struct frame_metadata)); + if (frame_metadata == NULL) + PANIC ("Couldn't allocate memory for frame metadata!\n"); frame_metadata->frame = frame; /* Newly allocated frames are pushed to the back of the circular queue @@ -134,31 +160,55 @@ frame_alloc (enum palloc_flags flags, void *upage, struct thread *owner) hash_insert (&frame_table, &frame_metadata->hash_elem); } + struct frame_owner *frame_owner = malloc (sizeof (struct frame_owner)); + if (frame_owner == NULL) + PANIC ("Couldn't allocate memory for frame owner!\n"); + frame_owner->owner = owner; + list_init (&frame_metadata->owners); + list_push_back (&frame_metadata->owners, &frame_owner->elem); frame_metadata->upage = upage; - frame_metadata->owner = owner; + frame_metadata->pinned = false; lock_release (&lru_lock); - return frame_metadata->frame; } +void +frame_pin (void *frame) +{ + struct frame_metadata *frame_metadata = frame_metadata_get (frame); + if (frame_metadata == NULL) + PANIC ("Attempted to pin a frame at an unallocated kernel address '%p'\n", + frame); + + frame_metadata->pinned = true; +} + +void +frame_unpin (void *frame) +{ + struct frame_metadata *frame_metadata = frame_metadata_get (frame); + if (frame_metadata == NULL) + PANIC ("Attempted to unpin a frame at an unallocated kernel address '%p'\n", + frame); + + frame_metadata->pinned = false; +} + /* Attempt to deallocate a frame for a user process by removing it from the frame table as well as lru_list, and freeing the underlying page memory & metadata struct. Panics if the frame isn't active in memory. */ void frame_free (void *frame) { - struct frame_metadata key_metadata; - key_metadata.frame = frame; + struct frame_metadata *frame_metadata = frame_metadata_find (frame); + if (frame_metadata == NULL) + PANIC ("Attempted to free a frame at kernel address %p, " + "but this address is not allocated!\n", + frame); - struct hash_elem *e = - hash_delete (&frame_table, &key_metadata.hash_elem); - if (e == NULL) PANIC ("Attempted to free a frame at kernel address %p, " - "but this address is not allocated!\n", frame); - - struct frame_metadata *frame_metadata = - hash_entry (e, struct frame_metadata, hash_elem); - + free_owners (&frame_metadata->owners); lock_acquire (&lru_lock); + hash_delete (&frame_table, &frame_metadata->hash_elem); list_remove (&frame_metadata->list_elem); /* If we're freeing the frame marked as the next victim, update @@ -177,33 +227,117 @@ frame_free (void *frame) palloc_free_page (frame); } +/* Add a thread to a frame's frame_metadata owners list. */ +bool +frame_owner_insert (void *frame, struct thread *owner) +{ + struct frame_metadata *frame_metadata = frame_metadata_find (frame); + if (frame_metadata == NULL) + return false; + + struct frame_owner *frame_owner = malloc (sizeof (struct frame_owner)); + if (frame_owner == NULL) + return false; + frame_owner->owner = owner; + list_push_back (&frame_metadata->owners, &frame_owner->elem); + return true; +} + +/* Remove and deallocate a frame owner from the frame_metadata owners list. + */ +void +frame_owner_remove (void *frame, struct thread *owner) +{ + struct frame_metadata *frame_metadata = frame_metadata_find (frame); + if (frame_metadata == NULL) + PANIC ("Attempted to remove an owner from a frame at kernel " + "address %p, but this address is not allocated!\n", + frame); + + struct list_elem *oe; + for (oe = list_begin (&frame_metadata->owners); + oe != list_end (&frame_metadata->owners);) + { + struct frame_owner *frame_owner + = list_entry (oe, struct frame_owner, elem); + oe = list_next (oe); + if (frame_owner->owner == owner) + { + list_remove (&frame_owner->elem); + free (frame_owner); + return; + } + } + NOT_REACHED (); +} + +/* Find a frame_metadata entry in the frame table. */ +static struct frame_metadata * +frame_metadata_find (void *frame) +{ + struct frame_metadata key_metadata; + key_metadata.frame = frame; + + struct hash_elem *e = hash_find (&frame_table, &key_metadata.hash_elem); + if (e == NULL) + return NULL; + return hash_entry (e, struct frame_metadata, hash_elem); +} + /* TODO: Account for page aliases when checking accessed bit. */ /* A pre-condition for calling this function is that the calling thread owns lru_lock and that lru_list is non-empty. */ static struct frame_metadata * get_victim (void) { - struct list_elem *e = next_victim; + struct list_elem *ve = next_victim; struct frame_metadata *frame_metadata; - uint32_t *pd; - void *upage; - for (;;) + bool found = false; + while (!found) { - frame_metadata = list_entry (e, struct frame_metadata, list_elem); - pd = frame_metadata->owner->pagedir; - upage = frame_metadata->upage; - e = lru_next (e); + frame_metadata = list_entry (ve, struct frame_metadata, list_elem); + ve = lru_next (ve); + struct list_elem *oe; - if (!pagedir_is_accessed (pd, upage)) - break; + /* Skip pinned frames */ + if (frame_metadata->pinned) + continue; - pagedir_set_accessed (pd, upage, false); + /* Returns once a frame that was not accessed by any owner is found. */ + found = true; + for (oe = list_begin (&frame_metadata->owners); + oe != list_end (&frame_metadata->owners); oe = list_next (oe)) + { + struct frame_owner *frame_owner + = list_entry (oe, struct frame_owner, elem); + uint32_t *pd = frame_owner->owner->pagedir; + void *upage = frame_metadata->upage; + + if (pagedir_is_accessed (pd, upage)) + { + found = false; + pagedir_set_accessed (pd, upage, false); + } + } } - next_victim = e; + next_victim = ve; return frame_metadata; } +static void +free_owners (struct list *owners) +{ + struct list_elem *oe; + for (oe = list_begin (owners); oe != list_end (owners);) + { + struct frame_owner *frame_owner + = list_entry (oe, struct frame_owner, elem); + oe = list_remove (oe); + free (frame_owner); + } +} + /* Hash function for frame metadata, used for storing entries in the frame table. */ unsigned @@ -221,14 +355,26 @@ frame_metadata_hash (const struct hash_elem *e, void *aux UNUSED) bool frame_metadata_less (const struct hash_elem *a_, const struct hash_elem *b_, void *aux UNUSED) - { - struct frame_metadata *a = - hash_entry (a_, struct frame_metadata, hash_elem); - struct frame_metadata *b = - hash_entry (b_, struct frame_metadata, hash_elem); +{ + struct frame_metadata *a = + hash_entry (a_, struct frame_metadata, hash_elem); + struct frame_metadata *b = + hash_entry (b_, struct frame_metadata, hash_elem); - return a->frame < b->frame; - } + return a->frame < b->frame; +} + +static struct frame_metadata * +frame_metadata_get (void *frame) +{ + struct frame_metadata key_metadata; + key_metadata.frame = frame; + + struct hash_elem *e = hash_find (&frame_table, &key_metadata.hash_elem); + if (e == NULL) return NULL; + + return hash_entry (e, struct frame_metadata, hash_elem); +} /* Returns the next recently used element after the one provided, which is achieved by iterating through lru_list like a circular queue @@ -253,4 +399,3 @@ lru_prev (struct list_elem *e) return list_prev (e); } - diff --git a/src/vm/frame.h b/src/vm/frame.h index 93081d3..76a801a 100644 --- a/src/vm/frame.h +++ b/src/vm/frame.h @@ -4,8 +4,19 @@ #include "threads/thread.h" #include "threads/palloc.h" +struct frame_owner +{ + struct thread *owner; /* The thread that owns the frame. */ + struct list_elem elem; /* List element for the list of owners. */ +}; + void frame_init (void); void *frame_alloc (enum palloc_flags, void *, struct thread *); +void frame_pin (void *frame); +void frame_unpin (void *frame); void frame_free (void *frame); +bool frame_owner_insert (void *frame, struct thread *owner); +void frame_owner_remove (void *frame, struct thread *owner); + #endif /* vm/frame.h */ diff --git a/src/vm/mmap.c b/src/vm/mmap.c new file mode 100644 index 0000000..64ad28f --- /dev/null +++ b/src/vm/mmap.c @@ -0,0 +1,129 @@ +#include "mmap.h" +#include "page.h" +#include "threads/thread.h" +#include "threads/vaddr.h" +#include "threads/malloc.h" +#include "userprog/syscall.h" +#include "userprog/pagedir.h" +#include + +static unsigned mmap_hash (const struct hash_elem *e, void *aux); +static bool mmap_less (const struct hash_elem *a_, const struct hash_elem *b_, + void *aux); +static void mmap_cleanup(struct hash_elem *e, void *aux); + +/* Initializes the mmap table for the given thread, setting the mmap counter to + 0 and initializing the hash table. */ +bool +mmap_init (struct thread *t) +{ + t->mmap_counter = 0; + return hash_init (&t->mmap_files, mmap_hash, mmap_less, NULL); +} + +struct mmap_entry * +mmap_get (mapid_t mapping) +{ + struct mmap_entry fake_mmap_entry; + fake_mmap_entry.mapping = mapping; + + struct hash_elem *e + = hash_find (&thread_current ()->mmap_files, &fake_mmap_entry.elem); + + if (e == NULL) + return NULL; + + return hash_entry (e, struct mmap_entry, elem); +} + +/* Inserts a new mmap entry into the mmap table for the current thread. Upage + is the start address of the file data in the user VM. */ +struct mmap_entry * +mmap_insert (struct file *file, void *upage) +{ + if (file == NULL || upage == NULL) + return NULL; + + struct mmap_entry *mmap = malloc (sizeof (struct mmap_entry)); + if (mmap == NULL) + return NULL; + + mmap->mapping = thread_current ()->mmap_counter++; + mmap->file = file; + mmap->upage = upage; + + hash_insert (&thread_current ()->mmap_files, &mmap->elem); + return mmap; +} + +/* Unmaps the given mmap entry from the current thread's mmap table. */ +void +mmap_unmap (struct mmap_entry *mmap) +{ + if (mmap == NULL) + return; + + /* Free all the pages associated with the mapping, writing back to the file + if necessary. */ + off_t length = file_length (mmap->file); + for (off_t ofs = 0; ofs < length; ofs += PGSIZE) + { + void *upage = mmap->upage + ofs; + + /* Get the SPT page entry for this page. */ + struct page_entry *page = page_get(thread_current (), upage); + if (page == NULL) + continue; + + /* Write the page back to the file if it is dirty. */ + if (pagedir_is_dirty (thread_current ()->pagedir, upage)) + { + lock_acquire (&filesys_lock); + file_write_at (mmap->file, upage, page->read_bytes, ofs); + lock_release (&filesys_lock); + } + + /* Remove the page from the supplemental page table. */ + hash_delete (&thread_current ()->pages, &page->elem); + } + + file_close (mmap->file); + free (mmap); +} + +/* Destroys the mmap table for the current thread. Frees all the memory + allocated for the mmap entries. */ +void +mmap_destroy (void) +{ + hash_destroy (&thread_current ()->mmap_files, mmap_cleanup); +} + +/* A hash function for the mmap table. Returns a hash for an entry, based on its + mapping. */ +static unsigned +mmap_hash (const struct hash_elem *e, void *aux UNUSED) +{ + return hash_entry (e, struct mmap_entry, elem)->mapping; +} + +/* A comparator function for the mmap table. Compares two entries based on their + mappings. */ +static bool +mmap_less (const struct hash_elem *a_, const struct hash_elem *b_, + void *aux UNUSED) +{ + const struct mmap_entry *a = hash_entry (a_, struct mmap_entry, elem); + const struct mmap_entry *b = hash_entry (b_, struct mmap_entry, elem); + + return a->mapping < b->mapping; +} + +/* Cleans up the mmap table for the current thread. Implicitly unmaps the mmap + entry, freeing pages and writing back to the file if necessary. */ +static void +mmap_cleanup (struct hash_elem *e, void *aux UNUSED) +{ + struct mmap_entry *mmap = hash_entry (e, struct mmap_entry, elem); + mmap_unmap (mmap); +} diff --git a/src/vm/mmap.h b/src/vm/mmap.h new file mode 100644 index 0000000..cba21fc --- /dev/null +++ b/src/vm/mmap.h @@ -0,0 +1,27 @@ +#ifndef VM_MMAP_H +#define VM_MMAP_H + +#include +#include "threads/thread.h" +#include "filesys/file.h" + +/* A mapping identifier type. */ +typedef unsigned mapid_t; + +/* A structure to represent a memory mapped file. */ +struct mmap_entry { + mapid_t mapping; /* The mapping identifier of the mapped file. */ + struct file *file; /* A pointer to the file that is being mapped. */ + void *upage; /* The start address of the file data in the user VM. */ + + struct hash_elem elem; /* An elem for the hash table. */ +}; + +bool mmap_init (struct thread *t); +struct mmap_entry *mmap_get (mapid_t mapping); +struct mmap_entry *mmap_insert (struct file *file, void *upage); +void mmap_unmap (struct mmap_entry *mmap); +void mmap_umap_all (void); +void mmap_destroy (void); + +#endif /* vm/mmap.h */ diff --git a/src/vm/page.c b/src/vm/page.c index 8ebc71c..fb94fa0 100644 --- a/src/vm/page.c +++ b/src/vm/page.c @@ -1,20 +1,450 @@ #include "page.h" +#include +#include +#include +#include "filesys/file.h" +#include "threads/pte.h" +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/synch.h" +#include "devices/swap.h" +#include "userprog/process.h" +#include "userprog/pagedir.h" +#include "vm/frame.h" -/* Updates the 'owner' thread's page table entry for virtual address 'upage' - to have a present bit of 0 and stores the specified swap slot value in the - entry for later retrieval from disk. */ -void -page_set_swap (struct thread *owner, void *upage, size_t swap_slot) +#define SWAP_FLAG_BIT 9 +#define SHARED_FLAG_BIT 10 +#define ADDR_START_BIT 12 + +struct hash shared_file_pages; +struct lock shared_file_pages_lock; + +static unsigned page_hash (const struct hash_elem *e, void *aux UNUSED); +static bool page_less (const struct hash_elem *a_, const struct hash_elem *b_, + void *aux UNUSED); + +static void page_flag_shared (struct thread *owner, void *upage, bool shared); +static unsigned shared_file_page_hash (const struct hash_elem *e, + void *aux UNUSED); +static bool shared_file_page_less (const struct hash_elem *a_, + const struct hash_elem *b_, + void *aux UNUSED); +static struct shared_file_page *shared_file_page_get (struct file *file, + void *upage); + +/* Initialise a supplementary page table. */ +bool +init_pages (struct hash *pages) { + ASSERT (pages != NULL); + return hash_init (pages, page_hash, page_less, NULL); +} +/* Hashing function needed for the SPT table. Returns a hash for an entry, + based on its upage. */ +static unsigned +page_hash (const struct hash_elem *e, void *aux UNUSED) +{ + struct page_entry *page = hash_entry (e, struct page_entry, elem); + return hash_ptr (page->upage); +} + +/* Comparator function for the SPT table. Compares two entries based on their + upages. */ +static bool +page_less (const struct hash_elem *a_, const struct hash_elem *b_, + void *aux UNUSED) +{ + const struct page_entry *a = hash_entry (a_, struct page_entry, elem); + const struct page_entry *b = hash_entry (b_, struct page_entry, elem); + + return a->upage < b->upage; +} + +static void page_flag_swap (uint32_t *pte, bool set); +static void page_set_swap (struct thread *owner, uint32_t *pte, + size_t swap_slot); + +// TODO: Deal with NULL malloc returns +/* Swap out 'owner' process's 'upage' stored at 'kpage'. Then, allocate and + insert a new page entry into the user process thread's SPT representing + this swapped out page. */ +bool +page_insert_swapped (void *upage, void *kpage, struct list *owners) +{ + struct file *exec_file = NULL; + struct list_elem *e; + for (e = list_begin (owners); e != list_end (owners); e = list_next (e)) + { + struct thread *owner = list_entry (e, struct frame_owner, elem)->owner; + uint32_t *pte = lookup_page (owner->pagedir, upage, false); + if (exec_file != NULL || page_is_shared_pte (pte)) + { + ASSERT (page_is_shared_pte (pte)); + pagedir_clear_page (owner->pagedir, upage); + exec_file = owner->exec_file; + ASSERT (exec_file != NULL); + continue; + } + ASSERT (list_size (owners) == 1); + + /* 1. Initialize swapped page entry. */ + struct page_entry *page = page_get (owner, upage); + if (page == NULL) + { + page = malloc (sizeof (struct page_entry)); + if (page == NULL) + return NULL; + page->upage = upage; + lock_init (&page->lock); + hash_insert (&owner->pages, &page->elem); + } + + /* Mark page as 'swapped' and flag the page directory as having + been modified *before* eviction begins to prevent the owner of the + victim page from accessing/modifying it mid-eviction. */ + /* TODO: We need to stop the process from destroying pagedir mid-eviction, + as this could render the page table entry invalid. */ + page_flag_swap (pte, true); + lock_acquire (&page->lock); + pagedir_clear_page (owner->pagedir, upage); + + size_t swap_slot = swap_out (kpage); + page_set_swap (owner, pte, swap_slot); + + lock_release (&page->lock); + } + if (exec_file != NULL) + { + lock_acquire (&shared_file_pages_lock); + struct shared_file_page *sfp = shared_file_page_get (exec_file, upage); + sfp->frame = NULL; + sfp->swap_slot = swap_out (kpage); + lock_release (&shared_file_pages_lock); + } + return true; +} + +/* Allocate and insert a new page entry into the user process thread's + SPT representing a file page. */ +struct page_entry * +page_insert_file (struct file *file, off_t ofs, void *upage, + uint32_t read_bytes, uint32_t zero_bytes, bool writable, + enum page_type type) +{ + /* If page exists, just update it. */ + struct page_entry *existing = page_get (thread_current (), upage); + if (existing != NULL) + { + ASSERT (existing->read_bytes == read_bytes); + ASSERT (existing->zero_bytes == zero_bytes); + existing->writable = existing->writable || writable; + return existing; + } + + struct page_entry *page = malloc(sizeof (struct page_entry)); + if (page == NULL) + return NULL; + + page->type = type; + page->file = file; + page->offset = ofs; + page->upage = upage; + page->read_bytes = read_bytes; + page->zero_bytes = zero_bytes; + page->writable = writable; + lock_init (&page->lock); + + hash_insert (&thread_current ()->pages, &page->elem); + return page; +} + +/* Gets a page_entry from the starting address of the page. Returns NULL if no + such page_entry exists in the hash map.*/ +struct page_entry * +page_get (struct thread *thread, void *upage) +{ + struct page_entry fake_page_entry; + fake_page_entry.upage = upage; + + struct hash_elem *e + = hash_find (&thread->pages, &fake_page_entry.elem); + if (e == NULL) + return NULL; + + return hash_entry (e, struct page_entry, elem); +} + +bool +page_load_file (struct page_entry *page) +{ + /* Allocate a frame for the page. If a frame allocation fails, then + frame_alloc should try to evict a page. If it is still NULL, the OS + panics as this should not happen if eviction is working correctly. */ + struct thread *t = thread_current (); + bool shareable = !page->writable && file_compare (page->file, t->exec_file); + if (shareable) + { + lock_acquire (&shared_file_pages_lock); + struct shared_file_page *sfp + = shared_file_page_get (page->file, page->upage); + if (sfp != NULL) + { + /* Frame exists, just install it. */ + if (sfp->frame != NULL) + { + if (!install_page (page->upage, sfp->frame, page->writable)) + { + lock_release (&shared_file_pages_lock); + return false; + } + /* First time adding the shared page, so add thread as owner. */ + if (page->type != PAGE_SHARED) + { + frame_owner_insert (sfp->frame, t); + } + } + /* Shared page is in swap. Load it. */ + else + { + void *frame = frame_alloc (PAL_USER, page->upage, t); + if (frame == NULL) + PANIC ( + "Could not allocate a frame to load page into memory."); + swap_in (frame, sfp->swap_slot); + + if (!install_page (page->upage, frame, false)) + { + frame_free (frame); + lock_release (&shared_file_pages_lock); + return false; + } + } + page_flag_shared (t, page->upage, true); + if (page->type != PAGE_SHARED) + { + sfp->ref_count++; + page->type = PAGE_SHARED; + } + lock_release (&shared_file_pages_lock); + return true; + } + } + + void *frame = frame_alloc (PAL_USER, page->upage, t); + pagedir_set_accessed (t->pagedir, page->upage, true); + if (frame == NULL) + PANIC ("Could not allocate a frame to load page into memory."); + + /* Map the page to the frame. */ + if (!install_page (page->upage, frame, page->writable)) + { + if (shareable) + lock_release (&shared_file_pages_lock); + frame_free (frame); + return false; + } + + /* Move the file pointer to the correct location in the file. Then, read the + data from the file into the frame. Checks that we were able to read the + expected number of bytes. */ + file_seek (page->file, page->offset); + if (file_read (page->file, frame, page->read_bytes) != (int) page->read_bytes) + { + if (shareable) + lock_release (&shared_file_pages_lock); + frame_free (frame); + return false; + } + + /* Zero out the remaining bytes in the frame. */ + memset (frame + page->read_bytes, 0, page->zero_bytes); + + /* If file page is read-only, make it shared. */ + if (shareable) + { + struct shared_file_page *sfp = malloc (sizeof (struct shared_file_page)); + if (sfp == NULL) + { + lock_release (&shared_file_pages_lock); + frame_free (frame); + return false; + } + sfp->file = page->file; + sfp->upage = page->upage; + sfp->frame = frame; + sfp->swap_slot = 0; + sfp->ref_count = 1; + hash_insert (&shared_file_pages, &sfp->elem); + page_flag_shared (t, page->upage, true); + page->type = PAGE_SHARED; + lock_release (&shared_file_pages_lock); + } + + /* Mark the page as loaded successfully. */ + return true; +} + +/* Function to clean up a page_entry. Given the elem of that page_entry, frees + the page_entry itself. */ +void +page_cleanup (struct hash_elem *e, void *aux UNUSED) +{ + struct page_entry *page = hash_entry (e, struct page_entry, elem); + if (page->type == PAGE_SHARED) + { + lock_acquire (&shared_file_pages_lock); + struct shared_file_page *sfp + = shared_file_page_get (page->file, page->upage); + ASSERT (sfp != NULL); + if (sfp->frame != NULL) + frame_owner_remove (sfp->frame, thread_current ()); + sfp->ref_count--; + if (sfp->ref_count == 0) + { + hash_delete (&shared_file_pages, &sfp->elem); + if (sfp->frame != NULL) + frame_free (sfp->frame); + else + swap_drop (sfp->swap_slot); + free (sfp); + } + lock_release (&shared_file_pages_lock); + } + free (page); +} + +/* Flags the provided page table entry as representing a swapped out page. */ +void +page_flag_swap (uint32_t *pte, bool set) + { + if (set) + *pte |= (1 << SWAP_FLAG_BIT); + else + *pte &= ~(1 << SWAP_FLAG_BIT); + } + +/* Sets the address bits of the page table entry to the provided swap slot + value. To be used for later retrieval of the swap slot when page faulting. */ +static void +page_set_swap (struct thread *owner, uint32_t *pte, size_t swap_slot) +{ + /* Store the provided swap slot in the address bits of the page table + entry, truncating excess bits. */ + *pte |= (1 << SWAP_FLAG_BIT); + uint32_t swap_slot_bits = (swap_slot << ADDR_START_BIT) & PTE_ADDR; + *pte = (*pte & PTE_FLAGS) | swap_slot_bits; + + invalidate_pagedir (owner->pagedir); +} + +/* Returns true iff the page with user address 'upage' owned by 'owner' + is flagged to be in the swap disk via the owner's page table. */ +bool +page_in_swap (struct thread *owner, void *upage) +{ + uint32_t *pte = lookup_page (owner->pagedir, upage, false); + return page_in_swap_pte (pte); +} + +/* Returns true iff the page table entry is marked to be in the swap disk. */ +bool +page_in_swap_pte (uint32_t *pte) +{ + return pte != NULL && (*pte & (1 << SWAP_FLAG_BIT)) != 0; } /* Given that the page with user address 'upage' owned by 'owner' is flagged to be in the swap disk via the owner's page table, returns its stored - swap slot. Otherwise panics the kernel. */ + swap slot and marks the PTE as not being in swap. */ size_t page_get_swap (struct thread *owner, void *upage) { + uint32_t *pte = lookup_page (owner->pagedir, upage, false); + ASSERT (pte != NULL); + ASSERT ((*pte & PTE_P) == 0); + + /* Masks the address bits and returns truncated value. */ + page_flag_swap (pte, false); + return ((*pte & PTE_ADDR) >> ADDR_START_BIT); } +/* Returns the swap slot stored in a PTE. */ +size_t +page_get_swap_pte (uint32_t *pte) +{ + ASSERT (pte != NULL); + ASSERT ((*pte & PTE_P) == 0); + return ((*pte & PTE_ADDR) >> ADDR_START_BIT); +} + +/* Flags the provided page table entry as representing a shared page. */ +static void +page_flag_shared (struct thread *owner, void *upage, bool shared) +{ + uint32_t *pte = lookup_page (owner->pagedir, upage, false); + ASSERT (pte != NULL); + + if (shared) + *pte |= (1 << SHARED_FLAG_BIT); + else + *pte &= ~(1 << SHARED_FLAG_BIT); +} + +/* Returns true iff the page table entry is marked to be shared. */ +bool +page_is_shared_pte (uint32_t *pte) +{ + return pte != NULL && (*pte & (1 << SHARED_FLAG_BIT)) != 0; +} + +/* Initializes the shared file pages hash table. */ +void +shared_file_pages_init () +{ + if (!hash_init (&shared_file_pages, shared_file_page_hash, + shared_file_page_less, NULL)) + PANIC ("Failed to initialize shared file pages hash table."); + lock_init (&shared_file_pages_lock); +} + +/* Hash function for shared file pages, used for storing entries in the + shared file pages table. */ +static unsigned +shared_file_page_hash (const struct hash_elem *e, void *aux UNUSED) +{ + struct shared_file_page *sfp = hash_entry (e, struct shared_file_page, elem); + void *inode = file_get_inode (sfp->file); + void *upage = sfp->upage; + void *bytes[2] = { inode, upage }; + return hash_bytes (bytes, sizeof (bytes)); +} + +/* 'less_func' comparison function for shared file pages, used for comparing + the keys of the shared file pages table. */ +static bool +shared_file_page_less (const struct hash_elem *a_, const struct hash_elem *b_, + void *aux UNUSED) +{ + const struct shared_file_page *a + = hash_entry (a_, struct shared_file_page, elem); + const struct shared_file_page *b + = hash_entry (b_, struct shared_file_page, elem); + + return !file_compare (a->file, b->file) || a->upage < b->upage; +} + +static struct shared_file_page * +shared_file_page_get (struct file *file, void *upage) +{ + struct shared_file_page fake_sfp; + fake_sfp.file = file; + fake_sfp.upage = upage; + + struct hash_elem *e = hash_find (&shared_file_pages, &fake_sfp.elem); + if (e == NULL) + return NULL; + + return hash_entry (e, struct shared_file_page, elem); +} diff --git a/src/vm/page.h b/src/vm/page.h index 7259ca9..7b45d9d 100644 --- a/src/vm/page.h +++ b/src/vm/page.h @@ -2,8 +2,63 @@ #define VM_PAGE_H #include "threads/thread.h" +#include "threads/synch.h" +#include "filesys/off_t.h" -void page_set_swap (struct thread *, void *, size_t); -size_t page_get_swap (struct thread *, void *); +enum page_type +{ + PAGE_FILE, + PAGE_MMAP, + PAGE_EMPTY, + PAGE_SHARED +}; -#endif /* vm/frame.h */ +struct page_entry +{ + enum page_type type; /* Type of Data that should go into the page */ + void *upage; /* Start Address of the User Page (Key of hash table). */ + + /* Data for swapped pages */ + struct lock lock; /* Enforces mutual exclusion in accessing the page + referenced by the entry between its owning process + and any thread performing page eviction. */ + + /* File Data */ + struct file *file; /* Pointer to the file for executables. */ + off_t offset; /* Offset of the page content within the file. */ + uint32_t read_bytes; /* Number of bytes to read within the page. */ + uint32_t zero_bytes; /* Number of bytes to zero within the page. */ + bool writable; /* Flag for whether this page is writable or not. */ + + struct hash_elem elem; /* An elem for the hash table. */ +}; + +struct shared_file_page +{ + struct file *file; + void *upage; + void *frame; + size_t swap_slot; + int ref_count; + + struct hash_elem elem; +}; + +bool init_pages (struct hash *pages); +bool page_insert_swapped (void *upage, void *kpage, struct list *owners); +struct page_entry *page_insert_file (struct file *file, off_t ofs, void *upage, + uint32_t read_bytes, uint32_t zero_bytes, + bool writable, enum page_type); +struct page_entry *page_get (struct thread *thread, void *upage); +bool page_load_file (struct page_entry *page); +void page_cleanup (struct hash_elem *e, void *aux); + +bool page_in_swap (struct thread *, void *); +bool page_in_swap_pte (uint32_t *pte); +size_t page_get_swap (struct thread *owner, void *upage); +size_t page_get_swap_pte (uint32_t *pte); + +bool page_is_shared_pte (uint32_t *pte); +void shared_file_pages_init (void); + +#endif diff --git a/tests/devices/src/.gitignore b/tests/devices/src/.gitignore new file mode 100644 index 0000000..0ab34c0 --- /dev/null +++ b/tests/devices/src/.gitignore @@ -0,0 +1,4 @@ +cscope.files +cscope.out +TAGS +tags diff --git a/tests/devices/src/LICENSE b/tests/devices/src/LICENSE new file mode 100644 index 0000000..74687d2 --- /dev/null +++ b/tests/devices/src/LICENSE @@ -0,0 +1,95 @@ +PintOS, including its documentation, is subject to the following +license: + + Copyright (C) 2004, 2005, 2006 Board of Trustees, Leland Stanford + Jr. University. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +A few individual files in PintOS were originally derived from other +projects, but they have been extensively modified for use in PintOS. +The original code falls under the original license, and modifications +for PintOS are additionally covered by the PintOS license above. + +In particular, code derived from Nachos is subject to the following +license: + +/* Copyright (c) 1992-1996 The Regents of the University of California. + All rights reserved. + + Permission to use, copy, modify, and distribute this software + and its documentation for any purpose, without fee, and + without written agreement is hereby granted, provided that the + above copyright notice and the following two paragraphs appear + in all copies of this software. + + IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO + ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE + AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA + HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" + BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO + PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR + MODIFICATIONS. +*/ + +Also, code derived from MIT's 6.828 course code is subject to the +following license: + +/* + * Copyright (C) 1997 Massachusetts Institute of Technology + * + * This software is being provided by the copyright holders under the + * following license. By obtaining, using and/or copying this software, + * you agree that you have read, understood, and will comply with the + * following terms and conditions: + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose and without fee or royalty is + * hereby granted, provided that the full text of this NOTICE appears on + * ALL copies of the software and documentation or portions thereof, + * including modifications, that you make. + * + * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, + * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR + * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR + * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY + * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT + * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR + * DOCUMENTATION. + * + * The name and trademarks of copyright holders may NOT be used in + * advertising or publicity pertaining to the software without specific, + * written prior permission. Title to copyright in this software and any + * associated documentation will at all times remain with copyright + * holders. See the file AUTHORS which should have accompanied this software + * for a list of all copyright holders. + * + * This file may be derived from previously copyrighted software. This + * copyright applies only to those changes made by the copyright + * holders listed in the AUTHORS file. The rest of this file is covered by + * the copyright notices, if any, listed below. + */ diff --git a/tests/devices/src/Make.config b/tests/devices/src/Make.config new file mode 100644 index 0000000..dd498ae --- /dev/null +++ b/tests/devices/src/Make.config @@ -0,0 +1,68 @@ +# -*- makefile -*- + +SHELL = /bin/sh + +VPATH = $(SRCDIR) + +# Binary utilities. +# If the host appears to be x86, use the normal tools. +# If it's x86-64, use the compiler and linker in 32-bit mode. +# Otherwise assume cross-tools are installed as i386-elf-*. +X86 = i.86\|pentium.*\|[pk][56]\|nexgen\|viac3\|6x86\|athlon.*\|i86pc +X86_64 = x86_64 +ifneq (0, $(shell expr `uname -m` : '$(X86)')) + CC = gcc + LD = ld + OBJCOPY = objcopy +else + ifneq (0, $(shell expr `uname -m` : '$(X86_64)')) + CC = gcc -m32 + LD = ld -melf_i386 + OBJCOPY = objcopy + else + CC = i386-elf-gcc + LD = i386-elf-ld + OBJCOPY = i386-elf-objcopy + endif +endif + +# by default randomizing static libraries can be done using the host compiler +RANLIB = ranlib + +# macOS: force compiling with the i686-elf cross compiler suite +UNAME_S = $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + CC = i686-elf-gcc + LD = i686-elf-ld + OBJCOPY = i686-elf-objcopy + RANLIB = i686-elf-ranlib +endif + +ifeq ($(strip $(shell command -v $(CC) 2> /dev/null)),) +$(warning *** Compiler ($(CC)) not found. Did you set $$PATH properly? Please refer to the Getting Started section in the documentation for details. ***) +endif + +# Compiler and assembler invocation. +DEFINES = +WARNINGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers -Wno-frame-address +CFLAGS = -g -msoft-float -O -fno-omit-frame-pointer -ffreestanding -fno-pic -fcommon -mno-sse +CPPFLAGS = -nostdinc -I$(SRCDIR) -I$(SRCDIR)/lib +ASFLAGS = -Wa,--gstabs +LDFLAGS = +DEPS = -MMD -MF $(@:.o=.d) + +# Turn off -fstack-protector, which we don't support. +ifeq ($(strip $(shell echo | $(CC) -fno-stack-protector -E - > /dev/null 2>&1; echo $$?)),0) +CFLAGS += -fno-stack-protector +endif + +# Turn off --build-id in the linker, which confuses the PintOS loader. +ifeq ($(strip $(shell $(LD) --help | grep -q build-id; echo $$?)),0) +LDFLAGS += -Wl,--build-id=none +endif + +%.o: %.c + $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) $(WARNINGS) $(DEFINES) $(DEPS) + +%.o: %.S + $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) $(DEPS) diff --git a/tests/devices/src/Makefile b/tests/devices/src/Makefile new file mode 100644 index 0000000..9c7aea0 --- /dev/null +++ b/tests/devices/src/Makefile @@ -0,0 +1,29 @@ +BUILD_SUBDIRS = devices threads userprog vm filesys + +all:: + @echo "Run 'make' in subdirectories: $(BUILD_SUBDIRS)." + @echo "This top-level make has only 'clean' targets." + +CLEAN_SUBDIRS = $(BUILD_SUBDIRS) examples utils + +clean:: + for d in $(CLEAN_SUBDIRS); do $(MAKE) -C $$d $@; done + rm -f TAGS tags + +distclean:: clean + find . -name '*~' -exec rm '{}' \; + +TAGS_SUBDIRS = $(BUILD_SUBDIRS) devices lib +TAGS_SOURCES = find $(TAGS_SUBDIRS) -name \*.[chS] -print + +TAGS:: + etags --members `$(TAGS_SOURCES)` + +tags:: + ctags -T --no-warn `$(TAGS_SOURCES)` + +cscope.files:: + $(TAGS_SOURCES) > cscope.files + +cscope:: cscope.files + cscope -b -q -k diff --git a/tests/devices/src/Makefile.build b/tests/devices/src/Makefile.build new file mode 100644 index 0000000..7778f57 --- /dev/null +++ b/tests/devices/src/Makefile.build @@ -0,0 +1,111 @@ +# -*- makefile -*- + +SRCDIR = ../.. + +all: kernel.bin loader.bin + +include ../../Make.config +include ../Make.vars +include ../../tests/Make.tests + +# Compiler and assembler options. +kernel.bin: CPPFLAGS += -I$(SRCDIR)/lib/kernel + +# Core kernel. +threads_SRC = threads/start.S # Startup code. +threads_SRC += threads/init.c # Main program. +threads_SRC += threads/thread.c # Thread management core. +threads_SRC += threads/switch.S # Thread switch routine. +threads_SRC += threads/interrupt.c # Interrupt core. +threads_SRC += threads/intr-stubs.S # Interrupt stubs. +threads_SRC += threads/synch.c # Synchronization. +threads_SRC += threads/palloc.c # Page allocator. +threads_SRC += threads/malloc.c # Subpage allocator. + +# Device driver code. +devices_SRC = devices/pit.c # Programmable interrupt timer chip. +devices_SRC += devices/timer.c # Periodic timer device. +devices_SRC += devices/kbd.c # Keyboard device. +devices_SRC += devices/vga.c # Video device. +devices_SRC += devices/serial.c # Serial port device. +devices_SRC += devices/block.c # Block device abstraction layer. +devices_SRC += devices/partition.c # Partition block device. +devices_SRC += devices/ide.c # IDE disk block device. +devices_SRC += devices/input.c # Serial and keyboard input. +devices_SRC += devices/intq.c # Interrupt queue. +devices_SRC += devices/rtc.c # Real-time clock. +devices_SRC += devices/shutdown.c # Reboot and power off. +devices_SRC += devices/speaker.c # PC speaker. + +# Library code shared between kernel and user programs. +lib_SRC = lib/debug.c # Debug helpers. +lib_SRC += lib/random.c # Pseudo-random numbers. +lib_SRC += lib/stdio.c # I/O library. +lib_SRC += lib/stdlib.c # Utility functions. +lib_SRC += lib/string.c # String functions. +lib_SRC += lib/arithmetic.c # 64-bit arithmetic for GCC. +lib_SRC += lib/ustar.c # Unix standard tar format utilities. + +# Kernel-specific library code. +lib/kernel_SRC = lib/kernel/debug.c # Debug helpers. +lib/kernel_SRC += lib/kernel/list.c # Doubly-linked lists. +lib/kernel_SRC += lib/kernel/bitmap.c # Bitmaps. +lib/kernel_SRC += lib/kernel/hash.c # Hash tables. +lib/kernel_SRC += lib/kernel/console.c # printf(), putchar(). + +# User process code. +userprog_SRC = userprog/process.c # Process loading. +userprog_SRC += userprog/pagedir.c # Page directories. +userprog_SRC += userprog/exception.c # User exception handler. +userprog_SRC += userprog/syscall.c # System call handler. +userprog_SRC += userprog/gdt.c # GDT initialization. +userprog_SRC += userprog/tss.c # TSS management. + +# Virtual memory code. +vm_SRC += vm/frame.c # Frame table manager. +vm_SRC += vm/page.c # Page table manager. +vm_SRC += devices/swap.c # Swap block manager. + +# Filesystem code. +filesys_SRC = filesys/filesys.c # Filesystem core. +filesys_SRC += filesys/free-map.c # Free sector bitmap. +filesys_SRC += filesys/file.c # Files. +filesys_SRC += filesys/directory.c # Directories. +filesys_SRC += filesys/inode.c # File headers. +filesys_SRC += filesys/fsutil.c # Utilities. + +SOURCES = $(foreach dir,$(KERNEL_SUBDIRS),$($(dir)_SRC)) +OBJECTS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SOURCES))) +DEPENDS = $(patsubst %.o,%.d,$(OBJECTS)) + +threads/kernel.lds.s: CPPFLAGS += -P +threads/kernel.lds.s: threads/kernel.lds.S threads/loader.h + +kernel.o: threads/kernel.lds.s $(OBJECTS) + $(LD) -T $< -o $@ $(OBJECTS) + +kernel.bin: kernel.o + $(OBJCOPY) -R .note -R .comment -S $< $@ + +threads/loader.o: threads/loader.S + $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) + +loader.bin: threads/loader.o loader.ld + $(LD) -N -e 0 -T $(word 2, $^) -o $@ $< + +os.dsk: kernel.bin + cat $^ > $@ + +clean:: + rm -f $(OBJECTS) $(DEPENDS) + rm -f threads/loader.o threads/kernel.lds.s threads/loader.d + rm -f kernel.bin.tmp + rm -f kernel.o kernel.lds.s + rm -f kernel.bin loader.bin + rm -f bochsout.txt bochsrc.txt + rm -f results grade + +Makefile: $(SRCDIR)/Makefile.build + cp $< $@ + +-include $(DEPENDS) diff --git a/tests/devices/src/Makefile.kernel b/tests/devices/src/Makefile.kernel new file mode 100644 index 0000000..162a411 --- /dev/null +++ b/tests/devices/src/Makefile.kernel @@ -0,0 +1,20 @@ +# -*- makefile -*- + +all: + +include Make.vars + +DIRS = $(sort $(addprefix build/,$(KERNEL_SUBDIRS) $(TEST_SUBDIRS) lib/user)) + +all grade check: $(DIRS) build/Makefile + cd build && $(MAKE) $@ +$(DIRS): + mkdir -p $@ +build/Makefile: ../Makefile.build + cp $< $@ + +build/%: $(DIRS) build/Makefile + cd build && $(MAKE) $* + +clean: + rm -rf build diff --git a/tests/devices/src/Makefile.userprog b/tests/devices/src/Makefile.userprog new file mode 100644 index 0000000..d8e79ba --- /dev/null +++ b/tests/devices/src/Makefile.userprog @@ -0,0 +1,52 @@ +# -*- makefile -*- + +$(PROGS): CPPFLAGS += -I$(SRCDIR)/lib/user -I. + +# Linker flags. +$(PROGS): LDFLAGS += -nostdlib -static -Wl,-T,$(LDSCRIPT) +$(PROGS): LDSCRIPT = $(SRCDIR)/lib/user/user.lds + +# Library code shared between kernel and user programs. +lib_SRC = lib/debug.c # Debug code. +lib_SRC += lib/random.c # Pseudo-random numbers. +lib_SRC += lib/stdio.c # I/O library. +lib_SRC += lib/stdlib.c # Utility functions. +lib_SRC += lib/string.c # String functions. +lib_SRC += lib/arithmetic.c # 64-bit arithmetic for GCC. +lib_SRC += lib/ustar.c # Unix standard tar format utilities. + +# User level only library code. +lib/user_SRC = lib/user/debug.c # Debug helpers. +lib/user_SRC += lib/user/syscall.c # System calls. +lib/user_SRC += lib/user/console.c # Console code. + +LIB_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(lib_SRC) $(lib/user_SRC))) +LIB_DEP = $(patsubst %.o,%.d,$(LIB_OBJ)) +LIB = lib/user/entry.o libc.a + +PROGS_SRC = $(foreach prog,$(PROGS),$($(prog)_SRC)) +PROGS_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(PROGS_SRC))) +PROGS_DEP = $(patsubst %.o,%.d,$(PROGS_OBJ)) + +all: $(PROGS) + +define TEMPLATE +$(1)_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$($(1)_SRC))) +$(1): $$($(1)_OBJ) $$(LIB) $$(LDSCRIPT) + $$(CC) $$(LDFLAGS) $$($(1)_OBJ) $$(LIB) -o $$@ +endef + +$(foreach prog,$(PROGS),$(eval $(call TEMPLATE,$(prog)))) + +libc.a: $(LIB_OBJ) + rm -f $@ + ar r $@ $^ + $(RANLIB) $@ + +clean:: + rm -f $(PROGS) $(PROGS_OBJ) $(PROGS_DEP) + rm -f $(LIB_DEP) $(LIB_OBJ) lib/user/entry.[do] libc.a + +.PHONY: all clean + +-include $(LIB_DEP) $(PROGS_DEP) diff --git a/tests/devices/src/devices/.gitignore b/tests/devices/src/devices/.gitignore new file mode 100644 index 0000000..6d5357c --- /dev/null +++ b/tests/devices/src/devices/.gitignore @@ -0,0 +1,3 @@ +build +bochsrc.txt +bochsout.txt diff --git a/tests/devices/src/devices/Make.vars b/tests/devices/src/devices/Make.vars new file mode 100644 index 0000000..816bce2 --- /dev/null +++ b/tests/devices/src/devices/Make.vars @@ -0,0 +1,7 @@ +# -*- makefile -*- + +kernel.bin: DEFINES = +KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS) +TEST_SUBDIRS = tests/devices +GRADING_FILE = $(SRCDIR)/tests/devices/Grading +SIMULATOR = --qemu diff --git a/tests/devices/src/devices/Makefile b/tests/devices/src/devices/Makefile new file mode 100644 index 0000000..34c10aa --- /dev/null +++ b/tests/devices/src/devices/Makefile @@ -0,0 +1 @@ +include ../Makefile.kernel diff --git a/tests/devices/src/devices/block.c b/tests/devices/src/devices/block.c new file mode 100644 index 0000000..ec4ba04 --- /dev/null +++ b/tests/devices/src/devices/block.c @@ -0,0 +1,223 @@ +#include "devices/block.h" +#include +#include +#include +#include "devices/ide.h" +#include "threads/malloc.h" + +/* A block device. */ +struct block + { + struct list_elem list_elem; /* Element in all_blocks. */ + + char name[16]; /* Block device name. */ + enum block_type type; /* Type of block device. */ + block_sector_t size; /* Size in sectors. */ + + const struct block_operations *ops; /* Driver operations. */ + void *aux; /* Extra data owned by driver. */ + + unsigned long long read_cnt; /* Number of sectors read. */ + unsigned long long write_cnt; /* Number of sectors written. */ + }; + +/* List of all block devices. */ +static struct list all_blocks = LIST_INITIALIZER (all_blocks); + +/* The block block assigned to each PintOS role. */ +static struct block *block_by_role[BLOCK_ROLE_CNT]; + +static struct block *list_elem_to_block (struct list_elem *); + +/* Returns a human-readable name for the given block device + TYPE. */ +const char * +block_type_name (enum block_type type) +{ + static const char *block_type_names[BLOCK_CNT] = + { + "kernel", + "filesys", + "scratch", + "swap", + "raw", + "foreign", + }; + + ASSERT (type < BLOCK_CNT); + return block_type_names[type]; +} + +/* Returns the block device fulfilling the given ROLE, or a null + pointer if no block device has been assigned that role. */ +struct block * +block_get_role (enum block_type role) +{ + ASSERT (role < BLOCK_ROLE_CNT); + return block_by_role[role]; +} + +/* Assigns BLOCK the given ROLE. */ +void +block_set_role (enum block_type role, struct block *block) +{ + ASSERT (role < BLOCK_ROLE_CNT); + block_by_role[role] = block; +} + +/* Returns the first block device in kernel probe order, or a + null pointer if no block devices are registered. */ +struct block * +block_first (void) +{ + return list_elem_to_block (list_begin (&all_blocks)); +} + +/* Returns the block device following BLOCK in kernel probe + order, or a null pointer if BLOCK is the last block device. */ +struct block * +block_next (struct block *block) +{ + return list_elem_to_block (list_next (&block->list_elem)); +} + +/* Returns the block device with the given NAME, or a null + pointer if no block device has that name. */ +struct block * +block_get_by_name (const char *name) +{ + struct list_elem *e; + + for (e = list_begin (&all_blocks); e != list_end (&all_blocks); + e = list_next (e)) + { + struct block *block = list_entry (e, struct block, list_elem); + if (!strcmp (name, block->name)) + return block; + } + + return NULL; +} + +/* Verifies that SECTOR is a valid offset within BLOCK. + Panics if not. */ +static void +check_sector (struct block *block, block_sector_t sector) +{ + if (sector >= block->size) + { + /* We do not use ASSERT because we want to panic here + regardless of whether NDEBUG is defined. */ + PANIC ("Access past end of device %s (sector=%"PRDSNu", " + "size=%"PRDSNu")\n", block_name (block), sector, block->size); + } +} + +/* Reads sector SECTOR from BLOCK into BUFFER, which must + have room for BLOCK_SECTOR_SIZE bytes. + Internally synchronizes accesses to block devices, so external + per-block device locking is unneeded. */ +void +block_read (struct block *block, block_sector_t sector, void *buffer) +{ + check_sector (block, sector); + block->ops->read (block->aux, sector, buffer); + block->read_cnt++; +} + +/* Write sector SECTOR to BLOCK from BUFFER, which must contain + BLOCK_SECTOR_SIZE bytes. Returns after the block device has + acknowledged receiving the data. + Internally synchronizes accesses to block devices, so external + per-block device locking is unneeded. */ +void +block_write (struct block *block, block_sector_t sector, const void *buffer) +{ + check_sector (block, sector); + ASSERT (block->type != BLOCK_FOREIGN); + block->ops->write (block->aux, sector, buffer); + block->write_cnt++; +} + +/* Returns the number of sectors in BLOCK. */ +block_sector_t +block_size (struct block *block) +{ + return block->size; +} + +/* Returns BLOCK's name (e.g. "hda"). */ +const char * +block_name (struct block *block) +{ + return block->name; +} + +/* Returns BLOCK's type. */ +enum block_type +block_type (struct block *block) +{ + return block->type; +} + +/* Prints statistics for each block device used for a PintOS role. */ +void +block_print_stats (void) +{ + int i; + + for (i = 0; i < BLOCK_ROLE_CNT; i++) + { + struct block *block = block_by_role[i]; + if (block != NULL) + { + printf ("%s (%s): %llu reads, %llu writes\n", + block->name, block_type_name (block->type), + block->read_cnt, block->write_cnt); + } + } +} + +/* Registers a new block device with the given NAME. If + EXTRA_INFO is non-null, it is printed as part of a user + message. The block device's SIZE in sectors and its TYPE must + be provided, as well as the it operation functions OPS, which + will be passed AUX in each function call. */ +struct block * +block_register (const char *name, enum block_type type, + const char *extra_info, block_sector_t size, + const struct block_operations *ops, void *aux) +{ + struct block *block = malloc (sizeof *block); + if (block == NULL) + PANIC ("Failed to allocate memory for block device descriptor"); + + list_push_back (&all_blocks, &block->list_elem); + strlcpy (block->name, name, sizeof block->name); + block->type = type; + block->size = size; + block->ops = ops; + block->aux = aux; + block->read_cnt = 0; + block->write_cnt = 0; + + printf ("%s: %'"PRDSNu" sectors (", block->name, block->size); + print_human_readable_size ((uint64_t) block->size * BLOCK_SECTOR_SIZE); + printf (")"); + if (extra_info != NULL) + printf (", %s", extra_info); + printf ("\n"); + + return block; +} + +/* Returns the block device corresponding to LIST_ELEM, or a null + pointer if LIST_ELEM is the list end of all_blocks. */ +static struct block * +list_elem_to_block (struct list_elem *list_elem) +{ + return (list_elem != list_end (&all_blocks) + ? list_entry (list_elem, struct block, list_elem) + : NULL); +} + diff --git a/tests/devices/src/devices/block.h b/tests/devices/src/devices/block.h new file mode 100644 index 0000000..6932cbd --- /dev/null +++ b/tests/devices/src/devices/block.h @@ -0,0 +1,74 @@ +#ifndef DEVICES_BLOCK_H +#define DEVICES_BLOCK_H + +#include +#include + +/* Size of a block device sector in bytes. + All IDE disks use this sector size, as do most USB and SCSI + disks. It's not worth it to try to cater to other sector + sizes in PintOS (yet). */ +#define BLOCK_SECTOR_SIZE 512 + +/* Index of a block device sector. + Good enough for devices up to 2 TB. */ +typedef uint32_t block_sector_t; + +/* Format specifier for printf(), e.g.: + printf ("sector=%"PRDSNu"\n", sector); */ +#define PRDSNu PRIu32 + +/* Higher-level interface for file systems, etc. */ + +struct block; + +/* Type of a block device. */ +enum block_type + { + /* Block device types that play a role in PintOS. */ + BLOCK_KERNEL, /* PintOS OS kernel. */ + BLOCK_FILESYS, /* File system. */ + BLOCK_SCRATCH, /* Scratch. */ + BLOCK_SWAP, /* Swap. */ + BLOCK_ROLE_CNT, + + /* Other kinds of block devices that PintOS may see but does + not interact with. */ + BLOCK_RAW = BLOCK_ROLE_CNT, /* "Raw" device with unidentified contents. */ + BLOCK_FOREIGN, /* Owned by non-PintOS operating system. */ + BLOCK_CNT /* Number of PintOS block types. */ + }; + +const char *block_type_name (enum block_type); + +/* Finding block devices. */ +struct block *block_get_role (enum block_type); +void block_set_role (enum block_type, struct block *); +struct block *block_get_by_name (const char *name); + +struct block *block_first (void); +struct block *block_next (struct block *); + +/* Block device operations. */ +block_sector_t block_size (struct block *); +void block_read (struct block *, block_sector_t, void *); +void block_write (struct block *, block_sector_t, const void *); +const char *block_name (struct block *); +enum block_type block_type (struct block *); + +/* Statistics. */ +void block_print_stats (void); + +/* Lower-level interface to block device drivers. */ + +struct block_operations + { + void (*read) (void *aux, block_sector_t, void *buffer); + void (*write) (void *aux, block_sector_t, const void *buffer); + }; + +struct block *block_register (const char *name, enum block_type, + const char *extra_info, block_sector_t size, + const struct block_operations *, void *aux); + +#endif /* devices/block.h */ diff --git a/tests/devices/src/devices/ide.c b/tests/devices/src/devices/ide.c new file mode 100644 index 0000000..bad29f7 --- /dev/null +++ b/tests/devices/src/devices/ide.c @@ -0,0 +1,527 @@ +#include "devices/ide.h" +#include +#include +#include +#include +#include "devices/block.h" +#include "devices/partition.h" +#include "devices/timer.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "threads/synch.h" + +/* The code in this file is an interface to an ATA (IDE) + controller. It attempts to comply to [ATA-3]. */ + +/* ATA command block port addresses. */ +#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0) /* Data. */ +#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1) /* Error. */ +#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2) /* Sector Count. */ +#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3) /* LBA 0:7. */ +#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4) /* LBA 15:8. */ +#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5) /* LBA 23:16. */ +#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6) /* Device/LBA 27:24. */ +#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7) /* Status (r/o). */ +#define reg_command(CHANNEL) reg_status (CHANNEL) /* Command (w/o). */ + +/* ATA control block port addresses. + (If we supported non-legacy ATA controllers this would not be + flexible enough, but it's fine for what we do.) */ +#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206) /* Control (w/o). */ +#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL) /* Alt Status (r/o). */ + +/* Alternate Status Register bits. */ +#define STA_BSY 0x80 /* Busy. */ +#define STA_DRDY 0x40 /* Device Ready. */ +#define STA_DRQ 0x08 /* Data Request. */ + +/* Control Register bits. */ +#define CTL_SRST 0x04 /* Software Reset. */ + +/* Device Register bits. */ +#define DEV_MBS 0xa0 /* Must be set. */ +#define DEV_LBA 0x40 /* Linear based addressing. */ +#define DEV_DEV 0x10 /* Select device: 0=master, 1=slave. */ + +/* Commands. + Many more are defined but this is the small subset that we + use. */ +#define CMD_IDENTIFY_DEVICE 0xec /* IDENTIFY DEVICE. */ +#define CMD_READ_SECTOR_RETRY 0x20 /* READ SECTOR with retries. */ +#define CMD_WRITE_SECTOR_RETRY 0x30 /* WRITE SECTOR with retries. */ + +/* An ATA device. */ +struct ata_disk + { + char name[8]; /* Name, e.g. "hda". */ + struct channel *channel; /* Channel that disk is attached to. */ + int dev_no; /* Device 0 or 1 for master or slave. */ + bool is_ata; /* Is device an ATA disk? */ + }; + +/* An ATA channel (aka controller). + Each channel can control up to two disks. */ +struct channel + { + char name[8]; /* Name, e.g. "ide0". */ + uint16_t reg_base; /* Base I/O port. */ + uint8_t irq; /* Interrupt in use. */ + + struct lock lock; /* Must acquire to access the controller. */ + bool expecting_interrupt; /* True if an interrupt is expected, false if + any interrupt would be spurious. */ + struct semaphore completion_wait; /* Up'd by interrupt handler. */ + + struct ata_disk devices[2]; /* The devices on this channel. */ + }; + +/* We support the two "legacy" ATA channels found in a standard PC. */ +#define CHANNEL_CNT 2 +static struct channel channels[CHANNEL_CNT]; + +static struct block_operations ide_operations; + +static void reset_channel (struct channel *); +static bool check_device_type (struct ata_disk *); +static void identify_ata_device (struct ata_disk *); + +static void select_sector (struct ata_disk *, block_sector_t); +static void issue_pio_command (struct channel *, uint8_t command); +static void input_sector (struct channel *, void *); +static void output_sector (struct channel *, const void *); + +static void wait_until_idle (const struct ata_disk *); +static bool wait_while_busy (const struct ata_disk *); +static void select_device (const struct ata_disk *); +static void select_device_wait (const struct ata_disk *); + +static void interrupt_handler (struct intr_frame *); + +/* Initialize the disk subsystem and detect disks. */ +void +ide_init (void) +{ + size_t chan_no; + + for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++) + { + struct channel *c = &channels[chan_no]; + int dev_no; + + /* Initialize channel. */ + snprintf (c->name, sizeof c->name, "ide%zu", chan_no); + switch (chan_no) + { + case 0: + c->reg_base = 0x1f0; + c->irq = 14 + 0x20; + break; + case 1: + c->reg_base = 0x170; + c->irq = 15 + 0x20; + break; + default: + NOT_REACHED (); + } + lock_init (&c->lock); + c->expecting_interrupt = false; + sema_init (&c->completion_wait, 0); + + /* Initialize devices. */ + for (dev_no = 0; dev_no < 2; dev_no++) + { + struct ata_disk *d = &c->devices[dev_no]; + snprintf (d->name, sizeof d->name, + "hd%c", 'a' + chan_no * 2 + dev_no); + d->channel = c; + d->dev_no = dev_no; + d->is_ata = false; + } + + /* Register interrupt handler. */ + intr_register_ext (c->irq, interrupt_handler, c->name); + + /* Reset hardware. */ + reset_channel (c); + + /* Distinguish ATA hard disks from other devices. */ + if (check_device_type (&c->devices[0])) + check_device_type (&c->devices[1]); + + /* Read hard disk identity information. */ + for (dev_no = 0; dev_no < 2; dev_no++) + if (c->devices[dev_no].is_ata) + identify_ata_device (&c->devices[dev_no]); + } +} + +/* Disk detection and identification. */ + +static char *descramble_ata_string (char *, int size); + +/* Resets an ATA channel and waits for any devices present on it + to finish the reset. */ +static void +reset_channel (struct channel *c) +{ + bool present[2]; + int dev_no; + + /* The ATA reset sequence depends on which devices are present, + so we start by detecting device presence. */ + for (dev_no = 0; dev_no < 2; dev_no++) + { + struct ata_disk *d = &c->devices[dev_no]; + + select_device (d); + + outb (reg_nsect (c), 0x55); + outb (reg_lbal (c), 0xaa); + + outb (reg_nsect (c), 0xaa); + outb (reg_lbal (c), 0x55); + + outb (reg_nsect (c), 0x55); + outb (reg_lbal (c), 0xaa); + + present[dev_no] = (inb (reg_nsect (c)) == 0x55 + && inb (reg_lbal (c)) == 0xaa); + } + + /* Issue soft reset sequence, which selects device 0 as a side effect. + Also enable interrupts. */ + outb (reg_ctl (c), 0); + timer_usleep (10); + outb (reg_ctl (c), CTL_SRST); + timer_usleep (10); + outb (reg_ctl (c), 0); + + timer_msleep (150); + + /* Wait for device 0 to clear BSY. */ + if (present[0]) + { + select_device (&c->devices[0]); + wait_while_busy (&c->devices[0]); + } + + /* Wait for device 1 to clear BSY. */ + if (present[1]) + { + int i; + + select_device (&c->devices[1]); + for (i = 0; i < 3000; i++) + { + if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1) + break; + timer_msleep (10); + } + wait_while_busy (&c->devices[1]); + } +} + +/* Checks whether device D is an ATA disk and sets D's is_ata + member appropriately. If D is device 0 (master), returns true + if it's possible that a slave (device 1) exists on this + channel. If D is device 1 (slave), the return value is not + meaningful. */ +static bool +check_device_type (struct ata_disk *d) +{ + struct channel *c = d->channel; + uint8_t error, lbam, lbah, status; + + select_device (d); + + error = inb (reg_error (c)); + lbam = inb (reg_lbam (c)); + lbah = inb (reg_lbah (c)); + status = inb (reg_status (c)); + + if ((error != 1 && (error != 0x81 || d->dev_no == 1)) + || (status & STA_DRDY) == 0 + || (status & STA_BSY) != 0) + { + d->is_ata = false; + return error != 0x81; + } + else + { + d->is_ata = (lbam == 0 && lbah == 0) || (lbam == 0x3c && lbah == 0xc3); + return true; + } +} + +/* Sends an IDENTIFY DEVICE command to disk D and reads the + response. Registers the disk with the block device + layer. */ +static void +identify_ata_device (struct ata_disk *d) +{ + struct channel *c = d->channel; + char id[BLOCK_SECTOR_SIZE]; + block_sector_t capacity; + char *model, *serial; + char extra_info[128]; + struct block *block; + + ASSERT (d->is_ata); + + /* Send the IDENTIFY DEVICE command, wait for an interrupt + indicating the device's response is ready, and read the data + into our buffer. */ + select_device_wait (d); + issue_pio_command (c, CMD_IDENTIFY_DEVICE); + sema_down (&c->completion_wait); + if (!wait_while_busy (d)) + { + d->is_ata = false; + return; + } + input_sector (c, id); + + /* Calculate capacity. + Read model name and serial number. */ + capacity = *(uint32_t *) &id[60 * 2]; + model = descramble_ata_string (&id[10 * 2], 20); + serial = descramble_ata_string (&id[27 * 2], 40); + snprintf (extra_info, sizeof extra_info, + "model \"%s\", serial \"%s\"", model, serial); + + /* Disable access to IDE disks over 1 GB, which are likely + physical IDE disks rather than virtual ones. If we don't + allow access to those, we're less likely to scribble on + someone's important data. You can disable this check by + hand if you really want to do so. */ + if (capacity >= 1024 * 1024 * 1024 / BLOCK_SECTOR_SIZE) + { + printf ("%s: ignoring ", d->name); + print_human_readable_size (capacity * 512); + printf ("disk for safety\n"); + d->is_ata = false; + return; + } + + /* Register. */ + block = block_register (d->name, BLOCK_RAW, extra_info, capacity, + &ide_operations, d); + partition_scan (block); +} + +/* Translates STRING, which consists of SIZE bytes in a funky + format, into a null-terminated string in-place. Drops + trailing whitespace and null bytes. Returns STRING. */ +static char * +descramble_ata_string (char *string, int size) +{ + int i; + + /* Swap all pairs of bytes. */ + for (i = 0; i + 1 < size; i += 2) + { + char tmp = string[i]; + string[i] = string[i + 1]; + string[i + 1] = tmp; + } + + /* Find the last non-white, non-null character. */ + for (size--; size > 0; size--) + { + int c = string[size - 1]; + if (c != '\0' && !isspace (c)) + break; + } + string[size] = '\0'; + + return string; +} + +/* Reads sector SEC_NO from disk D into BUFFER, which must have + room for BLOCK_SECTOR_SIZE bytes. + Internally synchronizes accesses to disks, so external + per-disk locking is unneeded. */ +static void +ide_read (void *d_, block_sector_t sec_no, void *buffer) +{ + struct ata_disk *d = d_; + struct channel *c = d->channel; + lock_acquire (&c->lock); + select_sector (d, sec_no); + issue_pio_command (c, CMD_READ_SECTOR_RETRY); + sema_down (&c->completion_wait); + if (!wait_while_busy (d)) + PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no); + input_sector (c, buffer); + lock_release (&c->lock); +} + +/* Write sector SEC_NO to disk D from BUFFER, which must contain + BLOCK_SECTOR_SIZE bytes. Returns after the disk has + acknowledged receiving the data. + Internally synchronizes accesses to disks, so external + per-disk locking is unneeded. */ +static void +ide_write (void *d_, block_sector_t sec_no, const void *buffer) +{ + struct ata_disk *d = d_; + struct channel *c = d->channel; + lock_acquire (&c->lock); + select_sector (d, sec_no); + issue_pio_command (c, CMD_WRITE_SECTOR_RETRY); + if (!wait_while_busy (d)) + PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no); + output_sector (c, buffer); + sema_down (&c->completion_wait); + lock_release (&c->lock); +} + +static struct block_operations ide_operations = + { + ide_read, + ide_write + }; + +/* Selects device D, waiting for it to become ready, and then + writes SEC_NO to the disk's sector selection registers. (We + use LBA mode.) */ +static void +select_sector (struct ata_disk *d, block_sector_t sec_no) +{ + struct channel *c = d->channel; + + ASSERT (sec_no < (1UL << 28)); + + select_device_wait (d); + outb (reg_nsect (c), 1); + outb (reg_lbal (c), sec_no); + outb (reg_lbam (c), sec_no >> 8); + outb (reg_lbah (c), (sec_no >> 16)); + outb (reg_device (c), + DEV_MBS | DEV_LBA | (d->dev_no == 1 ? DEV_DEV : 0) | (sec_no >> 24)); +} + +/* Writes COMMAND to channel C and prepares for receiving a + completion interrupt. */ +static void +issue_pio_command (struct channel *c, uint8_t command) +{ + /* Interrupts must be enabled or our semaphore will never be + up'd by the completion handler. */ + ASSERT (intr_get_level () == INTR_ON); + + c->expecting_interrupt = true; + outb (reg_command (c), command); +} + +/* Reads a sector from channel C's data register in PIO mode into + SECTOR, which must have room for BLOCK_SECTOR_SIZE bytes. */ +static void +input_sector (struct channel *c, void *sector) +{ + insw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2); +} + +/* Writes SECTOR to channel C's data register in PIO mode. + SECTOR must contain BLOCK_SECTOR_SIZE bytes. */ +static void +output_sector (struct channel *c, const void *sector) +{ + outsw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2); +} + +/* Low-level ATA primitives. */ + +/* Wait up to 10 seconds for the controller to become idle, that + is, for the BSY and DRQ bits to clear in the status register. + + As a side effect, reading the status register clears any + pending interrupt. */ +static void +wait_until_idle (const struct ata_disk *d) +{ + int i; + + for (i = 0; i < 1000; i++) + { + if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0) + return; + timer_usleep (10); + } + + printf ("%s: idle timeout\n", d->name); +} + +/* Wait up to 30 seconds for disk D to clear BSY, + and then return the status of the DRQ bit. + The ATA standards say that a disk may take as long as that to + complete its reset. */ +static bool +wait_while_busy (const struct ata_disk *d) +{ + struct channel *c = d->channel; + int i; + + for (i = 0; i < 3000; i++) + { + if (i == 700) + printf ("%s: busy, waiting...", d->name); + if (!(inb (reg_alt_status (c)) & STA_BSY)) + { + if (i >= 700) + printf ("ok\n"); + return (inb (reg_alt_status (c)) & STA_DRQ) != 0; + } + timer_msleep (10); + } + + printf ("failed\n"); + return false; +} + +/* Program D's channel so that D is now the selected disk. */ +static void +select_device (const struct ata_disk *d) +{ + struct channel *c = d->channel; + uint8_t dev = DEV_MBS; + if (d->dev_no == 1) + dev |= DEV_DEV; + outb (reg_device (c), dev); + inb (reg_alt_status (c)); + timer_nsleep (400); +} + +/* Select disk D in its channel, as select_device(), but wait for + the channel to become idle before and after. */ +static void +select_device_wait (const struct ata_disk *d) +{ + wait_until_idle (d); + select_device (d); + wait_until_idle (d); +} + +/* ATA interrupt handler. */ +static void +interrupt_handler (struct intr_frame *f) +{ + struct channel *c; + + for (c = channels; c < channels + CHANNEL_CNT; c++) + if (f->vec_no == c->irq) + { + if (c->expecting_interrupt) + { + inb (reg_status (c)); /* Acknowledge interrupt. */ + sema_up (&c->completion_wait); /* Wake up waiter. */ + } + else + printf ("%s: unexpected interrupt\n", c->name); + return; + } + + NOT_REACHED (); +} + + diff --git a/tests/devices/src/devices/ide.h b/tests/devices/src/devices/ide.h new file mode 100644 index 0000000..b35da5e --- /dev/null +++ b/tests/devices/src/devices/ide.h @@ -0,0 +1,6 @@ +#ifndef DEVICES_IDE_H +#define DEVICES_IDE_H + +void ide_init (void); + +#endif /* devices/ide.h */ diff --git a/tests/devices/src/devices/input.c b/tests/devices/src/devices/input.c new file mode 100644 index 0000000..4a12160 --- /dev/null +++ b/tests/devices/src/devices/input.c @@ -0,0 +1,52 @@ +#include "devices/input.h" +#include +#include "devices/intq.h" +#include "devices/serial.h" + +/* Stores keys from the keyboard and serial port. */ +static struct intq buffer; + +/* Initializes the input buffer. */ +void +input_init (void) +{ + intq_init (&buffer); +} + +/* Adds a key to the input buffer. + Interrupts must be off and the buffer must not be full. */ +void +input_putc (uint8_t key) +{ + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (!intq_full (&buffer)); + + intq_putc (&buffer, key); + serial_notify (); +} + +/* Retrieves a key from the input buffer. + If the buffer is empty, waits for a key to be pressed. */ +uint8_t +input_getc (void) +{ + enum intr_level old_level; + uint8_t key; + + old_level = intr_disable (); + key = intq_getc (&buffer); + serial_notify (); + intr_set_level (old_level); + + return key; +} + +/* Returns true if the input buffer is full, + false otherwise. + Interrupts must be off. */ +bool +input_full (void) +{ + ASSERT (intr_get_level () == INTR_OFF); + return intq_full (&buffer); +} diff --git a/tests/devices/src/devices/input.h b/tests/devices/src/devices/input.h new file mode 100644 index 0000000..a2f50e9 --- /dev/null +++ b/tests/devices/src/devices/input.h @@ -0,0 +1,12 @@ +#ifndef DEVICES_INPUT_H +#define DEVICES_INPUT_H + +#include +#include + +void input_init (void); +void input_putc (uint8_t); +uint8_t input_getc (void); +bool input_full (void); + +#endif /* devices/input.h */ diff --git a/tests/devices/src/devices/intq.c b/tests/devices/src/devices/intq.c new file mode 100644 index 0000000..16dc71c --- /dev/null +++ b/tests/devices/src/devices/intq.c @@ -0,0 +1,114 @@ +#include "devices/intq.h" +#include +#include "threads/thread.h" + +static int next (int pos); +static void wait (struct intq *q, struct thread **waiter); +static void signal (struct intq *q, struct thread **waiter); + +/* Initializes interrupt queue Q. */ +void +intq_init (struct intq *q) +{ + lock_init (&q->lock); + q->not_full = q->not_empty = NULL; + q->head = q->tail = 0; +} + +/* Returns true if Q is empty, false otherwise. */ +bool +intq_empty (const struct intq *q) +{ + ASSERT (intr_get_level () == INTR_OFF); + return q->head == q->tail; +} + +/* Returns true if Q is full, false otherwise. */ +bool +intq_full (const struct intq *q) +{ + ASSERT (intr_get_level () == INTR_OFF); + return next (q->head) == q->tail; +} + +/* Removes a byte from Q and returns it. + If Q is empty, sleeps until a byte is added. + When called from an interrupt handler, Q must not be empty. */ +uint8_t +intq_getc (struct intq *q) +{ + uint8_t byte; + + ASSERT (intr_get_level () == INTR_OFF); + while (intq_empty (q)) + { + ASSERT (!intr_context ()); + lock_acquire (&q->lock); + wait (q, &q->not_empty); + lock_release (&q->lock); + } + + byte = q->buf[q->tail]; + q->tail = next (q->tail); + signal (q, &q->not_full); + return byte; +} + +/* Adds BYTE to the end of Q. + If Q is full, sleeps until a byte is removed. + When called from an interrupt handler, Q must not be full. */ +void +intq_putc (struct intq *q, uint8_t byte) +{ + ASSERT (intr_get_level () == INTR_OFF); + while (intq_full (q)) + { + ASSERT (!intr_context ()); + lock_acquire (&q->lock); + wait (q, &q->not_full); + lock_release (&q->lock); + } + + q->buf[q->head] = byte; + q->head = next (q->head); + signal (q, &q->not_empty); +} + +/* Returns the position after POS within an intq. */ +static int +next (int pos) +{ + return (pos + 1) % INTQ_BUFSIZE; +} + +/* WAITER must be the address of Q's not_empty or not_full + member. Waits until the given condition is true. */ +static void +wait (struct intq *q UNUSED, struct thread **waiter) +{ + ASSERT (!intr_context ()); + ASSERT (intr_get_level () == INTR_OFF); + ASSERT ((waiter == &q->not_empty && intq_empty (q)) + || (waiter == &q->not_full && intq_full (q))); + + *waiter = thread_current (); + thread_block (); +} + +/* WAITER must be the address of Q's not_empty or not_full + member, and the associated condition must be true. If a + thread is waiting for the condition, wakes it up and resets + the waiting thread. */ +static void +signal (struct intq *q UNUSED, struct thread **waiter) +{ + ASSERT (intr_get_level () == INTR_OFF); + ASSERT ((waiter == &q->not_empty && !intq_empty (q)) + || (waiter == &q->not_full && !intq_full (q))); + + if (*waiter != NULL) + { + thread_unblock (*waiter); + *waiter = NULL; + } +} diff --git a/tests/devices/src/devices/intq.h b/tests/devices/src/devices/intq.h new file mode 100644 index 0000000..2312b12 --- /dev/null +++ b/tests/devices/src/devices/intq.h @@ -0,0 +1,43 @@ +#ifndef DEVICES_INTQ_H +#define DEVICES_INTQ_H + +#include "threads/interrupt.h" +#include "threads/synch.h" + +/* An "interrupt queue", a circular buffer shared between + kernel threads and external interrupt handlers. + + Interrupt queue functions can be called from kernel threads or + from external interrupt handlers. Except for intq_init(), + interrupts must be off in either case. + + The interrupt queue has the structure of a "monitor". Locks + and condition variables from threads/synch.h cannot be used in + this case, as they normally would, because they can only + protect kernel threads from one another, not from interrupt + handlers. */ + +/* Queue buffer size, in bytes. */ +#define INTQ_BUFSIZE 64 + +/* A circular queue of bytes. */ +struct intq + { + /* Waiting threads. */ + struct lock lock; /* Only one thread may wait at once. */ + struct thread *not_full; /* Thread waiting for not-full condition. */ + struct thread *not_empty; /* Thread waiting for not-empty condition. */ + + /* Queue. */ + uint8_t buf[INTQ_BUFSIZE]; /* Buffer. */ + int head; /* New data is written here. */ + int tail; /* Old data is read here. */ + }; + +void intq_init (struct intq *); +bool intq_empty (const struct intq *); +bool intq_full (const struct intq *); +uint8_t intq_getc (struct intq *); +void intq_putc (struct intq *, uint8_t); + +#endif /* devices/intq.h */ diff --git a/tests/devices/src/devices/kbd.c b/tests/devices/src/devices/kbd.c new file mode 100644 index 0000000..efe1718 --- /dev/null +++ b/tests/devices/src/devices/kbd.c @@ -0,0 +1,213 @@ +#include "devices/kbd.h" +#include +#include +#include +#include +#include "devices/input.h" +#include "devices/shutdown.h" +#include "threads/interrupt.h" +#include "threads/io.h" + +/* Keyboard data register port. */ +#define DATA_REG 0x60 + +/* Current state of shift keys. + True if depressed, false otherwise. */ +static bool left_shift, right_shift; /* Left and right Shift keys. */ +static bool left_alt, right_alt; /* Left and right Alt keys. */ +static bool left_ctrl, right_ctrl; /* Left and right Ctl keys. */ + +/* Status of Caps Lock. + True when on, false when off. */ +static bool caps_lock; + +/* Number of keys pressed. */ +static int64_t key_cnt; + +static intr_handler_func keyboard_interrupt; + +/* Initializes the keyboard. */ +void +kbd_init (void) +{ + intr_register_ext (0x21, keyboard_interrupt, "8042 Keyboard"); +} + +/* Prints keyboard statistics. */ +void +kbd_print_stats (void) +{ + printf ("Keyboard: %lld keys pressed\n", key_cnt); +} + +/* Maps a set of contiguous scancodes into characters. */ +struct keymap + { + uint8_t first_scancode; /* First scancode. */ + const char *chars; /* chars[0] has scancode first_scancode, + chars[1] has scancode first_scancode + 1, + and so on to the end of the string. */ + }; + +/* Keys that produce the same characters regardless of whether + the Shift keys are down. Case of letters is an exception + that we handle elsewhere. */ +static const struct keymap invariant_keymap[] = + { + {0x01, "\033"}, /* Escape. */ + {0x0e, "\b"}, + {0x0f, "\tQWERTYUIOP"}, + {0x1c, "\r"}, + {0x1e, "ASDFGHJKL"}, + {0x2c, "ZXCVBNM"}, + {0x37, "*"}, + {0x39, " "}, + {0x53, "\177"}, /* Delete. */ + {0, NULL}, + }; + +/* Characters for keys pressed without Shift, for those keys + where it matters. */ +static const struct keymap unshifted_keymap[] = + { + {0x02, "1234567890-="}, + {0x1a, "[]"}, + {0x27, ";'`"}, + {0x2b, "\\"}, + {0x33, ",./"}, + {0, NULL}, + }; + +/* Characters for keys pressed with Shift, for those keys where + it matters. */ +static const struct keymap shifted_keymap[] = + { + {0x02, "!@#$%^&*()_+"}, + {0x1a, "{}"}, + {0x27, ":\"~"}, + {0x2b, "|"}, + {0x33, "<>?"}, + {0, NULL}, + }; + +static bool map_key (const struct keymap[], unsigned scancode, uint8_t *); + +static void +keyboard_interrupt (struct intr_frame *args UNUSED) +{ + /* Status of shift keys. */ + bool shift = left_shift || right_shift; + bool alt = left_alt || right_alt; + bool ctrl = left_ctrl || right_ctrl; + + /* Keyboard scancode. */ + unsigned code; + + /* False if key pressed, true if key released. */ + bool release; + + /* Character that corresponds to `code'. */ + uint8_t c; + + /* Read scancode, including second byte if prefix code. */ + code = inb (DATA_REG); + if (code == 0xe0) + code = (code << 8) | inb (DATA_REG); + + /* Bit 0x80 distinguishes key press from key release + (even if there's a prefix). */ + release = (code & 0x80) != 0; + code &= ~0x80u; + + /* Interpret key. */ + if (code == 0x3a) + { + /* Caps Lock. */ + if (!release) + caps_lock = !caps_lock; + } + else if (map_key (invariant_keymap, code, &c) + || (!shift && map_key (unshifted_keymap, code, &c)) + || (shift && map_key (shifted_keymap, code, &c))) + { + /* Ordinary character. */ + if (!release) + { + /* Reboot if Ctrl+Alt+Del pressed. */ + if (c == 0177 && ctrl && alt) + shutdown_reboot (); + + /* Handle Ctrl, Shift. + Note that Ctrl overrides Shift. */ + if (ctrl && c >= 0x40 && c < 0x60) + { + /* A is 0x41, Ctrl+A is 0x01, etc. */ + c -= 0x40; + } + else if (shift == caps_lock) + c = tolower (c); + + /* Handle Alt by setting the high bit. + This 0x80 is unrelated to the one used to + distinguish key press from key release. */ + if (alt) + c += 0x80; + + /* Append to keyboard buffer. */ + if (!input_full ()) + { + key_cnt++; + input_putc (c); + } + } + } + else + { + /* Maps a keycode into a shift state variable. */ + struct shift_key + { + unsigned scancode; + bool *state_var; + }; + + /* Table of shift keys. */ + static const struct shift_key shift_keys[] = + { + { 0x2a, &left_shift}, + { 0x36, &right_shift}, + { 0x38, &left_alt}, + {0xe038, &right_alt}, + { 0x1d, &left_ctrl}, + {0xe01d, &right_ctrl}, + {0, NULL}, + }; + + const struct shift_key *key; + + /* Scan the table. */ + for (key = shift_keys; key->scancode != 0; key++) + if (key->scancode == code) + { + *key->state_var = !release; + break; + } + } +} + +/* Scans the array of keymaps K for SCANCODE. + If found, sets *C to the corresponding character and returns + true. + If not found, returns false and C is ignored. */ +static bool +map_key (const struct keymap k[], unsigned scancode, uint8_t *c) +{ + for (; k->first_scancode != 0; k++) + if (scancode >= k->first_scancode + && scancode < k->first_scancode + strlen (k->chars)) + { + *c = k->chars[scancode - k->first_scancode]; + return true; + } + + return false; +} diff --git a/tests/devices/src/devices/kbd.h b/tests/devices/src/devices/kbd.h new file mode 100644 index 0000000..ed9c06b --- /dev/null +++ b/tests/devices/src/devices/kbd.h @@ -0,0 +1,9 @@ +#ifndef DEVICES_KBD_H +#define DEVICES_KBD_H + +#include + +void kbd_init (void); +void kbd_print_stats (void); + +#endif /* devices/kbd.h */ diff --git a/tests/devices/src/devices/partition.c b/tests/devices/src/devices/partition.c new file mode 100644 index 0000000..36f45f6 --- /dev/null +++ b/tests/devices/src/devices/partition.c @@ -0,0 +1,324 @@ +#include "devices/partition.h" +#include +#include +#include +#include +#include "devices/block.h" +#include "threads/malloc.h" + +/* A partition of a block device. */ +struct partition + { + struct block *block; /* Underlying block device. */ + block_sector_t start; /* First sector within device. */ + }; + +static struct block_operations partition_operations; + +static void read_partition_table (struct block *, block_sector_t sector, + block_sector_t primary_extended_sector, + int *part_nr); +static void found_partition (struct block *, uint8_t type, + block_sector_t start, block_sector_t size, + int part_nr); +static const char *partition_type_name (uint8_t); + +/* Scans BLOCK for partitions of interest to PintOS. */ +void +partition_scan (struct block *block) +{ + int part_nr = 0; + read_partition_table (block, 0, 0, &part_nr); + if (part_nr == 0) + printf ("%s: Device contains no partitions\n", block_name (block)); +} + +/* Reads the partition table in the given SECTOR of BLOCK and + scans it for partitions of interest to PintOS. + + If SECTOR is 0, so that this is the top-level partition table + on BLOCK, then PRIMARY_EXTENDED_SECTOR is not meaningful; + otherwise, it should designate the sector of the top-level + extended partition table that was traversed to arrive at + SECTOR, for use in finding logical partitions (see the large + comment below). + + PART_NR points to the number of non-empty primary or logical + partitions already encountered on BLOCK. It is incremented as + partitions are found. */ +static void +read_partition_table (struct block *block, block_sector_t sector, + block_sector_t primary_extended_sector, + int *part_nr) +{ + /* Format of a partition table entry. See [Partitions]. */ + struct partition_table_entry + { + uint8_t bootable; /* 0x00=not bootable, 0x80=bootable. */ + uint8_t start_chs[3]; /* Encoded starting cylinder, head, sector. */ + uint8_t type; /* Partition type (see partition_type_name). */ + uint8_t end_chs[3]; /* Encoded ending cylinder, head, sector. */ + uint32_t offset; /* Start sector offset from partition table. */ + uint32_t size; /* Number of sectors. */ + } + PACKED; + + /* Partition table sector. */ + struct partition_table + { + uint8_t loader[446]; /* Loader, in top-level partition table. */ + struct partition_table_entry partitions[4]; /* Table entries. */ + uint16_t signature; /* Should be 0xaa55. */ + } + PACKED; + + struct partition_table *pt; + size_t i; + + /* Check SECTOR validity. */ + if (sector >= block_size (block)) + { + printf ("%s: Partition table at sector %"PRDSNu" past end of device.\n", + block_name (block), sector); + return; + } + + /* Read sector. */ + ASSERT (sizeof *pt == BLOCK_SECTOR_SIZE); + pt = malloc (sizeof *pt); + if (pt == NULL) + PANIC ("Failed to allocate memory for partition table."); + block_read (block, 0, pt); + + /* Check signature. */ + if (pt->signature != 0xaa55) + { + if (primary_extended_sector == 0) + printf ("%s: Invalid partition table signature\n", block_name (block)); + else + printf ("%s: Invalid extended partition table in sector %"PRDSNu"\n", + block_name (block), sector); + free (pt); + return; + } + + /* Parse partitions. */ + for (i = 0; i < sizeof pt->partitions / sizeof *pt->partitions; i++) + { + struct partition_table_entry *e = &pt->partitions[i]; + + if (e->size == 0 || e->type == 0) + { + /* Ignore empty partition. */ + } + else if (e->type == 0x05 /* Extended partition. */ + || e->type == 0x0f /* Windows 98 extended partition. */ + || e->type == 0x85 /* Linux extended partition. */ + || e->type == 0xc5) /* DR-DOS extended partition. */ + { + printf ("%s: Extended partition in sector %"PRDSNu"\n", + block_name (block), sector); + + /* The interpretation of the offset field for extended + partitions is bizarre. When the extended partition + table entry is in the master boot record, that is, + the device's primary partition table in sector 0, then + the offset is an absolute sector number. Otherwise, + no matter how deep the partition table we're reading + is nested, the offset is relative to the start of + the extended partition that the MBR points to. */ + if (sector == 0) + read_partition_table (block, e->offset, e->offset, part_nr); + else + read_partition_table (block, e->offset + primary_extended_sector, + primary_extended_sector, part_nr); + } + else + { + ++*part_nr; + + found_partition (block, e->type, e->offset + sector, + e->size, *part_nr); + } + } + + free (pt); +} + +/* We have found a primary or logical partition of the given TYPE + on BLOCK, starting at sector START and continuing for SIZE + sectors, which we are giving the partition number PART_NR. + Check whether this is a partition of interest to PintOS, and + if so then add it to the proper element of partitions[]. */ +static void +found_partition (struct block *block, uint8_t part_type, + block_sector_t start, block_sector_t size, + int part_nr) +{ + if (start >= block_size (block)) + printf ("%s%d: Partition starts past end of device (sector %"PRDSNu")\n", + block_name (block), part_nr, start); + else if (start + size < start || start + size > block_size (block)) + printf ("%s%d: Partition end (%"PRDSNu") past end of device (%"PRDSNu")\n", + block_name (block), part_nr, start + size, block_size (block)); + else + { + enum block_type type = (part_type == 0x20 ? BLOCK_KERNEL + : part_type == 0x21 ? BLOCK_FILESYS + : part_type == 0x22 ? BLOCK_SCRATCH + : part_type == 0x23 ? BLOCK_SWAP + : BLOCK_FOREIGN); + struct partition *p; + char extra_info[128]; + char name[16]; + + p = malloc (sizeof *p); + if (p == NULL) + PANIC ("Failed to allocate memory for partition descriptor"); + p->block = block; + p->start = start; + + snprintf (name, sizeof name, "%s%d", block_name (block), part_nr); + snprintf (extra_info, sizeof extra_info, "%s (%02x)", + partition_type_name (part_type), part_type); + block_register (name, type, extra_info, size, &partition_operations, p); + } +} + +/* Returns a human-readable name for the given partition TYPE. */ +static const char * +partition_type_name (uint8_t type) +{ + /* Name of each known type of partition. + From util-linux-2.12r/fdisk/i386_sys_types.c. + This initializer makes use of a C99 feature that allows + array elements to be initialized by index. */ + static const char *type_names[256] = + { + [0x00] = "Empty", + [0x01] = "FAT12", + [0x02] = "XENIX root", + [0x03] = "XENIX usr", + [0x04] = "FAT16 <32M", + [0x05] = "Extended", + [0x06] = "FAT16", + [0x07] = "HPFS/NTFS", + [0x08] = "AIX", + [0x09] = "AIX bootable", + [0x0a] = "OS/2 Boot Manager", + [0x0b] = "W95 FAT32", + [0x0c] = "W95 FAT32 (LBA)", + [0x0e] = "W95 FAT16 (LBA)", + [0x0f] = "W95 Ext'd (LBA)", + [0x10] = "OPUS", + [0x11] = "Hidden FAT12", + [0x12] = "Compaq diagnostics", + [0x14] = "Hidden FAT16 <32M", + [0x16] = "Hidden FAT16", + [0x17] = "Hidden HPFS/NTFS", + [0x18] = "AST SmartSleep", + [0x1b] = "Hidden W95 FAT32", + [0x1c] = "Hidden W95 FAT32 (LBA)", + [0x1e] = "Hidden W95 FAT16 (LBA)", + [0x20] = "PintOS OS kernel", + [0x21] = "PintOS file system", + [0x22] = "PintOS scratch", + [0x23] = "PintOS swap", + [0x24] = "NEC DOS", + [0x39] = "Plan 9", + [0x3c] = "PartitionMagic recovery", + [0x40] = "Venix 80286", + [0x41] = "PPC PReP Boot", + [0x42] = "SFS", + [0x4d] = "QNX4.x", + [0x4e] = "QNX4.x 2nd part", + [0x4f] = "QNX4.x 3rd part", + [0x50] = "OnTrack DM", + [0x51] = "OnTrack DM6 Aux1", + [0x52] = "CP/M", + [0x53] = "OnTrack DM6 Aux3", + [0x54] = "OnTrackDM6", + [0x55] = "EZ-Drive", + [0x56] = "Golden Bow", + [0x5c] = "Priam Edisk", + [0x61] = "SpeedStor", + [0x63] = "GNU HURD or SysV", + [0x64] = "Novell Netware 286", + [0x65] = "Novell Netware 386", + [0x70] = "DiskSecure Multi-Boot", + [0x75] = "PC/IX", + [0x80] = "Old Minix", + [0x81] = "Minix / old Linux", + [0x82] = "Linux swap / Solaris", + [0x83] = "Linux", + [0x84] = "OS/2 hidden C: drive", + [0x85] = "Linux extended", + [0x86] = "NTFS volume set", + [0x87] = "NTFS volume set", + [0x88] = "Linux plaintext", + [0x8e] = "Linux LVM", + [0x93] = "Amoeba", + [0x94] = "Amoeba BBT", + [0x9f] = "BSD/OS", + [0xa0] = "IBM Thinkpad hibernation", + [0xa5] = "FreeBSD", + [0xa6] = "OpenBSD", + [0xa7] = "NeXTSTEP", + [0xa8] = "Darwin UFS", + [0xa9] = "NetBSD", + [0xab] = "Darwin boot", + [0xb7] = "BSDI fs", + [0xb8] = "BSDI swap", + [0xbb] = "Boot Wizard hidden", + [0xbe] = "Solaris boot", + [0xbf] = "Solaris", + [0xc1] = "DRDOS/sec (FAT-12)", + [0xc4] = "DRDOS/sec (FAT-16 < 32M)", + [0xc6] = "DRDOS/sec (FAT-16)", + [0xc7] = "Syrinx", + [0xda] = "Non-FS data", + [0xdb] = "CP/M / CTOS / ...", + [0xde] = "Dell Utility", + [0xdf] = "BootIt", + [0xe1] = "DOS access", + [0xe3] = "DOS R/O", + [0xe4] = "SpeedStor", + [0xeb] = "BeOS fs", + [0xee] = "EFI GPT", + [0xef] = "EFI (FAT-12/16/32)", + [0xf0] = "Linux/PA-RISC boot", + [0xf1] = "SpeedStor", + [0xf4] = "SpeedStor", + [0xf2] = "DOS secondary", + [0xfd] = "Linux raid autodetect", + [0xfe] = "LANstep", + [0xff] = "BBT", + }; + + return type_names[type] != NULL ? type_names[type] : "Unknown"; +} + +/* Reads sector SECTOR from partition P into BUFFER, which must + have room for BLOCK_SECTOR_SIZE bytes. */ +static void +partition_read (void *p_, block_sector_t sector, void *buffer) +{ + struct partition *p = p_; + block_read (p->block, p->start + sector, buffer); +} + +/* Write sector SECTOR to partition P from BUFFER, which must + contain BLOCK_SECTOR_SIZE bytes. Returns after the block has + acknowledged receiving the data. */ +static void +partition_write (void *p_, block_sector_t sector, const void *buffer) +{ + struct partition *p = p_; + block_write (p->block, p->start + sector, buffer); +} + +static struct block_operations partition_operations = + { + partition_read, + partition_write + }; diff --git a/tests/devices/src/devices/partition.h b/tests/devices/src/devices/partition.h new file mode 100644 index 0000000..47fea4d --- /dev/null +++ b/tests/devices/src/devices/partition.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_PARTITION_H +#define DEVICES_PARTITION_H + +struct block; + +void partition_scan (struct block *); + +#endif /* devices/partition.h */ diff --git a/tests/devices/src/devices/pit.c b/tests/devices/src/devices/pit.c new file mode 100644 index 0000000..7634c28 --- /dev/null +++ b/tests/devices/src/devices/pit.c @@ -0,0 +1,83 @@ +#include "devices/pit.h" +#include +#include +#include "threads/interrupt.h" +#include "threads/io.h" + +/* Interface to 8254 Programmable Interrupt Timer (PIT). + Refer to [8254] for details. */ + +/* 8254 registers. */ +#define PIT_PORT_CONTROL 0x43 /* Control port. */ +#define PIT_PORT_COUNTER(CHANNEL) (0x40 + (CHANNEL)) /* Counter port. */ + +/* PIT cycles per second. */ +#define PIT_HZ 1193180 + +/* Configure the given CHANNEL in the PIT. In a PC, the PIT's + three output channels are hooked up like this: + + - Channel 0 is connected to interrupt line 0, so that it can + be used as a periodic timer interrupt, as implemented in + PintOS in devices/timer.c. + + - Channel 1 is used for dynamic RAM refresh (in older PCs). + No good can come of messing with this. + + - Channel 2 is connected to the PC speaker, so that it can + be used to play a tone, as implemented in PintOS in + devices/speaker.c. + + MODE specifies the form of output: + + - Mode 2 is a periodic pulse: the channel's output is 1 for + most of the period, but drops to 0 briefly toward the end + of the period. This is useful for hooking up to an + interrupt controller to generate a periodic interrupt. + + - Mode 3 is a square wave: for the first half of the period + it is 1, for the second half it is 0. This is useful for + generating a tone on a speaker. + + - Other modes are less useful. + + FREQUENCY is the number of periods per second, in Hz. */ +void +pit_configure_channel (int channel, int mode, int frequency) +{ + uint16_t count; + enum intr_level old_level; + + ASSERT (channel == 0 || channel == 2); + ASSERT (mode == 2 || mode == 3); + + /* Convert FREQUENCY to a PIT counter value. The PIT has a + clock that runs at PIT_HZ cycles per second. We must + translate FREQUENCY into a number of these cycles. */ + if (frequency < 19) + { + /* Frequency is too low: the quotient would overflow the + 16-bit counter. Force it to 0, which the PIT treats as + 65536, the highest possible count. This yields a 18.2 + Hz timer, approximately. */ + count = 0; + } + else if (frequency > PIT_HZ) + { + /* Frequency is too high: the quotient would underflow to + 0, which the PIT would interpret as 65536. A count of 1 + is illegal in mode 2, so we force it to 2, which yields + a 596.590 kHz timer, approximately. (This timer rate is + probably too fast to be useful anyhow.) */ + count = 2; + } + else + count = (PIT_HZ + frequency / 2) / frequency; + + /* Configure the PIT mode and load its counters. */ + old_level = intr_disable (); + outb (PIT_PORT_CONTROL, (channel << 6) | 0x30 | (mode << 1)); + outb (PIT_PORT_COUNTER (channel), count); + outb (PIT_PORT_COUNTER (channel), count >> 8); + intr_set_level (old_level); +} diff --git a/tests/devices/src/devices/pit.h b/tests/devices/src/devices/pit.h new file mode 100644 index 0000000..dff36ae --- /dev/null +++ b/tests/devices/src/devices/pit.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_PIT_H +#define DEVICES_PIT_H + +#include + +void pit_configure_channel (int channel, int mode, int frequency); + +#endif /* devices/pit.h */ diff --git a/tests/devices/src/devices/rtc.c b/tests/devices/src/devices/rtc.c new file mode 100644 index 0000000..d99eb46 --- /dev/null +++ b/tests/devices/src/devices/rtc.c @@ -0,0 +1,112 @@ +#include "devices/rtc.h" +#include +#include "threads/io.h" + +/* This code is an interface to the MC146818A-compatible real + time clock found on PC motherboards. See [MC146818A] for + hardware details. */ + +/* I/O register addresses. */ +#define CMOS_REG_SET 0x70 /* Selects CMOS register exposed by REG_IO. */ +#define CMOS_REG_IO 0x71 /* Contains the selected data byte. */ + +/* Indexes of CMOS registers with real-time clock functions. + Note that all of these registers are in BCD format, + so that 0x59 means 59, not 89. */ +#define RTC_REG_SEC 0 /* Second: 0x00...0x59. */ +#define RTC_REG_MIN 2 /* Minute: 0x00...0x59. */ +#define RTC_REG_HOUR 4 /* Hour: 0x00...0x23. */ +#define RTC_REG_MDAY 7 /* Day of the month: 0x01...0x31. */ +#define RTC_REG_MON 8 /* Month: 0x01...0x12. */ +#define RTC_REG_YEAR 9 /* Year: 0x00...0x99. */ + +/* Indexes of CMOS control registers. */ +#define RTC_REG_A 0x0a /* Register A: update-in-progress. */ +#define RTC_REG_B 0x0b /* Register B: 24/12 hour time, irq enables. */ +#define RTC_REG_C 0x0c /* Register C: pending interrupts. */ +#define RTC_REG_D 0x0d /* Register D: valid time? */ + +/* Register A. */ +#define RTCSA_UIP 0x80 /* Set while time update in progress. */ + +/* Register B. */ +#define RTCSB_SET 0x80 /* Disables update to let time be set. */ +#define RTCSB_DM 0x04 /* 0 = BCD time format, 1 = binary format. */ +#define RTCSB_24HR 0x02 /* 0 = 12-hour format, 1 = 24-hour format. */ + +static int bcd_to_bin (uint8_t); +static uint8_t cmos_read (uint8_t index); + +/* Returns number of seconds since Unix epoch of January 1, + 1970. */ +time_t +rtc_get_time (void) +{ + static const int days_per_month[12] = + { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + int sec, min, hour, mday, mon, year; + time_t time; + int i; + + /* Get time components. + + We repeatedly read the time until it is stable from one read + to another, in case we start our initial read in the middle + of an update. This strategy is not recommended by the + MC146818A datasheet, but it is simpler than any of their + suggestions and, furthermore, it is also used by Linux. + + The MC146818A can be configured for BCD or binary format, + but for historical reasons everyone always uses BCD format + except on obscure non-PC platforms, so we don't bother + trying to detect the format in use. */ + do + { + sec = bcd_to_bin (cmos_read (RTC_REG_SEC)); + min = bcd_to_bin (cmos_read (RTC_REG_MIN)); + hour = bcd_to_bin (cmos_read (RTC_REG_HOUR)); + mday = bcd_to_bin (cmos_read (RTC_REG_MDAY)); + mon = bcd_to_bin (cmos_read (RTC_REG_MON)); + year = bcd_to_bin (cmos_read (RTC_REG_YEAR)); + } + while (sec != bcd_to_bin (cmos_read (RTC_REG_SEC))); + + /* Translate years-since-1900 into years-since-1970. + If it's before the epoch, assume that it has passed 2000. + This will break at 2070, but that's long after our 31-bit + time_t breaks in 2038. */ + if (year < 70) + year += 100; + year -= 70; + + /* Break down all components into seconds. */ + time = (year * 365 + (year - 1) / 4) * 24 * 60 * 60; + for (i = 1; i <= mon; i++) + time += days_per_month[i - 1] * 24 * 60 * 60; + if (mon > 2 && year % 4 == 0) + time += 24 * 60 * 60; + time += (mday - 1) * 24 * 60 * 60; + time += hour * 60 * 60; + time += min * 60; + time += sec; + + return time; +} + +/* Returns the integer value of the given BCD byte. */ +static int +bcd_to_bin (uint8_t x) +{ + return (x & 0x0f) + ((x >> 4) * 10); +} + +/* Reads a byte from the CMOS register with the given INDEX and + returns the byte read. */ +static uint8_t +cmos_read (uint8_t index) +{ + outb (CMOS_REG_SET, index); + return inb (CMOS_REG_IO); +} diff --git a/tests/devices/src/devices/rtc.h b/tests/devices/src/devices/rtc.h new file mode 100644 index 0000000..96a822f --- /dev/null +++ b/tests/devices/src/devices/rtc.h @@ -0,0 +1,8 @@ +#ifndef RTC_H +#define RTC_H + +typedef unsigned long time_t; + +time_t rtc_get_time (void); + +#endif diff --git a/tests/devices/src/devices/serial.c b/tests/devices/src/devices/serial.c new file mode 100644 index 0000000..9a9b498 --- /dev/null +++ b/tests/devices/src/devices/serial.c @@ -0,0 +1,228 @@ +#include "devices/serial.h" +#include +#include "devices/input.h" +#include "devices/intq.h" +#include "devices/timer.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "threads/synch.h" +#include "threads/thread.h" + +/* Register definitions for the 16550A UART used in PCs. + The 16550A has a lot more going on than shown here, but this + is all we need. + + Refer to [PC16650D] for hardware information. */ + +/* I/O port base address for the first serial port. */ +#define IO_BASE 0x3f8 + +/* DLAB=0 registers. */ +#define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */ +#define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */ +#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg.. */ + +/* DLAB=1 registers. */ +#define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */ +#define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */ + +/* DLAB-insensitive registers. */ +#define IIR_REG (IO_BASE + 2) /* Interrupt Identification Reg. (read-only) */ +#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */ +#define LCR_REG (IO_BASE + 3) /* Line Control Register. */ +#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */ +#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */ + +/* Interrupt Enable Register bits. */ +#define IER_RECV 0x01 /* Interrupt when data received. */ +#define IER_XMIT 0x02 /* Interrupt when transmit finishes. */ + +/* Line Control Register bits. */ +#define LCR_N81 0x03 /* No parity, 8 data bits, 1 stop bit. */ +#define LCR_DLAB 0x80 /* Divisor Latch Access Bit (DLAB). */ + +/* MODEM Control Register. */ +#define MCR_OUT2 0x08 /* Output line 2. */ + +/* Line Status Register. */ +#define LSR_DR 0x01 /* Data Ready: received data byte is in RBR. */ +#define LSR_THRE 0x20 /* THR Empty. */ + +/* Transmission mode. */ +static enum { UNINIT, POLL, QUEUE } mode; + +/* Data to be transmitted. */ +static struct intq txq; + +static void set_serial (int bps); +static void putc_poll (uint8_t); +static void write_ier (void); +static intr_handler_func serial_interrupt; + +/* Initializes the serial port device for polling mode. + Polling mode busy-waits for the serial port to become free + before writing to it. It's slow, but until interrupts have + been initialized it's all we can do. */ +static void +init_poll (void) +{ + ASSERT (mode == UNINIT); + outb (IER_REG, 0); /* Turn off all interrupts. */ + outb (FCR_REG, 0); /* Disable FIFO. */ + set_serial (9600); /* 9.6 kbps, N-8-1. */ + outb (MCR_REG, MCR_OUT2); /* Required to enable interrupts. */ + intq_init (&txq); + mode = POLL; +} + +/* Initializes the serial port device for queued interrupt-driven + I/O. With interrupt-driven I/O we don't waste CPU time + waiting for the serial device to become ready. */ +void +serial_init_queue (void) +{ + enum intr_level old_level; + + if (mode == UNINIT) + init_poll (); + ASSERT (mode == POLL); + + intr_register_ext (0x20 + 4, serial_interrupt, "serial"); + mode = QUEUE; + old_level = intr_disable (); + write_ier (); + intr_set_level (old_level); +} + +/* Sends BYTE to the serial port. */ +void +serial_putc (uint8_t byte) +{ + enum intr_level old_level = intr_disable (); + + if (mode != QUEUE) + { + /* If we're not set up for interrupt-driven I/O yet, + use dumb polling to transmit a byte. */ + if (mode == UNINIT) + init_poll (); + putc_poll (byte); + } + else + { + /* Otherwise, queue a byte and update the interrupt enable + register. */ + if (old_level == INTR_OFF && intq_full (&txq)) + { + /* Interrupts are off and the transmit queue is full. + If we wanted to wait for the queue to empty, + we'd have to reenable interrupts. + That's impolite, so we'll send a character via + polling instead. */ + putc_poll (intq_getc (&txq)); + } + + intq_putc (&txq, byte); + write_ier (); + } + + intr_set_level (old_level); +} + +/* Flushes anything in the serial buffer out the port in polling + mode. */ +void +serial_flush (void) +{ + enum intr_level old_level = intr_disable (); + while (!intq_empty (&txq)) + putc_poll (intq_getc (&txq)); + intr_set_level (old_level); +} + +/* The fullness of the input buffer may have changed. Reassess + whether we should block receive interrupts. + Called by the input buffer routines when characters are added + to or removed from the buffer. */ +void +serial_notify (void) +{ + ASSERT (intr_get_level () == INTR_OFF); + if (mode == QUEUE) + write_ier (); +} + +/* Configures the serial port for BPS bits per second. */ +static void +set_serial (int bps) +{ + int base_rate = 1843200 / 16; /* Base rate of 16550A, in Hz. */ + uint16_t divisor = base_rate / bps; /* Clock rate divisor. */ + + ASSERT (bps >= 300 && bps <= 115200); + + /* Enable DLAB. */ + outb (LCR_REG, LCR_N81 | LCR_DLAB); + + /* Set data rate. */ + outb (LS_REG, divisor & 0xff); + outb (MS_REG, divisor >> 8); + + /* Reset DLAB. */ + outb (LCR_REG, LCR_N81); +} + +/* Update interrupt enable register. */ +static void +write_ier (void) +{ + uint8_t ier = 0; + + ASSERT (intr_get_level () == INTR_OFF); + + /* Enable transmit interrupt if we have any characters to + transmit. */ + if (!intq_empty (&txq)) + ier |= IER_XMIT; + + /* Enable receive interrupt if we have room to store any + characters we receive. */ + if (!input_full ()) + ier |= IER_RECV; + + outb (IER_REG, ier); +} + +/* Polls the serial port until it's ready, + and then transmits BYTE. */ +static void +putc_poll (uint8_t byte) +{ + ASSERT (intr_get_level () == INTR_OFF); + + while ((inb (LSR_REG) & LSR_THRE) == 0) + continue; + outb (THR_REG, byte); +} + +/* Serial interrupt handler. */ +static void +serial_interrupt (struct intr_frame *f UNUSED) +{ + /* Inquire about interrupt in UART. Without this, we can + occasionally miss an interrupt running under QEMU. */ + inb (IIR_REG); + + /* As long as we have room to receive a byte, and the hardware + has a byte for us, receive a byte. */ + while (!input_full () && (inb (LSR_REG) & LSR_DR) != 0) + input_putc (inb (RBR_REG)); + + /* As long as we have a byte to transmit, and the hardware is + ready to accept a byte for transmission, transmit a byte. */ + while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0) + outb (THR_REG, intq_getc (&txq)); + + /* Update interrupt enable register based on queue status. */ + write_ier (); +} diff --git a/tests/devices/src/devices/serial.h b/tests/devices/src/devices/serial.h new file mode 100644 index 0000000..6e04778 --- /dev/null +++ b/tests/devices/src/devices/serial.h @@ -0,0 +1,11 @@ +#ifndef DEVICES_SERIAL_H +#define DEVICES_SERIAL_H + +#include + +void serial_init_queue (void); +void serial_putc (uint8_t); +void serial_flush (void); +void serial_notify (void); + +#endif /* devices/serial.h */ diff --git a/tests/devices/src/devices/shutdown.c b/tests/devices/src/devices/shutdown.c new file mode 100644 index 0000000..0064a26 --- /dev/null +++ b/tests/devices/src/devices/shutdown.c @@ -0,0 +1,133 @@ +#include "devices/shutdown.h" +#include +#include +#include "devices/kbd.h" +#include "devices/serial.h" +#include "devices/timer.h" +#include "threads/io.h" +#include "threads/thread.h" +#ifdef USERPROG +#include "userprog/exception.h" +#endif +#ifdef FILESYS +#include "devices/block.h" +#include "filesys/filesys.h" +#endif + +/* Keyboard control register port. */ +#define CONTROL_REG 0x64 + +/* How to shut down when shutdown() is called. */ +static enum shutdown_type how = SHUTDOWN_NONE; + +static void print_stats (void); + +/* Shuts down the machine in the way configured by + shutdown_configure(). If the shutdown type is SHUTDOWN_NONE + (which is the default), returns without doing anything. */ +void +shutdown (void) +{ + switch (how) + { + case SHUTDOWN_POWER_OFF: + shutdown_power_off (); + break; + + case SHUTDOWN_REBOOT: + shutdown_reboot (); + break; + + default: + /* Nothing to do. */ + break; + } +} + +/* Sets TYPE as the way that machine will shut down when PintOS + execution is complete. */ +void +shutdown_configure (enum shutdown_type type) +{ + how = type; +} + +/* Reboots the machine via the keyboard controller. */ +void +shutdown_reboot (void) +{ + printf ("Rebooting...\n"); + + /* See [kbd] for details on how to program the keyboard + * controller. */ + for (;;) + { + int i; + + /* Poll keyboard controller's status byte until + * 'input buffer empty' is reported. */ + for (i = 0; i < 0x10000; i++) + { + if ((inb (CONTROL_REG) & 0x02) == 0) + break; + timer_udelay (2); + } + + timer_udelay (50); + + /* Pulse bit 0 of the output port P2 of the keyboard controller. + * This will reset the CPU. */ + outb (CONTROL_REG, 0xfe); + timer_udelay (50); + } +} + +/* Powers down the machine we're running on, + as long as we're running on Bochs or QEMU. */ +void +shutdown_power_off (void) +{ + const char s[] = "Shutdown"; + const char *p; + +#ifdef FILESYS + filesys_done (); +#endif + + print_stats (); + + printf ("Powering off...\n"); + serial_flush (); + + /* This is a special power-off sequence supported by Bochs and + QEMU, but not by physical hardware. */ + for (p = s; *p != '\0'; p++){ + outb (0x8900, *p); + } + outw (0x604, 0x00 | 0x2000); + + /* This will power off a VMware VM if "gui.exitOnCLIHLT = TRUE" + is set in its configuration file. (The "pintos" script does + that automatically.) */ + asm volatile ("cli; hlt" : : : "memory"); + + /* None of those worked. */ + printf ("still running...\n"); + for (;;); +} + +/* Print statistics about PintOS execution. */ +static void +print_stats (void) +{ + timer_print_stats (); + thread_print_stats (); +#ifdef FILESYS + block_print_stats (); +#endif + console_print_stats (); + kbd_print_stats (); +#ifdef USERPROG + exception_print_stats (); +#endif +} diff --git a/tests/devices/src/devices/shutdown.h b/tests/devices/src/devices/shutdown.h new file mode 100644 index 0000000..57bfc7f --- /dev/null +++ b/tests/devices/src/devices/shutdown.h @@ -0,0 +1,19 @@ +#ifndef DEVICES_SHUTDOWN_H +#define DEVICES_SHUTDOWN_H + +#include + +/* How to shut down when PintOS has nothing left to do. */ +enum shutdown_type + { + SHUTDOWN_NONE, /* Loop forever. */ + SHUTDOWN_POWER_OFF, /* Power off the machine (if possible). */ + SHUTDOWN_REBOOT, /* Reboot the machine (if possible). */ + }; + +void shutdown (void); +void shutdown_configure (enum shutdown_type); +void shutdown_reboot (void) NO_RETURN; +void shutdown_power_off (void) NO_RETURN; + +#endif /* devices/shutdown.h */ diff --git a/tests/devices/src/devices/speaker.c b/tests/devices/src/devices/speaker.c new file mode 100644 index 0000000..b300259 --- /dev/null +++ b/tests/devices/src/devices/speaker.c @@ -0,0 +1,68 @@ +#include "devices/speaker.h" +#include "devices/pit.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "devices/timer.h" + +/* Speaker port enable I/O register. */ +#define SPEAKER_PORT_GATE 0x61 + +/* Speaker port enable bits. */ +#define SPEAKER_GATE_ENABLE 0x03 + +/* Sets the PC speaker to emit a tone at the given FREQUENCY, in + Hz. */ +void +speaker_on (int frequency) +{ + if (frequency >= 20 && frequency <= 20000) + { + /* Set the timer channel that's connected to the speaker to + output a square wave at the given FREQUENCY, then + connect the timer channel output to the speaker. */ + enum intr_level old_level = intr_disable (); + pit_configure_channel (2, 3, frequency); + outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) | SPEAKER_GATE_ENABLE); + intr_set_level (old_level); + } + else + { + /* FREQUENCY is outside the range of normal human hearing. + Just turn off the speaker. */ + speaker_off (); + } +} + +/* Turn off the PC speaker, by disconnecting the timer channel's + output from the speaker. */ +void +speaker_off (void) +{ + enum intr_level old_level = intr_disable (); + outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) & ~SPEAKER_GATE_ENABLE); + intr_set_level (old_level); +} + +/* Briefly beep the PC speaker. */ +void +speaker_beep (void) +{ + /* Only attempt to beep the speaker if interrupts are enabled, + because we don't want to freeze the machine during the beep. + We could add a hook to the timer interrupt to avoid that + problem, but then we'd risk failing to ever stop the beep if + PintOS crashes for some unrelated reason. There's nothing + more annoying than a machine whose beeping you can't stop + without a power cycle. + + We can't just enable interrupts while we sleep. For one + thing, we get called (indirectly) from printf, which should + always work, even during boot before we're ready to enable + interrupts. */ + if (intr_get_level () == INTR_ON) + { + speaker_on (440); + timer_msleep (250); + speaker_off (); + } +} diff --git a/tests/devices/src/devices/speaker.h b/tests/devices/src/devices/speaker.h new file mode 100644 index 0000000..98cef7b --- /dev/null +++ b/tests/devices/src/devices/speaker.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_SPEAKER_H +#define DEVICES_SPEAKER_H + +void speaker_on (int frequency); +void speaker_off (void); +void speaker_beep (void); + +#endif /* devices/speaker.h */ diff --git a/tests/devices/src/devices/swap.c b/tests/devices/src/devices/swap.c new file mode 100644 index 0000000..995d68d --- /dev/null +++ b/tests/devices/src/devices/swap.c @@ -0,0 +1,82 @@ +#include "devices/swap.h" +#include "devices/block.h" +#include "threads/synch.h" +#include "threads/vaddr.h" +#include +#include +#include + +/* Pointer to the swap device */ +static struct block *swap_device; + +/* Pointer to a bitmap to track used swap pages */ +static struct bitmap *swap_bitmap; + +/* Lock that protects swap_bitmap from unsynchronised access */ +static struct lock swap_lock; + +/* Number of sectors needed to store a page */ +#define PAGE_SECTORS (PGSIZE / BLOCK_SECTOR_SIZE) + +/* Sets up the swap space */ +void +swap_init (void) +{ + // locate the swap block allocated to the kernel + swap_device = block_get_role (BLOCK_SWAP); + if (swap_device == NULL) { + printf ("no swap device--swap disabled\n"); + swap_bitmap = bitmap_create (0); + } else { + // create a bitmap with 1 slot per page-sized chunk of memory on the swap block + swap_bitmap = bitmap_create (block_size (swap_device) / PAGE_SECTORS); + } + + if (swap_bitmap == NULL){ + PANIC ("couldn't create swap bitmap"); + } + lock_init (&swap_lock); +} + +/* Swaps page at VADDR out of memory, returns the swap-slot used */ +size_t +swap_out (const void *vaddr) +{ + // find available swap-slot for the page to be swapped out + lock_acquire (&swap_lock); + size_t slot = bitmap_scan_and_flip (swap_bitmap, 0, 1, false); + lock_release (&swap_lock); + if (slot == BITMAP_ERROR) + return BITMAP_ERROR; + + // calculate block sector from swap-slot number + size_t sector = slot * PAGE_SECTORS; + + // loop over each sector of the page, copying it from memory into swap + for (size_t i = 0; i < PAGE_SECTORS; i++) + block_write (swap_device, sector + i, vaddr + i * BLOCK_SECTOR_SIZE); + + return slot; +} + +/* Swaps page on disk in swap-slot SLOT into memory at VADDR */ +void +swap_in (void *vaddr, size_t slot) +{ + // calculate block sector from swap-slot number + size_t sector = slot * PAGE_SECTORS; + + // loop over each sector of the page, copying it from swap into memory + for (size_t i = 0; i < PAGE_SECTORS; i++) + block_read (swap_device, sector + i, vaddr + i * BLOCK_SECTOR_SIZE); + + // clear the swap-slot previously used by this page + swap_drop (slot); +} + +/* Clears the swap-slot SLOT so that it can be used for another page */ +void +swap_drop (size_t slot) +{ + bitmap_reset (swap_bitmap, slot); +} diff --git a/tests/devices/src/devices/swap.h b/tests/devices/src/devices/swap.h new file mode 100644 index 0000000..deb54f2 --- /dev/null +++ b/tests/devices/src/devices/swap.h @@ -0,0 +1,11 @@ +#ifndef DEVICES_SWAP_H +#define DEVICES_SWAP_H 1 + +#include + +void swap_init (void); +size_t swap_out (const void *vaddr); +void swap_in (void *vaddr, size_t slot); +void swap_drop (size_t slot); + +#endif /* devices/swap.h */ diff --git a/tests/devices/src/devices/timer.c b/tests/devices/src/devices/timer.c new file mode 100644 index 0000000..e7fdc53 --- /dev/null +++ b/tests/devices/src/devices/timer.c @@ -0,0 +1,293 @@ +#include "devices/timer.h" +#include +#include +#include +#include +#include +#include "devices/pit.h" +#include "threads/interrupt.h" +#include "threads/synch.h" +#include "threads/thread.h" + +/* See [8254] for hardware details of the 8254 timer chip. */ + +#if TIMER_FREQ < 19 +#error 8254 timer requires TIMER_FREQ >= 19 +#endif +#if TIMER_FREQ > 1000 +#error TIMER_FREQ <= 1000 recommended +#endif + +struct asleep_thread +{ + int64_t end_at; /* Number of timer ticks to stop sleeping at. */ + struct semaphore semaphore; /* Semaphore used to block the thread. */ + struct list_elem elem; /* List element. */ +}; +/* List of threads that are sleeping. */ +static struct list sleeping_threads; + +/* Number of timer ticks since OS booted. */ +static int64_t ticks; + +/* Number of loops per timer tick. + Initialized by timer_calibrate(). */ +static unsigned loops_per_tick; + +static intr_handler_func timer_interrupt; +static bool too_many_loops (unsigned loops); +static void busy_wait (int64_t loops); +static void real_time_sleep (int64_t num, int32_t denom); +static void real_time_delay (int64_t num, int32_t denom); +static bool sleeping_threads_less (const struct list_elem *a, + const struct list_elem *b, + void *aux UNUSED); + +/* Sets up the timer to interrupt TIMER_FREQ times per second, + and registers the corresponding interrupt. */ +void +timer_init (void) +{ + pit_configure_channel (0, 2, TIMER_FREQ); + list_init (&sleeping_threads); + intr_register_ext (0x20, timer_interrupt, "8254 Timer"); +} + +/* Calibrates loops_per_tick, used to implement brief delays. */ +void +timer_calibrate (void) +{ + unsigned high_bit, test_bit; + + ASSERT (intr_get_level () == INTR_ON); + printf ("Calibrating timer... "); + + /* Approximate loops_per_tick as the largest power-of-two + still less than one timer tick. */ + loops_per_tick = 1u << 10; + while (!too_many_loops (loops_per_tick << 1)) + { + loops_per_tick <<= 1; + ASSERT (loops_per_tick != 0); + } + + /* Refine the next 8 bits of loops_per_tick. */ + high_bit = loops_per_tick; + for (test_bit = high_bit >> 1; test_bit != high_bit >> 10; test_bit >>= 1) + if (!too_many_loops (high_bit | test_bit)) + loops_per_tick |= test_bit; + + printf ("%'"PRIu64" loops/s.\n", (uint64_t) loops_per_tick * TIMER_FREQ); +} + +/* Returns the number of timer ticks since the OS booted. */ +int64_t +timer_ticks (void) +{ + enum intr_level old_level = intr_disable (); + int64_t t = ticks; + intr_set_level (old_level); + return t; +} + +/* Returns the number of timer ticks elapsed since THEN, which + should be a value once returned by timer_ticks(). */ +int64_t +timer_elapsed (int64_t then) +{ + return timer_ticks () - then; +} + +/* Sleeps for approximately TICKS timer ticks. Interrupts must + be turned on. */ +void +timer_sleep (int64_t ticks) +{ + enum intr_level old_level; + int64_t start = timer_ticks (); + + ASSERT (intr_get_level () == INTR_ON); + if (ticks < 0) + return; + + struct asleep_thread st; + st.end_at = start + ticks; + sema_init (&st.semaphore, 0); + + old_level = intr_disable (); + list_insert_ordered (&sleeping_threads, &st.elem, &sleeping_threads_less, + NULL); + intr_set_level (old_level); + sema_down (&st.semaphore); +} + +/* Sleeps for approximately MS milliseconds. Interrupts must be + turned on. */ +void +timer_msleep (int64_t ms) +{ + real_time_sleep (ms, 1000); +} + +/* Sleeps for approximately US microseconds. Interrupts must be + turned on. */ +void +timer_usleep (int64_t us) +{ + real_time_sleep (us, 1000 * 1000); +} + +/* Sleeps for approximately NS nanoseconds. Interrupts must be + turned on. */ +void +timer_nsleep (int64_t ns) +{ + real_time_sleep (ns, 1000 * 1000 * 1000); +} + +/* Busy-waits for approximately MS milliseconds. Interrupts need + not be turned on. + + Busy waiting wastes CPU cycles, and busy waiting with + interrupts off for the interval between timer ticks or longer + will cause timer ticks to be lost. Thus, use timer_msleep() + instead if interrupts are enabled. */ +void +timer_mdelay (int64_t ms) +{ + real_time_delay (ms, 1000); +} + +/* Sleeps for approximately US microseconds. Interrupts need not + be turned on. + + Busy waiting wastes CPU cycles, and busy waiting with + interrupts off for the interval between timer ticks or longer + will cause timer ticks to be lost. Thus, use timer_usleep() + instead if interrupts are enabled. */ +void +timer_udelay (int64_t us) +{ + real_time_delay (us, 1000 * 1000); +} + +/* Sleeps execution for approximately NS nanoseconds. Interrupts + need not be turned on. + + Busy waiting wastes CPU cycles, and busy waiting with + interrupts off for the interval between timer ticks or longer + will cause timer ticks to be lost. Thus, use timer_nsleep() + instead if interrupts are enabled.*/ +void +timer_ndelay (int64_t ns) +{ + real_time_delay (ns, 1000 * 1000 * 1000); +} + +/* Prints timer statistics. */ +void +timer_print_stats (void) +{ + printf ("Timer: %"PRId64" ticks\n", timer_ticks ()); +} + +/* Timer interrupt handler. */ +static void +timer_interrupt (struct intr_frame *args UNUSED) +{ + ticks++; + for (struct list_elem *e = list_begin (&sleeping_threads); + e != list_end (&sleeping_threads); e = list_next (e)) + { + struct asleep_thread *st = list_entry (e, struct asleep_thread, elem); + if (ticks >= st->end_at) + { + list_remove (&st->elem); + sema_up (&st->semaphore); + } + else + break; + } + thread_tick (); +} + +/* Returns true if LOOPS iterations waits for more than one timer + tick, otherwise false. */ +static bool +too_many_loops (unsigned loops) +{ + /* Wait for a timer tick. */ + int64_t start = ticks; + while (ticks == start) + barrier (); + + /* Run LOOPS loops. */ + start = ticks; + busy_wait (loops); + + /* If the tick count changed, we iterated too long. */ + barrier (); + return start != ticks; +} + +/* Iterates through a simple loop LOOPS times, for implementing + brief delays. + + Marked NO_INLINE because code alignment can significantly + affect timings, so that if this function was inlined + differently in different places the results would be difficult + to predict. */ +static void NO_INLINE +busy_wait (int64_t loops) +{ + while (loops-- > 0) + barrier (); +} + +/* Sleep for approximately NUM/DENOM seconds. */ +static void +real_time_sleep (int64_t num, int32_t denom) +{ + /* Convert NUM/DENOM seconds into timer ticks, rounding down. + + (NUM / DENOM) s + ---------------------- = NUM * TIMER_FREQ / DENOM ticks. + 1 s / TIMER_FREQ ticks + */ + int64_t ticks = num * TIMER_FREQ / denom; + + ASSERT (intr_get_level () == INTR_ON); + if (ticks > 0) + { + /* We're waiting for at least one full timer tick. Use + timer_sleep() because it will yield the CPU to other + processes. */ + timer_sleep (ticks); + } + else + { + /* Otherwise, use a busy-wait loop for more accurate + sub-tick timing. */ + real_time_delay (num, denom); + } +} + +/* Busy-wait for approximately NUM/DENOM seconds. */ +static void +real_time_delay (int64_t num, int32_t denom) +{ + /* Scale the numerator and denominator down by 1000 to avoid + the possibility of overflow. */ + ASSERT (denom % 1000 == 0); + busy_wait (loops_per_tick * num / 1000 * TIMER_FREQ / (denom / 1000)); +} + +/* list_less_func for sleeping_threads list */ +bool +sleeping_threads_less (const struct list_elem *a, const struct list_elem *b, + void *aux UNUSED) +{ + struct asleep_thread *sta = list_entry (a, struct asleep_thread, elem); + struct asleep_thread *stb = list_entry (b, struct asleep_thread, elem); + return sta->end_at < stb->end_at; +} diff --git a/tests/devices/src/devices/timer.h b/tests/devices/src/devices/timer.h new file mode 100644 index 0000000..cd3d6bb --- /dev/null +++ b/tests/devices/src/devices/timer.h @@ -0,0 +1,29 @@ +#ifndef DEVICES_TIMER_H +#define DEVICES_TIMER_H + +#include +#include + +/* Number of timer interrupts per second. */ +#define TIMER_FREQ 100 + +void timer_init (void); +void timer_calibrate (void); + +int64_t timer_ticks (void); +int64_t timer_elapsed (int64_t); + +/* Sleep and yield the CPU to other threads. */ +void timer_sleep (int64_t ticks); +void timer_msleep (int64_t milliseconds); +void timer_usleep (int64_t microseconds); +void timer_nsleep (int64_t nanoseconds); + +/* Busy waits. */ +void timer_mdelay (int64_t milliseconds); +void timer_udelay (int64_t microseconds); +void timer_ndelay (int64_t nanoseconds); + +void timer_print_stats (void); + +#endif /* devices/timer.h */ diff --git a/tests/devices/src/devices/vga.c b/tests/devices/src/devices/vga.c new file mode 100644 index 0000000..b0b77cd --- /dev/null +++ b/tests/devices/src/devices/vga.c @@ -0,0 +1,172 @@ +#include "devices/vga.h" +#include +#include +#include +#include +#include "devices/speaker.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "threads/vaddr.h" + +/* VGA text screen support. See [FREEVGA] for more information. */ + +/* Number of columns and rows on the text display. */ +#define COL_CNT 80 +#define ROW_CNT 25 + +/* Current cursor position. (0,0) is in the upper left corner of + the display. */ +static size_t cx, cy; + +/* Attribute value for gray text on a black background. */ +#define GRAY_ON_BLACK 0x07 + +/* Framebuffer. See [FREEVGA] under "VGA Text Mode Operation". + The character at (x,y) is fb[y][x][0]. + The attribute at (x,y) is fb[y][x][1]. */ +static uint8_t (*fb)[COL_CNT][2]; + +static void clear_row (size_t y); +static void cls (void); +static void newline (void); +static void move_cursor (void); +static void find_cursor (size_t *x, size_t *y); + +/* Initializes the VGA text display. */ +static void +init (void) +{ + /* Already initialized? */ + static bool inited; + if (!inited) + { + fb = ptov (0xb8000); + find_cursor (&cx, &cy); + inited = true; + } +} + +/* Writes C to the VGA text display, interpreting control + characters in the conventional ways. */ +void +vga_putc (int c) +{ + /* Disable interrupts to lock out interrupt handlers + that might write to the console. */ + enum intr_level old_level = intr_disable (); + + init (); + + switch (c) + { + case '\n': + newline (); + break; + + case '\f': + cls (); + break; + + case '\b': + if (cx > 0) + cx--; + break; + + case '\r': + cx = 0; + break; + + case '\t': + cx = ROUND_UP (cx + 1, 8); + if (cx >= COL_CNT) + newline (); + break; + + case '\a': + intr_set_level (old_level); + speaker_beep (); + intr_disable (); + break; + + default: + fb[cy][cx][0] = c; + fb[cy][cx][1] = GRAY_ON_BLACK; + if (++cx >= COL_CNT) + newline (); + break; + } + + /* Update cursor position. */ + move_cursor (); + + intr_set_level (old_level); +} + +/* Clears the screen and moves the cursor to the upper left. */ +static void +cls (void) +{ + size_t y; + + for (y = 0; y < ROW_CNT; y++) + clear_row (y); + + cx = cy = 0; + move_cursor (); +} + +/* Clears row Y to spaces. */ +static void +clear_row (size_t y) +{ + size_t x; + + for (x = 0; x < COL_CNT; x++) + { + fb[y][x][0] = ' '; + fb[y][x][1] = GRAY_ON_BLACK; + } +} + +/* Advances the cursor to the first column in the next line on + the screen. If the cursor is already on the last line on the + screen, scrolls the screen upward one line. */ +static void +newline (void) +{ + cx = 0; + cy++; + if (cy >= ROW_CNT) + { + cy = ROW_CNT - 1; + memmove (&fb[0], &fb[1], sizeof fb[0] * (ROW_CNT - 1)); + clear_row (ROW_CNT - 1); + } +} + +/* Moves the hardware cursor to (cx,cy). */ +static void +move_cursor (void) +{ + /* See [FREEVGA] under "Manipulating the Text-mode Cursor". */ + uint16_t cp = cx + COL_CNT * cy; + outw (0x3d4, 0x0e | (cp & 0xff00)); + outw (0x3d4, 0x0f | (cp << 8)); +} + +/* Reads the current hardware cursor position into (*X,*Y). */ +static void +find_cursor (size_t *x, size_t *y) +{ + /* See [FREEVGA] under "Manipulating the Text-mode Cursor". */ + uint16_t cp; + + outb (0x3d4, 0x0e); + cp = inb (0x3d5) << 8; + + outb (0x3d4, 0x0f); + cp |= inb (0x3d5); + + *x = cp % COL_CNT; + *y = cp / COL_CNT; +} diff --git a/tests/devices/src/devices/vga.h b/tests/devices/src/devices/vga.h new file mode 100644 index 0000000..59690fb --- /dev/null +++ b/tests/devices/src/devices/vga.h @@ -0,0 +1,6 @@ +#ifndef DEVICES_VGA_H +#define DEVICES_VGA_H + +void vga_putc (int); + +#endif /* devices/vga.h */ diff --git a/tests/devices/src/lib/arithmetic.c b/tests/devices/src/lib/arithmetic.c new file mode 100644 index 0000000..cab140a --- /dev/null +++ b/tests/devices/src/lib/arithmetic.c @@ -0,0 +1,189 @@ +#include + +/* On x86, division of one 64-bit integer by another cannot be + done with a single instruction or a short sequence. Thus, GCC + implements 64-bit division and remainder operations through + function calls. These functions are normally obtained from + libgcc, which is automatically included by GCC in any link + that it does. + + Some x86-64 machines, however, have a compiler and utilities + that can generate 32-bit x86 code without having any of the + necessary libraries, including libgcc. Thus, we can make + PintOS work on these machines by simply implementing our own + 64-bit division routines, which are the only routines from + libgcc that PintOS requires. + + Completeness is another reason to include these routines. If + PintOS is completely self-contained, then that makes it that + much less mysterious. */ + +/* Uses x86 DIVL instruction to divide 64-bit N by 32-bit D to + yield a 32-bit quotient. Returns the quotient. + Traps with a divide error (#DE) if the quotient does not fit + in 32 bits. */ +static inline uint32_t +divl (uint64_t n, uint32_t d) +{ + uint32_t n1 = n >> 32; + uint32_t n0 = n; + uint32_t q, r; + + asm ("divl %4" + : "=d" (r), "=a" (q) + : "0" (n1), "1" (n0), "rm" (d)); + + return q; +} + +/* Returns the number of leading zero bits in X, + which must be nonzero. */ +static int +nlz (uint32_t x) +{ + /* This technique is portable, but there are better ways to do + it on particular systems. With sufficiently new enough GCC, + you can use __builtin_clz() to take advantage of GCC's + knowledge of how to do it. Or you can use the x86 BSR + instruction directly. */ + int n = 0; + if (x <= 0x0000FFFF) + { + n += 16; + x <<= 16; + } + if (x <= 0x00FFFFFF) + { + n += 8; + x <<= 8; + } + if (x <= 0x0FFFFFFF) + { + n += 4; + x <<= 4; + } + if (x <= 0x3FFFFFFF) + { + n += 2; + x <<= 2; + } + if (x <= 0x7FFFFFFF) + n++; + return n; +} + +/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the + quotient. */ +static uint64_t +udiv64 (uint64_t n, uint64_t d) +{ + if ((d >> 32) == 0) + { + /* Proof of correctness: + + Let n, d, b, n1, and n0 be defined as in this function. + Let [x] be the "floor" of x. Let T = b[n1/d]. Assume d + nonzero. Then: + [n/d] = [n/d] - T + T + = [n/d - T] + T by (1) below + = [(b*n1 + n0)/d - T] + T by definition of n + = [(b*n1 + n0)/d - dT/d] + T + = [(b(n1 - d[n1/d]) + n0)/d] + T + = [(b[n1 % d] + n0)/d] + T, by definition of % + which is the expression calculated below. + + (1) Note that for any real x, integer i: [x] + i = [x + i]. + + To prevent divl() from trapping, [(b[n1 % d] + n0)/d] must + be less than b. Assume that [n1 % d] and n0 take their + respective maximum values of d - 1 and b - 1: + [(b(d - 1) + (b - 1))/d] < b + <=> [(bd - 1)/d] < b + <=> [b - 1/d] < b + which is a tautology. + + Therefore, this code is correct and will not trap. */ + uint64_t b = 1ULL << 32; + uint32_t n1 = n >> 32; + uint32_t n0 = n; + uint32_t d0 = d; + + return divl (b * (n1 % d0) + n0, d0) + b * (n1 / d0); + } + else + { + /* Based on the algorithm and proof available from + http://www.hackersdelight.org/revisions.pdf. */ + if (n < d) + return 0; + else + { + uint32_t d1 = d >> 32; + int s = nlz (d1); + uint64_t q = divl (n >> 1, (d << s) >> 32) >> (31 - s); + return n - (q - 1) * d < d ? q - 1 : q; + } + } +} + +/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the + remainder. */ +static uint32_t +umod64 (uint64_t n, uint64_t d) +{ + return n - d * udiv64 (n, d); +} + +/* Divides signed 64-bit N by signed 64-bit D and returns the + quotient. */ +static int64_t +sdiv64 (int64_t n, int64_t d) +{ + uint64_t n_abs = n >= 0 ? (uint64_t) n : -(uint64_t) n; + uint64_t d_abs = d >= 0 ? (uint64_t) d : -(uint64_t) d; + uint64_t q_abs = udiv64 (n_abs, d_abs); + return (n < 0) == (d < 0) ? (int64_t) q_abs : -(int64_t) q_abs; +} + +/* Divides signed 64-bit N by signed 64-bit D and returns the + remainder. */ +static int32_t +smod64 (int64_t n, int64_t d) +{ + return n - d * sdiv64 (n, d); +} + +/* These are the routines that GCC calls. */ + +long long __divdi3 (long long n, long long d); +long long __moddi3 (long long n, long long d); +unsigned long long __udivdi3 (unsigned long long n, unsigned long long d); +unsigned long long __umoddi3 (unsigned long long n, unsigned long long d); + +/* Signed 64-bit division. */ +long long +__divdi3 (long long n, long long d) +{ + return sdiv64 (n, d); +} + +/* Signed 64-bit remainder. */ +long long +__moddi3 (long long n, long long d) +{ + return smod64 (n, d); +} + +/* Unsigned 64-bit division. */ +unsigned long long +__udivdi3 (unsigned long long n, unsigned long long d) +{ + return udiv64 (n, d); +} + +/* Unsigned 64-bit remainder. */ +unsigned long long +__umoddi3 (unsigned long long n, unsigned long long d) +{ + return umod64 (n, d); +} diff --git a/tests/devices/src/lib/ctype.h b/tests/devices/src/lib/ctype.h new file mode 100644 index 0000000..9096aca --- /dev/null +++ b/tests/devices/src/lib/ctype.h @@ -0,0 +1,28 @@ +#ifndef __LIB_CTYPE_H +#define __LIB_CTYPE_H + +static inline int islower (int c) { return c >= 'a' && c <= 'z'; } +static inline int isupper (int c) { return c >= 'A' && c <= 'Z'; } +static inline int isalpha (int c) { return islower (c) || isupper (c); } +static inline int isdigit (int c) { return c >= '0' && c <= '9'; } +static inline int isalnum (int c) { return isalpha (c) || isdigit (c); } +static inline int isxdigit (int c) { + return isdigit (c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} +static inline int isspace (int c) { + return (c == ' ' || c == '\f' || c == '\n' + || c == '\r' || c == '\t' || c == '\v'); +} +static inline int isblank (int c) { return c == ' ' || c == '\t'; } +static inline int isgraph (int c) { return c > 32 && c < 127; } +static inline int isprint (int c) { return c >= 32 && c < 127; } +static inline int iscntrl (int c) { return (c >= 0 && c < 32) || c == 127; } +static inline int isascii (int c) { return c >= 0 && c < 128; } +static inline int ispunct (int c) { + return isprint (c) && !isalnum (c) && !isspace (c); +} + +static inline int tolower (int c) { return isupper (c) ? c - 'A' + 'a' : c; } +static inline int toupper (int c) { return islower (c) ? c - 'a' + 'A' : c; } + +#endif /* lib/ctype.h */ diff --git a/tests/devices/src/lib/debug.c b/tests/devices/src/lib/debug.c new file mode 100644 index 0000000..fe5aeb2 --- /dev/null +++ b/tests/devices/src/lib/debug.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include +#include + +/* Prints the call stack, that is, a list of addresses, one in + each of the functions we are nested within. gdb or addr2line + may be applied to kernel.o to translate these into file names, + line numbers, and function names. */ +void +debug_backtrace (void) +{ + static bool explained; + void **frame; + + printf ("Call stack: %p", __builtin_return_address (0)); + for (frame = __builtin_frame_address (1); + (uintptr_t) frame >= 0x1000 && frame[0] != NULL; + frame = frame[0]) + printf (" %p", frame[1]); + printf (".\n"); + + if (!explained) + { + explained = true; + printf ("The `backtrace' program can make call stacks useful.\n" + "Read \"Backtraces\" in the \"Debugging Tools\" chapter\n" + "of the PintOS documentation for more information.\n"); + } +} diff --git a/tests/devices/src/lib/debug.h b/tests/devices/src/lib/debug.h new file mode 100644 index 0000000..888ab7b --- /dev/null +++ b/tests/devices/src/lib/debug.h @@ -0,0 +1,39 @@ +#ifndef __LIB_DEBUG_H +#define __LIB_DEBUG_H + +/* GCC lets us add "attributes" to functions, function + parameters, etc. to indicate their properties. + See the GCC manual for details. */ +#define UNUSED __attribute__ ((unused)) +#define NO_RETURN __attribute__ ((noreturn)) +#define NO_INLINE __attribute__ ((noinline)) +#define PRINTF_FORMAT(FMT, FIRST) __attribute__ ((format (printf, FMT, FIRST))) + +/* Halts the OS, printing the source file name, line number, and + function name, plus a user-specific message. */ +#define PANIC(...) debug_panic (__FILE__, __LINE__, __func__, __VA_ARGS__) + +void debug_panic (const char *file, int line, const char *function, + const char *message, ...) PRINTF_FORMAT (4, 5) NO_RETURN; +void debug_backtrace (void); +void debug_backtrace_all (void); + +#endif + + + +/* This is outside the header guard so that debug.h may be + included multiple times with different settings of NDEBUG. */ +#undef ASSERT +#undef NOT_REACHED + +#ifndef NDEBUG +#define ASSERT(CONDITION) \ + if (CONDITION) { } else { \ + PANIC ("assertion `%s' failed.", #CONDITION); \ + } +#define NOT_REACHED() PANIC ("executed an unreachable statement"); +#else +#define ASSERT(CONDITION) ((void) 0) +#define NOT_REACHED() for (;;) +#endif /* lib/debug.h */ diff --git a/tests/devices/src/lib/inttypes.h b/tests/devices/src/lib/inttypes.h new file mode 100644 index 0000000..f703725 --- /dev/null +++ b/tests/devices/src/lib/inttypes.h @@ -0,0 +1,48 @@ +#ifndef __LIB_INTTYPES_H +#define __LIB_INTTYPES_H + +#include + +#define PRId8 "hhd" +#define PRIi8 "hhi" +#define PRIo8 "hho" +#define PRIu8 "hhu" +#define PRIx8 "hhx" +#define PRIX8 "hhX" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" + +#define PRId32 "d" +#define PRIi32 "i" +#define PRIo32 "o" +#define PRIu32 "u" +#define PRIx32 "x" +#define PRIX32 "X" + +#define PRId64 "lld" +#define PRIi64 "lli" +#define PRIo64 "llo" +#define PRIu64 "llu" +#define PRIx64 "llx" +#define PRIX64 "llX" + +#define PRIdMAX "jd" +#define PRIiMAX "ji" +#define PRIoMAX "jo" +#define PRIuMAX "ju" +#define PRIxMAX "jx" +#define PRIXMAX "jX" + +#define PRIdPTR "td" +#define PRIiPTR "ti" +#define PRIoPTR "to" +#define PRIuPTR "tu" +#define PRIxPTR "tx" +#define PRIXPTR "tX" + +#endif /* lib/inttypes.h */ diff --git a/tests/devices/src/lib/kernel/bitmap.c b/tests/devices/src/lib/kernel/bitmap.c new file mode 100644 index 0000000..9c51a35 --- /dev/null +++ b/tests/devices/src/lib/kernel/bitmap.c @@ -0,0 +1,372 @@ +#include "bitmap.h" +#include +#include +#include +#include +#include "threads/malloc.h" +#ifdef FILESYS +#include "filesys/file.h" +#endif + +/* Element type. + + This must be an unsigned integer type at least as wide as int. + + Each bit represents one bit in the bitmap. + If bit 0 in an element represents bit K in the bitmap, + then bit 1 in the element represents bit K+1 in the bitmap, + and so on. */ +typedef unsigned long elem_type; + +/* Number of bits in an element. */ +#define ELEM_BITS (sizeof (elem_type) * CHAR_BIT) + +/* From the outside, a bitmap is an array of bits. From the + inside, it's an array of elem_type (defined above) that + simulates an array of bits. */ +struct bitmap + { + size_t bit_cnt; /* Number of bits. */ + elem_type *bits; /* Elements that represent bits. */ + }; + +/* Returns the index of the element that contains the bit + numbered BIT_IDX. */ +static inline size_t +elem_idx (size_t bit_idx) +{ + return bit_idx / ELEM_BITS; +} + +/* Returns an elem_type where only the bit corresponding to + BIT_IDX is turned on. */ +static inline elem_type +bit_mask (size_t bit_idx) +{ + return (elem_type) 1 << (bit_idx % ELEM_BITS); +} + +/* Returns the number of elements required for BIT_CNT bits. */ +static inline size_t +elem_cnt (size_t bit_cnt) +{ + return DIV_ROUND_UP (bit_cnt, ELEM_BITS); +} + +/* Returns the number of bytes required for BIT_CNT bits. */ +static inline size_t +byte_cnt (size_t bit_cnt) +{ + return sizeof (elem_type) * elem_cnt (bit_cnt); +} + +/* Returns a bit mask in which the bits actually used in the last + element of B's bits are set to 1 and the rest are set to 0. */ +static inline elem_type +last_mask (const struct bitmap *b) +{ + int last_bits = b->bit_cnt % ELEM_BITS; + return last_bits ? ((elem_type) 1 << last_bits) - 1 : (elem_type) -1; +} + +/* Creation and destruction. */ + +/* Initializes B to be a bitmap of BIT_CNT bits + and sets all of its bits to false. + Returns true if success, false if memory allocation + failed. */ +struct bitmap * +bitmap_create (size_t bit_cnt) +{ + struct bitmap *b = malloc (sizeof *b); + if (b != NULL) + { + b->bit_cnt = bit_cnt; + b->bits = malloc (byte_cnt (bit_cnt)); + if (b->bits != NULL || bit_cnt == 0) + { + bitmap_set_all (b, false); + return b; + } + free (b); + } + return NULL; +} + +/* Creates and returns a bitmap with BIT_CNT bits in the + BLOCK_SIZE bytes of storage preallocated at BLOCK. + BLOCK_SIZE must be at least bitmap_needed_bytes(BIT_CNT). */ +struct bitmap * +bitmap_create_in_buf (size_t bit_cnt, void *block, size_t block_size UNUSED) +{ + struct bitmap *b = block; + + ASSERT (block_size >= bitmap_buf_size (bit_cnt)); + + b->bit_cnt = bit_cnt; + b->bits = (elem_type *) (b + 1); + bitmap_set_all (b, false); + return b; +} + +/* Returns the number of bytes required to accomodate a bitmap + with BIT_CNT bits (for use with bitmap_create_in_buf()). */ +size_t +bitmap_buf_size (size_t bit_cnt) +{ + return sizeof (struct bitmap) + byte_cnt (bit_cnt); +} + +/* Destroys bitmap B, freeing its storage. + Not for use on bitmaps created by + bitmap_create_preallocated(). */ +void +bitmap_destroy (struct bitmap *b) +{ + if (b != NULL) + { + free (b->bits); + free (b); + } +} + +/* Bitmap size. */ + +/* Returns the number of bits in B. */ +size_t +bitmap_size (const struct bitmap *b) +{ + return b->bit_cnt; +} + +/* Setting and testing single bits. */ + +/* Atomically sets the bit numbered IDX in B to VALUE. */ +void +bitmap_set (struct bitmap *b, size_t idx, bool value) +{ + ASSERT (b != NULL); + ASSERT (idx < b->bit_cnt); + if (value) + bitmap_mark (b, idx); + else + bitmap_reset (b, idx); +} + +/* Atomically sets the bit numbered BIT_IDX in B to true. */ +void +bitmap_mark (struct bitmap *b, size_t bit_idx) +{ + size_t idx = elem_idx (bit_idx); + elem_type mask = bit_mask (bit_idx); + + /* This is equivalent to `b->bits[idx] |= mask' except that it + is guaranteed to be atomic on a uniprocessor machine. See + the description of the OR instruction in [IA32-v2b]. */ + asm ("orl %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc"); +} + +/* Atomically sets the bit numbered BIT_IDX in B to false. */ +void +bitmap_reset (struct bitmap *b, size_t bit_idx) +{ + size_t idx = elem_idx (bit_idx); + elem_type mask = bit_mask (bit_idx); + + /* This is equivalent to `b->bits[idx] &= ~mask' except that it + is guaranteed to be atomic on a uniprocessor machine. See + the description of the AND instruction in [IA32-v2a]. */ + asm ("andl %1, %0" : "=m" (b->bits[idx]) : "r" (~mask) : "cc"); +} + +/* Atomically toggles the bit numbered IDX in B; + that is, if it is true, makes it false, + and if it is false, makes it true. */ +void +bitmap_flip (struct bitmap *b, size_t bit_idx) +{ + size_t idx = elem_idx (bit_idx); + elem_type mask = bit_mask (bit_idx); + + /* This is equivalent to `b->bits[idx] ^= mask' except that it + is guaranteed to be atomic on a uniprocessor machine. See + the description of the XOR instruction in [IA32-v2b]. */ + asm ("xorl %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc"); +} + +/* Returns the value of the bit numbered IDX in B. */ +bool +bitmap_test (const struct bitmap *b, size_t idx) +{ + ASSERT (b != NULL); + ASSERT (idx < b->bit_cnt); + return (b->bits[elem_idx (idx)] & bit_mask (idx)) != 0; +} + +/* Setting and testing multiple bits. */ + +/* Sets all bits in B to VALUE. */ +void +bitmap_set_all (struct bitmap *b, bool value) +{ + ASSERT (b != NULL); + + bitmap_set_multiple (b, 0, bitmap_size (b), value); +} + +/* Sets the CNT bits starting at START in B to VALUE. */ +void +bitmap_set_multiple (struct bitmap *b, size_t start, size_t cnt, bool value) +{ + size_t i; + + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + ASSERT (start + cnt <= b->bit_cnt); + + for (i = 0; i < cnt; i++) + bitmap_set (b, start + i, value); +} + +/* Returns the number of bits in B between START and START + CNT, + exclusive, that are set to VALUE. */ +size_t +bitmap_count (const struct bitmap *b, size_t start, size_t cnt, bool value) +{ + size_t i, value_cnt; + + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + ASSERT (start + cnt <= b->bit_cnt); + + value_cnt = 0; + for (i = 0; i < cnt; i++) + if (bitmap_test (b, start + i) == value) + value_cnt++; + return value_cnt; +} + +/* Returns true if any bits in B between START and START + CNT, + exclusive, are set to VALUE, and false otherwise. */ +bool +bitmap_contains (const struct bitmap *b, size_t start, size_t cnt, bool value) +{ + size_t i; + + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + ASSERT (start + cnt <= b->bit_cnt); + + for (i = 0; i < cnt; i++) + if (bitmap_test (b, start + i) == value) + return true; + return false; +} + +/* Returns true if any bits in B between START and START + CNT, + exclusive, are set to true, and false otherwise.*/ +bool +bitmap_any (const struct bitmap *b, size_t start, size_t cnt) +{ + return bitmap_contains (b, start, cnt, true); +} + +/* Returns true if no bits in B between START and START + CNT, + exclusive, are set to true, and false otherwise.*/ +bool +bitmap_none (const struct bitmap *b, size_t start, size_t cnt) +{ + return !bitmap_contains (b, start, cnt, true); +} + +/* Returns true if every bit in B between START and START + CNT, + exclusive, is set to true, and false otherwise. */ +bool +bitmap_all (const struct bitmap *b, size_t start, size_t cnt) +{ + return !bitmap_contains (b, start, cnt, false); +} + +/* Finding set or unset bits. */ + +/* Finds and returns the starting index of the first group of CNT + consecutive bits in B at or after START that are all set to + VALUE. + If there is no such group, returns BITMAP_ERROR. */ +size_t +bitmap_scan (const struct bitmap *b, size_t start, size_t cnt, bool value) +{ + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + + if (cnt <= b->bit_cnt) + { + size_t last = b->bit_cnt - cnt; + size_t i; + for (i = start; i <= last; i++) + if (!bitmap_contains (b, i, cnt, !value)) + return i; + } + return BITMAP_ERROR; +} + +/* Finds the first group of CNT consecutive bits in B at or after + START that are all set to VALUE, flips them all to !VALUE, + and returns the index of the first bit in the group. + If there is no such group, returns BITMAP_ERROR. + If CNT is zero, returns 0. + Bits are set atomically, but testing bits is not atomic with + setting them. */ +size_t +bitmap_scan_and_flip (struct bitmap *b, size_t start, size_t cnt, bool value) +{ + size_t idx = bitmap_scan (b, start, cnt, value); + if (idx != BITMAP_ERROR) + bitmap_set_multiple (b, idx, cnt, !value); + return idx; +} + +/* File input and output. */ + +#ifdef FILESYS +/* Returns the number of bytes needed to store B in a file. */ +size_t +bitmap_file_size (const struct bitmap *b) +{ + return byte_cnt (b->bit_cnt); +} + +/* Reads B from FILE. Returns true if successful, false + otherwise. */ +bool +bitmap_read (struct bitmap *b, struct file *file) +{ + bool success = true; + if (b->bit_cnt > 0) + { + off_t size = byte_cnt (b->bit_cnt); + success = file_read_at (file, b->bits, size, 0) == size; + b->bits[elem_cnt (b->bit_cnt) - 1] &= last_mask (b); + } + return success; +} + +/* Writes B to FILE. Return true if successful, false + otherwise. */ +bool +bitmap_write (const struct bitmap *b, struct file *file) +{ + off_t size = byte_cnt (b->bit_cnt); + return file_write_at (file, b->bits, size, 0) == size; +} +#endif /* FILESYS */ + +/* Debugging. */ + +/* Dumps the contents of B to the console as hexadecimal. */ +void +bitmap_dump (const struct bitmap *b) +{ + hex_dump (0, b->bits, byte_cnt (b->bit_cnt), false); +} + diff --git a/tests/devices/src/lib/kernel/bitmap.h b/tests/devices/src/lib/kernel/bitmap.h new file mode 100644 index 0000000..a50593c --- /dev/null +++ b/tests/devices/src/lib/kernel/bitmap.h @@ -0,0 +1,51 @@ +#ifndef __LIB_KERNEL_BITMAP_H +#define __LIB_KERNEL_BITMAP_H + +#include +#include +#include + +/* Bitmap abstract data type. */ + +/* Creation and destruction. */ +struct bitmap *bitmap_create (size_t bit_cnt); +struct bitmap *bitmap_create_in_buf (size_t bit_cnt, void *, size_t byte_cnt); +size_t bitmap_buf_size (size_t bit_cnt); +void bitmap_destroy (struct bitmap *); + +/* Bitmap size. */ +size_t bitmap_size (const struct bitmap *); + +/* Setting and testing single bits. */ +void bitmap_set (struct bitmap *, size_t idx, bool); +void bitmap_mark (struct bitmap *, size_t idx); +void bitmap_reset (struct bitmap *, size_t idx); +void bitmap_flip (struct bitmap *, size_t idx); +bool bitmap_test (const struct bitmap *, size_t idx); + +/* Setting and testing multiple bits. */ +void bitmap_set_all (struct bitmap *, bool); +void bitmap_set_multiple (struct bitmap *, size_t start, size_t cnt, bool); +size_t bitmap_count (const struct bitmap *, size_t start, size_t cnt, bool); +bool bitmap_contains (const struct bitmap *, size_t start, size_t cnt, bool); +bool bitmap_any (const struct bitmap *, size_t start, size_t cnt); +bool bitmap_none (const struct bitmap *, size_t start, size_t cnt); +bool bitmap_all (const struct bitmap *, size_t start, size_t cnt); + +/* Finding set or unset bits. */ +#define BITMAP_ERROR SIZE_MAX +size_t bitmap_scan (const struct bitmap *, size_t start, size_t cnt, bool); +size_t bitmap_scan_and_flip (struct bitmap *, size_t start, size_t cnt, bool); + +/* File input and output. */ +#ifdef FILESYS +struct file; +size_t bitmap_file_size (const struct bitmap *); +bool bitmap_read (struct bitmap *, struct file *); +bool bitmap_write (const struct bitmap *, struct file *); +#endif + +/* Debugging. */ +void bitmap_dump (const struct bitmap *); + +#endif /* lib/kernel/bitmap.h */ diff --git a/tests/devices/src/lib/kernel/console.c b/tests/devices/src/lib/kernel/console.c new file mode 100644 index 0000000..705d2f8 --- /dev/null +++ b/tests/devices/src/lib/kernel/console.c @@ -0,0 +1,191 @@ +#include +#include +#include +#include "devices/serial.h" +#include "devices/vga.h" +#include "threads/init.h" +#include "threads/interrupt.h" +#include "threads/synch.h" + +static void vprintf_helper (char, void *); +static void putchar_have_lock (uint8_t c); + +/* The console lock. + Both the vga and serial layers do their own locking, so it's + safe to call them at any time. + But this lock is useful to prevent simultaneous printf() calls + from mixing their output, which looks confusing. */ +static struct lock console_lock; + +/* True in ordinary circumstances: we want to use the console + lock to avoid mixing output between threads, as explained + above. + + False in early boot before the point that locks are functional + or the console lock has been initialized, or after a kernel + panics. In the former case, taking the lock would cause an + assertion failure, which in turn would cause a panic, turning + it into the latter case. In the latter case, if it is a buggy + lock_acquire() implementation that caused the panic, we'll + likely just recurse. */ +static bool use_console_lock; + +/* It's possible, if you add enough debug output to PintOS, to + try to recursively grab console_lock from a single thread. As + a real example, I added a printf() call to palloc_free(). + Here's a real backtrace that resulted: + + lock_console() + vprintf() + printf() - palloc() tries to grab the lock again + palloc_free() + thread_schedule_tail() - another thread dying as we switch threads + schedule() + thread_yield() + intr_handler() - timer interrupt + intr_set_level() + serial_putc() + putchar_have_lock() + putbuf() + sys_write() - one process writing to the console + syscall_handler() + intr_handler() + + This kind of thing is very difficult to debug, so we avoid the + problem by simulating a recursive lock with a depth + counter. */ +static int console_lock_depth; + +/* Number of characters written to console. */ +static int64_t write_cnt; + +/* Enable console locking. */ +void +console_init (void) +{ + lock_init (&console_lock); + use_console_lock = true; +} + +/* Notifies the console that a kernel panic is underway, + which warns it to avoid trying to take the console lock from + now on. */ +void +console_panic (void) +{ + use_console_lock = false; +} + +/* Prints console statistics. */ +void +console_print_stats (void) +{ + printf ("Console: %lld characters output\n", write_cnt); +} + +/* Acquires the console lock. */ +static void +acquire_console (void) +{ + if (!intr_context () && use_console_lock) + { + if (lock_held_by_current_thread (&console_lock)) + console_lock_depth++; + else + lock_acquire (&console_lock); + } +} + +/* Releases the console lock. */ +static void +release_console (void) +{ + if (!intr_context () && use_console_lock) + { + if (console_lock_depth > 0) + console_lock_depth--; + else + lock_release (&console_lock); + } +} + +/* Returns true if the current thread has the console lock, + false otherwise. */ +static bool +console_locked_by_current_thread (void) +{ + return (intr_context () + || !use_console_lock + || lock_held_by_current_thread (&console_lock)); +} + +/* The standard vprintf() function, + which is like printf() but uses a va_list. + Writes its output to both vga display and serial port. */ +int +vprintf (const char *format, va_list args) +{ + int char_cnt = 0; + + acquire_console (); + __vprintf (format, args, vprintf_helper, &char_cnt); + release_console (); + + return char_cnt; +} + +/* Writes string S to the console, followed by a new-line + character. */ +int +puts (const char *s) +{ + acquire_console (); + while (*s != '\0') + putchar_have_lock (*s++); + putchar_have_lock ('\n'); + release_console (); + + return 0; +} + +/* Writes the N characters in BUFFER to the console. */ +void +putbuf (const char *buffer, size_t n) +{ + acquire_console (); + while (n-- > 0) + putchar_have_lock (*buffer++); + release_console (); +} + +/* Writes C to the vga display and serial port. */ +int +putchar (int c) +{ + acquire_console (); + putchar_have_lock (c); + release_console (); + + return c; +} + +/* Helper function for vprintf(). */ +static void +vprintf_helper (char c, void *char_cnt_) +{ + int *char_cnt = char_cnt_; + (*char_cnt)++; + putchar_have_lock (c); +} + +/* Writes C to the vga display and serial port. + The caller has already acquired the console lock if + appropriate. */ +static void +putchar_have_lock (uint8_t c) +{ + ASSERT (console_locked_by_current_thread ()); + write_cnt++; + serial_putc (c); + vga_putc (c); +} diff --git a/tests/devices/src/lib/kernel/console.h b/tests/devices/src/lib/kernel/console.h new file mode 100644 index 0000000..ab99249 --- /dev/null +++ b/tests/devices/src/lib/kernel/console.h @@ -0,0 +1,8 @@ +#ifndef __LIB_KERNEL_CONSOLE_H +#define __LIB_KERNEL_CONSOLE_H + +void console_init (void); +void console_panic (void); +void console_print_stats (void); + +#endif /* lib/kernel/console.h */ diff --git a/tests/devices/src/lib/kernel/debug.c b/tests/devices/src/lib/kernel/debug.c new file mode 100644 index 0000000..b12f4f9 --- /dev/null +++ b/tests/devices/src/lib/kernel/debug.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include "threads/init.h" +#include "threads/interrupt.h" +#include "threads/thread.h" +#include "threads/switch.h" +#include "threads/vaddr.h" +#include "devices/serial.h" +#include "devices/shutdown.h" + +/* Halts the OS, printing the source file name, line number, and + function name, plus a user-specific message. */ +void +debug_panic (const char *file, int line, const char *function, + const char *message, ...) +{ + static int level; + va_list args; + + intr_disable (); + console_panic (); + + level++; + if (level == 1) + { + printf ("Kernel PANIC at %s:%d in %s(): ", file, line, function); + + va_start (args, message); + vprintf (message, args); + printf ("\n"); + va_end (args); + + debug_backtrace (); + } + else if (level == 2) + printf ("Kernel PANIC recursion at %s:%d in %s().\n", + file, line, function); + else + { + /* Don't print anything: that's probably why we recursed. */ + } + + serial_flush (); + shutdown (); + for (;;); +} + +/* Print call stack of a thread. + The thread may be running, ready, or blocked. */ +static void +print_stacktrace(struct thread *t, void *aux UNUSED) +{ + void *retaddr = NULL, **frame = NULL; + const char *status = "UNKNOWN"; + + switch (t->status) { + case THREAD_RUNNING: + status = "RUNNING"; + break; + + case THREAD_READY: + status = "READY"; + break; + + case THREAD_BLOCKED: + status = "BLOCKED"; + break; + + default: + break; + } + + printf ("Call stack of thread `%s' (status %s):", t->name, status); + + if (t == thread_current()) + { + frame = __builtin_frame_address (1); + retaddr = __builtin_return_address (0); + } + else + { + /* Retrieve the values of the base and instruction pointers + as they were saved when this thread called switch_threads. */ + struct switch_threads_frame * saved_frame; + + saved_frame = (struct switch_threads_frame *)t->stack; + + /* Skip threads if they have been added to the all threads + list, but have never been scheduled. + We can identify because their `stack' member either points + at the top of their kernel stack page, or the + switch_threads_frame's 'eip' member points at switch_entry. + See also threads.c. */ + if (t->stack == (uint8_t *)t + PGSIZE || saved_frame->eip == switch_entry) + { + printf (" thread was never scheduled.\n"); + return; + } + + frame = (void **) saved_frame->ebp; + retaddr = (void *) saved_frame->eip; + } + + printf (" %p", retaddr); + for (; (uintptr_t) frame >= 0x1000 && frame[0] != NULL; frame = frame[0]) + printf (" %p", frame[1]); + printf (".\n"); +} + +/* Prints call stack of all threads. */ +void +debug_backtrace_all (void) +{ + enum intr_level oldlevel = intr_disable (); + + thread_foreach (print_stacktrace, 0); + intr_set_level (oldlevel); +} diff --git a/tests/devices/src/lib/kernel/hash.c b/tests/devices/src/lib/kernel/hash.c new file mode 100644 index 0000000..da2a560 --- /dev/null +++ b/tests/devices/src/lib/kernel/hash.c @@ -0,0 +1,437 @@ +/* Hash table. + + This data structure is thoroughly documented in the Tour of + PintOS for Task 3. + + See hash.h for basic information. */ + +#include "hash.h" +#include "../debug.h" +#include "threads/malloc.h" + +#define list_elem_to_hash_elem(LIST_ELEM) \ + list_entry(LIST_ELEM, struct hash_elem, list_elem) + +static struct list *find_bucket (struct hash *, struct hash_elem *); +static struct hash_elem *find_elem (struct hash *, struct list *, + struct hash_elem *); +static void insert_elem (struct hash *, struct list *, struct hash_elem *); +static void remove_elem (struct hash *, struct hash_elem *); +static void rehash (struct hash *); + +/* Initializes hash table H to compute hash values using HASH and + compare hash elements using LESS, given auxiliary data AUX. */ +bool +hash_init (struct hash *h, + hash_hash_func *hash, hash_less_func *less, void *aux) +{ + h->elem_cnt = 0; + h->bucket_cnt = 4; + h->buckets = malloc (sizeof *h->buckets * h->bucket_cnt); + h->hash = hash; + h->less = less; + h->aux = aux; + + if (h->buckets != NULL) + { + hash_clear (h, NULL); + return true; + } + else + return false; +} + +/* Removes all the elements from H. + + If DESTRUCTOR is non-null, then it is called for each element + in the hash. DESTRUCTOR may, if appropriate, deallocate the + memory used by the hash element. However, modifying hash + table H while hash_clear() is running, using any of the + functions hash_clear(), hash_destroy(), hash_insert(), + hash_replace(), or hash_delete(), yields undefined behavior, + whether done in DESTRUCTOR or elsewhere. */ +void +hash_clear (struct hash *h, hash_action_func *destructor) +{ + size_t i; + + for (i = 0; i < h->bucket_cnt; i++) + { + struct list *bucket = &h->buckets[i]; + + if (destructor != NULL) + while (!list_empty (bucket)) + { + struct list_elem *list_elem = list_pop_front (bucket); + struct hash_elem *hash_elem = list_elem_to_hash_elem (list_elem); + destructor (hash_elem, h->aux); + } + + list_init (bucket); + } + + h->elem_cnt = 0; +} + +/* Destroys hash table H. + + If DESTRUCTOR is non-null, then it is first called for each + element in the hash. DESTRUCTOR may, if appropriate, + deallocate the memory used by the hash element. However, + modifying hash table H while hash_clear() is running, using + any of the functions hash_clear(), hash_destroy(), + hash_insert(), hash_replace(), or hash_delete(), yields + undefined behavior, whether done in DESTRUCTOR or + elsewhere. */ +void +hash_destroy (struct hash *h, hash_action_func *destructor) +{ + if (destructor != NULL) + hash_clear (h, destructor); + free (h->buckets); +} + +/* Inserts NEW into hash table H and returns a null pointer, if + no equal element is already in the table. + If an equal element is already in the table, returns it + without inserting NEW. */ +struct hash_elem * +hash_insert (struct hash *h, struct hash_elem *new) +{ + struct list *bucket = find_bucket (h, new); + struct hash_elem *old = find_elem (h, bucket, new); + + if (old == NULL) + insert_elem (h, bucket, new); + + rehash (h); + + return old; +} + +/* Inserts NEW into hash table H, replacing any equal element + already in the table, which is returned. */ +struct hash_elem * +hash_replace (struct hash *h, struct hash_elem *new) +{ + struct list *bucket = find_bucket (h, new); + struct hash_elem *old = find_elem (h, bucket, new); + + if (old != NULL) + remove_elem (h, old); + insert_elem (h, bucket, new); + + rehash (h); + + return old; +} + +/* Finds and returns an element equal to E in hash table H, or a + null pointer if no equal element exists in the table. */ +struct hash_elem * +hash_find (struct hash *h, struct hash_elem *e) +{ + return find_elem (h, find_bucket (h, e), e); +} + +/* Finds, removes, and returns an element equal to E in hash + table H. Returns a null pointer if no equal element existed + in the table. + + If the elements of the hash table are dynamically allocated, + or own resources that are, then it is the caller's + responsibility to deallocate them. */ +struct hash_elem * +hash_delete (struct hash *h, struct hash_elem *e) +{ + struct hash_elem *found = find_elem (h, find_bucket (h, e), e); + if (found != NULL) + { + remove_elem (h, found); + rehash (h); + } + return found; +} + +/* Calls ACTION for each element in hash table H in arbitrary + order. + Modifying hash table H while hash_apply() is running, using + any of the functions hash_clear(), hash_destroy(), + hash_insert(), hash_replace(), or hash_delete(), yields + undefined behavior, whether done from ACTION or elsewhere. */ +void +hash_apply (struct hash *h, hash_action_func *action) +{ + size_t i; + + ASSERT (action != NULL); + + for (i = 0; i < h->bucket_cnt; i++) + { + struct list *bucket = &h->buckets[i]; + struct list_elem *elem, *next; + + for (elem = list_begin (bucket); elem != list_end (bucket); elem = next) + { + next = list_next (elem); + action (list_elem_to_hash_elem (elem), h->aux); + } + } +} + +/* Initializes I for iterating hash table H. + + Iteration idiom: + + struct hash_iterator i; + + hash_first (&i, h); + while (hash_next (&i)) + { + struct foo *f = hash_entry (hash_cur (&i), struct foo, elem); + ...do something with f... + } + + Modifying hash table H during iteration, using any of the + functions hash_clear(), hash_destroy(), hash_insert(), + hash_replace(), or hash_delete(), invalidates all + iterators. */ +void +hash_first (struct hash_iterator *i, struct hash *h) +{ + ASSERT (i != NULL); + ASSERT (h != NULL); + + i->hash = h; + i->bucket = i->hash->buckets; + i->elem = list_elem_to_hash_elem (list_head (i->bucket)); +} + +/* Advances I to the next element in the hash table and returns + it. Returns a null pointer if no elements are left. Elements + are returned in arbitrary order. + + Modifying a hash table H during iteration, using any of the + functions hash_clear(), hash_destroy(), hash_insert(), + hash_replace(), or hash_delete(), invalidates all + iterators. */ +struct hash_elem * +hash_next (struct hash_iterator *i) +{ + ASSERT (i != NULL); + + i->elem = list_elem_to_hash_elem (list_next (&i->elem->list_elem)); + while (i->elem == list_elem_to_hash_elem (list_end (i->bucket))) + { + if (++i->bucket >= i->hash->buckets + i->hash->bucket_cnt) + { + i->elem = NULL; + break; + } + i->elem = list_elem_to_hash_elem (list_begin (i->bucket)); + } + + return i->elem; +} + +/* Returns the current element in the hash table iteration, or a + null pointer at the end of the table. Undefined behavior + after calling hash_first() but before hash_next(). */ +struct hash_elem * +hash_cur (struct hash_iterator *i) +{ + return i->elem; +} + +/* Returns the number of elements in H. */ +size_t +hash_size (struct hash *h) +{ + return h->elem_cnt; +} + +/* Returns true if H contains no elements, false otherwise. */ +bool +hash_empty (struct hash *h) +{ + return h->elem_cnt == 0; +} + +/* Fowler-Noll-Vo hash constants, for 32-bit word sizes. */ +#define FNV_32_PRIME 16777619u +#define FNV_32_BASIS 2166136261u + +/* Returns a hash of the SIZE bytes in BUF. */ +unsigned +hash_bytes (const void *buf_, size_t size) +{ + /* Fowler-Noll-Vo 32-bit hash, for bytes. */ + const unsigned char *buf = buf_; + unsigned hash; + + ASSERT (buf != NULL); + + hash = FNV_32_BASIS; + while (size-- > 0) + hash = (hash * FNV_32_PRIME) ^ *buf++; + + return hash; +} + +/* Returns a hash of string S. */ +unsigned +hash_string (const char *s_) +{ + const unsigned char *s = (const unsigned char *) s_; + unsigned hash; + + ASSERT (s != NULL); + + hash = FNV_32_BASIS; + while (*s != '\0') + hash = (hash * FNV_32_PRIME) ^ *s++; + + return hash; +} + +/* Returns a hash of integer I. */ +unsigned +hash_int (int i) +{ + return hash_bytes (&i, sizeof i); +} + +/* Returns a hash of pointer P */ +unsigned +hash_ptr (const void *p) +{ + return hash_bytes (&p, sizeof p); +} + +/* Returns the bucket in H that E belongs in. */ +static struct list * +find_bucket (struct hash *h, struct hash_elem *e) +{ + size_t bucket_idx = h->hash (e, h->aux) & (h->bucket_cnt - 1); + return &h->buckets[bucket_idx]; +} + +/* Searches BUCKET in H for a hash element equal to E. Returns + it if found or a null pointer otherwise. */ +static struct hash_elem * +find_elem (struct hash *h, struct list *bucket, struct hash_elem *e) +{ + struct list_elem *i; + + for (i = list_begin (bucket); i != list_end (bucket); i = list_next (i)) + { + struct hash_elem *hi = list_elem_to_hash_elem (i); + if (!h->less (hi, e, h->aux) && !h->less (e, hi, h->aux)) + return hi; + } + return NULL; +} + +/* Returns X with its lowest-order bit set to 1 turned off. */ +static inline size_t +turn_off_least_1bit (size_t x) +{ + return x & (x - 1); +} + +/* Returns true if X is a power of 2, otherwise false. */ +static inline size_t +is_power_of_2 (size_t x) +{ + return x != 0 && turn_off_least_1bit (x) == 0; +} + +/* Element per bucket ratios. */ +#define MIN_ELEMS_PER_BUCKET 1 /* Elems/bucket < 1: reduce # of buckets. */ +#define BEST_ELEMS_PER_BUCKET 2 /* Ideal elems/bucket. */ +#define MAX_ELEMS_PER_BUCKET 4 /* Elems/bucket > 4: increase # of buckets. */ + +/* Changes the number of buckets in hash table H to match the + ideal. This function can fail because of an out-of-memory + condition, but that'll just make hash accesses less efficient; + we can still continue. */ +static void +rehash (struct hash *h) +{ + size_t old_bucket_cnt, new_bucket_cnt; + struct list *new_buckets, *old_buckets; + size_t i; + + ASSERT (h != NULL); + + /* Save old bucket info for later use. */ + old_buckets = h->buckets; + old_bucket_cnt = h->bucket_cnt; + + /* Calculate the number of buckets to use now. + We want one bucket for about every BEST_ELEMS_PER_BUCKET. + We must have at least four buckets, and the number of + buckets must be a power of 2. */ + new_bucket_cnt = h->elem_cnt / BEST_ELEMS_PER_BUCKET; + if (new_bucket_cnt < 4) + new_bucket_cnt = 4; + while (!is_power_of_2 (new_bucket_cnt)) + new_bucket_cnt = turn_off_least_1bit (new_bucket_cnt); + + /* Don't do anything if the bucket count wouldn't change. */ + if (new_bucket_cnt == old_bucket_cnt) + return; + + /* Allocate new buckets and initialize them as empty. */ + new_buckets = malloc (sizeof *new_buckets * new_bucket_cnt); + if (new_buckets == NULL) + { + /* Allocation failed. This means that use of the hash table will + be less efficient. However, it is still usable, so + there's no reason for it to be an error. */ + return; + } + for (i = 0; i < new_bucket_cnt; i++) + list_init (&new_buckets[i]); + + /* Install new bucket info. */ + h->buckets = new_buckets; + h->bucket_cnt = new_bucket_cnt; + + /* Move each old element into the appropriate new bucket. */ + for (i = 0; i < old_bucket_cnt; i++) + { + struct list *old_bucket; + struct list_elem *elem, *next; + + old_bucket = &old_buckets[i]; + for (elem = list_begin (old_bucket); + elem != list_end (old_bucket); elem = next) + { + struct list *new_bucket + = find_bucket (h, list_elem_to_hash_elem (elem)); + next = list_next (elem); + list_remove (elem); + list_push_front (new_bucket, elem); + } + } + + free (old_buckets); +} + +/* Inserts E into BUCKET (in hash table H). */ +static void +insert_elem (struct hash *h, struct list *bucket, struct hash_elem *e) +{ + h->elem_cnt++; + list_push_front (bucket, &e->list_elem); +} + +/* Removes E from hash table H. */ +static void +remove_elem (struct hash *h, struct hash_elem *e) +{ + h->elem_cnt--; + list_remove (&e->list_elem); +} + diff --git a/tests/devices/src/lib/kernel/hash.h b/tests/devices/src/lib/kernel/hash.h new file mode 100644 index 0000000..7dacc26 --- /dev/null +++ b/tests/devices/src/lib/kernel/hash.h @@ -0,0 +1,104 @@ +#ifndef __LIB_KERNEL_HASH_H +#define __LIB_KERNEL_HASH_H + +/* Hash table. + + This data structure is thoroughly documented in the PintOS + manual: Appendix A Reference Guide (A.8 Hash Table) + + This is a standard hash table with chaining. To locate an + element in the table, we compute a hash function over the + element's data and use that as an index into an array of + doubly linked lists, then linearly search the list. + + The chain lists do not use dynamic allocation. Instead, each + structure that can potentially be in a hash must embed a + struct hash_elem member. All of the hash functions operate on + these `struct hash_elem's. The hash_entry macro allows + conversion from a struct hash_elem back to a structure object + that contains it. This is the same technique used in the + linked list implementation. Refer to lib/kernel/list.h for a + detailed explanation. */ + +#include +#include +#include +#include "list.h" + +/* Hash element. */ +struct hash_elem + { + struct list_elem list_elem; + }; + +/* Converts pointer to hash element HASH_ELEM into a pointer to + the structure that HASH_ELEM is embedded inside. Supply the + name of the outer structure STRUCT and the member name MEMBER + of the hash element. See the big comment at the top of the + file for an example. */ +#define hash_entry(HASH_ELEM, STRUCT, MEMBER) \ + ((STRUCT *) ((uint8_t *) &(HASH_ELEM)->list_elem \ + - offsetof (STRUCT, MEMBER.list_elem))) + +/* Computes and returns the hash value for hash element E, given + auxiliary data AUX. */ +typedef unsigned hash_hash_func (const struct hash_elem *e, void *aux); + +/* Compares the value of two hash elements A and B, given + auxiliary data AUX. Returns true if A is less than B, or + false if A is greater than or equal to B. */ +typedef bool hash_less_func (const struct hash_elem *a, + const struct hash_elem *b, + void *aux); + +/* Performs some operation on hash element E, given auxiliary + data AUX. */ +typedef void hash_action_func (struct hash_elem *e, void *aux); + +/* Hash table. */ +struct hash + { + size_t elem_cnt; /* Number of elements in table. */ + size_t bucket_cnt; /* Number of buckets, a power of 2. */ + struct list *buckets; /* Array of `bucket_cnt' lists. */ + hash_hash_func *hash; /* Hash function. */ + hash_less_func *less; /* Comparison function. */ + void *aux; /* Auxiliary data for `hash' and `less'. */ + }; + +/* A hash table iterator. */ +struct hash_iterator + { + struct hash *hash; /* The hash table. */ + struct list *bucket; /* Current bucket. */ + struct hash_elem *elem; /* Current hash element in current bucket. */ + }; + +/* Basic life cycle. */ +bool hash_init (struct hash *, hash_hash_func *, hash_less_func *, void *aux); +void hash_clear (struct hash *, hash_action_func *); +void hash_destroy (struct hash *, hash_action_func *); + +/* Search, insertion, deletion. */ +struct hash_elem *hash_insert (struct hash *, struct hash_elem *); +struct hash_elem *hash_replace (struct hash *, struct hash_elem *); +struct hash_elem *hash_find (struct hash *, struct hash_elem *); +struct hash_elem *hash_delete (struct hash *, struct hash_elem *); + +/* Iteration. */ +void hash_apply (struct hash *, hash_action_func *); +void hash_first (struct hash_iterator *, struct hash *); +struct hash_elem *hash_next (struct hash_iterator *); +struct hash_elem *hash_cur (struct hash_iterator *); + +/* Information. */ +size_t hash_size (struct hash *); +bool hash_empty (struct hash *); + +/* Sample hash functions. */ +unsigned hash_bytes (const void *, size_t); +unsigned hash_string (const char *); +unsigned hash_int (int); +unsigned hash_ptr (const void *); + +#endif /* lib/kernel/hash.h */ diff --git a/tests/devices/src/lib/kernel/list.c b/tests/devices/src/lib/kernel/list.c new file mode 100644 index 0000000..f8f7fbb --- /dev/null +++ b/tests/devices/src/lib/kernel/list.c @@ -0,0 +1,527 @@ +#include "list.h" +#include "../debug.h" + +/* Our doubly linked lists have two header elements: the "head" + just before the first element and the "tail" just after the + last element. The `prev' link of the front header is null, as + is the `next' link of the back header. Their other two links + point toward each other via the interior elements of the list. + + An empty list looks like this: + + +------+ +------+ + <---| head |<--->| tail |---> + +------+ +------+ + + A list with two elements in it looks like this: + + +------+ +-------+ +-------+ +------+ + <---| head |<--->| 1 |<--->| 2 |<--->| tail |---> + +------+ +-------+ +-------+ +------+ + + The symmetry of this arrangement eliminates lots of special + cases in list processing. For example, take a look at + list_remove(): it takes only two pointer assignments and no + conditionals. That's a lot simpler than the code would be + without header elements. + + (Because only one of the pointers in each header element is used, + we could in fact combine them into a single header element + without sacrificing this simplicity. But using two separate + elements allows us to do a little bit of checking on some + operations, which can be valuable.) */ + +static bool is_sorted (struct list_elem *a, struct list_elem *b, + list_less_func *less, void *aux) UNUSED; + +/* Returns true if ELEM is a head, false otherwise. */ +static inline bool +is_head (struct list_elem *elem) +{ + return elem != NULL && elem->prev == NULL && elem->next != NULL; +} + +/* Returns true if ELEM is an interior element, + false otherwise. */ +static inline bool +is_interior (struct list_elem *elem) +{ + return elem != NULL && elem->prev != NULL && elem->next != NULL; +} + +/* Returns true if ELEM is a tail, false otherwise. */ +static inline bool +is_tail (struct list_elem *elem) +{ + return elem != NULL && elem->prev != NULL && elem->next == NULL; +} + +/* Initializes LIST as an empty list. */ +void +list_init (struct list *list) +{ + ASSERT (list != NULL); + list->head.prev = NULL; + list->head.next = &list->tail; + list->tail.prev = &list->head; + list->tail.next = NULL; +} + +/* Returns the beginning of LIST. */ +struct list_elem * +list_begin (struct list *list) +{ + ASSERT (list != NULL); + return list->head.next; +} + +/* Returns the element after ELEM in its list. If ELEM is the + last element in its list, returns the list tail. Results are + undefined if ELEM is itself a list tail. */ +struct list_elem * +list_next (struct list_elem *elem) +{ + ASSERT (is_head (elem) || is_interior (elem)); + return elem->next; +} + +/* Returns LIST's tail. + + list_end() is often used in iterating through a list from + front to back. See the big comment at the top of list.h for + an example. */ +struct list_elem * +list_end (struct list *list) +{ + ASSERT (list != NULL); + return &list->tail; +} + +/* Returns the LIST's reverse beginning, for iterating through + LIST in reverse order, from back to front. */ +struct list_elem * +list_rbegin (struct list *list) +{ + ASSERT (list != NULL); + return list->tail.prev; +} + +/* Returns the element before ELEM in its list. If ELEM is the + first element in its list, returns the list head. Results are + undefined if ELEM is itself a list head. */ +struct list_elem * +list_prev (struct list_elem *elem) +{ + ASSERT (is_interior (elem) || is_tail (elem)); + return elem->prev; +} + +/* Returns LIST's head. + + list_rend() is often used in iterating through a list in + reverse order, from back to front. Here's typical usage, + following the example from the top of list.h: + + for (e = list_rbegin (&foo_list); e != list_rend (&foo_list); + e = list_prev (e)) + { + struct foo *f = list_entry (e, struct foo, elem); + ...do something with f... + } +*/ +struct list_elem * +list_rend (struct list *list) +{ + ASSERT (list != NULL); + return &list->head; +} + +/* Return's LIST's head. + + list_head() can be used for an alternate style of iterating + through a list, e.g.: + + e = list_head (&list); + while ((e = list_next (e)) != list_end (&list)) + { + ... + } +*/ +struct list_elem * +list_head (struct list *list) +{ + ASSERT (list != NULL); + return &list->head; +} + +/* Return's LIST's tail. */ +struct list_elem * +list_tail (struct list *list) +{ + ASSERT (list != NULL); + return &list->tail; +} + +/* Inserts ELEM just before BEFORE, which may be either an + interior element or a tail. The latter case is equivalent to + list_push_back(). Undefined behavior if ELEM is already in the list. */ +void +list_insert (struct list_elem *before, struct list_elem *elem) +{ + ASSERT (is_interior (before) || is_tail (before)); + ASSERT (elem != NULL); + // Sanity checks to prevent (some) loop lists + ASSERT (before != elem); + ASSERT (before->prev != elem); + + elem->prev = before->prev; + elem->next = before; + before->prev->next = elem; + before->prev = elem; +} + +/* Removes elements FIRST though LAST (exclusive) from their + current list, then inserts them just before BEFORE, which may + be either an interior element or a tail. */ +void +list_splice (struct list_elem *before, + struct list_elem *first, struct list_elem *last) +{ + ASSERT (is_interior (before) || is_tail (before)); + if (first == last) + return; + last = list_prev (last); + + ASSERT (is_interior (first)); + ASSERT (is_interior (last)); + + /* Cleanly remove FIRST...LAST from its current list. */ + first->prev->next = last->next; + last->next->prev = first->prev; + + /* Splice FIRST...LAST into new list. */ + first->prev = before->prev; + last->next = before; + before->prev->next = first; + before->prev = last; +} + +/* Inserts ELEM at the beginning of LIST, so that it becomes the + front in LIST. */ +void +list_push_front (struct list *list, struct list_elem *elem) +{ + list_insert (list_begin (list), elem); +} + +/* Inserts ELEM at the end of LIST, so that it becomes the + back in LIST. */ +void +list_push_back (struct list *list, struct list_elem *elem) +{ + list_insert (list_end (list), elem); +} + +/* Removes ELEM from its list and returns the element that + followed it. Undefined behavior if ELEM is not in a list. + + A list element must be treated very carefully after removing + it from its list. Calling list_next() or list_prev() on ELEM + will return the item that was previously before or after ELEM, + but, e.g., list_prev(list_next(ELEM)) is no longer ELEM! + + The list_remove() return value provides a convenient way to + iterate and remove elements from a list: + + for (e = list_begin (&list); e != list_end (&list); e = list_remove (e)) + { + ...do something with e... + } + + If you need to free() elements of the list then you need to be + more conservative. Here's an alternate strategy that works + even in that case: + + while (!list_empty (&list)) + { + struct list_elem *e = list_pop_front (&list); + ...do something with e... + } +*/ +struct list_elem * +list_remove (struct list_elem *elem) +{ + ASSERT (is_interior (elem)); + elem->prev->next = elem->next; + elem->next->prev = elem->prev; + return elem->next; +} + +/* Removes the front element from LIST and returns it. + Undefined behavior if LIST is empty before removal. */ +struct list_elem * +list_pop_front (struct list *list) +{ + struct list_elem *front = list_front (list); + list_remove (front); + return front; +} + +/* Removes the back element from LIST and returns it. + Undefined behavior if LIST is empty before removal. */ +struct list_elem * +list_pop_back (struct list *list) +{ + struct list_elem *back = list_back (list); + list_remove (back); + return back; +} + +/* Returns the front element in LIST. + Undefined behavior if LIST is empty. */ +struct list_elem * +list_front (struct list *list) +{ + ASSERT (!list_empty (list)); + return list->head.next; +} + +/* Returns the back element in LIST. + Undefined behavior if LIST is empty. */ +struct list_elem * +list_back (struct list *list) +{ + ASSERT (!list_empty (list)); + return list->tail.prev; +} + +/* Returns the number of elements in LIST. + Runs in O(n) in the number of elements. */ +size_t +list_size (struct list *list) +{ + struct list_elem *e; + size_t cnt = 0; + + for (e = list_begin (list); e != list_end (list); e = list_next (e)) + cnt++; + return cnt; +} + +/* Returns true if LIST is empty, false otherwise. */ +bool +list_empty (struct list *list) +{ + return list_begin (list) == list_end (list); +} + +/* Swaps the `struct list_elem *'s that A and B point to. */ +static void +swap (struct list_elem **a, struct list_elem **b) +{ + struct list_elem *t = *a; + *a = *b; + *b = t; +} + +/* Reverses the order of LIST. */ +void +list_reverse (struct list *list) +{ + if (!list_empty (list)) + { + struct list_elem *e; + + for (e = list_begin (list); e != list_end (list); e = e->prev) + swap (&e->prev, &e->next); + swap (&list->head.next, &list->tail.prev); + swap (&list->head.next->prev, &list->tail.prev->next); + } +} + +/* Returns true only if the list elements A through B (exclusive) + are in order according to LESS given auxiliary data AUX. */ +static bool +is_sorted (struct list_elem *a, struct list_elem *b, + list_less_func *less, void *aux) +{ + if (a != b) + while ((a = list_next (a)) != b) + if (less (a, list_prev (a), aux)) + return false; + return true; +} + +/* Finds a run, starting at A and ending not after B, of list + elements that are in nondecreasing order according to LESS + given auxiliary data AUX. Returns the (exclusive) end of the + run. + A through B (exclusive) must form a non-empty range. */ +static struct list_elem * +find_end_of_run (struct list_elem *a, struct list_elem *b, + list_less_func *less, void *aux) +{ + ASSERT (a != NULL); + ASSERT (b != NULL); + ASSERT (less != NULL); + ASSERT (a != b); + + do + { + a = list_next (a); + } + while (a != b && !less (a, list_prev (a), aux)); + return a; +} + +/* Merges A0 through A1B0 (exclusive) with A1B0 through B1 + (exclusive) to form a combined range also ending at B1 + (exclusive). Both input ranges must be nonempty and sorted in + nondecreasing order according to LESS given auxiliary data + AUX. The output range will be sorted the same way. */ +static void +inplace_merge (struct list_elem *a0, struct list_elem *a1b0, + struct list_elem *b1, + list_less_func *less, void *aux) +{ + ASSERT (a0 != NULL); + ASSERT (a1b0 != NULL); + ASSERT (b1 != NULL); + ASSERT (less != NULL); + ASSERT (is_sorted (a0, a1b0, less, aux)); + ASSERT (is_sorted (a1b0, b1, less, aux)); + + while (a0 != a1b0 && a1b0 != b1) + if (!less (a1b0, a0, aux)) + a0 = list_next (a0); + else + { + a1b0 = list_next (a1b0); + list_splice (a0, list_prev (a1b0), a1b0); + } +} + +/* Sorts LIST according to LESS given auxiliary data AUX, using a + natural iterative merge sort that runs in O(n lg n) time and + O(1) space in the number of elements in LIST. */ +void +list_sort (struct list *list, list_less_func *less, void *aux) +{ + size_t output_run_cnt; /* Number of runs output in current pass. */ + + ASSERT (list != NULL); + ASSERT (less != NULL); + + /* Pass over the list repeatedly, merging adjacent runs of + nondecreasing elements, until only one run is left. */ + do + { + struct list_elem *a0; /* Start of first run. */ + struct list_elem *a1b0; /* End of first run, start of second. */ + struct list_elem *b1; /* End of second run. */ + + output_run_cnt = 0; + for (a0 = list_begin (list); a0 != list_end (list); a0 = b1) + { + /* Each iteration produces one output run. */ + output_run_cnt++; + + /* Locate two adjacent runs of nondecreasing elements + A0...A1B0 and A1B0...B1. */ + a1b0 = find_end_of_run (a0, list_end (list), less, aux); + if (a1b0 == list_end (list)) + break; + b1 = find_end_of_run (a1b0, list_end (list), less, aux); + + /* Merge the runs. */ + inplace_merge (a0, a1b0, b1, less, aux); + } + } + while (output_run_cnt > 1); + + ASSERT (is_sorted (list_begin (list), list_end (list), less, aux)); +} + +/* Inserts ELEM in the proper position in LIST, which must be + sorted according to LESS given auxiliary data AUX. + Runs in O(n) average case in the number of elements in LIST. */ +void +list_insert_ordered (struct list *list, struct list_elem *elem, + list_less_func *less, void *aux) +{ + struct list_elem *e; + + ASSERT (list != NULL); + ASSERT (elem != NULL); + ASSERT (less != NULL); + + for (e = list_begin (list); e != list_end (list); e = list_next (e)) + if (less (elem, e, aux)) + break; + return list_insert (e, elem); +} + +/* Iterates through LIST and removes all but the first in each + set of adjacent elements that are equal according to LESS + given auxiliary data AUX. If DUPLICATES is non-null, then the + elements from LIST are appended to DUPLICATES. */ +void +list_unique (struct list *list, struct list *duplicates, + list_less_func *less, void *aux) +{ + struct list_elem *elem, *next; + + ASSERT (list != NULL); + ASSERT (less != NULL); + if (list_empty (list)) + return; + + elem = list_begin (list); + while ((next = list_next (elem)) != list_end (list)) + if (!less (elem, next, aux) && !less (next, elem, aux)) + { + list_remove (next); + if (duplicates != NULL) + list_push_back (duplicates, next); + } + else + elem = next; +} + +/* Returns the element in LIST with the largest value according + to LESS given auxiliary data AUX. If there is more than one + maximum, returns the one that appears earlier in the list. If + the list is empty, returns its tail. */ +struct list_elem * +list_max (struct list *list, list_less_func *less, void *aux) +{ + struct list_elem *max = list_begin (list); + if (max != list_end (list)) + { + struct list_elem *e; + + for (e = list_next (max); e != list_end (list); e = list_next (e)) + if (less (max, e, aux)) + max = e; + } + return max; +} + +/* Returns the element in LIST with the smallest value according + to LESS given auxiliary data AUX. If there is more than one + minimum, returns the one that appears earlier in the list. If + the list is empty, returns its tail. */ +struct list_elem * +list_min (struct list *list, list_less_func *less, void *aux) +{ + struct list_elem *min = list_begin (list); + if (min != list_end (list)) + { + struct list_elem *e; + + for (e = list_next (min); e != list_end (list); e = list_next (e)) + if (less (e, min, aux)) + min = e; + } + return min; +} diff --git a/tests/devices/src/lib/kernel/list.h b/tests/devices/src/lib/kernel/list.h new file mode 100644 index 0000000..9c69ca1 --- /dev/null +++ b/tests/devices/src/lib/kernel/list.h @@ -0,0 +1,181 @@ +#ifndef __LIB_KERNEL_LIST_H +#define __LIB_KERNEL_LIST_H + +/* Doubly linked list. + + This implementation of a doubly linked list does not require + use of dynamically allocated memory. Instead, each structure + that is a potential list element must embed a struct list_elem + member. All of the list functions operate on these `struct + list_elem's. The list_entry macro allows conversion from a + struct list_elem back to a structure object that contains it. + + For example, suppose there is a need for a list of `struct + foo'. `struct foo' should contain a `struct list_elem' + member, like so: + + struct foo + { + struct list_elem elem; + int bar; + ...other members... + }; + + Then a list of `struct foo' can be be declared and initialized + like so: + + struct list foo_list; + + list_init (&foo_list); + + Iteration is a typical situation where it is necessary to + convert from a struct list_elem back to its enclosing + structure. Here's an example using foo_list: + + struct list_elem *e; + + for (e = list_begin (&foo_list); e != list_end (&foo_list); + e = list_next (e)) + { + struct foo *f = list_entry (e, struct foo, elem); + ...do something with f... + } + + You can find real examples of list usage throughout the + source; for example, malloc.c, palloc.c, and thread.c in the + threads directory all use lists. + + The interface for this list is inspired by the list<> template + in the C++ STL. If you're familiar with list<>, you should + find this easy to use. However, it should be emphasized that + these lists do *no* type checking and can't do much other + correctness checking. If you screw up, it will bite you. + + Glossary of list terms: + + - "front": The first element in a list. Undefined in an + empty list. Returned by list_front(). + + - "back": The last element in a list. Undefined in an empty + list. Returned by list_back(). + + - "tail": The element figuratively just after the last + element of a list. Well defined even in an empty list. + Returned by list_end(). Used as the end sentinel for an + iteration from front to back. + + - "beginning": In a non-empty list, the front. In an empty + list, the tail. Returned by list_begin(). Used as the + starting point for an iteration from front to back. + + - "head": The element figuratively just before the first + element of a list. Well defined even in an empty list. + Returned by list_rend(). Used as the end sentinel for an + iteration from back to front. + + - "reverse beginning": In a non-empty list, the back. In an + empty list, the head. Returned by list_rbegin(). Used as + the starting point for an iteration from back to front. + + - "interior element": An element that is not the head or + tail, that is, a real list element. An empty list does + not have any interior elements. +*/ + +#include +#include +#include + +/* List element. */ +struct list_elem + { + struct list_elem *prev; /* Previous list element. */ + struct list_elem *next; /* Next list element. */ + }; + +/* List. */ +struct list + { + struct list_elem head; /* List head. */ + struct list_elem tail; /* List tail. */ + }; + +/* Converts pointer to list element LIST_ELEM into a pointer to + the structure that LIST_ELEM is embedded inside. Supply the + name of the outer structure STRUCT and the member name MEMBER + of the list element. See the big comment at the top of the + file for an example. */ +#define list_entry(LIST_ELEM, STRUCT, MEMBER) \ + ((STRUCT *) ((uint8_t *) &(LIST_ELEM)->next \ + - offsetof (STRUCT, MEMBER.next))) + +/* List initialization. + + A list may be initialized by calling list_init(): + + struct list my_list; + list_init (&my_list); + + or with an initializer using LIST_INITIALIZER: + + struct list my_list = LIST_INITIALIZER (my_list); */ +#define LIST_INITIALIZER(NAME) { { NULL, &(NAME).tail }, \ + { &(NAME).head, NULL } } + +void list_init (struct list *); + +/* List traversal. */ +struct list_elem *list_begin (struct list *); +struct list_elem *list_next (struct list_elem *); +struct list_elem *list_end (struct list *); + +struct list_elem *list_rbegin (struct list *); +struct list_elem *list_prev (struct list_elem *); +struct list_elem *list_rend (struct list *); + +struct list_elem *list_head (struct list *); +struct list_elem *list_tail (struct list *); + +/* List insertion. */ +void list_insert (struct list_elem *, struct list_elem *); +void list_splice (struct list_elem *before, + struct list_elem *first, struct list_elem *last); +void list_push_front (struct list *, struct list_elem *); +void list_push_back (struct list *, struct list_elem *); + +/* List removal. */ +struct list_elem *list_remove (struct list_elem *); +struct list_elem *list_pop_front (struct list *); +struct list_elem *list_pop_back (struct list *); + +/* List elements. */ +struct list_elem *list_front (struct list *); +struct list_elem *list_back (struct list *); + +/* List properties. */ +size_t list_size (struct list *); +bool list_empty (struct list *); + +/* Miscellaneous. */ +void list_reverse (struct list *); + +/* Compares the value of two list elements A and B, given + auxiliary data AUX. Returns true if A is less than B, or + false if A is greater than or equal to B. */ +typedef bool list_less_func (const struct list_elem *a, + const struct list_elem *b, + void *aux); + +/* Operations on lists with ordered elements. */ +void list_sort (struct list *, + list_less_func *, void *aux); +void list_insert_ordered (struct list *, struct list_elem *, + list_less_func *, void *aux); +void list_unique (struct list *, struct list *duplicates, + list_less_func *, void *aux); + +/* Max and min. */ +struct list_elem *list_max (struct list *, list_less_func *, void *aux); +struct list_elem *list_min (struct list *, list_less_func *, void *aux); + +#endif /* lib/kernel/list.h */ diff --git a/tests/devices/src/lib/kernel/stdio.h b/tests/devices/src/lib/kernel/stdio.h new file mode 100644 index 0000000..3e5bae9 --- /dev/null +++ b/tests/devices/src/lib/kernel/stdio.h @@ -0,0 +1,6 @@ +#ifndef __LIB_KERNEL_STDIO_H +#define __LIB_KERNEL_STDIO_H + +void putbuf (const char *, size_t); + +#endif /* lib/kernel/stdio.h */ diff --git a/tests/devices/src/lib/limits.h b/tests/devices/src/lib/limits.h new file mode 100644 index 0000000..c957ec4 --- /dev/null +++ b/tests/devices/src/lib/limits.h @@ -0,0 +1,34 @@ +#ifndef __LIB_LIMITS_H +#define __LIB_LIMITS_H + +#define CHAR_BIT 8 + +#define SCHAR_MAX 127 +#define SCHAR_MIN (-SCHAR_MAX - 1) +#define UCHAR_MAX 255 + +#ifdef __CHAR_UNSIGNED__ +#define CHAR_MIN 0 +#define CHAR_MAX UCHAR_MAX +#else +#define CHAR_MIN SCHAR_MIN +#define CHAR_MAX SCHAR_MAX +#endif + +#define SHRT_MAX 32767 +#define SHRT_MIN (-SHRT_MAX - 1) +#define USHRT_MAX 65535 + +#define INT_MAX 2147483647 +#define INT_MIN (-INT_MAX - 1) +#define UINT_MAX 4294967295U + +#define LONG_MAX 2147483647L +#define LONG_MIN (-LONG_MAX - 1) +#define ULONG_MAX 4294967295UL + +#define LLONG_MAX 9223372036854775807LL +#define LLONG_MIN (-LLONG_MAX - 1) +#define ULLONG_MAX 18446744073709551615ULL + +#endif /* lib/limits.h */ diff --git a/tests/devices/src/lib/packed.h b/tests/devices/src/lib/packed.h new file mode 100644 index 0000000..9a9b6e2 --- /dev/null +++ b/tests/devices/src/lib/packed.h @@ -0,0 +1,10 @@ +#ifndef __LIB_PACKED_H +#define __LIB_PACKED_H + +/* The "packed" attribute, when applied to a structure, prevents + GCC from inserting padding bytes between or after structure + members. It must be specified at the time of the structure's + definition, normally just after the closing brace. */ +#define PACKED __attribute__ ((packed)) + +#endif /* lib/packed.h */ diff --git a/tests/devices/src/lib/random.c b/tests/devices/src/lib/random.c new file mode 100644 index 0000000..a4761b6 --- /dev/null +++ b/tests/devices/src/lib/random.c @@ -0,0 +1,83 @@ +#include "random.h" +#include +#include +#include "debug.h" + +/* RC4-based pseudo-random number generator (PRNG). + + RC4 is a stream cipher. We're not using it here for its + cryptographic properties, but because it is easy to implement + and its output is plenty random for non-cryptographic + purposes. + + See http://en.wikipedia.org/wiki/RC4_(cipher) for information + on RC4.*/ + +/* RC4 state. */ +static uint8_t s[256]; /* S[]. */ +static uint8_t s_i, s_j; /* i, j. */ + +/* Already initialized? */ +static bool inited; + +/* Swaps the bytes pointed to by A and B. */ +static inline void +swap_byte (uint8_t *a, uint8_t *b) +{ + uint8_t t = *a; + *a = *b; + *b = t; +} + +/* Initializes or reinitializes the PRNG with the given SEED. */ +void +random_init (unsigned seed) +{ + uint8_t *seedp = (uint8_t *) &seed; + int i; + uint8_t j; + + for (i = 0; i < 256; i++) + s[i] = i; + for (i = j = 0; i < 256; i++) + { + j += s[i] + seedp[i % sizeof seed]; + swap_byte (s + i, s + j); + } + + s_i = s_j = 0; + inited = true; +} + +/* Writes SIZE random bytes into BUF. */ +void +random_bytes (void *buf_, size_t size) +{ + uint8_t *buf; + + if (!inited) + random_init (0); + + for (buf = buf_; size-- > 0; buf++) + { + uint8_t s_k; + + s_i++; + s_j += s[s_i]; + swap_byte (s + s_i, s + s_j); + + s_k = s[s_i] + s[s_j]; + *buf = s[s_k]; + } +} + +/* Returns a pseudo-random unsigned long. + Use random_ulong() % n to obtain a random number in the range + 0...n (exclusive). */ +unsigned long +random_ulong (void) +{ + unsigned long ul; + random_bytes (&ul, sizeof ul); + return ul; +} diff --git a/tests/devices/src/lib/random.h b/tests/devices/src/lib/random.h new file mode 100644 index 0000000..0950ae2 --- /dev/null +++ b/tests/devices/src/lib/random.h @@ -0,0 +1,10 @@ +#ifndef __LIB_RANDOM_H +#define __LIB_RANDOM_H + +#include + +void random_init (unsigned seed); +void random_bytes (void *, size_t); +unsigned long random_ulong (void); + +#endif /* lib/random.h */ diff --git a/tests/devices/src/lib/round.h b/tests/devices/src/lib/round.h new file mode 100644 index 0000000..3aa6642 --- /dev/null +++ b/tests/devices/src/lib/round.h @@ -0,0 +1,18 @@ +#ifndef __LIB_ROUND_H +#define __LIB_ROUND_H + +/* Yields X rounded up to the nearest multiple of STEP. + For X >= 0, STEP >= 1 only. */ +#define ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP) * (STEP)) + +/* Yields X divided by STEP, rounded up. + For X >= 0, STEP >= 1 only. */ +#define DIV_ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP)) + +/* Yields X rounded down to the nearest multiple of STEP. + For X >= 0, STEP >= 1 only. */ +#define ROUND_DOWN(X, STEP) ((X) / (STEP) * (STEP)) + +/* There is no DIV_ROUND_DOWN. It would be simply X / STEP. */ + +#endif /* lib/round.h */ diff --git a/tests/devices/src/lib/stdarg.h b/tests/devices/src/lib/stdarg.h new file mode 100644 index 0000000..32622b5 --- /dev/null +++ b/tests/devices/src/lib/stdarg.h @@ -0,0 +1,14 @@ +#ifndef __LIB_STDARG_H +#define __LIB_STDARG_H + +/* GCC has functionality as built-ins, + so all we need is to use it. */ + +typedef __builtin_va_list va_list; + +#define va_start(LIST, ARG) __builtin_va_start (LIST, ARG) +#define va_end(LIST) __builtin_va_end (LIST) +#define va_arg(LIST, TYPE) __builtin_va_arg (LIST, TYPE) +#define va_copy(DST, SRC) __builtin_va_copy (DST, SRC) + +#endif /* lib/stdarg.h */ diff --git a/tests/devices/src/lib/stdbool.h b/tests/devices/src/lib/stdbool.h new file mode 100644 index 0000000..f173a91 --- /dev/null +++ b/tests/devices/src/lib/stdbool.h @@ -0,0 +1,9 @@ +#ifndef __LIB_STDBOOL_H +#define __LIB_STDBOOL_H + +#define bool _Bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined 1 + +#endif /* lib/stdbool.h */ diff --git a/tests/devices/src/lib/stddef.h b/tests/devices/src/lib/stddef.h new file mode 100644 index 0000000..4e74fa6 --- /dev/null +++ b/tests/devices/src/lib/stddef.h @@ -0,0 +1,12 @@ +#ifndef __LIB_STDDEF_H +#define __LIB_STDDEF_H + +#define NULL ((void *) 0) +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *) 0)->MEMBER) + +/* GCC predefines the types we need for ptrdiff_t and size_t, + so that we don't have to guess. */ +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __SIZE_TYPE__ size_t; + +#endif /* lib/stddef.h */ diff --git a/tests/devices/src/lib/stdint.h b/tests/devices/src/lib/stdint.h new file mode 100644 index 0000000..ef5f214 --- /dev/null +++ b/tests/devices/src/lib/stdint.h @@ -0,0 +1,51 @@ +#ifndef __LIB_STDINT_H +#define __LIB_STDINT_H + +typedef signed char int8_t; +#define INT8_MAX 127 +#define INT8_MIN (-INT8_MAX - 1) + +typedef signed short int int16_t; +#define INT16_MAX 32767 +#define INT16_MIN (-INT16_MAX - 1) + +typedef signed int int32_t; +#define INT32_MAX 2147483647 +#define INT32_MIN (-INT32_MAX - 1) + +typedef signed long long int int64_t; +#define INT64_MAX 9223372036854775807LL +#define INT64_MIN (-INT64_MAX - 1) + +typedef unsigned char uint8_t; +#define UINT8_MAX 255 + +typedef unsigned short int uint16_t; +#define UINT16_MAX 65535 + +typedef unsigned int uint32_t; +#define UINT32_MAX 4294967295U + +typedef unsigned long long int uint64_t; +#define UINT64_MAX 18446744073709551615ULL + +typedef int32_t intptr_t; +#define INTPTR_MIN INT32_MIN +#define INTPTR_MAX INT32_MAX + +typedef uint32_t uintptr_t; +#define UINTPTR_MAX UINT32_MAX + +typedef int64_t intmax_t; +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX + +typedef uint64_t uintmax_t; +#define UINTMAX_MAX UINT64_MAX + +#define PTRDIFF_MIN INT32_MIN +#define PTRDIFF_MAX INT32_MAX + +#define SIZE_MAX UINT32_MAX + +#endif /* lib/stdint.h */ diff --git a/tests/devices/src/lib/stdio.c b/tests/devices/src/lib/stdio.c new file mode 100644 index 0000000..843153b --- /dev/null +++ b/tests/devices/src/lib/stdio.c @@ -0,0 +1,655 @@ +#include +#include +#include +#include +#include +#include + +/* Auxiliary data for vsnprintf_helper(). */ +struct vsnprintf_aux + { + char *p; /* Current output position. */ + int length; /* Length of output string. */ + int max_length; /* Max length of output string. */ + }; + +static void vsnprintf_helper (char, void *); + +/* Like vprintf(), except that output is stored into BUFFER, + which must have space for BUF_SIZE characters. Writes at most + BUF_SIZE - 1 characters to BUFFER, followed by a null + terminator. BUFFER will always be null-terminated unless + BUF_SIZE is zero. Returns the number of characters that would + have been written to BUFFER, not including a null terminator, + had there been enough room. */ +int +vsnprintf (char *buffer, size_t buf_size, const char *format, va_list args) +{ + /* Set up aux data for vsnprintf_helper(). */ + struct vsnprintf_aux aux; + aux.p = buffer; + aux.length = 0; + aux.max_length = buf_size > 0 ? buf_size - 1 : 0; + + /* Do most of the work. */ + __vprintf (format, args, vsnprintf_helper, &aux); + + /* Add null terminator. */ + if (buf_size > 0) + *aux.p = '\0'; + + return aux.length; +} + +/* Helper function for vsnprintf(). */ +static void +vsnprintf_helper (char ch, void *aux_) +{ + struct vsnprintf_aux *aux = aux_; + + if (aux->length++ < aux->max_length) + *aux->p++ = ch; +} + +/* Like printf(), except that output is stored into BUFFER, + which must have space for BUF_SIZE characters. Writes at most + BUF_SIZE - 1 characters to BUFFER, followed by a null + terminator. BUFFER will always be null-terminated unless + BUF_SIZE is zero. Returns the number of characters that would + have been written to BUFFER, not including a null terminator, + had there been enough room. */ +int +snprintf (char *buffer, size_t buf_size, const char *format, ...) +{ + va_list args; + int retval; + + va_start (args, format); + retval = vsnprintf (buffer, buf_size, format, args); + va_end (args); + + return retval; +} + +/* Writes formatted output to the console. + In the kernel, the console is both the video display and first + serial port. + In userspace, the console is file descriptor 1. */ +int +printf (const char *format, ...) +{ + va_list args; + int retval; + + va_start (args, format); + retval = vprintf (format, args); + va_end (args); + + return retval; +} + +/* printf() formatting internals. */ + +/* A printf() conversion. */ +struct printf_conversion + { + /* Flags. */ + enum + { + MINUS = 1 << 0, /* '-' */ + PLUS = 1 << 1, /* '+' */ + SPACE = 1 << 2, /* ' ' */ + POUND = 1 << 3, /* '#' */ + ZERO = 1 << 4, /* '0' */ + GROUP = 1 << 5 /* '\'' */ + } + flags; + + /* Minimum field width. */ + int width; + + /* Numeric precision. + -1 indicates no precision was specified. */ + int precision; + + /* Type of argument to format. */ + enum + { + CHAR = 1, /* hh */ + SHORT = 2, /* h */ + INT = 3, /* (none) */ + INTMAX = 4, /* j */ + LONG = 5, /* l */ + LONGLONG = 6, /* ll */ + PTRDIFFT = 7, /* t */ + SIZET = 8 /* z */ + } + type; + }; + +struct integer_base + { + int base; /* Base. */ + const char *digits; /* Collection of digits. */ + int x; /* `x' character to use, for base 16 only. */ + int group; /* Number of digits to group with ' flag. */ + }; + +static const struct integer_base base_d = {10, "0123456789", 0, 3}; +static const struct integer_base base_o = {8, "01234567", 0, 3}; +static const struct integer_base base_x = {16, "0123456789abcdef", 'x', 4}; +static const struct integer_base base_X = {16, "0123456789ABCDEF", 'X', 4}; + +static const char *parse_conversion (const char *format, + struct printf_conversion *, + va_list *); +static void format_integer (uintmax_t value, bool is_signed, bool negative, + const struct integer_base *, + const struct printf_conversion *, + void (*output) (char, void *), void *aux); +static void output_dup (char ch, size_t cnt, + void (*output) (char, void *), void *aux); +static void format_string (const char *string, int length, + struct printf_conversion *, + void (*output) (char, void *), void *aux); + +void +__vprintf (const char *format, va_list args, + void (*output) (char, void *), void *aux) +{ + for (; *format != '\0'; format++) + { + struct printf_conversion c; + + /* Literally copy non-conversions to output. */ + if (*format != '%') + { + output (*format, aux); + continue; + } + format++; + + /* %% => %. */ + if (*format == '%') + { + output ('%', aux); + continue; + } + + /* Parse conversion specifiers. */ + format = parse_conversion (format, &c, &args); + + /* Do conversion. */ + switch (*format) + { + case 'd': + case 'i': + { + /* Signed integer conversions. */ + intmax_t value; + + switch (c.type) + { + case CHAR: + value = (signed char) va_arg (args, int); + break; + case SHORT: + value = (short) va_arg (args, int); + break; + case INT: + value = va_arg (args, int); + break; + case INTMAX: + value = va_arg (args, intmax_t); + break; + case LONG: + value = va_arg (args, long); + break; + case LONGLONG: + value = va_arg (args, long long); + break; + case PTRDIFFT: + value = va_arg (args, ptrdiff_t); + break; + case SIZET: + value = va_arg (args, size_t); + if (value > SIZE_MAX / 2) + value = value - SIZE_MAX - 1; + break; + default: + NOT_REACHED (); + } + + format_integer (value < 0 ? -value : value, + true, value < 0, &base_d, &c, output, aux); + } + break; + + case 'o': + case 'u': + case 'x': + case 'X': + { + /* Unsigned integer conversions. */ + uintmax_t value; + const struct integer_base *b; + + switch (c.type) + { + case CHAR: + value = (unsigned char) va_arg (args, unsigned); + break; + case SHORT: + value = (unsigned short) va_arg (args, unsigned); + break; + case INT: + value = va_arg (args, unsigned); + break; + case INTMAX: + value = va_arg (args, uintmax_t); + break; + case LONG: + value = va_arg (args, unsigned long); + break; + case LONGLONG: + value = va_arg (args, unsigned long long); + break; + case PTRDIFFT: + value = va_arg (args, ptrdiff_t); +#if UINTMAX_MAX != PTRDIFF_MAX + value &= ((uintmax_t) PTRDIFF_MAX << 1) | 1; +#endif + break; + case SIZET: + value = va_arg (args, size_t); + break; + default: + NOT_REACHED (); + } + + switch (*format) + { + case 'o': b = &base_o; break; + case 'u': b = &base_d; break; + case 'x': b = &base_x; break; + case 'X': b = &base_X; break; + default: NOT_REACHED (); + } + + format_integer (value, false, false, b, &c, output, aux); + } + break; + + case 'c': + { + /* Treat character as single-character string. */ + char ch = va_arg (args, int); + format_string (&ch, 1, &c, output, aux); + } + break; + + case 's': + { + /* String conversion. */ + const char *s = va_arg (args, char *); + if (s == NULL) + s = "(null)"; + + /* Limit string length according to precision. + Note: if c.precision == -1 then strnlen() will get + SIZE_MAX for MAXLEN, which is just what we want. */ + format_string (s, strnlen (s, c.precision), &c, output, aux); + } + break; + + case 'p': + { + /* Pointer conversion. + Format pointers as %#x. */ + void *p = va_arg (args, void *); + + c.flags = POUND; + format_integer ((uintptr_t) p, false, false, + &base_x, &c, output, aux); + } + break; + + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + case 'n': + /* We don't support floating-point arithmetic, + and %n can be part of a security hole. */ + __printf ("<>", output, aux, *format); + break; + + default: + __printf ("<>", output, aux, *format); + break; + } + } +} + +/* Parses conversion option characters starting at FORMAT and + initializes C appropriately. Returns the character in FORMAT + that indicates the conversion (e.g. the `d' in `%d'). Uses + *ARGS for `*' field widths and precisions. */ +static const char * +parse_conversion (const char *format, struct printf_conversion *c, + va_list *args) +{ + /* Parse flag characters. */ + c->flags = 0; + for (;;) + { + switch (*format++) + { + case '-': + c->flags |= MINUS; + break; + case '+': + c->flags |= PLUS; + break; + case ' ': + c->flags |= SPACE; + break; + case '#': + c->flags |= POUND; + break; + case '0': + c->flags |= ZERO; + break; + case '\'': + c->flags |= GROUP; + break; + default: + format--; + goto not_a_flag; + } + } + not_a_flag: + if (c->flags & MINUS) + c->flags &= ~ZERO; + if (c->flags & PLUS) + c->flags &= ~SPACE; + + /* Parse field width. */ + c->width = 0; + if (*format == '*') + { + format++; + c->width = va_arg (*args, int); + } + else + { + for (; isdigit (*format); format++) + c->width = c->width * 10 + *format - '0'; + } + if (c->width < 0) + { + c->width = -c->width; + c->flags |= MINUS; + } + + /* Parse precision. */ + c->precision = -1; + if (*format == '.') + { + format++; + if (*format == '*') + { + format++; + c->precision = va_arg (*args, int); + } + else + { + c->precision = 0; + for (; isdigit (*format); format++) + c->precision = c->precision * 10 + *format - '0'; + } + if (c->precision < 0) + c->precision = -1; + } + if (c->precision >= 0) + c->flags &= ~ZERO; + + /* Parse type. */ + c->type = INT; + switch (*format++) + { + case 'h': + if (*format == 'h') + { + format++; + c->type = CHAR; + } + else + c->type = SHORT; + break; + + case 'j': + c->type = INTMAX; + break; + + case 'l': + if (*format == 'l') + { + format++; + c->type = LONGLONG; + } + else + c->type = LONG; + break; + + case 't': + c->type = PTRDIFFT; + break; + + case 'z': + c->type = SIZET; + break; + + default: + format--; + break; + } + + return format; +} + +/* Performs an integer conversion, writing output to OUTPUT with + auxiliary data AUX. The integer converted has absolute value + VALUE. If IS_SIGNED is true, does a signed conversion with + NEGATIVE indicating a negative value; otherwise does an + unsigned conversion and ignores NEGATIVE. The output is done + according to the provided base B. Details of the conversion + are in C. */ +static void +format_integer (uintmax_t value, bool is_signed, bool negative, + const struct integer_base *b, + const struct printf_conversion *c, + void (*output) (char, void *), void *aux) +{ + char buf[64], *cp; /* Buffer and current position. */ + int x; /* `x' character to use or 0 if none. */ + int sign; /* Sign character or 0 if none. */ + int precision; /* Rendered precision. */ + int pad_cnt; /* # of pad characters to fill field width. */ + int digit_cnt; /* # of digits output so far. */ + + /* Determine sign character, if any. + An unsigned conversion will never have a sign character, + even if one of the flags requests one. */ + sign = 0; + if (is_signed) + { + if (c->flags & PLUS) + sign = negative ? '-' : '+'; + else if (c->flags & SPACE) + sign = negative ? '-' : ' '; + else if (negative) + sign = '-'; + } + + /* Determine whether to include `0x' or `0X'. + It will only be included with a hexadecimal conversion of a + nonzero value with the # flag. */ + x = (c->flags & POUND) && value ? b->x : 0; + + /* Accumulate digits into buffer. + This algorithm produces digits in reverse order, so later we + will output the buffer's content in reverse. */ + cp = buf; + digit_cnt = 0; + while (value > 0) + { + if ((c->flags & GROUP) && digit_cnt > 0 && digit_cnt % b->group == 0) + *cp++ = ','; + *cp++ = b->digits[value % b->base]; + value /= b->base; + digit_cnt++; + } + + /* Append enough zeros to match precision. + If requested precision is 0, then a value of zero is + rendered as a null string, otherwise as "0". + If the # flag is used with base 8, the result must always + begin with a zero. */ + precision = c->precision < 0 ? 1 : c->precision; + while (cp - buf < precision && cp < buf + sizeof buf - 1) + *cp++ = '0'; + if ((c->flags & POUND) && b->base == 8 && (cp == buf || cp[-1] != '0')) + *cp++ = '0'; + + /* Calculate number of pad characters to fill field width. */ + pad_cnt = c->width - (cp - buf) - (x ? 2 : 0) - (sign != 0); + if (pad_cnt < 0) + pad_cnt = 0; + + /* Do output. */ + if ((c->flags & (MINUS | ZERO)) == 0) + output_dup (' ', pad_cnt, output, aux); + if (sign) + output (sign, aux); + if (x) + { + output ('0', aux); + output (x, aux); + } + if (c->flags & ZERO) + output_dup ('0', pad_cnt, output, aux); + while (cp > buf) + output (*--cp, aux); + if (c->flags & MINUS) + output_dup (' ', pad_cnt, output, aux); +} + +/* Writes CH to OUTPUT with auxiliary data AUX, CNT times. */ +static void +output_dup (char ch, size_t cnt, void (*output) (char, void *), void *aux) +{ + while (cnt-- > 0) + output (ch, aux); +} + +/* Formats the LENGTH characters starting at STRING according to + the conversion specified in C. Writes output to OUTPUT with + auxiliary data AUX. */ +static void +format_string (const char *string, int length, + struct printf_conversion *c, + void (*output) (char, void *), void *aux) +{ + int i; + if (c->width > length && (c->flags & MINUS) == 0) + output_dup (' ', c->width - length, output, aux); + for (i = 0; i < length; i++) + output (string[i], aux); + if (c->width > length && (c->flags & MINUS) != 0) + output_dup (' ', c->width - length, output, aux); +} + +/* Wrapper for __vprintf() that converts varargs into a + va_list. */ +void +__printf (const char *format, + void (*output) (char, void *), void *aux, ...) +{ + va_list args; + + va_start (args, aux); + __vprintf (format, args, output, aux); + va_end (args); +} + +/* Dumps the SIZE bytes in BUF to the console as hex bytes + arranged 16 per line. Numeric offsets are also included, + starting at OFS for the first byte in BUF. If ASCII is true + then the corresponding ASCII characters are also rendered + alongside. */ +void +hex_dump (uintptr_t ofs, const void *buf_, size_t size, bool ascii) +{ + const uint8_t *buf = buf_; + const size_t per_line = 16; /* Maximum bytes per line. */ + + while (size > 0) + { + size_t start, end, n; + size_t i; + + /* Number of bytes on this line. */ + start = ofs % per_line; + end = per_line; + if (end - start > size) + end = start + size; + n = end - start; + + /* Print line. */ + printf ("%08jx ", (uintmax_t) ROUND_DOWN (ofs, per_line)); + for (i = 0; i < start; i++) + printf (" "); + for (; i < end; i++) + printf ("%02hhx%c", + buf[i - start], i == per_line / 2 - 1? '-' : ' '); + if (ascii) + { + for (; i < per_line; i++) + printf (" "); + printf ("|"); + for (i = 0; i < start; i++) + printf (" "); + for (; i < end; i++) + printf ("%c", + isprint (buf[i - start]) ? buf[i - start] : '.'); + for (; i < per_line; i++) + printf (" "); + printf ("|"); + } + printf ("\n"); + + ofs += n; + buf += n; + size -= n; + } +} + +/* Prints SIZE, which represents a number of bytes, in a + human-readable format, e.g. "256 kB". */ +void +print_human_readable_size (uint64_t size) +{ + if (size == 1) + printf ("1 byte"); + else + { + static const char *factors[] = {"bytes", "kB", "MB", "GB", "TB", NULL}; + const char **fp; + + for (fp = factors; size >= 1024 && fp[1] != NULL; fp++) + size /= 1024; + printf ("%"PRIu64" %s", size, *fp); + } +} diff --git a/tests/devices/src/lib/stdio.h b/tests/devices/src/lib/stdio.h new file mode 100644 index 0000000..2739c0a --- /dev/null +++ b/tests/devices/src/lib/stdio.h @@ -0,0 +1,40 @@ +#ifndef __LIB_STDIO_H +#define __LIB_STDIO_H + +#include +#include +#include +#include +#include + +/* Include lib/user/stdio.h or lib/kernel/stdio.h, as + appropriate. */ +#include_next + +/* Predefined file handles. */ +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 + +/* Standard functions. */ +int printf (const char *, ...) PRINTF_FORMAT (1, 2); +int snprintf (char *, size_t, const char *, ...) PRINTF_FORMAT (3, 4); +int vprintf (const char *, va_list) PRINTF_FORMAT (1, 0); +int vsnprintf (char *, size_t, const char *, va_list) PRINTF_FORMAT (3, 0); +int putchar (int); +int puts (const char *); + +/* Nonstandard functions. */ +void hex_dump (uintptr_t ofs, const void *, size_t size, bool ascii); +void print_human_readable_size (uint64_t sz); + +/* Internal functions. */ +void __vprintf (const char *format, va_list args, + void (*output) (char, void *), void *aux); +void __printf (const char *format, + void (*output) (char, void *), void *aux, ...); + +/* Try to be helpful. */ +#define sprintf dont_use_sprintf_use_snprintf +#define vsprintf dont_use_vsprintf_use_vsnprintf + +#endif /* lib/stdio.h */ diff --git a/tests/devices/src/lib/stdlib.c b/tests/devices/src/lib/stdlib.c new file mode 100644 index 0000000..84c7f61 --- /dev/null +++ b/tests/devices/src/lib/stdlib.c @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include + +/* Converts a string representation of a signed decimal integer + in S into an `int', which is returned. */ +int +atoi (const char *s) +{ + bool negative; + int value; + + ASSERT (s != NULL); + + /* Skip white space. */ + while (isspace ((unsigned char) *s)) + s++; + + /* Parse sign. */ + negative = false; + if (*s == '+') + s++; + else if (*s == '-') + { + negative = true; + s++; + } + + /* Parse digits. We always initially parse the value as + negative, and then make it positive later, because the + negative range of an int is bigger than the positive range + on a 2's complement system. */ + for (value = 0; isdigit (*s); s++) + value = value * 10 - (*s - '0'); + if (!negative) + value = -value; + + return value; +} + +/* Compares A and B by calling the AUX function. */ +static int +compare_thunk (const void *a, const void *b, void *aux) +{ + int (**compare) (const void *, const void *) = aux; + return (*compare) (a, b); +} + +/* Sorts ARRAY, which contains CNT elements of SIZE bytes each, + using COMPARE. When COMPARE is passed a pair of elements A + and B, respectively, it must return a strcmp()-type result, + i.e. less than zero if A < B, zero if A == B, greater than + zero if A > B. Runs in O(n lg n) time and O(1) space in + CNT. */ +void +qsort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *)) +{ + sort (array, cnt, size, compare_thunk, &compare); +} + +/* Swaps elements with 1-based indexes A_IDX and B_IDX in ARRAY + with elements of SIZE bytes each. */ +static void +do_swap (unsigned char *array, size_t a_idx, size_t b_idx, size_t size) +{ + unsigned char *a = array + (a_idx - 1) * size; + unsigned char *b = array + (b_idx - 1) * size; + size_t i; + + for (i = 0; i < size; i++) + { + unsigned char t = a[i]; + a[i] = b[i]; + b[i] = t; + } +} + +/* Compares elements with 1-based indexes A_IDX and B_IDX in + ARRAY with elements of SIZE bytes each, using COMPARE to + compare elements, passing AUX as auxiliary data, and returns a + strcmp()-type result. */ +static int +do_compare (unsigned char *array, size_t a_idx, size_t b_idx, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + return compare (array + (a_idx - 1) * size, array + (b_idx - 1) * size, aux); +} + +/* "Float down" the element with 1-based index I in ARRAY of CNT + elements of SIZE bytes each, using COMPARE to compare + elements, passing AUX as auxiliary data. */ +static void +heapify (unsigned char *array, size_t i, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + for (;;) + { + /* Set `max' to the index of the largest element among I + and its children (if any). */ + size_t left = 2 * i; + size_t right = 2 * i + 1; + size_t max = i; + if (left <= cnt && do_compare (array, left, max, size, compare, aux) > 0) + max = left; + if (right <= cnt + && do_compare (array, right, max, size, compare, aux) > 0) + max = right; + + /* If the maximum value is already in element I, we're + done. */ + if (max == i) + break; + + /* Swap and continue down the heap. */ + do_swap (array, i, max, size); + i = max; + } +} + +/* Sorts ARRAY, which contains CNT elements of SIZE bytes each, + using COMPARE to compare elements, passing AUX as auxiliary + data. When COMPARE is passed a pair of elements A and B, + respectively, it must return a strcmp()-type result, i.e. less + than zero if A < B, zero if A == B, greater than zero if A > + B. Runs in O(n lg n) time and O(1) space in CNT. */ +void +sort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + size_t i; + + ASSERT (array != NULL || cnt == 0); + ASSERT (compare != NULL); + ASSERT (size > 0); + + /* Build a heap. */ + for (i = cnt / 2; i > 0; i--) + heapify (array, i, cnt, size, compare, aux); + + /* Sort the heap. */ + for (i = cnt; i > 1; i--) + { + do_swap (array, 1, i, size); + heapify (array, 1, i - 1, size, compare, aux); + } +} + +/* Searches ARRAY, which contains CNT elements of SIZE bytes + each, for the given KEY. Returns a match is found, otherwise + a null pointer. If there are multiple matches, returns an + arbitrary one of them. + + ARRAY must be sorted in order according to COMPARE. + + Uses COMPARE to compare elements. When COMPARE is passed a + pair of elements A and B, respectively, it must return a + strcmp()-type result, i.e. less than zero if A < B, zero if A + == B, greater than zero if A > B. */ +void * +bsearch (const void *key, const void *array, size_t cnt, + size_t size, int (*compare) (const void *, const void *)) +{ + return binary_search (key, array, cnt, size, compare_thunk, &compare); +} + +/* Searches ARRAY, which contains CNT elements of SIZE bytes + each, for the given KEY. Returns a match is found, otherwise + a null pointer. If there are multiple matches, returns an + arbitrary one of them. + + ARRAY must be sorted in order according to COMPARE. + + Uses COMPARE to compare elements, passing AUX as auxiliary + data. When COMPARE is passed a pair of elements A and B, + respectively, it must return a strcmp()-type result, i.e. less + than zero if A < B, zero if A == B, greater than zero if A > + B. */ +void * +binary_search (const void *key, const void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + const unsigned char *first = array; + const unsigned char *last = array + size * cnt; + + while (first < last) + { + size_t range = (last - first) / size; + const unsigned char *middle = first + (range / 2) * size; + int cmp = compare (key, middle, aux); + + if (cmp < 0) + last = middle; + else if (cmp > 0) + first = middle + size; + else + return (void *) middle; + } + + return NULL; +} + diff --git a/tests/devices/src/lib/stdlib.h b/tests/devices/src/lib/stdlib.h new file mode 100644 index 0000000..d14afa3 --- /dev/null +++ b/tests/devices/src/lib/stdlib.h @@ -0,0 +1,22 @@ +#ifndef __LIB_STDLIB_H +#define __LIB_STDLIB_H + +#include + +/* Standard functions. */ +int atoi (const char *); +void qsort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *)); +void *bsearch (const void *key, const void *array, size_t cnt, + size_t size, int (*compare) (const void *, const void *)); + +/* Nonstandard functions. */ +void sort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux); +void *binary_search (const void *key, const void *array, size_t cnt, + size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux); + +#endif /* lib/stdlib.h */ diff --git a/tests/devices/src/lib/string.c b/tests/devices/src/lib/string.c new file mode 100644 index 0000000..d223c89 --- /dev/null +++ b/tests/devices/src/lib/string.c @@ -0,0 +1,375 @@ +#include +#include + +/* Copies SIZE bytes from SRC to DST, which must not overlap. + Returns DST. */ +void * +memcpy (void *dst_, const void *src_, size_t size) +{ + unsigned char *dst = dst_; + const unsigned char *src = src_; + + ASSERT (dst != NULL || size == 0); + ASSERT (src != NULL || size == 0); + + while (size-- > 0) + *dst++ = *src++; + + return dst_; +} + +/* Copies SIZE bytes from SRC to DST, which are allowed to + overlap. Returns DST. */ +void * +memmove (void *dst_, const void *src_, size_t size) +{ + unsigned char *dst = dst_; + const unsigned char *src = src_; + + ASSERT (dst != NULL || size == 0); + ASSERT (src != NULL || size == 0); + + if (dst < src) + { + while (size-- > 0) + *dst++ = *src++; + } + else + { + dst += size; + src += size; + while (size-- > 0) + *--dst = *--src; + } + + return dst; +} + +/* Find the first differing byte in the two blocks of SIZE bytes + at A and B. Returns a positive value if the byte in A is + greater, a negative value if the byte in B is greater, or zero + if blocks A and B are equal. */ +int +memcmp (const void *a_, const void *b_, size_t size) +{ + const unsigned char *a = a_; + const unsigned char *b = b_; + + ASSERT (a != NULL || size == 0); + ASSERT (b != NULL || size == 0); + + for (; size-- > 0; a++, b++) + if (*a != *b) + return *a > *b ? +1 : -1; + return 0; +} + +/* Finds the first differing characters in strings A and B. + Returns a positive value if the character in A (as an unsigned + char) is greater, a negative value if the character in B (as + an unsigned char) is greater, or zero if strings A and B are + equal. */ +int +strcmp (const char *a_, const char *b_) +{ + const unsigned char *a = (const unsigned char *) a_; + const unsigned char *b = (const unsigned char *) b_; + + ASSERT (a != NULL); + ASSERT (b != NULL); + + while (*a != '\0' && *a == *b) + { + a++; + b++; + } + + return *a < *b ? -1 : *a > *b; +} + +/* Returns a pointer to the first occurrence of CH in the first + SIZE bytes starting at BLOCK. Returns a null pointer if CH + does not occur in BLOCK. */ +void * +memchr (const void *block_, int ch_, size_t size) +{ + const unsigned char *block = block_; + unsigned char ch = ch_; + + ASSERT (block != NULL || size == 0); + + for (; size-- > 0; block++) + if (*block == ch) + return (void *) block; + + return NULL; +} + +/* Finds and returns the first occurrence of C in STRING, or a + null pointer if C does not appear in STRING. If C == '\0' + then returns a pointer to the null terminator at the end of + STRING. */ +char * +strchr (const char *string, int c_) +{ + char c = c_; + + ASSERT (string != NULL); + + for (;;) + if (*string == c) + return (char *) string; + else if (*string == '\0') + return NULL; + else + string++; +} + +/* Returns the length of the initial substring of STRING that + consists of characters that are not in STOP. */ +size_t +strcspn (const char *string, const char *stop) +{ + size_t length; + + for (length = 0; string[length] != '\0'; length++) + if (strchr (stop, string[length]) != NULL) + break; + return length; +} + +/* Returns a pointer to the first character in STRING that is + also in STOP. If no character in STRING is in STOP, returns a + null pointer. */ +char * +strpbrk (const char *string, const char *stop) +{ + for (; *string != '\0'; string++) + if (strchr (stop, *string) != NULL) + return (char *) string; + return NULL; +} + +/* Returns a pointer to the last occurrence of C in STRING. + Returns a null pointer if C does not occur in STRING. */ +char * +strrchr (const char *string, int c_) +{ + char c = c_; + const char *p = NULL; + + for (; *string != '\0'; string++) + if (*string == c) + p = string; + return (char *) p; +} + +/* Returns the length of the initial substring of STRING that + consists of characters in SKIP. */ +size_t +strspn (const char *string, const char *skip) +{ + size_t length; + + for (length = 0; string[length] != '\0'; length++) + if (strchr (skip, string[length]) == NULL) + break; + return length; +} + +/* Returns a pointer to the first occurrence of NEEDLE within + HAYSTACK. Returns a null pointer if NEEDLE does not exist + within HAYSTACK. */ +char * +strstr (const char *haystack, const char *needle) +{ + size_t haystack_len = strlen (haystack); + size_t needle_len = strlen (needle); + + if (haystack_len >= needle_len) + { + size_t i; + + for (i = 0; i <= haystack_len - needle_len; i++) + if (!memcmp (haystack + i, needle, needle_len)) + return (char *) haystack + i; + } + + return NULL; +} + +/* Breaks a string into tokens separated by DELIMITERS. The + first time this function is called, S should be the string to + tokenize, and in subsequent calls it must be a null pointer. + SAVE_PTR is the address of a `char *' variable used to keep + track of the tokenizer's position. The return value each time + is the next token in the string, or a null pointer if no + tokens remain. + + This function treats multiple adjacent delimiters as a single + delimiter. The returned tokens will never be length 0. + DELIMITERS may change from one call to the next within a + single string. + + strtok_r() modifies the string S, changing delimiters to null + bytes. Thus, S must be a modifiable string. String literals, + in particular, are *not* modifiable in C, even though for + backward compatibility they are not `const'. + + Example usage: + + char s[] = " String to tokenize. "; + char *token, *save_ptr; + + for (token = strtok_r (s, " ", &save_ptr); token != NULL; + token = strtok_r (NULL, " ", &save_ptr)) + printf ("'%s'\n", token); + + outputs: + + 'String' + 'to' + 'tokenize.' +*/ +char * +strtok_r (char *s, const char *delimiters, char **save_ptr) +{ + char *token; + + ASSERT (delimiters != NULL); + ASSERT (save_ptr != NULL); + + /* If S is nonnull, start from it. + If S is null, start from saved position. */ + if (s == NULL) + s = *save_ptr; + ASSERT (s != NULL); + + /* Skip any DELIMITERS at our current position. */ + while (strchr (delimiters, *s) != NULL) + { + /* strchr() will always return nonnull if we're searching + for a null byte, because every string contains a null + byte (at the end). */ + if (*s == '\0') + { + *save_ptr = s; + return NULL; + } + + s++; + } + + /* Skip any non-DELIMITERS up to the end of the string. */ + token = s; + while (strchr (delimiters, *s) == NULL) + s++; + if (*s != '\0') + { + *s = '\0'; + *save_ptr = s + 1; + } + else + *save_ptr = s; + return token; +} + +/* Sets the SIZE bytes in DST to VALUE. */ +void * +memset (void *dst_, int value, size_t size) +{ + unsigned char *dst = dst_; + + ASSERT (dst != NULL || size == 0); + + while (size-- > 0) + *dst++ = value; + + return dst_; +} + +/* Returns the length of STRING. */ +size_t +strlen (const char *string) +{ + const char *p; + + ASSERT (string != NULL); + + for (p = string; *p != '\0'; p++) + continue; + return p - string; +} + +/* If STRING is less than MAXLEN characters in length, returns + its actual length. Otherwise, returns MAXLEN. */ +size_t +strnlen (const char *string, size_t maxlen) +{ + size_t length; + + for (length = 0; string[length] != '\0' && length < maxlen; length++) + continue; + return length; +} + +/* Copies string SRC to DST. If SRC is longer than SIZE - 1 + characters, only SIZE - 1 characters are copied. A null + terminator is always written to DST, unless SIZE is 0. + Returns the length of SRC, not including the null terminator. + + strlcpy() is not in the standard C library, but it is an + increasingly popular extension. See + http://www.courtesan.com/todd/papers/strlcpy.html for + information on strlcpy(). */ +size_t +strlcpy (char *dst, const char *src, size_t size) +{ + size_t src_len; + + ASSERT (dst != NULL); + ASSERT (src != NULL); + + src_len = strlen (src); + if (size > 0) + { + size_t dst_len = size - 1; + if (src_len < dst_len) + dst_len = src_len; + memcpy (dst, src, dst_len); + dst[dst_len] = '\0'; + } + return src_len; +} + +/* Concatenates string SRC to DST. The concatenated string is + limited to SIZE - 1 characters. A null terminator is always + written to DST, unless SIZE is 0. Returns the length that the + concatenated string would have assuming that there was + sufficient space, not including a null terminator. + + strlcat() is not in the standard C library, but it is an + increasingly popular extension. See + http://www.courtesan.com/todd/papers/strlcpy.html for + information on strlcpy(). */ +size_t +strlcat (char *dst, const char *src, size_t size) +{ + size_t src_len, dst_len; + + ASSERT (dst != NULL); + ASSERT (src != NULL); + + src_len = strlen (src); + dst_len = strlen (dst); + if (size > 0 && dst_len < size) + { + size_t copy_cnt = size - dst_len - 1; + if (src_len < copy_cnt) + copy_cnt = src_len; + memcpy (dst + dst_len, src, copy_cnt); + dst[dst_len + copy_cnt] = '\0'; + } + return src_len + dst_len; +} + diff --git a/tests/devices/src/lib/string.h b/tests/devices/src/lib/string.h new file mode 100644 index 0000000..1fff82a --- /dev/null +++ b/tests/devices/src/lib/string.h @@ -0,0 +1,35 @@ +#ifndef __LIB_STRING_H +#define __LIB_STRING_H + +#include + +/* Standard. */ +void *memcpy (void *, const void *, size_t); +void *memmove (void *, const void *, size_t); +char *strncat (char *, const char *, size_t); +int memcmp (const void *, const void *, size_t); +int strcmp (const char *, const char *); +void *memchr (const void *, int, size_t); +char *strchr (const char *, int); +size_t strcspn (const char *, const char *); +char *strpbrk (const char *, const char *); +char *strrchr (const char *, int); +size_t strspn (const char *, const char *); +char *strstr (const char *, const char *); +void *memset (void *, int, size_t); +size_t strlen (const char *); + +/* Extensions. */ +size_t strlcpy (char *, const char *, size_t); +size_t strlcat (char *, const char *, size_t); +char *strtok_r (char *, const char *, char **); +size_t strnlen (const char *, size_t); + +/* Try to be helpful. */ +#define strcpy dont_use_strcpy_use_strlcpy +#define strncpy dont_use_strncpy_use_strlcpy +#define strcat dont_use_strcat_use_strlcat +#define strncat dont_use_strncat_use_strlcat +#define strtok dont_use_strtok_use_strtok_r + +#endif /* lib/string.h */ diff --git a/tests/devices/src/lib/syscall-nr.h b/tests/devices/src/lib/syscall-nr.h new file mode 100644 index 0000000..b03bfa9 --- /dev/null +++ b/tests/devices/src/lib/syscall-nr.h @@ -0,0 +1,34 @@ +#ifndef __LIB_SYSCALL_NR_H +#define __LIB_SYSCALL_NR_H + +/* System call numbers. */ +enum + { + /* Tasks 2 and later. */ + SYS_HALT, /* Halt the operating system. */ + SYS_EXIT, /* Terminate this process. */ + SYS_EXEC, /* Start another process. */ + SYS_WAIT, /* Wait for a child process to die. */ + SYS_CREATE, /* Create a file. */ + SYS_REMOVE, /* Delete a file. */ + SYS_OPEN, /* Open a file. */ + SYS_FILESIZE, /* Obtain a file's size. */ + SYS_READ, /* Read from a file. */ + SYS_WRITE, /* Write to a file. */ + SYS_SEEK, /* Change position in a file. */ + SYS_TELL, /* Report current position in a file. */ + SYS_CLOSE, /* Close a file. */ + + /* Task 3 and optionally task 4. */ + SYS_MMAP, /* Map a file into memory. */ + SYS_MUNMAP, /* Remove a memory mapping. */ + + /* Task 4 only. */ + SYS_CHDIR, /* Change the current directory. */ + SYS_MKDIR, /* Create a directory. */ + SYS_READDIR, /* Reads a directory entry. */ + SYS_ISDIR, /* Tests if a fd represents a directory. */ + SYS_INUMBER /* Returns the inode number for a fd. */ + }; + +#endif /* lib/syscall-nr.h */ diff --git a/tests/devices/src/lib/user/console.c b/tests/devices/src/lib/user/console.c new file mode 100644 index 0000000..dd64f32 --- /dev/null +++ b/tests/devices/src/lib/user/console.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include + +/* The standard vprintf() function, + which is like printf() but uses a va_list. */ +int +vprintf (const char *format, va_list args) +{ + return vhprintf (STDOUT_FILENO, format, args); +} + +/* Like printf(), but writes output to the given HANDLE. */ +int +hprintf (int handle, const char *format, ...) +{ + va_list args; + int retval; + + va_start (args, format); + retval = vhprintf (handle, format, args); + va_end (args); + + return retval; +} + +/* Writes string S to the console, followed by a new-line + character. */ +int +puts (const char *s) +{ + write (STDOUT_FILENO, s, strlen (s)); + putchar ('\n'); + + return 0; +} + +/* Writes C to the console. */ +int +putchar (int c) +{ + char c2 = c; + write (STDOUT_FILENO, &c2, 1); + return c; +} + +/* Auxiliary data for vhprintf_helper(). */ +struct vhprintf_aux + { + char buf[64]; /* Character buffer. */ + char *p; /* Current position in buffer. */ + int char_cnt; /* Total characters written so far. */ + int handle; /* Output file handle. */ + }; + +static void add_char (char, void *); +static void flush (struct vhprintf_aux *); + +/* Formats the printf() format specification FORMAT with + arguments given in ARGS and writes the output to the given + HANDLE. */ +int +vhprintf (int handle, const char *format, va_list args) +{ + struct vhprintf_aux aux; + aux.p = aux.buf; + aux.char_cnt = 0; + aux.handle = handle; + __vprintf (format, args, add_char, &aux); + flush (&aux); + return aux.char_cnt; +} + +/* Adds C to the buffer in AUX, flushing it if the buffer fills + up. */ +static void +add_char (char c, void *aux_) +{ + struct vhprintf_aux *aux = aux_; + *aux->p++ = c; + if (aux->p >= aux->buf + sizeof aux->buf) + flush (aux); + aux->char_cnt++; +} + +/* Flushes the buffer in AUX. */ +static void +flush (struct vhprintf_aux *aux) +{ + if (aux->p > aux->buf) + write (aux->handle, aux->buf, aux->p - aux->buf); + aux->p = aux->buf; +} diff --git a/tests/devices/src/lib/user/debug.c b/tests/devices/src/lib/user/debug.c new file mode 100644 index 0000000..f49b874 --- /dev/null +++ b/tests/devices/src/lib/user/debug.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include + +/* Aborts the user program, printing the source file name, line + number, and function name, plus a user-specific message. */ +void +debug_panic (const char *file, int line, const char *function, + const char *message, ...) +{ + va_list args; + + printf ("User process ABORT at %s:%d in %s(): ", file, line, function); + + va_start (args, message); + vprintf (message, args); + printf ("\n"); + va_end (args); + + debug_backtrace (); + + exit (1); +} diff --git a/tests/devices/src/lib/user/entry.c b/tests/devices/src/lib/user/entry.c new file mode 100644 index 0000000..a707c70 --- /dev/null +++ b/tests/devices/src/lib/user/entry.c @@ -0,0 +1,10 @@ +#include + +int main (int, char *[]); +void _start (int argc, char *argv[]); + +void +_start (int argc, char *argv[]) +{ + exit (main (argc, argv)); +} diff --git a/tests/devices/src/lib/user/stdio.h b/tests/devices/src/lib/user/stdio.h new file mode 100644 index 0000000..b9f3cc6 --- /dev/null +++ b/tests/devices/src/lib/user/stdio.h @@ -0,0 +1,7 @@ +#ifndef __LIB_USER_STDIO_H +#define __LIB_USER_STDIO_H + +int hprintf (int, const char *, ...) PRINTF_FORMAT (2, 3); +int vhprintf (int, const char *, va_list) PRINTF_FORMAT (2, 0); + +#endif /* lib/user/stdio.h */ diff --git a/tests/devices/src/lib/user/syscall.c b/tests/devices/src/lib/user/syscall.c new file mode 100644 index 0000000..b928b41 --- /dev/null +++ b/tests/devices/src/lib/user/syscall.c @@ -0,0 +1,184 @@ +#include +#include "../syscall-nr.h" + +/* Invokes syscall NUMBER, passing no arguments, and returns the + return value as an `int'. */ +#define syscall0(NUMBER) \ + ({ \ + int retval; \ + asm volatile \ + ("pushl %[number]; int $0x30; addl $4, %%esp" \ + : "=a" (retval) \ + : [number] "i" (NUMBER) \ + : "memory"); \ + retval; \ + }) + +/* Invokes syscall NUMBER, passing argument ARG0, and returns the + return value as an `int'. */ +#define syscall1(NUMBER, ARG0) \ + ({ \ + int retval; \ + asm volatile \ + ("pushl %[arg0]; pushl %[number]; int $0x30; addl $8, %%esp" \ + : "=a" (retval) \ + : [number] "i" (NUMBER), \ + [arg0] "g" (ARG0) \ + : "memory"); \ + retval; \ + }) + +/* Invokes syscall NUMBER, passing arguments ARG0 and ARG1, and + returns the return value as an `int'. */ +#define syscall2(NUMBER, ARG0, ARG1) \ + ({ \ + int retval; \ + asm volatile \ + ("pushl %[arg1]; pushl %[arg0]; " \ + "pushl %[number]; int $0x30; addl $12, %%esp" \ + : "=a" (retval) \ + : [number] "i" (NUMBER), \ + [arg0] "g" (ARG0), \ + [arg1] "g" (ARG1) \ + : "memory"); \ + retval; \ + }) + +/* Invokes syscall NUMBER, passing arguments ARG0, ARG1, and + ARG2, and returns the return value as an `int'. */ +#define syscall3(NUMBER, ARG0, ARG1, ARG2) \ + ({ \ + int retval; \ + asm volatile \ + ("pushl %[arg2]; pushl %[arg1]; pushl %[arg0]; " \ + "pushl %[number]; int $0x30; addl $16, %%esp" \ + : "=a" (retval) \ + : [number] "i" (NUMBER), \ + [arg0] "g" (ARG0), \ + [arg1] "g" (ARG1), \ + [arg2] "g" (ARG2) \ + : "memory"); \ + retval; \ + }) + +void +halt (void) +{ + syscall0 (SYS_HALT); + NOT_REACHED (); +} + +void +exit (int status) +{ + syscall1 (SYS_EXIT, status); + NOT_REACHED (); +} + +pid_t +exec (const char *file) +{ + return (pid_t) syscall1 (SYS_EXEC, file); +} + +int +wait (pid_t pid) +{ + return syscall1 (SYS_WAIT, pid); +} + +bool +create (const char *file, unsigned initial_size) +{ + return syscall2 (SYS_CREATE, file, initial_size); +} + +bool +remove (const char *file) +{ + return syscall1 (SYS_REMOVE, file); +} + +int +open (const char *file) +{ + return syscall1 (SYS_OPEN, file); +} + +int +filesize (int fd) +{ + return syscall1 (SYS_FILESIZE, fd); +} + +int +read (int fd, void *buffer, unsigned size) +{ + return syscall3 (SYS_READ, fd, buffer, size); +} + +int +write (int fd, const void *buffer, unsigned size) +{ + return syscall3 (SYS_WRITE, fd, buffer, size); +} + +void +seek (int fd, unsigned position) +{ + syscall2 (SYS_SEEK, fd, position); +} + +unsigned +tell (int fd) +{ + return syscall1 (SYS_TELL, fd); +} + +void +close (int fd) +{ + syscall1 (SYS_CLOSE, fd); +} + +mapid_t +mmap (int fd, void *addr) +{ + return syscall2 (SYS_MMAP, fd, addr); +} + +void +munmap (mapid_t mapid) +{ + syscall1 (SYS_MUNMAP, mapid); +} + +bool +chdir (const char *dir) +{ + return syscall1 (SYS_CHDIR, dir); +} + +bool +mkdir (const char *dir) +{ + return syscall1 (SYS_MKDIR, dir); +} + +bool +readdir (int fd, char name[FNAME_MAX_LEN + 1]) +{ + return syscall2 (SYS_READDIR, fd, name); +} + +bool +isdir (int fd) +{ + return syscall1 (SYS_ISDIR, fd); +} + +int +inumber (int fd) +{ + return syscall1 (SYS_INUMBER, fd); +} diff --git a/tests/devices/src/lib/user/syscall.h b/tests/devices/src/lib/user/syscall.h new file mode 100644 index 0000000..305c7af --- /dev/null +++ b/tests/devices/src/lib/user/syscall.h @@ -0,0 +1,46 @@ +#ifndef __LIB_USER_SYSCALL_H +#define __LIB_USER_SYSCALL_H + +#include +#include +#include "../../filesys/file.h" + +/* Process identifier. */ +typedef int pid_t; +#define PID_ERROR ((pid_t) -1) + +/* Map region identifier. */ +typedef int mapid_t; +#define MAP_FAILED ((mapid_t) -1) + +/* Typical return values from main() and arguments to exit(). */ +#define EXIT_SUCCESS 0 /* Successful execution. */ +#define EXIT_FAILURE 1 /* Unsuccessful execution. */ + +/* Tasks 2 and later. */ +void halt (void) NO_RETURN; +void exit (int status) NO_RETURN; +pid_t exec (const char *file); +int wait (pid_t); +bool create (const char *file, unsigned initial_size); +bool remove (const char *file); +int open (const char *file); +int filesize (int fd); +int read (int fd, void *buffer, unsigned length); +int write (int fd, const void *buffer, unsigned length); +void seek (int fd, unsigned position); +unsigned tell (int fd); +void close (int fd); + +/* Task 3 and optionally task 4. */ +mapid_t mmap (int fd, void *addr); +void munmap (mapid_t); + +/* Task 4 only. */ +bool chdir (const char *dir); +bool mkdir (const char *dir); +bool readdir (int fd, char name[FNAME_MAX_LEN + 1]); +bool isdir (int fd); +int inumber (int fd); + +#endif /* lib/user/syscall.h */ diff --git a/tests/devices/src/lib/user/user.lds b/tests/devices/src/lib/user/user.lds new file mode 100644 index 0000000..1785032 --- /dev/null +++ b/tests/devices/src/lib/user/user.lds @@ -0,0 +1,57 @@ +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) + +SECTIONS +{ + /* Read-only sections, merged into text segment, at fixed start offset. */ + __executable_start = 0x08048000; + . = 0x08048000; + .text : { *(.text) } + .rodata : { *(.rodata) } + + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN (0x1000) - ((0x1000 - .) & (0x1000 - 1)); + . = DATA_SEGMENT_ALIGN (0x1000, 0x1000); + + .data : { *(.data) } + .bss : { *(.bss) } + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /DISCARD/ : { *(.note.GNU-stack) } + /DISCARD/ : { *(.eh_frame) } +} diff --git a/tests/devices/src/lib/ustar.c b/tests/devices/src/lib/ustar.c new file mode 100644 index 0000000..49af69a --- /dev/null +++ b/tests/devices/src/lib/ustar.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include + +/* Header for ustar-format tar archive. See the documentation of + the "pax" utility in [SUSv3] for the the "ustar" format + specification. */ +struct ustar_header + { + char name[100]; /* File name. Null-terminated if room. */ + char mode[8]; /* Permissions as octal string. */ + char uid[8]; /* User ID as octal string. */ + char gid[8]; /* Group ID as octal string. */ + char size[12]; /* File size in bytes as octal string. */ + char mtime[12]; /* Modification time in seconds + from Jan 1, 1970, as octal string. */ + char chksum[8]; /* Sum of octets in header as octal string. */ + char typeflag; /* An enum ustar_type value. */ + char linkname[100]; /* Name of link target. + Null-terminated if room. */ + char magic[6]; /* "ustar\0" */ + char version[2]; /* "00" */ + char uname[32]; /* User name, always null-terminated. */ + char gname[32]; /* Group name, always null-terminated. */ + char devmajor[8]; /* Device major number as octal string. */ + char devminor[8]; /* Device minor number as octal string. */ + char prefix[155]; /* Prefix to file name. + Null-terminated if room. */ + char padding[12]; /* Pad to 512 bytes. */ + } +PACKED; + +/* Returns the checksum for the given ustar format HEADER. */ +static unsigned int +calculate_chksum (const struct ustar_header *h) +{ + const uint8_t *header = (const uint8_t *) h; + unsigned int chksum; + size_t i; + + chksum = 0; + for (i = 0; i < USTAR_HEADER_SIZE; i++) + { + /* The ustar checksum is calculated as if the chksum field + were all spaces. */ + const size_t chksum_start = offsetof (struct ustar_header, chksum); + const size_t chksum_end = chksum_start + sizeof h->chksum; + bool in_chksum_field = i >= chksum_start && i < chksum_end; + chksum += in_chksum_field ? ' ' : header[i]; + } + return chksum; +} + +/* Drop possibly dangerous prefixes from FILE_NAME and return the + stripped name. An archive with file names that start with "/" + or "../" could cause a naive tar extractor to write to + arbitrary parts of the file system, not just the destination + directory. We don't want to create such archives or be such a + naive extractor. + + The return value can be a suffix of FILE_NAME or a string + literal. */ +static const char * +strip_antisocial_prefixes (const char *file_name) +{ + while (*file_name == '/' + || !memcmp (file_name, "./", 2) + || !memcmp (file_name, "../", 3)) + file_name = strchr (file_name, '/') + 1; + return *file_name == '\0' || !strcmp (file_name, "..") ? "." : file_name; +} + +/* Composes HEADER as a USTAR_HEADER_SIZE (512)-byte archive + header in ustar format for a SIZE-byte file named FILE_NAME of + the given TYPE. The caller is responsible for writing the + header to a file or device. + + If successful, returns true. On failure (due to an + excessively long file name), returns false. */ +bool +ustar_make_header (const char *file_name, enum ustar_type type, + int size, char header[USTAR_HEADER_SIZE]) +{ + struct ustar_header *h = (struct ustar_header *) header; + + ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); + ASSERT (type == USTAR_REGULAR || type == USTAR_DIRECTORY); + + /* Check file name. */ + file_name = strip_antisocial_prefixes (file_name); + if (strlen (file_name) > 99) + { + printf ("%s: file name too long\n", file_name); + return false; + } + + /* Fill in header except for final checksum. */ + memset (h, 0, sizeof *h); + strlcpy (h->name, file_name, sizeof h->name); + snprintf (h->mode, sizeof h->mode, "%07o", + type == USTAR_REGULAR ? 0644 : 0755); + strlcpy (h->uid, "0000000", sizeof h->uid); + strlcpy (h->gid, "0000000", sizeof h->gid); + snprintf (h->size, sizeof h->size, "%011o", size); + snprintf (h->mtime, sizeof h->size, "%011o", 1136102400); + h->typeflag = type; + strlcpy (h->magic, "ustar", sizeof h->magic); + h->version[0] = h->version[1] = '0'; + strlcpy (h->gname, "root", sizeof h->gname); + strlcpy (h->uname, "root", sizeof h->uname); + + /* Compute and fill in final checksum. */ + snprintf (h->chksum, sizeof h->chksum, "%07o", calculate_chksum (h)); + + return true; +} + +/* Parses a SIZE-byte octal field in S in the format used by + ustar format. If successful, stores the field's value in + *VALUE and returns true; on failure, returns false. + + ustar octal fields consist of a sequence of octal digits + terminated by a space or a null byte. The ustar specification + seems ambiguous as to whether these fields must be padded on + the left with '0's, so we accept any field that fits in the + available space, regardless of whether it fills the space. */ +static bool +parse_octal_field (const char *s, size_t size, unsigned long int *value) +{ + size_t ofs; + + *value = 0; + for (ofs = 0; ofs < size; ofs++) + { + char c = s[ofs]; + if (c >= '0' && c <= '7') + { + if (*value > ULONG_MAX / 8) + { + /* Overflow. */ + return false; + } + *value = c - '0' + *value * 8; + } + else if (c == ' ' || c == '\0') + { + /* End of field, but disallow completely empty + fields. */ + return ofs > 0; + } + else + { + /* Bad character. */ + return false; + } + } + + /* Field did not end in space or null byte. */ + return false; +} + +/* Returns true if the CNT bytes starting at BLOCK are all zero, + false otherwise. */ +static bool +is_all_zeros (const char *block, size_t cnt) +{ + while (cnt-- > 0) + if (*block++ != 0) + return false; + return true; +} + +/* Parses HEADER as a ustar-format archive header for a regular + file or directory. If successful, stores the archived file's + name in *FILE_NAME (as a pointer into HEADER or a string + literal), its type in *TYPE, and its size in bytes in *SIZE, + and returns a null pointer. On failure, returns a + human-readable error message. */ +const char * +ustar_parse_header (const char header[USTAR_HEADER_SIZE], + const char **file_name, enum ustar_type *type, int *size) +{ + const struct ustar_header *h = (const struct ustar_header *) header; + unsigned long int chksum, size_ul; + + ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); + + /* Detect end of archive. */ + if (is_all_zeros (header, USTAR_HEADER_SIZE)) + { + *file_name = NULL; + *type = USTAR_EOF; + *size = 0; + return NULL; + } + + /* Validate ustar header. */ + if (memcmp (h->magic, "ustar", 6)) + return "not a ustar archive"; + else if (h->version[0] != '0' || h->version[1] != '0') + return "invalid ustar version"; + else if (!parse_octal_field (h->chksum, sizeof h->chksum, &chksum)) + return "corrupt chksum field"; + else if (chksum != calculate_chksum (h)) + return "checksum mismatch"; + else if (h->name[sizeof h->name - 1] != '\0' || h->prefix[0] != '\0') + return "file name too long"; + else if (h->typeflag != USTAR_REGULAR && h->typeflag != USTAR_DIRECTORY) + return "unimplemented file type"; + if (h->typeflag == USTAR_REGULAR) + { + if (!parse_octal_field (h->size, sizeof h->size, &size_ul)) + return "corrupt file size field"; + else if (size_ul > INT_MAX) + return "file too large"; + } + else + size_ul = 0; + + /* Success. */ + *file_name = strip_antisocial_prefixes (h->name); + *type = h->typeflag; + *size = size_ul; + return NULL; +} + diff --git a/tests/devices/src/lib/ustar.h b/tests/devices/src/lib/ustar.h new file mode 100644 index 0000000..6995da9 --- /dev/null +++ b/tests/devices/src/lib/ustar.h @@ -0,0 +1,29 @@ +#ifndef __LIB_USTAR_H +#define __LIB_USTAR_H + +/* Support for the standard Posix "ustar" format. See the + documentation of the "pax" utility in [SUSv3] for the the + "ustar" format specification. */ + +#include + +/* Type of a file entry in an archive. + The values here are the bytes that appear in the file format. + Only types of interest to PintOS are listed here. */ +enum ustar_type + { + USTAR_REGULAR = '0', /* Ordinary file. */ + USTAR_DIRECTORY = '5', /* Directory. */ + USTAR_EOF = -1 /* End of archive (not an official value). */ + }; + +/* Size of a ustar archive header, in bytes. */ +#define USTAR_HEADER_SIZE 512 + +bool ustar_make_header (const char *file_name, enum ustar_type, + int size, char header[USTAR_HEADER_SIZE]); +const char *ustar_parse_header (const char header[USTAR_HEADER_SIZE], + const char **file_name, + enum ustar_type *, int *size); + +#endif /* lib/ustar.h */ diff --git a/tests/devices/src/loader.ld b/tests/devices/src/loader.ld new file mode 100644 index 0000000..d733c6c --- /dev/null +++ b/tests/devices/src/loader.ld @@ -0,0 +1,10 @@ +OUTPUT_FORMAT(binary) + +SECTIONS +{ + . = 0x7C00; + .text : { *(.text*) } + /DISCARD/ : { *(*) } +} + +ASSERT(SIZEOF(.text) == 512, "Size of bootloader is not 512 bytes"); diff --git a/tests/devices/src/misc/gdb-macros b/tests/devices/src/misc/gdb-macros new file mode 100644 index 0000000..3d94811 --- /dev/null +++ b/tests/devices/src/misc/gdb-macros @@ -0,0 +1,175 @@ +# +# A set of useful macros that can help debug PintOS. +# +# Include with "source" cmd in gdb. +# Use "help user-defined" for help. +# +# Author: Godmar Back , Feb 2006 +# +# $Id: gdb-macros,v 1.1 2006-04-07 18:29:34 blp Exp $ +# + +# for internal use +define offsetof + set $rc = (char*)&((struct $arg0 *)0)->$arg1 - (char*)0 +end + +define list_entry + offsetof $arg1 $arg2 + set $rc = ((struct $arg1 *) ((uint8_t *) ($arg0) - $rc)) +end + +define hash_entry + list_entry $arg0 $arg1 $arg2 +end + +# dump a PintOS list +define dumplist + set $list = $arg0 + set $e = $list->head.next + set $i = 0 + while $e != &(($arg0).tail) + list_entry $e $arg1 $arg2 + set $l = $rc + printf "pintos-debug: dumplist #%d: %p ", $i++, $l + output *$l + set $e = $e->next + printf "\n" + end +end + +document dumplist + Dump the content of a PintOS list, + invoke as dumplist name_of_list name_of_struct name_of_elem_in_list_struct +end + +# dump a PintOS hash_entry +define dumphash + set $hash = $arg0 + set $bkts = $hash.bucket_cnt + set $i = 0 + while $i < $bkts + printf "pintos-debug: [hash bucket #%d]:\n", $i + set $list = &($hash.buckets[$i]) + set $e = $list->head.next + set $j = 0 + while $e != &($list.tail) + list_entry $e hash_elem list_elem + set $l = $rc + hash_entry $l $arg1 $arg2 + set $h = $rc + printf "pintos-debug: bucket element #%d => %p \n", $j++, $h + output *$h + set $e = $e.next + printf "\n" + end + set $i = $i+1 + end +end + +document dumphash + Dump the contents of a PintOS hash, + invoke as dumphash name_of_hash name_of_struct name_of_elem_in_hash_struct +end + +# print a thread's backtrace, given a pointer to the struct thread * +define btthread + if $arg0 == ($esp - ((unsigned)$esp % 4096)) + bt + else + set $saveEIP = $eip + set $saveESP = $esp + set $saveEBP = $ebp + + set $esp = ((struct thread *)$arg0)->stack + set $ebp = ((void**)$esp)[2] + set $eip = ((void**)$esp)[4] + + bt + + set $eip = $saveEIP + set $esp = $saveESP + set $ebp = $saveEBP + end +end +document btthread + Show the backtrace of a thread, + invoke as btthread pointer_to_struct_thread +end + +# print backtraces associated with all threads in a list +define btthreadlist + set $list = $arg0 + set $e = $list->head.next + while $e != &(($arg0).tail) + list_entry $e thread $arg1 + printf "pintos-debug: dumping backtrace of thread '%s' @%p\n", \ + ((struct thread*)$rc)->name, $rc + btthread $rc + set $e = $e->next + printf "\n" + end +end +document btthreadlist + Given a list of threads, print each thread's backtrace + invoke as btthreadlist name_of_list name_of_elem_in_list_struct +end + +# print backtraces of all threads (based on 'all_list' all threads list) +define btthreadall + btthreadlist &all_list allelem +end +document btthreadall + Print backtraces of all threads +end + +# print a correct backtrace by adjusting $eip +# this works best right at intr0e_stub +define btpagefault + set $saveeip = $eip + set $eip = ((void**)$esp)[1] + backtrace + set $eip = $saveeip +end +document btpagefault + Print a backtrace of the current thread after a pagefault +end + +# invoked whenever the program stops +define hook-stop + # stopped at stub #0E = #14 (page fault exception handler stub) + if ($eip == intr0e_stub) + set $savedeip = ((void**)$esp)[1] + # if this was in user mode, the OS should handle it + # either handle the page fault or terminate the process + if ($savedeip < 0xC0000000) + printf "pintos-debug: a page fault exception occurred in user mode\n" + printf "pintos-debug: hit 'c' to continue, or 's' to step to intr_handler\n" + else + # if this was in kernel mode, a stack trace might be useful + printf "pintos-debug: a page fault occurred in kernel mode\n" + btpagefault + end + end +end + +# load symbols for a PintOS user program +define loadusersymbols + shell objdump -h $arg0 | awk '/.text/ { print "add-symbol-file $arg0 0x"$4 }' > .loadsymbols + source .loadsymbols + shell rm -f .loadsymbols +end +document loadusersymbols + Load the symbols contained in a user program's executable. + Example: + loadusersymbols tests/userprog/exec-multiple +end + +define debugpintos + target remote localhost:1234 +end +document debugpintos + Attach debugger to pintos process +end + +set architecture i386 diff --git a/tests/devices/src/tests/Algorithm/Diff.pm b/tests/devices/src/tests/Algorithm/Diff.pm new file mode 100644 index 0000000..904c530 --- /dev/null +++ b/tests/devices/src/tests/Algorithm/Diff.pm @@ -0,0 +1,1713 @@ +package Algorithm::Diff; +# Skip to first "=head" line for documentation. +use strict; + +use integer; # see below in _replaceNextLargerWith() for mod to make + # if you don't use this +use vars qw( $VERSION @EXPORT_OK ); +$VERSION = 1.19_01; +# ^ ^^ ^^-- Incremented at will +# | \+----- Incremented for non-trivial changes to features +# \-------- Incremented for fundamental changes +require Exporter; +*import = \&Exporter::import; +@EXPORT_OK = qw( + prepare LCS LCDidx LCS_length + diff sdiff compact_diff + traverse_sequences traverse_balanced +); + +# McIlroy-Hunt diff algorithm +# Adapted from the Smalltalk code of Mario I. Wolczko, +# by Ned Konz, perl@bike-nomad.com +# Updates by Tye McQueen, http://perlmonks.org/?node=tye + +# Create a hash that maps each element of $aCollection to the set of +# positions it occupies in $aCollection, restricted to the elements +# within the range of indexes specified by $start and $end. +# The fourth parameter is a subroutine reference that will be called to +# generate a string to use as a key. +# Additional parameters, if any, will be passed to this subroutine. +# +# my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen ); + +sub _withPositionsOfInInterval +{ + my $aCollection = shift; # array ref + my $start = shift; + my $end = shift; + my $keyGen = shift; + my %d; + my $index; + for ( $index = $start ; $index <= $end ; $index++ ) + { + my $element = $aCollection->[$index]; + my $key = &$keyGen( $element, @_ ); + if ( exists( $d{$key} ) ) + { + unshift ( @{ $d{$key} }, $index ); + } + else + { + $d{$key} = [$index]; + } + } + return wantarray ? %d : \%d; +} + +# Find the place at which aValue would normally be inserted into the +# array. If that place is already occupied by aValue, do nothing, and +# return undef. If the place does not exist (i.e., it is off the end of +# the array), add it to the end, otherwise replace the element at that +# point with aValue. It is assumed that the array's values are numeric. +# This is where the bulk (75%) of the time is spent in this module, so +# try to make it fast! + +sub _replaceNextLargerWith +{ + my ( $array, $aValue, $high ) = @_; + $high ||= $#$array; + + # off the end? + if ( $high == -1 || $aValue > $array->[-1] ) + { + push ( @$array, $aValue ); + return $high + 1; + } + + # binary search for insertion point... + my $low = 0; + my $index; + my $found; + while ( $low <= $high ) + { + $index = ( $high + $low ) / 2; + + # $index = int(( $high + $low ) / 2); # without 'use integer' + $found = $array->[$index]; + + if ( $aValue == $found ) + { + return undef; + } + elsif ( $aValue > $found ) + { + $low = $index + 1; + } + else + { + $high = $index - 1; + } + } + + # now insertion point is in $low. + $array->[$low] = $aValue; # overwrite next larger + return $low; +} + +# This method computes the longest common subsequence in $a and $b. + +# Result is array or ref, whose contents is such that +# $a->[ $i ] == $b->[ $result[ $i ] ] +# foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined. + +# An additional argument may be passed; this is a hash or key generating +# function that should return a string that uniquely identifies the given +# element. It should be the case that if the key is the same, the elements +# will compare the same. If this parameter is undef or missing, the key +# will be the element as a string. + +# By default, comparisons will use "eq" and elements will be turned into keys +# using the default stringizing operator '""'. + +# Additional parameters, if any, will be passed to the key generation +# routine. + +sub _longestCommonSubsequence +{ + my $a = shift; # array ref or hash ref + my $b = shift; # array ref or hash ref + my $counting = shift; # scalar + my $keyGen = shift; # code ref + my $compare; # code ref + + if ( ref($a) eq 'HASH' ) + { # prepared hash must be in $b + my $tmp = $b; + $b = $a; + $a = $tmp; + } + + # Check for bogus (non-ref) argument values + if ( !ref($a) || !ref($b) ) + { + my @callerInfo = caller(1); + die 'error: must pass array or hash references to ' . $callerInfo[3]; + } + + # set up code refs + # Note that these are optimized. + if ( !defined($keyGen) ) # optimize for strings + { + $keyGen = sub { $_[0] }; + $compare = sub { my ( $a, $b ) = @_; $a eq $b }; + } + else + { + $compare = sub { + my $a = shift; + my $b = shift; + &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ ); + }; + } + + my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] ); + my ( $prunedCount, $bMatches ) = ( 0, {} ); + + if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us? + { + $bMatches = $b; + } + else + { + my ( $bStart, $bFinish ) = ( 0, $#$b ); + + # First we prune off any common elements at the beginning + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aStart], $b->[$bStart], @_ ) ) + { + $matchVector->[ $aStart++ ] = $bStart++; + $prunedCount++; + } + + # now the end + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) ) + { + $matchVector->[ $aFinish-- ] = $bFinish--; + $prunedCount++; + } + + # Now compute the equivalence classes of positions of elements + $bMatches = + _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ ); + } + my $thresh = []; + my $links = []; + + my ( $i, $ai, $j, $k ); + for ( $i = $aStart ; $i <= $aFinish ; $i++ ) + { + $ai = &$keyGen( $a->[$i], @_ ); + if ( exists( $bMatches->{$ai} ) ) + { + $k = 0; + for $j ( @{ $bMatches->{$ai} } ) + { + + # optimization: most of the time this will be true + if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j ) + { + $thresh->[$k] = $j; + } + else + { + $k = _replaceNextLargerWith( $thresh, $j, $k ); + } + + # oddly, it's faster to always test this (CPU cache?). + if ( defined($k) ) + { + $links->[$k] = + [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ]; + } + } + } + } + + if (@$thresh) + { + return $prunedCount + @$thresh if $counting; + for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] ) + { + $matchVector->[ $link->[1] ] = $link->[2]; + } + } + elsif ($counting) + { + return $prunedCount; + } + + return wantarray ? @$matchVector : $matchVector; +} + +sub traverse_sequences +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $finishedACallback = $callbacks->{'A_FINISHED'}; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $finishedBCallback = $callbacks->{'B_FINISHED'}; + my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); + + # Process all the lines in @$matchVector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai; + + for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ ) + { + my $bLine = $matchVector->[$ai]; + if ( defined($bLine) ) # matched + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine; + &$matchCallback( $ai, $bi++, @_ ); + } + else + { + &$discardACallback( $ai, $bi, @_ ); + } + } + + # The last entry (if any) processed was a match. + # $ai and $bi point just past the last matching lines in their sequences. + + while ( $ai <= $lastA or $bi <= $lastB ) + { + + # last A? + if ( $ai == $lastA + 1 and $bi <= $lastB ) + { + if ( defined($finishedACallback) ) + { + &$finishedACallback( $lastA, @_ ); + $finishedACallback = undef; + } + else + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB; + } + } + + # last B? + if ( $bi == $lastB + 1 and $ai <= $lastA ) + { + if ( defined($finishedBCallback) ) + { + &$finishedBCallback( $lastB, @_ ); + $finishedBCallback = undef; + } + else + { + &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA; + } + } + + &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA; + &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB; + } + + return 1; +} + +sub traverse_balanced +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $changeCallback = $callbacks->{'CHANGE'}; + my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); + + # Process all the lines in match vector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai = 0; + my $ma = -1; + my $mb; + + while (1) + { + + # Find next match indices $ma and $mb + do { + $ma++; + } while( + $ma <= $#$matchVector + && !defined $matchVector->[$ma] + ); + + last if $ma > $#$matchVector; # end of matchVector? + $mb = $matchVector->[$ma]; + + # Proceed with discard a/b or change events until + # next match + while ( $ai < $ma || $bi < $mb ) + { + + if ( $ai < $ma && $bi < $mb ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai < $ma ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi < $mb + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + # Match + &$matchCallback( $ai++, $bi++, @_ ); + } + + while ( $ai <= $lastA || $bi <= $lastB ) + { + if ( $ai <= $lastA && $bi <= $lastB ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai <= $lastA ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi <= $lastB + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + return 1; +} + +sub prepare +{ + my $a = shift; # array ref + my $keyGen = shift; # code ref + + # set up code ref + $keyGen = sub { $_[0] } unless defined($keyGen); + + return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ ); +} + +sub LCS +{ + my $a = shift; # array ref + my $b = shift; # array ref or hash ref + my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ ); + my @retval; + my $i; + for ( $i = 0 ; $i <= $#$matchVector ; $i++ ) + { + if ( defined( $matchVector->[$i] ) ) + { + push ( @retval, $a->[$i] ); + } + } + return wantarray ? @retval : \@retval; +} + +sub LCS_length +{ + my $a = shift; # array ref + my $b = shift; # array ref or hash ref + return _longestCommonSubsequence( $a, $b, 1, @_ ); +} + +sub LCSidx +{ + my $a= shift @_; + my $b= shift @_; + my $match= _longestCommonSubsequence( $a, $b, 0, @_ ); + my @am= grep defined $match->[$_], 0..$#$match; + my @bm= @{$match}[@am]; + return \@am, \@bm; +} + +sub compact_diff +{ + my $a= shift @_; + my $b= shift @_; + my( $am, $bm )= LCSidx( $a, $b, @_ ); + my @cdiff; + my( $ai, $bi )= ( 0, 0 ); + push @cdiff, $ai, $bi; + while( 1 ) { + while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) { + shift @$am; + shift @$bm; + ++$ai, ++$bi; + } + push @cdiff, $ai, $bi; + last if ! @$am; + $ai = $am->[0]; + $bi = $bm->[0]; + push @cdiff, $ai, $bi; + } + push @cdiff, 0+@$a, 0+@$b + if $ai < @$a || $bi < @$b; + return wantarray ? @cdiff : \@cdiff; +} + +sub diff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $hunk = []; + my $discard = sub { + push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ]; + }; + my $add = sub { + push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ]; + }; + my $match = sub { + push @$retval, $hunk + if 0 < @$hunk; + $hunk = [] + }; + traverse_sequences( $a, $b, + { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ ); + &$match(); + return wantarray ? @$retval : $retval; +} + +sub sdiff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) }; + my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) }; + my $change = sub { + push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + my $match = sub { + push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + traverse_balanced( + $a, + $b, + { + MATCH => $match, + DISCARD_A => $discard, + DISCARD_B => $add, + CHANGE => $change, + }, + @_ + ); + return wantarray ? @$retval : $retval; +} + +######################################## +my $Root= __PACKAGE__; +package Algorithm::Diff::_impl; +use strict; + +sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices + # 1 # $me->[1]: Ref to first sequence + # 2 # $me->[2]: Ref to second sequence +sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos +sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items +sub _Base() { 5 } # $me->[_Base]: Added to range's min and max +sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected +sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position +sub _Min() { -2 } # Added to _Off to get min instead of max+1 + +sub Die +{ + require Carp; + Carp::confess( @_ ); +} + +sub _ChkPos +{ + my( $me )= @_; + return if $me->[_Pos]; + my $meth= ( caller(1) )[3]; + Die( "Called $meth on 'reset' object" ); +} + +sub _ChkSeq +{ + my( $me, $seq )= @_; + return $seq + $me->[_Off] + if 1 == $seq || 2 == $seq; + my $meth= ( caller(1) )[3]; + Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" ); +} + +sub getObjPkg +{ + my( $us )= @_; + return ref $us if ref $us; + return $us . "::_obj"; +} + +sub new +{ + my( $us, $seq1, $seq2, $opts ) = @_; + my @args; + for( $opts->{keyGen} ) { + push @args, $_ if $_; + } + for( $opts->{keyGenArgs} ) { + push @args, @$_ if $_; + } + my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args ); + my $same= 1; + if( 0 == $cdif->[2] && 0 == $cdif->[3] ) { + $same= 0; + splice @$cdif, 0, 2; + } + my @obj= ( $cdif, $seq1, $seq2 ); + $obj[_End] = (1+@$cdif)/2; + $obj[_Same] = $same; + $obj[_Base] = 0; + my $me = bless \@obj, $us->getObjPkg(); + $me->Reset( 0 ); + return $me; +} + +sub Reset +{ + my( $me, $pos )= @_; + $pos= int( $pos || 0 ); + $pos += $me->[_End] + if $pos < 0; + $pos= 0 + if $pos < 0 || $me->[_End] <= $pos; + $me->[_Pos]= $pos || !1; + $me->[_Off]= 2*$pos - 1; + return $me; +} + +sub Base +{ + my( $me, $base )= @_; + my $oldBase= $me->[_Base]; + $me->[_Base]= 0+$base if defined $base; + return $oldBase; +} + +sub Copy +{ + my( $me, $pos, $base )= @_; + my @obj= @$me; + my $you= bless \@obj, ref($me); + $you->Reset( $pos ) if defined $pos; + $you->Base( $base ); + return $you; +} + +sub Next { + my( $me, $steps )= @_; + $steps= 1 if ! defined $steps; + if( $steps ) { + my $pos= $me->[_Pos]; + my $new= $pos + $steps; + $new= 0 if $pos && $new < 0; + $me->Reset( $new ) + } + return $me->[_Pos]; +} + +sub Prev { + my( $me, $steps )= @_; + $steps= 1 if ! defined $steps; + my $pos= $me->Next(-$steps); + $pos -= $me->[_End] if $pos; + return $pos; +} + +sub Diff { + my( $me )= @_; + $me->_ChkPos(); + return 0 if $me->[_Same] == ( 1 & $me->[_Pos] ); + my $ret= 0; + my $off= $me->[_Off]; + for my $seq ( 1, 2 ) { + $ret |= $seq + if $me->[_Idx][ $off + $seq + _Min ] + < $me->[_Idx][ $off + $seq ]; + } + return $ret; +} + +sub Min { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off= $me->_ChkSeq($seq); + $base= $me->[_Base] if !defined $base; + return $base + $me->[_Idx][ $off + _Min ]; +} + +sub Max { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off= $me->_ChkSeq($seq); + $base= $me->[_Base] if !defined $base; + return $base + $me->[_Idx][ $off ] -1; +} + +sub Range { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off = $me->_ChkSeq($seq); + if( !wantarray ) { + return $me->[_Idx][ $off ] + - $me->[_Idx][ $off + _Min ]; + } + $base= $me->[_Base] if !defined $base; + return ( $base + $me->[_Idx][ $off + _Min ] ) + .. ( $base + $me->[_Idx][ $off ] - 1 ); +} + +sub Items { + my( $me, $seq )= @_; + $me->_ChkPos(); + my $off = $me->_ChkSeq($seq); + if( !wantarray ) { + return $me->[_Idx][ $off ] + - $me->[_Idx][ $off + _Min ]; + } + return + @{$me->[$seq]}[ + $me->[_Idx][ $off + _Min ] + .. ( $me->[_Idx][ $off ] - 1 ) + ]; +} + +sub Same { + my( $me )= @_; + $me->_ChkPos(); + return wantarray ? () : 0 + if $me->[_Same] != ( 1 & $me->[_Pos] ); + return $me->Items(1); +} + +my %getName; +BEGIN { + %getName= ( + same => \&Same, + diff => \&Diff, + base => \&Base, + min => \&Min, + max => \&Max, + range=> \&Range, + items=> \&Items, # same thing + ); +} + +sub Get +{ + my $me= shift @_; + $me->_ChkPos(); + my @value; + for my $arg ( @_ ) { + for my $word ( split ' ', $arg ) { + my $meth; + if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/ + || not $meth= $getName{ lc $2 } + ) { + Die( $Root, ", Get: Invalid request ($word)" ); + } + my( $base, $name, $seq )= ( $1, $2, $3 ); + push @value, scalar( + 4 == length($name) + ? $meth->( $me ) + : $meth->( $me, $seq, $base ) + ); + } + } + if( wantarray ) { + return @value; + } elsif( 1 == @value ) { + return $value[0]; + } + Die( 0+@value, " values requested from ", + $Root, "'s Get in scalar context" ); +} + + +my $Obj= getObjPkg($Root); +no strict 'refs'; + +for my $meth ( qw( new getObjPkg ) ) { + *{$Root."::".$meth} = \&{$meth}; + *{$Obj ."::".$meth} = \&{$meth}; +} +for my $meth ( qw( + Next Prev Reset Copy Base Diff + Same Items Range Min Max Get + _ChkPos _ChkSeq +) ) { + *{$Obj."::".$meth} = \&{$meth}; +} + +1; +__END__ + +=head1 NAME + +Algorithm::Diff - Compute `intelligent' differences between two files / lists + +=head1 SYNOPSIS + + require Algorithm::Diff; + + # This example produces traditional 'diff' output: + + my $diff = Algorithm::Diff->new( \@seq1, \@seq2 ); + + $diff->Base( 1 ); # Return line numbers, not indices + while( $diff->Next() ) { + next if $diff->Same(); + my $sep = ''; + if( ! $diff->Items(2) ) { + sprintf "%d,%dd%d\n", + $diff->Get(qw( Min1 Max1 Max2 )); + } elsif( ! $diff->Items(1) ) { + sprint "%da%d,%d\n", + $diff->Get(qw( Max1 Min2 Max2 )); + } else { + $sep = "---\n"; + sprintf "%d,%dc%d,%d\n", + $diff->Get(qw( Min1 Max1 Min2 Max2 )); + } + print "< $_" for $diff->Items(1); + print $sep; + print "> $_" for $diff->Items(2); + } + + + # Alternate interfaces: + + use Algorithm::Diff qw( + LCS LCS_length LCSidx + diff sdiff compact_diff + traverse_sequences traverse_balanced ); + + @lcs = LCS( \@seq1, \@seq2 ); + $lcsref = LCS( \@seq1, \@seq2 ); + $count = LCS_length( \@seq1, \@seq2 ); + + ( $seq1idxref, $seq2idxref ) = LCSidx( \@seq1, \@seq2 ); + + + # Complicated interfaces: + + @diffs = diff( \@seq1, \@seq2 ); + + @sdiffs = sdiff( \@seq1, \@seq2 ); + + @cdiffs = compact_diff( \@seq1, \@seq2 ); + + traverse_sequences( + \@seq1, + \@seq2, + { MATCH => \&callback1, + DISCARD_A => \&callback2, + DISCARD_B => \&callback3, + }, + \&key_generator, + @extra_args, + ); + + traverse_balanced( + \@seq1, + \@seq2, + { MATCH => \&callback1, + DISCARD_A => \&callback2, + DISCARD_B => \&callback3, + CHANGE => \&callback4, + }, + \&key_generator, + @extra_args, + ); + + +=head1 INTRODUCTION + +(by Mark-Jason Dominus) + +I once read an article written by the authors of C; they said +that they worked very hard on the algorithm until they found the +right one. + +I think what they ended up using (and I hope someone will correct me, +because I am not very confident about this) was the `longest common +subsequence' method. In the LCS problem, you have two sequences of +items: + + a b c d f g h j q z + + a b c d e f g i j k r x y z + +and you want to find the longest sequence of items that is present in +both original sequences in the same order. That is, you want to find +a new sequence I which can be obtained from the first sequence by +deleting some items, and from the secend sequence by deleting other +items. You also want I to be as long as possible. In this case I +is + + a b c d f g j z + +From there it's only a small step to get diff-like output: + + e h i k q r x y + + - + + - + + + + +This module solves the LCS problem. It also includes a canned function +to generate C-like output. + +It might seem from the example above that the LCS of two sequences is +always pretty obvious, but that's not always the case, especially when +the two sequences have many repeated elements. For example, consider + + a x b y c z p d q + a b c a x b y c z + +A naive approach might start by matching up the C and C that +appear at the beginning of each sequence, like this: + + a x b y c z p d q + a b c a b y c z + +This finds the common subsequence C. But actually, the LCS +is C: + + a x b y c z p d q + a b c a x b y c z + +or + + a x b y c z p d q + a b c a x b y c z + +=head1 USAGE + +(See also the README file and several example +scripts include with this module.) + +This module now provides an object-oriented interface that uses less +memory and is easier to use than most of the previous procedural +interfaces. It also still provides several exportable functions. We'll +deal with these in ascending order of difficulty: C, +C, C, OO interface, C, C, C, +C, and C. + +=head2 C + +Given references to two lists of items, LCS returns an array containing +their longest common subsequence. In scalar context, it returns a +reference to such a list. + + @lcs = LCS( \@seq1, \@seq2 ); + $lcsref = LCS( \@seq1, \@seq2 ); + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + + @lcs = LCS( \@seq1, \@seq2, \&keyGen, @args ); + $lcsref = LCS( \@seq1, \@seq2, \&keyGen, @args ); + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C + +This is just like C except it only returns the length of the +longest common subsequence. This provides a performance gain of about +9% compared to C. + +=head2 C + +Like C except it returns references to two arrays. The first array +contains the indices into @seq1 where the LCS items are located. The +second array contains the indices into @seq2 where the LCS items are located. + +Therefore, the following three lists will contain the same values: + + my( $idx1, $idx2 ) = LCSidx( \@seq1, \@seq2 ); + my @list1 = @seq1[ @$idx1 ]; + my @list2 = @seq2[ @$idx2 ]; + my @list3 = LCS( \@seq1, \@seq2 ); + +=head2 C + + $diff = Algorithm::Diffs->new( \@seq1, \@seq2 ); + $diff = Algorithm::Diffs->new( \@seq1, \@seq2, \%opts ); + +C computes the smallest set of additions and deletions necessary +to turn the first sequence into the second and compactly records them +in the object. + +You use the object to iterate over I, where each hunk represents +a contiguous section of items which should be added, deleted, replaced, +or left unchanged. + +=over 4 + +The following summary of all of the methods looks a lot like Perl code +but some of the symbols have different meanings: + + [ ] Encloses optional arguments + : Is followed by the default value for an optional argument + | Separates alternate return results + +Method summary: + + $obj = Algorithm::Diff->new( \@seq1, \@seq2, [ \%opts ] ); + $pos = $obj->Next( [ $count : 1 ] ); + $revPos = $obj->Prev( [ $count : 1 ] ); + $obj = $obj->Reset( [ $pos : 0 ] ); + $copy = $obj->Copy( [ $pos, [ $newBase ] ] ); + $oldBase = $obj->Base( [ $newBase ] ); + +Note that all of the following methods C if used on an object that +is "reset" (not currently pointing at any hunk). + + $bits = $obj->Diff( ); + @items|$cnt = $obj->Same( ); + @items|$cnt = $obj->Items( $seqNum ); + @idxs |$cnt = $obj->Range( $seqNum, [ $base ] ); + $minIdx = $obj->Min( $seqNum, [ $base ] ); + $maxIdx = $obj->Max( $seqNum, [ $base ] ); + @values = $obj->Get( @names ); + +Passing in C for an optional argument is always treated the same +as if no argument were passed in. + +=item C + + $pos = $diff->Next(); # Move forward 1 hunk + $pos = $diff->Next( 2 ); # Move forward 2 hunks + $pos = $diff->Next(-5); # Move backward 5 hunks + +C moves the object to point at the next hunk. The object starts +out "reset", which means it isn't pointing at any hunk. If the object +is reset, then C moves to the first hunk. + +C returns a true value iff the move didn't go past the last hunk. +So C will return true iff the object is not reset. + +Actually, C returns the object's new position, which is a number +between 1 and the number of hunks (inclusive), or returns a false value. + +=item C + +C is almost identical to C; it moves to the $Nth +previous hunk. On a 'reset' object, C [and C] move +to the last hunk. + +The position returned by C is relative to the I of the +hunks; -1 for the last hunk, -2 for the second-to-last, etc. + +=item C + + $diff->Reset(); # Reset the object's position + $diff->Reset($pos); # Move to the specified hunk + $diff->Reset(1); # Move to the first hunk + $diff->Reset(-1); # Move to the last hunk + +C returns the object, so, for example, you could use +C<< $diff->Reset()->Next(-1) >> to get the number of hunks. + +=item C + + $copy = $diff->Copy( $newPos, $newBase ); + +C returns a copy of the object. The copy and the orignal object +share most of their data, so making copies takes very little memory. +The copy maintains its own position (separate from the original), which +is the main purpose of copies. It also maintains its own base. + +By default, the copy's position starts out the same as the original +object's position. But C takes an optional first argument to set the +new position, so the following three snippets are equivalent: + + $copy = $diff->Copy($pos); + + $copy = $diff->Copy(); + $copy->Reset($pos); + + $copy = $diff->Copy()->Reset($pos); + +C takes an optional second argument to set the base for +the copy. If you wish to change the base of the copy but leave +the position the same as in the original, here are two +equivalent ways: + + $copy = $diff->Copy(); + $copy->Base( 0 ); + + $copy = $diff->Copy(undef,0); + +Here are two equivalent way to get a "reset" copy: + + $copy = $diff->Copy(0); + + $copy = $diff->Copy()->Reset(); + +=item C + + $bits = $obj->Diff(); + +C returns a true value iff the current hunk contains items that are +different between the two sequences. It actually returns one of the +follow 4 values: + +=over 4 + +=item 3 + +C<3==(1|2)>. This hunk contains items from @seq1 and the items +from @seq2 that should replace them. Both sequence 1 and 2 +contain changed items so both the 1 and 2 bits are set. + +=item 2 + +This hunk only contains items from @seq2 that should be inserted (not +items from @seq1). Only sequence 2 contains changed items so only the 2 +bit is set. + +=item 1 + +This hunk only contains items from @seq1 that should be deleted (not +items from @seq2). Only sequence 1 contains changed items so only the 1 +bit is set. + +=item 0 + +This means that the items in this hunk are the same in both sequences. +Neither sequence 1 nor 2 contain changed items so neither the 1 nor the +2 bits are set. + +=back + +=item C + +C returns a true value iff the current hunk contains items that +are the same in both sequences. It actually returns the list of items +if they are the same or an emty list if they aren't. In a scalar +context, it returns the size of the list. + +=item C + + $count = $diff->Items(2); + @items = $diff->Items($seqNum); + +C returns the (number of) items from the specified sequence that +are part of the current hunk. + +If the current hunk contains only insertions, then +C<< $diff->Items(1) >> will return an empty list (0 in a scalar conext). +If the current hunk contains only deletions, then C<< $diff->Items(2) >> +will return an empty list (0 in a scalar conext). + +If the hunk contains replacements, then both C<< $diff->Items(1) >> and +C<< $diff->Items(2) >> will return different, non-empty lists. + +Otherwise, the hunk contains identical items and all of the following +will return the same lists: + + @items = $diff->Items(1); + @items = $diff->Items(2); + @items = $diff->Same(); + +=item C + + $count = $diff->Range( $seqNum ); + @indices = $diff->Range( $seqNum ); + @indices = $diff->Range( $seqNum, $base ); + +C is like C except that it returns a list of I to +the items rather than the items themselves. By default, the index of +the first item (in each sequence) is 0 but this can be changed by +calling the C method. So, by default, the following two snippets +return the same lists: + + @list = $diff->Items(2); + @list = @seq2[ $diff->Range(2) ]; + +You can also specify the base to use as the second argument. So the +following two snippets I return the same lists: + + @list = $diff->Items(1); + @list = @seq1[ $diff->Range(1,0) ]; + +=item C + + $curBase = $diff->Base(); + $oldBase = $diff->Base($newBase); + +C sets and/or returns the current base (usually 0 or 1) that is +used when you request range information. The base defaults to 0 so +that range information is returned as array indices. You can set the +base to 1 if you want to report traditional line numbers instead. + +=item C + + $min1 = $diff->Min(1); + $min = $diff->Min( $seqNum, $base ); + +C returns the first value that C would return (given the +same arguments) or returns C if C would return an empty +list. + +=item C + +C returns the last value that C would return or C. + +=item C + + ( $n, $x, $r ) = $diff->Get(qw( min1 max1 range1 )); + @values = $diff->Get(qw( 0min2 1max2 range2 same base )); + +C returns one or more scalar values. You pass in a list of the +names of the values you want returned. Each name must match one of the +following regexes: + + /^(-?\d+)?(min|max)[12]$/i + /^(range[12]|same|diff|base)$/i + +The 1 or 2 after a name says which sequence you want the information +for (and where allowed, it is required). The optional number before +"min" or "max" is the base to use. So the following equalities hold: + + $diff->Get('min1') == $diff->Min(1) + $diff->Get('0min2') == $diff->Min(2,0) + +Using C in a scalar context when you've passed in more than one +name is a fatal error (C is called). + +=back + +=head2 C + +Given a reference to a list of items, C returns a reference +to a hash which can be used when comparing this sequence to other +sequences with C or C. + + $prep = prepare( \@seq1 ); + for $i ( 0 .. 10_000 ) + { + @lcs = LCS( $prep, $seq[$i] ); + # do something useful with @lcs + } + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + + $prep = prepare( \@seq1, \&keyGen ); + for $i ( 0 .. 10_000 ) + { + @lcs = LCS( $seq[$i], $prep, \&keyGen ); + # do something useful with @lcs + } + +Using C provides a performance gain of about 50% when calling LCS +many times compared with not preparing. + +=head2 C + + @diffs = diff( \@seq1, \@seq2 ); + $diffs_ref = diff( \@seq1, \@seq2 ); + +C computes the smallest set of additions and deletions necessary +to turn the first sequence into the second, and returns a description +of these changes. The description is a list of I; each hunk +represents a contiguous section of items which should be added, +deleted, or replaced. (Hunks containing unchanged items are not +included.) + +The return value of C is a list of hunks, or, in scalar context, a +reference to such a list. If there are no differences, the list will be +empty. + +Here is an example. Calling C for the following two sequences: + + a b c e h j l m n p + b c d e f j k l m r s t + +would produce the following list: + + ( + [ [ '-', 0, 'a' ] ], + + [ [ '+', 2, 'd' ] ], + + [ [ '-', 4, 'h' ], + [ '+', 4, 'f' ] ], + + [ [ '+', 6, 'k' ] ], + + [ [ '-', 8, 'n' ], + [ '-', 9, 'p' ], + [ '+', 9, 'r' ], + [ '+', 10, 's' ], + [ '+', 11, 't' ] ], + ) + +There are five hunks here. The first hunk says that the C at +position 0 of the first sequence should be deleted (C<->). The second +hunk says that the C at position 2 of the second sequence should +be inserted (C<+>). The third hunk says that the C at position 4 +of the first sequence should be removed and replaced with the C +from position 4 of the second sequence. And so on. + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C + + @sdiffs = sdiff( \@seq1, \@seq2 ); + $sdiffs_ref = sdiff( \@seq1, \@seq2 ); + +C computes all necessary components to show two sequences +and their minimized differences side by side, just like the +Unix-utility I does: + + same same + before | after + old < - + - > new + +It returns a list of array refs, each pointing to an array of +display instructions. In scalar context it returns a reference +to such a list. If there are no differences, the list will have one +entry per item, each indicating that the item was unchanged. + +Display instructions consist of three elements: A modifier indicator +(C<+>: Element added, C<->: Element removed, C: Element unmodified, +C: Element changed) and the value of the old and new elements, to +be displayed side-by-side. + +An C of the following two sequences: + + a b c e h j l m n p + b c d e f j k l m r s t + +results in + + ( [ '-', 'a', '' ], + [ 'u', 'b', 'b' ], + [ 'u', 'c', 'c' ], + [ '+', '', 'd' ], + [ 'u', 'e', 'e' ], + [ 'c', 'h', 'f' ], + [ 'u', 'j', 'j' ], + [ '+', '', 'k' ], + [ 'u', 'l', 'l' ], + [ 'u', 'm', 'm' ], + [ 'c', 'n', 'r' ], + [ 'c', 'p', 's' ], + [ '+', '', 't' ], + ) + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C + +C is much like C except it returns a much more +compact description consisting of just one flat list of indices. An +example helps explain the format: + + my @a = qw( a b c e h j l m n p ); + my @b = qw( b c d e f j k l m r s t ); + @cdiff = compact_diff( \@a, \@b ); + # Returns: + # @a @b @a @b + # start start values values + ( 0, 0, # = + 0, 0, # a ! + 1, 0, # b c = b c + 3, 2, # ! d + 3, 3, # e = e + 4, 4, # f ! h + 5, 5, # j = j + 6, 6, # ! k + 6, 7, # l m = l m + 8, 9, # n p ! r s t + 10, 12, # + ); + +The 0th, 2nd, 4th, etc. entries are all indices into @seq1 (@a in the +above example) indicating where a hunk begins. The 1st, 3rd, 5th, etc. +entries are all indices into @seq2 (@b in the above example) indicating +where the same hunk begins. + +So each pair of indices (except the last pair) describes where a hunk +begins (in each sequence). Since each hunk must end at the item just +before the item that starts the next hunk, the next pair of indices can +be used to determine where the hunk ends. + +So, the first 4 entries (0..3) describe the first hunk. Entries 0 and 1 +describe where the first hunk begins (and so are always both 0). +Entries 2 and 3 describe where the next hunk begins, so subtracting 1 +from each tells us where the first hunk ends. That is, the first hunk +contains items C<$diff[0]> through C<$diff[2] - 1> of the first sequence +and contains items C<$diff[1]> through C<$diff[3] - 1> of the second +sequence. + +In other words, the first hunk consists of the following two lists of items: + + # 1st pair 2nd pair + # of indices of indices + @list1 = @a[ $cdiff[0] .. $cdiff[2]-1 ]; + @list2 = @b[ $cdiff[1] .. $cdiff[3]-1 ]; + # Hunk start Hunk end + +Note that the hunks will always alternate between those that are part of +the LCS (those that contain unchanged items) and those that contain +changes. This means that all we need to be told is whether the first +hunk is a 'same' or 'diff' hunk and we can determine which of the other +hunks contain 'same' items or 'diff' items. + +By convention, we always make the first hunk contain unchanged items. +So the 1st, 3rd, 5th, etc. hunks (all odd-numbered hunks if you start +counting from 1) all contain unchanged items. And the 2nd, 4th, 6th, +etc. hunks (all even-numbered hunks if you start counting from 1) all +contain changed items. + +Since @a and @b don't begin with the same value, the first hunk in our +example is empty (otherwise we'd violate the above convention). Note +that the first 4 index values in our example are all zero. Plug these +values into our previous code block and we get: + + @hunk1a = @a[ 0 .. 0-1 ]; + @hunk1b = @b[ 0 .. 0-1 ]; + +And C<0..-1> returns the empty list. + +Move down one pair of indices (2..5) and we get the offset ranges for +the second hunk, which contains changed items. + +Since C<@diff[2..5]> contains (0,0,1,0) in our example, the second hunk +consists of these two lists of items: + + @hunk2a = @a[ $cdiff[2] .. $cdiff[4]-1 ]; + @hunk2b = @b[ $cdiff[3] .. $cdiff[5]-1 ]; + # or + @hunk2a = @a[ 0 .. 1-1 ]; + @hunk2b = @b[ 0 .. 0-1 ]; + # or + @hunk2a = @a[ 0 .. 0 ]; + @hunk2b = @b[ 0 .. -1 ]; + # or + @hunk2a = ( 'a' ); + @hunk2b = ( ); + +That is, we would delete item 0 ('a') from @a. + +Since C<@diff[4..7]> contains (1,0,3,2) in our example, the third hunk +consists of these two lists of items: + + @hunk3a = @a[ $cdiff[4] .. $cdiff[6]-1 ]; + @hunk3a = @b[ $cdiff[5] .. $cdiff[7]-1 ]; + # or + @hunk3a = @a[ 1 .. 3-1 ]; + @hunk3a = @b[ 0 .. 2-1 ]; + # or + @hunk3a = @a[ 1 .. 2 ]; + @hunk3a = @b[ 0 .. 1 ]; + # or + @hunk3a = qw( b c ); + @hunk3a = qw( b c ); + +Note that this third hunk contains unchanged items as our convention demands. + +You can continue this process until you reach the last two indices, +which will always be the number of items in each sequence. This is +required so that subtracting one from each will give you the indices to +the last items in each sequence. + +=head2 C + +C used to be the most general facility provided by +this module (the new OO interface is more powerful and much easier to +use). + +Imagine that there are two arrows. Arrow A points to an element of +sequence A, and arrow B points to an element of the sequence B. +Initially, the arrows point to the first elements of the respective +sequences. C will advance the arrows through the +sequences one element at a time, calling an appropriate user-specified +callback function before each advance. It willadvance the arrows in +such a way that if there are equal elements C<$A[$i]> and C<$B[$j]> +which are equal and which are part of the LCS, there will be some moment +during the execution of C when arrow A is pointing +to C<$A[$i]> and arrow B is pointing to C<$B[$j]>. When this happens, +C will call the C callback function and then +it will advance both arrows. + +Otherwise, one of the arrows is pointing to an element of its sequence +that is not part of the LCS. C will advance that +arrow and will call the C or the C callback, +depending on which arrow it advanced. If both arrows point to elements +that are not part of the LCS, then C will advance +one of them and call the appropriate callback, but it is not specified +which it will call. + +The arguments to C are the two sequences to +traverse, and a hash which specifies the callback functions, like this: + + traverse_sequences( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + } + ); + +Callbacks for MATCH, DISCARD_A, and DISCARD_B are invoked with at least +the indices of the two arrows as their arguments. They are not expected +to return any values. If a callback is omitted from the table, it is +not called. + +Callbacks for A_FINISHED and B_FINISHED are invoked with at least the +corresponding index in A or B. + +If arrow A reaches the end of its sequence, before arrow B does, +C will call the C callback when it +advances arrow B, if there is such a function; if not it will call +C instead. Similarly if arrow B finishes first. +C returns when both arrows are at the ends of their +respective sequences. It returns true on success and false on failure. +At present there is no way to fail. + +C may be passed an optional fourth parameter; this +is a CODE reference to a key generation function. See L. + +Additional parameters, if any, will be passed to the key generation function. + +If you want to pass additional parameters to your callbacks, but don't +need a custom key generation function, you can get the default by +passing undef: + + traverse_sequences( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + }, + undef, # default key-gen + $myArgument1, + $myArgument2, + $myArgument3, + ); + +C does not have a useful return value; you are +expected to plug in the appropriate behavior with the callback +functions. + +=head2 C + +C is an alternative to C. It +uses a different algorithm to iterate through the entries in the +computed LCS. Instead of sticking to one side and showing element changes +as insertions and deletions only, it will jump back and forth between +the two sequences and report I occurring as deletions on one +side followed immediatly by an insertion on the other side. + +In addition to the C, C, and C callbacks +supported by C, C supports +a C callback indicating that one element got C by another: + + traverse_balanced( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + CHANGE => $callback_4, + } + ); + +If no C callback is specified, C +will map C events to C and C actions, +therefore resulting in a similar behaviour as C +with different order of events. + +C might be a bit slower than C, +noticable only while processing huge amounts of data. + +The C function of this module +is implemented as call to C. + +C does not have a useful return value; you are expected to +plug in the appropriate behavior with the callback functions. + +=head1 KEY GENERATION FUNCTIONS + +Most of the functions accept an optional extra parameter. This is a +CODE reference to a key generating (hashing) function that should return +a string that uniquely identifies a given element. It should be the +case that if two elements are to be considered equal, their keys should +be the same (and the other way around). If no key generation function +is provided, the key will be the element as a string. + +By default, comparisons will use "eq" and elements will be turned into keys +using the default stringizing operator '""'. + +Where this is important is when you're comparing something other than +strings. If it is the case that you have multiple different objects +that should be considered to be equal, you should supply a key +generation function. Otherwise, you have to make sure that your arrays +contain unique references. + +For instance, consider this example: + + package Person; + + sub new + { + my $package = shift; + return bless { name => '', ssn => '', @_ }, $package; + } + + sub clone + { + my $old = shift; + my $new = bless { %$old }, ref($old); + } + + sub hash + { + return shift()->{'ssn'}; + } + + my $person1 = Person->new( name => 'Joe', ssn => '123-45-6789' ); + my $person2 = Person->new( name => 'Mary', ssn => '123-47-0000' ); + my $person3 = Person->new( name => 'Pete', ssn => '999-45-2222' ); + my $person4 = Person->new( name => 'Peggy', ssn => '123-45-9999' ); + my $person5 = Person->new( name => 'Frank', ssn => '000-45-9999' ); + +If you did this: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4, $person5 ]; + Algorithm::Diff::diff( $array1, $array2 ); + +everything would work out OK (each of the objects would be converted +into a string like "Person=HASH(0x82425b0)" for comparison). + +But if you did this: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; + Algorithm::Diff::diff( $array1, $array2 ); + +$person4 and $person4->clone() (which have the same name and SSN) +would be seen as different objects. If you wanted them to be considered +equivalent, you would have to pass in a key generation function: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; + Algorithm::Diff::diff( $array1, $array2, \&Person::hash ); + +This would use the 'ssn' field in each Person as a comparison key, and +so would consider $person4 and $person4->clone() as equal. + +You may also pass additional parameters to the key generation function +if you wish. + +=head1 ERROR CHECKING + +If you pass these routines a non-reference and they expect a reference, +they will die with a message. + +=head1 AUTHOR + +This version released by Tye McQueen (http://perlmonks.org/?node=tye). + +=head1 LICENSE + +Parts Copyright (c) 2000-2004 Ned Konz. All rights reserved. +Parts by Tye McQueen. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl. + +=head1 MAILING LIST + +Mark-Jason still maintains a mailing list. To join a low-volume mailing +list for announcements related to diff and Algorithm::Diff, send an +empty mail message to mjd-perl-diff-request@plover.com. + +=head1 CREDITS + +Versions through 0.59 (and much of this documentation) were written by: + +Mark-Jason Dominus, mjd-perl-diff@plover.com + +This version borrows some documentation and routine names from +Mark-Jason's, but Diff.pm's code was completely replaced. + +This code was adapted from the Smalltalk code of Mario Wolczko +, which is available at +ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st + +C and C were written by Mike Schilli +. + +The algorithm is that described in +I, +CACM, vol.20, no.5, pp.350-353, May 1977, with a few +minor improvements to improve the speed. + +Much work was done by Ned Konz (perl@bike-nomad.com). + +The OO interface and some other changes are by Tye McQueen. + +=cut diff --git a/tests/devices/src/tests/Make.tests b/tests/devices/src/tests/Make.tests new file mode 100644 index 0000000..1df2e68 --- /dev/null +++ b/tests/devices/src/tests/Make.tests @@ -0,0 +1,75 @@ +# -*- makefile -*- + +include $(patsubst %,$(SRCDIR)/%/Make.tests,$(TEST_SUBDIRS)) + +PROGS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_PROGS)) +TESTS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_TESTS)) +EXTRA_GRADES = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_EXTRA_GRADES)) + +OUTPUTS = $(addsuffix .output,$(TESTS) $(EXTRA_GRADES)) +ERRORS = $(addsuffix .errors,$(TESTS) $(EXTRA_GRADES)) +RESULTS = $(addsuffix .result,$(TESTS) $(EXTRA_GRADES)) + +ifdef PROGS +include ../../Makefile.userprog +endif + +TIMEOUT = 60 + +clean:: + rm -f $(OUTPUTS) $(ERRORS) $(RESULTS) + +grade:: results + $(SRCDIR)/tests/make-grade $(SRCDIR) $< $(GRADING_FILE) | tee $@ + +check:: results + @cat $< + @COUNT="`egrep '^(pass|FAIL) ' $< | wc -l | sed 's/[ ]//g;'`"; \ + FAILURES="`egrep '^FAIL ' $< | wc -l | sed 's/[ ]//g;'`"; \ + if [ $$FAILURES = 0 ]; then \ + echo "All $$COUNT tests passed!"; \ + else \ + echo "Warning: $$FAILURES of $$COUNT tests failed!"; \ + fi + +results: $(RESULTS) + @for d in $(TESTS) $(EXTRA_GRADES); do \ + if echo PASS | cmp -s $$d.result -; then \ + echo "pass $$d"; \ + else \ + echo "FAIL $$d"; \ + fi; \ + done > $@ + +outputs:: $(OUTPUTS) + +$(foreach prog,$(PROGS),$(eval $(prog).output: $(prog))) +$(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES))) +$(foreach test,$(TESTS),$(eval $(test).output: TEST = $(test))) + +# Prevent an environment variable VERBOSE from surprising us. +VERBOSE = + +TESTCMD = pintos -v -k -T $(TIMEOUT) +TESTCMD += $(SIMULATOR) +TESTCMD += $(PINTOSOPTS) +ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) +TESTCMD += $(FILESYSSOURCE) +TESTCMD += $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file))) +endif +ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm) +TESTCMD += --swap-size=8 +endif +TESTCMD += -- -q +TESTCMD += $(KERNELFLAGS) +ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) +TESTCMD += -f +endif +TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F)) +TESTCMD += < /dev/null +TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).output +%.output: kernel.bin loader.bin + $(TESTCMD) + +%.result: %.ck %.output + perl -I$(SRCDIR) $< $* $@ diff --git a/tests/devices/src/tests/arc4.c b/tests/devices/src/tests/arc4.c new file mode 100644 index 0000000..54183aa --- /dev/null +++ b/tests/devices/src/tests/arc4.c @@ -0,0 +1,53 @@ +#include +#include "tests/arc4.h" + +/* Swap bytes. */ +static inline void +swap_byte (uint8_t *a, uint8_t *b) +{ + uint8_t t = *a; + *a = *b; + *b = t; +} + +void +arc4_init (struct arc4 *arc4, const void *key_, size_t size) +{ + const uint8_t *key = key_; + size_t key_idx; + uint8_t *s; + int i, j; + + s = arc4->s; + arc4->i = arc4->j = 0; + for (i = 0; i < 256; i++) + s[i] = (uint8_t) i; + for (key_idx = 0, i = j = 0; i < 256; i++) + { + j = (j + s[i] + key[key_idx]) & 255; + swap_byte (s + i, s + j); + if (++key_idx >= size) + key_idx = 0; + } +} + +void +arc4_crypt (struct arc4 *arc4, void *buf_, size_t size) +{ + uint8_t *buf = buf_; + uint8_t *s; + uint8_t i, j; + + s = arc4->s; + i = arc4->i; + j = arc4->j; + while (size-- > 0) + { + i = (uint8_t) (i + 1); + j = (uint8_t) (j + s[i]); + swap_byte (s + i, s + j); + *buf++ ^= s[(s[i] + s[j]) & 255]; + } + arc4->i = i; + arc4->j = j; +} diff --git a/tests/devices/src/tests/arc4.h b/tests/devices/src/tests/arc4.h new file mode 100644 index 0000000..61c533a --- /dev/null +++ b/tests/devices/src/tests/arc4.h @@ -0,0 +1,17 @@ +#ifndef TESTS_ARC4_H +#define TESTS_ARC4_H + +#include +#include + +/* Alleged RC4 algorithm encryption state. */ +struct arc4 + { + uint8_t s[256]; + uint8_t i, j; + }; + +void arc4_init (struct arc4 *, const void *, size_t); +void arc4_crypt (struct arc4 *, void *, size_t); + +#endif /* tests/arc4.h */ diff --git a/tests/devices/src/tests/arc4.pm b/tests/devices/src/tests/arc4.pm new file mode 100644 index 0000000..df19216 --- /dev/null +++ b/tests/devices/src/tests/arc4.pm @@ -0,0 +1,29 @@ +use strict; +use warnings; + +sub arc4_init { + my ($key) = @_; + my (@s) = 0...255; + my ($j) = 0; + for my $i (0...255) { + $j = ($j + $s[$i] + ord (substr ($key, $i % length ($key), 1))) & 0xff; + @s[$i, $j] = @s[$j, $i]; + } + return (0, 0, @s); +} + +sub arc4_crypt { + my ($arc4, $buf) = @_; + my ($i, $j, @s) = @$arc4; + my ($out) = ""; + for my $c (split (//, $buf)) { + $i = ($i + 1) & 0xff; + $j = ($j + $s[$i]) & 0xff; + @s[$i, $j] = @s[$j, $i]; + $out .= chr (ord ($c) ^ $s[($s[$i] + $s[$j]) & 0xff]); + } + @$arc4 = ($i, $j, @s); + return $out; +} + +1; diff --git a/tests/devices/src/tests/cksum.c b/tests/devices/src/tests/cksum.c new file mode 100644 index 0000000..6d108bb --- /dev/null +++ b/tests/devices/src/tests/cksum.c @@ -0,0 +1,92 @@ +/* crctab[] and cksum() are from the `cksum' entry in SUSv3. */ + +#include +#include "tests/cksum.h" + +static unsigned long crctab[] = { + 0x00000000, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/* This is the algorithm used by the Posix `cksum' utility. */ +unsigned long +cksum (const void *b_, size_t n) +{ + const unsigned char *b = b_; + uint32_t s = 0; + size_t i; + for (i = n; i > 0; --i) + { + unsigned char c = *b++; + s = (s << 8) ^ crctab[(s >> 24) ^ c]; + } + while (n != 0) + { + unsigned char c = (unsigned char) n; + n >>= 8; + s = (s << 8) ^ crctab[(s >> 24) ^ c]; + } + return ~s; +} + +#ifdef STANDALONE_TEST +#include +int +main (void) +{ + char buf[65536]; + int n = fread (buf, 1, sizeof buf, stdin); + printf ("%lu\n", cksum (buf, n)); + return 0; +} +#endif diff --git a/tests/devices/src/tests/cksum.h b/tests/devices/src/tests/cksum.h new file mode 100644 index 0000000..23a1fe9 --- /dev/null +++ b/tests/devices/src/tests/cksum.h @@ -0,0 +1,8 @@ +#ifndef TESTS_CKSUM_H +#define TESTS_CKSUM_H + +#include + +unsigned long cksum(const void *, size_t); + +#endif /* tests/cksum.h */ diff --git a/tests/devices/src/tests/cksum.pm b/tests/devices/src/tests/cksum.pm new file mode 100644 index 0000000..73be5f2 --- /dev/null +++ b/tests/devices/src/tests/cksum.pm @@ -0,0 +1,87 @@ +# From the `cksum' entry in SUSv3. + +use strict; +use warnings; + +my (@crctab) = + (0x00000000, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4); + +sub cksum { + my ($b) = @_; + my ($n) = length ($b); + my ($s) = 0; + for my $i (0...$n - 1) { + my ($c) = ord (substr ($b, $i, 1)); + $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c]; + $s &= 0xffff_ffff; + } + while ($n != 0) { + my ($c) = $n & 0xff; + $n >>= 8; + $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c]; + $s &= 0xffff_ffff; + } + return ~$s & 0xffff_ffff; +} + +sub cksum_file { + my ($file) = @_; + open (FILE, '<', $file) or die "$file: open: $!\n"; + my ($data); + sysread (FILE, $data, -s FILE) == -s FILE or die "$file: read: $!\n"; + close (FILE); + return cksum ($data); +} + +1; diff --git a/tests/devices/src/tests/devices/Grading b/tests/devices/src/tests/devices/Grading new file mode 100644 index 0000000..4dc802b --- /dev/null +++ b/tests/devices/src/tests/devices/Grading @@ -0,0 +1,4 @@ +# Percentage of the testing point total designated for each set of tests. + +50.0% tests/devices/Rubric.alarmfunc +50.0% tests/devices/Rubric.alarmrobust diff --git a/tests/devices/src/tests/devices/Make.tests b/tests/devices/src/tests/devices/Make.tests new file mode 100644 index 0000000..8c51fc2 --- /dev/null +++ b/tests/devices/src/tests/devices/Make.tests @@ -0,0 +1,48 @@ +# -*- makefile -*- + +# Test names. +tests/devices_TESTS = $(addprefix tests/devices/,alarm-single \ +alarm-multiple alarm-simultaneous alarm-no-busy-wait alarm-one \ +alarm-zero alarm-negative) + +# Sources for tests. +tests/devices_SRC = tests/devices/tests.c +tests/devices_SRC += tests/devices/alarm-wait.c +tests/devices_SRC += tests/devices/alarm-simultaneous.c +tests/devices_SRC += tests/devices/alarm-no-busy-wait.c +tests/devices_SRC += tests/devices/alarm-one.c +tests/devices_SRC += tests/devices/alarm-zero.c +tests/devices_SRC += tests/devices/alarm-negative.c + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/devices/src/tests/devices/Rubric.alarmfunc b/tests/devices/src/tests/devices/Rubric.alarmfunc new file mode 100644 index 0000000..9cf26b3 --- /dev/null +++ b/tests/devices/src/tests/devices/Rubric.alarmfunc @@ -0,0 +1,4 @@ +Functionality of alarm clock: +10 alarm-no-busy-wait +5 alarm-single +5 alarm-multiple diff --git a/tests/devices/src/tests/devices/Rubric.alarmrobust b/tests/devices/src/tests/devices/Rubric.alarmrobust new file mode 100644 index 0000000..1690204 --- /dev/null +++ b/tests/devices/src/tests/devices/Rubric.alarmrobust @@ -0,0 +1,5 @@ +Robustness of alarm clock: +10 alarm-simultaneous +5 alarm-one +5 alarm-zero +5 alarm-negative diff --git a/tests/devices/src/tests/devices/alarm-multiple.ck b/tests/devices/src/tests/devices/alarm-multiple.ck new file mode 100644 index 0000000..b0923f2 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-multiple.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::devices::alarm; +check_alarm (7); diff --git a/tests/devices/src/tests/devices/alarm-negative.c b/tests/devices/src/tests/devices/alarm-negative.c new file mode 100644 index 0000000..d218b03 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-negative.c @@ -0,0 +1,15 @@ +/* Tests timer_sleep(-100). Only requirement is that it not crash. */ + +#include +#include "tests/devices/tests.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_alarm_negative (void) +{ + timer_sleep (-100); + pass (); +} diff --git a/tests/devices/src/tests/devices/alarm-negative.ck b/tests/devices/src/tests/devices/alarm-negative.ck new file mode 100644 index 0000000..0d2bab0 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-negative.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-negative) begin +(alarm-negative) PASS +(alarm-negative) end +EOF +pass; diff --git a/tests/devices/src/tests/devices/alarm-no-busy-wait.c b/tests/devices/src/tests/devices/alarm-no-busy-wait.c new file mode 100644 index 0000000..db9a6e8 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-no-busy-wait.c @@ -0,0 +1,131 @@ +/* Creates 5 threads, each of which sleeps for 20 ticks. + Checks to ensure that the threads are genuinely sleeping (not busy-waiting) + by inspecting the ready_list half-way though the sleep time. +*/ + +#include +#include "tests/devices/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_sleep (int thread_cnt, int iterations); + +void +test_alarm_no_busy_wait (void) +{ + test_sleep (5, 1); +} + +/* Information about the test. */ +struct sleep_test + { + int64_t start; /* Current time at start of test. */ + int iterations; /* Number of iterations per thread. */ + + /* Output. */ + struct lock output_lock; /* Lock protecting output buffer. */ + int *output_pos; /* Current position in output buffer. */ + }; + +/* Information about an individual thread in the test. */ +struct sleep_thread + { + struct sleep_test *test; /* Info shared between all threads. */ + int id; /* Sleeper ID. */ + int duration; /* Number of ticks to sleep. */ + int iterations; /* Iterations counted so far. */ + }; + +static void sleeper (void *); + +/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */ +static void +test_sleep (int thread_cnt, int iterations) +{ + struct sleep_test test; + struct sleep_thread *threads; + int *output; + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations); + msg ("Each thread sleeps for 20 ticks at a time,"); + msg ("Test is successful if the threads are not running"); + msg ("when they are supposed to be asleep."); + + /* Allocate memory. */ + threads = malloc (sizeof *threads * (int)(thread_cnt)); + output = malloc (sizeof *output * (int)(iterations * thread_cnt * 2)); + if (threads == NULL || output == NULL) + PANIC ("couldn't allocate memory for test"); + + /* Initialize test. */ + test.start = timer_ticks () + 100; + test.iterations = iterations; + lock_init (&test.output_lock); + test.output_pos = output; + + /* Start threads. */ + ASSERT (output != NULL); + for (i = 0; i < thread_cnt; i++) + { + struct sleep_thread *t = threads + i; + char name[16]; + + t->test = &test; + t->id = i; + t->duration = 20; + t->iterations = 0; + + snprintf (name, sizeof name, "thread %d", i); + thread_create (name, PRI_DEFAULT, sleeper, t); + } + + /* yield the CPU so that the new threads have sufficient time to go to sleep */ + timer_sleep(10); + + /* now check that all of the threads are indeed asleep */ + + /* Acquire the output lock so we cannot race with the sleeping threads */ + lock_acquire (&test.output_lock); + + /* Inspect and print the number of threads on the ready list . */ + size_t num_ready_threads = threads_ready(); + msg("%d threads on the ready list", num_ready_threads); + + if (num_ready_threads > 0) + fail ("too many threads on the ready_list (they should all be asleep)!"); + + pass (); + lock_release (&test.output_lock); + + /* Wait long enough for all the threads to finish. */ + timer_sleep (100 + thread_cnt * iterations * 20 + 100); + + /* Clean up after ourselves */ + free (output); + free (threads); +} + +/* Sleeper thread. */ +static void +sleeper (void *t_) +{ + struct sleep_thread *t = t_; + struct sleep_test *test = t->test; + int i; + + for (i = 1; i <= test->iterations; i++) + { + int64_t sleep_until = test->start + i * t->duration; + timer_sleep (sleep_until - timer_ticks ()); + lock_acquire (&test->output_lock); + *test->output_pos++ = t->id; + lock_release (&test->output_lock); + } +} diff --git a/tests/devices/src/tests/devices/alarm-no-busy-wait.ck b/tests/devices/src/tests/devices/alarm-no-busy-wait.ck new file mode 100644 index 0000000..ce364d1 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-no-busy-wait.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-no-busy-wait) begin +(alarm-no-busy-wait) Creating 5 threads to sleep 1 times each. +(alarm-no-busy-wait) Each thread sleeps for 20 ticks at a time, +(alarm-no-busy-wait) Test is successful if the threads are not running +(alarm-no-busy-wait) when they are supposed to be asleep. +(alarm-no-busy-wait) 0 threads on the ready list +(alarm-no-busy-wait) PASS +(alarm-no-busy-wait) end +EOF +pass; diff --git a/tests/devices/src/tests/devices/alarm-one.c b/tests/devices/src/tests/devices/alarm-one.c new file mode 100644 index 0000000..e5d62ab --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-one.c @@ -0,0 +1,18 @@ +/* Tests timer_sleep(1), which should return shortly after called. + This test can expose a race-condition if the kernel attempts to unblock + the thread before it has been blocked. + */ + +#include +#include "tests/devices/tests.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_alarm_one (void) +{ + timer_sleep (1); + pass (); +} diff --git a/tests/devices/src/tests/devices/alarm-one.ck b/tests/devices/src/tests/devices/alarm-one.ck new file mode 100644 index 0000000..ed1b950 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-one.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-one) begin +(alarm-one) PASS +(alarm-one) end +EOF +pass; diff --git a/tests/devices/src/tests/devices/alarm-simultaneous.c b/tests/devices/src/tests/devices/alarm-simultaneous.c new file mode 100644 index 0000000..8b97493 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-simultaneous.c @@ -0,0 +1,94 @@ +/* Creates N threads, each of which sleeps a different, fixed + duration, M times. Records the wake-up order and verifies + that it is valid. */ + +#include +#include "tests/devices/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_sleep (int thread_cnt, int iterations); + +void +test_alarm_simultaneous (void) +{ + test_sleep (3, 5); +} + +/* Information about the test. */ +struct sleep_test + { + int64_t start; /* Current time at start of test. */ + int iterations; /* Number of iterations per thread. */ + int *output_pos; /* Current position in output buffer. */ + }; + +static void sleeper (void *); + +/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */ +static void +test_sleep (int thread_cnt, int iterations) +{ + struct sleep_test test; + int *output; + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations); + msg ("Each thread sleeps 10 ticks each time."); + msg ("Within an iteration, all threads should wake up on the same tick."); + + /* Allocate memory. */ + output = malloc (sizeof *output * (int)(iterations * thread_cnt * 2)); + if (output == NULL) + PANIC ("couldn't allocate memory for test"); + + /* Initialize test. */ + test.start = timer_ticks () + 100; + test.iterations = iterations; + test.output_pos = output; + + /* Start threads. */ + ASSERT (output != NULL); + for (i = 0; i < thread_cnt; i++) + { + char name[16]; + snprintf (name, sizeof name, "thread %d", i); + thread_create (name, PRI_DEFAULT, sleeper, &test); + } + + /* Wait long enough for all the threads to finish. */ + timer_sleep (100 + iterations * 10 + 100); + + /* Print completion order. */ + msg ("iteration 0, thread 0: woke up after %d ticks", output[0]); + for (i = 1; i < test.output_pos - output; i++) + msg ("iteration %d, thread %d: woke up %d ticks later", + i / thread_cnt, i % thread_cnt, output[i] - output[i - 1]); + + free (output); +} + +/* Sleeper thread. */ +static void +sleeper (void *test_) +{ + struct sleep_test *test = test_; + int i; + + /* Make sure we're at the beginning of a timer tick. */ + timer_sleep (1); + + for (i = 1; i <= test->iterations; i++) + { + int64_t sleep_until = test->start + i * 10; + timer_sleep (sleep_until - timer_ticks ()); + *test->output_pos++ = (int)(timer_ticks () - test->start); + thread_yield (); + } +} diff --git a/tests/devices/src/tests/devices/alarm-simultaneous.ck b/tests/devices/src/tests/devices/alarm-simultaneous.ck new file mode 100644 index 0000000..406b8b0 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-simultaneous.ck @@ -0,0 +1,27 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-simultaneous) begin +(alarm-simultaneous) Creating 3 threads to sleep 5 times each. +(alarm-simultaneous) Each thread sleeps 10 ticks each time. +(alarm-simultaneous) Within an iteration, all threads should wake up on the same tick. +(alarm-simultaneous) iteration 0, thread 0: woke up after 10 ticks +(alarm-simultaneous) iteration 0, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 0, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 1, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 1, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 1, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 2, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 2, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 2, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 3, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 3, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 3, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 4, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 4, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 4, thread 2: woke up 0 ticks later +(alarm-simultaneous) end +EOF +pass; diff --git a/tests/devices/src/tests/devices/alarm-single.ck b/tests/devices/src/tests/devices/alarm-single.ck new file mode 100644 index 0000000..b8a6c93 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-single.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::devices::alarm; +check_alarm (1); diff --git a/tests/devices/src/tests/devices/alarm-wait.c b/tests/devices/src/tests/devices/alarm-wait.c new file mode 100644 index 0000000..3488373 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-wait.c @@ -0,0 +1,152 @@ +/* Creates N threads, each of which sleeps a different, fixed + duration, M times. Records the wake-up order and verifies + that it is valid. */ + +#include +#include "tests/devices/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_sleep (int thread_cnt, int iterations); + +void +test_alarm_single (void) +{ + test_sleep (5, 1); +} + +void +test_alarm_multiple (void) +{ + test_sleep (5, 7); +} + +/* Information about the test. */ +struct sleep_test + { + int64_t start; /* Current time at start of test. */ + int iterations; /* Number of iterations per thread. */ + + /* Output. */ + struct lock output_lock; /* Lock protecting output buffer. */ + int *output_pos; /* Current position in output buffer. */ + }; + +/* Information about an individual thread in the test. */ +struct sleep_thread + { + struct sleep_test *test; /* Info shared between all threads. */ + int id; /* Sleeper ID. */ + int duration; /* Number of ticks to sleep. */ + int iterations; /* Iterations counted so far. */ + }; + +static void sleeper (void *); + +/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */ +static void +test_sleep (int thread_cnt, int iterations) +{ + struct sleep_test test; + struct sleep_thread *threads; + int *output, *op; + int product; + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations); + msg ("Thread 0 sleeps 10 ticks each time,"); + msg ("thread 1 sleeps 20 ticks each time, and so on."); + msg ("If successful, product of iteration count and"); + msg ("sleep duration will appear in nondescending order."); + + /* Allocate memory. */ + threads = malloc (sizeof *threads * (int)(thread_cnt)); + output = malloc (sizeof *output * (int)(iterations * thread_cnt * 2)); + if (threads == NULL || output == NULL) + PANIC ("couldn't allocate memory for test"); + + /* Initialize test. */ + test.start = timer_ticks () + 100; + test.iterations = iterations; + lock_init (&test.output_lock); + test.output_pos = output; + + /* Start threads. */ + ASSERT (output != NULL); + for (i = 0; i < thread_cnt; i++) + { + struct sleep_thread *t = threads + i; + char name[16]; + + t->test = &test; + t->id = i; + t->duration = (i + 1) * 10; + t->iterations = 0; + + snprintf (name, sizeof name, "thread %d", i); + thread_create (name, PRI_DEFAULT, sleeper, t); + } + + /* Wait long enough for all the threads to finish. */ + timer_sleep (100 + thread_cnt * iterations * 10 + 100); + + /* Acquire the output lock in case some rogue thread is still + running. */ + lock_acquire (&test.output_lock); + + /* Print completion order. */ + product = 0; + for (op = output; op < test.output_pos; op++) + { + struct sleep_thread *t; + int new_prod; + + ASSERT (*op >= 0 && *op < thread_cnt); + t = threads + *op; + + new_prod = ++t->iterations * t->duration; + + msg ("thread %d: duration=%d, iteration=%d, product=%d", + t->id, t->duration, t->iterations, new_prod); + + if (new_prod >= product) + product = new_prod; + else + fail ("thread %d woke up out of order (%d > %d)!", + t->id, product, new_prod); + } + + /* Verify that we had the proper number of wakeups. */ + for (i = 0; i < thread_cnt; i++) + if (threads[i].iterations != iterations) + fail ("thread %d woke up %d times instead of %d", + i, threads[i].iterations, iterations); + + lock_release (&test.output_lock); + free (output); + free (threads); +} + +/* Sleeper thread. */ +static void +sleeper (void *t_) +{ + struct sleep_thread *t = t_; + struct sleep_test *test = t->test; + int i; + + for (i = 1; i <= test->iterations; i++) + { + int64_t sleep_until = test->start + i * t->duration; + timer_sleep (sleep_until - timer_ticks ()); + lock_acquire (&test->output_lock); + *test->output_pos++ = t->id; + lock_release (&test->output_lock); + } +} diff --git a/tests/devices/src/tests/devices/alarm-zero.c b/tests/devices/src/tests/devices/alarm-zero.c new file mode 100644 index 0000000..bec1072 --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-zero.c @@ -0,0 +1,15 @@ +/* Tests timer_sleep(0), which should return immediately. */ + +#include +#include "tests/devices/tests.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_alarm_zero (void) +{ + timer_sleep (0); + pass (); +} diff --git a/tests/devices/src/tests/devices/alarm-zero.ck b/tests/devices/src/tests/devices/alarm-zero.ck new file mode 100644 index 0000000..a6b1a3c --- /dev/null +++ b/tests/devices/src/tests/devices/alarm-zero.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-zero) begin +(alarm-zero) PASS +(alarm-zero) end +EOF +pass; diff --git a/tests/devices/src/tests/devices/alarm.pm b/tests/devices/src/tests/devices/alarm.pm new file mode 100644 index 0000000..5150fbe --- /dev/null +++ b/tests/devices/src/tests/devices/alarm.pm @@ -0,0 +1,32 @@ +sub check_alarm { + my ($iterations) = @_; + our ($test); + + @output = read_text_file ("$test.output"); + common_checks ("run", @output); + + my (@products); + for (my ($i) = 0; $i < $iterations; $i++) { + for (my ($t) = 0; $t < 5; $t++) { + push (@products, ($i + 1) * ($t + 1) * 10); + } + } + @products = sort {$a <=> $b} @products; + + local ($_); + foreach (@output) { + fail $_ if /out of order/i; + + my ($p) = /product=(\d+)$/; + next if !defined $p; + + my ($q) = shift (@products); + fail "Too many wakeups.\n" if !defined $q; + fail "Out of order wakeups ($p vs. $q).\n" if $p != $q; + } + fail scalar (@products) . " fewer wakeups than expected.\n" + if @products != 0; + pass; +} + +1; diff --git a/tests/devices/src/tests/devices/tests.c b/tests/devices/src/tests/devices/tests.c new file mode 100644 index 0000000..a7451fd --- /dev/null +++ b/tests/devices/src/tests/devices/tests.c @@ -0,0 +1,117 @@ +#include "tests/devices/tests.h" +#include +#include +#include + +struct test + { + const char *name; + test_func *function; + }; + +#ifndef THREADS +static const struct test tests[] = + { + {"alarm-single", test_alarm_single}, + {"alarm-multiple", test_alarm_multiple}, + {"alarm-simultaneous", test_alarm_simultaneous}, + {"alarm-no-busy-wait", test_alarm_no_busy_wait}, + {"alarm-one", test_alarm_one}, + {"alarm-zero", test_alarm_zero}, + {"alarm-negative", test_alarm_negative} + }; +#else +static const struct test tests[] = + { + {"alarm-single", test_alarm_single}, + {"alarm-multiple", test_alarm_multiple}, + {"alarm-simultaneous", test_alarm_simultaneous}, + {"alarm-no-busy-wait", test_alarm_no_busy_wait}, + {"alarm-one", test_alarm_one}, + {"alarm-zero", test_alarm_zero}, + {"alarm-negative", test_alarm_negative}, + {"alarm-priority", test_alarm_priority}, + {"priority-change", test_priority_change}, + {"priority-donate-one", test_priority_donate_one}, + {"priority-donate-multiple", test_priority_donate_multiple}, + {"priority-donate-multiple2", test_priority_donate_multiple2}, + {"priority-donate-nest", test_priority_donate_nest}, + {"priority-donate-sema", test_priority_donate_sema}, + {"priority-donate-lower", test_priority_donate_lower}, + {"priority-donate-chain", test_priority_donate_chain}, + {"priority-preservation", test_priority_preservation}, + {"priority-fifo", test_priority_fifo}, + {"priority-preempt", test_priority_preempt}, + {"priority-sema", test_priority_sema}, + {"priority-condvar", test_priority_condvar}, + {"mlfqs-load-1", test_mlfqs_load_1}, + {"mlfqs-load-60", test_mlfqs_load_60}, + {"mlfqs-load-avg", test_mlfqs_load_avg}, + {"mlfqs-recent-1", test_mlfqs_recent_1}, + {"mlfqs-fair-2", test_mlfqs_fair_2}, + {"mlfqs-fair-20", test_mlfqs_fair_20}, + {"mlfqs-nice-2", test_mlfqs_nice_2}, + {"mlfqs-nice-10", test_mlfqs_nice_10}, + {"mlfqs-block", test_mlfqs_block}, + }; +#endif + +static const char *test_name; + +/* Runs the test named NAME. */ +void +run_test (const char *name) +{ + const struct test *t; + + for (t = tests; t < tests + sizeof tests / sizeof *tests; t++) + if (!strcmp (name, t->name)) + { + test_name = name; + msg ("begin"); + t->function (); + msg ("end"); + return; + } + PANIC ("no test named \"%s\"", name); +} + +/* Prints FORMAT as if with printf(), + prefixing the output by the name of the test + and following it with a new-line character. */ +void +msg (const char *format, ...) +{ + va_list args; + + printf ("(%s) ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); +} + +/* Prints failure message FORMAT as if with printf(), + prefixing the output by the name of the test and FAIL: + and following it with a new-line character, + and then panics the kernel. */ +void +fail (const char *format, ...) +{ + va_list args; + + printf ("(%s) FAIL: ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); + + PANIC ("test failed"); +} + +/* Prints a message indicating the current test passed. */ +void +pass (void) +{ + printf ("(%s) PASS\n", test_name); +} diff --git a/tests/devices/src/tests/devices/tests.h b/tests/devices/src/tests/devices/tests.h new file mode 100644 index 0000000..53af97d --- /dev/null +++ b/tests/devices/src/tests/devices/tests.h @@ -0,0 +1,47 @@ +#ifndef TESTS_DEVICES_TESTS_H +#define TESTS_DEVICES_TESTS_H + +void run_test (const char *); + +typedef void test_func (void); + +extern test_func test_alarm_single; +extern test_func test_alarm_multiple; +extern test_func test_alarm_simultaneous; +extern test_func test_alarm_no_busy_wait; +extern test_func test_alarm_one; +extern test_func test_alarm_zero; +extern test_func test_alarm_negative; + +#ifdef THREADS +extern test_func test_alarm_priority; +extern test_func test_priority_change; +extern test_func test_priority_donate_one; +extern test_func test_priority_donate_multiple; +extern test_func test_priority_donate_multiple2; +extern test_func test_priority_donate_sema; +extern test_func test_priority_donate_nest; +extern test_func test_priority_donate_lower; +extern test_func test_priority_donate_chain; +extern test_func test_priority_preservation; +extern test_func test_priority_fifo; +extern test_func test_priority_preempt; +extern test_func test_priority_sema; +extern test_func test_priority_condvar; +extern test_func test_mlfqs_load_1; +extern test_func test_mlfqs_load_60; +extern test_func test_mlfqs_load_avg; +extern test_func test_mlfqs_recent_1; +extern test_func test_mlfqs_fair_2; +extern test_func test_mlfqs_fair_20; +extern test_func test_mlfqs_nice_2; +extern test_func test_mlfqs_nice_10; +extern test_func test_mlfqs_block; +#endif + +void msg (const char *, ...); +void fail (const char *, ...); +void pass (void); + +#endif /* tests/devices/tests.h */ + diff --git a/tests/devices/src/tests/internal/list.c b/tests/devices/src/tests/internal/list.c new file mode 100644 index 0000000..c4bc50e --- /dev/null +++ b/tests/devices/src/tests/internal/list.c @@ -0,0 +1,174 @@ +/* Test program for lib/kernel/list.c. + + Attempts to test the list functionality that is not + sufficiently tested elsewhere in PintOS. + + This is not a test we will run on your submitted tasks. + It is here for completeness. +*/ + +#undef NDEBUG +#include +#include +#include +#include +#include "threads/test.h" + +/* Maximum number of elements in a linked list that we will + test. */ +#define MAX_SIZE 64 + +/* A linked list element. */ +struct value + { + struct list_elem elem; /* List element. */ + int value; /* Item value. */ + }; + +static void shuffle (struct value[], size_t); +static bool value_less (const struct list_elem *, const struct list_elem *, + void *); +static void verify_list_fwd (struct list *, int size); +static void verify_list_bkwd (struct list *, int size); + +/* Test the linked list implementation. */ +void +test (void) +{ + int size; + + printf ("testing various size lists:"); + for (size = 0; size < MAX_SIZE; size++) + { + int repeat; + + printf (" %d", size); + for (repeat = 0; repeat < 10; repeat++) + { + static struct value values[MAX_SIZE * 4]; + struct list list; + struct list_elem *e; + int i, ofs; + + /* Put values 0...SIZE in random order in VALUES. */ + for (i = 0; i < size; i++) + values[i].value = i; + shuffle (values, size); + + /* Assemble list. */ + list_init (&list); + for (i = 0; i < size; i++) + list_push_back (&list, &values[i].elem); + + /* Verify correct minimum and maximum elements. */ + e = list_min (&list, value_less, NULL); + ASSERT (size ? list_entry (e, struct value, elem)->value == 0 + : e == list_begin (&list)); + e = list_max (&list, value_less, NULL); + ASSERT (size ? list_entry (e, struct value, elem)->value == size - 1 + : e == list_begin (&list)); + + /* Sort and verify list. */ + list_sort (&list, value_less, NULL); + verify_list_fwd (&list, size); + + /* Reverse and verify list. */ + list_reverse (&list); + verify_list_bkwd (&list, size); + + /* Shuffle, insert using list_insert_ordered(), + and verify ordering. */ + shuffle (values, size); + list_init (&list); + for (i = 0; i < size; i++) + list_insert_ordered (&list, &values[i].elem, + value_less, NULL); + verify_list_fwd (&list, size); + + /* Duplicate some items, uniquify, and verify. */ + ofs = size; + for (e = list_begin (&list); e != list_end (&list); + e = list_next (e)) + { + struct value *v = list_entry (e, struct value, elem); + int copies = random_ulong () % 4; + while (copies-- > 0) + { + values[ofs].value = v->value; + list_insert (e, &values[ofs++].elem); + } + } + ASSERT ((size_t) ofs < sizeof values / sizeof *values); + list_unique (&list, NULL, value_less, NULL); + verify_list_fwd (&list, size); + } + } + + printf (" done\n"); + printf ("list: PASS\n"); +} + +/* Shuffles the CNT elements in ARRAY into random order. */ +static void +shuffle (struct value *array, size_t cnt) +{ + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + struct value t = array[j]; + array[j] = array[i]; + array[i] = t; + } +} + +/* Returns true if value A is less than value B, false + otherwise. */ +static bool +value_less (const struct list_elem *a_, const struct list_elem *b_, + void *aux UNUSED) +{ + const struct value *a = list_entry (a_, struct value, elem); + const struct value *b = list_entry (b_, struct value, elem); + + return a->value < b->value; +} + +/* Verifies that LIST contains the values 0...SIZE when traversed + in forward order. */ +static void +verify_list_fwd (struct list *list, int size) +{ + struct list_elem *e; + int i; + + for (i = 0, e = list_begin (list); + i < size && e != list_end (list); + i++, e = list_next (e)) + { + struct value *v = list_entry (e, struct value, elem); + ASSERT (i == v->value); + } + ASSERT (i == size); + ASSERT (e == list_end (list)); +} + +/* Verifies that LIST contains the values 0...SIZE when traversed + in reverse order. */ +static void +verify_list_bkwd (struct list *list, int size) +{ + struct list_elem *e; + int i; + + for (i = 0, e = list_rbegin (list); + i < size && e != list_rend (list); + i++, e = list_prev (e)) + { + struct value *v = list_entry (e, struct value, elem); + ASSERT (i == v->value); + } + ASSERT (i == size); + ASSERT (e == list_rend (list)); +} diff --git a/tests/devices/src/tests/internal/stdio.c b/tests/devices/src/tests/internal/stdio.c new file mode 100644 index 0000000..d2a566e --- /dev/null +++ b/tests/devices/src/tests/internal/stdio.c @@ -0,0 +1,208 @@ +/* Test program for printf() in lib/stdio.c. + + Attempts to test printf() functionality that is not + sufficiently tested elsewhere in PintOS. + + This is not a test we will run on your submitted tasks. + It is here for completeness. +*/ + +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include "threads/test.h" + +/* Number of failures so far. */ +static int failure_cnt; + +static void +checkf (const char *expect, const char *format, ...) +{ + char output[128]; + va_list args; + + printf ("\"%s\" -> \"%s\": ", format, expect); + + va_start (args, format); + vsnprintf (output, sizeof output, format, args); + va_end (args); + + if (strcmp (expect, output)) + { + printf ("\nFAIL: actual output \"%s\"\n", output); + failure_cnt++; + } + else + printf ("okay\n"); +} + +/* Test printf() implementation. */ +void +test (void) +{ + printf ("Testing formats:"); + + /* Check that commas show up in the right places, for positive + numbers. */ + checkf ("1", "%'d", 1); + checkf ("12", "%'d", 12); + checkf ("123", "%'d", 123); + checkf ("1,234", "%'d", 1234); + checkf ("12,345", "%'d", 12345); + checkf ("123,456", "%'ld", 123456L); + checkf ("1,234,567", "%'ld", 1234567L); + checkf ("12,345,678", "%'ld", 12345678L); + checkf ("123,456,789", "%'ld", 123456789L); + checkf ("1,234,567,890", "%'ld", 1234567890L); + checkf ("12,345,678,901", "%'lld", 12345678901LL); + checkf ("123,456,789,012", "%'lld", 123456789012LL); + checkf ("1,234,567,890,123", "%'lld", 1234567890123LL); + checkf ("12,345,678,901,234", "%'lld", 12345678901234LL); + checkf ("123,456,789,012,345", "%'lld", 123456789012345LL); + checkf ("1,234,567,890,123,456", "%'lld", 1234567890123456LL); + checkf ("12,345,678,901,234,567", "%'lld", 12345678901234567LL); + checkf ("123,456,789,012,345,678", "%'lld", 123456789012345678LL); + checkf ("1,234,567,890,123,456,789", "%'lld", 1234567890123456789LL); + + /* Check that commas show up in the right places, for positive + numbers. */ + checkf ("-1", "%'d", -1); + checkf ("-12", "%'d", -12); + checkf ("-123", "%'d", -123); + checkf ("-1,234", "%'d", -1234); + checkf ("-12,345", "%'d", -12345); + checkf ("-123,456", "%'ld", -123456L); + checkf ("-1,234,567", "%'ld", -1234567L); + checkf ("-12,345,678", "%'ld", -12345678L); + checkf ("-123,456,789", "%'ld", -123456789L); + checkf ("-1,234,567,890", "%'ld", -1234567890L); + checkf ("-12,345,678,901", "%'lld", -12345678901LL); + checkf ("-123,456,789,012", "%'lld", -123456789012LL); + checkf ("-1,234,567,890,123", "%'lld", -1234567890123LL); + checkf ("-12,345,678,901,234", "%'lld", -12345678901234LL); + checkf ("-123,456,789,012,345", "%'lld", -123456789012345LL); + checkf ("-1,234,567,890,123,456", "%'lld", -1234567890123456LL); + checkf ("-12,345,678,901,234,567", "%'lld", -12345678901234567LL); + checkf ("-123,456,789,012,345,678", "%'lld", -123456789012345678LL); + checkf ("-1,234,567,890,123,456,789", "%'lld", -1234567890123456789LL); + + /* Check signed integer conversions. */ + checkf (" 0", "%5d", 0); + checkf ("0 ", "%-5d", 0); + checkf (" +0", "%+5d", 0); + checkf ("+0 ", "%+-5d", 0); + checkf (" 0", "% 5d", 0); + checkf ("00000", "%05d", 0); + checkf (" ", "%5.0d", 0); + checkf (" 00", "%5.2d", 0); + checkf ("0", "%d", 0); + + checkf (" 1", "%5d", 1); + checkf ("1 ", "%-5d", 1); + checkf (" +1", "%+5d", 1); + checkf ("+1 ", "%+-5d", 1); + checkf (" 1", "% 5d", 1); + checkf ("00001", "%05d", 1); + checkf (" 1", "%5.0d", 1); + checkf (" 01", "%5.2d", 1); + checkf ("1", "%d", 1); + + checkf (" -1", "%5d", -1); + checkf ("-1 ", "%-5d", -1); + checkf (" -1", "%+5d", -1); + checkf ("-1 ", "%+-5d", -1); + checkf (" -1", "% 5d", -1); + checkf ("-0001", "%05d", -1); + checkf (" -1", "%5.0d", -1); + checkf (" -01", "%5.2d", -1); + checkf ("-1", "%d", -1); + + checkf ("12345", "%5d", 12345); + checkf ("12345", "%-5d", 12345); + checkf ("+12345", "%+5d", 12345); + checkf ("+12345", "%+-5d", 12345); + checkf (" 12345", "% 5d", 12345); + checkf ("12345", "%05d", 12345); + checkf ("12345", "%5.0d", 12345); + checkf ("12345", "%5.2d", 12345); + checkf ("12345", "%d", 12345); + + checkf ("123456", "%5d", 123456); + checkf ("123456", "%-5d", 123456); + checkf ("+123456", "%+5d", 123456); + checkf ("+123456", "%+-5d", 123456); + checkf (" 123456", "% 5d", 123456); + checkf ("123456", "%05d", 123456); + checkf ("123456", "%5.0d", 123456); + checkf ("123456", "%5.2d", 123456); + checkf ("123456", "%d", 123456); + + /* Check unsigned integer conversions. */ + checkf (" 0", "%5u", 0); + checkf (" 0", "%5o", 0); + checkf (" 0", "%5x", 0); + checkf (" 0", "%5X", 0); + checkf (" 0", "%#5o", 0); + checkf (" 0", "%#5x", 0); + checkf (" 0", "%#5X", 0); + checkf (" 00000000", "%#10.8x", 0); + + checkf (" 1", "%5u", 1); + checkf (" 1", "%5o", 1); + checkf (" 1", "%5x", 1); + checkf (" 1", "%5X", 1); + checkf (" 01", "%#5o", 1); + checkf (" 0x1", "%#5x", 1); + checkf (" 0X1", "%#5X", 1); + checkf ("0x00000001", "%#10.8x", 1); + + checkf ("123456", "%5u", 123456); + checkf ("361100", "%5o", 123456); + checkf ("1e240", "%5x", 123456); + checkf ("1E240", "%5X", 123456); + checkf ("0361100", "%#5o", 123456); + checkf ("0x1e240", "%#5x", 123456); + checkf ("0X1E240", "%#5X", 123456); + checkf ("0x0001e240", "%#10.8x", 123456); + + /* Character and string conversions. */ + checkf ("foobar", "%c%c%c%c%c%c", 'f', 'o', 'o', 'b', 'a', 'r'); + checkf (" left-right ", "%6s%s%-7s", "left", "-", "right"); + checkf ("trim", "%.4s", "trimoff"); + checkf ("%%", "%%%%"); + + /* From Cristian Cadar's automatic test case generator. */ + checkf (" abcdefgh", "%9s", "abcdefgh"); + checkf ("36657730000", "%- o", (unsigned) 036657730000); + checkf ("4139757568", "%- u", (unsigned) 4139757568UL); + checkf ("f6bfb000", "%- x", (unsigned) 0xf6bfb000); + checkf ("36657730000", "%-to", (ptrdiff_t) 036657730000); + checkf ("4139757568", "%-tu", (ptrdiff_t) 4139757568UL); + checkf ("-155209728", "%-zi", (size_t) -155209728); + checkf ("-155209728", "%-zd", (size_t) -155209728); + checkf ("036657730000", "%+#o", (unsigned) 036657730000); + checkf ("0xf6bfb000", "%+#x", (unsigned) 0xf6bfb000); + checkf ("-155209728", "% zi", (size_t) -155209728); + checkf ("-155209728", "% zd", (size_t) -155209728); + checkf ("4139757568", "% tu", (ptrdiff_t) 4139757568UL); + checkf ("036657730000", "% #o", (unsigned) 036657730000); + checkf ("0xf6bfb000", "% #x", (unsigned) 0xf6bfb000); + checkf ("0xf6bfb000", "%# x", (unsigned) 0xf6bfb000); + checkf ("-155209728", "%#zd", (size_t) -155209728); + checkf ("-155209728", "%0zi", (size_t) -155209728); + checkf ("4,139,757,568", "%'tu", (ptrdiff_t) 4139757568UL); + checkf ("-155,209,728", "%-'d", -155209728); + checkf ("-155209728", "%.zi", (size_t) -155209728); + checkf ("-155209728", "%zi", (size_t) -155209728); + checkf ("-155209728", "%zd", (size_t) -155209728); + checkf ("-155209728", "%+zi", (size_t) -155209728); + + if (failure_cnt == 0) + printf ("\nstdio: PASS\n"); + else + printf ("\nstdio: FAIL: %d tests failed\n", failure_cnt); +} diff --git a/tests/devices/src/tests/internal/stdlib.c b/tests/devices/src/tests/internal/stdlib.c new file mode 100644 index 0000000..c1212a5 --- /dev/null +++ b/tests/devices/src/tests/internal/stdlib.c @@ -0,0 +1,114 @@ +/* Test program for sorting and searching in lib/stdlib.c. + + Attempts to test the sorting and searching functionality that + is not sufficiently tested elsewhere in PintOS. + + This is not a test we will run on your submitted tasks. + It is here for completeness. +*/ + +#undef NDEBUG +#include +#include +#include +#include +#include +#include "threads/test.h" + +/* Maximum number of elements in an array that we will test. */ +#define MAX_CNT 4096 + +static void shuffle (int[], size_t); +static int compare_ints (const void *, const void *); +static void verify_order (const int[], size_t); +static void verify_bsearch (const int[], size_t); + +/* Test sorting and searching implementations. */ +void +test (void) +{ + int cnt; + + printf ("testing various size arrays:"); + for (cnt = 0; cnt < MAX_CNT; cnt = cnt * 4 / 3 + 1) + { + int repeat; + + printf (" %zu", cnt); + for (repeat = 0; repeat < 10; repeat++) + { + static int values[MAX_CNT]; + int i; + + /* Put values 0...CNT in random order in VALUES. */ + for (i = 0; i < cnt; i++) + values[i] = i; + shuffle (values, cnt); + + /* Sort VALUES, then verify ordering. */ + qsort (values, cnt, sizeof *values, compare_ints); + verify_order (values, cnt); + verify_bsearch (values, cnt); + } + } + + printf (" done\n"); + printf ("stdlib: PASS\n"); +} + +/* Shuffles the CNT elements in ARRAY into random order. */ +static void +shuffle (int *array, size_t cnt) +{ + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + int t = array[j]; + array[j] = array[i]; + array[i] = t; + } +} + +/* Returns 1 if *A is greater than *B, + 0 if *A equals *B, + -1 if *A is less than *B. */ +static int +compare_ints (const void *a_, const void *b_) +{ + const int *a = a_; + const int *b = b_; + + return *a < *b ? -1 : *a > *b; +} + +/* Verifies that ARRAY contains the CNT ints 0...CNT-1. */ +static void +verify_order (const int *array, size_t cnt) +{ + int i; + + for (i = 0; (size_t) i < cnt; i++) + ASSERT (array[i] == i); +} + +/* Checks that bsearch() works properly in ARRAY. ARRAY must + contain the values 0...CNT-1. */ +static void +verify_bsearch (const int *array, size_t cnt) +{ + int not_in_array[] = {0, -1, INT_MAX, MAX_CNT, MAX_CNT + 1, MAX_CNT * 2}; + int i; + + /* Check that all the values in the array are found properly. */ + for (i = 0; (size_t) i < cnt; i++) + ASSERT (bsearch (&i, array, cnt, sizeof *array, compare_ints) + == array + i); + + /* Check that some values not in the array are not found. */ + not_in_array[0] = cnt; + for (i = 0; (size_t) i < sizeof not_in_array / sizeof *not_in_array; i++) + ASSERT (bsearch (¬_in_array[i], array, cnt, sizeof *array, compare_ints) + == NULL); +} diff --git a/tests/devices/src/tests/lib.c b/tests/devices/src/tests/lib.c new file mode 100644 index 0000000..7f7935b --- /dev/null +++ b/tests/devices/src/tests/lib.c @@ -0,0 +1,196 @@ +#include "tests/lib.h" +#include +#include +#include +#include +#include + +const char *test_name; +bool quiet = false; + +static void +vmsg (const char *format, va_list args, const char *suffix) +{ + /* We go to some trouble to stuff the entire message into a + single buffer and output it in a single system call, because + that'll (typically) ensure that it gets sent to the console + atomically. Otherwise kernel messages like "foo: exit(0)" + can end up being interleaved if we're unlucky. */ + static char buf[1024]; + + snprintf (buf, sizeof buf, "(%s) ", test_name); + vsnprintf (buf + strlen (buf), sizeof buf - strlen (buf), format, args); + strlcpy (buf + strlen (buf), suffix, sizeof buf - strlen (buf)); + write (STDOUT_FILENO, buf, strlen (buf)); +} + +void +msg (const char *format, ...) +{ + va_list args; + + if (quiet) + return; + va_start (args, format); + vmsg (format, args, "\n"); + va_end (args); +} + +void +fail (const char *format, ...) +{ + va_list args; + + va_start (args, format); + vmsg (format, args, ": FAILED\n"); + va_end (args); + + exit (1); +} + +static void +swap (void *a_, void *b_, size_t size) +{ + uint8_t *a = a_; + uint8_t *b = b_; + size_t i; + + for (i = 0; i < size; i++) + { + uint8_t t = a[i]; + a[i] = b[i]; + b[i] = t; + } +} + +void +shuffle (void *buf_, size_t cnt, size_t size) +{ + char *buf = buf_; + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + swap (buf + i * size, buf + j * size, size); + } +} + +void +exec_children (const char *child_name, pid_t pids[], size_t child_cnt) +{ + size_t i; + + for (i = 0; i < child_cnt; i++) + { + char cmd_line[128]; + snprintf (cmd_line, sizeof cmd_line, "%s %zu", child_name, i); + CHECK ((pids[i] = exec (cmd_line)) != PID_ERROR, + "exec child %zu of %zu: \"%s\"", i + 1, child_cnt, cmd_line); + } +} + +void +wait_children (pid_t pids[], size_t child_cnt) +{ + size_t i; + + for (i = 0; i < child_cnt; i++) + { + int status = wait (pids[i]); + CHECK (status == (int) i, + "wait for child %zu of %zu returned %d (expected %zu)", + i + 1, child_cnt, status, i); + } +} + +void +check_file_handle (int fd, + const char *file_name, const void *buf_, size_t size) +{ + const char *buf = buf_; + size_t ofs = 0; + size_t file_size; + + /* Warn about file of wrong size. Don't fail yet because we + may still be able to get more information by reading the + file. */ + file_size = (size_t) filesize (fd); + if (file_size != size) + msg ("size of %s (%zu) differs from expected (%zu)", + file_name, file_size, size); + + /* Read the file block-by-block, comparing data as we go. */ + while (ofs < size) + { + char block[512]; + size_t block_size, ret_val; + + block_size = size - ofs; + if (block_size > sizeof block) + block_size = sizeof block; + + ret_val = (size_t) read (fd, block, block_size); + if (ret_val != block_size) + fail ("read of %zu bytes at offset %zu in \"%s\" returned %zu", + block_size, ofs, file_name, ret_val); + + compare_bytes (block, buf + ofs, block_size, ofs, file_name); + ofs += block_size; + } + + /* Now fail due to wrong file size. */ + if (file_size != size) + fail ("size of %s (%zu) differs from expected (%zu)", + file_name, file_size, size); + + msg ("verified contents of \"%s\"", file_name); +} + +void +check_file (const char *file_name, const void *buf, size_t size) +{ + int fd; + + CHECK ((fd = open (file_name)) > 1, "open \"%s\" for verification", + file_name); + check_file_handle (fd, file_name, buf, size); + msg ("close \"%s\"", file_name); + close (fd); +} + +void +compare_bytes (const void *read_data_, const void *expected_data_, size_t size, + size_t ofs, const char *file_name) +{ + const uint8_t *read_data = read_data_; + const uint8_t *expected_data = expected_data_; + size_t i, j; + size_t show_cnt; + + if (!memcmp (read_data, expected_data, size)) + return; + + for (i = 0; i < size; i++) + if (read_data[i] != expected_data[i]) + break; + for (j = i + 1; j < size; j++) + if (read_data[j] == expected_data[j]) + break; + + quiet = false; + msg ("%zu bytes read starting at offset %zu in \"%s\" differ " + "from expected.", j - i, ofs + i, file_name); + show_cnt = j - i; + if (j - i > 64) + { + show_cnt = 64; + msg ("Showing first differing %zu bytes.", show_cnt); + } + msg ("Data actually read:"); + hex_dump (ofs + i, read_data + i, show_cnt, true); + msg ("Expected data:"); + hex_dump (ofs + i, expected_data + i, show_cnt, true); + fail ("%zu bytes read starting at offset %zu in \"%s\" differ " + "from expected", j - i, ofs + i, file_name); +} diff --git a/tests/devices/src/tests/lib.h b/tests/devices/src/tests/lib.h new file mode 100644 index 0000000..648327b --- /dev/null +++ b/tests/devices/src/tests/lib.h @@ -0,0 +1,50 @@ +#ifndef TESTS_LIB_H +#define TESTS_LIB_H + +#include +#include +#include +#include + +extern const char *test_name; +extern bool quiet; + +void msg (const char *, ...) PRINTF_FORMAT (1, 2); +void fail (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN; + +/* Takes an expression to test for SUCCESS and a message, which + may include printf-style arguments. Logs the message, then + tests the expression. If it is zero, indicating failure, + emits the message as a failure. + + Somewhat tricky to use: + + - SUCCESS must not have side effects that affect the + message, because that will cause the original message and + the failure message to differ. + + - The message must not have side effects of its own, because + it will be printed twice on failure, or zero times on + success if quiet is set. */ +#define CHECK(SUCCESS, ...) \ + do \ + { \ + msg (__VA_ARGS__); \ + if (!(SUCCESS)) \ + fail (__VA_ARGS__); \ + } \ + while (0) + +void shuffle (void *, size_t cnt, size_t size); + +void exec_children (const char *child_name, pid_t pids[], size_t child_cnt); +void wait_children (pid_t pids[], size_t child_cnt); + +void check_file_handle (int fd, const char *file_name, + const void *buf_, size_t filesize); +void check_file (const char *file_name, const void *buf, size_t filesize); + +void compare_bytes (const void *read_data, const void *expected_data, + size_t size, size_t ofs, const char *file_name); + +#endif /* test/lib.h */ diff --git a/tests/devices/src/tests/lib.pm b/tests/devices/src/tests/lib.pm new file mode 100644 index 0000000..bc37ae5 --- /dev/null +++ b/tests/devices/src/tests/lib.pm @@ -0,0 +1,19 @@ +use strict; +use warnings; + +use tests::random; + +sub shuffle { + my ($in, $cnt, $sz) = @_; + $cnt * $sz == length $in or die; + my (@a) = 0...$cnt - 1; + for my $i (0...$cnt - 1) { + my ($j) = $i + random_ulong () % ($cnt - $i); + @a[$i, $j] = @a[$j, $i]; + } + my ($out) = ""; + $out .= substr ($in, $_ * $sz, $sz) foreach @a; + return $out; +} + +1; diff --git a/tests/devices/src/tests/main.c b/tests/devices/src/tests/main.c new file mode 100644 index 0000000..ad1b0f1 --- /dev/null +++ b/tests/devices/src/tests/main.c @@ -0,0 +1,15 @@ +#include +#include "tests/lib.h" +#include "tests/main.h" + +int +main (int argc UNUSED, char *argv[]) +{ + test_name = argv[0]; + + msg ("begin"); + random_init (0); + test_main (); + msg ("end"); + return 0; +} diff --git a/tests/devices/src/tests/main.h b/tests/devices/src/tests/main.h new file mode 100644 index 0000000..f0e8818 --- /dev/null +++ b/tests/devices/src/tests/main.h @@ -0,0 +1,6 @@ +#ifndef TESTS_MAIN_H +#define TESTS_MAIN_H + +void test_main (void); + +#endif /* tests/main.h */ diff --git a/tests/devices/src/tests/make-grade b/tests/devices/src/tests/make-grade new file mode 100755 index 0000000..e8ec1d5 --- /dev/null +++ b/tests/devices/src/tests/make-grade @@ -0,0 +1,152 @@ +#! /usr/bin/perl + +use strict; +use warnings; + +@ARGV == 3 || die; +my ($src_dir, $results_file, $grading_file) = @ARGV; + +# Read pass/file verdicts from $results_file. +open (RESULTS, '<', $results_file) || die "$results_file: open: $!\n"; +my (%verdicts, %verdict_counts); +while () { + my ($verdict, $test) = /^(pass|FAIL) (.*)$/ or die; + $verdicts{$test} = $verdict eq 'pass'; +} +close RESULTS; + +my (@failures); +my (@overall, @rubrics, @summary); +my ($pct_actual, $pct_possible) = (0, 0); + +# Read grading file. +my (@items); +open (GRADING, '<', $grading_file) || die "$grading_file: open: $!\n"; +while () { + s/#.*//; + next if /^\s*$/; + my ($max_pct, $rubric_suffix) = /^\s*(\d+(?:\.\d+)?)%\t(.*)/ or die; + my ($dir) = $rubric_suffix =~ /^(.*)\//; + my ($rubric_file) = "$src_dir/$rubric_suffix"; + open (RUBRIC, '<', $rubric_file) or die "$rubric_file: open: $!\n"; + + # Rubric file must begin with title line. + my $title = ; + chomp $title; + $title =~ s/:$// or die; + $title .= " ($rubric_suffix):"; + push (@rubrics, $title); + + my ($score, $possible) = (0, 0); + my ($cnt, $passed) = (0, 0); + my ($was_score) = 0; + while () { + chomp; + push (@rubrics, "\t$_"), next if /^-/; + push (@rubrics, ""), next if /^\s*$/; + my ($poss, $name) = /^(\d+)\t(.*)$/ or die; + my ($test) = "$dir/$name"; + my ($points) = 0; + if (!defined $verdicts{$test}) { + push (@overall, "warning: $test not tested, assuming failure"); + } elsif ($verdicts{$test}) { + $points = $poss; + $passed++; + } + push (@failures, $test) if !$points; + $verdict_counts{$test}++; + push (@rubrics, sprintf ("\t%4s%2d/%2d %s", + $points ? '' : '**', $points, $poss, $test)); + $score += $points; + $possible += $poss; + $cnt++; + } + close (RUBRIC); + + push (@rubrics, ""); + push (@rubrics, "\t- Section summary."); + push (@rubrics, sprintf ("\t%4s%3d/%3d %s", + '', $passed, $cnt, 'tests passed')); + push (@rubrics, sprintf ("\t%4s%3d/%3d %s", + '', $score, $possible, 'points subtotal')); + push (@rubrics, ''); + + my ($pct) = ($score / $possible) * $max_pct; + push (@summary, sprintf ("%-45s %3d/%3d %5.1f%%/%5.1f%%", + $rubric_suffix, + $score, $possible, + $pct, $max_pct)); + $pct_actual += $pct; + $pct_possible += $max_pct; +} +close GRADING; + +my ($sum_line) + = "--------------------------------------------- --- --- ------ ------"; +unshift (@summary, + "SUMMARY BY TEST SET", + '', + sprintf ("%-45s %3s %3s %6s %6s", + "Test Set", "Pts", "Max", "% Ttl", "% Max"), + $sum_line); +push (@summary, + $sum_line, + sprintf ("%-45s %3s %3s %5.1f%%/%5.1f%%", + 'Total', '', '', $pct_actual, $pct_possible)); + +unshift (@rubrics, + "SUMMARY OF INDIVIDUAL TESTS", + ''); + +foreach my $name (keys (%verdicts)) { + my ($count) = $verdict_counts{$name}; + if (!defined ($count) || $count != 1) { + if (!defined ($count) || !$count) { + push (@overall, "warning: test $name doesn't count for grading"); + } else { + push (@overall, + "warning: test $name counted $count times in grading"); + } + } +} +push (@overall, sprintf ("TOTAL TESTING SCORE: %.1f%%", $pct_actual)); +if (sprintf ("%.1f", $pct_actual) eq sprintf ("%.1f", $pct_possible)) { + push (@overall, "ALL TESTED PASSED -- PERFECT SCORE"); +} + +my (@divider) = ('', '- ' x 38, ''); + +print map ("$_\n", @overall, @divider, @summary, @divider, @rubrics); + +for my $test (@failures) { + print map ("$_\n", @divider); + print "DETAILS OF $test FAILURE:\n\n"; + + if (open (RESULT, '<', "$test.result")) { + my $first_line = ; + my ($cnt) = 0; + while () { + print; + $cnt++; + } + close (RESULT); + } + + if (open (OUTPUT, '<', "$test.output")) { + print "\nOUTPUT FROM $test:\n\n"; + + my ($panics, $boots) = (0, 0); + while () { + if (/PANIC/ && ++$panics > 2) { + print "[...details of additional panic(s) omitted...]\n"; + last; + } + print; + if (/PintOS booting/ && ++$boots > 1) { + print "[...details of reboot(s) omitted...]\n"; + last; + } + } + close (OUTPUT); + } +} diff --git a/tests/devices/src/tests/random.pm b/tests/devices/src/tests/random.pm new file mode 100644 index 0000000..be008ff --- /dev/null +++ b/tests/devices/src/tests/random.pm @@ -0,0 +1,27 @@ +use strict; +use warnings; + +use tests::arc4; + +my (@arc4); + +sub random_init { + if (@arc4 == 0) { + my ($seed) = @_; + $seed = 0 if !defined $seed; + @arc4 = arc4_init (pack ("V", $seed)); + } +} + +sub random_bytes { + random_init (); + my ($n) = @_; + return arc4_crypt (\@arc4, "\0" x $n); +} + +sub random_ulong { + random_init (); + return unpack ("V", random_bytes (4)); +} + +1; diff --git a/tests/devices/src/tests/tests.pm b/tests/devices/src/tests/tests.pm new file mode 100644 index 0000000..6cdbf86 --- /dev/null +++ b/tests/devices/src/tests/tests.pm @@ -0,0 +1,650 @@ +use strict; +use warnings; +use tests::Algorithm::Diff; +use File::Temp 'tempfile'; +use Fcntl qw(SEEK_SET SEEK_CUR); + +sub fail; +sub pass; + +die if @ARGV != 2; +our ($test, $src_dir) = @ARGV; + +my ($msg_file) = tempfile (); +select ($msg_file); + +our (@prereq_tests) = (); +if ($test =~ /^(.*)-persistence$/) { + push (@prereq_tests, $1); +} +for my $prereq_test (@prereq_tests) { + my (@result) = read_text_file ("$prereq_test.result"); + fail "Prerequisite test $prereq_test failed.\n" if $result[0] ne 'PASS'; +} + + +# Generic testing. + +sub check_expected { + my ($expected) = pop @_; + my (@options) = @_; + my (@output) = read_text_file ("$test.output"); + common_checks ("run", @output); + compare_output ("run", @options, \@output, $expected); +} + +sub common_checks { + my ($run, @output) = @_; + + fail "\u$run produced no output at all\n" if @output == 0; + + check_for_panic ($run, @output); + check_for_keyword ($run, "FAIL", @output); + check_for_triple_fault ($run, @output); + check_for_keyword ($run, "TIMEOUT", @output); + + fail "\u$run didn't start up properly: no \"PintOS booting\" message\n" + if !grep (/PintOS booting with.*kB RAM\.\.\./, @output); + fail "\u$run didn't start up properly: no \"Boot complete\" message\n" + if !grep (/Boot complete/, @output); + fail "\u$run didn't shut down properly: no \"Timer: # ticks\" message\n" + if !grep (/Timer: \d+ ticks/, @output); + fail "\u$run didn't shut down properly: no \"Powering off\" message\n" + if !grep (/Powering off/, @output); +} + +sub check_for_panic { + my ($run, @output) = @_; + + my ($panic) = grep (/PANIC/, @output); + return unless defined $panic; + + print "Kernel panic in $run: ", substr ($panic, index ($panic, "PANIC")), + "\n"; + + my (@stack_line) = grep (/Call stack:/, @output); + if (@stack_line != 0) { + my ($addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/; + + # Find a user program to translate user virtual addresses. + my ($userprog) = ""; + $userprog = "$test" + if grep (hex ($_) < 0xc0000000, split (' ', $addrs)) > 0 && -e $test; + + # Get and print the backtrace. + my ($trace) = scalar (`backtrace kernel.o $userprog $addrs`); + print "Call stack:$addrs\n"; + print "Translation of call stack:\n"; + print $trace; + + # Print disclaimer. + if ($userprog ne '' && index ($trace, $userprog) >= 0) { + print <capacity/) { + print < 0; + + print < $_), @$expected)}; + } + foreach my $key (keys %$expected) { + my (@expected) = split ("\n", $expected->{$key}); + + $msg .= "Acceptable output:\n"; + $msg .= join ('', map (" $_\n", @expected)); + + # Check whether actual and expected match. + # If it's a perfect match, we're done. + if ($#output == $#expected) { + my ($eq) = 1; + for (my ($i) = 0; $i <= $#expected; $i++) { + $eq = 0 if $output[$i] ne $expected[$i]; + } + return $key if $eq; + } + + # They differ. Output a diff. + my (@diff) = ""; + my ($d) = Algorithm::Diff->new (\@expected, \@output); + while ($d->Next ()) { + my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2)); + if ($d->Same ()) { + push (@diff, map (" $_\n", $d->Items (1))); + } else { + push (@diff, map ("- $_\n", $d->Items (1))) if $d->Items (1); + push (@diff, map ("+ $_\n", $d->Items (2))) if $d->Items (2); + } + } + + $msg .= "Differences in `diff -u' format:\n"; + $msg .= join ('', @diff); + } + + # Failed to match. Report failure. + $msg .= "\n(Process exit codes are excluded for matching purposes.)\n" + if $ignore_exit_codes; + $msg .= "\n(User fault messages are excluded for matching purposes.)\n" + if $ignore_user_faults; + $msg .= "\n(Kernel fault messages are excluded for matching purposes.)\n" + if $ignore_kernel_faults; + $msg .= "\n(Divide Error messages are excluded for matching purposes.)\n" + if $ignore_div0_faults; + fail "Test output failed to match any acceptable form.\n\n$msg"; +} + +# File system extraction. + +# check_archive (\%CONTENTS) +# +# Checks that the extracted file system's contents match \%CONTENTS. +# Each key in the hash is a file name. Each value may be: +# +# - $FILE: Name of a host file containing the expected contents. +# +# - [$FILE, $OFFSET, $LENGTH]: An excerpt of host file $FILE +# comprising the $LENGTH bytes starting at $OFFSET. +# +# - [$CONTENTS]: The literal expected file contents, as a string. +# +# - {SUBDIR}: A subdirectory, in the same form described here, +# recursively. +sub check_archive { + my ($expected_hier) = @_; + + my (@output) = read_text_file ("$test.output"); + common_checks ("file system extraction run", @output); + + @output = get_core_output ("file system extraction run", @output); + @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\d+\)$/, @output); + fail join ("\n", "Error extracting file system:", @output) if @output; + + my ($test_base_name) = $test; + $test_base_name =~ s%.*/%%; + $test_base_name =~ s%-persistence$%%; + $expected_hier->{$test_base_name} = $prereq_tests[0]; + $expected_hier->{'tar'} = 'tests/filesys/extended/tar'; + + my (%expected) = normalize_fs (flatten_hierarchy ($expected_hier, "")); + my (%actual) = read_tar ("$prereq_tests[0].tar"); + + my ($errors) = 0; + foreach my $name (sort keys %expected) { + if (exists $actual{$name}) { + if (is_dir ($actual{$name}) && !is_dir ($expected{$name})) { + print "$name is a directory but should be an ordinary file.\n"; + $errors++; + } elsif (!is_dir ($actual{$name}) && is_dir ($expected{$name})) { + print "$name is an ordinary file but should be a directory.\n"; + $errors++; + } + } else { + print "$name is missing from the file system.\n"; + $errors++; + } + } + foreach my $name (sort keys %actual) { + if (!exists $expected{$name}) { + if ($name =~ /^[[:print:]]+$/) { + print "$name exists in the file system but it should not.\n"; + } else { + my ($esc_name) = $name; + $esc_name =~ s/[^[:print:]]/./g; + print <[0]); + $file = tempfile (); + syswrite ($file, $value->[0]) == $length + or die "writing temporary file: $!\n"; + sysseek ($file, 0, SEEK_SET); + } elsif (@$value == 3) { + $length = $value->[2]; + open ($file, '<', $value->[0]) or die "$value->[0]: open: $!\n"; + die "$value->[0]: file is smaller than expected\n" + if -s $file < $value->[1] + $length; + sysseek ($file, $value->[1], SEEK_SET); + } else { + die; + } + return ($file, $length); +} + +# compare_files ($A, $A_SIZE, $B, $B_SIZE, $NAME, $VERBOSE) +# +# Compares $A_SIZE bytes in $A to $B_SIZE bytes in $B. +# ($A and $B are handles.) +# If their contents differ, prints a brief message describing +# the differences, using $NAME to identify the file. +# The message contains more detail if $VERBOSE is nonzero. +# Returns 1 if the contents are identical, 0 otherwise. +sub compare_files { + my ($a, $a_size, $b, $b_size, $name, $verbose) = @_; + my ($ofs) = 0; + select(STDOUT); + for (;;) { + my ($a_amt) = $a_size >= 1024 ? 1024 : $a_size; + my ($b_amt) = $b_size >= 1024 ? 1024 : $b_size; + my ($a_data, $b_data); + if (!defined (sysread ($a, $a_data, $a_amt)) + || !defined (sysread ($b, $b_data, $b_amt))) { + die "reading $name: $!\n"; + } + + my ($a_len) = length $a_data; + my ($b_len) = length $b_data; + last if $a_len == 0 && $b_len == 0; + + if ($a_data ne $b_data) { + my ($min_len) = $a_len < $b_len ? $a_len : $b_len; + my ($diff_ofs); + for ($diff_ofs = 0; $diff_ofs < $min_len; $diff_ofs++) { + last if (substr ($a_data, $diff_ofs, 1) + ne substr ($b_data, $diff_ofs, 1)); + } + + printf "\nFile $name differs from expected " + . "starting at offset 0x%x.\n", $ofs + $diff_ofs; + if ($verbose ) { + print "Expected contents:\n"; + hex_dump (substr ($a_data, $diff_ofs, 64), $ofs + $diff_ofs); + print "Actual contents:\n"; + hex_dump (substr ($b_data, $diff_ofs, 64), $ofs + $diff_ofs); + } + return 0; + } + + $ofs += $a_len; + $a_size -= $a_len; + $b_size -= $b_len; + } + return 1; +} + +# hex_dump ($DATA, $OFS) +# +# Prints $DATA in hex and text formats. +# The first byte of $DATA corresponds to logical offset $OFS +# in whatever file the data comes from. +sub hex_dump { + my ($data, $ofs) = @_; + + if ($data eq '') { + printf " (File ends at offset %08x.)\n", $ofs; + return; + } + + my ($per_line) = 16; + while ((my $size = length ($data)) > 0) { + my ($start) = $ofs % $per_line; + my ($end) = $per_line; + $end = $start + $size if $end - $start > $size; + my ($n) = $end - $start; + + printf "0x%08x ", int ($ofs / $per_line) * $per_line; + + # Hex version. + print " " x $start; + for my $i ($start...$end - 1) { + printf "%02x", ord (substr ($data, $i - $start, 1)); + print $i == $per_line / 2 - 1 ? '-' : ' '; + } + print " " x ($per_line - $end); + + # Character version. + my ($esc_data) = substr ($data, 0, $n); + $esc_data =~ s/[^[:print:]]/./g; + print "|", " " x $start, $esc_data, " " x ($per_line - $end), "|"; + + print "\n"; + + $data = substr ($data, $n); + $ofs += $n; + } +} + +# print_fs (%FS) +# +# Prints a list of files in %FS, which must be a file system +# as flattened by flatten_hierarchy() and normalized by +# normalize_fs(). +sub print_fs { + my (%fs) = @_; + foreach my $name (sort keys %fs) { + my ($esc_name) = $name; + $esc_name =~ s/[^[:print:]]/./g; + print "$esc_name: "; + if (!is_dir ($fs{$name})) { + print +file_size ($fs{$name}), "-byte file"; + } else { + print "directory"; + } + print "\n"; + } + print "(empty)\n" if !@_; +} + +# normalize_fs (%FS) +# +# Takes a file system as flattened by flatten_hierarchy(). +# Returns a similar file system in which values of the form $FILE +# are replaced by those of the form [$FILE, $OFFSET, $LENGTH]. +sub normalize_fs { + my (%fs) = @_; + foreach my $name (keys %fs) { + my ($value) = $fs{$name}; + next if is_dir ($value) || ref ($value) ne ''; + die "can't open $value\n" if !stat $value; + $fs{$name} = [$value, 0, -s _]; + } + return %fs; +} + +# is_dir ($VALUE) +# +# Takes a value like one in the hash returned by flatten_hierarchy() +# and returns 1 if it represents a directory, 0 otherwise. +sub is_dir { + my ($value) = @_; + return ref ($value) eq '' && $value eq 'directory'; +} + +# file_size ($VALUE) +# +# Takes a value like one in the hash returned by flatten_hierarchy() +# and returns the size of the file it represents. +sub file_size { + my ($value) = @_; + die if is_dir ($value); + die if ref ($value) ne 'ARRAY'; + return @$value > 1 ? $value->[2] : length ($value->[0]); +} + +# flatten_hierarchy ($HIER_FS, $PREFIX) +# +# Takes a file system in the format expected by check_archive() and +# returns a "flattened" version in which file names include all parent +# directory names and the value of directories is just "directory". +sub flatten_hierarchy { + my (%hier_fs) = %{$_[0]}; + my ($prefix) = $_[1]; + my (%flat_fs); + for my $name (keys %hier_fs) { + my ($value) = $hier_fs{$name}; + if (ref $value eq 'HASH') { + %flat_fs = (%flat_fs, flatten_hierarchy ($value, "$prefix$name/")); + $flat_fs{"$prefix$name"} = 'directory'; + } else { + $flat_fs{"$prefix$name"} = $value; + } + } + return %flat_fs; +} + +# read_tar ($ARCHIVE) +# +# Reads the ustar-format tar file in $ARCHIVE +# and returns a flattened file system for it. +sub read_tar { + my ($archive) = @_; + my (%content); + open (ARCHIVE, '<', $archive) or fail "$archive: open: $!\n"; + for (;;) { + my ($header); + if ((my $retval = sysread (ARCHIVE, $header, 512)) != 512) { + fail "$archive: unexpected end of file\n" if $retval >= 0; + fail "$archive: read: $!\n"; + } + + last if $header eq "\0" x 512; + + # Verify magic numbers. + if (substr ($header, 257, 6) ne "ustar\0" + || substr ($header, 263, 2) ne '00') { + fail "$archive: corrupt ustar header\n"; + } + + # Verify checksum. + my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8, ' ' x 8))); + my ($correct_chksum) = unpack ("%32a*", $header); + fail "$archive: bad header checksum\n" if $chksum != $correct_chksum; + + # Get file name. + my ($name) = unpack ("Z100", $header); + my ($prefix) = unpack ("Z*", substr ($header, 345)); + $name = "$prefix/$name" if $prefix ne ''; + fail "$archive: contains file with empty name" if $name eq ''; + + # Get type. + my ($typeflag) = substr ($header, 156, 1); + $typeflag = '0' if $typeflag eq "\0"; + fail "unknown file type '$typeflag'\n" if $typeflag !~ /[05]/; + + # Get size. + my ($size) = oct (unpack ("Z*", substr ($header, 124, 12))); + fail "bad size $size\n" if $size < 0; + $size = 0 if $typeflag eq '5'; + + # Store content. + $name =~ s%^(/|\./|\.\./)*%%; # Strip leading "/", "./", "../". + $name = '' if $name eq '.' || $name eq '..'; + if (exists $content{$name}) { + fail "$archive: contains multiple entries for $name\n"; + } + if ($typeflag eq '5') { + $content{$name} = 'directory' if $name ne ''; + } else { + fail "$archive: contains file with empty name\n" if $name eq ''; + my ($position) = sysseek (ARCHIVE, 0, SEEK_CUR); + $content{$name} = [$archive, $position, $size]; + sysseek (ARCHIVE, int (($size + 511) / 512) * 512, SEEK_CUR); + } + } + close (ARCHIVE); + return %content; +} + +# Utilities. + +sub fail { + finish ("FAIL", @_); +} + +sub pass { + finish ("PASS", @_); +} + +sub finish { + my ($verdict, @messages) = @_; + + seek ($msg_file, 0, 0); + push (@messages, <$msg_file>); + close ($msg_file); + chomp (@messages); + + my ($result_fn) = "$test.result"; + open (RESULT, '>', $result_fn) or die "$result_fn: create: $!\n"; + print RESULT "$verdict\n"; + print RESULT "$_\n" foreach @messages; + close (RESULT); + + if ($verdict eq 'PASS') { + print STDOUT "pass $test\n"; + } else { + print STDOUT "FAIL $test\n"; + } + print STDOUT "$_\n" foreach @messages; + + exit 0; +} + +sub read_text_file { + my ($file_name) = @_; + open (FILE, '<', $file_name) or die "$file_name: open: $!\n"; + my (@content) = ; + chomp (@content); + close (FILE); + return @content; +} + +1; diff --git a/tests/devices/src/utils/.gitignore b/tests/devices/src/utils/.gitignore new file mode 100644 index 0000000..b96f278 --- /dev/null +++ b/tests/devices/src/utils/.gitignore @@ -0,0 +1,3 @@ +setitimer-helper +squish-pty +squish-unix diff --git a/tests/devices/src/utils/Makefile b/tests/devices/src/utils/Makefile new file mode 100644 index 0000000..d4c1e45 --- /dev/null +++ b/tests/devices/src/utils/Makefile @@ -0,0 +1,11 @@ +all: setitimer-helper squish-pty squish-unix + +CC = gcc +CFLAGS = -Wall -W +LDLIBS = -lm +setitimer-helper: setitimer-helper.o +squish-pty: squish-pty.o +squish-unix: squish-unix.o + +clean: + rm -f *.o setitimer-helper squish-pty squish-unix diff --git a/tests/devices/src/utils/Pintos.pm b/tests/devices/src/utils/Pintos.pm new file mode 100644 index 0000000..312eb1b --- /dev/null +++ b/tests/devices/src/utils/Pintos.pm @@ -0,0 +1,491 @@ +# PintOS helper subroutines. + +# Number of bytes available for the loader at the beginning of the MBR. +# Kernel command-line arguments follow the loader. +our $LOADER_SIZE = 314; + +# Partition types. +my (%role2type) = (KERNEL => 0x20, + FILESYS => 0x21, + SCRATCH => 0x22, + SWAP => 0x23); +my (%type2role) = reverse %role2type; + +# Order of roles within a given disk. +our (@role_order) = qw (KERNEL FILESYS SCRATCH SWAP); + +# Partitions. +# +# Valid keys are KERNEL, FILESYS, SCRATCH, SWAP. Only those +# partitions which are in use are included. +# +# Each value is a reference to a hash. If the partition's contents +# are to be obtained from a file (that will be copied into a new +# virtual disk), then the hash contains: +# +# FILE => name of file from which the partition's contents are copied +# (perhaps "/dev/zero"), +# OFFSET => offset in bytes in FILE, +# BYTES => size in bytes of contents from FILE, +# +# If the partition is taken from a virtual disk directly, then it +# contains the following. The same keys are also filled in once a +# file-based partition has been copied into a new virtual disk: +# +# DISK => name of virtual disk file, +# START => sector offset of start of partition within DISK, +# SECTORS => number of sectors of partition within DISK, which is usually +# greater than round_up (BYTES, 512) due to padding. +our (%parts); + +# set_part($opt, $arg) +# +# For use as a helper function for Getopt::Long::GetOptions to set +# disk sources. +sub set_part { + my ($opt, $arg) = @_; + my ($role, $source) = $opt =~ /^([a-z]+)(?:-([a-z]+))?/ or die; + + $role = uc $role; + $source = 'FILE' if $source eq ''; + + die "can't have two sources for \L$role\E partition" + if exists $parts{$role}; + + do_set_part ($role, $source, $arg); +} + +# do_set_part($role, $source, $arg) +# +# Sets partition $role as coming from $source (one of 'file', 'from', +# or 'size'). $arg is a file name for 'file' or 'from', a size in +# megabytes for 'size'. +sub do_set_part { + my ($role, $source, $arg) = @_; + + my ($p) = $parts{$role} = {}; + if ($source eq 'file') { + if (read_mbr ($arg)) { + print STDERR "warning: $arg looks like a partitioned disk "; + print STDERR "(did you want --$role-from=$arg or --disk=$arg?)\n" + } + + $p->{FILE} = $arg; + $p->{OFFSET} = 0; + $p->{BYTES} = -s $arg; + } elsif ($source eq 'from') { + my (%pt) = read_partition_table ($arg); + my ($sp) = $pt{$role}; + die "$arg: does not contain \L$role\E partition\n" if !defined $sp; + + $p->{FILE} = $arg; + $p->{OFFSET} = $sp->{START} * 512; + $p->{BYTES} = $sp->{SECTORS} * 512; + } elsif ($source eq 'size') { + $arg =~ /^\d+(\.\d+)?|\.\d+$/ or die "$arg: not a valid size in MB\n"; + + $p->{FILE} = "/dev/zero"; + $p->{OFFSET} = 0; + $p->{BYTES} = ceil ($arg * 1024 * 1024); + } else { + die; + } +} + +# set_geometry('HEADS,SPT') +# set_geometry('zip') +# +# For use as a helper function for Getopt::Long::GetOptions to set +# disk geometry. +sub set_geometry { + local ($_) = $_[1]; + if ($_ eq 'zip') { + @geometry{'H', 'S'} = (64, 32); + } else { + @geometry{'H', 'S'} = /^(\d+)[,\s]+(\d+)$/ + or die "bad syntax for geometry\n"; + $geometry{H} <= 255 or die "heads limited to 255\n"; + $geometry{S} <= 63 or die "sectors per track limited to 63\n"; + } +} + +# set_align('bochs|full|none') +# +# For use as a helper function for Getopt::Long::GetOptions to set +# partition alignment. +sub set_align { + $align = $_[1]; + die "unknown alignment type \"$align\"\n" + if $align ne 'bochs' && $align ne 'full' && $align ne 'none'; +} + +# assemble_disk(%args) +# +# Creates a virtual disk $args{DISK} containing the partitions +# described by @args{KERNEL, FILESYS, SCRATCH, SWAP}. +# +# Required arguments: +# DISK => output disk file name +# HANDLE => output file handle (will be closed) +# +# Normally at least one of the following is included: +# KERNEL, FILESYS, SCRATCH, SWAP => {input: +# FILE => file to read, +# OFFSET => byte offset in file, +# BYTES => byte count from file, +# +# output: +# DISK => output disk file name, +# START => sector offset in DISK, +# SECTORS => sector count in DISK}, +# +# Optional arguments: +# ALIGN => 'bochs' (default), 'full', or 'none' +# GEOMETRY => {H => heads, S => sectors per track} (default 16, 63) +# FORMAT => 'partitioned' (default) or 'raw' +# LOADER => $LOADER_SIZE-byte string containing the loader binary +# ARGS => ['arg 1', 'arg 2', ...] +sub assemble_disk { + my (%args) = @_; + + my (%geometry) = $args{GEOMETRY} || (H => 16, S => 63); + + my ($align); # Align partition start, end to cylinder boundary? + my ($pad); # Pad end of disk out to cylinder boundary? + if (!defined ($args{ALIGN}) || $args{ALIGN} eq 'bochs') { + $align = 0; + $pad = 1; + } elsif ($args{ALIGN} eq 'full') { + $align = 1; + $pad = 0; + } elsif ($args{ALIGN} eq 'none') { + $align = $pad = 0; + } else { + die; + } + + my ($format) = $args{FORMAT} || 'partitioned'; + die if $format ne 'partitioned' && $format ne 'raw'; + + # Check that we have apartitions to copy in. + my $part_cnt = grep (defined ($args{$_}), keys %role2type); + die "must have exactly one partition for raw output\n" + if $format eq 'raw' && $part_cnt != 1; + + # Calculate the disk size. + my ($total_sectors) = 0; + if ($format eq 'partitioned') { + $total_sectors += $align ? $geometry{S} : 1; + } + for my $role (@role_order) { + my ($p) = $args{$role}; + next if !defined $p; + + die if $p->{DISK}; + + my ($bytes) = $p->{BYTES}; + my ($start) = $total_sectors; + my ($end) = $start + div_round_up ($bytes, 512); + $end = round_up ($end, cyl_sectors (%geometry)) if $align; + + $p->{DISK} = $args{DISK}; + $p->{START} = $start; + $p->{SECTORS} = $end - $start; + $total_sectors = $end; + } + + # Write the disk. + my ($disk_fn) = $args{DISK}; + my ($disk) = $args{HANDLE}; + if ($format eq 'partitioned') { + # Pack loader into MBR. + my ($loader) = $args{LOADER} || "\xcd\x18"; + my ($mbr) = pack ("a$LOADER_SIZE", $loader); + + $mbr .= make_kernel_command_line (@{$args{ARGS}}); + + # Pack partition table into MBR. + $mbr .= make_partition_table (\%geometry, \%args); + + # Add signature to MBR. + $mbr .= pack ("v", 0xaa55); + + die if length ($mbr) != 512; + write_fully ($disk, $disk_fn, $mbr); + write_zeros ($disk, $disk_fn, 512 * ($geometry{S} - 1)) if $align; + } + for my $role (@role_order) { + my ($p) = $args{$role}; + next if !defined $p; + + my ($source); + my ($fn) = $p->{FILE}; + open ($source, '<', $fn) or die "$fn: open: $!\n"; + if ($p->{OFFSET}) { + sysseek ($source, $p->{OFFSET}, 0) == $p->{OFFSET} + or die "$fn: seek: $!\n"; + } + copy_file ($source, $fn, $disk, $disk_fn, $p->{BYTES}); + close ($source) or die "$fn: close: $!\n"; + + write_zeros ($disk, $disk_fn, $p->{SECTORS} * 512 - $p->{BYTES}); + } + if ($pad) { + my ($pad_sectors) = round_up ($total_sectors, cyl_sectors (%geometry)); + write_zeros ($disk, $disk_fn, ($pad_sectors - $total_sectors) * 512); + } + close ($disk) or die "$disk: close: $!\n"; +} + +# make_partition_table({H => heads, S => sectors}, {KERNEL => ..., ...}) +# +# Creates and returns a partition table for the given partitions and +# disk geometry. +sub make_partition_table { + my ($geometry, $partitions) = @_; + my ($table) = ''; + for my $role (@role_order) { + defined (my $p = $partitions->{$role}) or next; + + my $end = $p->{START} + $p->{SECTORS} - 1; + my $bootable = $role eq 'KERNEL'; + + $table .= pack ("C", $bootable ? 0x80 : 0); # Bootable? + $table .= pack_chs ($p->{START}, $geometry); # CHS of partition start + $table .= pack ("C", $role2type{$role}); # Partition type + $table .= pack_chs($end, $geometry); # CHS of partition end + $table .= pack ("V", $p->{START}); # LBA of partition start + $table .= pack ("V", $p->{SECTORS}); # Length in sectors + die if length ($table) % 16; + } + return pack ("a64", $table); +} + +# make_kernel_command_line(@args) +# +# Returns the raw bytes to write to an MBR at offset $LOADER_SIZE to +# set a PintOS kernel command line. +sub make_kernel_command_line { + my (@args) = @_; + my ($args) = join ('', map ("$_\0", @args)); + die "command line exceeds 128 bytes" if length ($args) > 128; + return pack ("V a128", scalar (@args), $args); +} + +# copy_file($from_handle, $from_file_name, $to_handle, $to_file_name, $size) +# +# Copies $size bytes from $from_handle to $to_handle. +# $from_file_name and $to_file_name are used in error messages. +sub copy_file { + my ($from_handle, $from_file_name, $to_handle, $to_file_name, $size) = @_; + + while ($size > 0) { + my ($chunk_size) = 4096; + $chunk_size = $size if $chunk_size > $size; + $size -= $chunk_size; + + my ($data) = read_fully ($from_handle, $from_file_name, $chunk_size); + write_fully ($to_handle, $to_file_name, $data); + } +} + +# read_fully($handle, $file_name, $bytes) +# +# Reads exactly $bytes bytes from $handle and returns the data read. +# $file_name is used in error messages. +sub read_fully { + my ($handle, $file_name, $bytes) = @_; + my ($data); + my ($read_bytes) = sysread ($handle, $data, $bytes); + die "$file_name: read: $!\n" if !defined $read_bytes; + die "$file_name: unexpected end of file\n" if $read_bytes != $bytes; + return $data; +} + +# write_fully($handle, $file_name, $data) +# +# Write $data to $handle. +# $file_name is used in error messages. +sub write_fully { + my ($handle, $file_name, $data) = @_; + my ($written_bytes) = syswrite ($handle, $data); + die "$file_name: write: $!\n" if !defined $written_bytes; + die "$file_name: short write\n" if $written_bytes != length $data; +} + +sub write_zeros { + my ($handle, $file_name, $size) = @_; + + while ($size > 0) { + my ($chunk_size) = 4096; + $chunk_size = $size if $chunk_size > $size; + $size -= $chunk_size; + + write_fully ($handle, $file_name, "\0" x $chunk_size); + } +} + +# div_round_up($x,$y) +# +# Returns $x / $y, rounded up to the nearest integer. +# $y must be an integer. +sub div_round_up { + my ($x, $y) = @_; + return int ((ceil ($x) + $y - 1) / $y); +} + +# round_up($x, $y) +# +# Returns $x rounded up to the nearest multiple of $y. +# $y must be an integer. +sub round_up { + my ($x, $y) = @_; + return div_round_up ($x, $y) * $y; +} + +# cyl_sectors(H => heads, S => sectors) +# +# Returns the number of sectors in a cylinder of a disk with the given +# geometry. +sub cyl_sectors { + my (%geometry) = @_; + return $geometry{H} * $geometry{S}; +} + +# read_loader($file_name) +# +# Reads and returns the first $LOADER_SIZE bytes in $file_name. +# If $file_name is undefined, tries to find the default loader. +# Makes sure that the loader is a reasonable size. +sub read_loader { + my ($name) = @_; + $name = find_file ("loader.bin") if !defined $name; + die "Cannot find loader\n" if !defined $name; + + my ($handle); + open ($handle, '<', $name) or die "$name: open: $!\n"; + -s $handle == $LOADER_SIZE || -s $handle == 512 + or die "$name: must be exactly $LOADER_SIZE or 512 bytes long\n"; + $loader = read_fully ($handle, $name, $LOADER_SIZE); + close ($handle) or die "$name: close: $!\n"; + return $loader; +} + +# pack_chs($lba, {H => heads, S => sectors}) +# +# Converts logical sector $lba to a 3-byte packed geometrical sector +# in the format used in PC partition tables (see [Partitions]) and +# returns the geometrical sector as a 3-byte string. +sub pack_chs { + my ($lba, $geometry) = @_; + my ($cyl, $head, $sect) = lba_to_chs ($lba, $geometry); + return pack ("CCC", $head, $sect | (($cyl >> 2) & 0xc0), $cyl & 0xff); +} + +# lba_to_chs($lba, {H => heads, S => sectors}) +# +# Returns the geometrical sector corresponding to logical sector $lba +# given the specified geometry. +sub lba_to_chs { + my ($lba, $geometry) = @_; + my ($hpc) = $geometry->{H}; + my ($spt) = $geometry->{S}; + + # Source: + # http://en.wikipedia.org/wiki/CHS_conversion + use integer; + my $cyl = $lba / ($hpc * $spt); + my $temp = $lba % ($hpc * $spt); + my $head = $temp / $spt; + my $sect = $temp % $spt + 1; + + # Source: + # http://www.cgsecurity.org/wiki/Intel_Partition_Table + if ($cyl <= 1023) { + return ($cyl, $head, $sect); + } else { + return (1023, 254, 63); ## or should this be (1023, $hpc, $spt)? + } +} + +# read_mbr($file) +# +# Tries to read an MBR from $file. Returns the 512-byte MBR if +# successful, otherwise numeric 0. +sub read_mbr { + my ($file) = @_; + my ($retval) = 0; + open (FILE, '<', $file) or die "$file: open: $!\n"; + if (-s FILE == 0) { + die "$file: file has zero size\n"; + } elsif (-s FILE >= 512) { + my ($mbr); + sysread (FILE, $mbr, 512) == 512 or die "$file: read: $!\n"; + $retval = $mbr if unpack ("v", substr ($mbr, 510)) == 0xaa55; + } + close (FILE); + return $retval; +} + +# interpret_partition_table($mbr, $disk) +# +# Parses the partition-table in the specified 512-byte $mbr and +# returns the partitions. $disk is used for error messages. +sub interpret_partition_table { + my ($mbr, $disk) = @_; + my (%parts); + for my $i (0...3) { + my ($bootable, $valid, $type, $lba_start, $lba_length) + = unpack ("C X V C x3 V V", substr ($mbr, 446 + 16 * $i, 16)); + next if !$valid; + + (print STDERR "warning: invalid partition entry $i in $disk\n"), + next if $bootable != 0 && $bootable != 0x80; + + my ($role) = $type2role{$type}; + (printf STDERR "warning: non-PintOS partition type 0x%02x in %s\n", + $type, $disk), + next if !defined $role; + + (print STDERR "warning: duplicate \L$role\E partition in $disk\n"), + next if exists $parts{$role}; + + $parts{$role} = {START => $lba_start, + SECTORS => $lba_length}; + } + return %parts; +} + +# find_file($base_name) +# +# Looks for a file named $base_name in a couple of likely spots. If +# found, returns the name; otherwise, returns undef. +sub find_file { + my ($base_name) = @_; + -e && return $_ foreach $base_name, "build/$base_name"; + return undef; +} + +# read_partition_table($file) +# +# Reads a partition table from $file and returns the parsed +# partitions. Dies if partitions can't be read. +sub read_partition_table { + my ($file) = @_; + my ($mbr) = read_mbr ($file); + die "$file: not a partitioned disk\n" if !$mbr; + return interpret_partition_table ($mbr, $file); +} + +# max(@args) +# +# Returns the numerically largest value in @args. +sub max { + my ($max) = $_[0]; + foreach (@_[1..$#_]) { + $max = $_ if $_ > $max; + } + return $max; +} + +1; diff --git a/tests/devices/src/utils/backtrace b/tests/devices/src/utils/backtrace new file mode 100755 index 0000000..e7be466 --- /dev/null +++ b/tests/devices/src/utils/backtrace @@ -0,0 +1,106 @@ +#! /usr/bin/perl -w + +use strict; + +# Check command line. +if (grep ($_ eq '-h' || $_ eq '--help', @ARGV)) { + print <<'EOF'; +backtrace, for converting raw addresses into symbolic backtraces +usage: backtrace [BINARY]... ADDRESS... +where BINARY is the binary file or files from which to obtain symbols + and ADDRESS is a raw address to convert to a symbol name. + +If no BINARY is unspecified, the default is the first of kernel.o or +build/kernel.o that exists. If multiple binaries are specified, each +symbol printed is from the first binary that contains a match. + +The ADDRESS list should be taken from the "Call stack:" printed by the +kernel. Read "Backtraces" in the "Debugging Tools" chapter of the +PintOS documentation for more information. +EOF + exit 0; +} +die "backtrace: at least one argument required (use --help for help)\n" + if @ARGV == 0; + +# Drop garbage inserted by kernel. +@ARGV = grep (!/^(call|stack:?|[-+])$/i, @ARGV); +s/\.$// foreach @ARGV; + +# Find binaries. +my (@binaries); +while ($ARGV[0] !~ /^0x/) { + my ($bin) = shift @ARGV; + die "backtrace: $bin: not found (use --help for help)\n" if ! -e $bin; + push (@binaries, $bin); +} +if (!@binaries) { + my ($bin); + if (-e 'kernel.o') { + $bin = 'kernel.o'; + } elsif (-e 'build/kernel.o') { + $bin = 'build/kernel.o'; + } else { + die "backtrace: no binary specified and neither \"kernel.o\" nor \"build/kernel.o\" exists (use --help for help)\n"; + } + push (@binaries, $bin); +} + +# Find addr2line. +my ($a2l) = search_path ("i686-elf-addr2line") || search_path ("addr2line"); +if (!$a2l) { + die "backtrace: neither `i686-elf-addr2line' nor `addr2line' in PATH\n"; +} +sub search_path { + my ($target) = @_; + for my $dir (split (':', $ENV{PATH})) { + my ($file) = "$dir/$target"; + return $file if -e $file; + } + return undef; +} + +# Figure out backtrace. +my (@locs) = map ({ADDR => $_}, @ARGV); +for my $bin (@binaries) { + open (A2L, "$a2l -fe $bin " . join (' ', map ($_->{ADDR}, @locs)) . "|"); + for (my ($i) = 0; ; $i++) { + my ($function, $line); + chomp ($function = $_); + chomp ($line = ); + next if defined $locs[$i]{BINARY}; + + if ($function ne '??' || $line ne '??:0') { + $locs[$i]{FUNCTION} = $function; + $locs[$i]{LINE} = $line; + $locs[$i]{BINARY} = $bin; + } + } + close (A2L); +} + +# Print backtrace. +my ($cur_binary); +for my $loc (@locs) { + if (defined ($loc->{BINARY}) + && @binaries > 1 + && (!defined ($cur_binary) || $loc->{BINARY} ne $cur_binary)) { + $cur_binary = $loc->{BINARY}; + print "In $cur_binary:\n"; + } + + my ($addr) = $loc->{ADDR}; + $addr = sprintf ("0x%08x", hex ($addr)) if $addr =~ /^0x[0-9a-f]+$/i; + + print $addr, ": "; + if (defined ($loc->{BINARY})) { + my ($function) = $loc->{FUNCTION}; + my ($line) = $loc->{LINE}; + $line =~ s/^(\.\.\/)*//; + $line = "..." . substr ($line, -25) if length ($line) > 28; + print "$function ($line)"; + } else { + print "(unknown)"; + } + print "\n"; +} diff --git a/tests/devices/src/utils/pintos b/tests/devices/src/utils/pintos new file mode 100755 index 0000000..2aed034 --- /dev/null +++ b/tests/devices/src/utils/pintos @@ -0,0 +1,947 @@ +#! /usr/bin/perl -w + +use strict; +use POSIX; +use Fcntl; +use File::Temp 'tempfile'; +use Getopt::Long qw(:config bundling); +use Fcntl qw(SEEK_SET SEEK_CUR); + +# Read Pintos.pm from the same directory as this program. +BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } + +# Command-line options. +our ($start_time) = time (); +our ($sim); # Simulator: bochs, qemu, or player. +our ($debug) = "none"; # Debugger: none, monitor, or gdb. +our ($mem) = 4; # Physical RAM in MB. +our ($serial) = 1; # Use serial port for input and output? +our ($vga); # VGA output: window, terminal, or none. +our ($jitter); # Seed for random timer interrupts, if set. +our ($realtime); # Synchronize timer interrupts with real time? +our ($timeout); # Maximum runtime in seconds, if set. +our ($kill_on_failure); # Abort quickly on test failure? +our (@puts); # Files to copy into the VM. +our (@gets); # Files to copy out of the VM. +our ($as_ref); # Reference to last addition to @gets or @puts. +our (@kernel_args); # Arguments to pass to kernel. +our (%parts); # Partitions. +our ($make_disk); # Name of disk to create. +our ($tmp_disk) = 1; # Delete $make_disk after run? +our (@disks); # Extra disk images to pass to simulator. +our ($loader_fn); # Bootstrap loader. +our (%geometry); # IDE disk geometry. +our ($align); # Partition alignment. + +parse_command_line (); +prepare_scratch_disk (); +find_disks (); +run_vm (); +finish_scratch_disk (); + +exit 0; + +# Parses the command line. +sub parse_command_line { + usage (0) if @ARGV == 0 || (@ARGV == 1 && $ARGV[0] eq '--help'); + + @kernel_args = @ARGV; + if (grep ($_ eq '--', @kernel_args)) { + @ARGV = (); + while ((my $arg = shift (@kernel_args)) ne '--') { + push (@ARGV, $arg); + } + GetOptions ("sim=s" => sub { set_sim ($_[1]) }, + "bochs" => sub { set_sim ("bochs") }, + "qemu" => sub { set_sim ("qemu") }, + "player" => sub { set_sim ("player") }, + + "debug=s" => sub { set_debug ($_[1]) }, + "no-debug" => sub { set_debug ("none") }, + "monitor" => sub { set_debug ("monitor") }, + "gdb" => sub { set_debug ("gdb") }, + + "m|memory=i" => \$mem, + "j|jitter=i" => sub { set_jitter ($_[1]) }, + "r|realtime" => sub { set_realtime () }, + + "T|timeout=i" => \$timeout, + "k|kill-on-failure" => \$kill_on_failure, + + "v|no-vga" => sub { set_vga ('none'); }, + "s|no-serial" => sub { $serial = 0; }, + "t|terminal" => sub { set_vga ('terminal'); }, + + "p|put-file=s" => sub { add_file (\@puts, $_[1]); }, + "g|get-file=s" => sub { add_file (\@gets, $_[1]); }, + "a|as=s" => sub { set_as ($_[1]); }, + + "h|help" => sub { usage (0); }, + + "kernel=s" => \&set_part, + "filesys=s" => \&set_part, + "swap=s" => \&set_part, + + "filesys-size=s" => \&set_part, + "scratch-size=s" => \&set_part, + "swap-size=s" => \&set_part, + + "kernel-from=s" => \&set_part, + "filesys-from=s" => \&set_part, + "swap-from=s" => \&set_part, + + "make-disk=s" => sub { $make_disk = $_[1]; + $tmp_disk = 0; }, + "disk=s" => sub { set_disk ($_[1]); }, + "loader=s" => \$loader_fn, + + "geometry=s" => \&set_geometry, + "align=s" => \&set_align) + or exit 1; + } + + $sim = "qemu" if !defined $sim; + $debug = "none" if !defined $debug; + $vga = exists ($ENV{DISPLAY}) ? "window" : "none" if !defined $vga; + + undef $timeout, print "warning: disabling timeout with --$debug\n" + if defined ($timeout) && $debug ne 'none'; + + print "warning: enabling serial port for -k or --kill-on-failure\n" + if $kill_on_failure && !$serial; + + $align = "bochs", + print STDERR "warning: setting --align=bochs for Bochs support\n" + if $sim eq 'bochs' && defined ($align) && $align eq 'none'; +} + +# usage($exitcode). +# Prints a usage message and exits with $exitcode. +sub usage { + my ($exitcode) = @_; + $exitcode = 1 unless defined $exitcode; + print <<'EOF'; +pintos, a utility for running PintOS in a simulator +Usage: pintos [OPTION...] -- [ARGUMENT...] +where each OPTION is one of the following options + and each ARGUMENT is passed to PintOS kernel verbatim. +Simulator selection: + --qemu (default) Use QEMU as simulator + --bochs Use Bochs as simulator + --player Use VMware Player as simulator +Debugger selection: + --no-debug (default) No debugger + --monitor Debug with simulator's monitor + --gdb Debug with gdb +Display options: (default is both VGA and serial) + -v, --no-vga No VGA display or keyboard + -s, --no-serial No serial input or output + -t, --terminal Display VGA in terminal (Bochs only) +Timing options: (Bochs only) + -j SEED Randomize timer interrupts + -r, --realtime Use realistic, not reproducible, timings +Testing options: + -T, --timeout=N Kill PintOS after N seconds CPU time or N*load_avg + seconds wall-clock time (whichever comes first) + -k, --kill-on-failure Kill PintOS a few seconds after a kernel or user + panic, test failure, or triple fault +Configuration options: + -m, --mem=N Give PintOS N MB physical RAM (default: 4) +File system commands: + -p, --put-file=HOSTFN Copy HOSTFN into VM, by default under same name + -g, --get-file=GUESTFN Copy GUESTFN out of VM, by default under same name + -a, --as=FILENAME Specifies guest (for -p) or host (for -g) file name +Partition options: (where PARTITION is one of: kernel filesys scratch swap) + --PARTITION=FILE Use a copy of FILE for the given PARTITION + --PARTITION-size=SIZE Create an empty PARTITION of the given SIZE in MB + --PARTITION-from=DISK Use of a copy of the given PARTITION in DISK + (There is no --kernel-size, --scratch, or --scratch-from option.) +Disk configuration options: + --make-disk=DISK Name the new DISK and don't delete it after the run + --disk=DISK Also use existing DISK (may be used multiple times) +Advanced disk configuration options: + --loader=FILE Use FILE as bootstrap loader (default: loader.bin) + --geometry=H,S Use H head, S sector geometry (default: 16,63) + --geometry=zip Use 64 head, 32 sector geometry for USB-ZIP boot + (see http://syslinux.zytor.com/usbkey.php) + --align=bochs Pad out disk to cylinder to support Bochs (default) + --align=full Align partition boundaries to cylinder boundary to + let fdisk guess correct geometry and quiet warnings + --align=none Don't align partitions at all, to save space +Other options: + -h, --help Display this help message. +EOF + exit $exitcode; +} + +# Sets the simulator. +sub set_sim { + my ($new_sim) = @_; + die "--$new_sim conflicts with --$sim\n" + if defined ($sim) && $sim ne $new_sim; + $sim = $new_sim; +} + +# Sets the debugger. +sub set_debug { + my ($new_debug) = @_; + die "--$new_debug conflicts with --$debug\n" + if $debug ne 'none' && $new_debug ne 'none' && $debug ne $new_debug; + $debug = $new_debug; +} + +# Sets VGA output destination. +sub set_vga { + my ($new_vga) = @_; + if (defined ($vga) && $vga ne $new_vga) { + print "warning: conflicting vga display options\n"; + } + $vga = $new_vga; +} + +# Sets randomized timer interrupts. +sub set_jitter { + my ($new_jitter) = @_; + die "--realtime conflicts with --jitter\n" if defined $realtime; + die "different --jitter already defined\n" + if defined $jitter && $jitter != $new_jitter; + $jitter = $new_jitter; +} + +# Sets real-time timer interrupts. +sub set_realtime { + die "--realtime conflicts with --jitter\n" if defined $jitter; + $realtime = 1; +} + +# add_file(\@list, $file) +# +# Adds [$file] to @list, which should be @puts or @gets. +# Sets $as_ref to point to the added element. +sub add_file { + my ($list, $file) = @_; + $as_ref = [$file]; + push (@$list, $as_ref); +} + +# Sets the guest/host name for the previous put/get. +sub set_as { + my ($as) = @_; + die "-a (or --as) is only allowed after -p or -g\n" if !defined $as_ref; + die "Only one -a (or --as) is allowed after -p or -g\n" + if defined $as_ref->[1]; + $as_ref->[1] = $as; +} + +# Sets $disk as a disk to be included in the VM to run. +sub set_disk { + my ($disk) = @_; + + push (@disks, $disk); + + my (%pt) = read_partition_table ($disk); + for my $role (keys %pt) { + die "can't have two sources for \L$role\E partition" + if exists $parts{$role}; + $parts{$role}{DISK} = $disk; + $parts{$role}{START} = $pt{$role}{START}; + $parts{$role}{SECTORS} = $pt{$role}{SECTORS}; + } +} + +# Locates the files used to back each of the virtual disks, +# and creates temporary disks. +sub find_disks { + # Find kernel, if we don't already have one. + if (!exists $parts{KERNEL}) { + my $name = find_file ('kernel.bin'); + die "Cannot find kernel\n" if !defined $name; + do_set_part ('KERNEL', 'file', $name); + } + + # Try to find file system and swap disks, if we don't already have + # partitions. + if (!exists $parts{FILESYS}) { + my $name = find_file ('filesys.dsk'); + set_disk ($name) if defined $name; + } + if (!exists $parts{SWAP}) { + my $name = find_file ('swap.dsk'); + set_disk ($name) if defined $name; + } + + # Warn about (potentially) missing partitions. + if (my ($task) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) { + if ((grep ($task eq $_, qw (userprog vm filesys))) + && !defined $parts{FILESYS}) { + print STDERR "warning: it looks like you're running the $task "; + print STDERR "task, but no file system partition is present\n"; + } + if ($task eq 'vm' && !defined $parts{SWAP}) { + print STDERR "warning: it looks like you're running the $task "; + print STDERR "task, but no swap partition is present\n"; + } + } + + # Open disk handle. + my ($handle); + if (!defined $make_disk) { + ($handle, $make_disk) = tempfile (UNLINK => $tmp_disk, + SUFFIX => '.dsk'); + } else { + die "$make_disk: already exists\n" if -e $make_disk; + open ($handle, '>', $make_disk) or die "$make_disk: create: $!\n"; + } + + # Prepare the arguments to pass to the PintOS kernel. + my (@args); + push (@args, shift (@kernel_args)) + while @kernel_args && $kernel_args[0] =~ /^-/; + push (@args, 'extract') if @puts; + push (@args, @kernel_args); + push (@args, 'append', $_->[0]) foreach @gets; + + # Make disk. + my (%disk); + our (@role_order); + for my $role (@role_order) { + my $p = $parts{$role}; + next if !defined $p; + next if exists $p->{DISK}; + $disk{$role} = $p; + } + $disk{DISK} = $make_disk; + $disk{HANDLE} = $handle; + $disk{ALIGN} = $align; + $disk{GEOMETRY} = %geometry; + $disk{FORMAT} = 'partitioned'; + $disk{LOADER} = read_loader ($loader_fn); + $disk{ARGS} = \@args; + assemble_disk (%disk); + + # Put the disk at the front of the list of disks. + unshift (@disks, $make_disk); + die "can't use more than " . scalar (@disks) . "disks\n" if @disks > 4; +} + +# Prepare the scratch disk for gets and puts. +sub prepare_scratch_disk { + return if !@gets && !@puts; + + my ($p) = $parts{SCRATCH}; + # Create temporary partition and write the files to put to it, + # then write an end-of-archive marker. + my ($part_handle, $part_fn) = tempfile (UNLINK => 1, SUFFIX => '.part'); + put_scratch_file ($_->[0], defined $_->[1] ? $_->[1] : $_->[0], + $part_handle, $part_fn) + foreach @puts; + write_fully ($part_handle, $part_fn, "\0" x 1024); + + # Make sure the scratch disk is big enough to get big files + # and at least as big as any requested size. + my ($size) = round_up (max (@gets * 1024 * 1024, $p->{BYTES} || 0), 512); + extend_file ($part_handle, $part_fn, $size); + close ($part_handle); + + if (exists $p->{DISK}) { + # Copy the scratch partition to the disk. + die "$p->{DISK}: scratch partition too small\n" + if $p->{SECTORS} * 512 < $size; + + my ($disk_handle); + open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n"; + open ($disk_handle, '+<', $p->{DISK}) or die "$p->{DISK}: open: $!\n"; + my ($start) = $p->{START} * 512; + sysseek ($disk_handle, $start, SEEK_SET) == $start + or die "$p->{DISK}: seek: $!\n"; + copy_file ($part_handle, $part_fn, $disk_handle, $p->{DISK}, $size); + close ($disk_handle) or die "$p->{DISK}: close: $!\n"; + close ($part_handle) or die "$part_fn: close: $!\n"; + } else { + # Set $part_fn as the source for the scratch partition. + do_set_part ('SCRATCH', 'file', $part_fn); + } +} + +# Read "get" files from the scratch disk. +sub finish_scratch_disk { + return if !@gets; + + # Open scratch partition. + my ($p) = $parts{SCRATCH}; + my ($part_handle); + my ($part_fn) = $p->{DISK}; + open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n"; + sysseek ($part_handle, $p->{START} * 512, SEEK_SET) == $p->{START} * 512 + or die "$part_fn: seek: $!\n"; + + # Read each file. + # If reading fails, delete that file and all subsequent files, but + # don't die with an error, because that's a guest error not a host + # error. (If we do exit with an error code, it fouls up the + # grading process.) Instead, just make sure that the host file(s) + # we were supposed to retrieve is unlinked. + my ($ok) = 1; + my ($part_end) = ($p->{START} + $p->{SECTORS}) * 512; + foreach my $get (@gets) { + my ($name) = defined ($get->[1]) ? $get->[1] : $get->[0]; + if ($ok) { + my ($error) = get_scratch_file ($name, $part_handle, $part_fn); + if (!$error && sysseek ($part_handle, 0, SEEK_CUR) > $part_end) { + $error = "$part_fn: scratch data overflows partition"; + } + if ($error) { + print STDERR "getting $name failed ($error)\n"; + $ok = 0; + } + } + die "$name: unlink: $!\n" if !$ok && !unlink ($name) && !$!{ENOENT}; + } +} + +# mk_ustar_field($number, $size) +# +# Returns $number in a $size-byte numeric field in the format used by +# the standard ustar archive header. +sub mk_ustar_field { + my ($number, $size) = @_; + my ($len) = $size - 1; + my ($out) = sprintf ("%0${len}o", $number) . "\0"; + die "$number: too large for $size-byte octal ustar field\n" + if length ($out) != $size; + return $out; +} + +# calc_ustar_chksum($s) +# +# Calculates and returns the ustar checksum of 512-byte ustar archive +# header $s. +sub calc_ustar_chksum { + my ($s) = @_; + die if length ($s) != 512; + substr ($s, 148, 8, ' ' x 8); + return unpack ("%32a*", $s); +} + +# put_scratch_file($src_file_name, $dst_file_name, +# $disk_handle, $disk_file_name). +# +# Copies $src_file_name into $disk_handle for extraction as +# $dst_file_name. $disk_file_name is used for error messages. +sub put_scratch_file { + my ($src_file_name, $dst_file_name, $disk_handle, $disk_file_name) = @_; + + print "Copying $src_file_name to scratch partition...\n"; + + # ustar format supports up to 100 characters for a file name, and + # even longer names given some common properties, but our code in + # the PintOS kernel only supports at most 99 characters. + die "$dst_file_name: name too long (max 99 characters)\n" + if length ($dst_file_name) > 99; + + # Compose and write ustar header. + stat $src_file_name or die "$src_file_name: stat: $!\n"; + my ($size) = -s _; + my ($header) = (pack ("a100", $dst_file_name) # name + . mk_ustar_field (0644, 8) # mode + . mk_ustar_field (0, 8) # uid + . mk_ustar_field (0, 8) # gid + . mk_ustar_field ($size, 12) # size + . mk_ustar_field (1136102400, 12) # mtime + . (' ' x 8) # chksum + . '0' # typeflag + . ("\0" x 100) # linkname + . "ustar\0" # magic + . "00" # version + . "root" . ("\0" x 28) # uname + . "root" . ("\0" x 28) # gname + . "\0" x 8 # devmajor + . "\0" x 8 # devminor + . ("\0" x 155)) # prefix + . "\0" x 12; # pad to 512 bytes + substr ($header, 148, 8) = mk_ustar_field (calc_ustar_chksum ($header), 8); + write_fully ($disk_handle, $disk_file_name, $header); + + # Copy file data. + my ($put_handle); + sysopen ($put_handle, $src_file_name, O_RDONLY) + or die "$src_file_name: open: $!\n"; + copy_file ($put_handle, $src_file_name, $disk_handle, $disk_file_name, + $size); + die "$src_file_name: changed size while being read\n" + if $size != -s $put_handle; + close ($put_handle); + + # Round up disk data to beginning of next sector. + write_fully ($disk_handle, $disk_file_name, "\0" x (512 - $size % 512)) + if $size % 512; +} + +# get_scratch_file($get_file_name, $disk_handle, $disk_file_name) +# +# Copies from $disk_handle to $get_file_name (which is created). +# $disk_file_name is used for error messages. +# Returns 1 if successful, 0 on failure. +sub get_scratch_file { + my ($get_file_name, $disk_handle, $disk_file_name) = @_; + + print "Copying $get_file_name out of $disk_file_name...\n"; + + # Read ustar header sector. + my ($header) = read_fully ($disk_handle, $disk_file_name, 512); + return "scratch disk tar archive ends unexpectedly" + if $header eq ("\0" x 512); + + # Verify magic numbers. + return "corrupt ustar signature" if substr ($header, 257, 6) ne "ustar\0"; + return "invalid ustar version" if substr ($header, 263, 2) ne '00'; + + # Verify checksum. + my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8))); + my ($correct_chksum) = calc_ustar_chksum ($header); + return "checksum mismatch" if $chksum != $correct_chksum; + + # Get type. + my ($typeflag) = substr ($header, 156, 1); + return "not a regular file" if $typeflag ne '0' && $typeflag ne "\0"; + + # Get size. + my ($size) = oct (unpack ("Z*", substr ($header, 124, 12))); + return "bad size $size\n" if $size < 0; + + # Copy file data. + my ($get_handle); + sysopen ($get_handle, $get_file_name, O_WRONLY | O_CREAT, 0666) + or die "$get_file_name: create: $!\n"; + copy_file ($disk_handle, $disk_file_name, $get_handle, $get_file_name, + $size); + close ($get_handle); + + # Skip forward in disk up to beginning of next sector. + read_fully ($disk_handle, $disk_file_name, 512 - $size % 512) + if $size % 512; + + return 0; +} + +# Running simulators. + +# Runs the selected simulator. +sub run_vm { + if ($sim eq 'bochs') { + run_bochs (); + } elsif ($sim eq 'qemu') { + run_qemu (); + } elsif ($sim eq 'player') { + run_player (); + } else { + die "unknown simulator `$sim'\n"; + } +} + +# Runs Bochs. +sub run_bochs { + # Select Bochs binary based on the chosen debugger. + my ($bin) = $debug eq 'monitor' ? 'bochs-dbg' : 'bochs'; + + my ($squish_pty); + if ($serial) { + $squish_pty = find_in_path ("squish-pty"); + print "warning: can't find squish-pty, so terminal input will fail\n" + if !defined $squish_pty; + } + + # Write bochsrc.txt configuration file. + open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n"; + print BOCHSRC < 2; + print_bochs_disk_line ("ata0-master", $disks[0]); + print_bochs_disk_line ("ata0-slave", $disks[1]); + print_bochs_disk_line ("ata1-master", $disks[2]); + print_bochs_disk_line ("ata1-slave", $disks[3]); + if ($vga ne 'terminal') { + if ($serial) { + my $mode = defined ($squish_pty) ? "term" : "file"; + print BOCHSRC "com1: enabled=1, mode=$mode, dev=/dev/stdout\n"; + } + print BOCHSRC "display_library: nogui\n" if $vga eq 'none'; + } else { + print BOCHSRC "display_library: term\n"; + } + close (BOCHSRC); + + # Compose Bochs command line. + my (@cmd) = ($bin, '-q'); + unshift (@cmd, $squish_pty) if defined $squish_pty; + push (@cmd, '-j', $jitter) if defined $jitter; + + # Run Bochs. + print join (' ', @cmd), "\n"; + my ($exit) = xsystem (@cmd); + if (WIFEXITED ($exit)) { + # Bochs exited normally. + # Ignore the exit code; Bochs normally exits with status 1, + # which is weird. + } elsif (WIFSIGNALED ($exit)) { + die "Bochs died with signal ", WTERMSIG ($exit), "\n"; + } else { + die "Bochs died: code $exit\n"; + } +} + +sub print_bochs_disk_line { + my ($device, $disk) = @_; + if (defined $disk) { + my (%geom) = disk_geometry ($disk); + print BOCHSRC "$device: type=disk, path=$disk, mode=flat, "; + print BOCHSRC "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, "; + print BOCHSRC "translation=none\n"; + } +} + +# Runs QEMU. +sub run_qemu { + print "warning: qemu doesn't support --terminal\n" + if $vga eq 'terminal'; + print "warning: qemu doesn't support jitter\n" + if defined $jitter; + my (@cmd) = ('qemu-system-i386'); + push (@cmd, '-drive', 'file='.$disks[0].',index=0,media=disk,format=raw') if defined $disks[0]; + push (@cmd, '-drive', 'file='.$disks[1].',index=1,media=disk,format=raw') if defined $disks[1]; + push (@cmd, '-drive', 'file='.$disks[2].',index=2,media=disk,format=raw') if defined $disks[2]; + push (@cmd, '-drive', 'file='.$disks[3].',index=3,media=disk,format=raw') if defined $disks[3]; + push (@cmd, '-m', $mem); + push (@cmd, '-net', 'none'); + push (@cmd, '-nographic') if $vga eq 'none'; + push (@cmd, '-serial', 'stdio') if $serial && $vga ne 'none'; + push (@cmd, '-S') if $debug eq 'monitor'; + push (@cmd, '-s', '-S') if $debug eq 'gdb'; + push (@cmd, '-monitor', 'null') if $vga eq 'none' && $debug eq 'none'; + run_command (@cmd); +} + +# player_unsup($flag) +# +# Prints a message that $flag is unsupported by VMware Player. +sub player_unsup { + my ($flag) = @_; + print "warning: no support for $flag with VMware Player\n"; +} + +# Runs VMware Player. +sub run_player { + player_unsup ("--$debug") if $debug ne 'none'; + player_unsup ("--no-vga") if $vga eq 'none'; + player_unsup ("--terminal") if $vga eq 'terminal'; + player_unsup ("--jitter") if defined $jitter; + player_unsup ("--timeout"), undef $timeout if defined $timeout; + player_unsup ("--kill-on-failure"), undef $kill_on_failure + if defined $kill_on_failure; + + $mem = round_up ($mem, 4); # Memory must be multiple of 4 MB. + + open (VMX, ">", "pintos.vmx") or die "pintos.vmx: create: $!\n"; + chmod 0777 & ~umask, "pintos.vmx"; + print VMX <", $pln) or die "$pln: create: $!\n"; + print PLN < $size / 512, + C => $cylinders, + H => 16, + S => 63); +} + +# Subprocess utilities. + +# run_command(@args) +# +# Runs xsystem(@args). +# Also prints the command it's running and checks that it succeeded. +sub run_command { + print join (' ', @_), "\n"; + die "command failed\n" if xsystem (@_); +} + +# xsystem(@args) +# +# Creates a subprocess via exec(@args) and waits for it to complete. +# Relays common signals to the subprocess. +# If $timeout is set then the subprocess will be killed after that long. +sub xsystem { + # QEMU turns off local echo and does not restore it if killed by a signal. + # We compensate by restoring it ourselves. + my $cleanup = sub {}; + if (isatty (0)) { + my $termios = POSIX::Termios->new; + $termios->getattr (0); + $cleanup = sub { $termios->setattr (0, &POSIX::TCSANOW); } + } + + # Create pipe for filtering output. + pipe (my $in, my $out) or die "pipe: $!\n" if $kill_on_failure; + + my ($pid) = fork; + if (!defined ($pid)) { + # Fork failed. + die "fork: $!\n"; + } elsif (!$pid) { + # Running in child process. + dup2 (fileno ($out), STDOUT_FILENO) or die "dup2: $!\n" + if $kill_on_failure; + exec_setitimer (@_); + } else { + # Running in parent process. + close $out if $kill_on_failure; + + my ($cause); + local $SIG{ALRM} = sub { timeout ($pid, $cause, $cleanup); }; + local $SIG{INT} = sub { relay_signal ($pid, "INT", $cleanup); }; + local $SIG{TERM} = sub { relay_signal ($pid, "TERM", $cleanup); }; + alarm ($timeout * get_load_average () + 1) if defined ($timeout); + if ($kill_on_failure) { + # Filter output. + my ($buf) = ""; + my ($boots) = 0; + local ($|) = 1; + for (;;) { + if (waitpid ($pid, WNOHANG) != 0) { + # Subprocess died. Pass through any remaining data. + do { print $buf } while sysread ($in, $buf, 4096) > 0; + last; + } + + # Read and print out pipe data. + my ($len) = length ($buf); + waitpid ($pid, 0), last + if sysread ($in, $buf, 4096, $len) <= 0; + print substr ($buf, $len); + + # Remove full lines from $buf and scan them for keywords. + while ((my $idx = index ($buf, "\n")) >= 0) { + local $_ = substr ($buf, 0, $idx + 1, ''); + next if defined ($cause); + if (/(Kernel PANIC|User process ABORT)/ ) { + $cause = "\L$1\E"; + alarm (5); + } elsif (/PintOS booting/ && ++$boots > 1) { + $cause = "triple fault"; + alarm (5); + } elsif (/FAILED/) { + $cause = "test failure"; + alarm (5); + } + } + } + } else { + waitpid ($pid, 0); + } + alarm (0); + &$cleanup (); + + if (WIFSIGNALED ($?) && WTERMSIG ($?) == SIGVTALRM2 ()) { + seek (STDOUT, 0, 2); + print "\nTIMEOUT after $timeout seconds of host CPU time\n"; + exit 0; + } + + return $?; + } +} + +# relay_signal($pid, $signal, &$cleanup) +# +# Relays $signal to $pid and then reinvokes it for us with the default +# handler. Also cleans up temporary files and invokes $cleanup. +sub relay_signal { + my ($pid, $signal, $cleanup) = @_; + kill $signal, $pid; + eval { File::Temp::cleanup() }; # Not defined in old File::Temp. + &$cleanup (); + $SIG{$signal} = 'DEFAULT'; + kill $signal, getpid (); +} + +# timeout($pid, $cause, &$cleanup) +# +# Interrupts $pid and dies with a timeout error message, +# after invoking $cleanup. +sub timeout { + my ($pid, $cause, $cleanup) = @_; + kill "INT", $pid; + waitpid ($pid, 0); + &$cleanup (); + seek (STDOUT, 0, 2); + if (!defined ($cause)) { + my ($load_avg) = `uptime` =~ /(load average:.*)$/i; + print "\nTIMEOUT after ", time () - $start_time, + " seconds of wall-clock time"; + print " - $load_avg" if defined $load_avg; + print "\n"; + } else { + print "Simulation terminated due to $cause.\n"; + } + exit 0; +} + +# Returns the system load average over the last minute. +# If the load average is less than 1.0 or cannot be determined, returns 1.0. +sub get_load_average { + my $avg; + if ($^O eq 'darwin') { + $avg = (split /\s/, `uptime`)[11]; + } else { + $avg = `uptime` =~ /load average:\s*([^,]+),/; + } + return $avg >= 1.0 ? $avg : 1.0; +} + +# Calls setitimer to set a timeout, then execs what was passed to us. +sub exec_setitimer { + if (defined $timeout) { + if ($] ge 5.8.0 && $^O ne 'darwin') { + eval " + use Time::HiRes qw(setitimer ITIMER_VIRTUAL); + setitimer (ITIMER_VIRTUAL, $timeout, 0); + "; + } else { + { exec ("setitimer-helper", $timeout, @_); }; + exit 1 if !$!{ENOENT}; + print STDERR "warning: setitimer-helper is not installed, so ", + "CPU time limit will not be enforced\n"; + } + } + exec (@_); + exit (1); +} + +sub SIGVTALRM2 { + use Config; + my $i = 0; + foreach my $name (split(' ', $Config{sig_name})) { + return $i if $name eq 'VTALRM'; + $i++; + } + return 0; +} + +# find_in_path ($program) +# +# Searches for $program in $ENV{PATH}. +# Returns $program if found, otherwise undef. +sub find_in_path { + my ($program) = @_; + -x "$_/$program" and return $program foreach split (':', $ENV{PATH}); + return; +} diff --git a/tests/devices/src/utils/pintos-gdb b/tests/devices/src/utils/pintos-gdb new file mode 100755 index 0000000..a89b33c --- /dev/null +++ b/tests/devices/src/utils/pintos-gdb @@ -0,0 +1,28 @@ +#! /bin/sh + +# Expects to be called from with a given task's build directory + +# Relative path to GDB macros file within PintOS repo. +# Customize for your machine if required. +GDBMACROS=../../misc/gdb-macros + +# Choose correct GDB for the current environment: +GDB=gdb + +UNAME_S=`uname -s` +if test "$UNAME_S" = "Darwin"; then + GDB=i686-elf-gdb +fi + +if command -v i386-elf-gdb >/dev/null 2>&1; then + GDB=i386-elf-gdb +fi + +# Run GDB. +if test -f "$GDBMACROS"; then + exec $GDB -x "$GDBMACROS" "$@" +else + echo "*** $GDBMACROS does not exist ***" + echo "*** PintOS GDB macros will not be available ***" + exec $GDB "$@" +fi diff --git a/tests/devices/src/utils/pintos-mkdisk b/tests/devices/src/utils/pintos-mkdisk new file mode 100755 index 0000000..d862dd7 --- /dev/null +++ b/tests/devices/src/utils/pintos-mkdisk @@ -0,0 +1,134 @@ +#! /usr/bin/perl + +use strict; +use warnings; +use POSIX; +use Getopt::Long qw(:config bundling); +use Fcntl 'SEEK_SET'; + +# Read Pintos.pm from the same directory as this program. +BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } + +our ($disk_fn); # Output disk file name. +our (%parts); # Partitions. +our ($format); # "partitioned" (default) or "raw" +our (%geometry); # IDE disk geometry. +our ($align); # Align partitions on cylinders? +our ($loader_fn); # File name of loader. +our ($include_loader); # Include loader? +our (@kernel_args); # Kernel arguments. + +if (grep ($_ eq '--', @ARGV)) { + @kernel_args = @ARGV; + @ARGV = (); + while ((my $arg = shift (@kernel_args)) ne '--') { + push (@ARGV, $arg); + } +} + +GetOptions ("h|help" => sub { usage (0); }, + + "kernel=s" => \&set_part, + "filesys=s" => \&set_part, + "scratch=s" => \&set_part, + "swap=s" => \&set_part, + + "filesys-size=s" => \&set_part, + "scratch-size=s" => \&set_part, + "swap-size=s" => \&set_part, + + "kernel-from=s" => \&set_part, + "filesys-from=s" => \&set_part, + "scratch-from=s" => \&set_part, + "swap-from=s" => \&set_part, + + "format=s" => \$format, + "loader:s" => \&set_loader, + "no-loader" => \&set_no_loader, + "geometry=s" => \&set_geometry, + "align=s" => \&set_align) + or exit 1; +usage (1) if @ARGV != 1; + +$disk_fn = $ARGV[0]; +die "$disk_fn: already exists\n" if -e $disk_fn; + +# Sets the loader to copy to the MBR. +sub set_loader { + die "can't specify both --loader and --no-loader\n" + if defined ($include_loader) && !$include_loader; + $include_loader = 1; + $loader_fn = $_[1] if $_[1] ne ''; +} + +# Disables copying a loader to the MBR. +sub set_no_loader { + die "can't specify both --loader and --no-loader\n" + if defined ($include_loader) && $include_loader; + $include_loader = 0; +} + +# Figure out whether to include a loader. +$include_loader = exists ($parts{KERNEL}) && $format eq 'partitioned' + if !defined ($include_loader); +die "can't write loader to raw disk\n" if $include_loader && $format eq 'raw'; +die "can't write command-line arguments without --loader or --kernel\n" + if @kernel_args && !$include_loader; +print STDERR "warning: --loader only makes sense without --kernel " + . "if this disk will be used to load a kernel from another disk\n" + if $include_loader && !exists ($parts{KERNEL}); + +# Open disk. +my ($disk_handle); +open ($disk_handle, '>', $disk_fn) or die "$disk_fn: create: $!\n"; + +# Read loader. +my ($loader); +$loader = read_loader ($loader_fn) if $include_loader; + +# Write disk. +my (%disk) = %parts; +$disk{DISK} = $disk_fn; +$disk{HANDLE} = $disk_handle; +$disk{ALIGN} = $align; +$disk{GEOMETRY} = %geometry; +$disk{FORMAT} = $format; +$disk{LOADER} = $loader; +$disk{ARGS} = \@kernel_args; +assemble_disk (%disk); + +# Done. +exit 0; + +sub usage { + print <<'EOF'; +pintos-mkdisk, a utility for creating PintOS virtual disks +Usage: pintos-mkdisk [OPTIONS] DISK [-- ARGUMENT...] +where DISK is the virtual disk to create, + each ARGUMENT is inserted into the command line written to DISK, + and each OPTION is one of the following options. +Partition options: (where PARTITION is one of: kernel filesys scratch swap) + --PARTITION=FILE Use a copy of FILE for the given PARTITION + --PARTITION-size=SIZE Create an empty PARTITION of the given SIZE in MB + --PARTITION-from=DISK Use of a copy of the given PARTITION in DISK + (There is no --kernel-size option.) +Output disk options: + --format=partitioned Write partition table to output (default) + --format=raw Do not write partition table to output + (PintOS can only use partitioned disks.) +Partitioned format output options: + --loader[=FILE] Get bootstrap loader from FILE (default: loader.bin + if --kernel option is specified, empty otherwise) + --no-loader Do not include a bootstrap loader + --geometry=H,S Use H head, S sector geometry (default: 16, 63) + --geometry=zip Use 64 head, 32 sector geometry for USB-ZIP boot + per http://syslinux.zytor.com/usbkey.php + --align=bochs Round size to cylinder for Bochs support (default) + --align=full Align partition boundaries to cylinder boundary to + let fdisk guess correct geometry and quiet warnings + --align=none Don't align partitions at all, to save space +Other options: + -h, --help Display this help message. +EOF + exit ($_[0]); +} diff --git a/tests/devices/src/utils/pintos-set-cmdline b/tests/devices/src/utils/pintos-set-cmdline new file mode 100644 index 0000000..7a8882c --- /dev/null +++ b/tests/devices/src/utils/pintos-set-cmdline @@ -0,0 +1,42 @@ +#! /usr/bin/perl -w + +use strict; +use Fcntl 'SEEK_SET'; + +# Read Pintos.pm from the same directory as this program. +BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } + +# Get command-line arguments. +usage (0) if @ARGV == 1 && $ARGV[0] eq '--help'; +usage (1) if @ARGV < 2 || $ARGV[1] ne '--'; +my ($disk, undef, @kernel_args) = @ARGV; + +# Open disk. +my ($handle); +open ($handle, '+<', $disk) or die "$disk: open: $!\n"; + +# Check that it's a partitioned disk with a PintOS loader. +my ($buffer) = read_fully ($handle, $disk, 512); +unpack ("x510 v", $buffer) == 0xaa55 or die "$disk: not a partitioned disk\n"; +$buffer =~ /PintOS/ or die "$disk: does not contain PintOS loader\n"; + +# Write the command line. +our ($LOADER_SIZE); +sysseek ($handle, $LOADER_SIZE, SEEK_SET) == $LOADER_SIZE + or die "$disk: seek: $!\n"; +write_fully ($handle, $disk, make_kernel_command_line (@kernel_args)); + +# Close disk. +close ($handle) or die "$disk: close: $!\n"; + +exit 0; + +sub usage { + print <<'EOF'; +pintos-set-cmdline, a utility for changing the command line in PintOS disks +Usage: pintos-set-cmdline DISK -- [ARGUMENT...] +where DISK is a bootable disk containing a PintOS loader + and each ARGUMENT is inserted into the command line written to DISK. +EOF + exit ($_[0]); +} diff --git a/tests/devices/src/utils/setitimer-helper.c b/tests/devices/src/utils/setitimer-helper.c new file mode 100644 index 0000000..772d736 --- /dev/null +++ b/tests/devices/src/utils/setitimer-helper.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + const char *program_name = argv[0]; + double timeout; + + if (argc < 3) + { + fprintf (stderr, + "setitimer-helper: runs a program with a virtual CPU limit\n" + "usage: %s TIMEOUT PROGRAM [ARG...]\n" + " where TIMEOUT is the virtual CPU limit, in seconds,\n" + " and remaining arguments specify the program to run\n" + " and its argument.\n", + program_name); + return EXIT_FAILURE; + } + + timeout = strtod (argv[1], NULL); + if (timeout >= 0.0 && timeout < LONG_MAX) + { + struct itimerval it; + + it.it_interval.tv_sec = 0; + it.it_interval.tv_usec = 0; + it.it_value.tv_sec = timeout; + it.it_value.tv_usec = (timeout - floor (timeout)) * 1000000; + if (setitimer (ITIMER_VIRTUAL, &it, NULL) < 0) + fprintf (stderr, "%s: setitimer: %s\n", + program_name, strerror (errno)); + } + else + fprintf (stderr, "%s: invalid timeout value \"%s\"\n", + program_name, argv[1]); + + execvp (argv[2], &argv[2]); + fprintf (stderr, "%s: couldn't exec \"%s\": %s\n", + program_name, argv[2], strerror (errno)); + return EXIT_FAILURE; +} diff --git a/tests/devices/src/utils/squish-pty.c b/tests/devices/src/utils/squish-pty.c new file mode 100644 index 0000000..3fdddde --- /dev/null +++ b/tests/devices/src/utils/squish-pty.c @@ -0,0 +1,345 @@ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void +fail_io (const char *msg, ...) + __attribute__ ((noreturn)) + __attribute__ ((format (printf, 1, 2))); + +/* Prints MSG, formatting as with printf(), + plus an error message based on errno, + and exits. */ +static void +fail_io (const char *msg, ...) +{ + va_list args; + + va_start (args, msg); + vfprintf (stderr, msg, args); + va_end (args); + + if (errno != 0) + fprintf (stderr, ": %s", strerror (errno)); + putc ('\n', stderr); + exit (EXIT_FAILURE); +} + +/* If FD is a terminal, configures it for noncanonical input mode + with VMIN and VTIME set as indicated. + If FD is not a terminal, has no effect. */ +static void +make_noncanon (int fd, int vmin, int vtime) +{ + if (isatty (fd)) + { + struct termios termios; + if (tcgetattr (fd, &termios) < 0) + fail_io ("tcgetattr"); + termios.c_lflag &= ~(ICANON | ECHO); + termios.c_cc[VMIN] = vmin; + termios.c_cc[VTIME] = vtime; + if (tcsetattr (fd, TCSANOW, &termios) < 0) + fail_io ("tcsetattr"); + } +} + +/* Make FD non-blocking if NONBLOCKING is true, + or blocking if NONBLOCKING is false. */ +static void +make_nonblocking (int fd, bool nonblocking) +{ + int flags = fcntl (fd, F_GETFL); + if (flags < 0) + fail_io ("fcntl"); + if (nonblocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + if (fcntl (fd, F_SETFL, flags) < 0) + fail_io ("fcntl"); +} + +/* Handle a read or write on *FD, which is the pty if FD_IS_PTY + is true, that returned end-of-file or error indication RETVAL. + The system call is named CALL, for use in error messages. + Returns true if processing may continue, false if we're all + done. */ +static bool +handle_error (ssize_t retval, int *fd, bool fd_is_pty, const char *call) +{ + if (fd_is_pty) + { + if (retval < 0) + { + if (errno == EIO) + { + /* Slave side of pty has been closed. */ + return false; + } + else + fail_io ("%s", call); + } + else + return true; + } + else + { + if (retval == 0) + { + close (*fd); + *fd = -1; + return true; + } + else + fail_io ("%s", call); + } +} + +/* Copies data from stdin to PTY and from PTY to stdout until no + more data can be read or written. */ +static void +relay (int pty, int dead_child_fd) +{ + struct pipe + { + int in, out; + char buf[BUFSIZ]; + size_t size, ofs; + bool active; + }; + struct pipe pipes[2]; + + /* Make PTY, stdin, and stdout non-blocking. */ + make_nonblocking (pty, true); + make_nonblocking (STDIN_FILENO, true); + make_nonblocking (STDOUT_FILENO, true); + + /* Configure noncanonical mode on PTY and stdin to avoid + waiting for end-of-line. We want to minimize context + switching on PTY (for efficiency) and minimize latency on + stdin to avoid a laggy user experience. */ + make_noncanon (pty, 16, 1); + make_noncanon (STDIN_FILENO, 1, 0); + + memset (pipes, 0, sizeof pipes); + pipes[0].in = STDIN_FILENO; + pipes[0].out = pty; + pipes[1].in = pty; + pipes[1].out = STDOUT_FILENO; + + while (pipes[0].in != -1 || pipes[1].in != -1) + { + fd_set read_fds, write_fds; + int retval; + int i; + + FD_ZERO (&read_fds); + FD_ZERO (&write_fds); + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + + /* Don't do anything with the stdin->pty pipe until we + have some data for the pty->stdout pipe. If we get + too eager, Bochs will throw away our input. */ + if (i == 0 && !pipes[1].active) + continue; + + if (p->in != -1 && p->size + p->ofs < sizeof p->buf) + FD_SET (p->in, &read_fds); + if (p->out != -1 && p->size > 0) + FD_SET (p->out, &write_fds); + } + FD_SET (dead_child_fd, &read_fds); + + do + { + retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL); + } + while (retval < 0 && errno == EINTR); + if (retval < 0) + fail_io ("select"); + + if (FD_ISSET (dead_child_fd, &read_fds)) + { + /* Child died. Do final relaying. */ + struct pipe *p = &pipes[1]; + if (p->out == -1) + return; + make_nonblocking (STDOUT_FILENO, false); + for (;;) + { + ssize_t n; + + /* Write buffer. */ + while (p->size > 0) + { + n = write (p->out, p->buf + p->ofs, p->size); + if (n < 0) + fail_io ("write"); + else if (n == 0) + fail_io ("zero-length write"); + p->ofs += n; + p->size -= n; + } + p->ofs = 0; + + p->size = n = read (p->in, p->buf, sizeof p->buf); + if (n <= 0) + return; + } + } + + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + if (p->in != -1 && FD_ISSET (p->in, &read_fds)) + { + ssize_t n = read (p->in, p->buf + p->ofs + p->size, + sizeof p->buf - p->ofs - p->size); + if (n > 0) + { + p->active = true; + p->size += n; + if (p->size == BUFSIZ && p->ofs != 0) + { + memmove (p->buf, p->buf + p->ofs, p->size); + p->ofs = 0; + } + } + else if (!handle_error (n, &p->in, p->in == pty, "read")) + return; + } + if (p->out != -1 && FD_ISSET (p->out, &write_fds)) + { + ssize_t n = write (p->out, p->buf + p->ofs, p->size); + if (n > 0) + { + p->ofs += n; + p->size -= n; + if (p->size == 0) + p->ofs = 0; + } + else if (!handle_error (n, &p->out, p->out == pty, "write")) + return; + } + } + } +} + +static int dead_child_fd; + +static void +sigchld_handler (int signo __attribute__ ((unused))) +{ + if (write (dead_child_fd, "", 1) < 0) + _exit (1); +} + +int +main (int argc __attribute__ ((unused)), char *argv[]) +{ + int master, slave; + char *name; + pid_t pid; + struct sigaction sa; + int pipe_fds[2]; + struct itimerval zero_itimerval, old_itimerval; + + if (argc < 2) + { + fprintf (stderr, + "usage: squish-pty COMMAND [ARG]...\n" + "Squishes both stdin and stdout into a single pseudoterminal,\n" + "which is passed as stdout to run the specified COMMAND.\n"); + return EXIT_FAILURE; + } + + /* Open master side of pty and get ready to open slave. */ + master = open ("/dev/ptmx", O_RDWR | O_NOCTTY); + if (master < 0) + fail_io ("open \"/dev/ptmx\""); + if (grantpt (master) < 0) + fail_io ("grantpt"); + if (unlockpt (master) < 0) + fail_io ("unlockpt"); + + /* Open slave side of pty. */ + name = ptsname (master); + if (name == NULL) + fail_io ("ptsname"); + slave = open (name, O_RDWR); + if (slave < 0) + fail_io ("open \"%s\"", name); + + /* Arrange to get notified when a child dies, by writing a byte + to a pipe fd. We really want to use pselect() and + sigprocmask(), but Solaris 2.7 doesn't have it. */ + if (pipe (pipe_fds) < 0) + fail_io ("pipe"); + dead_child_fd = pipe_fds[1]; + + memset (&sa, 0, sizeof sa); + sa.sa_handler = sigchld_handler; + sigemptyset (&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction (SIGCHLD, &sa, NULL) < 0) + fail_io ("sigaction"); + + /* Save the virtual interval timer, which might have been set + by the process that ran us. It really should be applied to + our child process. */ + memset (&zero_itimerval, 0, sizeof zero_itimerval); + if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, &old_itimerval) < 0) + fail_io ("setitimer"); + + pid = fork (); + if (pid < 0) + fail_io ("fork"); + else if (pid != 0) + { + /* Running in parent process. */ + int status; + close (slave); + relay (master, pipe_fds[0]); + + /* If the subprocess has died, die in the same fashion. + In particular, dying from SIGVTALRM tells the pintos + script that we ran out of CPU time. */ + if (waitpid (pid, &status, WNOHANG) > 0) + { + if (WIFEXITED (status)) + return WEXITSTATUS (status); + else if (WIFSIGNALED (status)) + raise (WTERMSIG (status)); + } + return 0; + } + else + { + /* Running in child process. */ + if (setitimer (ITIMER_VIRTUAL, &old_itimerval, NULL) < 0) + fail_io ("setitimer"); + if (dup2 (slave, STDOUT_FILENO) < 0) + fail_io ("dup2"); + if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0 + || close (slave) < 0 || close (master) < 0) + fail_io ("close"); + execvp (argv[1], argv + 1); + fail_io ("exec"); + } +} diff --git a/tests/devices/src/utils/squish-unix.c b/tests/devices/src/utils/squish-unix.c new file mode 100644 index 0000000..07d785f --- /dev/null +++ b/tests/devices/src/utils/squish-unix.c @@ -0,0 +1,337 @@ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void +fail_io (const char *msg, ...) + __attribute__ ((noreturn)) + __attribute__ ((format (printf, 1, 2))); + +/* Prints MSG, formatting as with printf(), + plus an error message based on errno, + and exits. */ +static void +fail_io (const char *msg, ...) +{ + va_list args; + + va_start (args, msg); + vfprintf (stderr, msg, args); + va_end (args); + + if (errno != 0) + fprintf (stderr, ": %s", strerror (errno)); + putc ('\n', stderr); + exit (EXIT_FAILURE); +} + +/* If FD is a terminal, configures it for noncanonical input mode + with VMIN and VTIME set as indicated. + If FD is not a terminal, has no effect. */ +static void +make_noncanon (int fd, int vmin, int vtime) +{ + if (isatty (fd)) + { + struct termios termios; + if (tcgetattr (fd, &termios) < 0) + fail_io ("tcgetattr"); + termios.c_lflag &= ~(ICANON | ECHO); + termios.c_cc[VMIN] = vmin; + termios.c_cc[VTIME] = vtime; + if (tcsetattr (fd, TCSANOW, &termios) < 0) + fail_io ("tcsetattr"); + } +} + +/* Make FD non-blocking if NONBLOCKING is true, + or blocking if NONBLOCKING is false. */ +static void +make_nonblocking (int fd, bool nonblocking) +{ + int flags = fcntl (fd, F_GETFL); + if (flags < 0) + fail_io ("fcntl"); + if (nonblocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + if (fcntl (fd, F_SETFL, flags) < 0) + fail_io ("fcntl"); +} + +/* Handle a read or write on *FD, which is the socket if + FD_IS_SOCK is true, that returned end-of-file or error + indication RETVAL. The system call is named CALL, for use in + error messages. Returns true if processing may continue, + false if we're all done. */ +static bool +handle_error (ssize_t retval, int *fd, bool fd_is_sock, const char *call) +{ + if (retval == 0) + { + if (fd_is_sock) + return false; + else + { + *fd = -1; + return true; + } + } + else + fail_io ("%s", call); +} + +/* Copies data from stdin to SOCK and from SOCK to stdout until no + more data can be read or written. */ +static void +relay (int sock) +{ + struct pipe + { + int in, out; + char buf[BUFSIZ]; + size_t size, ofs; + bool active; + }; + struct pipe pipes[2]; + + /* In case stdin is a file, go back to the beginning. + This allows replaying the input on reset. */ + lseek (STDIN_FILENO, 0, SEEK_SET); + + /* Make SOCK, stdin, and stdout non-blocking. */ + make_nonblocking (sock, true); + make_nonblocking (STDIN_FILENO, true); + make_nonblocking (STDOUT_FILENO, true); + + /* Configure noncanonical mode on stdin to avoid waiting for + end-of-line. */ + make_noncanon (STDIN_FILENO, 1, 0); + + memset (pipes, 0, sizeof pipes); + pipes[0].in = STDIN_FILENO; + pipes[0].out = sock; + pipes[1].in = sock; + pipes[1].out = STDOUT_FILENO; + + while (pipes[0].in != -1 || pipes[1].in != -1 + || (pipes[1].size && pipes[1].out != -1)) + { + fd_set read_fds, write_fds; + sigset_t empty_set; + int retval; + int i; + + FD_ZERO (&read_fds); + FD_ZERO (&write_fds); + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + + /* Don't do anything with the stdin->sock pipe until we + have some data for the sock->stdout pipe. If we get + too eager, vmplayer will throw away our input. */ + if (i == 0 && !pipes[1].active) + continue; + + if (p->in != -1 && p->size + p->ofs < sizeof p->buf) + FD_SET (p->in, &read_fds); + if (p->out != -1 && p->size > 0) + FD_SET (p->out, &write_fds); + } + sigemptyset (&empty_set); + retval = pselect (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL, + &empty_set); + if (retval < 0) + { + if (errno == EINTR) + { + /* Child died. Do final relaying. */ + struct pipe *p = &pipes[1]; + if (p->out == -1) + exit (0); + make_nonblocking (STDOUT_FILENO, false); + for (;;) + { + ssize_t n; + + /* Write buffer. */ + while (p->size > 0) + { + n = write (p->out, p->buf + p->ofs, p->size); + if (n < 0) + fail_io ("write"); + else if (n == 0) + fail_io ("zero-length write"); + p->ofs += n; + p->size -= n; + } + p->ofs = 0; + + p->size = n = read (p->in, p->buf, sizeof p->buf); + if (n <= 0) + exit (0); + } + } + fail_io ("select"); + } + + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + if (p->in != -1 && FD_ISSET (p->in, &read_fds)) + { + ssize_t n = read (p->in, p->buf + p->ofs + p->size, + sizeof p->buf - p->ofs - p->size); + if (n > 0) + { + p->active = true; + p->size += n; + if (p->size == BUFSIZ && p->ofs != 0) + { + memmove (p->buf, p->buf + p->ofs, p->size); + p->ofs = 0; + } + } + else if (!handle_error (n, &p->in, p->in == sock, "read")) + return; + } + if (p->out != -1 && FD_ISSET (p->out, &write_fds)) + { + ssize_t n = write (p->out, p->buf + p->ofs, p->size); + if (n > 0) + { + p->ofs += n; + p->size -= n; + if (p->size == 0) + p->ofs = 0; + } + else if (!handle_error (n, &p->out, p->out == sock, "write")) + return; + } + } + } +} + +static void +sigchld_handler (int signo __attribute__ ((unused))) +{ + /* Nothing to do. */ +} + +int +main (int argc __attribute__ ((unused)), char *argv[]) +{ + pid_t pid; + struct itimerval zero_itimerval; + struct sockaddr_un sun; + sigset_t sigchld_set; + int sock; + + if (argc < 3) + { + fprintf (stderr, + "usage: squish-unix SOCKET COMMAND [ARG]...\n" + "Squishes both stdin and stdout into a single Unix domain\n" + "socket named SOCKET, and runs COMMAND as a subprocess.\n"); + return EXIT_FAILURE; + } + + /* Create socket. */ + sock = socket (PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) + fail_io ("socket"); + + /* Configure socket. */ + sun.sun_family = AF_LOCAL; + strncpy (sun.sun_path, argv[1], sizeof sun.sun_path); + sun.sun_path[sizeof sun.sun_path - 1] = '\0'; + if (unlink (sun.sun_path) < 0 && errno != ENOENT) + fail_io ("unlink"); + if (bind (sock, (struct sockaddr *) &sun, + (offsetof (struct sockaddr_un, sun_path) + + strlen (sun.sun_path) + 1)) < 0) + fail_io ("bind"); + + /* Listen on socket. */ + if (listen (sock, 1) < 0) + fail_io ("listen"); + + /* Block SIGCHLD and set up a handler for it. */ + sigemptyset (&sigchld_set); + sigaddset (&sigchld_set, SIGCHLD); + if (sigprocmask (SIG_BLOCK, &sigchld_set, NULL) < 0) + fail_io ("sigprocmask"); + if (signal (SIGCHLD, sigchld_handler) == SIG_ERR) + fail_io ("signal"); + + /* Save the virtual interval timer, which might have been set + by the process that ran us. It really should be applied to + our child process. */ + memset (&zero_itimerval, 0, sizeof zero_itimerval); + if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, NULL) < 0) + fail_io ("setitimer"); + + pid = fork (); + if (pid < 0) + fail_io ("fork"); + else if (pid != 0) + { + /* Running in parent process. */ + make_nonblocking (sock, true); + for (;;) + { + fd_set read_fds; + sigset_t empty_set; + int retval; + int conn; + + /* Wait for connection. */ + FD_ZERO (&read_fds); + FD_SET (sock, &read_fds); + sigemptyset (&empty_set); + retval = pselect (sock + 1, &read_fds, NULL, NULL, NULL, &empty_set); + if (retval < 0) + { + if (errno == EINTR) + break; + fail_io ("select"); + } + + /* Accept connection. */ + conn = accept (sock, NULL, NULL); + if (conn < 0) + fail_io ("accept"); + + /* Relay connection. */ + relay (conn); + close (conn); + } + return 0; + } + else + { + /* Running in child process. */ + if (close (sock) < 0) + fail_io ("close"); + execvp (argv[2], argv + 2); + fail_io ("exec"); + } +}