Commit 2e1d8076 authored by Eddie Kohler's avatar Eddie Kohler

Initial handout code for 2021.

parent 00bfb093
......@@ -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)
......
......@@ -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
......@@ -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
......
#! /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
......@@ -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-%
......
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
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/
......@@ -136,7 +136,7 @@ class ioapicstate {
inline lapicstate& lapicstate::get() {
return *reinterpret_cast<lapicstate*>(pa2ka(lapic_pa));
return *pa2kptr<lapicstate*>(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<ioapicstate*>(pa2ka(ioapic_pa));
return *pa2kptr<ioapicstate*>(ioapic_pa);
}
inline uint32_t ioapicstate::id() const {
return read(reg_id) >> 24;
......
......@@ -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;
......
......@@ -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<int> 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();
}
}
......
......@@ -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<bool> cursor_show_ = true;
std::atomic<int> displayed_cpos_ = -1;
};
......
......@@ -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<