├── .gitattributes ├── .gitignore ├── GNUmakefile ├── README.md ├── boot.cc ├── boot.ld ├── bootentry.S ├── build ├── chickadee.gdb ├── chickadeefsck.cc ├── findgcc.sh ├── findprocesses.awk ├── flags.mk ├── functions.gdb ├── mkchickadeefs.cc ├── mkchickadeesymtab.cc ├── mkinitfs.awk ├── mkkernelasm.awk ├── qemu-nograb.c ├── quietobjcopy.sh └── rules.mk ├── cbyteswap.hh ├── chickadeefs.hh ├── crc32c.cc ├── diskfs ├── dickinson.txt ├── thoreau.txt └── wheatley.txt ├── docker ├── Dockerfile ├── Dockerfile.arm64 ├── README.md ├── build-docker └── ubuntu-arm64.sources ├── elf.h ├── initfs └── emerson.txt ├── journalreplayer.cc ├── k-ahci.cc ├── k-ahci.hh ├── k-alloc.cc ├── k-apic.hh ├── k-chkfs.cc ├── k-chkfs.hh ├── k-chkfsiter.cc ├── k-chkfsiter.hh ├── k-cpu.cc ├── k-devices.cc ├── k-devices.hh ├── k-exception.S ├── k-hardware.cc ├── k-init.cc ├── k-list.hh ├── k-lock.hh ├── k-memrange.hh ├── k-memviewer.cc ├── k-mpspec.cc ├── k-pci.hh ├── k-proc.cc ├── k-sanitizers.cc ├── k-testwait.cc ├── k-vmiter.cc ├── k-vmiter.hh ├── k-wait.hh ├── k-waitstruct.hh ├── kernel.cc ├── kernel.hh ├── kernel.ld ├── lib.cc ├── lib.hh ├── p-allocator.cc ├── p-allocexit.cc ├── p-cat.cc ├── p-echo.cc ├── p-execallocexit.cc ├── p-exececho.cc ├── p-false.cc ├── p-ktestwait.cc ├── p-nastyalloc.cc ├── p-readdiskfile.cc ├── p-sh.cc ├── p-sleep.cc ├── p-testeintr.cc ├── p-testforksimple.cc ├── p-testgetusage.cc ├── p-testhalt.cc ├── p-testkalloc.cc ├── p-testmemfs.cc ├── p-testmsleep.cc ├── p-testpipe.cc ├── p-testppid.cc ├── p-testrwaddr.cc ├── p-testthread.cc ├── p-testvfs.cc ├── p-testwaitpid.cc ├── p-testwritefs.cc ├── p-testwritefs2.cc ├── p-testwritefs3.cc ├── p-testwritefs4.cc ├── p-testzombie.cc ├── p-true.cc ├── p-wc.cc ├── p-wcdiskfile.cc ├── process.ld ├── psets ├── pset1answers.md ├── pset1collab.md ├── pset2answers.md ├── pset2collab.md ├── pset3answers.md ├── pset3collab.md ├── pset3vfs.md ├── pset4answers.md ├── pset4collab.md ├── pset5answers.md └── pset5collab.md ├── run-docker ├── types.h ├── u-lib.cc ├── u-lib.hh └── x86-64.h /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf 2 | run-docker text eol=lf 3 | build-docker text eol=lf 4 | initfs/*.txt text eol=lf 5 | diskfs/*.txt text eol=lf 6 | *.cc diff=cpp 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #*# 2 | *.aux 3 | *.backup 4 | *.bak 5 | *.core 6 | *.dSYM 7 | *.dvi 8 | *.gcda 9 | *.gch 10 | *.icloud 11 | *.img 12 | *.log 13 | *.noopt 14 | *.o 15 | *.optimized 16 | *.pch 17 | *.swp 18 | *.unsafe 19 | *.xcodeproj 20 | *~ 21 | .DS_Store 22 | .cproject 23 | .deps 24 | .gitcheckout 25 | .goutputstream-* 26 | .hg 27 | .project 28 | .svn 29 | .cs61tmpid 30 | .vscode 31 | a.out 32 | cmake-build-* 33 | config.mk 34 | core* 35 | cscope.out 36 | debuglog.txt 37 | log.txt 38 | obj 39 | pset.tgz 40 | pset[1-9].tgz 41 | pset[1-9]grade.tgz 42 | strace.out 43 | tags 44 | tags.* 45 | typescript 46 | vgcore* 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chickadee OS 2 | ============ 3 | 4 | This is Chickadee, a teaching operating system built for Harvard’s 5 | [CS 161]. 6 | 7 | Quickstart: `make run` or `make run-PROGRAM` will run the OS using the 8 | [QEMU] emulator. 9 | 10 | Make targets 11 | ------------ 12 | 13 | `make NCPU=N run` runs the OS with `N` virtual CPUs (default is 2). Close 14 | the QEMU window, or type `q` inside it, to exit the OS. 15 | 16 | `make run-console` runs the OS in the console window. 17 | 18 | `make SAN=1 run` runs with sanitizers enabled. 19 | 20 | Chickadee’s debug log is written to `log.txt` by default. `make 21 | LOG=stdio run` redirects the debug log to the standard output, and 22 | `make LOG=file:FILENAME run` redirects it to `FILENAME`. 23 | 24 | `make D=1 run` tells QEMU to print verbose information about interrupts and 25 | CPU resets to the file `qemu.log`. This setting will also cause QEMU to quit 26 | after encountering a [triple fault][] (normally it will reboot). 27 | 28 | `make run-PROGRAM` runs `p-PROGRAM.cc` as the first non-init process. The 29 | default is `alloc`. 30 | 31 | `make HALT=10 run-PROGRAM` should make QEMU exit 10 centiseconds (0.1 sec) 32 | after all processes exit. 33 | 34 | Chickadee process executables are built from files named `p-*.cc`. If a 35 | process source file starts with `#define CHICKADEE_OPTIONAL_PROCESS 1`, then 36 | it is included only on explicit request (e.g., `make run-testforksimple`). 37 | 38 | Troubleshooting 39 | --------------- 40 | 41 | There are several ways to kill a recalcitrant QEMU (for instance, if your 42 | OS has become unresponsive). 43 | 44 | * If QEMU is running in its own graphical window, then close the window. This 45 | will kill the embedded OS. 46 | 47 | * If QEMU is running in a terminal window (in Docker, for instance), then 48 | press `Alt-2` (or `Option-2`). This will bring up the QEMU Monitor, which 49 | looks like this: 50 | 51 | ``` 52 | compat_monitor0 console 53 | QEMU 4.2.0 monitor - type 'help' for more information 54 | (qemu) 55 | ``` 56 | 57 | Type `quit` and hit Return to kill the embedded OS and return to your 58 | shell. If this leaves the terminal looking funny, enter the `reset` shell 59 | command to restore it. 60 | 61 | If `Alt-2` does not work, you may need to configure your terminal to 62 | properly send the Alt key. For instance, on Mac OS X’s Terminal, go to the 63 | Edit menu, select “Use Option as Meta key”, and press `Option-2`. You can 64 | also configure a special keyboard shortcut that sends the `Escape 2` 65 | sequence. 66 | 67 | * Run `make stop` in another terminal. This will kill all QEMU processes you 68 | own. (If you’re using Docker, this other terminal must be open to the same 69 | Docker instance.) 70 | 71 | Run `make run-gdb` to start up the OS with support for GDB debugging. This 72 | will start the OS, but not GDB. You must run `gdb -ix build/chickadee.gdb` to 73 | connect to the running emulator; when GDB connects, it will stop the OS and 74 | wait for instructions. 75 | 76 | If you experience runtime errors involving `obj/libqemu-nograb.so.1`, put 77 | `QEMU_PRELOAD_LIBRARY=` in `config.mk`. This disables a shim we use that 78 | prevents QEMU from grabbing the mouse. 79 | 80 | Source files 81 | ------------ 82 | 83 | ### Common files 84 | 85 | | File | Description | 86 | | --------------- | -------------------------------------- | 87 | | `types.h` | Type definitions | 88 | | `lib.hh/cc` | C library | 89 | | `x86-64.h` | x86-64 hardware definitions | 90 | | `elf.h` | ELF64 structures for loading programs | 91 | 92 | ### Boot loader 93 | 94 | | File | Description | 95 | | --------------- | ---------------------------- | 96 | | `bootentry.S` | Boot loader entry point | 97 | | `boot.cc` | Boot loader main code | 98 | | `boot.ld` | Boot loader linker script | 99 | 100 | ### Kernel core 101 | 102 | | File | Description | 103 | | ------------------- | ------------------------------------ | 104 | | `kernel.hh` | Kernel declarations | 105 | | `k-exception.S` | Kernel entry points | 106 | | `k-init.cc` | Kernel initialization | 107 | | `k-lock.hh` | Kernel spinlock | 108 | | `k-vmiter.hh/cc` | Page table iterators | 109 | | `k-cpu.cc` | Kernel `cpustate` type | 110 | | `k-proc.cc` | Kernel `proc` type | 111 | | `kernel.cc` | Kernel exception handlers | 112 | | `k-memviewer.cc` | Kernel memory viewer | 113 | | `kernel.ld` | Kernel linker script | 114 | 115 | ### Kernel libraries 116 | 117 | | File | Description | 118 | | ------------------- | ------------------------------------ | 119 | | `k-memrange.hh` | Memory range type tracker | 120 | | `k-hardware.cc` | General hardware access | 121 | | `k-devices.hh/cc` | Keyboard, console, memory files | 122 | | `k-apic.hh/cc` | Interrupt controller hardware | 123 | | `k-pci.hh` | PCI bus hardware | 124 | | `k-mpspec.cc` | Boot-time configuration | 125 | | `k-sanitizers.cc` | Sanitizer support | 126 | 127 | ### Processes 128 | 129 | | File | Description | 130 | | ----------------- | ------------------------------------------------ | 131 | | `u-lib.cc/hh` | Process library and system call implementations | 132 | | `p-allocator.cc` | Allocator process | 133 | | `process.ld` | Process binary linker script | 134 | 135 | ### File system 136 | 137 | | File | Description | 138 | | --------------------- | ------------------------------------------------ | 139 | | `chickadeefs.hh` | Defines chkfs (ChickadeeFS) layout | 140 | | `journalreplayer.cc` | Logic for replaying chkfs journals | 141 | 142 | Build files 143 | ----------- 144 | 145 | The main output of the build process is a disk image, 146 | `chickadeeboot.img`. QEMU “boots” off this disk image, but the image 147 | could conceivably boot on real hardware! The build process also 148 | produces other files that can be useful to examine. 149 | 150 | | File | Description | 151 | | -------------------------- | ------------------------------------ | 152 | | `obj/kernel.asm` | Kernel assembly (with addresses) | 153 | | `obj/kernel.sym` | Kernel defined symbols | 154 | | `obj/p-PROCESS.asm`, `sym` | Same for process binaries | 155 | 156 | [CS 161]: https://read.seas.harvard.edu/cs161/2021/ 157 | [triple fault]: https://en.wikipedia.org/wiki/Triple_fault 158 | [QEMU]: https://qemu.org/ 159 | [Homebrew]: https://brew.sh/ 160 | -------------------------------------------------------------------------------- /boot.cc: -------------------------------------------------------------------------------- 1 | #include "x86-64.h" 2 | #include "elf.h" 3 | 4 | // boot.cc 5 | // 6 | // Chickadee boot loader. Loads the kernel from the first IDE hard disk. 7 | // 8 | // A BOOT LOADER is a tiny program that loads an operating system into 9 | // memory. It has to be tiny because it can contain no more than 510 bytes 10 | // of instructions: it is stored in the disk's first 512-byte sector. 11 | // 12 | // When the CPU boots it loads the BIOS into memory and executes it. The 13 | // BIOS intializes devices and CPU state, reads the first 512-byte sector of 14 | // the boot device (hard drive) into memory at address 0x7C00, and jumps to 15 | // that address. 16 | // 17 | // The boot loader is contained in bootentry.S and boot.cc. Control starts 18 | // in bootentry.S, which initializes the CPU and sets up a stack, then 19 | // transfers here. This code reads in the kernel image and calls the 20 | // kernel. 21 | // 22 | // The main kernel is stored as a contiguous ELF executable image 23 | // starting in the disk's sector KERNEL_START_SECTOR. 24 | 25 | #define SECTORSIZE 512 26 | #define ELFHDR ((elf_header*) 0x3000) // scratch space 27 | #define KERNEL_START_SECTOR 128 28 | 29 | extern "C" { 30 | [[noreturn]] void boot(); 31 | static void boot_readsect(uintptr_t dst, uint32_t src_sect); 32 | static void boot_readseg(uintptr_t dst, uint32_t src_sect, 33 | size_t filesz, size_t memsz); 34 | } 35 | 36 | 37 | // boot 38 | // Load the kernel and jump to it. 39 | [[noreturn]] void boot() { 40 | // read 1st page off disk (should include programs as well as header) 41 | // and check validity 42 | boot_readseg((uintptr_t) ELFHDR, KERNEL_START_SECTOR, 43 | PAGESIZE, PAGESIZE); 44 | while (ELFHDR->e_magic != ELF_MAGIC) { 45 | /* do nothing */ 46 | } 47 | 48 | // load each program segment 49 | elf_program* ph = (elf_program*) ((uint8_t*) ELFHDR + ELFHDR->e_phoff); 50 | elf_program* eph = ph + ELFHDR->e_phnum; 51 | for (; ph < eph; ++ph) { 52 | boot_readseg(ph->p_va, 53 | KERNEL_START_SECTOR + ph->p_offset / SECTORSIZE, 54 | ph->p_filesz, ph->p_memsz); 55 | } 56 | 57 | // jump to the kernel 58 | using kernel_entry_t = void (*)(); 59 | kernel_entry_t kernel_entry = (kernel_entry_t) ELFHDR->e_entry; 60 | kernel_entry(); 61 | __builtin_unreachable(); // tell compiler `kernel_entry` does not return 62 | } 63 | 64 | 65 | // boot_readseg(dst, src_sect, filesz, memsz) 66 | // Load an ELF segment at virtual address `dst` from the IDE disk's sector 67 | // `src_sect`. Copies `filesz` bytes into memory at `dst` from sectors 68 | // `src_sect` and up, then clears memory in the range 69 | // `[dst+filesz, dst+memsz)`. 70 | static void boot_readseg(uintptr_t ptr, uint32_t src_sect, 71 | size_t filesz, size_t memsz) { 72 | uintptr_t end_ptr = ptr + filesz; 73 | memsz += ptr; 74 | 75 | // round down to sector boundary 76 | ptr &= ~(SECTORSIZE - 1); 77 | 78 | // read sectors 79 | for (; ptr < end_ptr; ptr += SECTORSIZE, ++src_sect) { 80 | boot_readsect(ptr, src_sect); 81 | } 82 | 83 | // clear bss segment 84 | for (; end_ptr < memsz; ++end_ptr) { 85 | *(uint8_t*) end_ptr = 0; 86 | } 87 | } 88 | 89 | 90 | // boot_waitdisk 91 | // Wait for the disk to be ready. 92 | static void boot_waitdisk() { 93 | // Wait until the ATA status register says ready (0x40 is on) 94 | // & not busy (0x80 is off) 95 | while ((inb(0x1F7) & 0xC0) != 0x40) { 96 | /* do nothing */ 97 | } 98 | } 99 | 100 | 101 | // boot_readsect(dst, src_sect) 102 | // Read disk sector number `src_sect` into address `dst`. 103 | static void boot_readsect(uintptr_t dst, uint32_t src_sect) { 104 | // programmed I/O for "read sector" 105 | boot_waitdisk(); 106 | outb(0x1F2, 1); // send `count = 1` as an ATA argument 107 | outb(0x1F3, src_sect); // send `src_sect`, the sector number 108 | outb(0x1F4, src_sect >> 8); 109 | outb(0x1F5, src_sect >> 16); 110 | outb(0x1F6, (src_sect >> 24) | 0xE0); 111 | outb(0x1F7, 0x20); // send the command: 0x20 = read sectors 112 | 113 | // then move the data into memory 114 | boot_waitdisk(); 115 | insl(0x1F0, (void*) dst, SECTORSIZE/4); // read 128 words from the disk 116 | } 117 | -------------------------------------------------------------------------------- /boot.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | OUTPUT_ARCH(i386:x86-64) 3 | ENTRY(boot_entry) 4 | 5 | SECTIONS { 6 | . = 0x7c00; 7 | 8 | /* Text segment: instructions only. 9 | The boot loader must fit in 512 bytes. */ 10 | .text : { 11 | *(.text.unlikely .text.*_unlikely .text.unlikely.*) 12 | *(.text.exit .text.exit.*) 13 | *(.text.startup .text.startup.*) 14 | *(.text.hot .text.hot.*) 15 | *(.text .stub .text.* .gnu.linkonce.t.*) 16 | } 17 | 18 | /DISCARD/ : { *(.eh_frame .note.GNU-stack) } 19 | } 20 | -------------------------------------------------------------------------------- /bootentry.S: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # BOOT ENTRY POINT 3 | # 4 | # After the BIOS initializes the hardware on startup or system reset, 5 | # it loads the first 512-byte sector of the hard disk 6 | # into physical addresses 0x7C00-0x7DFF. 7 | # Then it jumps to address 0x7C00 and the OS starts running! 8 | # 9 | # This file contains the code loaded at that address. 10 | # The `boot_entry` routine switches the CPU out of compatibility mode, 11 | # then calls `boot` from `boot.cc` to finish the booting process. 12 | # 13 | # There is no need to understand this code in detail! 14 | # (You may stop reading now.) 15 | # 16 | ############################################################################### 17 | 18 | 19 | ############################################################################### 20 | # For Your Information: COMPATIBILITY MODES 21 | # 22 | # The Intel x86 architecture has many compatibility modes, going back to 23 | # the 8086, which supported only 16-bit addresses. In the CPU we expect, 24 | # the BIOS starts the OS while running in the oldest mode, 16-bit real 25 | # mode. The machine acts like addresses are only 16 bits long, 26 | # there's no paging support, and there isn't even any support for 27 | # user-mode applications. The following weird magic transitions the 28 | # processor to 64-bit mode and sets up a page table suitable for initial 29 | # kernel execution. More recent iterations of 64-bit processors boot 30 | # directly into 64-bit mode. 31 | # 32 | ############################################################################### 33 | 34 | /* import constants from kernel.hh and x86-64.h */ 35 | #include "obj/k-asm.h" 36 | 37 | .globl boot_entry # Entry point 38 | boot_entry: 39 | .code16 # This runs in real mode 40 | cli # Disable interrupts 41 | cld # String operations increment 42 | 43 | # All segments are initially 0. 44 | # Set up the stack pointer, growing downward from 0x7c00. 45 | movw $boot_entry, %sp 46 | 47 | notify_bios64: 48 | # Notify the BIOS (the machine's firmware) to optimize itself 49 | # for x86-64 code. https://wiki.osdev.org/X86-64 50 | movw $0xEC00, %ax 51 | movw $2, %bx 52 | int $0x15 53 | 54 | init_boot_pagetable: 55 | # clear memory for boot page table 56 | .set BOOT_PAGETABLE,0x1000 57 | movl $BOOT_PAGETABLE, %edi 58 | xorl %eax, %eax 59 | movl $(0x2000 / 4), %ecx 60 | rep stosl 61 | 62 | # set up boot page table 63 | # 0x1000: L4 page table; entries 0, 256, and 511 point to: 64 | # 0x2000: L3 page table; entries 0 and 510 map 1st 1GB of physmem 65 | # This is the minimal page table that maps all of 66 | # low-canonical, high-canonical, and kernel-text addresses to 67 | # the first 1GB of physical memory. 68 | movl $BOOT_PAGETABLE, %edi 69 | leal 0x1000 + PTE_P + PTE_W(%edi), %ecx 70 | movl %ecx, (%edi) 71 | movl %ecx, 0x800(%edi) 72 | movl %ecx, 0xFF8(%edi) 73 | movl $(PTE_P + PTE_W + PTE_PS), -3(%ecx) 74 | movl $(PTE_P + PTE_W + PTE_PS), 0xFED(%ecx) 75 | 76 | # Switch from real to protected mode: 77 | # Up until now, there's been no protection, so we've gotten along perfectly 78 | # well without explicitly telling the processor how to translate addresses. 79 | # When we switch to protected mode, this is no longer true! 80 | # We need at least to set up some "segments" that tell the processor it's 81 | # OK to run code at any address, or write to any address. 82 | # The `gdt` and `gdtdesc` tables below define these segments. 83 | # This code loads them into the processor. 84 | # We need this setup to ensure the transition to protected mode is smooth. 85 | 86 | real_to_prot: 87 | movl %cr4, %eax # enable physical address extensions 88 | orl $(CR4_PSE | CR4_PAE), %eax 89 | movl %eax, %cr4 90 | movl %edi, %cr3 91 | 92 | movl $MSR_IA32_EFER, %ecx # turn on 64-bit mode 93 | rdmsr 94 | orl $(IA32_EFER_LME | IA32_EFER_SCE | IA32_EFER_NXE), %eax 95 | wrmsr 96 | 97 | movl %cr0, %eax # turn on protected mode 98 | orl $(CR0_PE | CR0_WP | CR0_PG), %eax 99 | movl %eax, %cr0 100 | 101 | lgdt gdtdesc + 6 # load GDT 102 | 103 | # CPU magic: jump to relocation, flush prefetch queue, and 104 | # reload %cs. Has the effect of just jmp to the next 105 | # instruction, but simultaneously loads CS with 106 | # $SEGSEL_BOOT_CODE. 107 | ljmp $SEGSEL_BOOT_CODE, $boot 108 | 109 | 110 | # Segment descriptors 111 | .code32 112 | .p2align 3 # force 8 byte alignment 113 | gdt: .word 0, 0, 0, 0 # null 114 | .word 0, 0 # kernel code segment 115 | .byte 0, 0x9A, 0x20, 0 116 | gdtdesc: 117 | .word 0, 0, 0 118 | .word 0x0f # sizeof(gdt) - 1 119 | .long gdt # address gdt 120 | .long 0 121 | -------------------------------------------------------------------------------- /build/chickadee.gdb: -------------------------------------------------------------------------------- 1 | set $loaded = 1 2 | set arch i386:x86-64 3 | file obj/kernel.full 4 | add-symbol-file obj/bootsector.full 0x7c00 5 | source obj/firstprocess.gdb 6 | target remote localhost:12949 7 | source build/functions.gdb 8 | display/5i $pc 9 | -------------------------------------------------------------------------------- /build/findgcc.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # Look for gcc-{14,9}; prefer gcc-12 or above 4 | wantv=12 5 | 6 | ver1 () { 7 | ( $1 -dumpversion 2>/dev/null || echo 0 ) | sed 's/[^0-9].*//' 8 | } 9 | 10 | prog="$1" 11 | if "$prog" -v 2>&1 | grep '^gcc' >/dev/null; then 12 | base=`echo "$prog" | sed 's/^\([^g]\)/g\1/'` 13 | v=`ver1 "$prog"` 14 | if [ $v -lt $wantv ]; then 15 | for vx in 14 13 12 11 10 9; do 16 | if [ `ver1 "$base-$vx"` -ge $wantv ]; then 17 | echo "$base-$vx"; exit 18 | fi 19 | done 20 | fi 21 | fi 22 | echo "$prog" 23 | -------------------------------------------------------------------------------- /build/findprocesses.awk: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | optional = 2; 3 | MIN = MIN + 0; 4 | } 5 | /^# *if *SOL/ { 6 | skip = 0; 7 | if (match($0, "^# *if *SOL *>= *")) { 8 | skip = SOL < (substr($0, RLENGTH) + 0); 9 | } else if (match($0, "^# *if *SOL *> *")) { 10 | skip = SOL <= (substr($0, RLENGTH) + 0); 11 | } else if (match($0, "^# *if *SOL *== *")) { 12 | skip = SOL != (substr($0, RLENGTH) + 0); 13 | } else if (match($0, "^# *if *SOL *!= *")) { 14 | skip = SOL == (substr($0, RLENGTH) + 0); 15 | } else if (match($0, "^# *if *SOL *<= *")) { 16 | skip = SOL > (substr($0, RLENGTH) + 0); 17 | } else if (match($0, "^# *if *SOL *< *")) { 18 | skip = SOL >= (substr($0, RLENGTH) + 0); 19 | } 20 | if (skip) { 21 | optional = 2; 22 | nextfile; 23 | } 24 | next; 25 | } 26 | /^# *if *LAB/ { 27 | skip = 0; 28 | if (match($0, "^# *if *LAB *>= *")) { 29 | skip = LAB < (substr($0, RLENGTH) + 0); 30 | } else if (match($0, "^# *if *LAB *> *")) { 31 | skip = LAB <= (substr($0, RLENGTH) + 0); 32 | } else if (match($0, "^# *if *LAB *== *")) { 33 | skip = LAB != (substr($0, RLENGTH) + 0); 34 | } else if (match($0, "^# *if *LAB *!= *")) { 35 | skip = LAB == (substr($0, RLENGTH) + 0); 36 | } else if (match($0, "^# *if *LAB *<= *")) { 37 | skip = LAB > (substr($0, RLENGTH) + 0); 38 | } else if (match($0, "^# *if *LAB *< *")) { 39 | skip = LAB >= (substr($0, RLENGTH) + 0); 40 | } 41 | if (skip) { 42 | optional = 2; 43 | nextfile; 44 | } 45 | next; 46 | } 47 | /^# *define *CHICKADEE_OPTIONAL_PROCESS/ { 48 | match($0, "^# *define *CHICKADEE_OPTIONAL_PROCESS[ \t\r\n]*"); 49 | if (substr($0, RLENGTH) == "") { 50 | optional = 1; 51 | } else { 52 | optional = substr($0, RLENGTH) + 0; 53 | } 54 | next; 55 | } 56 | { 57 | f = FILENAME; 58 | sub("\\.cc$", "", f); 59 | if (optional == 0 || (MIN < 0 && DISK) || (MIN == 0 && optional == 2) || ("p-" CHICKADEE_FIRST_PROCESS) == f) { 60 | print f; 61 | } 62 | optional = 2; 63 | nextfile; 64 | } 65 | -------------------------------------------------------------------------------- /build/flags.mk: -------------------------------------------------------------------------------- 1 | # Initialize compiler flags so `config.mk` can augment them 2 | 3 | # Set up dependency files 4 | DEPCFLAGS = -MD -MF $(DEPSDIR)/$(@F).d -MP 5 | 6 | # Flags for building programs that run on the host (not in Chickadee) 7 | HOSTCPPFLAGS = $(DEFS) -I. 8 | HOSTCFLAGS := -std=gnu23 $(CFLAGS) -Wall -W 9 | HOSTCXXFLAGS := -std=gnu++23 $(CXXFLAGS) -Wall -W 10 | 11 | # Flags for building Chickadee kernel and process code 12 | # preprocessor flags 13 | CPPFLAGS = $(DEFS) -I. 14 | # flags common to C and C++, and to kernel and user code 15 | CCOMMONFLAGS := -m64 -mno-mmx -mno-sse -mno-sse2 -mno-sse3 \ 16 | -mno-3dnow -ffreestanding -fno-pic -fno-stack-protector \ 17 | -Wall -W -Wshadow -Wno-format -Wno-unused-parameter 18 | # flags for C 19 | CFLAGS := -std=gnu23 $(CCOMMONFLAGS) $(CFLAGS) 20 | # flags for C++ 21 | CXXFLAGS := -std=gnu++23 -fno-exceptions -fno-rtti -ffunction-sections \ 22 | $(CXXFLAGS) $(CCOMMONFLAGS) $(CXXFLAGS) 23 | # flags for debuggability (not used in boot loader) 24 | DEBUGFLAGS := -gdwarf-4 -fno-omit-frame-pointer -fno-optimize-sibling-calls \ 25 | -mno-omit-leaf-frame-pointer 26 | # flags for kernel sources 27 | KERNELCXXFLAGS := $(CXXFLAGS) -mno-red-zone $(DEBUGFLAGS) $(SANITIZEFLAGS) 28 | # assembler flags 29 | ASFLAGS := $(CCOMMONFLAGS) 30 | # linker flags 31 | LDFLAGS := $(LDFLAGS) -Os --gc-sections -z max-page-size=0x1000 \ 32 | -z noexecstack -static -nostdlib 33 | -------------------------------------------------------------------------------- /build/functions.gdb: -------------------------------------------------------------------------------- 1 | set $lastcs = -1 2 | set arch i386:x86-64 3 | -------------------------------------------------------------------------------- /build/mkinitfs.awk: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | OFS = ""; files = ""; 3 | print "#include \"k-devices.hh\""; 4 | } 5 | { 6 | for (i = 1; i <= NF; ++i) { 7 | name = $i; 8 | gsub(/^\.\//, "", name); 9 | gsub(/^initfs\//, "", name); 10 | gsub(/^obj\/p-/, "", name); 11 | gsub(/^obj\//, "", name); 12 | eq = index(name, "="); 13 | if (eq) { 14 | files = files " memfile(\"" substr(name, 1, eq - 1) "\", \"" substr(name, eq + 1, length(name) - eq) "\"),\n"; 15 | } else { 16 | prefix = "_binary_" $i; 17 | gsub(/[^a-zA-Z0-9_]/, "_", prefix); 18 | print "extern unsigned char ", prefix, "_start[];"; 19 | print "extern unsigned char ", prefix, "_end[];"; 20 | files = files " memfile(\"" name "\", " prefix "_start, " prefix "_end),\n"; 21 | } 22 | } 23 | } 24 | END { 25 | print "memfile memfile::initfs[initfs_size] = {"; 26 | print files, "};"; 27 | } 28 | -------------------------------------------------------------------------------- /build/mkkernelasm.awk: -------------------------------------------------------------------------------- 1 | BEGIN { OFS = ORS = "" } 2 | /^#define *([A-Z][A-Z0-9_]*)[ \t]*\(?(-?[0-9][0-9]*|0x[0-9A-Fa-f]*)U?L? *(<< *[0-9]*)?\)?[ \t]*(\/\/.*|\/\*.*\*\/)?[ \t]*$/ { 3 | print "#define ", $2, " "; 4 | gsub(/^#define *[^ ]*[ \t]*/, ""); 5 | gsub(/[ \t]*(\/\/.*|\/\*.*\*\/)?[ \t]*$/, ""); 6 | gsub(/[UL]/, ""); 7 | print $0, "\n"; 8 | } 9 | -------------------------------------------------------------------------------- /build/qemu-nograb.c: -------------------------------------------------------------------------------- 1 | // Prevent QEMU from grabbing the user's mouse. 2 | // QEMU does not offer an option for doing this, except if we were to 3 | // enable USB within our OS. That's complicated, so we cheat. 4 | 5 | #define _GNU_SOURCE 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // these two overrides are enough for QEMU 2.0 with either SDL version 13 | int SDL_GetAppState(void) { 14 | return 0; 15 | } 16 | 17 | int SDL_GetWindowFlags(void) { 18 | return 0; 19 | } 20 | 21 | 22 | // old QEMU versions (0.15) don't always check SDL_GetAppState, 23 | // so we override malloc() and calloc() to mark the PS/2 input device 24 | // as absolute (which prevents grabbing) 25 | static void* (*next_malloc)(size_t sz); 26 | static void* (*next_calloc)(size_t nmemb, size_t sz); 27 | 28 | typedef struct fake_qemu_put_mouse_event { 29 | void* qemu_put_mouse_event; 30 | void* qemu_put_mouse_event_opaque; 31 | int qemu_put_mouse_event_absolute; 32 | char* qemu_put_mouse_event_name; 33 | int index; 34 | void* node_next; 35 | void* node_prev; 36 | } fake_qemu_put_mouse_event; 37 | 38 | static const char mouse_string[] = "QEMU PS/2 Mouse"; 39 | static int test_mode = 0; 40 | static fake_qemu_put_mouse_event* test_event; 41 | static char* test_name; 42 | 43 | static void check(void) { 44 | if (test_mode == 2 45 | && test_event 46 | && test_event->qemu_put_mouse_event_name == test_name 47 | && memcmp(test_name, mouse_string, sizeof(mouse_string)) == 0 48 | && test_event->qemu_put_mouse_event_absolute == 0) { 49 | test_event->qemu_put_mouse_event_absolute = 1; 50 | test_mode = -1; 51 | } 52 | } 53 | 54 | static inline void track(size_t nmemb, size_t sz, void* ptr) { 55 | if (test_mode == 1 && sz == sizeof(mouse_string) && nmemb == 1) { 56 | test_name = ptr; 57 | test_mode = 2; 58 | } else if (sz == sizeof(fake_qemu_put_mouse_event) && nmemb == 1) { 59 | test_event = ptr; 60 | test_mode = 1; 61 | } else { 62 | test_mode = 0; 63 | } 64 | } 65 | 66 | void* malloc(size_t sz) { 67 | if (!next_malloc) { 68 | next_malloc = dlsym(RTLD_NEXT, "malloc"); 69 | next_calloc = dlsym(RTLD_NEXT, "calloc"); 70 | } 71 | 72 | if (test_mode == 2) { 73 | check(); 74 | } 75 | 76 | void* ptr = next_malloc(sz); 77 | 78 | if (test_mode >= 0) { 79 | track(1, sz, ptr); 80 | } 81 | return ptr; 82 | } 83 | 84 | /* 0.15 requires we track calloc too */ 85 | void* calloc(size_t nmemb, size_t sz) { 86 | if (!next_calloc) { 87 | extern void* __libc_calloc(size_t nmemb, size_t sz); 88 | return __libc_calloc(nmemb, sz); /* avoid infinite regress */ 89 | } 90 | 91 | if (test_mode == 2) { 92 | check(); 93 | } 94 | 95 | void* ptr = next_calloc(nmemb, sz); 96 | 97 | if (test_mode >= 0) { 98 | track(1, sz, ptr); 99 | } 100 | return ptr; 101 | } 102 | -------------------------------------------------------------------------------- /build/quietobjcopy.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | onexit () { 4 | test -n "$tmp" && rm -f "$tmp" 5 | } 6 | trap onexit 0 7 | tmp=`mktemp /tmp/objcopy.XXXXXX` 8 | "$@" 2> $tmp 9 | v=$? 10 | cat $tmp | grep -v "empty loadable segment" 1>&2 11 | exit $v 12 | -------------------------------------------------------------------------------- /build/rules.mk: -------------------------------------------------------------------------------- 1 | OBJDIR := obj 2 | comma = , 3 | export LC_ALL = C 4 | 5 | # Optimization flag 6 | O ?= -O2 7 | ifeq ($(filter 0 1 2 3 s z g fast,$(O)),$(strip $(O))) 8 | override O := -O$(O) 9 | endif 10 | 11 | # Compiler toolchain 12 | CCPREFIX ?= 13 | 14 | ifeq ($(CCPREFIX),) 15 | ifeq ($(origin CC),default) 16 | CC := $(shell $(SHELL) build/findgcc.sh $(CC)) 17 | endif 18 | ifeq ($(origin CXX),default) 19 | CXX := $(shell $(SHELL) build/findgcc.sh $(CXX)) 20 | endif 21 | else 22 | # Override implicit settings, allow user settings 23 | ifeq ($(origin CC),default) 24 | CC := $(CCPREFIX)cc 25 | endif 26 | ifeq ($(origin CXX),default) 27 | CXX := $(CCPREFIX)c++ 28 | endif 29 | ifeq ($(origin LD),default) 30 | LD := $(CCPREFIX)ld 31 | endif 32 | endif 33 | 34 | OBJCOPY ?= $(CCPREFIX)objcopy 35 | OBJDUMP ?= $(CCPREFIX)objdump 36 | NM ?= $(CCPREFIX)nm 37 | STRIP ?= $(CCPREFIX)strip 38 | 39 | # Native commands 40 | HOSTCC ?= cc 41 | HOSTCXX ?= c++ 42 | TAR ?= tar 43 | PERL ?= perl 44 | 45 | # Update flags based on `make` options and program support 46 | ASFLAGS += $(shell $(CXX) -no-integrated-as -E -x c /dev/null >/dev/null 2>&1 && echo -no-integrated-as) 47 | 48 | LDFLAGS += $(shell $(LD) -m elf_x86_64 --help >/dev/null 2>&1 && echo "-m elf_x86_64") 49 | LDFLAGS += $(shell $(LD) --no-warn-rwx-segments --help >/dev/null 2>&1 && echo "--no-warn-rwx-segments") 50 | 51 | ifeq ($(filter 1,$(SAN) $(UBSAN)),1) 52 | KERNEL_OBJS += $(OBJDIR)/k-sanitizers.ko 53 | KERNELCXXFLAGS += -DHAVE_SANITIZERS 54 | SANITIZEFLAGS := -fsanitize=undefined -fsanitize=kernel-address 55 | $(OBJDIR)/k-alloc.ko $(OBJDIR)/k-sanitizers.ko: SANITIZEFLAGS := 56 | endif 57 | 58 | QUIETOBJCOPY = sh build/quietobjcopy.sh $(OBJCOPY) 59 | 60 | 61 | # Dependencies 62 | DEPSDIR := .deps 63 | BUILDSTAMP := $(DEPSDIR)/rebuildstamp 64 | KERNELBUILDSTAMP := $(DEPSDIR)/krebuildstamp 65 | DEPFILES := $(wildcard $(DEPSDIR)/*.d) 66 | ifneq ($(DEPFILES),) 67 | include $(DEPFILES) 68 | endif 69 | 70 | ifneq ($(DEP_CC),$(CC) $(CPPFLAGS) $(CFLAGS) $(DEPCFLAGS) $(O) _ $(LDFLAGS)) 71 | DEP_CC := $(shell mkdir -p $(DEPSDIR); echo >$(BUILDSTAMP); echo "DEP_CC:=$(CC) $(CPPFLAGS) $(CFLAGS) $(DEPCFLAGS) $(O) _ $(LDFLAGS)" >$(DEPSDIR)/_cc.d; echo "DEP_PREFER_GCC:=$(PREFER_GCC)" >>$(DEPSDIR)/_cc.d) 72 | endif 73 | ifneq ($(DEP_CXX),$(CXX) $(HOSTCPPFLAGS) $(DEPCFLAGS) $(CXXFLAGS) $(O) _ $(HOSTCXXFLAGS)) 74 | DEP_CXX := $(shell mkdir -p $(DEPSDIR); echo >$(BUILDSTAMP); echo "DEP_CXX:=$(CXX) $(HOSTCPPFLAGS) $(DEPCFLAGS) $(CXXFLAGS) $(O) _ $(HOSTCXXFLAGS)" >$(DEPSDIR)/_cxx.d) 75 | endif 76 | ifneq ($(DEP_KERNELCXX),$(CXX) $(CPPFLAGS) $(DEPCFLAGS) $(KERNELCXXFLAGS) $(O)) 77 | DEP_KERNELCXX := $(shell mkdir -p $(DEPSDIR); echo >$(KERNELBUILDSTAMP); echo "DEP_KERNELCXX:=$(CXX) $(CPPFLAGS) $(DEPCFLAGS) $(KERNELCXXFLAGS) $(O)" >$(DEPSDIR)/_kernelcxx.d) 78 | endif 79 | 80 | BUILDSTAMPS = $(OBJDIR)/stamp $(BUILDSTAMP) 81 | GDBFILES = $(OBJDIR)/firstprocess.gdb 82 | KERNELBUILDSTAMPS = $(OBJDIR)/stamp $(KERNELBUILDSTAMP) 83 | 84 | $(OBJDIR)/stamp $(BUILDSTAMP): 85 | $(call run,mkdir -p $(@D)) 86 | $(call run,touch $@) 87 | 88 | k_asm_h_input_command := $(CXX) $(CPPFLAGS) $(KERNELCXXFLAGS) -DCHICKADEE_KERNEL -dM -E kernel.hh 89 | u_asm_h_input_command := $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DCHICKADEE_PROCESS -dM -E u-lib.hh 90 | asm_h_build_command := awk -f build/mkkernelasm.awk | sort 91 | 92 | ifneq ($(wildcard $(OBJDIR)/k-asm.h),) 93 | DEPCHECK_K_ASM_H := $(shell \ 94 | $(k_asm_h_input_command) | $(asm_h_build_command) > $(OBJDIR)/k-asm.h1; \ 95 | cmp $(OBJDIR)/k-asm.h $(OBJDIR)/k-asm.h1 >/dev/null 2>&1 || rm -f $(OBJDIR)/k-asm.h) 96 | endif 97 | ifneq ($(wildcard $(OBJDIR)/u-asm.h),) 98 | DEPCHECK_U_ASM_H := $(shell \ 99 | $(u_asm_h_input_command) | $(asm_h_build_command) > $(OBJDIR)/u-asm.h1; \ 100 | cmp $(OBJDIR)/u-asm.h $(OBJDIR)/u-asm.h1 >/dev/null 2>&1 || rm -f $(OBJDIR)/u-asm.h) 101 | endif 102 | 103 | ifneq ($(strip $(INITFS_CONTENTS) $(INITFS_PARAMS)),$(DEP_INITFS_CONTENTS)) 104 | INITFS_BUILDSTAMP := $(shell echo "DEP_INITFS_CONTENTS:=$(strip $(INITFS_CONTENTS) $(INITFS_PARAMS))" > $(DEPSDIR)/_initfs.d; echo always) 105 | endif 106 | 107 | ifneq ($(strip $(DISKFS_CONTENTS)),$(DEP_DISKFS_CONTENTS)) 108 | DISKFS_BUILDSTAMP := $(shell echo "DEP_DISKFS_CONTENTS:=$(strip $(DISKFS_CONTENTS))" > $(DEPSDIR)/_diskfs.d; echo always) 109 | endif 110 | 111 | 112 | # Qemu emulator 113 | QEMU ?= qemu-system-x86_64 114 | QEMUCONSOLE ?= $(if $(or $(DISPLAY),$(filter Darwin,$(shell uname))),,1) 115 | QEMUDISPLAY ?= $(if $(filter 1 y yes,$(QEMUCONSOLE)),console,graphic) 116 | 117 | $(OBJDIR)/libqemu-nograb.so.1: build/qemu-nograb.c 118 | $(call run,mkdir -p $(@D)) 119 | -$(call run,$(HOSTCC) -fPIC -shared -Wl$(comma)-soname$(comma)$(@F) -ldl -o $@ $<) 120 | 121 | ifeq ($(origin QEMU_PRELOAD_LIBRARY),undefined) 122 | ifneq ($(strip $(shell uname)),Darwin) 123 | QEMU_PRELOAD_LIBRARY = $(OBJDIR)/libqemu-nograb.so.1 124 | endif 125 | endif 126 | 127 | ifneq ($(QEMU_PRELOAD_LIBRARY),) 128 | QEMU_PRELOAD = $(shell if test -r $(QEMU_PRELOAD_LIBRARY); then echo LD_PRELOAD=$(QEMU_PRELOAD_LIBRARY); fi) 129 | endif 130 | 131 | 132 | # Run the emulator 133 | check-qemu-console: 134 | @if test -z "$$(which $(QEMU) 2>/dev/null)"; then \ 135 | echo 1>&2; echo "***" 1>&2; \ 136 | echo "*** Cannot run $(QEMU). You may not have installed it yet." 1>&2; \ 137 | if test -x /usr/bin/apt-get; then \ 138 | cmd="apt-get -y install"; else cmd="yum install -y"; fi; \ 139 | if test $$(whoami) = jharvard; then \ 140 | echo "*** I am going to try to install it for you." 1>&2; \ 141 | echo "***" 1>&2; echo 1>&2; \ 142 | echo sudo $$cmd qemu-system-x86; \ 143 | sudo $$cmd qemu-system-x86 || exit 1; \ 144 | else echo "*** If on Linux, try running this command to install it:" 1>&2; \ 145 | echo sudo $$cmd qemu-system-x86 1>&2; \ 146 | echo 1>&2; exit 1; fi; \ 147 | else :; fi 148 | 149 | check-qemu: $(QEMU_PRELOAD_LIBRARY) check-qemu-console 150 | 151 | 152 | # Delete the build 153 | clean: 154 | $(call run,rm -rf $(DEPSDIR) $(OBJDIR) *.img core *.core,CLEAN) 155 | 156 | realclean: clean 157 | $(call run,rm -rf $(DISTDIR)-$(USER).tar.gz $(DISTDIR)-$(USER)) 158 | 159 | distclean: realclean 160 | @: 161 | 162 | 163 | # Boilerplate 164 | always: 165 | @: 166 | 167 | # These targets don't correspond to files 168 | .PHONY: all always clean realclean distclean cleanfs fsck \ 169 | run run-graphic run-console run-monitor \ 170 | run-gdb run-gdb-graphic run-gdb-console run-gdb-report \ 171 | check-qemu-console check-qemu stop kill \ 172 | run-% run-graphic-% run-console-% run-monitor-% \ 173 | run-gdb-% run-gdb-graphic-% run-gdb-console-% 174 | 175 | # Eliminate default suffix rules 176 | .SUFFIXES: 177 | 178 | # Keep intermediate files 179 | .SECONDARY: 180 | 181 | # Delete target files if there is an error (or make is interrupted) 182 | .DELETE_ON_ERROR: 183 | 184 | # But no intermediate .o files should be deleted 185 | .PRECIOUS: %.o $(OBJDIR)/%.o $(OBJDIR)/%.full $(OBJDIR)/bootsector 186 | -------------------------------------------------------------------------------- /cbyteswap.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_CBYTESWAP_HH 2 | #define CHICKADEE_CBYTESWAP_HH 3 | #if defined(CHICKADEE_KERNEL) || defined(CHICKADEE_PROCESS) 4 | 5 | // Chickadee requires x86-64, which is little-endian. 6 | template 7 | inline T to_le(T x) { 8 | return x; 9 | } 10 | template 11 | inline T from_le(T x) { 12 | return x; 13 | } 14 | 15 | #else 16 | #include 17 | #ifdef __APPLE__ 18 | # include 19 | # define htole16(x) OSSwapHostToLittleInt16((x)) 20 | # define htole32(x) OSSwapHostToLittleInt32((x)) 21 | # define htole64(x) OSSwapHostToLittleInt64((x)) 22 | # define le16toh(x) OSSwapLittleToHostInt16((x)) 23 | # define le32toh(x) OSSwapLittleToHostInt32((x)) 24 | # define le64toh(x) OSSwapLittleToHostInt64((x)) 25 | #else 26 | # include 27 | #endif 28 | 29 | template struct sized_to_from_le; 30 | template <> struct sized_to_from_le<1UL> { 31 | static inline uint8_t to_le(uint8_t x) { return x; } 32 | static inline uint8_t from_le(uint8_t x) { return x; } 33 | }; 34 | template <> struct sized_to_from_le<2UL> { 35 | static inline uint16_t to_le(uint16_t x) { return htole16(x); } 36 | static inline uint16_t from_le(uint16_t x) { return le16toh(x); } 37 | }; 38 | template <> struct sized_to_from_le<4UL> { 39 | static inline uint32_t to_le(uint32_t x) { return htole32(x); } 40 | static inline uint32_t from_le(uint32_t x) { return le32toh(x); } 41 | }; 42 | template <> struct sized_to_from_le<8UL> { 43 | static inline uint64_t to_le(uint64_t x) { return htole64(x); } 44 | static inline uint64_t from_le(uint64_t x) { return le64toh(x); } 45 | }; 46 | 47 | template 48 | inline T to_le(T x) { 49 | return sized_to_from_le::to_le(x); 50 | } 51 | template 52 | inline T from_le(T x) { 53 | return sized_to_from_le::from_le(x); 54 | } 55 | 56 | #endif 57 | #endif 58 | -------------------------------------------------------------------------------- /diskfs/dickinson.txt: -------------------------------------------------------------------------------- 1 | The Birds begun at Four o'clock - 2 | Their period for Dawn - 3 | A Music numerous as space - 4 | But neighboring as Noon - 5 | 6 | I could not count their Force - 7 | Their Voices did expend 8 | As Brook by Brook bestows itself 9 | To multiply the Pond. 10 | 11 | The Listener - was not - 12 | Except occasional man - 13 | In homely industry arrayed - 14 | To overtake the Morn - 15 | 16 | Nor was it for applause - 17 | That I could ascertain - 18 | But independent Ecstasy 19 | Of Universe, and Men - 20 | 21 | By Six, the Flood had done - 22 | No Tumult there had been 23 | Of Dressing, or Departure - 24 | And yet the Band - was gone - 25 | 26 | The Sun engrossed the East - 27 | The Day Resumed the World - 28 | The Miracle that introduced 29 | Forgotten, as fulfilled. 30 | -------------------------------------------------------------------------------- /diskfs/thoreau.txt: -------------------------------------------------------------------------------- 1 | The moon now rises to her absolute rule, 2 | And the husbandman and hunter 3 | Acknowledge her for their mistress. 4 | Asters and golden reign in the fields 5 | And the life everlasting withers not. 6 | The fields are reaped and shorn of their pride 7 | But an inward verdure still crowns them; 8 | The thistle scatters its down on the pool 9 | And yellow leaves clothe the river--- 10 | And nought disturbs the serious life of men. 11 | But behind the sheaves and under the sod 12 | There lurks a ripe fruit which the reapers have not gathered, 13 | The true harvest of the year---the boreal fruit 14 | Which it bears forever, 15 | With fondness annually watering and maturing it. 16 | But man never severs the stalk 17 | Which bears this palatable fruit. 18 | -------------------------------------------------------------------------------- /diskfs/wheatley.txt: -------------------------------------------------------------------------------- 1 | I. 2 | 3 | Adieu, *New-England's* smiling meads, 4 | Adieu, the flow'ry plain: 5 | I leave thine op'ning charms, O spring, 6 | And tempt the roaring main. 7 | 8 | II. 9 | 10 | In vain for me the flow'rets rise, 11 | And boast their gaudy pride, 12 | While here beneath the northern skies 13 | I mourn for *health* deny'd. 14 | 15 | III. 16 | 17 | Celestial maid of rosy hue, 18 | O let me feel thy reign! 19 | I languish till thy face I view, 20 | Thy vanish'd joys regain. 21 | 22 | IV. 23 | 24 | *Susannah* mourns, nor can I bear 25 | To see the crystal show'r, 26 | Or mark the tender falling tear 27 | At sad departure's hour; 28 | 29 | V. 30 | 31 | Not regarding can I see 32 | Her soul with grief opprest: 33 | But let no sighs, no groans for me 34 | Steal from her pensive breast. 35 | 36 | VI. 37 | 38 | In vain the feather'd warblers sing, 39 | In vain the garden blooms, 40 | And on the bosom of the spring 41 | Breathes out her sweet perfumes, 42 | 43 | VII. 44 | 45 | While for *Britannia's* distant shore 46 | We weep the liquid plain, 47 | And with astonish'd eyes explore 48 | The wide-extended main. 49 | 50 | VIII. 51 | 52 | Lo! *Health* appears! celestial dame! 53 | Complacent and serene, 54 | With *Hebe's* mantle o'er her frame, 55 | With soul-delighting mein. 56 | 57 | IX. 58 | 59 | To mark the vale where *London* lies 60 | With misty vapours crown'd, 61 | Which cloud *Aurora's* thousand dyes, 62 | And veil her charms around, 63 | 64 | X. 65 | 66 | Why, *Phoebus,* moves thy car so slow? 67 | So slow thy rising ray? 68 | Give us the famous town to view, 69 | Thou glorious king of day! 70 | 71 | XI. 72 | 73 | For thee, *Britannia,* I resign 74 | *New-England's* smiling fields; 75 | To view again her charms divine, 76 | What joy the prospect yields! 77 | 78 | XII. 79 | 80 | But thou! Temptation hence away, 81 | With all thy fatal train 82 | Nor once seduce my soul away, 83 | By thine enchanting strain. 84 | 85 | XIII. 86 | 87 | Thrice happy they, whose heav'nly shield 88 | Secures their souls from harms, 89 | And fell *Temptation* on the field 90 | Of all its pow'r disarms! 91 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:noble 2 | 3 | # set environment variables for tzdata 4 | ARG TZ=America/New_York 5 | ENV TZ=${TZ} 6 | 7 | # include manual pages and documentation 8 | ARG DEBIAN_FRONTEND=noninteractive 9 | RUN apt-get update &&\ 10 | apt-get -y install unminimize &&\ 11 | yes | unminimize 12 | 13 | # install GCC-related packages 14 | RUN apt-get -y install\ 15 | binutils-doc\ 16 | cpp-doc\ 17 | gcc-doc\ 18 | g++\ 19 | g++-multilib\ 20 | gdb\ 21 | gdb-doc\ 22 | glibc-doc\ 23 | libblas-dev\ 24 | liblapack-dev\ 25 | liblapack-doc\ 26 | libstdc++-13-doc\ 27 | make\ 28 | make-doc 29 | 30 | # install clang-related packages 31 | RUN apt-get -y install\ 32 | clang\ 33 | clang-18-doc\ 34 | lldb 35 | 36 | # install qemu for WeensyOS (sadly, this pulls in a lot of crap) 37 | RUN apt-get -y install\ 38 | qemu-system-x86 39 | 40 | # install programs used for system exploration 41 | RUN apt-get -y install\ 42 | blktrace\ 43 | linux-tools-generic\ 44 | strace\ 45 | tcpdump 46 | 47 | # install interactive programs (emacs, vim, nano, man, sudo, etc.) 48 | RUN apt-get -y install\ 49 | bc\ 50 | curl\ 51 | dc\ 52 | emacs-nox\ 53 | git\ 54 | git-doc\ 55 | man\ 56 | micro\ 57 | nano\ 58 | psmisc\ 59 | sudo\ 60 | vim\ 61 | wget 62 | 63 | # set up libraries 64 | RUN apt-get -y install\ 65 | libreadline-dev\ 66 | locales\ 67 | wamerican 68 | 69 | # install programs used for networking 70 | RUN apt-get -y install\ 71 | dnsutils\ 72 | inetutils-ping\ 73 | iproute2\ 74 | net-tools\ 75 | netcat-openbsd\ 76 | telnet\ 77 | time\ 78 | traceroute 79 | 80 | # install programs useful for disk images 81 | RUN apt-get -y install\ 82 | fdisk\ 83 | mtools 84 | 85 | # set up default locale 86 | RUN locale-gen en_US.UTF-8 87 | ENV LANG=en_US.UTF-8 88 | ENV EDITOR=nano 89 | 90 | # remove unneeded .deb files 91 | RUN rm -r /var/lib/apt/lists/* 92 | 93 | # set up passwordless sudo for user cs61-user 94 | ARG UID=1001 95 | RUN useradd -m -s /bin/bash -u "${UID}" cs61-user &&\ 96 | echo "cs61-user ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/cs61-init 97 | 98 | # create binary reporting version of dockerfile 99 | RUN (echo '#\!/bin/sh'; echo 'echo 20') > /usr/bin/cs61-docker-version; chmod ugo+rx,u+w,go-w /usr/bin/cs61-docker-version 100 | 101 | # git build arguments 102 | ARG USER=CS61\ User 103 | ARG EMAIL=nobody@example.com 104 | 105 | # configure your environment 106 | USER cs61-user 107 | RUN git config --global user.name "${USER}" &&\ 108 | git config --global user.email "${EMAIL}" &&\ 109 | git config --global safe.directory "*" &&\ 110 | (echo "(custom-set-variables"; echo " '(c-basic-offset 4)"; echo " '(indent-tabs-mode nil))") > ~/.emacs &&\ 111 | (echo "set expandtab"; echo "set shiftwidth=4"; echo "set softtabstop=4") > ~/.vimrc &&\ 112 | cat /dev/null > ~/.bash_profile &&\ 113 | echo "# 2022: avoid a Docker bug with user mapping by listing working directory" >> ~/.bash_profile &&\ 114 | echo "ls -al > /dev/null" >> ~/.bash_profile &&\ 115 | echo "for i in \`mount | grep /home/cs61-user | sed 's/^.*\\(\\/home[^ ]*\\).*/\\\\1/'\`; do ls -al \$i > /dev/null; done" >> ~/.bash_profile &&\ 116 | echo "# make ssh-auth.sock user-readable" >> ~/.bash_profile &&\ 117 | (echo "if test -e /run/host-services/ssh-auth.sock; then"; echo " sudo chown cs61-user:cs61-user /run/host-services/ssh-auth.sock"; echo "fi") >> ~/.bash_profile &&\ 118 | echo ". ~/.bashrc" >> ~/.bash_profile &&\ 119 | rm -f ~/.bash_logout &&\ 120 | echo "add-auto-load-safe-path ~" > ~/.gdbinit 121 | 122 | WORKDIR /home/cs61-user 123 | CMD ["/bin/bash", "-l"] 124 | 125 | # Initial version of this Dockerfile by Todd Morrill, CS 61 DCE student 126 | -------------------------------------------------------------------------------- /docker/Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | FROM ubuntu:noble 2 | 3 | # set environment variables for tzdata 4 | ARG TZ=America/New_York 5 | ENV TZ=${TZ} 6 | 7 | # include manual pages and documentation 8 | ARG DEBIAN_FRONTEND=noninteractive 9 | RUN apt-get update &&\ 10 | apt-get -y install unminimize &&\ 11 | yes | unminimize 12 | 13 | # copy new sources.list 14 | COPY --chown=root:root ubuntu-arm64.sources /etc/apt/sources.list.d/ubuntu.sources 15 | 16 | # include multiarch support 17 | RUN apt-get -y install binfmt-support &&\ 18 | dpkg --add-architecture amd64 &&\ 19 | apt-get update &&\ 20 | apt-get upgrade 21 | 22 | # install GCC-related packages 23 | RUN apt-get -y install\ 24 | binutils-doc\ 25 | cpp-doc\ 26 | gcc-doc\ 27 | g++\ 28 | gdb\ 29 | gdb-doc\ 30 | glibc-doc\ 31 | libblas-dev\ 32 | liblapack-dev\ 33 | liblapack-doc\ 34 | libstdc++-13-doc\ 35 | make\ 36 | make-doc 37 | 38 | # install clang-related packages 39 | RUN apt-get -y install\ 40 | clang\ 41 | clang-18-doc\ 42 | lldb 43 | 44 | # install qemu for WeensyOS (sadly, this pulls in a lot of crap) 45 | RUN apt-get -y install\ 46 | qemu-system-x86 47 | 48 | # install programs used for system exploration 49 | RUN apt-get -y install\ 50 | blktrace\ 51 | linux-tools-generic\ 52 | strace\ 53 | tcpdump 54 | 55 | # install interactive programs (emacs, vim, nano, man, sudo, etc.) 56 | RUN apt-get -y install\ 57 | bc\ 58 | curl\ 59 | dc\ 60 | emacs-nox\ 61 | git\ 62 | git-doc\ 63 | man\ 64 | micro\ 65 | nano\ 66 | psmisc\ 67 | sudo\ 68 | vim\ 69 | wget 70 | 71 | # set up libraries 72 | RUN apt-get -y install\ 73 | libreadline-dev\ 74 | locales\ 75 | wamerican 76 | 77 | # install programs used for networking 78 | RUN apt-get -y install\ 79 | dnsutils\ 80 | inetutils-ping\ 81 | iproute2\ 82 | net-tools\ 83 | netcat-openbsd\ 84 | telnet\ 85 | time\ 86 | traceroute 87 | 88 | # install programs useful for disk images 89 | RUN apt-get -y install\ 90 | fdisk\ 91 | mtools 92 | 93 | # install GCC-related packages for amd64 94 | RUN apt-get -y install\ 95 | g++-13-x86-64-linux-gnu\ 96 | gdb-multiarch\ 97 | libc6:amd64\ 98 | libstdc++6:amd64\ 99 | libasan8:amd64\ 100 | libtsan2:amd64\ 101 | libubsan1:amd64\ 102 | libreadline-dev:amd64\ 103 | libblas-dev:amd64\ 104 | liblapack-dev:amd64\ 105 | qemu-user 106 | 107 | # link x86-64 versions of common tools into /usr/x86_64-linux-gnu/bin 108 | RUN for i in addr2line c++filt cpp-13 g++-13 gcc-13 gcov-13 gcov-dump-13 gcov-tool-13 size strings; do \ 109 | ln -s /usr/bin/x86_64-linux-gnu-$i /usr/x86_64-linux-gnu/bin/$i; done &&\ 110 | ln -s /usr/bin/x86_64-linux-gnu-cpp-13 /usr/x86_64-linux-gnu/bin/cpp &&\ 111 | ln -s /usr/bin/x86_64-linux-gnu-g++-13 /usr/x86_64-linux-gnu/bin/c++ &&\ 112 | ln -s /usr/bin/x86_64-linux-gnu-g++-13 /usr/x86_64-linux-gnu/bin/g++ &&\ 113 | ln -s /usr/bin/x86_64-linux-gnu-gcc-13 /usr/x86_64-linux-gnu/bin/gcc &&\ 114 | ln -s /usr/bin/x86_64-linux-gnu-gcc-13 /usr/x86_64-linux-gnu/bin/cc &&\ 115 | ln -s /usr/bin/gdb-multiarch /usr/x86_64-linux-gnu/bin/gdb 116 | 117 | # set up default locale 118 | RUN locale-gen en_US.UTF-8 119 | ENV LANG=en_US.UTF-8 120 | ENV EDITOR=nano 121 | 122 | # remove unneeded .deb files 123 | RUN rm -r /var/lib/apt/lists/* 124 | 125 | # set up passwordless sudo for user cs61-user 126 | ARG UID=1001 127 | RUN useradd -m -s /bin/bash -u "${UID}" cs61-user &&\ 128 | echo "cs61-user ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/cs61-init 129 | 130 | # create binary reporting version of dockerfile 131 | RUN (echo '#\!/bin/sh'; echo 'if test "x$1" = x-n; then echo 20; else echo 20.arm64; fi') > /usr/bin/cs61-docker-version; chmod ugo+rx,u+w,go-w /usr/bin/cs61-docker-version 132 | 133 | # git build arguments 134 | ARG USER=CS61\ User 135 | ARG EMAIL=nobody@example.com 136 | 137 | # configure your environment 138 | USER cs61-user 139 | RUN git config --global user.name "${USER}" &&\ 140 | git config --global user.email "${EMAIL}" &&\ 141 | git config --global safe.directory "*" &&\ 142 | (echo "(custom-set-variables"; echo " '(c-basic-offset 4)"; echo " '(indent-tabs-mode nil))") > ~/.emacs &&\ 143 | (echo "(setq native-comp-jit-compilation nil)"; echo "(setq native-comp-async-report-warnings-errors 'silent)") >> ~/.emacs &&\ 144 | (echo "set expandtab"; echo "set shiftwidth=4"; echo "set softtabstop=4") > ~/.vimrc &&\ 145 | cat /dev/null > ~/.bash_profile &&\ 146 | echo "# 2022: avoid a Docker bug with user mapping by listing working directory" >> ~/.bash_profile &&\ 147 | echo "ls -al > /dev/null" >> ~/.bash_profile &&\ 148 | echo "for i in \`mount | grep /home/cs61-user | sed 's/^.*\\(\\/home[^ ]*\\).*/\\\\1/'\`; do ls -al \$i > /dev/null; done" >> ~/.bash_profile &&\ 149 | echo "# make ssh-auth.sock user-readable" >> ~/.bash_profile &&\ 150 | (echo "if test -e /run/host-services/ssh-auth.sock; then"; echo " sudo chown cs61-user:cs61-user /run/host-services/ssh-auth.sock"; echo "fi") >> ~/.bash_profile &&\ 151 | echo ". ~/.bashrc" >> ~/.bash_profile &&\ 152 | echo "export PATH=/usr/x86_64-linux-gnu/bin:\$PATH" >> ~/.bashrc &&\ 153 | rm -f ~/.bash_logout &&\ 154 | echo "add-auto-load-safe-path ~" > ~/.gdbinit 155 | 156 | WORKDIR /home/cs61-user 157 | CMD ["/bin/bash", "-l"] 158 | 159 | # Initial version of this Dockerfile by Todd Morrill, CS 61 DCE student 160 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | CS 61/Chickadee Docker 2 | ====================== 3 | 4 | The [Docker][] container-based virtualization service lets you run a 5 | minimal Chickadee environment, including Linux, on a Mac OS X or Windows 6 | computer, without the overhead of a full virtual machine like [VMware 7 | Workstation][], [VMware Fusion][], or [VirtualBox][]. 8 | 9 | It should be possible to do all your work for Chickadee on Docker. 10 | (However, you may prefer to set up a local environent.) 11 | 12 | Advantages of Docker: 13 | 14 | * Docker can start and stop virtual machines incredibly quickly. 15 | * Docker-based virtual machines are small and occupy little space on your machine. 16 | * With Docker, you can easily *edit* your code in your home environment, but 17 | *compile and run* it on a Linux host. 18 | 19 | Disadvantages of Docker: 20 | 21 | * Docker does not offer a graphical environment. You will need to run Chickadee 22 | exclusively in the terminal. 23 | * Docker technology is less user-friendly than virtual machines. You’ll have 24 | to type weird commands. 25 | * You won’t get the fun, different feeling of a graphical Linux desktop. 26 | 27 | 28 | ## Creating Chickadee Docker 29 | 30 | To prepare to build your Docker environment: 31 | 32 | 1. Download and install [Docker][]. 33 | 34 | 2. Clone your [Chickadee repository][repo] onto your computer. 35 | 36 | 3. Change into the `chickadee/docker` directory. 37 | 38 | 4. Run this command. It will take a while—up to ten minutes. 39 | 40 | ```shellsession 41 | $ ./build-docker 42 | ``` 43 | 44 | The command starts up a virtual Linux-based computer running inside your 45 | computer. It then installs a bunch of software useful for Chickadee on that 46 | environment, then takes a snapshot of the running environment. (The 47 | snapshot has a name, such as `chickadee:latest` or `chickadee:arm64`.) Once the 48 | snapshot is created, it’ll take just a second or so for Docker to restart 49 | it. 50 | 51 | We may need to change the Docker image during the term. If we do, you’ll 52 | update your repository to get the latest Dockerfile, then re-run the 53 | `./build-docker` command from Step 4. However, later runs should be 54 | faster since they’ll take advantage of your previous work. 55 | 56 | > `./build-docker` is a wrapper around `docker build`. On x86-64 hosts, it runs 57 | > `docker build -t chickadee:latest -f Dockerfile --platform linux/amd64 .` 58 | {.note} 59 | 60 | ## Running Chickadee Docker by script 61 | 62 | Our handout repository contains 63 | a `run-docker` script that provides good arguments and boots Docker into 64 | a view of the current directory. We will update this script throughout the 65 | term. 66 | 67 | For example, here’s an example of running CS 61 Docker on a Mac OS X host. At 68 | first, `uname` (a program that prints the name of the currently running 69 | operating system) reports `Darwin`. But after `./cs61-run-docker` connects the 70 | terminal to a Linux virtual machine, `uname` reports `Linux`. At the end of 71 | the example, `exit` quits the Docker environment and returns the terminal to 72 | Mac OS X. 73 | 74 | ```shellsession 75 | $ cd ~/chickadee 76 | $ uname 77 | Darwin 78 | $ uname -a 79 | Darwin Eddies-MacBook-Pro.local 20.6.0 Darwin Kernel Version 20.6.0: Wed Jun 23 00:26:27 PDT 2021; root:xnu-7195.141.2~5/RELEASE_ARM64_T8101 arm64 80 | $ ./run-docker 81 | cs61-user@a47f05ea5085:~/chickadee$ uname 82 | Linux 83 | cs61-user@a47f05ea5085:~/chickadee$ uname -a 84 | Linux 8006bb91a43b 6.10.4-linuxkit #1 SMP PREEMPT_DYNAMIC Mon Aug 12 08:48:58 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux 85 | cs61-user@a47f05ea5085:~/chickadee$ ls 86 | common cs61-run-docker docker README.md 87 | cs61-user@a47f05ea5085:~/chickadee$ exit 88 | exit 89 | $ 90 | ``` 91 | 92 | A prompt like `cs61-user@a47f05ea5085:~$` means that your terminal is 93 | connected to the VM. (The `a47f05ea5085` part is a unique identifier for this 94 | running VM.) You can execute any Linux commands you want. To escape from the 95 | VM, type Control-D or run the `exit` command. 96 | 97 | The script assumes your Docker container is named `chickadee:latest` or `chickadee:arm64`. 98 | 99 | 100 | ### Running Docker by hand 101 | 102 | If you don’t want to use the script, use a command like the following. 103 | 104 | ```shellsession 105 | $ docker run -it --platform linux/amd64 --rm -v ~/chickadee:/home/cs61-user/chickadee chickadee:latest 106 | ``` 107 | 108 | Explanation: 109 | 110 | * `docker run` tells Docker to start a new virtual machine. 111 | * `-it` says Docker should run interactively (`-i`) using a terminal (`-t`). 112 | * `--platform linux/amd64` says Docker should emulate an x86-64-based machine. 113 | It’s necessary to specify this if you have (for example) an Apple M1-based 114 | laptop. 115 | * `--rm` says Docker should remove the virtual machine when it is done. 116 | * `-v LOCALDIR:LINUXDUR` says Docker should share a directory between your 117 | host and the Docker virtual machine. Here, I’ve asked for the host’s 118 | `~/chickadee` directory to be mapped inside the virtual machine onto the 119 | `/home/cs61-user/chickadee` directory, which is the virtual machine 120 | user’s `~/chickadee` directory. 121 | * `cs61:latest` names the Docker image to run (namely, the one you built). 122 | 123 | Here’s an example session: 124 | 125 | ```shellsession 126 | $ docker run -it --platform linux/amd64 --rm -v ~/chickadee:/home/cs61-user/chickadee chickadee:latest 127 | cs61-user@a15e6c4c8dbe:~$ ls 128 | chickadee 129 | cs61-user@a15e6c4c8dbe:~$ echo "Hello, world" 130 | Hello, world 131 | cs61-user@a15e6c4c8dbe:~$ cs61-docker-version 132 | 19 133 | cs61-user@a15e6c4c8dbe:~$ exit 134 | exit 135 | $ 136 | ``` 137 | 138 | [Docker]: https://docker.com/ 139 | [VMware Workstation]: https://www.vmware.com/products/workstation-player.html 140 | [VMware Fusion]: https://www.vmware.com/products/fusion.html 141 | [VirtualBox]: https://www.virtualbox.org/ 142 | [repo]: https://github.com/cs161/chickadee-s25/ 143 | -------------------------------------------------------------------------------- /docker/build-docker: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | cd `dirname $0` 4 | 5 | arch="`arch`" 6 | tag= 7 | uid= 8 | platform= 9 | args=() 10 | 11 | # Except on Darwin, adopt current UID by default. 12 | if test "`uname`" != Darwin; then 13 | uid="`id -u`" 14 | if [ $uid -le 1000 ]; then uid=""; fi 15 | fi 16 | 17 | usage () { 18 | armtext= 19 | if test "$arch" = "arm64" -o "$arch" = "aarch64"; then 20 | armtext=" [-a|--arm] [-x|--x86-64] [-u UID]" 21 | fi 22 | echo "Usage: build-docker$armtext" 1>&2 23 | exit 1 24 | } 25 | 26 | while test "$#" -ne 0; do 27 | if test "$1" = "-a" -o "$1" = "--arm" -o "$1" = "--arm64"; then 28 | if test "`arch`" = "arm64" -o "`arch`" = "aarch64"; then 29 | platform=linux/arm64 30 | shift 31 | else 32 | echo "\`build-docker --arm\` only works on ARM64 hosts" 1>&2 33 | exit 1 34 | fi 35 | elif test "$1" = "-x" -o "$1" = "--x86-64" -o "$1" = "--x86_64" -o "$1" = "--amd64"; then 36 | platform=linux/amd64 37 | shift 38 | elif expr "$1" : "--progress=.*" "|" "$1" = "--no-cache" > /dev/null; then 39 | args+=( "$1" ) 40 | shift 41 | elif test "$1" = "-u" -o "$1" = "--user"; then 42 | if expr "$2" : "[0-9][0-9]*$" >/dev/null; then 43 | uid="$2" 44 | shift 2 45 | else 46 | usage 47 | fi 48 | elif expr "$1" : "-u[0-9]*$" "|" "$1" : "--user=[0-9][0-9]*" >/dev/null; then 49 | uid="`echo "$1" | sed 's/^[^0-9]*//'`" 50 | shift 51 | elif test "$1" = "-t" -o "$1" = "--tag"; then 52 | if expr "$2" : "..*$" >/dev/null; then 53 | tag="$2" 54 | shift 2 55 | else 56 | usage 57 | fi 58 | elif expr "$1" : "-t..*$" "|" "$1" : "--tag=..*" >/dev/null; then 59 | tag="`echo "$1" | sed 's/^--tag=//;s/^-t//'`" 60 | shift 61 | else 62 | usage 63 | fi 64 | done 65 | 66 | if test -z "$platform" -a \( "$arch" = "arm64" -o "$arch" = "aarch64" \); then 67 | platform=linux/arm64 68 | elif test -z "$platform"; then 69 | platform=linux/amd64 70 | fi 71 | 72 | if test -z "$tag" -a "$platform" = linux/arm64; then 73 | tag=chickadee:arm64 74 | elif test -z "$tag"; then 75 | tag=chickadee:latest 76 | fi 77 | 78 | if test -n "$uid"; then 79 | args+=( --build-arg UID="$uid" ) 80 | fi 81 | 82 | if test $platform = linux/arm64; then 83 | exec docker build -t "$tag" -f Dockerfile.arm64 --platform linux/arm64 ${args[@]} . 84 | else 85 | exec docker build -t "$tag" -f Dockerfile --platform linux/amd64 ${args[@]} . 86 | fi 87 | -------------------------------------------------------------------------------- /docker/ubuntu-arm64.sources: -------------------------------------------------------------------------------- 1 | Types: deb 2 | URIs: http://ports.ubuntu.com/ubuntu-ports/ 3 | Suites: noble noble-updates noble-backports 4 | Components: main universe restricted multiverse 5 | Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 6 | Architectures: arm64 7 | 8 | Types: deb 9 | URIs: http://ports.ubuntu.com/ubuntu-ports/ 10 | Suites: noble-security 11 | Components: main universe restricted multiverse 12 | Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 13 | Architectures: arm64 14 | 15 | Types: deb 16 | URIs: http://archive.ubuntu.com/ubuntu/ 17 | Suites: noble noble-updates noble-backports 18 | Components: main universe restricted multiverse 19 | Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 20 | Architectures: amd64 21 | 22 | Types: deb 23 | URIs: http://security.ubuntu.com/ubuntu/ 24 | Suites: noble-security 25 | Components: main universe restricted multiverse 26 | Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 27 | Architectures: amd64 28 | -------------------------------------------------------------------------------- /elf.h: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_ELF_H 2 | #define CHICKADEE_ELF_H 3 | #if defined(CHICKADEE_KERNEL) || defined(CHICKADEE_PROCESS) 4 | #include "types.h" 5 | #else 6 | #include 7 | #include 8 | #endif 9 | 10 | // elf.h 11 | // 12 | // Structures and constants for ELF (Executable Linking Format) executable 13 | // files. 14 | 15 | #define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian 16 | 17 | // executable header 18 | struct elf_header { 19 | uint32_t e_magic; // @0 must equal ELF_MAGIC 20 | uint8_t e_elf[12]; 21 | uint16_t e_type; // @0x10 22 | uint16_t e_machine; 23 | uint32_t e_version; 24 | uint64_t e_entry; // @0x18 entry point: address of first instruction 25 | uint64_t e_phoff; // @0x20 offset from elf_header to 1st elf_program 26 | uint64_t e_shoff; // @0x28 offset from elf_header to 1st elf_section 27 | uint32_t e_flags; 28 | uint16_t e_ehsize; 29 | uint16_t e_phentsize; // @0x36 should equal sizeof(elf_program) 30 | uint16_t e_phnum; // @0x38 number of elf_programs 31 | uint16_t e_shentsize; // @0x3a should equal sizeof(elf_section) 32 | uint16_t e_shnum; // @0x3c number of elf_sections 33 | uint16_t e_shstrndx; // @0x3e 34 | }; 35 | 36 | // program header (required by the loader) 37 | struct elf_program { 38 | uint32_t p_type; // @0x00 see ELF_PTYPE below 39 | uint32_t p_flags; // @0x04 see ELF_PFLAG below 40 | uint64_t p_offset; // @0x08 offset from elf_header to program data 41 | uint64_t p_va; // @0x10 virtual address to load data 42 | uint64_t p_pa; // @0x18 not used 43 | uint64_t p_filesz; // @0x20 number of bytes of program data 44 | uint64_t p_memsz; // @0x28 number of bytes in memory (any bytes beyond 45 | // p_filesz are initialized to zero) 46 | uint64_t p_align; // @0x30 47 | }; 48 | 49 | // section definition (not required by loader) 50 | struct elf_section { 51 | uint32_t sh_name; 52 | uint32_t sh_type; 53 | uint64_t sh_flags; 54 | uint64_t sh_addr; 55 | uint64_t sh_offset; 56 | uint64_t sh_size; 57 | uint32_t sh_link; 58 | uint32_t sh_info; 59 | uint64_t sh_addralign; 60 | uint64_t sh_entsize; 61 | }; 62 | 63 | // symbol table entry (not required by loader) 64 | struct elf_symbol { 65 | uint32_t st_name; 66 | uint8_t st_info; 67 | uint8_t st_other; 68 | uint16_t st_shndx; 69 | uint64_t st_value; 70 | uint64_t st_size; 71 | }; 72 | 73 | // in-memory reference to debug symbol table + string table 74 | struct elf_symtabref { 75 | elf_symbol* sym; 76 | size_t nsym; 77 | char* strtab; 78 | size_t size; 79 | }; 80 | 81 | // Values for elf_header::e_type 82 | #define ELF_ET_EXEC 2 // executable file 83 | 84 | // Values for elf_program::p_type 85 | #define ELF_PTYPE_LOAD 1 86 | 87 | // Flag bits for elf_program::p_flags 88 | #define ELF_PFLAG_EXEC 1 89 | #define ELF_PFLAG_WRITE 2 90 | #define ELF_PFLAG_READ 4 91 | 92 | // Values for elf_section::sh_type 93 | #define ELF_SHT_NULL 0 94 | #define ELF_SHT_PROGBITS 1 95 | #define ELF_SHT_SYMTAB 2 96 | #define ELF_SHT_STRTAB 3 97 | #define ELF_SHT_NOBITS 8 98 | 99 | // Values for elf_section::sh_flags 100 | #define ELF_SHF_ALLOC 2 101 | 102 | // Values for elf_symbol::st_name 103 | #define ELF_STN_UNDEF 0 104 | 105 | // Values for elf_symbol::st_shndx 106 | #define ELF_SHN_UNDEF 0 107 | #define ELF_SHN_ABS 0xFFF1U 108 | #define ELF_SHN_COMMON 0xFFF2U 109 | 110 | // Values for elf_symbol::st_info 111 | #define ELF_STB_MASK 0xF0 112 | #define ELF_STB_LOCAL 0x00 113 | #define ELF_STB_GLOBAL 0x10 114 | #define ELF_STB_WEAK 0x20 115 | #define ELF_STT_MASK 0x0F 116 | #define ELF_STT_OBJECT 0x01 117 | #define ELF_STT_FUNC 0x02 118 | 119 | #endif /* !CHICKADEE_ELF_H */ 120 | -------------------------------------------------------------------------------- /initfs/emerson.txt: -------------------------------------------------------------------------------- 1 | When piped a tiny voice hard by, 2 | Gay and polite, a cheerful cry, 3 | Chic-chicadeedee! saucy note 4 | Out of sound heart and merry throat 5 | -------------------------------------------------------------------------------- /k-ahci.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_AHCI_HH 2 | #define CHICKADEE_K_AHCI_HH 3 | #include "kernel.hh" 4 | #include "k-wait.hh" 5 | 6 | // achistate: communication with modern SATA disks 7 | 8 | struct ahcistate { 9 | // interesting IDE commands 10 | // Specification: "ATA/ATAPI Command Set" for ATA-8 11 | enum idecommand { 12 | cmd_identify_device = 0xEC, 13 | cmd_set_features = 0xEF, 14 | cmd_read_fpdma_queued = 0x60, 15 | cmd_write_fpdma_queued = 0x61 16 | }; 17 | 18 | // memory-mapped I/O structures for device communication 19 | // Specification/comment field names: "Serial ATA AHCI 1.3.1 Specification" 20 | // The kernel finds the MMIO address of a `struct regs` using PCI. 21 | // This contains up to 32 `struct portregs`, each corresponding to 22 | // a different disk; most communication with the disk uses the 23 | // `portregs`. One `ahcistate` corresponds to one disk. 24 | struct portregs { 25 | uint64_t cmdlist_pa; // PxCLB: physical addr of `cmdheader` array 26 | uint64_t rfis_pa; // PxRFIS: physical address of `rfisstate` 27 | uint32_t interrupt_status; // PxIS 28 | uint32_t interrupt_enable; // PxIE 29 | uint32_t command; // PxCMD 30 | uint32_t reserved0; 31 | uint32_t rstatus; // PxTFD 32 | uint32_t psig; // PxSIG 33 | uint32_t sstatus; // PxSSTS: 0 means port doesn't exist 34 | uint32_t scontrol; // PxSCTL 35 | uint32_t serror; // PxSERR [R/clear] 36 | uint32_t ncq_active_mask; // PxSACT 37 | uint32_t command_mask; // PxCI 38 | uint32_t sata_notification; // PxSNTF 39 | uint32_t pfbs, sleep; 40 | uint32_t reserved[14]; 41 | }; 42 | struct regs { 43 | uint32_t capabilities; // CAP: device capabilities [R] 44 | uint32_t ghc; // GHC: global [adapter] control [R/W] 45 | uint32_t interrupt_status; // IS: interrupt status [R/clear] 46 | uint32_t port_mask; // PI: addressable ports (may not exist) 47 | uint32_t ahci_version; // VS 48 | uint32_t ccc_control; // CCC_CTL 49 | uint32_t ccc_port_mask; // CCC_PORTS 50 | uint32_t em_loc, em_ctl, cap2, bohc, reserved[53]; 51 | portregs p[32]; 52 | }; 53 | enum { 54 | pcmd_interface_mask = 0xF0000000U, 55 | pcmd_interface_active = 0x10000000U, pcmd_interface_idle = 0U, 56 | pcmd_command_running = 0x8000, pcmd_rfis_running = 0x4000, 57 | pcmd_rfis_enable = 0x0010, 58 | pcmd_rfis_clear = 0x8, pcmd_power_up = 0x6, pcmd_start = 0x1U, 59 | 60 | rstatus_busy = 0x80U, rstatus_datareq = 0x08U, rstatus_error = 0x01U, 61 | 62 | interrupt_device_to_host = 0x1U, 63 | interrupt_ncq_complete = 0x8U, 64 | interrupt_error_mask = 0x7D800010U, 65 | interrupt_fatal_error_mask = 0x78000000U, // HBFS|HBDS|IFS|TFES 66 | 67 | ghc_ahci_enable = 0x80000000U, ghc_interrupt_enable = 0x2U 68 | }; 69 | static inline bool sstatus_active(uint32_t sstatus) { 70 | return (sstatus & 0x03) == 3 71 | || ((1U << ((sstatus & 0xF00) >> 8)) & 0x144) != 0; 72 | } 73 | 74 | // DMA structures for device communication 75 | // Contains data buffers and commands for disk communication. 76 | // This structure lives in host memory; the device finds it via 77 | // physical-memory pointers in `portregs`. 78 | struct bufstate { // initialized by `push_buffer` 79 | uint64_t pa; 80 | uint32_t reserved; 81 | uint32_t maxbyte; // size of buffer minus 1 82 | }; 83 | struct __attribute__((aligned(128))) cmdtable { 84 | uint32_t cfis[16]; // command definition; set by `issue_*` 85 | uint32_t acmd[4]; 86 | uint32_t reserved[12]; 87 | bufstate buf[16]; // called PRD in specifications 88 | }; 89 | struct cmdheader { 90 | uint16_t flags; 91 | uint16_t nbuf; 92 | uint32_t buf_byte_pos; 93 | uint64_t cmdtable_pa; // physical address of `cmdtable` 94 | uint64_t reserved[2]; 95 | }; 96 | struct __attribute__((aligned(256))) rfisstate { 97 | uint32_t rfis[64]; 98 | }; 99 | struct __attribute__((aligned(1024))) dmastate { 100 | cmdheader ch[32]; 101 | volatile rfisstate rfis; 102 | cmdtable ct[32]; 103 | }; 104 | 105 | enum { 106 | cfis_command = 0x8027, 107 | ch_clear_flag = 0x400, ch_write_flag = 0x40 108 | }; 109 | 110 | static constexpr size_t sectorsize = 512; 111 | 112 | 113 | // DMA and memory-mapped I/O state 114 | dmastate dma_; 115 | int pci_addr_; 116 | int sata_port_; 117 | volatile regs* dr_; 118 | volatile portregs* pr_; 119 | 120 | // metadata read from disk at startup (constant thereafter) 121 | unsigned irq_; // interrupt number 122 | size_t nsectors_; // # sectors on disk 123 | unsigned nslots_; // # NCQ slots 124 | unsigned slots_full_mask_; // mask with each valid slot set to 1 125 | 126 | // modifiable state 127 | spinlock lock_; 128 | wait_queue wq_; 129 | unsigned nslots_available_; // # slots available for commands 130 | uint32_t slots_outstanding_mask_; // 1 == that slot is used 131 | std::atomic* slot_status_[32]; // ptrs to status storage, one per slot 132 | 133 | 134 | ahcistate(int pci_addr, int sata_port, volatile regs* mr); 135 | NO_COPY_OR_ASSIGN(ahcistate); 136 | static ahcistate* find(int pci_addr = 0, int sata_port = 0); 137 | 138 | // high-level functions (they block) 139 | inline int read(void* buf, size_t sz, size_t off); 140 | inline int write(const void* buf, size_t sz, size_t off); 141 | int read_or_write(idecommand cmd, void* buf, size_t sz, size_t off); 142 | 143 | // interrupt handlers 144 | void handle_interrupt(); 145 | void handle_error_interrupt(); 146 | 147 | // internal functions 148 | void clear(int slot); 149 | void push_buffer(int slot, void* data, size_t sz); 150 | void issue_meta(int slot, idecommand cmd, int features, int count = -1); 151 | void issue_ncq(int slot, idecommand cmd, size_t sector, 152 | bool fua = false, int priority = 0); 153 | void acknowledge(int slot, int status = 0); 154 | void await_basic(int slot); 155 | }; 156 | 157 | inline int ahcistate::read(void* buf, size_t sz, size_t off) { 158 | return read_or_write(cmd_read_fpdma_queued, buf, sz, off); 159 | } 160 | inline int ahcistate::write(const void* buf, size_t sz, size_t off) { 161 | return read_or_write(cmd_write_fpdma_queued, const_cast(buf), 162 | sz, off); 163 | } 164 | 165 | #endif 166 | -------------------------------------------------------------------------------- /k-alloc.cc: -------------------------------------------------------------------------------- 1 | #include "kernel.hh" 2 | #include "k-lock.hh" 3 | 4 | static spinlock page_lock; 5 | static uintptr_t next_free_pa; 6 | 7 | 8 | // init_kalloc 9 | // Initialize stuff needed by `kalloc`. Called from `init_hardware`, 10 | // after `physical_ranges` is initialized. 11 | void init_kalloc() { 12 | // do nothing for now 13 | } 14 | 15 | 16 | // kalloc(sz) 17 | // Allocate and return a pointer to at least `sz` contiguous bytes of 18 | // memory. Returns `nullptr` if `sz == 0` or on failure. 19 | // 20 | // The caller should initialize the returned memory before using it. 21 | // The handout allocator sets returned memory to 0xCC (this corresponds 22 | // to the x86 `int3` instruction and may help you debug). 23 | // 24 | // If `sz` is a multiple of `PAGESIZE`, the returned pointer is guaranteed 25 | // to be page-aligned. 26 | // 27 | // The handout code does not free memory and allocates memory in units 28 | // of pages. 29 | void* kalloc(size_t sz) { 30 | if (sz == 0 || sz > PAGESIZE) { 31 | return nullptr; 32 | } 33 | 34 | auto irqs = page_lock.lock(); 35 | void* ptr = nullptr; 36 | 37 | // skip over reserved and kernel memory 38 | auto range = physical_ranges.find(next_free_pa); 39 | while (range != physical_ranges.end()) { 40 | if (range->type() == mem_available) { 41 | // use this page 42 | ptr = pa2kptr(next_free_pa); 43 | next_free_pa += PAGESIZE; 44 | break; 45 | } else { 46 | // move to next range 47 | next_free_pa = range->last(); 48 | ++range; 49 | } 50 | } 51 | 52 | page_lock.unlock(irqs); 53 | 54 | if (ptr) { 55 | // tell sanitizers the allocated page is accessible 56 | asan_mark_memory(ka2pa(ptr), PAGESIZE, false); 57 | // initialize to `int3` 58 | memset(ptr, 0xCC, PAGESIZE); 59 | } 60 | return ptr; 61 | } 62 | 63 | 64 | // kfree(ptr) 65 | // Free a pointer previously returned by `kalloc`. Does nothing if 66 | // `ptr == nullptr`. 67 | void kfree(void* ptr) { 68 | if (ptr) { 69 | // tell sanitizers the freed page is inaccessible 70 | asan_mark_memory(ka2pa(ptr), PAGESIZE, true); 71 | } 72 | log_printf("kfree not implemented yet\n"); 73 | } 74 | 75 | 76 | // operator new, operator delete 77 | // Expressions like `new (std::nothrow) T(...)` and `delete x` work, 78 | // and call kalloc/kfree. 79 | void* operator new(size_t sz, const std::nothrow_t&) noexcept { 80 | return kalloc(sz); 81 | } 82 | void* operator new(size_t sz, std::align_val_t, const std::nothrow_t&) noexcept { 83 | return kalloc(sz); 84 | } 85 | void* operator new[](size_t sz, const std::nothrow_t&) noexcept { 86 | return kalloc(sz); 87 | } 88 | void* operator new[](size_t sz, std::align_val_t, const std::nothrow_t&) noexcept { 89 | return kalloc(sz); 90 | } 91 | void operator delete(void* ptr) noexcept { 92 | kfree(ptr); 93 | } 94 | void operator delete(void* ptr, size_t) noexcept { 95 | kfree(ptr); 96 | } 97 | void operator delete(void* ptr, std::align_val_t) noexcept { 98 | kfree(ptr); 99 | } 100 | void operator delete(void* ptr, size_t, std::align_val_t) noexcept { 101 | kfree(ptr); 102 | } 103 | void operator delete[](void* ptr) noexcept { 104 | kfree(ptr); 105 | } 106 | void operator delete[](void* ptr, size_t) noexcept { 107 | kfree(ptr); 108 | } 109 | void operator delete[](void* ptr, std::align_val_t) noexcept { 110 | kfree(ptr); 111 | } 112 | void operator delete[](void* ptr, size_t, std::align_val_t) noexcept { 113 | kfree(ptr); 114 | } 115 | -------------------------------------------------------------------------------- /k-apic.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_APIC_HH 2 | #define CHICKADEE_K_APIC_HH 3 | #include "kernel.hh" 4 | 5 | #define MSR_IA32_APIC_BASE 0x1B 6 | 7 | struct lapicstate { 8 | // APIC register IDs 9 | enum { 10 | reg_id = 0x02, 11 | reg_tpr = 0x08, 12 | reg_svr = 0x0F, 13 | reg_isr = 0x10, 14 | reg_tmr = 0x18, 15 | reg_irr = 0x20, 16 | reg_esr = 0x28, 17 | reg_eoi = 0x0B, 18 | reg_cmci = 0x2F, 19 | reg_lvt_timer = 0x32, 20 | reg_lvt_lint0 = 0x35, 21 | reg_lvt_lint1 = 0x36, 22 | reg_lvt_error = 0x37, 23 | reg_icr_low = 0x30, 24 | reg_icr_high = 0x31, 25 | reg_timer_initial_count = 0x38, 26 | reg_timer_current_count = 0x39, 27 | reg_timer_divide = 0x3E 28 | }; 29 | 30 | enum ipi_type_t { 31 | ipi_init = 0x500, 32 | ipi_startup = 0x600 33 | }; 34 | 35 | enum { 36 | ipi_delivery_status = 0x1000, 37 | ipi_level_assert = 0x4000, 38 | ipi_trigger_level = 0x8000, 39 | ipi_given = 0, 40 | ipi_self = 0x40000, 41 | ipi_all = 0x80000, 42 | ipi_all_excluding_self = 0xC0000 43 | }; 44 | 45 | enum { 46 | timer_divide_1 = 0x0B, 47 | timer_periodic = 0x20000 48 | }; 49 | 50 | enum { 51 | lvt_masked = 0x10000 52 | }; 53 | 54 | static constexpr uint64_t lapic_pa = 0xFEE00000; 55 | 56 | // APICs can't be copied or assigned 57 | NO_COPY_OR_ASSIGN(lapicstate) 58 | 59 | // return the local APIC for the current CPU 60 | static inline lapicstate& get(); 61 | 62 | // enable this APIC with interrupt number `vector` for spurious IRQs 63 | inline void enable_lapic(int vector); 64 | // disable this APIC 65 | inline void disable_lapic(); 66 | 67 | // return this APIC's ID 68 | inline uint32_t id() const; 69 | 70 | // return the current error state 71 | inline uint32_t error(); 72 | 73 | // acknowledge any outstanding interrupts 74 | inline void ack(); 75 | 76 | // send an IPI to all other processes 77 | inline void ipi_others(ipi_type_t ipi_type, int vector = 0); 78 | // return if the previous IPI has not completed 79 | inline bool ipi_pending() const; 80 | 81 | // read a value from APIC register `reg` 82 | inline uint32_t read(int reg) const; 83 | // write `v` to APIC register `reg` 84 | inline void write(int reg, uint32_t v); 85 | 86 | private: 87 | struct apic_reg { 88 | uint32_t v; 89 | uint32_t padding[3]; 90 | }; 91 | 92 | volatile apic_reg reg_[0x40]; 93 | }; 94 | 95 | 96 | class ioapicstate { 97 | public: 98 | // APIC register IDs 99 | enum { 100 | reg_id = 0x0, 101 | reg_ver = 0x1, 102 | reg_redtbl = 0x10 103 | }; 104 | 105 | enum { 106 | redtbl_masked = 0x10000 107 | }; 108 | 109 | static constexpr uint64_t ioapic_pa = 0xFEC00000; 110 | 111 | // APICs can't be copied or assigned 112 | NO_COPY_OR_ASSIGN(ioapicstate) 113 | 114 | // return the machine's IOAPIC 115 | static inline ioapicstate& get(); 116 | 117 | // return this APIC's ID 118 | inline uint32_t id() const; 119 | 120 | inline void enable_irq(int entry, int vector, int cpu_apicid); 121 | inline void disable_irq(int entry); 122 | 123 | // read a value from APIC register `reg` 124 | inline uint32_t read(int reg) const; 125 | // write `v` to APIC register `reg` 126 | inline void write(int reg, uint32_t v); 127 | 128 | private: 129 | struct apic_reg { 130 | uint32_t v; 131 | uint32_t padding[3]; 132 | }; 133 | 134 | volatile mutable apic_reg reg_[2]; 135 | }; 136 | 137 | 138 | inline lapicstate& lapicstate::get() { 139 | return *pa2kptr(lapic_pa); 140 | } 141 | inline uint32_t lapicstate::id() const { 142 | return read(reg_id) >> 24; 143 | } 144 | inline void lapicstate::enable_lapic(int vector) { 145 | write(reg_svr, (read(reg_svr) & ~0xFF) | 0x100 | vector); 146 | } 147 | inline void lapicstate::disable_lapic() { 148 | write(reg_svr, read(reg_svr) & ~0x100); 149 | } 150 | inline uint32_t lapicstate::error() { 151 | write(reg_esr, 0); 152 | return read(reg_esr); 153 | } 154 | inline void lapicstate::ack() { 155 | write(reg_eoi, 0); 156 | } 157 | inline void lapicstate::ipi_others(ipi_type_t t, int vector) { 158 | write(reg_icr_low, ipi_all_excluding_self | ipi_level_assert | t | vector); 159 | } 160 | inline bool lapicstate::ipi_pending() const { 161 | return (read(reg_icr_low) & ipi_delivery_status) != 0; 162 | } 163 | inline uint32_t lapicstate::read(int reg) const { 164 | return reg_[reg].v; 165 | } 166 | inline void lapicstate::write(int reg, uint32_t v) { 167 | reg_[reg].v = v; 168 | } 169 | 170 | inline ioapicstate& ioapicstate::get() { 171 | return *pa2kptr(ioapic_pa); 172 | } 173 | inline uint32_t ioapicstate::id() const { 174 | return read(reg_id) >> 24; 175 | } 176 | inline void ioapicstate::enable_irq(int entry, int vector, int cpu_lapicid) { 177 | write(reg_redtbl + 2 * entry, vector); 178 | write(reg_redtbl + 2 * entry + 1, unsigned(cpu_lapicid) << 24); 179 | } 180 | inline void ioapicstate::disable_irq(int entry) { 181 | write(reg_redtbl + 2 * entry, redtbl_masked); 182 | write(reg_redtbl + 2 * entry + 1, 0); 183 | } 184 | inline uint32_t ioapicstate::read(int reg) const { 185 | reg_[0].v = reg; 186 | return reg_[1].v; 187 | } 188 | inline void ioapicstate::write(int reg, uint32_t v) { 189 | reg_[0].v = reg; 190 | reg_[1].v = v; 191 | } 192 | 193 | #endif 194 | -------------------------------------------------------------------------------- /k-chkfs.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_CHKFS_HH 2 | #define CHICKADEE_K_CHKFS_HH 3 | #include "kernel.hh" 4 | #include "chickadeefs.hh" 5 | #include "k-lock.hh" 6 | #include "k-wait.hh" 7 | 8 | // buffer cache 9 | 10 | using block_clean_function = void (*)(bcslot*); 11 | 12 | struct bcslot { 13 | using blocknum_t = chkfs::blocknum_t; 14 | 15 | enum state_t { 16 | s_empty, s_allocated, s_loading, s_clean, s_dirty 17 | }; 18 | 19 | spinlock lock_; 20 | 21 | std::atomic state_ = s_empty; // slot state 22 | std::atomic ref_ = 0; // reference count 23 | 24 | blocknum_t bn_; // disk block number (unless empty) 25 | unsigned char* buf_ = nullptr; // memory buffer 26 | proc* buf_owner_ = nullptr; // `proc` holding buffer content lock 27 | 28 | 29 | // return the index of this slot in the buffer cache 30 | inline size_t index() const; 31 | 32 | // test if this slot is empty (`state_ == s_empty`) 33 | inline bool empty() const; 34 | 35 | // test if `ptr` is contained in this slot's memory buffer 36 | inline bool contains(const void* ptr) const; 37 | 38 | // decrement reference count 39 | void decrement_reference_count(); 40 | 41 | // acquire/release buffer_lock_, the lock on buffer data 42 | void lock_buffer(); 43 | void unlock_buffer(); 44 | 45 | 46 | // internal functions 47 | void clear(); 48 | bool load(irqstate& irqs, block_clean_function cleaner); 49 | }; 50 | 51 | using bcref = ref_ptr; 52 | 53 | struct bufcache { 54 | using blocknum_t = chkfs::blocknum_t; 55 | 56 | static constexpr size_t nslots = 10; 57 | 58 | spinlock lock_; // protects all entries' bn_ and ref_ 59 | wait_queue read_wq_; 60 | bcslot slots_[nslots]; 61 | 62 | 63 | static inline bufcache& get(); 64 | 65 | bcref load(blocknum_t bn, block_clean_function cleaner = nullptr); 66 | 67 | int sync(int drop); 68 | 69 | private: 70 | static bufcache bc; 71 | 72 | bufcache(); 73 | NO_COPY_OR_ASSIGN(bufcache); 74 | }; 75 | 76 | 77 | inline bufcache& bufcache::get() { 78 | return bc; 79 | } 80 | 81 | inline size_t bcslot::index() const { 82 | auto& bc = bufcache::get(); 83 | assert(this >= bc.slots_ && this < bc.slots_ + bc.nslots); 84 | return this - bc.slots_; 85 | } 86 | 87 | inline bool bcslot::empty() const { 88 | return state_.load(std::memory_order_relaxed) == s_empty; 89 | } 90 | 91 | inline bool bcslot::contains(const void* ptr) const { 92 | return state_.load(std::memory_order_relaxed) >= s_clean 93 | && reinterpret_cast(ptr) - reinterpret_cast(buf_) 94 | < chkfs::blocksize; 95 | } 96 | 97 | inline void bcslot::clear() { 98 | assert(ref_ == 0); 99 | state_ = s_empty; 100 | if (buf_) { 101 | kfree(buf_); 102 | buf_ = nullptr; 103 | } 104 | } 105 | 106 | 107 | using chkfs_iref = ref_ptr; 108 | 109 | 110 | // chickadeefs state: a Chickadee file system on a specific disk 111 | // (Our implementation only speaks to `sata_disk`.) 112 | 113 | struct chkfsstate { 114 | using blocknum_t = chkfs::blocknum_t; 115 | using inum_t = chkfs::inum_t; 116 | static constexpr size_t blocksize = chkfs::blocksize; 117 | 118 | 119 | static inline chkfsstate& get(); 120 | 121 | // obtain an inode by number 122 | chkfs_iref inode(inum_t inum); 123 | // directory lookup in `dirino` 124 | chkfs_iref lookup_inode(chkfs::inode* dirino, const char* name); 125 | // directory lookup starting at root directory 126 | chkfs_iref lookup_inode(const char* name); 127 | 128 | blocknum_t allocate_extent(unsigned count = 1); 129 | 130 | 131 | private: 132 | static chkfsstate fs; 133 | 134 | chkfsstate(); 135 | NO_COPY_OR_ASSIGN(chkfsstate); 136 | }; 137 | 138 | 139 | inline chkfsstate& chkfsstate::get() { 140 | return fs; 141 | } 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /k-chkfsiter.cc: -------------------------------------------------------------------------------- 1 | #include "k-chkfsiter.hh" 2 | 3 | 4 | // MAIN CHICKADEEFS ITERATOR FUNCTIONS 5 | 6 | chkfs_fileiter& chkfs_fileiter::find(off_t off) { 7 | // if moving backwards, rewind to start 8 | off_ = off; 9 | if (!eptr_ || off_ < eoff_) { 10 | eoff_ = 0; 11 | eidx_ = 0; 12 | eptr_ = &ino_->direct[0]; 13 | indirect_slot_.reset(); 14 | } 15 | 16 | // walk extents to relevant position 17 | while (off_ >= eoff_ + eptr_->count * blocksize) { 18 | if (eptr_->count == 0) { 19 | goto not_found; 20 | } 21 | 22 | eoff_ += eptr_->count * blocksize; 23 | ++eidx_; 24 | ++eptr_; 25 | 26 | if (eidx_ < chkfs::ndirect) { 27 | // do nothing 28 | } else if ((eidx_ - chkfs::ndirect) % chkfs::extentsperblock == 0) { 29 | indirect_slot_.reset(); 30 | unsigned ibi = (eidx_ - chkfs::ndirect) / chkfs::extentsperblock; 31 | if (ino_->indirect.count <= ibi) { 32 | goto not_found; 33 | } 34 | auto& bc = bufcache::get(); 35 | indirect_slot_ = bc.load(ino_->indirect.first + ibi); 36 | if (!indirect_slot_) { 37 | goto not_found; 38 | } 39 | eptr_ = reinterpret_cast(indirect_slot_->buf_); 40 | } 41 | } 42 | 43 | return *this; 44 | 45 | not_found: 46 | eptr_ = nullptr; 47 | return *this; 48 | } 49 | 50 | 51 | void chkfs_fileiter::next() { 52 | if (eptr_ && eptr_->count != 0) { 53 | do { 54 | find(round_up(off_ + 1, blocksize)); 55 | } while (eptr_ && eptr_->first == 0 && eptr_->count != 0); 56 | } 57 | } 58 | 59 | 60 | int chkfs_fileiter::insert(blocknum_t first, unsigned count) { 61 | assert(ino_->is_write_locked()); 62 | assert(count != 0); 63 | assert(!eptr_ || !eptr_->count); 64 | assert((eoff_ % blocksize) == 0); 65 | auto& bc = bufcache::get(); 66 | auto ino_slot = inode()->slot(); 67 | 68 | // grow previous direct extent if possible 69 | if (eidx_ > 0 && eidx_ <= chkfs::ndirect) { 70 | chkfs::extent* peptr = &ino_->direct[eidx_ - 1]; 71 | if (peptr->first + peptr->count == first) { 72 | ino_slot->lock_buffer(); 73 | eptr_ = peptr; 74 | --eidx_; 75 | eoff_ -= eptr_->count * blocksize; 76 | eptr_->count += count; 77 | ino_slot->unlock_buffer(); 78 | return 0; 79 | } 80 | } 81 | 82 | // allocate & initialize indirect extent if necessary 83 | if (eidx_ == chkfs::ndirect && !ino_->indirect.count) { 84 | auto& chkfs = chkfsstate::get(); 85 | assert(!indirect_slot_); 86 | 87 | blocknum_t indirect_bn = chkfs.allocate_extent(1); 88 | if (indirect_bn >= blocknum_t(E_MINERROR)) { 89 | return int(indirect_bn); 90 | } 91 | 92 | indirect_slot_ = bc.load(indirect_bn); 93 | if (!indirect_slot_) { 94 | return E_NOMEM; 95 | } 96 | 97 | indirect_slot_->lock_buffer(); 98 | memset(indirect_slot_->buf_, 0, blocksize); 99 | indirect_slot_->unlock_buffer(); 100 | 101 | ino_slot->lock_buffer(); 102 | ino_->indirect.first = indirect_bn; 103 | ino_->indirect.count = 1; 104 | ino_slot->unlock_buffer(); 105 | } 106 | 107 | // fail if required to grow indirect extent 108 | if (eidx_ >= chkfs::ndirect && !indirect_slot_) { 109 | return E_FBIG; 110 | } 111 | 112 | // add new extent 113 | bcslot* slot; 114 | if (eidx_ < chkfs::ndirect) { 115 | slot = ino_slot; 116 | eptr_ = &ino_->direct[eidx_]; 117 | } else { 118 | slot = indirect_slot_.get(); 119 | eptr_ = reinterpret_cast(indirect_slot_->buf_) 120 | + (eidx_ - chkfs::ndirect) % chkfs::extentsperblock; 121 | } 122 | slot->lock_buffer(); 123 | if (eidx_ >= chkfs::ndirect 124 | && (eidx_ - chkfs::ndirect) % chkfs::extentsperblock != 0 125 | && eptr_[-1].first + eptr_[-1].count == first) { 126 | // grow previous extent 127 | --eptr_; 128 | --eidx_; 129 | eoff_ -= eptr_->count * blocksize; 130 | eptr_->count += count; 131 | } else { 132 | // add new extent 133 | eptr_->first = first; 134 | eptr_->count = count; 135 | } 136 | slot->unlock_buffer(); 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /k-chkfsiter.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_CHKFSITER_HH 2 | #define CHICKADEE_K_CHKFSITER_HH 3 | #include "k-chkfs.hh" 4 | 5 | class chkfs_fileiter { 6 | public: 7 | using blocknum_t = chkfs::blocknum_t; 8 | static constexpr size_t blocksize = chkfs::blocksize; 9 | 10 | 11 | // Initialize an iterator for `ino` at file offset `off`. 12 | // The caller must have a reference on `ino`. 13 | chkfs_fileiter(chkfs::inode* ino, off_t off = 0); 14 | NO_COPY_OR_ASSIGN(chkfs_fileiter); 15 | 16 | // Return the inode 17 | inline chkfs::inode* inode() const; 18 | 19 | // Return the current file offset 20 | inline off_t offset() const; 21 | // Return true iff the offset is within the file (i.e., in some extent) 22 | inline bool active() const; 23 | // Return true iff the offset does not point at data 24 | inline bool empty() const; 25 | 26 | // Return the block number corresponding to the current file offset. 27 | // Returns 0 if there is no block stored for the current offset. 28 | inline blocknum_t blocknum() const; 29 | // Return a buffer cache entry containing the current file offset’s data. 30 | // Returns nullptr if there is no block stored for the current offset. 31 | inline bcref load() const; 32 | // Return the file offset relative to the current block 33 | inline unsigned block_relative_offset() const; 34 | 35 | 36 | // Move the iterator to file offset `off`. Returns `*this`. 37 | chkfs_fileiter& find(off_t off); 38 | // Like `find(offset() + delta)` 39 | inline chkfs_fileiter& operator+=(ssize_t delta); 40 | // Like `find(offset() - delta)` 41 | inline chkfs_fileiter& operator-=(ssize_t delta); 42 | 43 | 44 | // Move the iterator to the next larger file offset with a different 45 | // present block. At the end of the file, the iterator becomes `!active()`. 46 | void next(); 47 | 48 | 49 | // Add the extent `[first, first+count)` at this file offset. 50 | // Returns 0 on success, a negative error code on failure. 51 | // 52 | // The handout version can only add an extent to the end of the file, 53 | // immediately after all existing extents. An assertion will fail if 54 | // `offset()` is in the middle of the file or is not block-aligned. 55 | // 56 | // The handout version will allocate an indirect extent if necessary, 57 | // but it supports at most one indirect-extent block. 58 | // 59 | // This function can call: 60 | // * `chkfsstate::allocate_extent`, to allocate an indirect extent 61 | // * `bufcache::load`, to find indirect-extent blocks 62 | // * `bcslot::lock_buffer` and `bcslot::unlock_buffer`, to acquire write 63 | // locks for blocks containing inode and/or indirect-extent entries 64 | int insert(blocknum_t first, uint32_t count = 1); 65 | 66 | 67 | private: 68 | chkfs::inode* ino_; // inode 69 | size_t off_ = 0; // file offset 70 | size_t eoff_ = 0; // file offset of this extent 71 | size_t eidx_ = 0; // index of this extent 72 | chkfs::extent* eptr_; // pointer into buffer cache to 73 | // extent for `off_` 74 | 75 | // bcref for indirect extent block for `eidx_` 76 | bcref indirect_slot_; 77 | }; 78 | 79 | 80 | inline chkfs_fileiter::chkfs_fileiter(chkfs::inode* ino, off_t off) 81 | : ino_(ino), eptr_(&ino->direct[0]) { 82 | assert(ino_); 83 | if (off != 0) { 84 | find(off); 85 | } 86 | } 87 | 88 | inline chkfs::inode* chkfs_fileiter::inode() const { 89 | return ino_; 90 | } 91 | inline off_t chkfs_fileiter::offset() const { 92 | return off_; 93 | } 94 | inline bool chkfs_fileiter::active() const { 95 | return eptr_ && eptr_->count != 0; 96 | } 97 | inline unsigned chkfs_fileiter::block_relative_offset() const { 98 | return off_ % blocksize; 99 | } 100 | inline bool chkfs_fileiter::empty() const { 101 | return !eptr_ || eptr_->first == 0; 102 | } 103 | inline auto chkfs_fileiter::blocknum() const -> blocknum_t { 104 | if (!empty()) { 105 | return eptr_->first + (off_ - eoff_) / blocksize; 106 | } else { 107 | return 0; 108 | } 109 | } 110 | inline bcref chkfs_fileiter::load() const { 111 | blocknum_t bn = blocknum(); 112 | return bn ? bufcache::get().load(bn) : bcref(); 113 | } 114 | 115 | inline chkfs_fileiter& chkfs_fileiter::operator+=(ssize_t delta) { 116 | return find(off_ + delta); 117 | } 118 | inline chkfs_fileiter& chkfs_fileiter::operator-=(ssize_t delta) { 119 | return find(off_ - delta); 120 | } 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /k-cpu.cc: -------------------------------------------------------------------------------- 1 | #include "kernel.hh" 2 | #include "k-apic.hh" 3 | 4 | cpustate cpus[MAXCPU]; 5 | int ncpu; 6 | 7 | 8 | // cpustate::init() 9 | // Initialize a `cpustate`. Should be called once per active CPU, 10 | // by the relevant CPU. 11 | 12 | void cpustate::init() { 13 | assert(this_cpu() == this); 14 | assert(current() == nullptr); 15 | // Note that the `cpu::cpu` constructor has already been called. 16 | 17 | { 18 | // check that this CPU is one of the expected CPUs 19 | uintptr_t addr = reinterpret_cast(this); 20 | assert((addr & PAGEOFFMASK) == 0); 21 | assert(this >= cpus && this < cpus + MAXCPU); 22 | assert(rdrsp() > addr && rdrsp() <= addr + CPUSTACK_SIZE); 23 | 24 | // ensure layout `k-exception.S` expects 25 | assert(reinterpret_cast(&self_) == addr); 26 | assert(reinterpret_cast(¤t_) == addr + 8); 27 | assert(reinterpret_cast(&syscall_scratch_) == addr + 16); 28 | } 29 | 30 | cpuindex_ = this - cpus; 31 | runq_lock_.clear(); 32 | idle_task_ = nullptr; 33 | nschedule_ = 0; 34 | spinlock_depth_ = 0; 35 | 36 | // now initialize the CPU hardware 37 | init_cpu_hardware(); 38 | } 39 | 40 | 41 | // cpustate::enable_irq(irqno) 42 | // Enable external interrupt `irqno`, delivering it to 43 | // this CPU. 44 | void cpustate::enable_irq(int irqno) { 45 | assert(irqno >= IRQ_TIMER && irqno <= IRQ_SPURIOUS); 46 | auto& ioapic = ioapicstate::get(); 47 | ioapic.enable_irq(irqno, INT_IRQ + irqno, lapic_id_); 48 | } 49 | 50 | // cpustate::disable_irq(irqno) 51 | // Disable external interrupt `irqno`. 52 | void cpustate::disable_irq(int irqno) { 53 | assert(irqno >= IRQ_TIMER && irqno <= IRQ_SPURIOUS); 54 | auto& ioapic = ioapicstate::get(); 55 | ioapic.disable_irq(irqno); 56 | } 57 | 58 | 59 | // cpustate::schedule() 60 | // Run a process, or the current CPU's idle task if no runnable 61 | // process exists. Prefers to run a process that is not the most 62 | // recent process. 63 | 64 | void cpustate::schedule() { 65 | assert(contains(rdrsp())); // running on CPU stack 66 | assert(is_cli()); // interrupts are currently disabled 67 | assert(spinlock_depth_ == 0); // no spinlocks are held 68 | 69 | // initialize idle task 70 | if (!idle_task_) { 71 | init_idle_task(); 72 | } 73 | 74 | // increment schedule counter 75 | ++nschedule_; 76 | 77 | // find a runnable process (preferring one different from `current_`) 78 | bool first_try = true; 79 | while (!current_ 80 | || current_->pstate_ != proc::ps_runnable 81 | || first_try) { 82 | first_try = false; 83 | 84 | proc* prev = current_; 85 | 86 | runq_lock_.lock_noirq(); 87 | 88 | // reschedule old current if necessary 89 | if (prev && prev->pstate_ == proc::ps_runnable) { 90 | assert(prev->resumable()); 91 | if (!prev->runq_links_.is_linked()) { 92 | runq_.push_back(prev); 93 | } 94 | } 95 | 96 | // run idle task as last resort 97 | current_ = runq_.empty() ? idle_task_ : runq_.pop_front(); 98 | 99 | runq_lock_.unlock_noirq(); 100 | } 101 | 102 | // run `current_` 103 | set_pagetable(current_->pagetable_); 104 | current_->resume(); // does not return 105 | } 106 | 107 | 108 | // cpustate::enqueue(p) 109 | // Claim new task `p` for this CPU and enqueue it on this CPU's run queue. 110 | // Acquires `runq_lock_`. `p` must belong to any CPU, and must be resumable 111 | // (or not runnable). 112 | 113 | void cpustate::enqueue(proc* p) { 114 | assert(p->runq_cpu_ == -1); 115 | assert(!p->runq_links_.is_linked()); 116 | assert(p->resumable() || p->pstate_ != proc::ps_runnable); 117 | spinlock_guard guard(runq_lock_); 118 | p->runq_cpu_ = cpuindex_; 119 | runq_.push_back(p); 120 | } 121 | 122 | 123 | // cpustate::reenqueue(p) 124 | // Enqueue `p` on this CPU's run queue. Acquires `runq_lock_`. `p` must 125 | // belong to this CPU (its `runq_cpu_` must equal `cpuindex_`). Does 126 | // nothing if `p` is currently running or is already scheduled on this 127 | // CPU's run queue; otherwise `p` must be resumable (or not runnable). 128 | 129 | void cpustate::reenqueue(proc* p) { 130 | assert(p->runq_cpu_ == cpuindex_); 131 | spinlock_guard guard(runq_lock_); 132 | if (current_ != p && !p->runq_links_.is_linked()) { 133 | assert(p->resumable() || p->pstate_ != proc::ps_runnable); 134 | runq_.push_back(p); 135 | } 136 | } 137 | 138 | 139 | // cpustate::idle_task 140 | // Every CPU has an *idle task*, which is a kernel task (i.e., a 141 | // `proc` that runs in kernel mode) that just stops the processor 142 | // until an interrupt is received. The idle task runs when a CPU 143 | // has nothing better to do. 144 | 145 | void idle() { 146 | sti(); 147 | while (true) { 148 | asm volatile("hlt"); 149 | } 150 | } 151 | 152 | void cpustate::init_idle_task() { 153 | assert(!idle_task_); 154 | idle_task_ = knew(); 155 | idle_task_->init_kernel(idle); 156 | idle_task_->runq_cpu_ = cpuindex_; 157 | // Note that the idle task is not actually on the run queue. 158 | } 159 | -------------------------------------------------------------------------------- /k-devices.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_DEVICES_HH 2 | #define CHICKADEE_K_DEVICES_HH 3 | #include "kernel.hh" 4 | #include "k-wait.hh" 5 | 6 | // keyboardstate: keyboard buffer and keyboard interrupts 7 | 8 | #define KEY_UP 0xC0 9 | #define KEY_RIGHT 0xC1 10 | #define KEY_DOWN 0xC2 11 | #define KEY_LEFT 0xC3 12 | #define KEY_HOME 0xC4 13 | #define KEY_END 0xC5 14 | #define KEY_PAGEUP 0xC6 15 | #define KEY_PAGEDOWN 0xC7 16 | #define KEY_INSERT 0xC8 17 | #define KEY_DELETE 0xC9 18 | 19 | struct keyboardstate { 20 | spinlock lock_; 21 | char buf_[256]; 22 | unsigned pos_ = 0; // next position to read 23 | unsigned len_ = 0; // number of characters in buffer 24 | unsigned eol_ = 0; // position in buffer of most recent \n 25 | enum { boot, input, fail } state_ = boot; 26 | 27 | static keyboardstate& get() { 28 | return kbd; 29 | } 30 | 31 | void check_invariants() { 32 | assert(pos_ < sizeof(buf_)); 33 | assert(len_ <= sizeof(buf_)); 34 | assert(eol_ <= len_); 35 | } 36 | 37 | // called from proc::exception(); read characters from device 38 | void handle_interrupt(); 39 | 40 | // consume `n` characters from buffer (0 <= n <= len_) 41 | void consume(size_t n); 42 | 43 | private: 44 | static keyboardstate kbd; 45 | keyboardstate() = default; 46 | 47 | void maybe_echo(int ch); 48 | }; 49 | 50 | 51 | // consolestate: lock for console access 52 | 53 | struct consolestate { 54 | spinlock lock_; 55 | 56 | static consolestate& get() { 57 | return console; 58 | } 59 | 60 | void cursor(); 61 | void cursor(bool show); 62 | 63 | private: 64 | static consolestate console; 65 | consolestate() = default; 66 | 67 | spinlock cursor_lock_; 68 | std::atomic cursor_show_ = true; 69 | std::atomic displayed_cpos_ = -1; 70 | }; 71 | 72 | 73 | // memfile: in-memory file system of contiguous files 74 | 75 | struct memfile { 76 | static constexpr unsigned namesize = 64; 77 | char name_[namesize]; // name of file 78 | unsigned char* data_; // file data (nullptr if empty) 79 | size_t len_; // length of file data 80 | size_t capacity_; // # bytes available in `data_` 81 | 82 | inline memfile(); 83 | inline memfile(const char* name, unsigned char* first, 84 | unsigned char* last); 85 | inline memfile(const char* name, const char* data); 86 | 87 | // Test if this `memfile` is unused 88 | inline bool empty() const; 89 | 90 | // Set file length to `len`; return 0 or an error like `E_NOSPC` on failure 91 | int set_length(size_t len); 92 | 93 | // memfile::initfs[] is the initial file system built in to the kernel 94 | static constexpr unsigned initfs_size = 64; 95 | static memfile initfs[initfs_size]; 96 | 97 | // Return the index in `initfs` of the memfile named `name`. 98 | // When the named memfile is not found, the behavior depends on `flag`: 99 | // if `flag == required`, the function asserts failure; if `flag == 100 | // create`, the named memfile is created; and otherwise, an error code 101 | // is returned. 102 | enum lookup_flag { optional = 0, required, create }; 103 | static int initfs_lookup(const char* name, lookup_flag flag = optional); 104 | }; 105 | 106 | inline memfile::memfile() 107 | : name_(""), data_(nullptr), len_(0), capacity_(0) { 108 | } 109 | inline memfile::memfile(const char* name, unsigned char* first, 110 | unsigned char* last) 111 | : data_(first) { 112 | size_t namelen = strlen(name); 113 | ssize_t datalen = reinterpret_cast(last) 114 | - reinterpret_cast(first); 115 | assert(namelen < namesize && datalen >= 0); 116 | strcpy(name_, name); 117 | len_ = capacity_ = datalen; 118 | } 119 | inline memfile::memfile(const char* name, const char* data) 120 | : data_(reinterpret_cast(const_cast(data))), 121 | len_(strlen(data)), capacity_(0) { 122 | size_t namelen = strlen(name); 123 | assert(namelen < namesize); 124 | strcpy(name_, name); 125 | } 126 | inline bool memfile::empty() const { 127 | return name_[0] == 0; 128 | } 129 | 130 | 131 | // memfile::loader: loads a `proc` from a `memfile` 132 | 133 | struct memfile_loader : public proc_loader { 134 | memfile* memfile_; 135 | inline memfile_loader(memfile* mf, x86_64_pagetable* pt) 136 | : proc_loader(pt), memfile_(mf) { 137 | } 138 | inline memfile_loader(int mf_index, x86_64_pagetable* pt) 139 | : proc_loader(pt) { 140 | assert(mf_index >= 0 && unsigned(mf_index) < memfile::initfs_size); 141 | memfile_ = &memfile::initfs[mf_index]; 142 | } 143 | get_page_type get_page(size_t off) override; 144 | void put_page(buffer) override; 145 | }; 146 | 147 | #endif 148 | -------------------------------------------------------------------------------- /k-list.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_LIST_HH 2 | #define CHICKADEE_LIST_HH 3 | #include "types.h" 4 | 5 | struct list_links { 6 | list_links* next_ = nullptr; 7 | list_links* prev_ = nullptr; 8 | 9 | 10 | // Initialize an empty `list_links` 11 | list_links() = default; 12 | NO_COPY_OR_ASSIGN(list_links); 13 | 14 | // Reset this `list_links` to empty 15 | inline void reset(); 16 | 17 | // Return true iff this `list_links` is linked in to some list 18 | inline bool is_linked() const; 19 | // Synonym for `!is_linked()` 20 | inline bool empty() const; 21 | // Remove this `list_links` from its containing list 22 | inline void erase(); 23 | // Insert this `list_links` in a list immediately before `position` 24 | inline void insert_before(list_links* position); 25 | }; 26 | 27 | 28 | template 29 | struct list { 30 | list_links head_; 31 | 32 | 33 | // Construct an empty list 34 | inline list(); 35 | // Reset this list to empty, ignoring its current contents 36 | inline void reset(); 37 | 38 | // Return true iff list is empty 39 | inline constexpr bool empty() const; 40 | 41 | // Return head of list (nullptr if empty) 42 | inline T* front() const; 43 | // Return tail of list (nullptr if empty) 44 | inline T* back() const; 45 | // Return list item after `x` (nullptr if last) 46 | inline constexpr T* next(T* x) const; 47 | // Return list item previous to `x` (nullptr if first) 48 | inline constexpr T* prev(T* x) const; 49 | 50 | // Push `x` onto head of list 51 | inline void push_front(T* x); 52 | // Remove and return head of list (return nullptr if empty) 53 | inline T* pop_front(); 54 | 55 | // Push `x` onto tail of list 56 | inline void push_back(T* x); 57 | // Remove and return tail of list (return nullptr if empty) 58 | inline T* pop_back(); 59 | 60 | // Remove `x` from list 61 | inline void erase(T* x); 62 | // Insert `x` immediately before `position`. 63 | // If `position == nullptr`, insert at tail 64 | inline void insert(T* position, T* x); 65 | 66 | // Swap the contents of this list with those of `other`. 67 | inline void swap(list& other); 68 | 69 | 70 | private: 71 | static T* from_links(list_links* ll) { 72 | return mem_container(ll, member); 73 | } 74 | }; 75 | 76 | 77 | inline void list_links::reset() { 78 | next_ = prev_ = nullptr; 79 | } 80 | 81 | inline bool list_links::is_linked() const { 82 | return next_ != nullptr; 83 | } 84 | 85 | inline bool list_links::empty() const { 86 | return !is_linked(); 87 | } 88 | 89 | inline void list_links::erase() { 90 | assert(next_ && prev_); 91 | assert(next_->prev_ == this && prev_->next_ == this); 92 | prev_->next_ = next_; 93 | next_->prev_ = prev_; 94 | reset(); 95 | } 96 | 97 | inline void list_links::insert_before(list_links* position) { 98 | assert(position->next_ && position->prev_); 99 | assert(!next_ && !prev_); 100 | prev_ = position->prev_; 101 | next_ = position; 102 | position->prev_->next_ = this; 103 | position->prev_ = this; 104 | } 105 | 106 | 107 | template 108 | inline list::list() { 109 | reset(); 110 | } 111 | 112 | template 113 | inline void list::reset() { 114 | head_.next_ = head_.prev_ = &head_; 115 | } 116 | 117 | template 118 | inline constexpr bool list::empty() const { 119 | return head_.next_ == &head_; 120 | } 121 | 122 | template 123 | inline T* list::front() const { 124 | return empty() ? nullptr : from_links(head_.next_); 125 | } 126 | 127 | template 128 | inline T* list::back() const { 129 | return empty() ? nullptr : from_links(head_.prev_); 130 | } 131 | 132 | template 133 | inline constexpr T* list::next(T* x) const { 134 | return (x->*member).next_ == &head_ 135 | ? nullptr : from_links((x->*member).next_); 136 | } 137 | 138 | template 139 | inline constexpr T* list::prev(T* x) const { 140 | return (x->*member).prev_ == &head_ 141 | ? nullptr : from_links((x->*member).prev_); 142 | } 143 | 144 | template 145 | inline void list::push_front(T* x) { 146 | (x->*member).insert_before(head_.next_); 147 | } 148 | 149 | template 150 | inline T* list::pop_front() { 151 | T* x = front(); 152 | if (x) { 153 | erase(x); 154 | } 155 | return x; 156 | } 157 | 158 | template 159 | inline void list::push_back(T* x) { 160 | (x->*member).insert_before(&head_); 161 | } 162 | 163 | template 164 | inline T* list::pop_back() { 165 | T* x = back(); 166 | if (x) { 167 | erase(x); 168 | } 169 | return x; 170 | } 171 | 172 | template 173 | inline void list::erase(T* x) { 174 | (x->*member).erase(); 175 | } 176 | 177 | template 178 | inline void list::insert(T* position, T* x) { 179 | (x->*member).insert_before(position ? &head_ : &(position->*member)); 180 | } 181 | 182 | template 183 | inline void list::swap(list& other) { 184 | list_links x, *p1, *p2; 185 | p1 = head_.next_; 186 | p2 = head_.prev_; 187 | p1->prev_ = p2->next_ = &other.head_; 188 | p1 = other.head_.next_; 189 | p2 = other.head_.prev_; 190 | p1->prev_ = p2->next_ = &head_; 191 | memcpy(&x, &head_, sizeof(x)); 192 | memcpy(&head_, &other.head_, sizeof(x)); 193 | memcpy(&other.head_, &x, sizeof(x)); 194 | } 195 | 196 | #endif 197 | -------------------------------------------------------------------------------- /k-lock.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_LOCK_HH 2 | #define CHICKADEE_K_LOCK_HH 3 | #include 4 | #include 5 | #include "x86-64.h" 6 | inline void adjust_this_cpu_spinlock_depth(int delta); 7 | #define LOCK_DEBUG_PAUSE 1 8 | 9 | 10 | struct irqstate { 11 | irqstate() 12 | : flags_(0) { 13 | } 14 | irqstate(irqstate&& x) 15 | : flags_(x.flags_) { 16 | x.flags_ = 0; 17 | } 18 | irqstate(const irqstate&) = delete; 19 | irqstate& operator=(const irqstate&) = delete; 20 | ~irqstate() { 21 | assert(!flags_ && "forgot to unlock a spinlock"); 22 | } 23 | 24 | static irqstate get() { 25 | irqstate s; 26 | s.flags_ = rdeflags(); 27 | return s; 28 | } 29 | void restore() { 30 | if (flags_ & EFLAGS_IF) { 31 | sti(); 32 | } 33 | flags_ = 0; 34 | } 35 | void clear() { 36 | flags_ = 0; 37 | } 38 | 39 | void operator=(irqstate&& s) { 40 | assert(flags_ == 0); 41 | flags_ = s.flags_; 42 | s.flags_ = 0; 43 | } 44 | 45 | uint64_t flags_; 46 | }; 47 | 48 | 49 | struct spinlock { 50 | spinlock() { 51 | f_.clear(); 52 | } 53 | 54 | irqstate lock() { 55 | irqstate irqs = irqstate::get(); 56 | cli(); 57 | lock_noirq(); 58 | adjust_this_cpu_spinlock_depth(1); 59 | return irqs; 60 | } 61 | bool trylock(irqstate& irqs) { 62 | irqs = irqstate::get(); 63 | cli(); 64 | bool r = trylock_noirq(); 65 | if (r) { 66 | adjust_this_cpu_spinlock_depth(1); 67 | } else { 68 | irqs.restore(); 69 | } 70 | return r; 71 | } 72 | void unlock(irqstate& irqs) { 73 | adjust_this_cpu_spinlock_depth(-1); 74 | unlock_noirq(); 75 | irqs.restore(); 76 | } 77 | 78 | inline void debug_pause() { 79 | #if LOCK_DEBUG_PAUSE 80 | pause(); 81 | #endif 82 | } 83 | 84 | void lock_noirq() { 85 | debug_pause(); 86 | while (f_.test_and_set()) { 87 | pause(); 88 | } 89 | } 90 | bool trylock_noirq() { 91 | debug_pause(); 92 | return !f_.test_and_set(); 93 | } 94 | void unlock_noirq() { 95 | f_.clear(); 96 | debug_pause(); 97 | } 98 | 99 | void clear() { 100 | f_.clear(); 101 | } 102 | 103 | bool is_locked() const { 104 | return f_.test(std::memory_order_relaxed); 105 | } 106 | 107 | private: 108 | std::atomic_flag f_; 109 | }; 110 | 111 | 112 | struct spinlock_guard { 113 | explicit spinlock_guard(spinlock& lock) 114 | : lock_(lock), irqs_(lock_.lock()), locked_(true) { 115 | } 116 | ~spinlock_guard() { 117 | if (locked_) { 118 | lock_.unlock(irqs_); 119 | } 120 | } 121 | NO_COPY_OR_ASSIGN(spinlock_guard); 122 | 123 | void unlock() { 124 | assert(locked_); 125 | lock_.unlock(irqs_); 126 | locked_ = false; 127 | } 128 | void lock() { 129 | assert(!locked_); 130 | irqs_ = lock_.lock(); 131 | locked_ = true; 132 | } 133 | 134 | constexpr bool is_locked() { 135 | return locked_; 136 | } 137 | 138 | 139 | spinlock& lock_; 140 | irqstate irqs_; 141 | bool locked_; 142 | }; 143 | 144 | 145 | template 146 | struct ref_ptr { 147 | using element_type = T; 148 | using pointer = T*; 149 | 150 | T* e_; 151 | 152 | 153 | ref_ptr() 154 | : e_(nullptr) { 155 | } 156 | 157 | ref_ptr(T* e) 158 | : e_(e) { 159 | } 160 | 161 | ref_ptr(ref_ptr&& x) 162 | : e_(x.e_) { 163 | x.e_ = nullptr; 164 | } 165 | 166 | ref_ptr(const ref_ptr&) = delete; 167 | 168 | ~ref_ptr() { 169 | reset(); 170 | } 171 | 172 | 173 | T* get() const noexcept { 174 | return e_; 175 | } 176 | 177 | explicit constexpr operator bool() { 178 | return e_ != nullptr; 179 | } 180 | 181 | 182 | T& operator*() const noexcept { 183 | assert(e_); 184 | return *e_; 185 | } 186 | 187 | T* operator->() const noexcept { 188 | assert(e_); 189 | return e_; 190 | } 191 | 192 | 193 | T* release() { 194 | T* e = e_; 195 | e_ = nullptr; 196 | return e; 197 | } 198 | 199 | ref_ptr& operator=(ref_ptr&& x) { 200 | if (x.e_ != e_) { 201 | reset(x.e_); 202 | x.e_ = nullptr; 203 | } 204 | return *this; 205 | } 206 | 207 | void reset(T* x = nullptr) noexcept { 208 | if (e_) { 209 | e_->decrement_reference_count(); 210 | } 211 | e_ = x; 212 | } 213 | 214 | 215 | ref_ptr& operator=(const ref_ptr&) = delete; 216 | }; 217 | 218 | #endif 219 | -------------------------------------------------------------------------------- /k-memrange.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_MEMRANGE_HH 2 | #define CHICKADEE_K_MEMRANGE_HH 3 | #include "types.h" 4 | template class memrangeset; 5 | 6 | // `memrangeset` stores type information for a range of memory addresses. 7 | // Chickadee uses it to remember which physical addresses are reserved or 8 | // occupied by the kernel. 9 | 10 | class memrange { 11 | public: 12 | inline uint8_t type() const; // type of range 13 | inline uintptr_t first() const; // first address in range 14 | inline uintptr_t last() const; // one past last address in range 15 | inline size_t size() const; // size of range in bytes 16 | 17 | private: 18 | uintptr_t addr_; 19 | uint8_t type_; 20 | 21 | template friend class memrangeset; 22 | }; 23 | 24 | 25 | template 26 | class memrangeset { 27 | public: 28 | // initialize range mapping [0, `limit`) to type 0 29 | inline memrangeset(uintptr_t limit); 30 | 31 | // return `limit` 32 | inline uintptr_t limit() const; 33 | // return number of ranges 34 | inline unsigned size() const; 35 | // return pointer to first range 36 | inline const memrange* begin() const; 37 | // return pointer to range bound 38 | inline const memrange* end() const; 39 | 40 | // return range containing `addr`, or `end()` 41 | const memrange* find(uintptr_t addr) const; 42 | // return type for `addr`. Requires `0 <= addr < limit()` 43 | uint8_t type(uintptr_t addr) const; 44 | 45 | // set type of address range [`first`, `last`) to `type`. 46 | // Requires `0 <= first <= last <= limit()`. Returns true iff 47 | // assignment succeeded. 48 | bool set(uintptr_t first, uintptr_t last, uint8_t type); 49 | 50 | // print contents to log 51 | void log_print(const char* prefix = "") const; 52 | // validate correctness of data structure 53 | void validate() const; 54 | 55 | private: 56 | unsigned n_; 57 | memrange r_[maxsize + 1]; 58 | 59 | void split(unsigned i, uintptr_t addr); 60 | }; 61 | 62 | 63 | inline uint8_t memrange::type() const { 64 | return type_; 65 | } 66 | inline uintptr_t memrange::first() const { 67 | return addr_; 68 | } 69 | inline uintptr_t memrange::last() const { 70 | return this[1].addr_; 71 | } 72 | inline size_t memrange::size() const { 73 | return this[1].addr_ - addr_; 74 | } 75 | 76 | template 77 | inline memrangeset::memrangeset(uintptr_t limit) 78 | : n_(1) { 79 | r_[0].addr_ = 0; 80 | r_[0].type_ = 0; 81 | r_[1].addr_ = limit; 82 | } 83 | template 84 | inline uintptr_t memrangeset::limit() const { 85 | return r_[n_].addr_; 86 | } 87 | template 88 | inline unsigned memrangeset::size() const { 89 | return n_; 90 | } 91 | template 92 | inline const memrange* memrangeset::begin() const { 93 | return &r_[0]; 94 | } 95 | template 96 | inline const memrange* memrangeset::end() const { 97 | return &r_[n_]; 98 | } 99 | template 100 | inline const memrange* memrangeset::find(uintptr_t addr) const { 101 | unsigned i = 0; 102 | while (i < n_ && addr >= r_[i + 1].addr_) { 103 | ++i; 104 | } 105 | return &r_[i]; 106 | } 107 | template 108 | inline uint8_t memrangeset::type(uintptr_t addr) const { 109 | auto r = find(addr); 110 | assert(r != end()); 111 | return r->type(); 112 | } 113 | template 114 | void memrangeset::split(unsigned i, uintptr_t addr) { 115 | assert(i < n_ && n_ + 1 <= maxsize); 116 | assert(r_[i].addr_ < addr && r_[i + 1].addr_ > addr); 117 | memmove(&r_[i + 2], &r_[i + 1], (n_ - i) * sizeof(memrange)); 118 | r_[i + 1].addr_ = addr; 119 | r_[i + 1].type_ = r_[i].type_; 120 | ++n_; 121 | } 122 | template 123 | bool memrangeset::set(uintptr_t first, uintptr_t last, uint8_t type) { 124 | assert(first <= last && last <= r_[n_].addr_); 125 | if (first == last) { 126 | return true; 127 | } 128 | 129 | // find lower bound of insertion position for range 130 | unsigned i = 0; 131 | while (first >= r_[i + 1].addr_) { 132 | ++i; 133 | } 134 | assert(i < n_ && first >= r_[i].addr_ && first < r_[i + 1].addr_); 135 | // try consolidating range 136 | if (first == r_[i].addr_ && i > 0 && r_[i - 1].type_ == type) { 137 | --i; 138 | } 139 | if (r_[i].type_ == type) { 140 | first = r_[i].addr_; 141 | } 142 | 143 | // find upper bound of insertion position for range 144 | unsigned j = i; 145 | while (j < n_ && last >= r_[j + 1].addr_) { 146 | ++j; 147 | } 148 | assert(j <= n_ && last >= r_[j].addr_ 149 | && (j == n_ || last < r_[j + 1].addr_)); 150 | if (j < n_ && r_[j].type_ == type) { 151 | ++j; 152 | last = r_[j].addr_; 153 | } else if (j > i && first == r_[i].addr_) { 154 | r_[j].addr_ = last; 155 | } 156 | 157 | // split range at left and right 158 | if (n_ + (first != r_[i].addr_) + (last != r_[j].addr_) > maxsize) { 159 | return false; 160 | } 161 | if (first != r_[i].addr_) { 162 | split(i, first); 163 | ++i; 164 | ++j; 165 | } 166 | if (last != r_[j].addr_) { 167 | split(j, last); 168 | ++j; 169 | } 170 | 171 | // assign new range 172 | assert(r_[i].addr_ == first && r_[j].addr_ == last); 173 | r_[i].type_ = type; 174 | if (i + 1 < j) { 175 | memmove(&r_[i + 1], &r_[j], (n_ + 1 - j) * sizeof(memrange)); 176 | n_ -= j - (i + 1); 177 | } 178 | return true; 179 | } 180 | template 181 | void memrangeset::validate() const { 182 | assert(n_ > 0 && n_ <= maxsize); 183 | assert(r_[0].addr_ == 0); 184 | for (unsigned i = 0; i < n_; ++i) { 185 | assert(r_[i].addr_ < r_[i + 1].addr_); 186 | assert(i == 0 || r_[i].type_ != r_[i - 1].type_); 187 | } 188 | } 189 | template 190 | void memrangeset::log_print(const char* prefix) const { 191 | void log_printf(const char* format, ...) __attribute__((noinline)); 192 | for (unsigned i = 0; i < n_; ++i) { 193 | log_printf("%s[%u]: [%p,%p)=%d\n", 194 | prefix, i, r_[i].first(), r_[i].last(), r_[i].type()); 195 | } 196 | } 197 | 198 | #endif 199 | -------------------------------------------------------------------------------- /k-mpspec.cc: -------------------------------------------------------------------------------- 1 | #include "kernel.hh" 2 | #include "k-pci.hh" 3 | 4 | namespace { 5 | 6 | static uint8_t sum_bytes(const uint8_t* x, size_t len) { 7 | uint8_t sum = 0; 8 | for (; len > 0; ++x, --len) { 9 | sum += *x; 10 | } 11 | return sum; 12 | } 13 | 14 | struct mpfloat { 15 | char signature[4]; 16 | uint32_t mpconfig_pa; 17 | uint8_t len; 18 | uint8_t spec_rev; 19 | uint8_t checksum; 20 | uint8_t feature[5]; 21 | 22 | bool check() const { 23 | return memcmp(signature, "_MP_", 4) == 0 24 | && len == 1 25 | && (spec_rev == 1 || spec_rev == 4) 26 | && sum_bytes(reinterpret_cast(this), 27 | sizeof(*this)) == 0; 28 | } 29 | }; 30 | 31 | struct mpconfig { 32 | char signature[4]; 33 | uint16_t len; 34 | uint8_t spec_rev; 35 | uint8_t checksum; 36 | char oem_id[8]; 37 | char product_id[12]; 38 | uint32_t oemt_pa; 39 | uint16_t oemt_length; 40 | uint16_t entry_count; 41 | uint32_t lapic_pa; 42 | uint16_t ext_len; 43 | uint8_t ext_checksum; 44 | uint8_t reserved; 45 | 46 | bool check() const { 47 | return memcmp(signature, "PCMP", 4) == 0 48 | && len >= sizeof(*this) 49 | && (spec_rev == 1 || spec_rev == 4) 50 | && sum_bytes(reinterpret_cast(this), len) == 0; 51 | } 52 | const uint8_t* first() const { 53 | return reinterpret_cast(this) + sizeof(*this); 54 | } 55 | const uint8_t* last() const { 56 | return reinterpret_cast(this) + len; 57 | } 58 | inline const uint8_t* next(const uint8_t* e) const; 59 | 60 | static const mpconfig* find(); 61 | }; 62 | 63 | struct proc_config { 64 | static constexpr int id = 0; 65 | uint8_t entry_type; 66 | uint8_t lapic_id; 67 | uint8_t lapic_version; 68 | uint8_t cpu_flags; 69 | uint32_t cpu_signature; 70 | uint32_t feature_flags; 71 | uint32_t reserved[2]; 72 | }; 73 | 74 | struct bus_config { 75 | static constexpr int id = 1; 76 | uint8_t entry_type; 77 | uint8_t bus_id; 78 | uint8_t bus_type[6]; 79 | }; 80 | 81 | struct ioapic_config { 82 | static constexpr int id = 2; 83 | uint8_t entry_type; 84 | uint8_t ioapic_id; 85 | uint8_t ioapic_version; 86 | uint8_t ioapic_flags; 87 | uint32_t ioapic_pa; 88 | 89 | bool unusable() const { 90 | return ioapic_flags & 1; 91 | } 92 | }; 93 | 94 | struct int_config { 95 | static constexpr int ioint_id = 3; 96 | static constexpr int lint_id = 4; 97 | uint8_t entry_type; 98 | uint8_t int_type; 99 | uint16_t int_flags; 100 | uint8_t bus_id; 101 | uint8_t bus_irq; 102 | uint8_t apic_id; 103 | uint8_t apic_intno; 104 | }; 105 | 106 | inline const uint8_t* mpconfig::next(const uint8_t* e) const { 107 | switch (*e) { 108 | case proc_config::id: 109 | return e + sizeof(proc_config); 110 | case bus_config::id: 111 | return e + sizeof(bus_config); 112 | case ioapic_config::id: 113 | return e + sizeof(ioapic_config); 114 | case int_config::ioint_id: 115 | case int_config::lint_id: 116 | return e + sizeof(int_config); 117 | default: 118 | return last(); 119 | } 120 | } 121 | 122 | static const mpfloat* find_float(const uint8_t* x, size_t len) { 123 | const uint8_t* end = x + len - sizeof(mpfloat); 124 | for (; x <= end; x += sizeof(mpfloat)) { 125 | auto mpf = reinterpret_cast(x); 126 | if (mpf->check()) { 127 | return mpf; 128 | } 129 | } 130 | return nullptr; 131 | } 132 | 133 | const mpconfig* mpconfig::find() { 134 | static const mpconfig* config; 135 | static bool initialized = false; 136 | if (!initialized) { 137 | const mpfloat* mpf; 138 | disable_asan(); 139 | if (uint16_t ebda_base = read_unaligned_pa 140 | (X86_BDA_EBDA_BASE_ADDRESS_PA)) { 141 | mpf = find_float(pa2kptr(ebda_base << 4), 1024); 142 | } else { 143 | // `basemem` is reported in KiB - 1KiB 144 | uint16_t basemem = read_unaligned_pa 145 | (X86_BDA_BASE_MEMORY_SIZE_PA); 146 | mpf = find_float(pa2kptr(basemem << 10), 1024); 147 | } 148 | if (!mpf) { 149 | mpf = find_float(pa2kptr(0xF0000), 0x10000); 150 | } 151 | if (mpf && mpf->mpconfig_pa) { 152 | const mpconfig* c = pa2kptr(mpf->mpconfig_pa); 153 | if (c->check()) { 154 | config = c; 155 | } 156 | } 157 | enable_asan(); 158 | initialized = true; 159 | } 160 | return config; 161 | } 162 | 163 | } 164 | 165 | 166 | unsigned machine_ncpu() { 167 | auto mpc = mpconfig::find(); 168 | if (!mpc) { 169 | return 0; 170 | } 171 | 172 | size_t n = 0; 173 | for (auto e = mpc->first(); e < mpc->last(); e = mpc->next(e)) { 174 | if (*e == proc_config::id) { 175 | ++n; 176 | } 177 | } 178 | return n; 179 | } 180 | 181 | unsigned machine_pci_irq(int pci_addr, int intr_pin) { 182 | auto mpc = mpconfig::find(); 183 | if (!mpc) { 184 | return 0; 185 | } 186 | 187 | int bus_id = -1; 188 | int bus_irq = intr_pin | (pcistate::addr_slot(pci_addr) << 2); 189 | for (auto e = mpc->first(); e < mpc->last(); e = mpc->next(e)) { 190 | if (*e == bus_config::id) { 191 | auto x = reinterpret_cast(e); 192 | if (bus_id == -1 && memcmp(x->bus_type, "PCI ", 6) == 0) { 193 | bus_id = x->bus_id; 194 | } 195 | } else if (*e == int_config::ioint_id) { 196 | auto x = reinterpret_cast(e); 197 | if (x->bus_id == bus_id 198 | && x->bus_irq == bus_irq 199 | && x->apic_id == 0) { 200 | return x->apic_intno; 201 | } 202 | } 203 | } 204 | 205 | return 0; 206 | } 207 | -------------------------------------------------------------------------------- /k-pci.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_PCI_HH 2 | #define CHICKADEE_K_PCI_HH 3 | 4 | struct pcistate { 5 | // PCI I/O registers 6 | enum { 7 | reg_host_bridge_config_addr = 0xCF8, 8 | reg_host_bridge_config_data = 0xCFC 9 | }; 10 | 11 | // PCI register config offsets 12 | enum { 13 | config_vendor = 0, 14 | config_command = 4, 15 | config_rpsc = 8, 16 | config_subclass = 10, 17 | config_lthb = 12, 18 | config_bar0 = 16, config_bar1 = 20, config_bar2 = 24, 19 | config_bar3 = 28, config_bar4 = 32, config_bar5 = 36 20 | }; 21 | 22 | // support at most 8 PCI buses 23 | static constexpr int addr_end = 0x80000; 24 | 25 | NO_COPY_OR_ASSIGN(pcistate) 26 | 27 | static inline pcistate& get(); 28 | 29 | static inline int make_addr(int bus, int slot, int func); 30 | static inline int addr_bus(int addr); 31 | static inline int addr_slot(int addr); 32 | static inline int addr_func(int addr); 33 | 34 | int next(int addr) const; 35 | template int find(F predicate, int addr = 0) const; 36 | 37 | inline uint32_t readl(int addr) const; 38 | inline uint16_t readw(int addr) const; 39 | inline uint8_t readb(int addr) const; 40 | 41 | inline void writel(int addr, uint32_t x); 42 | inline void writew(int addr, uint16_t x); 43 | inline void writeb(int addr, uint8_t x); 44 | 45 | void enable(int addr); 46 | 47 | private: 48 | static pcistate state; 49 | 50 | pcistate() = default; 51 | }; 52 | 53 | 54 | inline pcistate& pcistate::get() { 55 | return state; 56 | } 57 | 58 | inline int pcistate::make_addr(int bus, int slot, int func) { 59 | assert(bus >= 0 && bus < 256); 60 | assert(slot >= 0 && slot < 32); 61 | assert(func >= 0 && func < 8); 62 | return (bus << 16) | (slot << 11) | (func << 8); 63 | } 64 | 65 | inline int pcistate::addr_bus(int addr) { 66 | assert(addr >= 0 && addr < 0x1000000); 67 | return addr >> 16; 68 | } 69 | inline int pcistate::addr_slot(int addr) { 70 | assert(addr >= 0 && addr < 0x1000000); 71 | return (addr >> 11) & 0x1F; 72 | } 73 | inline int pcistate::addr_func(int addr) { 74 | assert(addr >= 0 && addr < 0x1000000); 75 | return (addr >> 8) & 0x7; 76 | } 77 | 78 | template 79 | int pcistate::find(F predicate, int addr) const { 80 | while (addr >= 0 && !predicate(addr)) { 81 | addr = next(addr); 82 | } 83 | return addr; 84 | } 85 | 86 | inline uint32_t pcistate::readl(int addr) const { 87 | assert(addr >= 0 && !(addr & 3)); 88 | outl(reg_host_bridge_config_addr, 0x80000000U | addr); 89 | return inl(reg_host_bridge_config_data); 90 | } 91 | inline uint16_t pcistate::readw(int addr) const { 92 | assert(addr >= 0 && !(addr & 1)); 93 | outl(reg_host_bridge_config_addr, 0x80000000U | addr); 94 | return inw(reg_host_bridge_config_data + (addr & 3)); 95 | } 96 | inline uint8_t pcistate::readb(int addr) const { 97 | assert(addr >= 0); 98 | outl(reg_host_bridge_config_addr, 0x80000000U | addr); 99 | return inb(reg_host_bridge_config_data + (addr & 3)); 100 | } 101 | 102 | inline void pcistate::writel(int addr, uint32_t data) { 103 | assert(addr >= 0 && !(addr & 3)); 104 | outl(reg_host_bridge_config_addr, 0x80000000U | addr); 105 | outl(reg_host_bridge_config_data, data); 106 | } 107 | inline void pcistate::writew(int addr, uint16_t data) { 108 | assert(addr >= 0 && !(addr & 1)); 109 | outl(reg_host_bridge_config_addr, 0x80000000U | addr); 110 | outw(reg_host_bridge_config_data, data + (addr & 3)); 111 | } 112 | inline void pcistate::writeb(int addr, uint8_t data) { 113 | assert(addr >= 0); 114 | outl(reg_host_bridge_config_addr, 0x80000000U | addr); 115 | outb(reg_host_bridge_config_data, data + (addr & 3)); 116 | } 117 | 118 | #endif 119 | -------------------------------------------------------------------------------- /k-vmiter.cc: -------------------------------------------------------------------------------- 1 | #include "k-vmiter.hh" 2 | 3 | const x86_64_pageentry_t vmiter::zero_pe = 0; 4 | 5 | void vmiter::down() { 6 | while (lbits_ > PAGEOFFBITS && (*pep_ & (PTE_P | PTE_PS)) == PTE_P) { 7 | perm_ &= *pep_ | ~(PTE_P | PTE_W | PTE_U); 8 | lbits_ -= PAGEINDEXBITS; 9 | uintptr_t pa = *pep_ & PTE_PAMASK; 10 | x86_64_pagetable* pt = pa2kptr(pa); 11 | pep_ = &pt->entry[(va_ >> lbits_) & 0x1FF]; 12 | } 13 | if ((*pep_ & PTE_PAMASK) >= 0x100000000UL 14 | && lbits_ < PAGEOFFBITS + 2 * PAGEINDEXBITS) { 15 | panic("Page table %p may contain uninitialized memory!\n" 16 | "(Page table contents: %p)\n", pt_, *pep_); 17 | } 18 | } 19 | 20 | void vmiter::find_impl(uintptr_t va, bool stepping) { 21 | if (stepping && va == 0) { 22 | lbits_ = done_lbits; 23 | perm_ = 0; 24 | pep_ = const_cast(&zero_pe); 25 | } else if (lbits_ < initial_lbits 26 | && ((va_ ^ va) & (~uintptr_t(0) << (lbits_ + PAGEINDEXBITS))) == 0) { 27 | // stepping to next entry in current page table level 28 | int curidx = (reinterpret_cast(pep_) % PAGESIZE) >> 3; 29 | pep_ += ((va >> lbits_) & 0x1FF) - curidx; 30 | } else if (!va_is_canonical(va)) { 31 | lbits_ = noncanonical_lbits; 32 | perm_ = 0; 33 | pep_ = const_cast(&zero_pe); 34 | } else { 35 | lbits_ = initial_lbits; 36 | perm_ = initial_perm; 37 | pep_ = &pt_->entry[(va >> lbits_) & 0x1FF]; 38 | } 39 | va_ = va; 40 | down(); 41 | } 42 | 43 | void vmiter::next() { 44 | int lbits = PAGEOFFBITS; 45 | if (lbits_ > PAGEOFFBITS && !perm()) { 46 | lbits = lbits_; 47 | } 48 | find_impl((va_ | lbits_mask(lbits)) + 1, true); 49 | } 50 | 51 | int vmiter::try_map(uintptr_t pa, int perm) { 52 | if (pa == (uintptr_t) -1 && perm == 0) { 53 | pa = 0; 54 | } 55 | // virtual address is page-aligned 56 | assert((va_ % PAGESIZE) == 0, "vmiter::try_map va not aligned"); 57 | if (perm & PTE_P) { 58 | // if mapping present, physical address is page-aligned 59 | assert(pa != (uintptr_t) -1, "vmiter::try_map mapping nonexistent pa"); 60 | assert((pa & PTE_PAMASK) == pa, "vmiter::try_map pa not aligned"); 61 | } else { 62 | assert((pa & PTE_P) == 0, "vmiter::try_map invalid pa"); 63 | } 64 | // new permissions (`perm`) cannot be less restrictive than permissions 65 | // imposed by higher-level page tables (`perm_`) 66 | assert(!(perm & ~perm_ & (PTE_P | PTE_W | PTE_U))); 67 | 68 | while (lbits_ > PAGEOFFBITS && perm) { 69 | assert(!(*pep_ & PTE_P)); 70 | x86_64_pagetable* pt = knew(); 71 | if (!pt) { 72 | return -1; 73 | } 74 | memset(pt, 0, PAGESIZE); 75 | std::atomic_thread_fence(std::memory_order_release); 76 | *pep_ = ka2pa(pt) | PTE_P | PTE_W | PTE_U; 77 | down(); 78 | } 79 | 80 | if (lbits_ == PAGEOFFBITS) { 81 | std::atomic_thread_fence(std::memory_order_release); 82 | *pep_ = pa | perm; 83 | } 84 | return 0; 85 | } 86 | 87 | 88 | uint64_t vmiter::range_perm(size_t sz) const { 89 | uint64_t p = sz > 0 ? perm() : uint64_t(-1); 90 | if (!p || sz <= range_size()) { 91 | return p; 92 | } 93 | vmiter copy(*this); 94 | copy.next_range(); 95 | return copy.range_perm_impl(p, sz - range_size()); 96 | } 97 | 98 | uint64_t vmiter::range_perm_impl(uint64_t p, size_t sz) { 99 | while (p != 0 && sz > 0) { 100 | p &= va() ? perm() : 0; 101 | sz -= min(sz, range_size()); 102 | next_range(); 103 | } 104 | return p; 105 | } 106 | 107 | 108 | ptiter::ptiter(x86_64_pagetable* pt) 109 | : pt_(pt), pep_(&pt_->entry[0]), lbits_(vmiter::initial_lbits), va_(0) { 110 | down(false); 111 | } 112 | 113 | void ptiter::down(bool skip) { 114 | int stop_lbits = PAGEOFFBITS + PAGEINDEXBITS; 115 | while (lbits_ < vmiter::done_lbits) { 116 | if (!skip && (*pep_ & (PTE_P | PTE_PS)) == PTE_P) { 117 | if (lbits_ == stop_lbits) { 118 | return; 119 | } 120 | lbits_ -= PAGEINDEXBITS; 121 | uintptr_t pa = *pep_ & PTE_PAMASK; 122 | x86_64_pagetable* pt = pa2kptr(pa); 123 | pep_ = &pt->entry[(va_ >> lbits_) & 0x1FF]; 124 | continue; 125 | } 126 | uintptr_t va = (va_ | vmiter::lbits_mask(lbits_)) + 1; 127 | if ((va ^ va_) & ~vmiter::lbits_mask(lbits_ + PAGEINDEXBITS)) { 128 | // up one level 129 | if (va == 0 && lbits_ == vmiter::initial_lbits) { 130 | lbits_ = vmiter::done_lbits; 131 | return; 132 | } 133 | stop_lbits = lbits_ + PAGEINDEXBITS; 134 | lbits_ = vmiter::initial_lbits; 135 | pep_ = &pt_->entry[(va_ >> lbits_) & 0x1FF]; 136 | } else { 137 | ++pep_; 138 | va_ = va_is_canonical(va) ? va : VA_HIGHMIN; 139 | } 140 | skip = false; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /k-wait.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_WAIT_HH 2 | #define CHICKADEE_K_WAIT_HH 3 | #include "k-list.hh" 4 | struct proc; 5 | struct wait_queue; 6 | struct spinlock; 7 | struct irqstate; 8 | struct spinlock_guard; 9 | 10 | // k-wait.hh 11 | // Chickadee wait queues. 12 | // 13 | // Unlike many header files, `k-wait.hh` is #included twice. When it is 14 | // #included by `kernel.hh`, it declares `struct waiter` and `struct 15 | // wait_queue`, which `struct proc` requires, but does not define inline 16 | // functions like `waiter::wait_until` (it cannot define these yet because 17 | // they depend on methods in `struct proc`). When #included a second time 18 | // (for instance, by `kernel.cc`), it defines those inline functions. 19 | 20 | 21 | struct waiter { 22 | proc* p_; 23 | wait_queue* wq_; 24 | list_links links_; 25 | 26 | explicit inline waiter(); 27 | inline ~waiter(); 28 | NO_COPY_OR_ASSIGN(waiter); 29 | inline void prepare(wait_queue& wq); 30 | inline void maybe_block(); 31 | inline void clear(); 32 | inline void notify(); 33 | 34 | template 35 | inline void wait_until(wait_queue& wq, F predicate); 36 | template 37 | inline void wait_until(wait_queue& wq, F predicate, 38 | spinlock& lock, irqstate& irqs); 39 | template 40 | inline void wait_until(wait_queue& wq, F predicate, 41 | spinlock_guard& guard); 42 | 43 | inline void wait_once(wait_queue& wq); 44 | inline void wait_once(wait_queue& wq, 45 | spinlock& lock, irqstate& irqs); 46 | inline void wait_once(wait_queue& wq, 47 | spinlock_guard& guard); 48 | }; 49 | 50 | 51 | struct wait_queue { 52 | list q_; 53 | mutable spinlock lock_; 54 | 55 | // you might want to provide some convenience methods here 56 | inline void notify_all(); 57 | }; 58 | 59 | 60 | // End of structure definitions (first inclusion) 61 | #endif /* CHICKADEE_K_WAIT_HH */ 62 | #if !CHICKADEE_WAIT_FUNCTIONS && CHICKADEE_PROC_DECLARATION 63 | #define CHICKADEE_WAIT_FUNCTIONS 1 64 | // Beginning of inline functions (second inclusion) 65 | 66 | 67 | inline waiter::waiter() 68 | : p_(current()) { 69 | } 70 | 71 | inline waiter::~waiter() { 72 | assert(!links_.is_linked()); 73 | // Feel free to add more sanity checks if you’d like! 74 | } 75 | 76 | inline void waiter::prepare(wait_queue& wq) { 77 | assert(p_ == current()); 78 | assert(!links_.is_linked()); 79 | wq_ = &wq; 80 | // your code here 81 | } 82 | 83 | inline void waiter::maybe_block() { 84 | assert(p_ == current() && wq_ != nullptr); 85 | // Thanks to concurrent wakeups, `p_->pstate_` might or might not equal 86 | // `proc::ps_blocked`, and `links_` might or might not be linked. 87 | // When the function returns, `p_->pstate_` MUST NOT equal 88 | // `proc::ps_blocked`, and `links_` MUST NOT be linked. 89 | // your code here 90 | } 91 | 92 | inline void waiter::clear() { 93 | assert(p_ == current()); 94 | // your code here 95 | } 96 | 97 | inline void waiter::notify() { 98 | assert(!links_.is_linked()); 99 | p_->unblock(); 100 | } 101 | 102 | 103 | // waiter::wait_until(wq, predicate) 104 | // Block on `wq` until `predicate()` returns true. 105 | template 106 | inline void waiter::wait_until(wait_queue& wq, F predicate) { 107 | while (true) { 108 | prepare(wq); 109 | if (predicate()) { 110 | break; 111 | } 112 | maybe_block(); 113 | } 114 | clear(); 115 | } 116 | 117 | // waiter::wait_until(wq, predicate, lock, irqs) 118 | // Block on `wq` until `predicate()` returns true. The `lock` 119 | // must be locked; it is unlocked before blocking (if blocking 120 | // is necessary). All calls to `predicate` have `lock` locked, 121 | // and `lock` is locked on return. 122 | template 123 | inline void waiter::wait_until(wait_queue& wq, F predicate, 124 | spinlock& lock, irqstate& irqs) { 125 | while (true) { 126 | prepare(wq); 127 | if (predicate()) { 128 | break; 129 | } 130 | lock.unlock(irqs); 131 | maybe_block(); 132 | irqs = lock.lock(); 133 | } 134 | clear(); 135 | } 136 | 137 | // waiter::wait_until(wq, predicate, guard) 138 | // Block on `wq` until `predicate()` returns true. The `guard` 139 | // must be locked on entry; it is unlocked before blocking (if 140 | // blocking is necessary) and locked on return. 141 | template 142 | inline void waiter::wait_until(wait_queue& wq, F predicate, 143 | spinlock_guard& guard) { 144 | wait_until(wq, predicate, guard.lock_, guard.irqs_); 145 | } 146 | 147 | 148 | // waiter::wait_once(wq) 149 | // Block on `wq` at most once. 150 | inline void waiter::wait_once(wait_queue& wq) { 151 | prepare(wq); 152 | maybe_block(); 153 | clear(); 154 | } 155 | 156 | // waiter::wait_once(wq, lock, irqs) 157 | // Block on `wq` at most once. The `lock` must be locked; it is 158 | // unlocked before blocking (if blocking is necessary), but locked 159 | // on return. 160 | inline void waiter::wait_once(wait_queue& wq, 161 | spinlock& lock, irqstate& irqs) { 162 | prepare(wq); 163 | lock.unlock(irqs); 164 | maybe_block(); 165 | irqs = lock.lock(); 166 | clear(); 167 | } 168 | 169 | // waiter::wait_once(wq, guard) 170 | // Block on `wq` at most once. The `guard` must be locked on entry; 171 | // it is unlocked before blocking (if blocking is necessary) and 172 | // locked on return. 173 | inline void waiter::wait_once(wait_queue& wq, 174 | spinlock_guard& guard) { 175 | wait_once(wq, guard.lock_, guard.irqs_); 176 | } 177 | 178 | 179 | // wait_queue::notify_all() 180 | // Lock the wait queue, then clear it by waking all waiters. 181 | inline void wait_queue::notify_all() { 182 | spinlock_guard guard(lock_); 183 | while (auto w = q_.pop_front()) { 184 | w->notify(); 185 | } 186 | } 187 | 188 | #endif /* CHICKADEE_WAIT_FUNCTIONS */ 189 | -------------------------------------------------------------------------------- /k-waitstruct.hh: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_K_WAITSTRUCT_HH 2 | #define CHICKADEE_K_WAITSTRUCT_HH 3 | #include "k-list.hh" 4 | struct proc; 5 | struct wait_queue; 6 | struct spinlock; 7 | struct irqstate; 8 | struct spinlock_guard; 9 | 10 | // k-waitstruct.hh 11 | // Includes the struct definitions for `waiter` and `wait_queue`. 12 | // The inline functions declared here are defined in `k-wait.hh`. 13 | 14 | 15 | struct waiter { 16 | proc* p_ = nullptr; 17 | wait_queue* wq_; 18 | list_links links_; 19 | 20 | explicit inline waiter(); 21 | inline ~waiter(); 22 | NO_COPY_OR_ASSIGN(waiter); 23 | inline void prepare(wait_queue& wq); 24 | inline void maybe_block(); 25 | inline void clear(); 26 | inline void wake(); 27 | 28 | template 29 | inline void block_until(wait_queue& wq, F predicate); 30 | template 31 | inline void block_until(wait_queue& wq, F predicate, 32 | spinlock& lock, irqstate& irqs); 33 | template 34 | inline void block_until(wait_queue& wq, F predicate, 35 | spinlock_guard& guard); 36 | }; 37 | 38 | 39 | struct wait_queue { 40 | list q_; 41 | mutable spinlock lock_; 42 | 43 | // you might want to provide some convenience methods here 44 | inline void wake_all(); 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /kernel.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | ENTRY(kernel_entry) 3 | 4 | PHDRS { 5 | low PT_LOAD; 6 | text PT_LOAD; 7 | } 8 | 9 | SECTIONS { 10 | /* Boot process uses 0x1000-0x3FFF */ 11 | 12 | 13 | /* Low text and data must be at very low physical addresses */ 14 | . = 0xFFFFFFFF80004000; 15 | _low_data_start = .; 16 | .lowtext : { 17 | KEEP (*(.lowtext)) 18 | } :low 19 | .lowdata (NOLOAD) : { 20 | *(.lowdata) 21 | } :NONE 22 | _low_data_end = .; 23 | 24 | 25 | /* Text segment: instructions and read-only globals */ 26 | . = 0xFFFFFFFF80100000; 27 | _kernel_start = .; 28 | .text : { 29 | *(.text.unlikely .text.*_unlikely .text.unlikely.*) 30 | *(.text.exit .text.exit.*) 31 | *(.text.startup .text.startup.*) 32 | *(.text.hot .text.hot.*) 33 | *(.text .stub .text.* .gnu.linkonce.t.*) 34 | } :text 35 | _etext = .; 36 | 37 | .rodata : { 38 | *(.rodata .rodata.* .gnu.linkonce.r.*) 39 | *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) 40 | *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) 41 | } :text 42 | 43 | /* Constructors: these sections support global initialization 44 | functions, such as for global C++ objects with constructors. */ 45 | .init_array : { 46 | PROVIDE(__init_array_start = .); 47 | KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) 48 | SORT_BY_INIT_PRIORITY(.ctors.*))) 49 | KEEP (*(.init_array .ctors)) 50 | PROVIDE(__init_array_end = .); 51 | } :text 52 | .ctors : { 53 | KEEP (*crtbegin.o(.ctors)) 54 | KEEP (*(SORT(.ctors.*))) 55 | KEEP (*(.ctors)) 56 | } :text 57 | 58 | /* Data segment: read/write and zero-initialized globals */ 59 | . = ALIGN(4096); /* Align to a page boundary */ 60 | .data : { 61 | *(.data .data.* .gnu.linkonce.d.*) 62 | . = ALIGN(16); 63 | interrupt_descriptors = .; 64 | KEEP (*(.interrupt_descriptors)) 65 | } :text 66 | 67 | .bss : { 68 | *(.bss .bss.* .gnu.linkonce.b.*) 69 | } :text 70 | PROVIDE(_kernel_end = .); 71 | 72 | PROVIDE(console = 0xFFFFFFFF800B8000); 73 | PROVIDE(cursorpos = 0xFFFFFFFF800B8FF8); 74 | PROVIDE(consoletype = 0xFFFFFFFF800B8FFC); 75 | 76 | PROVIDE(early_pagetable_low = early_pagetable & 0xFFFF); 77 | PROVIDE(early_gdt_low = early_gdt & 0xFFFF); 78 | PROVIDE(ap_rest_low = ap_rest & 0xFFFF); 79 | 80 | /DISCARD/ : { *(.eh_frame .note.GNU-stack) } 81 | } 82 | -------------------------------------------------------------------------------- /p-allocator.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | #define ALLOC_SLOWDOWN 24 3 | 4 | extern uint8_t end[]; 5 | 6 | uint8_t* heap_top; 7 | uint8_t* stack_bottom; 8 | 9 | void process_main() { 10 | sys_consoletype(CONSOLE_MEMVIEWER); 11 | 12 | // Fork three new copies. (But ignore failures.) 13 | (void) sys_fork(); 14 | (void) sys_fork(); 15 | 16 | pid_t p = sys_getpid(); 17 | srand(p); 18 | 19 | // The heap starts on the page right after the 'end' symbol, 20 | // whose address is the first address not allocated to process code 21 | // or data. 22 | heap_top = reinterpret_cast( 23 | round_up(reinterpret_cast(end), PAGESIZE) 24 | ); 25 | 26 | // The bottom of the stack is the first address on the current 27 | // stack page (this process never needs more than one stack page). 28 | stack_bottom = reinterpret_cast( 29 | round_down(rdrsp() - 1, PAGESIZE) 30 | ); 31 | 32 | while (true) { 33 | if (rand(0, ALLOC_SLOWDOWN - 1) < p) { 34 | if (heap_top == stack_bottom || sys_page_alloc(heap_top) < 0) { 35 | break; 36 | } 37 | *heap_top = p; /* check we have write access to new page */ 38 | heap_top += PAGESIZE; 39 | } 40 | sys_yield(); 41 | if (rand() < RAND_MAX / 32) { 42 | sys_pause(); 43 | } 44 | } 45 | 46 | // After running out of memory, do nothing forever 47 | while (true) { 48 | sys_yield(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /p-allocexit.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | #ifndef ALLOC_SLOWDOWN 3 | #define ALLOC_SLOWDOWN 18 4 | #endif 5 | 6 | extern uint8_t end[]; 7 | 8 | uint8_t* heap_top; 9 | uint8_t* stack_bottom; 10 | 11 | void process_main() { 12 | sys_consoletype(CONSOLE_MEMVIEWER); 13 | 14 | // First process never allocates; it alternates between forking children 15 | // and yielding; sometimes it exits. Each forked child allocates. 16 | while (true) { 17 | (void) sys_waitpid(0, nullptr, W_NOHANG); 18 | int x = rand(0, ALLOC_SLOWDOWN); 19 | if (x == 0) { 20 | // fork, then either exit or start allocating 21 | pid_t p = sys_fork(); 22 | int choice = rand(0, 2); 23 | if (choice == 0 && p > 0) { 24 | sys_exit(0); 25 | } else if (choice != 2 ? p > 0 : p == 0) { 26 | break; 27 | } 28 | } else { 29 | sys_yield(); 30 | } 31 | } 32 | 33 | int speed = rand(1, 16); 34 | 35 | // The heap starts on the page right after the 'end' symbol, 36 | // whose address is the first address not allocated to process code 37 | // or data. 38 | uint8_t* data_top = heap_top = reinterpret_cast( 39 | round_up(reinterpret_cast(end), PAGESIZE) 40 | ); 41 | 42 | // The bottom of the stack is the first address on the current 43 | // stack page (this process never needs more than one stack page). 44 | stack_bottom = reinterpret_cast( 45 | round_down(rdrsp() - 1, PAGESIZE) 46 | ); 47 | 48 | unsigned nalloc = 0; 49 | 50 | // Allocate heap pages until out of address space, 51 | // forking along the way. 52 | while (heap_top != stack_bottom) { 53 | if (rand(0, 6 * ALLOC_SLOWDOWN) >= 8 * speed) { 54 | (void) sys_waitpid(0, nullptr, W_NOHANG); 55 | sys_yield(); 56 | continue; 57 | } 58 | 59 | int x = rand(0, 7 + min(nalloc / 4, 10U)); 60 | if (x < 2) { 61 | if (sys_fork() == 0) { 62 | speed = rand(1, 16); 63 | } 64 | } else if (x < 3) { 65 | sys_exit(0); 66 | } else if (sys_page_alloc(heap_top) >= 0) { 67 | *heap_top = speed; // check we have write access to new page 68 | heap_top += PAGESIZE; 69 | nalloc = (heap_top - data_top) / PAGESIZE; 70 | } else if (nalloc < 4) { 71 | sys_exit(0); 72 | } else { 73 | nalloc -= 4; 74 | } 75 | } 76 | 77 | // After running out of memory 78 | while (true) { 79 | if (rand(0, 2 * ALLOC_SLOWDOWN - 1) == 0) { 80 | sys_exit(0); 81 | } else { 82 | (void) sys_waitpid(0, nullptr, 0); 83 | sys_yield(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /p-cat.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | void process_main(int argc, char** argv) { 4 | char buf[256]; 5 | 6 | for (int i = 1; i == 1 || i < argc; ++i) { 7 | int f = 0; 8 | if (i < argc && strcmp(argv[i], "-") != 0) { 9 | f = sys_open(argv[i], OF_READ); 10 | if (f < 0) { 11 | dprintf(2, "%s: error %d\n", argv[i], f); 12 | sys_exit(1); 13 | } 14 | } 15 | while (true) { 16 | ssize_t n = sys_read(f, buf, sizeof(buf)); 17 | if (n == 0 || (n < 0 && n != E_AGAIN)) { 18 | break; 19 | } 20 | if (n > 0) { 21 | ssize_t w = sys_write(1, buf, n); 22 | if (w != n) { 23 | break; 24 | } 25 | } 26 | } 27 | if (f != 0) { 28 | sys_close(f); 29 | } 30 | } 31 | 32 | sys_exit(0); 33 | } 34 | -------------------------------------------------------------------------------- /p-echo.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | void process_main(int argc, char** argv) { 4 | int i = 1; 5 | while (argv[i]) { 6 | if (i > 1) { 7 | sys_write(1, " ", 1); 8 | } 9 | sys_write(1, argv[i], strlen(argv[i])); 10 | ++i; 11 | } 12 | sys_write(1, "\n", 1); 13 | assert(i == argc); 14 | 15 | sys_exit(0); 16 | } 17 | -------------------------------------------------------------------------------- /p-execallocexit.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | void process_main() { 4 | for (int i = 5; i > 0; --i) { 5 | printf("Running memviewer in %d...\n", i); 6 | sys_msleep(250); 7 | } 8 | 9 | const char* args[] = { 10 | "allocexit", nullptr 11 | }; 12 | int r = sys_execv("allocexit", args); 13 | assert_eq(r, 0); 14 | 15 | sys_exit(0); 16 | } 17 | -------------------------------------------------------------------------------- /p-exececho.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | void process_main() { 4 | sys_write(1, "About to greet you...\n", 22); 5 | 6 | const char* args[] = { 7 | "echo", "hello,", "world", nullptr 8 | }; 9 | int r = sys_execv("echo", args); 10 | assert_eq(r, 0); 11 | 12 | sys_exit(0); 13 | } 14 | -------------------------------------------------------------------------------- /p-false.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | void process_main() { 4 | sys_exit(1); 5 | } 6 | -------------------------------------------------------------------------------- /p-ktestwait.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | int r; 6 | while (true) { 7 | r = make_syscall(SYSCALL_KTEST, 1); 8 | if (r < 0) { 9 | console_printf(CS_ERROR "ktestwait appears to have failed!\n"); 10 | console_printf(CS_ERROR "(However, this could be due to a slow computer.)\n"); 11 | sys_exit(1); 12 | } else if (r >= 1000) { 13 | sys_exit(0); 14 | } 15 | sys_msleep(500); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /p-nastyalloc.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | #define ALLOC_SLOWDOWN 24 3 | 4 | extern uint8_t end[]; 5 | 6 | uint8_t* heap_top; 7 | uint8_t* stack_bottom; 8 | 9 | void process_main() { 10 | sys_consoletype(CONSOLE_MEMVIEWER); 11 | 12 | pid_t p = sys_getpid(); 13 | srand(p); 14 | 15 | heap_top = reinterpret_cast( 16 | round_up(reinterpret_cast(end), PAGESIZE) 17 | ); 18 | stack_bottom = reinterpret_cast( 19 | round_down(rdrsp() - 1, PAGESIZE) 20 | ); 21 | 22 | while (true) { 23 | // Add code to this loop to call your new, nasty system call 24 | // with some probability! 25 | 26 | if (rand(0, ALLOC_SLOWDOWN - 1) < p) { 27 | if (heap_top == stack_bottom || sys_page_alloc(heap_top) < 0) { 28 | break; 29 | } 30 | *heap_top = p; /* check we have write access to new page */ 31 | heap_top += PAGESIZE; 32 | } 33 | sys_yield(); 34 | if (rand() < RAND_MAX / 32) { 35 | sys_pause(); 36 | } 37 | } 38 | 39 | while (true) { 40 | sys_yield(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /p-readdiskfile.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | void process_main(int argc, char** argv) { 4 | char buf[1]; 5 | 6 | const char* filename = "emerson.txt"; 7 | if (argc > 1) { 8 | filename = argv[1]; 9 | } 10 | 11 | size_t off = 0; 12 | while (true) { 13 | ssize_t n = sys_readdiskfile(filename, buf, sizeof(buf), off); 14 | if (n < 0) { 15 | dprintf(2, "%s: error %d\n", filename, int(n)); 16 | sys_exit(1); 17 | } else if (n == 0) { 18 | break; 19 | } 20 | sys_write(1, buf, n); 21 | off += n; 22 | } 23 | 24 | sys_exit(0); 25 | } 26 | -------------------------------------------------------------------------------- /p-sleep.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | void process_main(int argc, char** argv) { 4 | long ns; 5 | char* endptr; 6 | if (argc != 2 7 | || (ns = strtol(argv[1], &endptr, 0)) < 0 8 | || endptr == argv[1] 9 | || *endptr) { 10 | dprintf(2, "usage: sleep seconds\n"); 11 | sys_exit(1); 12 | } else { 13 | sys_msleep(1000 * ns); 14 | sys_exit(0); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /p-testeintr.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | static const int order[] = { 5 | 2, 7, 6, 4, 1, 8, 5, 3 6 | }; 7 | 8 | static void make_children(pid_t* children) { 9 | for (size_t i = 0; i != arraysize(order); ++i) { 10 | pid_t p = sys_fork(); 11 | if (p == 0) { 12 | sys_msleep(order[i] * 500); 13 | sys_exit(order[i]); 14 | } 15 | children[i] = p; 16 | } 17 | } 18 | 19 | void process_main() { 20 | pid_t children[arraysize(order)]; 21 | make_children(children); 22 | for (size_t i = 0; i != arraysize(order); ++i) { 23 | int r = sys_msleep(1000); 24 | assert_eq(r, E_INTR); 25 | 26 | int status = 0; 27 | pid_t ch = sys_waitpid(0, &status, W_NOHANG); 28 | assert_gt(ch, 0); 29 | 30 | size_t idx = 0; 31 | while (idx != arraysize(order) && children[idx] != ch) { 32 | ++idx; 33 | } 34 | assert(idx < arraysize(order)); 35 | children[idx] = 0; 36 | 37 | console_printf("%d @%lu: exit status %d\n", ch, idx, status); 38 | assert_eq(order[idx], status); 39 | } 40 | assert_eq(sys_waitpid(0), E_CHILD); 41 | assert_eq(sys_msleep(1000), 0); 42 | 43 | 44 | console_printf(CS_SUCCESS "testeintr succeeded!\n"); 45 | sys_exit(0); 46 | } 47 | -------------------------------------------------------------------------------- /p-testforksimple.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | pid_t initial_pid = sys_getpid(); 6 | assert_gt(initial_pid, 0); 7 | 8 | // Fork a total of three new copies, checking return values for simple 9 | // issues. 10 | pid_t p1 = sys_fork(); 11 | assert_ge(p1, 0); 12 | pid_t intermediate_pid = sys_getpid(); 13 | if (p1 == 0) { 14 | assert_ne(intermediate_pid, initial_pid); 15 | } else { 16 | assert_eq(intermediate_pid, initial_pid); 17 | assert_ne(p1, initial_pid); 18 | } 19 | 20 | pid_t p2 = sys_fork(); 21 | assert_ge(p2, 0); 22 | pid_t final_pid = sys_getpid(); 23 | if (p2 == 0) { 24 | assert_ne(final_pid, initial_pid); 25 | assert_ne(final_pid, intermediate_pid); 26 | } else { 27 | assert_eq(final_pid, intermediate_pid); 28 | assert_ne(p2, initial_pid); 29 | assert_ne(p2, intermediate_pid); 30 | assert_ne(p2, p1); 31 | } 32 | 33 | console_printf(CPOS(final_pid - 1, 0), 34 | CS_SUCCESS "testforksimple %d [%d] %d [%d] %d succeeded!\n", 35 | initial_pid, p1, intermediate_pid, p2, final_pid); 36 | 37 | // This test runs before `sys_exit` is implemented, so we can’t use it. 38 | while (true) { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /p-testgetusage.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | extern uint8_t end[]; 5 | 6 | void process_main() { 7 | usage u; 8 | int r = sys_getusage(&u); 9 | assert_eq(r, 0); 10 | 11 | assert_ge(static_cast(u.time), 0); 12 | 13 | size_t total_pages = u.free_pages + u.allocated_pages; 14 | assert_gt(total_pages, 0UL); 15 | assert_le(total_pages, 0x100000UL); 16 | assert_gt(u.free_pages, 3UL); 17 | 18 | // allocate a page and recheck 19 | uint8_t* ptr = reinterpret_cast( 20 | round_up(reinterpret_cast(end), PAGESIZE) 21 | ); 22 | r = sys_page_alloc(ptr); 23 | assert_eq(r, 0); 24 | 25 | usage u1; 26 | r = sys_getusage(&u1); 27 | assert_eq(r, 0); 28 | assert_ge(u1.time, u.time); 29 | assert_eq(total_pages, u1.free_pages + u1.allocated_pages); 30 | assert_le(u1.free_pages, u.free_pages - 1); 31 | 32 | // check illegal accesses 33 | r = sys_getusage(nullptr); 34 | assert_eq(r, E_FAULT); 35 | 36 | r = sys_getusage(reinterpret_cast(0xFFFF'FFFF'FFFF'FFFCUL)); 37 | assert_eq(r, E_FAULT); 38 | 39 | r = sys_getusage(reinterpret_cast(0x1000)); 40 | assert_eq(r, E_FAULT); 41 | 42 | r = sys_getusage(reinterpret_cast(&ptr[PAGESIZE - 4])); 43 | assert_eq(r, E_FAULT); 44 | 45 | // allocate two more pages 46 | r = sys_page_alloc(&ptr[PAGESIZE * 2]); 47 | assert_eq(r, 0); 48 | r = sys_page_alloc(&ptr[PAGESIZE]); 49 | assert_eq(r, 0); 50 | 51 | usage* uptr = reinterpret_cast(&ptr[PAGESIZE * 2 - 8]); 52 | r = sys_getusage(uptr); 53 | assert_eq(r, 0); 54 | assert_ge(uptr->time, u1.time); 55 | assert_eq(total_pages, uptr->free_pages + uptr->allocated_pages); 56 | assert_le(uptr->free_pages, u.free_pages - 3); 57 | 58 | console_printf(CS_SUCCESS "testgetusage succeeded!\n"); 59 | 60 | // This test runs before `sys_exit` is implemented, so we can’t use it. 61 | while (true) { 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /p-testhalt.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | assert_eq(sys_getppid(), 1); 6 | pid_t fork1 = sys_fork(); 7 | pid_t fork2 = sys_fork(); 8 | 9 | // Original exits 10 | if (fork1 != 0 && fork2 != 0) { 11 | console_printf("0\n"); 12 | sys_exit(0); 13 | } else if (fork1 == 0 && fork2 != 0) { 14 | sys_msleep(10); 15 | console_printf("1\n"); 16 | sys_exit(0); 17 | } else if (fork1 != 0 && fork2 == 0) { 18 | sys_msleep(100); 19 | console_printf("2\n"); 20 | sys_exit(0); 21 | } else { 22 | sys_msleep(300); 23 | console_printf("testhalt succeeds if you see 0-2 above and QEMU exits after a second\n"); 24 | console_printf("(QEMU will only exit if you ran with `HALT=1`)\n"); 25 | sys_msleep(1000); 26 | sys_exit(0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /p-testkalloc.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | // Your code here! 6 | // Running `testkalloc` should cause the kernel to run buddy allocator 7 | // tests. How you make this work is up to you. 8 | 9 | panic("testkalloc not implemented!\n"); 10 | } 11 | -------------------------------------------------------------------------------- /p-testmemfs.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | int f = sys_open("emerson.txt", OF_READ); 6 | assert_gt(f, 2); 7 | 8 | char buf[200]; 9 | ssize_t n = sys_read(f, buf, 1); 10 | assert_eq(n, 1); 11 | assert_memeq(buf, "W", 1); 12 | 13 | n = sys_read(f, buf, 2); 14 | assert_eq(n, 2); 15 | assert_memeq(buf, "he", 2); 16 | 17 | n = sys_read(f, buf, 3); 18 | assert_eq(n, 3); 19 | assert_memeq(buf, "n p", 3); 20 | 21 | 22 | int f2 = sys_open("emerson.txt", OF_READ); 23 | assert(f2 > 2 && f2 != f); 24 | 25 | n = sys_read(f2, buf, 5); 26 | assert_eq(n, 5); 27 | assert_memeq(buf, "When ", 5); 28 | 29 | n = sys_read(f, buf, 5); 30 | assert_eq(n, 5); 31 | assert_memeq(buf, "iped ", 5); 32 | 33 | 34 | int f3 = 3; 35 | while (f3 == f || f3 == f2) { 36 | ++f3; 37 | } 38 | int r = sys_dup2(f, f3); 39 | assert_ge(r, 0); 40 | 41 | n = sys_read(f, buf, 10); 42 | assert_eq(n, 10); 43 | assert_memeq(buf, "a tiny voi", 10); 44 | 45 | n = sys_read(f3, buf, 10); 46 | assert_eq(n, 10); 47 | assert_memeq(buf, "ce hard by", 10); 48 | 49 | n = sys_read(f, buf, 10); 50 | assert_eq(n, 10); 51 | assert_memeq(buf, ",\nGay and ", 10); 52 | 53 | r = sys_close(f); 54 | assert_eq(r, 0); 55 | 56 | n = sys_read(f, buf, 10); 57 | assert_eq(n, E_BADF); 58 | 59 | n = sys_read(f2, buf, 10); 60 | assert_eq(n, 10); 61 | assert_memeq(buf, "piped a ti", 10); 62 | 63 | n = sys_read(f3, buf, 10); 64 | assert_eq(n, 10); 65 | assert_memeq(buf, "polite, a ", 10); 66 | 67 | 68 | n = sys_read(f3, buf, sizeof(buf)); 69 | assert_eq(n, 79); 70 | assert_memeq(buf, "cheerful cry,\n", 14); 71 | assert_memeq(buf + 72, "throat\n", 7); 72 | 73 | n = sys_read(f3, buf, sizeof(buf)); 74 | assert_eq(n, 0); 75 | 76 | n = sys_read(f3, buf, sizeof(buf)); 77 | assert_eq(n, 0); 78 | 79 | 80 | r = sys_close(f2); 81 | assert_eq(r, 0); 82 | 83 | r = sys_close(f3); 84 | assert_eq(r, 0); 85 | 86 | 87 | r = sys_open(nullptr, OF_READ); 88 | assert_eq(r, E_FAULT); 89 | 90 | extern char end[]; 91 | char* page = reinterpret_cast( 92 | round_up(reinterpret_cast(end), PAGESIZE) 93 | ) + PAGESIZE; 94 | r = sys_page_alloc(page); 95 | assert_eq(r, 0); 96 | 97 | strcpy(page, "emerson.txt"); 98 | f = sys_open(page, OF_READ); 99 | assert_gt(f, 2); 100 | 101 | n = sys_read(f, buf, 4); 102 | assert_eq(n, 4); 103 | assert_memeq(buf, "When", 4); 104 | 105 | memcpy(page + PAGESIZE - 11, "emerson.txt", 11); 106 | f2 = sys_open(page + PAGESIZE - 11, OF_READ); 107 | assert_eq(f2, E_FAULT); 108 | 109 | 110 | console_printf(CS_SUCCESS "testmemfs succeeded!\n"); 111 | sys_exit(0); 112 | } 113 | -------------------------------------------------------------------------------- /p-testmsleep.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | static const int order[] = { 5 | 2, 7, 6, 4, 1, 8, 5, 3 6 | }; 7 | 8 | void process_main() { 9 | // create 8 processes 10 | int my_idx = 0; 11 | for (int i = 0; i < 3; ++i) { 12 | pid_t f = sys_fork(); 13 | assert_ge(f, 0); 14 | my_idx = (my_idx * 2) + (f == 0); 15 | } 16 | 17 | // each process sleeps for `100 * order[my_idx]` milliseconds 18 | int r = sys_msleep(100 * order[my_idx]); 19 | assert_eq(r, 0); 20 | 21 | // then prints its position 22 | console_printf("%d [pid %d]\n", order[my_idx], sys_getpid()); 23 | 24 | if (my_idx != 0) { 25 | sys_msleep(1000); 26 | sys_exit(0); 27 | } 28 | 29 | // test results by examining console 30 | sys_msleep(800); 31 | console_printf("You should see lines numbered 1-8 in order.\n"); 32 | for (int i = 0; i < 8; ++i) { 33 | assert((console[i * CONSOLE_COLUMNS] & 0xFF) == '1' + i); 34 | } 35 | console_printf(CS_SUCCESS "testmsleep succeeded!\n"); 36 | sys_exit(0); 37 | } 38 | -------------------------------------------------------------------------------- /p-testpipe.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | console_printf("creating pipes...\n"); 6 | 7 | int pfd[2] = {-1, -1}; 8 | int x = sys_pipe(pfd); 9 | assert_eq(x, 0); 10 | assert(pfd[0] > 2 && pfd[1] > 2); 11 | assert(pfd[0] != pfd[1]); 12 | 13 | int qfd[2] = {-1, -1}; 14 | x = sys_pipe(qfd); 15 | assert_eq(x, 0); 16 | assert(qfd[0] > 2 && qfd[1] > 2); 17 | assert(qfd[0] != qfd[1]); 18 | assert(pfd[0] != qfd[0] && pfd[0] != qfd[1]); 19 | assert(pfd[1] != qfd[0] && pfd[1] != qfd[1]); 20 | 21 | console_printf("pfd %d,%d, qfd %d,%d\n", pfd[0], pfd[1], qfd[0], qfd[1]); 22 | 23 | 24 | // simple tests 25 | console_printf("simple tests...\n"); 26 | 27 | ssize_t n = sys_write(pfd[1], "pfd1", 4); 28 | assert_eq(n, 4); 29 | 30 | n = sys_write(qfd[1], "qfd1", 4); 31 | assert_eq(n, 4); 32 | 33 | char buf[400]; 34 | n = sys_read(pfd[0], buf, 2); 35 | assert_eq(n, 2); 36 | assert_memeq(buf, "pf", 2); 37 | 38 | n = sys_read(pfd[0], buf, 8); 39 | assert_eq(n, 2); 40 | assert_memeq(buf, "d1", 2); 41 | 42 | n = sys_read(qfd[0], buf, 100); 43 | assert_eq(n, 4); 44 | assert_memeq(buf, "qfd1", 4); 45 | 46 | 47 | // interleaving tests 48 | console_printf("interleaving tests...\n"); 49 | 50 | n = sys_write(pfd[1], "W1", 2); 51 | assert_eq(n, 2); 52 | 53 | n = sys_write(qfd[1], "w1", 2); 54 | assert_eq(n, 2); 55 | 56 | n = sys_write(qfd[1], "w2", 2); 57 | assert_eq(n, 2); 58 | 59 | n = sys_write(pfd[1], "W2", 2); 60 | assert_eq(n, 2); 61 | 62 | n = sys_write(pfd[1], "W3!", 3); 63 | assert_eq(n, 3); 64 | 65 | n = sys_write(qfd[1], "w3!", 3); 66 | assert_eq(n, 3); 67 | 68 | n = sys_write(pfd[1], "W4!!!", 5); 69 | assert_eq(n, 5); 70 | 71 | n = sys_write(qfd[1], "w4!!!", 5); 72 | assert_eq(n, 5); 73 | 74 | n = sys_read(pfd[0], buf, 100); 75 | assert_eq(n, 12); 76 | assert_memeq(buf, "W1W2W3!W4!!!", 12); 77 | 78 | n = sys_read(qfd[0], buf, 100); 79 | assert_eq(n, 12); 80 | assert_memeq(buf, "w1w2w3!w4!!!", 12); 81 | 82 | 83 | // can't read from write end or write to read end 84 | console_printf("read/write error tests...\n"); 85 | 86 | n = sys_read(pfd[1], buf, 8); 87 | assert_eq(n, E_BADF); 88 | 89 | n = sys_write(qfd[0], buf, 1); 90 | assert_eq(n, E_BADF); 91 | 92 | 93 | // inheritance tests 94 | console_printf("child tests...\n"); 95 | 96 | pid_t p = sys_fork(); 97 | assert_ge(p, 0); 98 | 99 | if (p == 0) { 100 | x = sys_close(pfd[1]); 101 | assert_eq(x, 0); 102 | 103 | x = sys_close(qfd[0]); 104 | assert_eq(x, 0); 105 | 106 | x = sys_close(pfd[1]); 107 | assert_eq(x, E_BADF); 108 | 109 | n = sys_write(qfd[1], "hello mom", 9); 110 | assert_eq(n, 9); 111 | 112 | n = sys_read(pfd[0], buf, 100); 113 | assert_eq(n, 5); 114 | assert_memeq(buf, "hello", 5); 115 | 116 | n = sys_read(pfd[0], buf, 100); 117 | assert_eq(n, 5); 118 | assert_memeq(buf, " babe", 5); 119 | 120 | x = sys_msleep(300); 121 | assert_eq(x, 0); 122 | 123 | x = sys_close(pfd[0]); 124 | assert_eq(x, 0); 125 | 126 | x = sys_close(qfd[1]); 127 | assert_eq(x, 0); 128 | 129 | sys_exit(0); 130 | } 131 | 132 | x = sys_close(pfd[0]); 133 | assert_eq(x, 0); 134 | 135 | x = sys_close(qfd[1]); 136 | assert_eq(x, 0); 137 | 138 | n = sys_read(qfd[0], buf, 100); 139 | assert_eq(n, 9); 140 | assert_memeq(buf, "hello mom", 9); 141 | 142 | n = sys_write(pfd[1], "hello", 5); 143 | assert_eq(n, 5); 144 | 145 | sys_msleep(300); 146 | 147 | n = sys_write(pfd[1], " babe", 5); 148 | assert_eq(n, 5); 149 | 150 | n = sys_read(qfd[0], buf, 100); 151 | assert_eq(n, 0); 152 | 153 | n = sys_write(pfd[1], "wharg", 5); 154 | assert_eq(n, E_PIPE); 155 | 156 | x = sys_close(qfd[0]); 157 | assert_eq(x, 0); 158 | 159 | x = sys_close(pfd[1]); 160 | assert_eq(x, 0); 161 | 162 | sys_msleep(100); // try to ensure first child exits 163 | 164 | 165 | // inheritance tests with close-on-exit 166 | console_printf("close-on-exit tests...\n"); 167 | 168 | x = sys_pipe(pfd); 169 | assert(x == 0 && pfd[0] > 2 && pfd[1] > 2 && pfd[0] != pfd[1]); 170 | 171 | n = sys_write(pfd[1], "hello", 5); 172 | assert_eq(n, 5); 173 | 174 | p = sys_fork(); 175 | assert_ge(p, 0); 176 | 177 | if (p == 0) { 178 | n = sys_read(pfd[0], buf, 100); 179 | assert_eq(n, 5); 180 | assert_memeq(buf, "hello", 5); 181 | 182 | x = sys_msleep(400); 183 | assert_eq(x, 0); 184 | 185 | sys_exit(0); 186 | } 187 | 188 | x = sys_msleep(100); 189 | assert_eq(x, 0); 190 | 191 | x = sys_close(pfd[1]); 192 | assert_eq(x, 0); 193 | 194 | n = sys_read(pfd[0], buf, 100); 195 | assert_eq(n, 0); 196 | 197 | 198 | console_printf(CS_SUCCESS "testpipe succeeded!\n"); 199 | sys_exit(0); 200 | } 201 | -------------------------------------------------------------------------------- /p-testppid.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | assert_eq(sys_getppid(), 1); 6 | pid_t original = sys_getpid(); 7 | 8 | // Fork two children 9 | pid_t fork1 = sys_fork(); 10 | pid_t after1 = sys_getpid(); 11 | pid_t after1_parent = sys_getppid(); 12 | pid_t fork2 = sys_fork(); 13 | pid_t after2 = sys_getpid(); 14 | 15 | // Check their parents 16 | if (fork1 == 0 && fork2 == 0) { 17 | assert_ne(original, after1); 18 | assert_eq(original, after1_parent); 19 | assert_ne(after1, after2); 20 | assert_eq(sys_getppid(), after1); 21 | } else if (fork1 == 0) { 22 | assert_ne(original, after1); 23 | assert_eq(original, after1_parent); 24 | assert_eq(after1, after2); 25 | assert_eq(sys_getppid(), original); 26 | } else if (fork2 == 0) { 27 | assert_eq(original, after1); 28 | assert_eq(1, after1_parent); 29 | assert_ne(after1, after2); 30 | assert_eq(sys_getppid(), original); 31 | } else { 32 | assert_eq(original, after1); 33 | assert_eq(1, after1_parent); 34 | assert_eq(after1, after2); 35 | assert_eq(sys_getppid(), 1); 36 | } 37 | 38 | sys_msleep(50); 39 | if (sys_getpid() == original) { 40 | console_printf(CS_GREEN "ppid tests without exit succeeded!\n"); 41 | } else { 42 | sys_exit(0); 43 | } 44 | 45 | 46 | // Tests that implicate `exit` behavior 47 | assert(original != 1); 48 | fork1 = sys_fork(); 49 | 50 | if (fork1 == 0) { 51 | after1 = sys_getpid(); 52 | after1_parent = sys_getppid(); 53 | fork2 = sys_fork(); 54 | } else { 55 | fork2 = -1; 56 | } 57 | 58 | pid_t after2_parent, fork3; 59 | if (fork2 == 0) { 60 | after2 = sys_getpid(); 61 | after2_parent = sys_getppid(); 62 | fork3 = sys_fork(); 63 | } else { 64 | fork3 = -1; 65 | } 66 | 67 | if (fork3 == 0) { 68 | assert_ne(original, after1); 69 | assert_eq(after1_parent, original); 70 | assert_eq(after2_parent, after1); 71 | assert_eq(sys_getppid(), after2); 72 | sys_msleep(100); 73 | assert_eq(sys_getppid(), after2); 74 | sys_msleep(100); 75 | assert_eq(sys_getppid(), 1); 76 | sys_exit(0); 77 | } else if (fork2 == 0) { 78 | assert_ne(original, after1); 79 | assert_eq(sys_getppid(), after1); 80 | sys_msleep(100); 81 | assert_eq(sys_getppid(), 1); 82 | sys_msleep(50); 83 | sys_exit(0); 84 | } else if (fork1 == 0) { 85 | sys_msleep(50); 86 | sys_exit(0); 87 | } 88 | 89 | for (int i = 0; i != 6; ++i) { 90 | sys_msleep(50); // loop because a long `msleep` could be interrupted 91 | } 92 | console_printf(CS_GREEN "ppid tests with exit succeeded!\n"); 93 | console_printf(CS_SUCCESS "testppid succeeded!\n"); 94 | sys_exit(0); 95 | } 96 | -------------------------------------------------------------------------------- /p-testrwaddr.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | // check a normal read 6 | const char msg1[] = "Type a few characters and press return:\n"; 7 | ssize_t w = sys_write(1, msg1, strlen(msg1)); 8 | assert_eq(static_cast(w), strlen(msg1)); 9 | 10 | char buf[200]; 11 | ssize_t r = sys_read(0, buf, sizeof(buf)); 12 | assert_gt(r, 0); 13 | 14 | 15 | // check invalid addresses 16 | w = sys_write(1, msg1, 0xFFFFFFFFFFFFFFFFUL); 17 | assert_eq(w, E_FAULT); 18 | 19 | w = sys_write(1, reinterpret_cast(10), 1); 20 | assert_eq(w, E_FAULT); 21 | 22 | w = sys_write(1, reinterpret_cast(rdrbp()), 16384); 23 | assert_eq(w, E_FAULT); 24 | 25 | 26 | r = sys_read(0, buf, 0xFFFFFFFFFFFFFFFFUL); 27 | assert_eq(r, E_FAULT); 28 | 29 | r = sys_read(0, reinterpret_cast(10), 1); 30 | assert_eq(r, E_FAULT); 31 | 32 | r = sys_read(0, reinterpret_cast(rdrbp()), 16384); 33 | assert_eq(r, E_FAULT); 34 | 35 | 36 | // invalid addresses, but valid calls: It should be OK to read or 37 | // write 0 bytes at any low-canonical address, mapped or not. 38 | w = sys_write(1, reinterpret_cast(0x40000000UL), 0); 39 | assert_eq(w, 0); 40 | 41 | w = sys_write(1, reinterpret_cast(VA_LOWEND), 0); 42 | assert_eq(w, 0); 43 | 44 | r = sys_read(0, reinterpret_cast(0x40000000UL), 0); 45 | assert_eq(r, 0); 46 | 47 | r = sys_read(0, reinterpret_cast(VA_LOWEND), 0); 48 | assert_eq(r, 0); 49 | 50 | 51 | // ensure we can get right up to the end of low canonical memory 52 | char* pg = reinterpret_cast(VA_LOWEND - PAGESIZE); 53 | r = sys_page_alloc(pg); 54 | assert_eq(r, 0); 55 | 56 | memset(pg, '-', PAGESIZE); 57 | const char msg2[] = "Now type another few characters and press return:\n"; 58 | char* pg_msg2 = pg + PAGESIZE - strlen(msg2); 59 | memcpy(pg_msg2, msg2, strlen(msg2)); 60 | w = sys_write(1, pg_msg2, strlen(msg2)); 61 | assert_eq(static_cast(w), strlen(msg2)); 62 | 63 | r = sys_read(0, pg_msg2, strlen(msg2)); 64 | assert_gt(r, 0); 65 | 66 | 67 | console_printf(CS_SUCCESS "testrwaddr succeeded!\n"); 68 | sys_exit(0); 69 | } 70 | -------------------------------------------------------------------------------- /p-testthread.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | #include 4 | 5 | extern uint8_t end[]; 6 | 7 | std::atomic_flag message_lock; 8 | std::atomic phase = 0; 9 | int pfd[2] = {-1, -1}; 10 | const char* shared; 11 | 12 | static void message(const char* x) { 13 | while (message_lock.test_and_set()) { 14 | pause(); 15 | } 16 | int tid = sys_gettid(); 17 | int pid = sys_getpid(); 18 | assert(tid > 0 && pid > 0); 19 | console_printf("T%d (P%d): %s\n", tid, pid, x); 20 | message_lock.clear(); 21 | } 22 | 23 | 24 | static int thread1a(void* x) { 25 | message("starting thread1a"); 26 | 27 | // wait for phase 1 28 | while (phase != 1) { 29 | sys_yield(); 30 | } 31 | assert_memeq(shared, "Message to child\n", 17); 32 | 33 | // enter phase 2 34 | message("sending to main"); 35 | shared = "Message to parent\n"; 36 | phase = 2; 37 | 38 | // wait for phase 3 39 | while (phase != 3) { 40 | sys_yield(); 41 | } 42 | 43 | // read from pipe, write to pipe 44 | char buf[100]; 45 | memset(buf, 0, sizeof(buf)); 46 | ssize_t n = sys_read(pfd[0], buf, sizeof(buf)); 47 | assert_eq(n, 2); 48 | assert_memeq(buf, "Yo", 2); 49 | 50 | phase = 4; 51 | message("piping to main"); 52 | n = sys_write(pfd[1], "Hi", 2); 53 | assert_eq(n, 2); 54 | 55 | sys_texit(0); 56 | } 57 | 58 | 59 | static int thread1b(void*) { 60 | // the argument to `sys_texit` is thrown away; we're just 61 | // checking that nothing goes badly wrong 62 | return 0; 63 | } 64 | 65 | 66 | static void test1() { 67 | // create thread 68 | message("clone"); 69 | char* stack1 = reinterpret_cast( 70 | round_up(reinterpret_cast(end), PAGESIZE) + 16 * PAGESIZE 71 | ); 72 | int r = sys_page_alloc(stack1); 73 | assert_eq(r, 0); 74 | 75 | pid_t t = sys_clone(thread1a, pfd, stack1 + PAGESIZE); 76 | assert_gt(t, 0); 77 | 78 | // enter phase 1 79 | message("sending to secondary"); 80 | shared = "Message to child\n"; 81 | phase = 1; 82 | 83 | // wait for phase 2 84 | while (phase != 2) { 85 | sys_yield(); 86 | } 87 | assert_memeq(shared, "Message to parent\n", 18); 88 | 89 | // enter phase 3 90 | message("piping to secondary"); 91 | r = sys_pipe(pfd); 92 | assert_eq(r, 0); 93 | assert(pfd[0] > 2 && pfd[1] > 2); 94 | assert(pfd[0] != pfd[1]); 95 | phase = 3; 96 | 97 | r = sys_write(pfd[1], "Yo", 2); 98 | 99 | // enter phase 4 100 | while (phase != 4) { 101 | sys_yield(); 102 | } 103 | char buf[100]; 104 | memset(buf, 0, sizeof(buf)); 105 | r = sys_read(pfd[0], buf, sizeof(buf)); 106 | assert_eq(r, 2); 107 | assert_memeq(buf, "Hi", 2); 108 | 109 | // wait for thread to exit 110 | sys_msleep(10); 111 | message(CS_GREEN "simple thread tests succeeded!"); 112 | 113 | // start a new thread to check thread returning doesn't go wrong 114 | message("checking automated texit"); 115 | t = sys_clone(thread1b, pfd, stack1 + PAGESIZE); 116 | assert_gt(t, 0); 117 | sys_msleep(10); 118 | } 119 | 120 | 121 | static int thread2a(void*) { 122 | // this blocks forever 123 | char buf[20]; 124 | (void) sys_read(pfd[0], buf, sizeof(buf)); 125 | assert(false); 126 | } 127 | 128 | static void test2() { 129 | // create thread 130 | char* stack1 = reinterpret_cast( 131 | round_up(reinterpret_cast(end), PAGESIZE) + 16 * PAGESIZE 132 | ); 133 | int r = sys_page_alloc(stack1); 134 | assert_eq(r, 0); 135 | 136 | pid_t t = sys_clone(thread2a, pfd, stack1 + PAGESIZE); 137 | assert_gt(t, 0); 138 | 139 | // this should quit the `thread2a` thread too 140 | sys_exit(161); 141 | } 142 | 143 | 144 | static int thread3a(void*) { 145 | sys_msleep(10); 146 | return 161; 147 | } 148 | 149 | static void test3() { 150 | // create thread 151 | char* stack1 = reinterpret_cast( 152 | round_up(reinterpret_cast(end), PAGESIZE) + 16 * PAGESIZE 153 | ); 154 | int r = sys_page_alloc(stack1); 155 | assert_eq(r, 0); 156 | 157 | pid_t t = sys_clone(thread3a, pfd, stack1 + PAGESIZE); 158 | assert_gt(t, 0); 159 | 160 | // this exits the main thread, but allows `thread3a` to continue; 161 | // the eventual exit status should be `thread3a`'s 162 | sys_texit(0); 163 | } 164 | 165 | 166 | void process_main() { 167 | // test1 168 | pid_t p = sys_fork(); 169 | assert_ge(p, 0); 170 | if (p == 0) { 171 | test1(); 172 | sys_exit(0); 173 | } 174 | pid_t ch = sys_waitpid(p); 175 | assert_eq(ch, p); 176 | 177 | 178 | // test2 179 | message("checking that exit exits all threads"); 180 | int r = sys_pipe(pfd); 181 | assert_eq(r, 0); 182 | p = sys_fork(); 183 | assert_ge(p, 0); 184 | if (p == 0) { 185 | test2(); 186 | } 187 | int status = 0; 188 | ch = sys_waitpid(p, &status); 189 | assert_eq(ch, p); 190 | assert_eq(status, 161); 191 | 192 | // check that `thread2a` really exited; if it did not, then 193 | // the read end of the pipe will still be open (because `thread2a` 194 | // has the write end open) 195 | sys_close(pfd[1]); 196 | char buf[20]; 197 | ssize_t n = sys_read(pfd[0], buf, sizeof(buf)); 198 | assert_eq(n, 0); 199 | 200 | 201 | // test3 202 | message("checking that implicit texit sets status"); 203 | p = sys_fork(); 204 | assert_ge(p, 0); 205 | if (p == 0) { 206 | test3(); 207 | } 208 | status = 0; 209 | ch = sys_waitpid(p, &status); 210 | assert_eq(ch, p); 211 | assert_eq(status, 161); 212 | 213 | 214 | console_printf(CS_SUCCESS "testthread succeeded!\n"); 215 | sys_exit(0); 216 | } 217 | -------------------------------------------------------------------------------- /p-testvfs.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | int r = dprintf(0, "0 "); 6 | assert_gt(r, 0); 7 | 8 | r = dprintf(1, "1 "); 9 | assert_gt(r, 0); 10 | 11 | r = dprintf(2, "2 "); 12 | assert_gt(r, 0); 13 | 14 | r = dprintf(3, "Nope\n"); 15 | assert_eq(r, E_BADF); 16 | 17 | r = dprintf(-1, "Nope\n"); 18 | assert_eq(r, E_BADF); 19 | 20 | r = sys_dup2(2, 3); 21 | if (r != 0) { 22 | assert_eq(r, 3); 23 | } 24 | 25 | r = dprintf(3, "3 "); 26 | assert_gt(r, 0); 27 | 28 | r = sys_close(3); 29 | assert_eq(r, 0); 30 | 31 | r = dprintf(3, "Nope\n"); 32 | assert_eq(r, E_BADF); 33 | 34 | r = sys_dup2(2, 3); 35 | if (r != 0) { 36 | assert_eq(r, 3); 37 | } 38 | 39 | r = dprintf(3, "4 "); 40 | assert_gt(r, 0); 41 | 42 | r = sys_dup2(4, 3); 43 | assert_eq(r, E_BADF); 44 | 45 | r = dprintf(3, "5 "); 46 | assert_gt(r, 0); 47 | 48 | r = sys_dup2(3, 3); 49 | if (r != 0) { 50 | assert_eq(r, 3); 51 | } 52 | 53 | r = dprintf(3, "6 "); 54 | assert_gt(r, 0); 55 | 56 | r = sys_close(0); 57 | assert_eq(r, 0); 58 | 59 | r = sys_close(1); 60 | assert_eq(r, 0); 61 | 62 | r = dprintf(0, "Nope\n"); 63 | assert_eq(r, E_BADF); 64 | 65 | r = sys_close(2); 66 | assert_eq(r, 0); 67 | 68 | r = sys_dup2(3, 2); 69 | if (r != 0) { 70 | assert_eq(r, 2); 71 | } 72 | 73 | r = sys_dup2(2, 0); 74 | assert_eq(r, 0); 75 | 76 | r = dprintf(2, "7 "); 77 | assert_gt(r, 0); 78 | 79 | r = dprintf(3, "8 "); 80 | assert_gt(r, 0); 81 | 82 | r = dprintf(0, "9 "); 83 | assert_gt(r, 0); 84 | 85 | r = sys_dup2(2, 2); 86 | assert_eq(r, 2); 87 | 88 | r = dprintf(2, "10\n"); 89 | assert_gt(r, 0); 90 | 91 | 92 | console_printf("If you see 0-10 in sequence, testvfs succeeded.\n"); 93 | sys_exit(0); 94 | } 95 | -------------------------------------------------------------------------------- /p-testwaitpid.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | static const int order[] = { 5 | 2, 7, 6, 4, 1, 8, 5, 3 6 | }; 7 | 8 | static void make_children(pid_t* children) { 9 | for (size_t i = 0; i != arraysize(order); ++i) { 10 | pid_t p = sys_fork(); 11 | if (p == 0) { 12 | sys_msleep(order[i] * 100); 13 | sys_exit(order[i]); 14 | } 15 | assert_gt(p, 0); 16 | children[i] = p; 17 | } 18 | } 19 | 20 | void process_main() { 21 | console_printf("waitpid(0, W_NOHANG) tests...\n"); 22 | pid_t children[arraysize(order)]; 23 | make_children(children); 24 | for (size_t i = 0; i != arraysize(order); ++i) { 25 | pid_t ch; 26 | int status = 0; 27 | while ((ch = sys_waitpid(0, &status, W_NOHANG)) == E_AGAIN) { 28 | sys_yield(); 29 | } 30 | assert_gt(ch, 0); 31 | 32 | size_t idx = 0; 33 | while (idx != arraysize(order) && children[idx] != ch) { 34 | ++idx; 35 | } 36 | assert(idx < arraysize(order)); 37 | children[idx] = 0; 38 | 39 | console_printf("%d @%lu: exit status %d\n", ch, idx, status); 40 | assert_eq(order[idx], status); 41 | } 42 | assert_eq(sys_waitpid(0, nullptr, W_NOHANG), E_CHILD); 43 | console_printf(CS_GREEN "waitpid(0, W_NOHANG) tests succeeded!\n"); 44 | 45 | 46 | console_printf("waitpid(pid, W_NOHANG) tests...\n"); 47 | make_children(children); 48 | for (size_t i = 0; i != arraysize(order); ++i) { 49 | pid_t ch; 50 | int status = 0; 51 | while ((ch = sys_waitpid(children[i], &status, W_NOHANG)) == E_AGAIN) { 52 | sys_yield(); 53 | } 54 | assert_eq(ch, children[i]); 55 | 56 | console_printf("%d @%lu: exit status %d\n", ch, i, status); 57 | assert_eq(order[i], status); 58 | } 59 | assert_eq(sys_waitpid(0, nullptr, W_NOHANG), E_CHILD); 60 | console_printf(CS_GREEN "waitpid(pid, W_NOHANG) tests succeeded!\n"); 61 | 62 | 63 | console_printf("waitpid(0) blocking tests...\n"); 64 | make_children(children); 65 | for (size_t i = 0; i != arraysize(order); ++i) { 66 | int status = 0; 67 | pid_t ch = sys_waitpid(0, &status); 68 | assert_gt(ch, 0); 69 | 70 | size_t idx = 0; 71 | while (idx != arraysize(order) && children[idx] != ch) { 72 | ++idx; 73 | } 74 | assert(idx < arraysize(order)); 75 | children[idx] = 0; 76 | 77 | console_printf("%d @%lu: exit status %d\n", ch, idx, status); 78 | assert_eq(order[idx], status); 79 | } 80 | assert_eq(sys_waitpid(0), E_CHILD); 81 | console_printf(CS_GREEN "waitpid(0) blocking tests succeeded!\n"); 82 | 83 | 84 | console_printf("waitpid(pid) blocking tests...\n"); 85 | make_children(children); 86 | for (size_t i = 0; i != arraysize(order); ++i) { 87 | int status = 0; 88 | pid_t ch = sys_waitpid(children[i], &status); 89 | assert_eq(ch, children[i]); 90 | 91 | console_printf("%d @%lu: exit status %d\n", ch, i, status); 92 | assert_eq(order[i], status); 93 | } 94 | assert_eq(sys_waitpid(0), E_CHILD); 95 | console_printf(CS_GREEN "waitpid(pid) blocking tests succeeded!\n"); 96 | 97 | 98 | console_printf(CS_SUCCESS "testwaitpid succeeded!\n"); 99 | sys_exit(0); 100 | } 101 | -------------------------------------------------------------------------------- /p-testwritefs.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | printf("Starting testwritefs (assuming clean file system)...\n"); 6 | 7 | // read file 8 | printf("%s:%d: read...\n", __FILE__, __LINE__); 9 | 10 | int f = sys_open("emerson.txt", OF_READ); 11 | assert_gt(f, 2); 12 | 13 | char buf[200]; 14 | memset(buf, 0, sizeof(buf)); 15 | ssize_t n = sys_read(f, buf, 200); 16 | assert_eq(n, 130); 17 | assert_memeq(buf, "When piped a tiny voice hard by,\n" 18 | "Gay and polite, a cheerful cry,\n" 19 | "Chic-chicadeedee", 81); 20 | 21 | sys_close(f); 22 | 23 | 24 | // overwrite start of file 25 | printf("%s:%d: overwrite start...\n", __FILE__, __LINE__); 26 | 27 | f = sys_open("emerson.txt", OF_WRITE); 28 | assert_gt(f, 2); 29 | 30 | n = sys_write(f, "OLEK WAS HERE", 13); 31 | assert_eq(n, 13); 32 | 33 | sys_close(f); 34 | 35 | 36 | f = sys_open("emerson.txt", OF_READ); 37 | assert_gt(f, 2); 38 | 39 | memset(buf, 0, sizeof(buf)); 40 | n = sys_read(f, buf, 200); 41 | assert_eq(n, 130); 42 | assert_memeq(buf, "OLEK WAS HEREtiny voice hard by,\n" 43 | "Gay and polite, a cheerful cry,\n" 44 | "Chic-chicadeedee", 81); 45 | 46 | sys_close(f); 47 | 48 | 49 | // read & write same file 50 | printf("%s:%d: read and write...\n", __FILE__, __LINE__); 51 | 52 | f = sys_open("emerson.txt", OF_READ); 53 | assert_gt(f, 2); 54 | 55 | int wf = sys_open("emerson.txt", OF_WRITE); 56 | assert_gt(wf, 2); 57 | assert_ne(f, wf); 58 | 59 | memset(buf, 0, sizeof(buf)); 60 | n = sys_read(f, buf, 8); 61 | assert_eq(n, 8); 62 | assert_memeq(buf, "OLEK WAS", 8); 63 | 64 | n = sys_write(wf, "CLARE ROJAS WAS HERE", 20); 65 | assert_eq(n, 20); 66 | 67 | memset(buf, 0, sizeof(buf)); 68 | n = sys_read(f, buf, 20); 69 | assert_eq(n, 20); 70 | assert_memeq(buf, "JAS WAS HEREice hard", 20); 71 | 72 | n = sys_write(wf, "!", 1); 73 | assert_eq(n, 1); 74 | 75 | memset(buf, 0, sizeof(buf)); 76 | n = sys_read(f, buf, 4); 77 | assert_eq(n, 4); 78 | assert_memeq(buf, " by,", 4); 79 | 80 | // cannot write fd open only for reading 81 | n = sys_write(f, "!", 1); 82 | assert_eq(n, E_BADF); 83 | 84 | // cannot read fd open only for writing 85 | n = sys_read(wf, buf, 1); 86 | assert_eq(n, E_BADF); 87 | 88 | sys_close(f); 89 | sys_close(wf); 90 | 91 | 92 | // read & write same file with combination flags 93 | printf("%s:%d: read|write...\n", __FILE__, __LINE__); 94 | 95 | f = sys_open("emerson.txt", OF_READ); 96 | assert_gt(f, 2); 97 | 98 | wf = sys_open("emerson.txt", OF_READ | OF_WRITE); 99 | assert_gt(wf, 2); 100 | assert_ne(f, wf); 101 | 102 | memset(buf, 0, sizeof(buf)); 103 | n = sys_read(f, buf, 8); 104 | assert_eq(n, 8); 105 | assert_memeq(buf, "CLARE RO", 8); 106 | 107 | memset(buf, 0, sizeof(buf)); 108 | n = sys_read(wf, buf, 8); 109 | assert_eq(n, 8); 110 | assert_memeq(buf, "CLARE RO", 8); 111 | 112 | n = sys_write(wf, "OT Vegetables", 13); 113 | assert_eq(n, 13); 114 | 115 | memset(buf, 0, sizeof(buf)); 116 | n = sys_read(wf, buf, 7); 117 | assert_eq(n, 7); 118 | assert_memeq(buf, "ce hard", 7); 119 | 120 | memset(buf, 0, sizeof(buf)); 121 | n = sys_read(f, buf, 13); 122 | assert_eq(n, 13); 123 | assert_memeq(buf, "OT Vegetables", 13); 124 | 125 | sys_close(f); 126 | sys_close(wf); 127 | 128 | 129 | // synchronize disk 130 | printf("%s:%d: sync disk...\n", __FILE__, __LINE__); 131 | 132 | int r = sys_sync(2); 133 | assert_ge(r, 0); 134 | 135 | 136 | // read again, this time from disk 137 | printf("%s:%d: read...\n", __FILE__, __LINE__); 138 | 139 | f = sys_open("emerson.txt", OF_READ); 140 | assert_gt(f, 2); 141 | 142 | memset(buf, 0, sizeof(buf)); 143 | n = sys_read(f, buf, 200); 144 | assert_eq(n, 130); 145 | assert_memeq(buf, "CLARE ROOT Vegetablesce hard by,\n" 146 | "Gay and polite, a cheerful cry,\n" 147 | "Chic-chicadeedee", 81); 148 | 149 | sys_close(f); 150 | 151 | 152 | printf(CS_SUCCESS "testwritefs succeeded!\n"); 153 | sys_exit(0); 154 | } 155 | -------------------------------------------------------------------------------- /p-testwritefs3.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | printf("Starting testwritefs3 (assuming clean file system)...\n"); 6 | 7 | // read file 8 | printf("%s:%d: read...\n", __FILE__, __LINE__); 9 | 10 | int f = sys_open("emerson.txt", OF_READ); 11 | assert_gt(f, 2); 12 | 13 | char buf[200]; 14 | memset(buf, 0, sizeof(buf)); 15 | ssize_t n = sys_read(f, buf, 200); 16 | assert_eq(n, 130); 17 | assert_memeq(buf, "When piped a tiny voice hard by,\n" 18 | "Gay and polite, a cheerful cry,\n" 19 | "Chic-chicadeedee", 81); 20 | 21 | sys_close(f); 22 | 23 | 24 | // create file 25 | printf("%s:%d: create...\n", __FILE__, __LINE__); 26 | 27 | f = sys_open("geisel.txt", OF_WRITE); 28 | assert_lt(f, 0); 29 | assert_eq(f, E_NOENT); 30 | 31 | f = sys_open("geisel.txt", OF_WRITE | OF_CREATE); 32 | assert_gt(f, 2); 33 | 34 | n = sys_write(f, "Why, girl, you're insane!\n" 35 | "Elephants don't hatch chickadee eggs!\n", 64); 36 | assert_eq(n, 64); 37 | 38 | sys_close(f); 39 | 40 | 41 | // read back 42 | printf("%s:%d: read created...\n", __FILE__, __LINE__); 43 | 44 | f = sys_open("geisel.txt", OF_READ); 45 | assert_gt(f, 2); 46 | 47 | memset(buf, 0, sizeof(buf)); 48 | n = sys_read(f, buf, 200); 49 | assert_eq(n, 64); 50 | assert_memeq(buf, "Why, girl, you're insane!\n" 51 | "Elephants don't hatch chickadee eggs!\n", 64); 52 | 53 | sys_close(f); 54 | 55 | 56 | // synchronize disk 57 | printf("%s:%d: sync...\n", __FILE__, __LINE__); 58 | 59 | int r = sys_sync(1); 60 | assert_ge(r, 0); 61 | 62 | 63 | // read back 64 | printf("%s:%d: reread...\n", __FILE__, __LINE__); 65 | 66 | f = sys_open("geisel.txt", OF_READ); 67 | assert_gt(f, 2); 68 | 69 | memset(buf, 0, sizeof(buf)); 70 | n = sys_read(f, buf, 200); 71 | assert_eq(n, 64); 72 | assert_memeq(buf, "Why, girl, you're insane!\n" 73 | "Elephants don't hatch chickadee eggs!\n", 64); 74 | 75 | sys_close(f); 76 | 77 | 78 | // read & write same file 79 | f = sys_open("geisel.txt", OF_READ); 80 | assert_gt(f, 2); 81 | 82 | int wf = sys_open("geisel.txt", OF_WRITE); 83 | assert_gt(wf, 2); 84 | assert_ne(wf, f); 85 | 86 | memset(buf, 0, sizeof(buf)); 87 | n = sys_read(f, buf, 4); 88 | assert_eq(n, 4); 89 | assert_memeq(buf, "Why,", 4); 90 | 91 | n = sys_write(wf, "Am I scaring you tonight?", 25); 92 | assert_eq(n, 25); 93 | 94 | memset(buf, 0, sizeof(buf)); 95 | n = sys_read(f, buf, 25); 96 | assert_eq(n, 25); 97 | assert_memeq(buf, " scaring you tonight?\nEle", 25); 98 | 99 | n = sys_write(wf, "!", 1); 100 | assert_eq(n, 1); 101 | 102 | memset(buf, 0, sizeof(buf)); 103 | n = sys_read(f, buf, 5); 104 | assert_eq(n, 5); 105 | assert_memeq(buf, "phant", 5); 106 | 107 | sys_close(f); 108 | sys_close(wf); 109 | 110 | 111 | printf(CS_SUCCESS "testwritefs3 succeeded!\n"); 112 | sys_exit(0); 113 | } 114 | -------------------------------------------------------------------------------- /p-testwritefs4.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | printf("Starting testwritefs4 (assuming clean file system)...\n"); 6 | 7 | // read file 8 | printf("%s:%d: read...\n", __FILE__, __LINE__); 9 | 10 | int f = sys_open("emerson.txt", OF_READ); 11 | assert_gt(f, 2); 12 | 13 | char buf[200]; 14 | memset(buf, 0, sizeof(buf)); 15 | ssize_t n = sys_read(f, buf, 200); 16 | assert_eq(n, 130); 17 | assert_memeq(buf, "When piped a tiny voice hard by,\n" 18 | "Gay and polite, a cheerful cry,\n" 19 | "Chic-chicadeedee", 81); 20 | 21 | sys_close(f); 22 | 23 | 24 | // create file 25 | printf("%s:%d: create...\n", __FILE__, __LINE__); 26 | 27 | f = sys_open("geisel.txt", OF_WRITE | OF_CREATE); 28 | assert_gt(f, 2); 29 | 30 | n = sys_write(f, "Why, girl, you're insane!\n" 31 | "Elephants don't hatch chickadee eggs!\n", 64); 32 | assert_eq(n, 64); 33 | 34 | sys_close(f); 35 | 36 | 37 | // read back 38 | printf("%s:%d: read created...\n", __FILE__, __LINE__); 39 | 40 | f = sys_open("geisel.txt", OF_READ); 41 | assert_gt(f, 2); 42 | 43 | memset(buf, 0, sizeof(buf)); 44 | n = sys_read(f, buf, 200); 45 | assert_eq(n, 64); 46 | assert_memeq(buf, "Why, girl, you're insane!\n" 47 | "Elephants don't hatch chickadee eggs!\n", 64); 48 | 49 | sys_close(f); 50 | 51 | 52 | // remove file 53 | printf("%s:%d: unlink...\n", __FILE__, __LINE__); 54 | 55 | int r = sys_unlink("geisel.txt"); 56 | assert_eq(r, 0); 57 | 58 | f = sys_open("geisel.txt", OF_READ); 59 | assert_eq(f, E_NOENT); 60 | 61 | 62 | // synchronize disk 63 | printf("%s:%d: sync...\n", __FILE__, __LINE__); 64 | 65 | r = sys_sync(1); 66 | assert_ge(r, 0); 67 | 68 | 69 | // read back 70 | printf("%s:%d: reread...\n", __FILE__, __LINE__); 71 | 72 | f = sys_open("geisel.txt", OF_READ); 73 | assert_eq(f, E_NOENT); 74 | 75 | 76 | // unlink while open 77 | printf("%s:%d: unlink while open...\n", __FILE__, __LINE__); 78 | 79 | f = sys_open("dickinson.txt", OF_READ); 80 | assert_gt(f, 2); 81 | 82 | memset(buf, 0, sizeof(buf)); 83 | n = sys_read(f, buf, 34); 84 | assert_eq(n, 34); 85 | assert_memeq(buf, "The Birds begun at Four o'clock -\n", 34); 86 | 87 | r = sys_unlink("dickinson.txt"); 88 | assert_eq(r, 0); 89 | 90 | n = sys_read(f, buf, 24); 91 | assert_eq(n, 24); 92 | assert_memeq(buf, "Their period for Dawn -\n", 24); 93 | 94 | r = sys_unlink("dickinson.txt"); 95 | assert_eq(r, E_NOENT); 96 | 97 | 98 | // recreate 99 | printf("%s:%d: recreate...\n", __FILE__, __LINE__); 100 | 101 | int wf = sys_open("dickinson.txt", OF_READ | OF_WRITE | OF_CREATE); 102 | assert_gt(wf, 2); 103 | assert_ne(f, wf); 104 | 105 | n = sys_read(wf, buf, sizeof(buf)); 106 | assert_eq(n, 0); 107 | 108 | n = sys_write(wf, "A Bird, came down the Walk -\n" 109 | "He did not know I saw -\n", 53); 110 | assert_eq(n, 53); 111 | 112 | size_t sz = sys_lseek(wf, 0, LSEEK_SET); 113 | assert_eq(sz, 0U); 114 | 115 | 116 | // reread 117 | printf("%s:%d: read both...\n", __FILE__, __LINE__); 118 | 119 | n = sys_read(f, buf, 28); 120 | assert_eq(n, 28); 121 | assert_memeq(buf, "A Music numerous as space -\n", 28); 122 | 123 | n = sys_read(wf, buf, 29); 124 | assert_eq(n, 29); 125 | assert_memeq(buf, "A Bird, came down the Walk -\n", 29); 126 | 127 | 128 | // synchronize 129 | printf("%s:%d: sync...\n", __FILE__, __LINE__); 130 | 131 | r = sys_sync(1); 132 | assert_ge(r, 0); 133 | 134 | 135 | // keep reading 136 | printf("%s:%d: continue reading...\n", __FILE__, __LINE__); 137 | 138 | n = sys_read(f, buf, 26); 139 | assert_eq(n, 26); 140 | assert_memeq(buf, "But neighboring as Noon -\n", 26); 141 | 142 | n = sys_read(wf, buf, 24); 143 | assert_eq(n, 24); 144 | assert_memeq(buf, "He did not know I saw -\n", 24); 145 | 146 | 147 | // close (should free file data) 148 | printf("%s:%d: close...\n", __FILE__, __LINE__); 149 | 150 | r = sys_close(f); 151 | assert_ge(r, 0); 152 | 153 | r = sys_close(wf); 154 | assert_ge(r, 0); 155 | 156 | 157 | // synchronize 158 | printf("%s:%d: sync...\n", __FILE__, __LINE__); 159 | 160 | r = sys_sync(1); 161 | assert_ge(r, 0); 162 | 163 | 164 | printf(CS_SUCCESS "testwritefs4 succeeded!\n"); 165 | sys_exit(0); 166 | } 167 | -------------------------------------------------------------------------------- /p-testzombie.cc: -------------------------------------------------------------------------------- 1 | #define CHICKADEE_OPTIONAL_PROCESS 1 2 | #include "u-lib.hh" 3 | 4 | void process_main() { 5 | for (int i = 0; i < 8; ++i) { 6 | console_printf("making zombies, round %d...\n", i); 7 | pid_t child = sys_fork(); 8 | assert_ge(child, 0); 9 | if (child == 0) { 10 | for (int g = 0; g < 10; ++g) { 11 | pid_t grandchild = sys_fork(); 12 | assert_ge(grandchild, 0); 13 | if (grandchild == 0) { 14 | sys_msleep(100 + 10 * g); 15 | sys_exit(0); 16 | } 17 | } 18 | sys_exit(0); 19 | } 20 | 21 | pid_t waited = sys_waitpid(child); 22 | assert_eq(waited, child); 23 | console_printf("zombies hopefully being reaped\n"); 24 | sys_msleep(200); 25 | } 26 | 27 | console_printf(CS_SUCCESS "testzombie succeeded!\n"); 28 | sys_exit(0); 29 | } 30 | -------------------------------------------------------------------------------- /p-true.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | void process_main() { 4 | sys_exit(0); 5 | } 6 | -------------------------------------------------------------------------------- /p-wc.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | static int unparse_counts(char* buf, size_t bufsz, int mode, 4 | const size_t* counts, const char* fname) { 5 | const char* sep = strlen(fname) ? " " : ""; 6 | if (mode < 0) { 7 | return snprintf(buf, bufsz, "%8zu %7zu %7zu%s%s\n", 8 | counts[0], counts[1], counts[2], sep, fname); 9 | } else if (*sep) { 10 | return snprintf(buf, bufsz, "%8zu%s%s\n", 11 | counts[mode], sep, fname); 12 | } else { 13 | return snprintf(buf, bufsz, "%zu\n", counts[mode]); 14 | } 15 | } 16 | 17 | void process_main(int argc, char** argv) { 18 | static char buf[4096]; 19 | 20 | int mode = -1; 21 | int argno = 1; 22 | if (argno < argc) { 23 | if (strcmp(argv[argno], "-c") == 0) { 24 | mode = 0; 25 | ++argno; 26 | } else if (strcmp(argv[argno], "-w") == 0) { 27 | mode = 1; 28 | ++argno; 29 | } else if (strcmp(argv[argno], "-l") == 0) { 30 | mode = 2; 31 | ++argno; 32 | } else if (argv[argno][0] == '-' && argv[argno][1]) { 33 | sys_write(2, "wc: bad argument\n", 17); 34 | sys_exit(1); 35 | } 36 | } 37 | 38 | size_t totals[3] = {0, 0, 0}; 39 | unsigned nfiles = 0; 40 | 41 | while (argno < argc || nfiles == 0) { 42 | int fd = 0; 43 | bool newfd = false; 44 | if (argno < argc && strcmp(argv[argno], "-") != 0) { 45 | fd = sys_open(argv[argno], OF_READ); 46 | if (fd < 0) { 47 | dprintf(2, "%s: error %d\n", argv[argno], fd); 48 | sys_exit(1); 49 | } 50 | newfd = true; 51 | } 52 | 53 | size_t counts[3] = {0, 0, 0}; 54 | bool inword = false; 55 | while (true) { 56 | ssize_t n = sys_read(fd, buf, sizeof(buf)); 57 | if (n == 0 || (n < 0 && n != E_AGAIN)) { 58 | break; 59 | } 60 | for (ssize_t i = 0; i < n; ++i) { 61 | ++counts[0]; 62 | if (!inword && !isspace(buf[i])) { 63 | ++counts[1]; 64 | } 65 | inword = !isspace(buf[i]); 66 | if (buf[i] == '\n') { 67 | ++counts[2]; 68 | } 69 | } 70 | } 71 | 72 | if (newfd) { 73 | sys_close(fd); 74 | } 75 | 76 | int n = unparse_counts(buf, sizeof(buf), mode, counts, 77 | argno < argc ? argv[argno] : ""); 78 | ssize_t w = sys_write(1, buf, n); 79 | assert(w == n); 80 | 81 | for (int i = 0; i < 3; ++i) { 82 | totals[i] += counts[i]; 83 | } 84 | ++nfiles; 85 | ++argno; 86 | } 87 | 88 | if (nfiles > 1) { 89 | int n = unparse_counts(buf, sizeof(buf), mode, totals, "total"); 90 | ssize_t w = sys_write(1, buf, n); 91 | assert(w == n); 92 | } 93 | 94 | sys_exit(0); 95 | } 96 | -------------------------------------------------------------------------------- /p-wcdiskfile.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | static int unparse_counts(char* buf, size_t bufsz, int mode, 4 | const size_t* counts, const char* fname) { 5 | const char* sep = strlen(fname) ? " " : ""; 6 | if (mode < 0) { 7 | return snprintf(buf, bufsz, "%8zu %7zu %7zu%s%s\n", 8 | counts[0], counts[1], counts[2], sep, fname); 9 | } else if (*sep) { 10 | return snprintf(buf, bufsz, "%8zu%s%s\n", 11 | counts[mode], sep, fname); 12 | } else { 13 | return snprintf(buf, bufsz, "%zu\n", counts[mode]); 14 | } 15 | } 16 | 17 | void process_main(int argc, char** argv) { 18 | static char buf[125]; // to catch alignment bugs 19 | 20 | // read `kernel` `testpipe` `kernel` `testpipe` `sh` by default 21 | if (argc <= 1) { 22 | static const char* const default_argv[] = { 23 | "wcdiskfile", "kernel", "testpipe", "kernel", "testpipe", 24 | "sh", nullptr 25 | }; 26 | argc = 6; 27 | argv = const_cast(default_argv); 28 | } 29 | 30 | int mode = -1; 31 | int argno = 1; 32 | if (argno < argc) { 33 | if (strcmp(argv[argno], "-c") == 0) { 34 | mode = 0; 35 | ++argno; 36 | } else if (strcmp(argv[argno], "-w") == 0) { 37 | mode = 1; 38 | ++argno; 39 | } else if (strcmp(argv[argno], "-l") == 0) { 40 | mode = 2; 41 | ++argno; 42 | } else if (argv[argno][0] == '-' && argv[argno][1]) { 43 | sys_write(2, "wc: bad argument\n", 17); 44 | sys_exit(1); 45 | } 46 | } 47 | 48 | size_t totals[3] = {0, 0, 0}; 49 | unsigned nfiles = 0; 50 | 51 | while (argno < argc) { 52 | size_t counts[3] = {0, 0, 0}; 53 | bool inword = false; 54 | size_t off = 0; 55 | while (true) { 56 | ssize_t n = sys_readdiskfile(argv[argno], buf, sizeof(buf), off); 57 | if (n == 0 || (n < 0 && n != E_AGAIN)) { 58 | break; 59 | } 60 | for (ssize_t i = 0; i < n; ++i) { 61 | ++counts[0]; 62 | if (!inword && !isspace(buf[i])) { 63 | ++counts[1]; 64 | } 65 | inword = !isspace(buf[i]); 66 | if (buf[i] == '\n') { 67 | ++counts[2]; 68 | } 69 | } 70 | off += n; 71 | } 72 | 73 | int n = unparse_counts(buf, sizeof(buf), mode, counts, 74 | argno < argc ? argv[argno] : ""); 75 | ssize_t w = sys_write(1, buf, n); 76 | assert(w == n); 77 | 78 | for (int i = 0; i < 3; ++i) { 79 | totals[i] += counts[i]; 80 | } 81 | ++nfiles; 82 | ++argno; 83 | } 84 | 85 | if (nfiles > 1) { 86 | int n = unparse_counts(buf, sizeof(buf), mode, totals, "total"); 87 | ssize_t w = sys_write(1, buf, n); 88 | assert(w == n); 89 | } 90 | 91 | sys_exit(0); 92 | } 93 | -------------------------------------------------------------------------------- /process.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | OUTPUT_ARCH(i386:x86-64) 3 | ENTRY(process_main) 4 | 5 | PHDRS { 6 | text PT_LOAD; 7 | data PT_LOAD; 8 | } 9 | 10 | SECTIONS { 11 | . = 0x100000; 12 | 13 | /* Text segment: instructions and read-only globals */ 14 | .text : { 15 | *(.text.unlikely .text.*_unlikely .text.unlikely.*) 16 | *(.text.exit .text.exit.*) 17 | *(.text.startup .text.startup.*) 18 | *(.text.hot .text.hot.*) 19 | *(.text .stub .text.* .gnu.linkonce.t.*) 20 | } :text 21 | PROVIDE(etext = .); /* Define the `etext` symbol at this location */ 22 | 23 | /* support several signatures for `process_main` */ 24 | process_main = (DEFINED(process_main) ? process_main 25 | : DEFINED(_Z12process_mainiPPc) ? _Z12process_mainiPPc 26 | : _Z12process_mainv); 27 | 28 | .rodata : { 29 | *(.rodata .rodata.* .gnu.linkonce.r.*) 30 | *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) 31 | *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) 32 | } :text 33 | 34 | /* Constructors: these sections support global initialization 35 | functions, such as for global C++ objects with constructors. */ 36 | .init_array : { 37 | PROVIDE(__init_array_start = .); 38 | KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) 39 | SORT_BY_INIT_PRIORITY(.ctors.*))) 40 | KEEP (*(.init_array .ctors)) 41 | PROVIDE(__init_array_end = .); 42 | } :text 43 | .ctors : { 44 | KEEP (*crtbegin.o(.ctors)) 45 | KEEP (*(SORT(.ctors.*))) 46 | KEEP (*(.ctors)) 47 | } :text 48 | 49 | /* Data segment: read/write and zero-initialized globals */ 50 | . = ALIGN(4096); /* Align to a page boundary */ 51 | .data : { 52 | *(.data .data.* .gnu.linkonce.d.*) 53 | } :data 54 | PROVIDE(edata = .); 55 | .bss : { 56 | *(.bss .bss.* .gnu.linkonce.b.*) 57 | } :data 58 | PROVIDE(end = .); 59 | 60 | PROVIDE(console = 0xB8000); 61 | PROVIDE(cursorpos = 0xB8FF8); 62 | PROVIDE(consoletype = 0xB8FFC); 63 | 64 | /DISCARD/ : { *(.eh_frame .note.GNU-stack) } 65 | } 66 | -------------------------------------------------------------------------------- /psets/pset1answers.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 1 Answers 2 | ============================ 3 | Leave your name out of this file. Put collaboration notes and credit in 4 | `pset1collab.md`. 5 | 6 | Answers to written questions 7 | ---------------------------- 8 | 9 | Grading notes 10 | ------------- 11 | -------------------------------------------------------------------------------- /psets/pset1collab.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 1 Collaboration 2 | ================================== 3 | 4 | Collaborators 5 | ------------- 6 | (Other students you worked with) 7 | 8 | Citations 9 | --------- 10 | (Other sources consulted) 11 | -------------------------------------------------------------------------------- /psets/pset2answers.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 2 Answers 2 | ============================ 3 | 4 | Leave your name out of this file. Put collaboration notes and credit in 5 | `pset2collab.md`. 6 | 7 | **Brief, clear answers preferred!** 8 | 9 | 10 | C. Parent processes: Per-process metadata design 11 | ------------------------------------------------ 12 | 13 | 14 | C. Parent processes: Synchronization plan 15 | ----------------------------------------- 16 | 17 | 18 | D. Wait and exit status: Synchronization plan 19 | --------------------------------------------- 20 | 21 | 22 | Other notes 23 | ----------- 24 | 25 | 26 | Grading notes 27 | ------------- 28 | -------------------------------------------------------------------------------- /psets/pset2collab.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 2 Collaboration 2 | ================================== 3 | 4 | Collaborators 5 | ------------- 6 | (Other students you worked with) 7 | 8 | Citations 9 | --------- 10 | (Other sources consulted) 11 | -------------------------------------------------------------------------------- /psets/pset3answers.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 3 Answers 2 | ============================ 3 | Leave your name out of this file. Put collaboration notes and credit in 4 | `pset3collab.md`. 5 | 6 | Answers to written questions 7 | ---------------------------- 8 | 9 | Grading notes 10 | ------------- 11 | -------------------------------------------------------------------------------- /psets/pset3collab.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 3 Collaboration 2 | ================================== 3 | 4 | Collaborators 5 | ------------- 6 | (Other students you worked with) 7 | 8 | Citations 9 | --------- 10 | (Other sources consulted) 11 | -------------------------------------------------------------------------------- /psets/pset3vfs.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 3 VFS Design Document 2 | ======================================== 3 | -------------------------------------------------------------------------------- /psets/pset4answers.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 4 Answers 2 | ============================ 3 | Leave your name out of this file. Put collaboration notes and credit in 4 | `pset4collab.md`. 5 | 6 | Answers to written questions 7 | ---------------------------- 8 | 9 | Grading notes 10 | ------------- 11 | -------------------------------------------------------------------------------- /psets/pset4collab.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 4 Collaboration 2 | ================================== 3 | 4 | Collaborators 5 | ------------- 6 | (Other students you worked with) 7 | 8 | Citations 9 | --------- 10 | (Other sources consulted) 11 | -------------------------------------------------------------------------------- /psets/pset5answers.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 5 Answers 2 | ============================ 3 | Leave your name out of this file. Put collaboration notes and credit in 4 | `pset5collab.md`. 5 | 6 | Answers to written questions 7 | ---------------------------- 8 | 9 | Grading notes 10 | ------------- 11 | -------------------------------------------------------------------------------- /psets/pset5collab.md: -------------------------------------------------------------------------------- 1 | CS 161 Problem Set 5 Collaboration 2 | ================================== 3 | 4 | Collaborators 5 | ------------- 6 | (Other students you worked with) 7 | 8 | Citations 9 | --------- 10 | (Other sources consulted) 11 | -------------------------------------------------------------------------------- /run-docker: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | maindir="" 4 | destdir=chickadee 5 | 6 | fresh= 7 | verbose=false 8 | arch="`arch`" 9 | tag= 10 | platform= 11 | container= 12 | while test "$#" -ne 0; do 13 | if test "$1" = "-f" -o "$1" = "--fresh"; then 14 | fresh=1 15 | shift 16 | elif test "$1" = "-V" -o "$1" = "--verbose"; then 17 | verbose=true 18 | shift 19 | elif test "$1" = "-a" -o "$1" = "--arm" -o "$1" = "--arm64"; then 20 | if test "$arch" = "arm64" -o "$arch" = "aarch64"; then 21 | platform=linux/arm64 22 | shift 23 | else 24 | echo "\`run-docker --arm\` only works on ARM64 hosts" 1>&2 25 | exit 1 26 | fi 27 | elif test "$1" = "-x" -o "$1" = "--x86-64" -o "$1" = "--x86_64" -o "$1" = "--amd64"; then 28 | platform=linux/amd64 29 | shift 30 | elif test "(" "$1" = "-t" -o "$1" = "--tag" ")" -a "$#" -gt 1; then 31 | tag="$2" 32 | shift 2 33 | elif test -z "$container" -a -z "$fresh" -a -n "$1" && expr "$1" : '[0-9a-f][0-9a-f][0-9a-f]*$' >/dev/null 2>&1; then 34 | container="$1" 35 | shift 36 | else 37 | armtext= 38 | if test "$arch" = "arm64" -o "$arch" = "aarch64"; then 39 | armtext=" [-a|--arm] [-x|--x86-64]" 40 | fi 41 | echo "Usage: run-docker [-f|--fresh]$armtext [-V|--verbose] [CONTAINERID]" 1>&2 42 | exit 1 43 | fi 44 | done 45 | if test -z "$platform" -a \( "$arch" = "arm64" -o "$arch" = "aarch64" \); then 46 | platform=linux/arm64 47 | elif test -z "$platform"; then 48 | platform=linux/amd64 49 | fi 50 | if test -z "$tag" -a "$platform" = linux/arm64; then 51 | tag=chickadee:arm64 52 | elif test -z "$tag"; then 53 | tag=chickadee:latest 54 | fi 55 | 56 | 57 | vexec () { 58 | if $verbose; then 59 | echo "$@" 60 | fi 61 | exec "$@" 62 | } 63 | 64 | if stat --format %i / >/dev/null 2>&1; then 65 | statformatarg="--format" 66 | else 67 | statformatarg="-f" 68 | fi 69 | myfileid=`stat $statformatarg %d:%i "${BASH_SOURCE[0]}" 2>/dev/null` 70 | 71 | ssharg= 72 | sshenvarg= 73 | if test -n "$SSH_AUTH_SOCK" -a "`uname`" = Darwin; then 74 | ssharg=" --mount type=bind,src=/run/host-services/ssh-auth.sock,target=/run/host-services/ssh-auth.sock" 75 | sshenvarg=" -e SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock" 76 | fi 77 | 78 | dir="`pwd`" 79 | subdir="" 80 | while test "$dir" != / -a "$dir" != ""; do 81 | thisfileid=`stat $statformatarg %d:%i "$dir"/run-docker 2>/dev/null` 82 | if test -n "$thisfileid" -a "$thisfileid" = "$myfileid"; then 83 | maindir="$dir" 84 | break 85 | fi 86 | subdir="/`basename "$dir"`$subdir" 87 | dir="`dirname "$dir"`" 88 | done 89 | 90 | if test -z "$maindir" && expr "${BASH_SOURCE[0]}" : / >/dev/null 2>&1; then 91 | maindir="`dirname "${BASH_SOURCE[0]}"`" 92 | subdir="" 93 | fi 94 | 95 | existing_image="" 96 | if test -n "$container"; then 97 | existing_image="`docker ps -f "id=$container" --no-trunc --format "{{.CreatedAt}},{{.ID}}" | sort -r | head -n 1`" 98 | if test -z "$existing_image"; then 99 | echo "* Container $container not found" 1>&2 100 | echo "- To start a new container, try \`cs61-run-docker -f\`" 1>&2 101 | echo "- To list executing containers, try \`docker ps\`" 1>&2 102 | exit 1 103 | elif test "`echo "$existing_image" | wc -l`" -gt 1; then 104 | echo "* Container $container ambiguous" 1>&2 105 | echo "- To start a new container, try \`cs61-run-docker -f\`" 1>&2 106 | echo "- To list executing containers, try \`docker ps\`" 1>&2 107 | exit 1 108 | fi 109 | elif test -n "$maindir" -a -z "$fresh"; then 110 | existing_image="`docker ps -f status=running -f ancestor=$tag -f volume=/host_mnt"$maindir" --no-trunc --format "{{.CreatedAt}},{{.ID}}" | sort -r | head -n 1`" 111 | fi 112 | if test -n "$existing_image"; then 113 | created_at="`echo $existing_image | sed 's/,.*//'`" 114 | image="`echo $existing_image | sed 's/^.*,//'`" 115 | image12="`echo $image | head -c 12`" 116 | echo "* Using running container $image12, created $created_at" 1>&2 117 | echo "- To start a new container, exit then \`run-docker -f\`" 1>&2 118 | echo "- To kill this container, exit then \`docker kill $image12\`" 1>&2 119 | vexec docker exec -it$sshenvarg $image /bin/bash 120 | fi 121 | 122 | netarg= 123 | if test `uname` = Darwin; then 124 | if ! netstat -n -a -p tcp | grep '\.6169[ ].*LISTEN' >/dev/null; then 125 | netarg="$netarg "'--expose=6169/tcp -p 6169:6169/tcp' 126 | fi 127 | if ! netstat -n -a -p tcp | grep '\.12949[ ].*LISTEN' >/dev/null; then 128 | netarg="$netarg "'--expose=12949/tcp -p 12949:12949/tcp' 129 | fi 130 | elif test -x /bin/netstat; then 131 | if ! netstat -n -a -p tcp | grep '\.6169[ ].*LISTEN' >/dev/null; then 132 | netarg="$netarg "'--expose=6169/tcp -p 6169:6169/tcp' 133 | fi 134 | if ! netstat -n -l -t | grep ':12949[ ]' >/dev/null; then 135 | netarg="$netarg "'--expose=12949/tcp -p 12949:12949/tcp' 136 | fi 137 | fi 138 | 139 | if test -n "$maindir"; then 140 | vexec docker run -it --platform $platform --rm --privileged --cap-add=SYS_PTRACE --cap-add=NET_ADMIN --security-opt seccomp=unconfined -v "$maindir":/home/cs61-user/$destdir$ssharg -w "/home/cs61-user/$destdir$subdir" $netarg$sshenvarg $tag 141 | else 142 | vexec docker run -it --platform $platform --rm --privileged --cap-add=SYS_PTRACE --cap-add=NET_ADMIN --security-opt seccomp=unconfined $netarg$sshenvarg $tag 143 | fi 144 | -------------------------------------------------------------------------------- /types.h: -------------------------------------------------------------------------------- 1 | #ifndef CHICKADEE_TYPES_H 2 | #define CHICKADEE_TYPES_H 3 | 4 | // types.h 5 | // 6 | // Types and macros useful in both kernel and applications. 7 | 8 | #if __cplusplus 9 | #define NULL nullptr 10 | #else 11 | #define NULL ((void*) 0) 12 | #endif 13 | 14 | typedef __builtin_va_list va_list; 15 | #define va_start(val, last) __builtin_va_start(val, last) 16 | #define va_arg(val, type) __builtin_va_arg(val, type) 17 | #define va_end(val) __builtin_va_end(val) 18 | 19 | typedef signed char int8_t; 20 | typedef unsigned char uint8_t; 21 | typedef short int16_t; 22 | typedef unsigned short uint16_t; 23 | typedef int int32_t; 24 | typedef unsigned int uint32_t; 25 | typedef long int64_t; 26 | typedef unsigned long uint64_t; 27 | 28 | typedef long intptr_t; // ints big enough to hold pointers 29 | typedef unsigned long uintptr_t; 30 | 31 | #define SIZE_MAX ((unsigned long) -1) 32 | #define SSIZE_MAX ((long) 0x7FFFFFFFFFFFFFFF) 33 | typedef unsigned long size_t; // sizes and offsets 34 | typedef long ssize_t; 35 | typedef long off_t; 36 | 37 | typedef int pid_t; // process IDs 38 | 39 | #if __cplusplus 40 | #define NO_COPY_OR_ASSIGN(t) \ 41 | t(const t&) = delete; t(t&&) = delete; \ 42 | t& operator=(const t&) = delete; \ 43 | t& operator=(t&&) = delete; 44 | 45 | template 46 | inline T* mem_container(U* ptr, U (T::* mem_ptr)) { 47 | alignas(T) char space[sizeof(T)] = {}; 48 | T* dummy = reinterpret_cast(space); 49 | uintptr_t offset = reinterpret_cast(&(dummy->*mem_ptr)) 50 | - reinterpret_cast(dummy); 51 | return reinterpret_cast(reinterpret_cast(ptr) - offset); 52 | } 53 | #endif 54 | 55 | #define __section(x) __attribute__((section(x))) 56 | #define __no_asan __attribute__((no_sanitize_address)) 57 | #define __noinline __attribute__((noinline)) 58 | 59 | #if __cplusplus 60 | #define __always_inline inline __attribute__((always_inline)) 61 | #else 62 | #define __always_inline static inline __attribute__((always_inline)) 63 | #endif 64 | 65 | #if __cplusplus 66 | #define __constexpr constexpr 67 | #else 68 | #define __constexpr 69 | #endif 70 | 71 | #endif /* !CHICKADEE_TYPES_H */ 72 | -------------------------------------------------------------------------------- /u-lib.cc: -------------------------------------------------------------------------------- 1 | #include "u-lib.hh" 2 | 3 | // dprintf 4 | // Construct a string from `format` and pass it to `sys_write(fd)`. 5 | // Returns the number of characters printed, or E_2BIG if the string 6 | // could not be constructed. 7 | 8 | int dprintf(int fd, const char* format, ...) { 9 | char buf[513]; 10 | va_list val; 11 | va_start(val, format); 12 | size_t n = vsnprintf(buf, sizeof(buf), format, val); 13 | if (n < sizeof(buf)) { 14 | return sys_write(fd, buf, n); 15 | } else { 16 | return E_2BIG; 17 | } 18 | } 19 | 20 | 21 | // printf 22 | // Like `dprintf(1, ...)`. 23 | 24 | int printf(const char* format, ...) { 25 | char buf[513]; 26 | va_list val; 27 | va_start(val, format); 28 | size_t n = vsnprintf(buf, sizeof(buf), format, val); 29 | if (n < sizeof(buf)) { 30 | return sys_write(1, buf, n); 31 | } else { 32 | return E_2BIG; 33 | } 34 | } 35 | 36 | 37 | // panic, assert_fail 38 | // Call the SYSCALL_PANIC system call so the kernel loops until Control-C. 39 | 40 | void panic(const char* format, ...) { 41 | va_list val; 42 | va_start(val, format); 43 | char buf[160]; 44 | memcpy(buf, "PANIC: ", 7); 45 | int len = vsnprintf(&buf[7], sizeof(buf) - 7, format, val) + 7; 46 | va_end(val); 47 | if (len > 0 && buf[len - 1] != '\n') { 48 | strcpy(buf + len - (len == (int) sizeof(buf) - 1), "\n"); 49 | } 50 | int cpos = consoletype == CONSOLE_NORMAL ? -1 : CPOS(23, 0); 51 | (void) console_printf(cpos, CS_ERROR "%s", buf); 52 | sys_panic(nullptr); 53 | } 54 | 55 | void error_vprintf(int cpos, const char* format, va_list val) { 56 | console_vprintf(cpos, format, val); 57 | } 58 | 59 | void assert_fail(const char* file, int line, const char* msg, 60 | const char* description) { 61 | if (consoletype != CONSOLE_NORMAL) { 62 | cursorpos = CPOS(23, 0); 63 | } 64 | if (description) { 65 | error_printf("%s:%d: %s\n", file, line, description); 66 | } 67 | error_printf("%s:%d: user assertion '%s' failed\n", file, line, msg); 68 | sys_panic(nullptr); 69 | } 70 | 71 | 72 | // sys_clone 73 | // Create a new thread. 74 | 75 | pid_t sys_clone(int (*function)(void*), void* arg, char* stack_top) { 76 | // Your code here 77 | return E_NOSYS; 78 | } 79 | --------------------------------------------------------------------------------