diff --git a/GNUmakefile b/GNUmakefile index b2bff2c..7157f5c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -3,7 +3,8 @@ all: $(QEMUIMAGEFILES) # Place local configuration options, such as `CC=clang`, in # `config.mk` so you don't have to list them every time. --include config.mk +CONFIG ?= config.mk +-include $(CONFIG) # `$(V)` controls whether the makefiles print verbose commands (the shell # commands run by Make) or brief commands (like `COMPILE`). @@ -33,6 +34,9 @@ QEMUOPT = -net none -parallel $(LOG) -smp $(NCPU) ifeq ($(D),1) QEMUOPT += -d int,cpu_reset,guest_errors -no-reboot endif +ifneq ($(NOGDB),1) +QEMUGDB ?= -gdb tcp::12949 +endif # Sets of object files @@ -47,7 +51,7 @@ KERNEL_OBJS = $(OBJDIR)/k-exception.ko \ $(OBJDIR)/k-ahci.ko $(OBJDIR)/k-chkfs.ko $(OBJDIR)/k-chkfsiter.ko \ $(OBJDIR)/k-memviewer.ko $(OBJDIR)/lib.ko $(OBJDIR)/k-initfs.ko -PROCESSES = $(patsubst %.cc,%,$(wildcard p-*.cc)) +PROCESSES ?= $(patsubst %.cc,%,$(wildcard p-*.cc)) PROCESS_LIB_OBJS = $(OBJDIR)/lib.uo $(OBJDIR)/u-lib.uo $(OBJDIR)/crc32c.uo @@ -107,7 +111,7 @@ $(OBJDIR)/bootentry.o: $(OBJDIR)/%.o: \ $(OBJDIR)/%.uo: %.cc $(BUILDSTAMPS) $(call cxxcompile,$(CXXFLAGS) -O1 -DCHICKADEE_PROCESS -c $< -o $@,COMPILE $<) -$(OBJDIR)/%.uo: %.S $(OBJDIR)/u-asm.h $(KERNELBUILDSTAMPS) +$(OBJDIR)/%.uo: %.S $(OBJDIR)/u-asm.h $(BUILDSTAMPS) $(call assemble,-O2 -c $< -o $@,ASSEMBLE $<) @@ -150,7 +154,7 @@ $(OBJDIR)/kernel: $(OBJDIR)/kernel.full $(OBJDIR)/mkchickadeesymtab $(OBJDIR)/%: $(OBJDIR)/%.full $(call run,$(OBJDUMP) -C -S -j .text -j .ctors $< >$@.asm) $(call run,$(NM) -n $< >$@.sym) - $(call run,$(OBJCOPY) -j .text -j .rodata -j .data -j .bss -j .ctors -j .init_array $<,STRIP,$@) + $(call run,$(QUIETOBJCOPY) -j .text -j .rodata -j .data -j .bss -j .ctors -j .init_array $<,STRIP,$@) $(OBJDIR)/bootsector: $(BOOT_OBJS) boot.ld $(call link,-T boot.ld -o $@.full $(BOOT_OBJS),LINK) @@ -174,7 +178,7 @@ $(OBJDIR)/%.o: build/%.cc $(BUILDSTAMPS) $(call run,$(HOSTCXX) $(CPPFLAGS) $(HOSTCXXFLAGS) $(DEPCFLAGS) -c -o $@,HOSTCOMPILE,$<) $(OBJDIR)/mkchickadeefs: build/mkchickadeefs.cc $(BUILDSTAMPS) - $(call run,$(HOSTCXX) $(CPPFLAGS) $(HOSTCXXFLAGS) $(DEPCFLAGS) -o $@,HOSTCOMPILE,$<) + $(call run,$(HOSTCXX) $(CPPFLAGS) $(HOSTCXXFLAGS) $(DEPCFLAGS) -g -o $@,HOSTCOMPILE,$<) CHICKADEEFSCK_OBJS = $(OBJDIR)/chickadeefsck.o \ $(OBJDIR)/journalreplayer.o \ @@ -212,12 +216,12 @@ QEMUIMG = -M q35 \ run: run-$(QEMUDISPLAY) @: -run-graphic: $(QEMUIMAGEFILES) check-qemu - @echo '* Run `gdb -x build/chickadee.gdb` to connect gdb to qemu.' 1>&2 - $(call run,$(QEMU_PRELOAD) $(QEMU) $(QEMUOPT) -gdb tcp::12949 $(QEMUIMG),QEMU $<) -run-console: $(QEMUIMAGEFILES) check-qemu-console - @echo '* Run `gdb -x build/chickadee.gdb` to connect gdb to qemu.' 1>&2 - $(call run,$(QEMU) $(QEMUOPT) -curses -gdb tcp::12949 $(QEMUIMG),QEMU $<) +run-gdb-report: + @if test "$(QEMUGDB)" = "-gdb tcp::12949"; then echo '* Run `gdb -x build/weensyos.gdb` to connect gdb to qemu.' 1>&2; fi +run-graphic: $(QEMUIMAGEFILES) check-qemu run-gdb-report + $(call run,$(QEMU_PRELOAD) $(QEMU) $(QEMUOPT) $(QEMUGDB) $(QEMUIMG),QEMU $<) +run-console: $(QEMUIMAGEFILES) check-qemu-console run-gdb-report + $(call run,$(QEMU) $(QEMUOPT) -curses $(QEMUGDB) $(QEMUIMG),QEMU $<) run-monitor: $(QEMUIMAGEFILES) check-qemu $(call run,$(QEMU_PRELOAD) $(QEMU) $(QEMUOPT) -monitor stdio $(QEMUIMG),QEMU $<) run-gdb: run-gdb-$(QEMUDISPLAY) diff --git a/README.md b/README.md index 4cf9b83..8767891 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,16 @@ Quickstart: `make run` or `make run-PROGRAM` Make targets ------------ -Run `make NCPU=N run` to run with `N` virtual CPUs (default is 2). +`make NCPU=N run` will run the OS with `N` virtual CPUs (default is 2). Close +the QEMU window, or type `q` inside it, to exit the OS. -Run `make SAN=1 run` to run with sanitizers. +`make run-console` will run the OS in the console window. -Normally Chickadee’s debug log is written to `log.txt`. Run `make LOG=stdio -run` to redirect the debug log to the standard output, or `make -LOG=file:FILENAME run` to redirect it to `FILENAME`. +`make SAN=1 run` to run with sanitizers enabled. + +Normally Chickadee’s debug log is written to `log.txt`. `make LOG=stdio run` +will redirect the debug log to the standard output, and `make +LOG=file:FILENAME run` will redirect it to `FILENAME`. Run `make D=1 run` to ask QEMU to print verbose information about interrupts and CPU resets to the standard error. This setting will also cause QEMU to @@ -29,6 +32,36 @@ default is `alloc`. Troubleshooting --------------- +There are several ways to kill a recalcitrant QEMU (for instance, if your +OS has become unresponsive). + +* If QEMU is running in its own graphical window, then close the window. This + will kill the embedded OS. + +* If QEMU is running in a terminal window (in Docker, for instance), then + press `Alt-2`. This will bring up the QEMU Monitor, which looks like this: + + ``` + compat_monitor0 console + QEMU 4.2.0 monitor - type 'help' for more information + (qemu) + ``` + + Type `quit` and hit Return to kill the embedded OS and return to your + shell. If this leaves the terminal looking funny, enter the `reset` shell + command to restore it. + + If `Alt-2` does not work, you may need to configure your terminal to + properly send the Alt key. For instance, on Mac OS X’s Terminal, go to + Terminal > Preferences > Keyboard and select “Use Option as Meta key”. You + can also configure a special keyboard shortcut that sends the `Escape 2` + sequence. + +Run `make run-gdb` to start up the OS with support for GDB debugging. This +will start the OS, but not GDB. You must run `gdb -x build/weensyos.gdb` to +connect to the running emulator; when GDB connects, it will stop the OS and +wait for instructions. + If you experience runtime errors involving `obj/libqemu-nograb.so.1`, put `QEMU_PRELOAD_LIBRARY=` in `config.mk`. This disables a shim we use that prevents QEMU from grabbing the mouse. @@ -41,7 +74,7 @@ Source files | File | Description | | --------------- | ---------------------------- | | `types.h` | Type definitions | -| `lib.hh/cc` | Chickadee C library | +| `lib.hh/cc` | C library | | `x86-64.h` | x86-64 hardware definitions | | `elf.h` | ELF64 structures | @@ -103,11 +136,11 @@ The main output of the build process is a disk image, could conceivably boot on real hardware! The build process also produces other files that can be useful to examine. -| File | Description | -| ---------------------------- | ------------------------------------ | -| `obj/kernel.asm` | Kernel assembly (with addresses) | -| `obj/kernel.sym` | Kernel defined symbols | -| `obj/p-allocator.asm`, `sym` | Same for process binaries | +| File | Description | +| -------------------------- | ------------------------------------ | +| `obj/kernel.asm` | Kernel assembly (with addresses) | +| `obj/kernel.sym` | Kernel defined symbols | +| `obj/p-PROCESS.asm`, `sym` | Same for process binaries | -[CS 161]: https://read.seas.harvard.edu/cs161/2020/ +[CS 161]: https://read.seas.harvard.edu/cs161/2021/ [triple fault]: https://en.wikipedia.org/wiki/Triple_fault diff --git a/bootentry.S b/bootentry.S index cba6cbb..1273459 100644 --- a/bootentry.S +++ b/bootentry.S @@ -59,9 +59,9 @@ init_boot_pagetable: rep stosl # set up boot page table - # 0x1000: L1 page table; entries 0, 256, and 511 point to: - # 0x2000: L2 page table; entries 0 and 510 map 1st 1GB of physmem - # This weird setup is the minimal page table that maps all of + # 0x1000: L4 page table; entries 0, 256, and 511 point to: + # 0x2000: L3 page table; entries 0 and 510 map 1st 1GB of physmem + # This is the minimal page table that maps all of # low-canonical, high-canonical, and kernel-text addresses to # the first 1GB of physical memory. movl $BOOT_PAGETABLE, %edi diff --git a/build/quietobjcopy.sh b/build/quietobjcopy.sh new file mode 100644 index 0000000..e579489 --- /dev/null +++ b/build/quietobjcopy.sh @@ -0,0 +1,11 @@ +#! /bin/sh + +onexit () { + test -n "$tmp" && rm -f "$tmp" +} +trap onexit 0 +tmp=`mktemp /tmp/objcopy.XXXXXX` +"$@" 2> $tmp +v=$? +cat $tmp | grep -v "empty loadable segment" 1>&2 +exit $v diff --git a/build/rules.mk b/build/rules.mk index c0dfd8c..23d44d5 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -60,6 +60,8 @@ LDFLAGS := $(LDFLAGS) -Os --gc-sections -z max-page-size=0x1000 \ -static -nostdlib -nostartfiles LDFLAGS += $(shell $(LD) -m elf_x86_64 --help >/dev/null 2>&1 && echo -m elf_x86_64) +QUIETOBJCOPY = sh build/quietobjcopy.sh $(OBJCOPY) + # Dependencies DEPSDIR := .deps @@ -154,7 +156,7 @@ always: # These targets don't correspond to files .PHONY: all always clean realclean distclean cleanfs fsck \ run run-graphic run-console run-monitor \ - run-gdb run-gdb-graphic run-gdb-console \ + run-gdb run-gdb-graphic run-gdb-console run-gdb-report \ check-qemu-console check-qemu kill \ run-% run-graphic-% run-console-% run-monitor-% \ run-gdb-% run-gdb-graphic-% run-gdb-console-% diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..dbc5f16 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,102 @@ +FROM ubuntu:focal + +# set environment variables for tzdata +ARG TZ=America/New_York +ENV TZ ${TZ} + +# include manual pages and documentation +RUN DEBIAN_FRONTEND=noninteractive apt-get update && yes | unminimize + +# install GCC-related packages +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \ + binutils-doc \ + cpp-doc \ + gcc-doc \ + g++ \ + g++-multilib \ + gdb \ + gdb-doc \ + glibc-doc \ + libblas-dev \ + liblapack-dev \ + liblapack-doc \ + libstdc++-10-doc \ + make \ + make-doc + +# install clang-related packages +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \ + clang \ + clang-10-doc \ + lldb + +# install qemu for pset 4 (sadly, this pulls in a lot of crap) +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \ + qemu-system-x86 + +# install programs used for system exploration +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \ + blktrace \ + linux-tools-generic \ + strace \ + tcpdump + +# install interactive programs (emacs, vim, nano, man, sudo, etc.) +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \ + bc \ + curl \ + dc \ + emacs-nox \ + git \ + git-doc \ + man \ + micro \ + nano \ + sudo \ + vim \ + wget + +# set up libraries +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \ + libreadline-dev \ + locales \ + wamerican + +# install programs used for networking +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \ + dnsutils \ + inetutils-ping \ + net-tools \ + netcat \ + telnet \ + traceroute + +# set up default locale +RUN locale-gen en_US.UTF-8 +ENV LANG en_US.UTF-8 + +# remove unneeded .deb files +RUN rm -r /var/lib/apt/lists/* + +# set up passwordless sudo for user cs61-user +RUN useradd -m -s /bin/bash cs61-user && \ + echo "cs61-user ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/cs61-init + +# create binary reporting version of dockerfile +RUN (echo '#\!/bin/sh'; echo 'echo 5') > /usr/bin/cs61-docker-version; chmod ugo+rx,u+w,go-w /usr/bin/cs61-docker-version + +# git build arguments +ARG USER=CS61\ User +ARG EMAIL=nobody@example.com + +# configure your environment +USER cs61-user +RUN git config --global user.name "${USER}" && \ + git config --global user.email "${EMAIL}" && \ + (echo "(custom-set-variables"; echo " '(c-basic-offset 4)"; echo " '(indent-tabs-mode nil))") > ~/.emacs && \ + (echo "set expandtab"; echo "set shiftwidth=4"; echo "set softtabstop=4") > ~/.vimrc && \ + echo "add-auto-load-safe-path ~" > ~/.gdbinit + +WORKDIR /home/cs61-user + +# Initial version of this Dockerfile by Todd Morrill, CS 61 DCE student diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..a954715 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,119 @@ +CS 61/161 Docker +================ + +> **tl;dr**: +> * `docker build -t cs61:latest -f Dockerfile .` to build a Docker image +> * `docker system prune -a` to remove old Docker images + +The [Docker][] container-based virtualization service lets you run a +minimal CS 161 environment, including a virtual Linux host, on a Mac +OS X or Windows host, without the overhead of a full virtual machine +solution like [VMware Workstation][], [VMware Fusion][], or +[VirtualBox][]. + +It should be possible to do *all* CS 161 problem sets on CS 61 Docker. +(However, you may prefer to set up a local environent.) + +Advantages of Docker: + +* Docker can start and stop virtual machines incredibly quickly. +* Docker-based virtual machines are leaner and take less space on your + machine. +* With Docker, you can easily *edit* your code in your home + *environment, but compile and run* it on a Linux host. + +Disadvantages of Docker: + +* Docker does not offer a full graphical environment. You will need to + run QEMU exclusively in the terminal. +* Docker technology is less user-friendly than virtual machines. + You’ll have to type weird commands. +* It will run slower. + + +## Preparing CS 161 Docker + +To prepare to build your Docker environment: + +1. Download and install [Docker][]. + +2. Clone a copy of the [chickadee repository][]. + +3. Change into the `chickadee/docker` directory. + +To build your Docker environment, run this command. It will take a couple +minutes. You’ll want to re-run this command every time the Docker image +changes, but later runs should be much faster since they’ll take advantage of +your previous work. + +```shellsession +$ docker build -t cs61:latest -f Dockerfile . +``` + +## Running Docker by script + +The Chickadee repository contains a `run-docker` script that +provides good arguments and boots Docker into a view of the current +directory. + +For example: + +```shellsession +$ cd ~/chickadee +$ ./run-docker +cs61-user@a47f05ea5085:~/chickadee$ echo Hello, Linux +Hello, Linux +cs61-user@a47f05ea5085:~/chickadee$ exit +exit +$ +``` + +The script plonks you into a virtual machine! A prompt like +`cs61-user@a47f05ea5085:~$` means that your terminal is connected to the VM. +You can execute any commands you want. To escape from the VM, type Control-D +or run the `exit` command. + +The script assumes your Docker container is named `cs61:latest`, as it +was above. + + +### Running Docker by hand + +If you don’t want to use the script, use a command like the following. + +```shellsession +$ docker run -it --rm -v ~/chickadee:/home/cs61-user/chickadee cs61:latest +``` + +Explanation: + +* `docker run` tells Docker to start a new virtual machine. +* `-it` says Docker should run interactively (`-i`) using a terminal (`-t`). +* `--rm` says Docker should remove the virtual machine when it is done. +* `-v LOCALDIR:LINUXDUR` says Docker should share a directory between your + host and the Docker virtual machine. Here, I’ve asked for the host’s + `~/chickadee` directory to be mapped inside the virtual machine onto the + `/home/cs61-user/chickadee` directory, which is the virtual machine + user’s `~/chickadee` directory. +* `cs61:latest` names the Docker image to run (namely, the one you built). + +Here’s an example session: + +```shellsession +$ docker run -it --rm -v ~/chickadee:/home/cs61-user/chickadee cs61:latest +cs61-user@a15e6c4c8dbe:~$ ls +cs61-lectures +cs61-user@a15e6c4c8dbe:~$ echo "Hello, world" +Hello, world +cs61-user@a15e6c4c8dbe:~$ cs61-docker-version +3 +cs61-user@a15e6c4c8dbe:~$ exit +exit +$ +``` + +[Docker]: https://docker.com/ +[VMware Workstation]: https://www.vmware.com/products/workstation-player.html +[VMware Fusion]: https://www.vmware.com/products/fusion.html +[VirtualBox]: https://www.virtualbox.org/ +[Chickadee repository]: https://github.com/cs161/chickadee/ diff --git a/k-apic.hh b/k-apic.hh index 6ffa2f7..7777fea 100644 --- a/k-apic.hh +++ b/k-apic.hh @@ -136,7 +136,7 @@ class ioapicstate { inline lapicstate& lapicstate::get() { - return *reinterpret_cast(pa2ka(lapic_pa)); + return *pa2kptr(lapic_pa); } inline uint32_t lapicstate::id() const { return read(reg_id) >> 24; @@ -168,7 +168,7 @@ inline void lapicstate::write(int reg, uint32_t v) { } inline ioapicstate& ioapicstate::get() { - return *reinterpret_cast(pa2ka(ioapic_pa)); + return *pa2kptr(ioapic_pa); } inline uint32_t ioapicstate::id() const { return read(reg_id) >> 24; diff --git a/k-chkfsiter.hh b/k-chkfsiter.hh index e84d9fa..b0c9817 100644 --- a/k-chkfsiter.hh +++ b/k-chkfsiter.hh @@ -8,21 +8,21 @@ class chkfs_fileiter { static constexpr size_t blocksize = chkfs::blocksize; - // initialize an iterator for `ino` at file offset `off` + // Initialize an iterator for `ino` at file offset `off`. // The caller must have a reference on `ino`. chkfs_fileiter(chkfs::inode* ino, off_t off = 0); NO_COPY_OR_ASSIGN(chkfs_fileiter); ~chkfs_fileiter(); - // return the inode + // Return the inode inline chkfs::inode* inode() const; - // return the current file offset + // Return the current file offset inline off_t offset() const; - // return true iff the offset is within the file (i.e., in some extent) + // Return true iff the offset is within the file (i.e., in some extent) inline bool active() const; - // return true iff the offset points at data - inline bool present() const; + // Return true iff the offset does not point at data + inline bool empty() const; // Return the block number corresponding to the current file offset. // Returns 0 if there is no block stored for the current offset. @@ -30,15 +30,15 @@ class chkfs_fileiter { // Return a buffer cache entry containing the current file offset’s data. // Returns nullptr if there is no block stored for the current offset. inline bcentry* get_disk_entry() const; - // return the file offset relative to the current block + // Return the file offset relative to the current block inline unsigned block_relative_offset() const; // Move the iterator to file offset `off`. Returns `*this`. chkfs_fileiter& find(off_t off); - // Like `find(offset() + delta)`. + // Like `find(offset() + delta)` inline chkfs_fileiter& operator+=(ssize_t delta); - // Like `find(offset() - delta)`. + // Like `find(offset() - delta)` inline chkfs_fileiter& operator-=(ssize_t delta); @@ -104,11 +104,11 @@ inline bool chkfs_fileiter::active() const { inline unsigned chkfs_fileiter::block_relative_offset() const { return off_ % blocksize; } -inline bool chkfs_fileiter::present() const { - return eptr_ && eptr_->first != 0; +inline bool chkfs_fileiter::empty() const { + return !eptr_ || eptr_->first == 0; } inline auto chkfs_fileiter::blocknum() const -> blocknum_t { - if (eptr_ && eptr_->first != 0) { + if (!empty()) { return eptr_->first + (off_ - eoff_) / blocksize; } else { return 0; diff --git a/k-devices.cc b/k-devices.cc index 22bed84..b4d3ec5 100644 --- a/k-devices.cc +++ b/k-devices.cc @@ -27,7 +27,7 @@ #define KEY_NUMLOCK 0xFE #define KEY_SCROLLLOCK 0xFF -#define CKEY(cn) 0x80 + cn +#define CKEY(cn) (0x80 + cn) static const uint8_t keymap[256] = { /*0x00*/ 0, 033, CKEY(0), CKEY(1), CKEY(2), CKEY(3), CKEY(4), CKEY(5), @@ -122,10 +122,6 @@ int keyboard_readc() { // the global `keyboardstate` singleton keyboardstate keyboardstate::kbd; -keyboardstate::keyboardstate() - : pos_(0), len_(0), eol_(0), state_(boot) { -} - void keyboardstate::handle_interrupt() { auto irqs = lock_.lock(); @@ -209,22 +205,39 @@ void keyboardstate::consume(size_t n) { consolestate consolestate::console; -// console_show_cursor() +// consolestate::cursor() // Displays the console cursor at the current `cursorpos` position. -void console_show_cursor() { - static std::atomic displayed_cpos = -1; - static spinlock cursor_lock; +void consolestate::cursor() { int cpos = cursorpos; if (cpos >= 0 && cpos < CONSOLE_ROWS * CONSOLE_COLUMNS - && displayed_cpos.load(std::memory_order_relaxed) != cpos) { - spinlock_guard guard(cursor_lock); + && cursor_show_.load(std::memory_order_relaxed) + && displayed_cpos_.load(std::memory_order_relaxed) != cpos) { + spinlock_guard guard(cursor_lock_); outb(0x3D4, 15); outb(0x3D5, cpos % 256); outb(0x3D4, 14); outb(0x3D5, cpos / 256); - displayed_cpos = cpos; + displayed_cpos_ = cpos; + } +} + + +// consolestate::cursor(show) +// Enables or disables the cursor depending on `show`. + +void consolestate::cursor(bool show) { + { + spinlock_guard guard(cursor_lock_); + cursor_show_.store(show, std::memory_order_relaxed); + outb(0x3D4, 10); + outb(0x3D5, show ? 0x0E : 0x20); + outb(0x3D4, 11); + outb(0x3D5, show ? 0x0F : 0x20); + } + if (show) { + cursor(); } } diff --git a/k-devices.hh b/k-devices.hh index 1c5a6c0..d8e2340 100644 --- a/k-devices.hh +++ b/k-devices.hh @@ -2,11 +2,6 @@ #define CHICKADEE_K_DEVICES_HH #include "kernel.hh" -// console_show_cursor() -// Displays the console cursor to the current position (`cursorpos`). -void console_show_cursor(); - - // keyboardstate: keyboard buffer and keyboard interrupts #define KEY_UP 0xC0 @@ -23,10 +18,10 @@ void console_show_cursor(); struct keyboardstate { spinlock lock_; char buf_[256]; - unsigned pos_; // next position to read - unsigned len_; // number of characters in buffer - unsigned eol_; // position in buffer of most recent \n - enum { boot, input, fail } state_; + unsigned pos_ = 0; // next position to read + unsigned len_ = 0; // number of characters in buffer + unsigned eol_ = 0; // position in buffer of most recent \n + enum { boot, input, fail } state_ = boot; static keyboardstate& get() { return kbd; @@ -46,7 +41,7 @@ struct keyboardstate { private: static keyboardstate kbd; - keyboardstate(); + keyboardstate() = default; void maybe_echo(int ch); }; @@ -61,9 +56,16 @@ struct consolestate { return console; } + void cursor(); + void cursor(bool show); + private: static consolestate console; consolestate() = default; + + spinlock cursor_lock_; + std::atomic cursor_show_ = true; + std::atomic displayed_cpos_ = -1; }; diff --git a/k-exception.S b/k-exception.S index 42ac4e1..aabefb8 100644 --- a/k-exception.S +++ b/k-exception.S @@ -172,11 +172,11 @@ restore_and_iret: testb $1, 12(%rsp) // `reg_swapgs & 1` jnz 1f - // returning to kernel mode + // return to kernel mode addq $24, %rsp iretq -1: // returning to user mode +1: // return to user mode cli // prevent interrupts after `swapgs` swapgs pop %gs @@ -186,6 +186,7 @@ restore_and_iret: // syscall_entry // Kernel entry point for the `syscall` instruction + .globl syscall_entry syscall_entry: swapgs @@ -201,8 +202,8 @@ syscall_entry: subq $8, %rsp // error code unused pushq $-1 // reg_swapgs, reg_intno - push %gs - push %fs + push %gs // must restore + push %fs // must restore pushq %r15 // callee saved pushq %r14 // callee saved pushq %r13 // callee saved @@ -233,11 +234,17 @@ syscall_entry: cmpl $PROC_RUNNABLE, 24(%rdi) jne panic_nonrunnable - addq $(8 * 19), %rsp + addq $(8 * 15), %rsp // skip general-purpose registers + + pop %fs // restore `%fs` and `%gs` cli // prevent interrupts after `swapgs` swapgs + pop %gs + + addq $(8 * 2), %rsp iretq + panic_nonrunnable: call _ZN4proc17panic_nonrunnableEv @@ -332,9 +339,10 @@ resume_regstate_fail: jmp 1f resume_yieldstate_fail: movq $proc_contains_yields_assert, %rdx -1: xorl %esi, %esi +1: xorl %ecx, %ecx + xorl %esi, %esi movq $k_exception_str, %rdi - call _Z11assert_failPKciS0_ + call _Z11assert_failPKciS0_S0_ 1: jmp 1b diff --git a/k-hardware.cc b/k-hardware.cc index 92e7f1c..2575121 100644 --- a/k-hardware.cc +++ b/k-hardware.cc @@ -14,9 +14,9 @@ pcistate pcistate::state; // kalloc_pagetable -// Allocate and initialize a new page table. The page is allocated -// using `kalloc()`. The page table's high memory is copied from -// `early_pagetable`. +// Allocate, initialize, and return a new, empty page table. Memory is +// allocated using `kalloc()`. The page table's high memory is copied +// from `early_pagetable`. x86_64_pagetable* kalloc_pagetable() { x86_64_pagetable* pt = knew(); @@ -35,7 +35,7 @@ x86_64_pagetable* kalloc_pagetable() { // new page table, and calls panic() if they aren't. void set_pagetable(x86_64_pagetable* pagetable) { - assert(pagetable != nullptr); // must not be NULL + assert(pagetable != nullptr); // must not be nullptr assert(vmiter(pagetable, HIGHMEM_BASE).pa() == 0); assert(vmiter(pagetable, HIGHMEM_BASE).writable()); assert(!vmiter(pagetable, HIGHMEM_BASE).user()); @@ -114,11 +114,15 @@ void reboot() { // machine if `HALT=1` was specified during kernel build. void process_halt() { + // change keyboard state, hide cursor auto& kbd = keyboardstate::get(); kbd.state_ = keyboardstate::boot; + consolestate::get().cursor(false); + // maybe halt machine if (memfile::initfs_lookup(".halt") >= 0) { poweroff(); } + // otherwise yield forever while (true) { current()->yield(); } @@ -222,15 +226,13 @@ bool lookup_symbol(uintptr_t addr, const char** name, uintptr_t* start) { namespace { struct backtracer { - uintptr_t rbp_; - uintptr_t rsp_; - uintptr_t stack_top_; - backtracer(uintptr_t rbp, uintptr_t rsp, uintptr_t stack_top) : rbp_(rbp), rsp_(rsp), stack_top_(stack_top) { + pt_ = pa2kptr(rdcr3()); + check(); } bool ok() const { - return rbp_ >= rsp_ && stack_top_ - rbp_ >= 16; + return rsp_ != 0; } uintptr_t ret_rip() const { uintptr_t* rbpx = reinterpret_cast(rbp_); @@ -240,6 +242,21 @@ struct backtracer { uintptr_t* rbpx = reinterpret_cast(rbp_); rsp_ = rbp_ + 16; rbp_ = rbpx[0]; + check(); + } + +private: + uintptr_t rbp_; + uintptr_t rsp_; + uintptr_t stack_top_; + x86_64_pagetable* pt_; + + void check() { + if (rbp_ < rsp_ + || stack_top_ - rbp_ < 16 + || ((vmiter(pt_, rbp_).range_perm(16)) & PTE_P) == 0) { + rbp_ = rsp_ = 0; + } } }; } @@ -271,7 +288,7 @@ void log_backtrace(const char* prefix, uintptr_t rsp, uintptr_t rbp) { } -// error_vprintf, error_printf +// error_vprintf // Print debugging messages to the console and to the host's // `log.txt` file via `log_printf`. @@ -322,7 +339,7 @@ static void vpanic(uintptr_t rsp, uintptr_t rbp, uintptr_t rip, const char* format, va_list val) { panicking = true; - cursorpos = CPOS(24, 0); + cursorpos = CPOS(24, 80); if (format) { // Print panic message to both the screen and the log error_printf(-1, COLOR_ERROR, "PANIC: "); @@ -363,8 +380,12 @@ void panic_at(uintptr_t rsp, uintptr_t rbp, uintptr_t rip, fail(); } -void assert_fail(const char* file, int line, const char* msg) { +void assert_fail(const char* file, int line, const char* msg, + const char* description) { cursorpos = CPOS(23, 0); + if (description) { + error_printf("%s:%d: %s\n", file, line, description); + } error_printf("%s:%d: kernel assertion '%s' failed\n", file, line, msg); error_print_backtrace(rdrsp(), rdrbp()); fail(); diff --git a/k-memviewer.cc b/k-memviewer.cc index 2162e0d..69574b9 100644 --- a/k-memviewer.cc +++ b/k-memviewer.cc @@ -1,6 +1,12 @@ #include "kernel.hh" #include "k-vmiter.hh" +// k-memviewer.cc +// +// The `memusage` class tracks memory usage by walking page tables, +// looks for errors, and prints the memory map to the console. + + class memusage { public: // tracks physical addresses in the range [0, maxpa) @@ -31,10 +37,10 @@ class memusage { // both as kernel-only and process-associated. - // refresh the memory map from current state + // Refresh the memory map from current state void refresh(); - // return the symbol (character & color) associated with `pa` + // Return the symbol (character & color) associated with `pa` uint16_t symbol_at(uintptr_t pa) const; private: @@ -47,6 +53,12 @@ class memusage { v_[pa / PAGESIZE] |= flags; } } + // return one of the processes set in a mark + static int marked_pid(unsigned v) { + return lsb(v >> 2); + } + // print an error about a page table + void page_error(uintptr_t pa, const char* desc, int pid) const; }; @@ -86,7 +98,7 @@ void memusage::refresh() { auto irqs = p->lock_pagetable_read(); if (p->pagetable_ && p->pagetable_ != early_pagetable) { for (ptiter it(p); it.low(); it.next()) { - mark(it.ptp_pa(), f_kernel | f_process(pid)); + mark(it.pa(), f_kernel | f_process(pid)); } mark(ka2pa(p->pagetable_), f_kernel | f_process(pid)); @@ -104,6 +116,13 @@ void memusage::refresh() { } } +void memusage::page_error(uintptr_t pa, const char* desc, int pid) const { + const char* fmt = pid >= 0 + ? "PAGE TABLE ERROR: %lx: %s (pid %d)\n" + : "PAGE TABLE ERROR: %lx: %s\n"; + error_printf(CPOS(22, 0), COLOR_ERROR, fmt, pa, desc, pid); + log_printf(fmt, pa, desc, pid); +} uint16_t memusage::symbol_at(uintptr_t pa) const { auto range = physical_ranges.find(pa); @@ -136,14 +155,12 @@ uint16_t memusage::symbol_at(uintptr_t pa) const { return 'K' | 0x4000; } else if ((v & f_kernel) && (v & f_user)) { // kernel-restricted + user-accessible = error - log_printf("%p: sharing error, kernel-restricted + user-accessible\n", pa); + page_error(pa, "sharing error, kernel-restricted + user-accessible\n", + marked_pid(v)); return 'E' | 0xF400; } else { // find lowest process involved with this page - int pid = 1; - while (!(v & f_process(pid))) { - ++pid; - } + pid_t pid = marked_pid(v); // foreground color is that associated with `pid` static const uint8_t colors[] = { 0xF, 0xC, 0xA, 0x9, 0xE }; uint16_t ch = colors[pid % 5] << 8; @@ -193,6 +210,7 @@ static void console_memviewer_virtual(memusage& mu, proc* vmp) { void console_memviewer(proc* vmp) { + // track physical memory static memusage mu; mu.refresh(); // must be called with `ptable_lock` held diff --git a/k-proc.cc b/k-proc.cc index 70a8e88..d7f4b18 100644 --- a/k-proc.cc +++ b/k-proc.cc @@ -71,8 +71,8 @@ void proc::init_kernel(pid_t pid, void (*f)()) { void proc::panic_nonrunnable() { panic("Trying to resume proc %d, which is not runnable\n" - "(proc state %d, last user %%rip %p)", - id_, pstate_.load(), last_user_rip_); + "(proc state %d, recent user %%rip %p)", + id_, pstate_.load(), recent_user_rip_); } diff --git a/k-vmiter.cc b/k-vmiter.cc index dbb0b6c..b96d64d 100644 --- a/k-vmiter.cc +++ b/k-vmiter.cc @@ -2,9 +2,32 @@ const x86_64_pageentry_t vmiter::zero_pe = 0; +uint64_t vmiter::range_perm(size_t sz) const { + uint64_t p = perm(); + size_t rsz = pageoffmask(level_) + 1; + if ((p & PTE_P) != 0 && sz > rsz) { + if (sz > ((int64_t) va() < 0 ? 0 : VA_LOWEND) - va()) { + return 0; + } + vmiter it(*this); + sz += va() & (rsz - 1); + do { + sz -= rsz; + it.next_range(); + p &= it.perm(); + rsz = pageoffmask(it.level_) + 1; + } while ((p & PTE_P) != 0 && sz > rsz); + } + if ((p & PTE_P) != 0) { + return p; + } else { + return 0; + } +} + void vmiter::down() { while (level_ > 0 && (*pep_ & (PTE_P | PTE_PS)) == PTE_P) { - perm_ &= *pep_; + perm_ &= *pep_ | ~(PTE_P | PTE_W | PTE_U); --level_; uintptr_t pa = *pep_ & PTE_PAMASK; x86_64_pagetable* pt = pa2kptr(pa); @@ -27,7 +50,7 @@ void vmiter::real_find(uintptr_t va) { pep_ = const_cast(&zero_pe); } } else { - int curidx = (reinterpret_cast(pep_) & PAGEOFFMASK) >> 3; + int curidx = (reinterpret_cast(pep_) % PAGESIZE) >> 3; pep_ += pageindex(va, level_) - curidx; } va_ = va; @@ -46,13 +69,17 @@ int vmiter::try_map(uintptr_t pa, int perm) { if (pa == (uintptr_t) -1 && perm == 0) { pa = 0; } - assert(!(va_ & PAGEOFFMASK)); + // virtual address is page-aligned + assert((va_ % PAGESIZE) == 0, "vmiter::try_map va not aligned"); if (perm & PTE_P) { - assert(pa != (uintptr_t) -1); - assert((pa & PTE_PAMASK) == pa); + // if mapping present, physical address is page-aligned + assert(pa != (uintptr_t) -1, "vmiter::try_map mapping nonexistent pa"); + assert((pa & PTE_PAMASK) == pa, "vmiter::try_map pa not aligned"); } else { - assert(!(pa & PTE_P)); + assert((pa & PTE_P) == 0, "vmiter::try_map invalid pa"); } + // new permissions (`perm`) cannot be less restrictive than permissions + // imposed by higher-level page tables (`perm_`) assert(!(perm & ~perm_ & (PTE_P | PTE_W | PTE_U))); while (level_ > 0 && perm) { diff --git a/k-vmiter.hh b/k-vmiter.hh index c142690..54f6c2f 100644 --- a/k-vmiter.hh +++ b/k-vmiter.hh @@ -11,47 +11,68 @@ class vmiter { public: - // initialize a `vmiter` for `pt` pointing at `va` + // Initialize a `vmiter` for `pt`, with initial virtual address `va` inline vmiter(x86_64_pagetable* pt, uintptr_t va = 0); inline vmiter(const proc* p, uintptr_t va = 0); - inline uintptr_t va() const; // current virtual address - inline uintptr_t last_va() const; // one past last va in this range - inline bool low() const; // is va low? - inline uint64_t pa() const; // current physical address + // Return current virtual address + inline uintptr_t va() const; + // Return one past last virtual address in this mapping range + inline uintptr_t last_va() const; + // Return true iff `va() <= VA_LOWMAX` (is a low canonical address) + inline bool low() const; + // Return physical address mapped at `va()`, + // or `(uintptr_t) -1` if `va()` is unmapped + inline uint64_t pa() const; + // Return a kernel-accessible pointer corresponding to `pa()`, + // or `nullptr` if `va()` is unmapped template - inline T kptr() const; // kernel pointer for pa() - inline uint64_t perm() const; // current permissions - inline bool perm(uint64_t p) const; // are all permissions `p` enabled? - inline bool present() const; // is va present? - inline bool writable() const; // is va writable? - inline bool user() const; // is va user-accessible (unprivileged)? - - inline vmiter& find(uintptr_t va); // change virtual address to `va` - inline vmiter& operator+=(intptr_t delta); // advance `va` by `delta` - inline vmiter& operator-=(intptr_t delta); + inline T kptr() const; + + // Return permissions of current mapping. + // Returns 0 unless `PTE_P` is set. + inline uint64_t perm() const; + // Return true iff `va()` is present (`PTE_P`) + inline bool present() const; + // Return true iff `va()` is present and writable (`PTE_P|PTE_W`) + inline bool writable() const; + // Return true iff `va()` is present and unprivileged (`PTE_P|PTE_U`) + inline bool user() const; + // Return intersection of permissions in [va(), va() + sz) + uint64_t range_perm(size_t sz) const; + // Return true iff `(perm() & desired_perm) == desired_perm` + inline bool perm(uint64_t desired_perm) const; + // Return true iff `(range_perm(sz) & desired_perm) == desired_perm` + inline bool range_perm(size_t sz, uint64_t desired_perm) const; - // move to next page-aligned va, skipping large empty regions - // Never skips present pages. - void next(); - // move to next page-aligned va with different perm/pa (i.e., `last_va()`) - // Like next(), but also skips present pages. + // Move to virtual address `va`; return `*this` + inline vmiter& find(uintptr_t va); + // Advance to virtual address `va() + delta`; return `*this` + inline vmiter& operator+=(intptr_t delta); + // Advance to virtual address `va() - delta`; return `*this` + inline vmiter& operator-=(intptr_t delta); + // Move to next larger page-aligned virtual address, skipping large + // non-present regions + void next(); + // Move to `last_va()` void next_range(); - // map current va to `pa` with permissions `perm` - // Current va must be page-aligned. Calls `kalloc` to allocate - // page table pages if necessary. Panics on failure. + // Map current virtual address to `pa` with permissions `perm`. + // The current virtual address must be page-aligned. Calls `kalloc` + // to allocate page table pages if necessary; panics on failure. inline void map(uintptr_t pa, int perm); + // Same, but map a kernel pointer inline void map(void* kptr, int perm); - // Like `map`, but returns 0 on success and -1 on failure. - int try_map(uintptr_t pa, int perm) - __attribute__((warn_unused_result)); - inline int try_map(void* kptr, int perm) - __attribute__((warn_unused_result)); + // Map current virtual address to `pa` with permissions `perm`. + // The current virtual address must be page-aligned. Calls `kalloc` + // to allocate page table pages if necessary; returns 0 on success + // and -1 on failure. + [[gnu::warn_unused_result]] int try_map(uintptr_t pa, int perm); + [[gnu::warn_unused_result]] inline int try_map(void* kptr, int perm); - // free mapped page and clear mapping. Like `kfree(kptr()); map(0, 0)` + // Free mapped page and clear mapping. Like `kfree(kptr()); map(0, 0)` inline void kfree_page(); private: @@ -78,26 +99,44 @@ class vmiter { // } // kfree(pt); // ``` -// Note that `ptiter` will never visit the level 4 page table page. +// Note that `ptiter` will never visit the root (level-4) page table page. class ptiter { public: - // initialize a `ptiter` for `pt` pointing at `va` - inline ptiter(x86_64_pagetable* pt, uintptr_t va = 0); - inline ptiter(const proc* p, uintptr_t va = 0); - - inline uintptr_t va() const; // current virtual address - inline uintptr_t last_va() const; // one past last va covered by ptp - inline bool active() const; // does va exist? - inline bool low() const; // is va low? - inline int level() const; // current level (0-2) - inline x86_64_pagetable* ptp() const; // current page table page - inline uintptr_t ptp_pa() const; // physical address of ptp - inline void kfree_ptp(); // `kfree(ptp())` + clear mapping - - // move to next page table page in depth-first order + // Initialize a physical iterator for `pt` with initial virtual address 0 + inline ptiter(x86_64_pagetable* pt); + inline ptiter(const proc* p); + + // Return true once `ptiter` has iterated over all page table pages + // (not including the top-level page table page) + inline bool done() const; + + // Return physical address of current page table page + inline uintptr_t pa() const; + // Return kernel-accessible pointer to the current page table page + inline x86_64_pagetable* kptr() const; + // Move to next page table page in depth-first order inline void next(); + // Return current virtual address + inline uintptr_t va() const; + // Return one past the last virtual address in this mapping range + inline uintptr_t last_va() const; + // Return true iff `va() <= VA_LOWMAX` (is low canonical) + inline bool low() const; + // Return level of current page table page (0-2) + inline int level() const; + + // Return first virtual address covered by entry `idx` in current pt + inline uintptr_t entry_va(unsigned idx) const; + // Return one past the last virtual address covered by entry + inline uintptr_t entry_last_va(unsigned idx) const; + // Return current page table entry + inline x86_64_pageentry_t entry(unsigned idx) const; + + // Free current page table page (`kptr()`) and unmap current entry + inline void kfree_ptp(); + private: x86_64_pagetable* pt_; x86_64_pageentry_t* pep_; @@ -138,21 +177,23 @@ inline uint64_t vmiter::pa() const { } template inline T vmiter::kptr() const { - assert(*pep_ & PTE_P); - return pa2kptr(pa()); -} -inline uint64_t vmiter::perm() const { if (*pep_ & PTE_P) { - return *pep_ & perm_; + return pa2kptr(pa()); } else { - return 0; + return nullptr; } } -inline bool vmiter::perm(uint64_t p) const { - return (*pep_ & perm_ & p) == p; +inline uint64_t vmiter::perm() const { + // Returns 0-0xFFF. (XXX Does not track PTE_XD.) + // Returns 0 unless `(*pep_ & perm_ & PTE_P) != 0`. + uint64_t ph = *pep_ & perm_; + return ph & -(ph & PTE_P); +} +inline bool vmiter::perm(uint64_t desired_perm) const { + return (perm() & desired_perm) == desired_perm; } inline bool vmiter::present() const { - return (*pep_ & PTE_P) != 0; + return perm(PTE_P); } inline bool vmiter::writable() const { return perm(PTE_P | PTE_W); @@ -160,6 +201,9 @@ inline bool vmiter::writable() const { inline bool vmiter::user() const { return perm(PTE_P | PTE_U); } +inline bool vmiter::range_perm(size_t sz, uint64_t desired_perm) const { + return (range_perm(sz) & desired_perm) == desired_perm; +} inline vmiter& vmiter::find(uintptr_t va) { real_find(va); return *this; @@ -175,7 +219,7 @@ inline void vmiter::next_range() { } inline void vmiter::map(uintptr_t pa, int perm) { int r = try_map(pa, perm); - assert(r == 0); + assert(r == 0, "vmiter::map failed"); } inline void vmiter::map(void* kp, int perm) { map(kptr2pa(kp), perm); @@ -191,12 +235,12 @@ inline void vmiter::kfree_page() { *pep_ = 0; } -inline ptiter::ptiter(x86_64_pagetable* pt, uintptr_t va) +inline ptiter::ptiter(x86_64_pagetable* pt) : pt_(pt) { - go(va); + go(0); } -inline ptiter::ptiter(const proc* p, uintptr_t va) - : ptiter(p->pagetable_, va) { +inline ptiter::ptiter(const proc* p) + : ptiter(p->pagetable_) { } inline uintptr_t ptiter::va() const { return va_ & ~pageoffmask(level_); @@ -204,26 +248,36 @@ inline uintptr_t ptiter::va() const { inline uintptr_t ptiter::last_va() const { return (va_ | pageoffmask(level_)) + 1; } -inline bool ptiter::active() const { - return va_ <= VA_NONCANONMAX; -} inline bool ptiter::low() const { return va_ <= VA_LOWMAX; } +inline bool ptiter::done() const { + return va_ > VA_NONCANONMAX; +} inline int ptiter::level() const { return level_ - 1; } inline void ptiter::next() { down(true); } -inline uintptr_t ptiter::ptp_pa() const { +inline uintptr_t ptiter::pa() const { return *pep_ & PTE_PAMASK; } -inline x86_64_pagetable* ptiter::ptp() const { - return pa2kptr(ptp_pa()); +inline x86_64_pagetable* ptiter::kptr() const { + return pa2kptr(pa()); +} +inline uintptr_t ptiter::entry_va(unsigned idx) const { + return va() + idx * (pageoffmask(level_ - 1) + 1); +} +inline uintptr_t ptiter::entry_last_va(unsigned idx) const { + return va() + (idx + 1) * (pageoffmask(level_ - 1) + 1); +} +inline x86_64_pageentry_t ptiter::entry(unsigned idx) const { + assert(idx < (1U << PAGEINDEXBITS)); + return kptr()->entry[idx]; } inline void ptiter::kfree_ptp() { - kfree(ptp()); + kfree(kptr()); *pep_ = 0; } diff --git a/kernel.cc b/kernel.cc index 2b8d6ca..ae283ba 100644 --- a/kernel.cc +++ b/kernel.cc @@ -17,7 +17,7 @@ std::atomic ticks; // display type; initially KDISPLAY_CONSOLE std::atomic kdisplay; -static void kdisplay_ontick(); +static void tick(); static void boot_process_start(pid_t pid, const char* program_name); @@ -94,11 +94,11 @@ void proc::exception(regstate* regs) { // Record most recent user-mode %rip. if ((regs->reg_cs & 3) != 0) { - last_user_rip_ = regs->reg_rip; + recent_user_rip_ = regs->reg_rip; } // Show the current cursor location. - console_show_cursor(); + consolestate::get().cursor(); // Actually handle the exception. @@ -107,8 +107,7 @@ void proc::exception(regstate* regs) { case INT_IRQ + IRQ_TIMER: { cpustate* cpu = this_cpu(); if (cpu->cpuindex_ == 0) { - ++ticks; - kdisplay_ontick(); + tick(); } lapicstate::get().ack(); regs_ = regs; @@ -168,7 +167,7 @@ uintptr_t proc::syscall(regstate* regs) { //log_printf("proc %d: syscall %ld @%p\n", id_, regs->reg_rax, regs->reg_rip); // Record most recent user-mode %rip. - last_user_rip_ = regs->reg_rip; + recent_user_rip_ = regs->reg_rip; switch (regs->reg_rax) { @@ -383,13 +382,20 @@ uintptr_t proc::syscall_readdiskfile(regstate* regs) { // Uses `console_memviewer()`, a function defined in `k-memviewer.cc`. static void memshow() { - static unsigned last_ticks = 0; + static unsigned long last_redisplay = 0; + static unsigned long last_switch = 0; static int showing = 1; - // switch to a new process every 0.25 sec - if (last_ticks == 0 || ticks - last_ticks >= HZ / 2) { - last_ticks = ticks; + // redisplay every 0.04 sec + if (last_redisplay != 0 && ticks - last_redisplay < HZ / 25) { + return; + } + last_redisplay = ticks; + + // switch to a new process every 0.5 sec + if (ticks - last_switch >= HZ / 2) { showing = (showing + 1) % NPROC; + last_switch = ticks; } spinlock_guard guard(ptable_lock); @@ -403,16 +409,24 @@ static void memshow() { ++search; } - extern void console_memviewer(proc* vmp); console_memviewer(ptable[showing]); + if (!ptable[showing]) { + console_printf(CPOS(10, 29), 0x0F00, "VIRTUAL ADDRESS SPACE\n" + " [All processes have exited]\n" + "\n\n\n\n\n\n\n\n\n\n\n"); + } } -// kdisplay_ontick() -// Shows the currently-configured kdisplay. Called once every tick -// (every 0.01 sec) by CPU 0. +// tick() +// Called once every tick (0.01 sec, 1/HZ) by CPU 0. Updates the `ticks` +// counter and performs other periodic maintenance tasks. + +void tick() { + // Update current time + ++ticks; -void kdisplay_ontick() { + // Update memviewer display if (kdisplay.load(std::memory_order_relaxed) == KDISPLAY_MEMVIEWER) { memshow(); } diff --git a/kernel.hh b/kernel.hh index f7d6522..f73e78a 100644 --- a/kernel.hh +++ b/kernel.hh @@ -28,13 +28,13 @@ struct __attribute__((aligned(4096))) proc { }; // These four members must come first: - pid_t id_ = 0; // process ID - regstate* regs_ = nullptr; // process's current registers - yieldstate* yields_ = nullptr; // process's current yield state - std::atomic pstate_ = ps_blank; // process state + pid_t id_ = 0; // Process ID + regstate* regs_ = nullptr; // Process's current registers + yieldstate* yields_ = nullptr; // Process's current yield state + std::atomic pstate_ = ps_blank; // Process state - x86_64_pagetable* pagetable_ = nullptr; // process's page table - uintptr_t last_user_rip_ = 0; // last user-mode %rip + x86_64_pagetable* pagetable_ = nullptr; // Process's page table + uintptr_t recent_user_rip_ = 0; // Most recent user-mode %rip #if HAVE_SANITIZERS int sanitizer_status_ = 0; #endif @@ -344,10 +344,10 @@ inline __attribute__((malloc)) T* knew(Args&&... args) { void init_kalloc(); -// initialize hardware and CPUs +// Initialize hardware and CPUs void init_hardware(); -// query machine configuration +// Query machine configuration unsigned machine_ncpu(); unsigned machine_pci_irq(int pci_addr, int intr_pin); @@ -355,34 +355,37 @@ struct ahcistate; extern ahcistate* sata_disk; -// early page table (only kernel mappings) +// Early page table (only kernel mappings) extern x86_64_pagetable early_pagetable[3]; -// allocate and initialize a new level-4 page table +// Allocate and initialize a new, empty page table x86_64_pagetable* kalloc_pagetable(); -// change current page table +// Change current page table void set_pagetable(x86_64_pagetable* pagetable); +// Print memory viewer +void console_memviewer(proc* p); -// start the kernel + +// Start the kernel [[noreturn]] void kernel_start(const char* command); -// turn off the virtual machine +// Turn off the virtual machine [[noreturn]] void poweroff(); -// reboot the virtual machine +// Reboot the virtual machine [[noreturn]] void reboot(); -// call after last process exits +// Call after last process exits [[noreturn]] void process_halt(); // log_printf, log_vprintf // Print debugging messages to the host's `log.txt` file. We run QEMU // so that messages written to the QEMU "parallel port" end up in `log.txt`. -void log_printf(const char* format, ...) __attribute__((noinline)); -void log_vprintf(const char* format, va_list val) __attribute__((noinline)); +__noinline void log_printf(const char* format, ...); +__noinline void log_vprintf(const char* format, va_list val); // log_backtrace @@ -401,7 +404,7 @@ bool lookup_symbol(uintptr_t addr, const char** name, uintptr_t* start); #if HAVE_SANITIZERS -// sanitizer functions +// Sanitizer functions void init_sanitizers(); void disable_asan(); void enable_asan(); diff --git a/lib.cc b/lib.cc index 7f0881e..c6ab615 100644 --- a/lib.cc +++ b/lib.cc @@ -1,5 +1,8 @@ #include "lib.hh" #include "x86-64.h" +#if CHICKADEE_KERNEL +#include "k-devices.hh" /* for consolestate */ +#endif // lib.cc // @@ -88,6 +91,19 @@ char* strcpy(char* dst, const char* src) { return dst; } +char* strncpy(char* dst, const char* src, size_t maxlen) { + char* d = dst; + while (maxlen > 0 && *src != '\0') { + *d++ = *src++; + --maxlen; + } + while (maxlen > 0) { + *d++ = '\0'; + --maxlen; + } + return dst; +} + int strcmp(const char* a, const char* b) { while (true) { unsigned char ac = *a, bc = *b; @@ -511,8 +527,7 @@ void console_clear() { } cursorpos = 0; #if CHICKADEE_KERNEL - extern void console_show_cursor(); - console_show_cursor(); + consolestate::get().cursor(); #endif } @@ -557,8 +572,7 @@ __noinline void console_printer::move_cursor() { cursorpos = cell_ - console; #if CHICKADEE_KERNEL - extern void console_show_cursor(); - console_show_cursor(); + consolestate::get().cursor(); #endif } diff --git a/lib.hh b/lib.hh index 08f3adb..855c977 100644 --- a/lib.hh +++ b/lib.hh @@ -20,6 +20,7 @@ void* memchr(const void* s, int c, size_t n); size_t strlen(const char* s); size_t strnlen(const char* s, size_t maxlen); char* strcpy(char* dst, const char* src); +char* strncpy(char* dst, const char* src, size_t maxlen); int strcmp(const char* a, const char* b); int strncmp(const char* a, const char* b, size_t maxlen); int strcasecmp(const char* a, const char* b); @@ -45,70 +46,41 @@ void srand(unsigned seed); int rand(int min, int max); -// Returns the offset of `member` relative to the beginning of a struct type +// Return the offset of `member` relative to the beginning of a struct type #ifndef offsetof #define offsetof(type, member) __builtin_offsetof(type, member) #endif -// Returns the number of elements in an array +// Return the number of elements in an array #define arraysize(array) (sizeof(array) / sizeof(array[0])) -// Type information - -// printfmt -// `printfmt::spec` defines a printf specifier for type T. -// E.g., `printfmt::spec` is `"d"`. - -template struct printfmt {}; -template <> struct printfmt { static constexpr char spec[] = "d"; }; -template <> struct printfmt { static constexpr char spec[] = "c"; }; -template <> struct printfmt { static constexpr char spec[] = "d"; }; -template <> struct printfmt { static constexpr char spec[] = "u"; }; -template <> struct printfmt { static constexpr char spec[] = "d"; }; -template <> struct printfmt { static constexpr char spec[] = "u"; }; -template <> struct printfmt { static constexpr char spec[] = "d"; }; -template <> struct printfmt { static constexpr char spec[] = "u"; }; -template <> struct printfmt { static constexpr char spec[] = "ld"; }; -template <> struct printfmt { static constexpr char spec[] = "lu"; }; -template struct printfmt { static constexpr char spec[] = "p"; }; - -template constexpr char printfmt::spec[]; - - -// Min, max, and round-to-multiple operations +// Arithmetic +// min(a, b, ...) +// Return the minimum of the arguments. template inline constexpr T min(T a, T b) { return a < b ? a : b; } -template -inline constexpr T min(T a, T b, T c) { - return min(min(a, b), c); +template +inline constexpr T min(T a, T b, Rest... c) { + return min(min(a, b), c...); } + +// max(a, b, ...) +// Return the maximum of the arguments. template inline constexpr T max(T a, T b) { return b < a ? a : b; } -template -inline constexpr T max(T a, T b, T c) { - return max(max(a, b), c); +template +inline constexpr T max(T a, T b, Rest... c) { + return max(max(a, b), c...); } -template -inline constexpr T round_down(T x, unsigned multiple) { - static_assert(std::is_unsigned::value, "T must be unsigned"); - return x - (x % multiple); -} -template -inline constexpr T round_up(T x, unsigned multiple) { - static_assert(std::is_unsigned::value, "T must be unsigned"); - return round_down(x + multiple - 1, multiple); -} - - // msb(x) -// Returns the index of most significant one bit in `x`, plus one. +// Return index of most significant one bit in `x`, plus one. // Returns 0 if `x == 0`. inline constexpr int msb(int x) { return x ? sizeof(x) * 8 - __builtin_clz(x) : 0; @@ -130,7 +102,7 @@ inline constexpr int msb(unsigned long long x) { } // lsb(x) -// Returns the index of least significant one bit in `x`, plus one. +// Return index of least significant one bit in `x`, plus one. // Returns 0 if `x == 0`. inline constexpr int lsb(int x) { return __builtin_ffs(x); @@ -151,8 +123,28 @@ inline constexpr int lsb(unsigned long long x) { return __builtin_ffsll(x); } +// round_down(x, m) +// Return the largest multiple of `m` less than or equal to `x`. +// Equivalently, round `x` down to the nearest multiple of `m`. +template +inline constexpr T round_down(T x, unsigned m) { + static_assert(std::is_unsigned::value, "T must be unsigned"); + return x - (x % m); +} + +// round_up(x, m) +// Return the smallest multiple of `m` greater than or equal to `x`. +// Equivalently, round `x` up to the nearest multiple of `m`. +template +inline constexpr T round_up(T x, unsigned m) { + static_assert(std::is_unsigned::value, "T must be unsigned"); + return round_down(x + m - 1, m); +} + // round_down_pow2(x) -// Rounds x down to the nearest power of 2. +// Return the largest power of 2 less than or equal to `x`. +// Equivalently, round `x` down to the nearest power of 2. +// Returns 0 if `x == 0`. template inline constexpr T round_down_pow2(T x) { static_assert(std::is_unsigned::value, "T must be unsigned"); @@ -160,7 +152,9 @@ inline constexpr T round_down_pow2(T x) { } // round_up_pow2(x) -// Rounds x up to the nearest power of 2. +// Return the smallest power of 2 greater than or equal to `x`. +// Equivalently, round `x` up to the nearest power of 2. +// Returns 0 if `x == 0`. template inline constexpr T round_up_pow2(T x) { static_assert(std::is_unsigned::value, "T must be unsigned"); @@ -357,7 +351,7 @@ void console_clear(); // console_puts(cursor, color, s, len) -// Writes a string to the CGA console. Writes exactly `len` characters. +// Write a string to the CGA console. Writes exactly `len` characters. // // The `cursor` argument is a cursor position, such as `CPOS(r, c)` // for row number `r` and column number `c`. The `color` argument @@ -369,7 +363,7 @@ int console_puts(int cpos, int color, const char* s, size_t len); // console_printf(cursor, color, format, ...) -// Formats and prints a message to the CGA console. +// Format and print a message to the CGA console. // // The `format` argument supports some of the C printf function's escapes: // %d (to print an integer in decimal notation), %u (to print an unsigned @@ -417,17 +411,40 @@ void error_printf(const char* format, ...) __attribute__((noinline, cold)); +// Type information + +// printfmt +// `printfmt::spec` defines a printf specifier for type T. +// E.g., `printfmt::spec` is `"d"`. + +template struct printfmt {}; +template <> struct printfmt { static constexpr char spec[] = "d"; }; +template <> struct printfmt { static constexpr char spec[] = "c"; }; +template <> struct printfmt { static constexpr char spec[] = "d"; }; +template <> struct printfmt { static constexpr char spec[] = "u"; }; +template <> struct printfmt { static constexpr char spec[] = "d"; }; +template <> struct printfmt { static constexpr char spec[] = "u"; }; +template <> struct printfmt { static constexpr char spec[] = "d"; }; +template <> struct printfmt { static constexpr char spec[] = "u"; }; +template <> struct printfmt { static constexpr char spec[] = "ld"; }; +template <> struct printfmt { static constexpr char spec[] = "lu"; }; +template struct printfmt { static constexpr char spec[] = "p"; }; + +template constexpr char printfmt::spec[]; + + // Assertions // assert(x) -// If `x == 0`, prints a message and fails. -#define assert(x) do { \ +// If `x == 0`, print a message and fail. +#define assert(x, ...) do { \ if (!(x)) { \ - assert_fail(__FILE__, __LINE__, #x); \ + assert_fail(__FILE__, __LINE__, #x, ## __VA_ARGS__); \ } \ - } while (0) -void __attribute__((noinline, noreturn, cold)) -assert_fail(const char* file, int line, const char* msg); + } while (false) +__attribute__((noinline, noreturn, cold)) +void assert_fail(const char* file, int line, const char* msg, + const char* description = nullptr); // assert_[eq, ne, lt, le, gt, ge](x, y) @@ -460,7 +477,7 @@ assert_op_fail(const char* file, int line, const char* msg, // assert_memeq(x, y, sz) -// If `memcmp(x, y, sz) != 0`, prints a message and fails. +// If `memcmp(x, y, sz) != 0`, print a message and fail. #define assert_memeq(x, y, sz) do { \ auto __x = (x); auto __y = (y); size_t __sz = (sz); \ if (memcmp(__x, __y, __sz) != 0) { \ @@ -473,7 +490,7 @@ assert_memeq_fail(const char* file, int line, const char* msg, // panic(format, ...) -// Prints the message determined by `format` and fails. +// Print the message determined by `format` and fail. void __attribute__((noinline, noreturn, cold)) panic(const char* format, ...); diff --git a/p-sh.cc b/p-sh.cc index 3044f67..57e9518 100644 --- a/p-sh.cc +++ b/p-sh.cc @@ -221,7 +221,7 @@ static pid_t create_child(char** words, char nextch, if (words[1] && isdigit((unsigned char) words[1][0])) { char* endl; exit_status = strtol(words[1], &endl, 10); - if (*endl != '\0') { + if (*endl != '\0' || exit_status < 0) { exit_status = -2; } } else if (words[1]) { @@ -246,10 +246,16 @@ static pid_t create_child(char** words, char nextch, sys_close(pfd[1]); } if (exit_status == -1) { - exit_status = sys_execv(words[0], words); - } else if (exit_status == -2) { - dprintf(2, "exit: numeric argument required\n"); + // normal command execution + int r = sys_execv(words[0], words); + dprintf(2, "%s: execv failed (error %d)\n", words[0], r); exit_status = 1; + } else { + // `exit` command + if (exit_status == -2) { + dprintf(2, "exit: numeric argument required\n"); + exit_status = 1; + } } sys_exit(exit_status); } diff --git a/p-testwritefs2.cc b/p-testwritefs2.cc index ec039df..00ca3c4 100644 --- a/p-testwritefs2.cc +++ b/p-testwritefs2.cc @@ -222,8 +222,8 @@ void process_main() { sys_close(f); - // extend the file to use an indirect block - printf("%s:%d: extend to indirect", __FILE__, __LINE__); + // make a big file + printf("%s:%d: extend more", __FILE__, __LINE__); size_t bbsz = prepare_bigbuf(); diff --git a/run-docker b/run-docker new file mode 100755 index 0000000..e8b0e28 --- /dev/null +++ b/run-docker @@ -0,0 +1,74 @@ +#! /bin/bash + +maindir="" +destdir=chickadee + +fresh= +if test "$1" = "-f" -o "$1" = "--fresh"; then + fresh=1 + shift +fi +if test "$#" -ne 0; then + echo "Usage: run-docker [-f | --fresh]" 1>&2 + exit 1 +fi + +if stat --format %i / >/dev/null 2>&1; then + statformatarg="--format" +else + statformatarg="-f" +fi +myfileid=`stat $statformatarg %d:%i "${BASH_SOURCE[0]}" 2>/dev/null` + +dir="`pwd`" +subdir="" +while test "$dir" != / -a "$dir" != ""; do + thisfileid=`stat $statformatarg %d:%i "$dir"/run-docker 2>/dev/null` + if test -n "$thisfileid" -a "$thisfileid" = "$myfileid"; then + maindir="$dir" + break + fi + subdir="/`basename "$dir"`$subdir" + dir="`dirname "$dir"`" +done + +if test -z "$maindir" && expr "${BASH_SOURCE[0]}" : / >/dev/null 2>&1; then + maindir="`dirname "${BASH_SOURCE[0]}"`" + subdir="" +fi + +if test -n "$maindir" -a -z "$fresh"; then + existing_image="`docker ps -f status=running -f ancestor=cs61:latest -f volume=/host_mnt"$maindir" --no-trunc --format "{{.CreatedAt}},{{.ID}}" | sort -r | head -n 1`" + if test -n "$existing_image"; then + created_at="`echo $existing_image | sed 's/,.*//'`" + image="`echo $existing_image | sed 's/^.*,//'`" + image12="`echo $image | head -c 12`" + echo "* Using running container $image12, created $created_at" 1>&2 + echo "- To start a new container, exit then \`./run-docker -f\`" 1>&2 + echo "- To kill this container, exit then \`docker kill $image12\`" 1>&2 + exec docker exec -it $image /bin/bash + fi +fi + +netarg= +if test `uname` = Darwin; then + if ! netstat -n -a -p tcp | grep '\.6169[ ].*LISTEN' >/dev/null; then + netarg="$netarg "'--expose=6169/tcp -p 6169:6169/tcp' + fi + if ! netstat -n -a -p tcp | grep '\.12949[ ].*LISTEN' >/dev/null; then + netarg="$netarg "'--expose=12949/tcp -p 12949:12949/tcp' + fi +elif test -x /bin/netstat; then + if ! netstat -n -a -p tcp | grep '\.6169[ ].*LISTEN' >/dev/null; then + netarg="$netarg "'--expose=6169/tcp -p 6169:6169/tcp' + fi + if ! netstat -n -l -t | grep ':12949[ ]' >/dev/null; then + netarg="$netarg "'--expose=12949/tcp -p 12949:12949/tcp' + fi +fi + +if test -n "$maindir"; then + exec docker run -it --rm --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -v "$maindir":/home/cs61-user/$destdir -w "/home/cs61-user/$destdir$subdir" $netarg cs61:latest +else + exec docker run -it --rm --cap-add=SYS_PTRACE --security-opt seccomp=unconfined $netarg cs61:latest +fi diff --git a/u-lib.cc b/u-lib.cc index ef249cb..142fb21 100644 --- a/u-lib.cc +++ b/u-lib.cc @@ -55,10 +55,13 @@ int error_vprintf(int cpos, int color, const char* format, va_list val) { return console_vprintf(cpos, color, format, val); } -void assert_fail(const char* file, int line, const char* msg) { - error_printf(CPOS(23, 0), COLOR_ERROR, - "%s:%d: user assertion '%s' failed\n", - file, line, msg); +void assert_fail(const char* file, int line, const char* msg, + const char* description) { + cursorpos = CPOS(23, 0); + if (description) { + error_printf("%s:%d: %s\n", file, line, description); + } + error_printf("%s:%d: user assertion '%s' failed\n", file, line, msg); sys_panic(nullptr); } diff --git a/u-lib.hh b/u-lib.hh index ac25109..9d6f5af 100644 --- a/u-lib.hh +++ b/u-lib.hh @@ -14,8 +14,7 @@ // make_syscall, access_memory, clobber_memory // These functions define the Chickadee system call calling convention. -__attribute__((always_inline)) -inline uintptr_t make_syscall(int syscallno) { +__always_inline uintptr_t make_syscall(int syscallno) { register uintptr_t rax asm("rax") = syscallno; asm volatile ("syscall" : "+a" (rax) @@ -24,8 +23,7 @@ inline uintptr_t make_syscall(int syscallno) { return rax; } -__attribute__((always_inline)) -inline uintptr_t make_syscall(int syscallno, uintptr_t arg0) { +__always_inline uintptr_t make_syscall(int syscallno, uintptr_t arg0) { register uintptr_t rax asm("rax") = syscallno; asm volatile ("syscall" : "+a" (rax), "+D" (arg0) @@ -34,9 +32,8 @@ inline uintptr_t make_syscall(int syscallno, uintptr_t arg0) { return rax; } -__attribute__((always_inline)) -inline uintptr_t make_syscall(int syscallno, uintptr_t arg0, - uintptr_t arg1) { +__always_inline uintptr_t make_syscall(int syscallno, uintptr_t arg0, + uintptr_t arg1) { register uintptr_t rax asm("rax") = syscallno; asm volatile ("syscall" : "+a" (rax), "+D" (arg0), "+S" (arg1) @@ -45,9 +42,8 @@ inline uintptr_t make_syscall(int syscallno, uintptr_t arg0, return rax; } -__attribute__((always_inline)) -inline uintptr_t make_syscall(int syscallno, uintptr_t arg0, - uintptr_t arg1, uintptr_t arg2) { +__always_inline uintptr_t make_syscall(int syscallno, uintptr_t arg0, + uintptr_t arg1, uintptr_t arg2) { register uintptr_t rax asm("rax") = syscallno; asm volatile ("syscall" : "+a" (rax), "+D" (arg0), "+S" (arg1), "+d" (arg2) @@ -56,10 +52,9 @@ inline uintptr_t make_syscall(int syscallno, uintptr_t arg0, return rax; } -__attribute__((always_inline)) -inline uintptr_t make_syscall(int syscallno, uintptr_t arg0, - uintptr_t arg1, uintptr_t arg2, - uintptr_t arg3) { +__always_inline uintptr_t make_syscall(int syscallno, uintptr_t arg0, + uintptr_t arg1, uintptr_t arg2, + uintptr_t arg3) { register uintptr_t rax asm("rax") = syscallno; register uintptr_t r10 asm("r10") = arg3; asm volatile ("syscall" @@ -69,13 +64,11 @@ inline uintptr_t make_syscall(int syscallno, uintptr_t arg0, return rax; } -__attribute__((always_inline)) -inline void clobber_memory(void* ptr) { +__always_inline void clobber_memory(void* ptr) { asm volatile ("" : "+m" (*(char*) ptr)); } -__attribute__((always_inline)) -inline void access_memory(const void* ptr) { +__always_inline void access_memory(const void* ptr) { asm volatile ("" : : "m" (*(const char*) ptr)); }