├── .gitignore ├── README.md ├── lesson_1 ├── L0-amgame │ ├── Makefile │ ├── Makefile.lab │ ├── abstract-machine │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README │ │ ├── am │ │ │ ├── Makefile │ │ │ ├── include │ │ │ │ ├── am.h │ │ │ │ ├── amdev.h │ │ │ │ └── arch │ │ │ │ │ ├── mips32-nemu.h │ │ │ │ │ ├── native.h │ │ │ │ │ ├── riscv32-nemu.h │ │ │ │ │ ├── riscv64-mycpu.h │ │ │ │ │ ├── riscv64-nemu.h │ │ │ │ │ ├── spike.h │ │ │ │ │ ├── x86-nemu.h │ │ │ │ │ ├── x86-qemu.h │ │ │ │ │ └── x86_64-qemu.h │ │ │ └── src │ │ │ │ ├── native │ │ │ │ ├── cte.c │ │ │ │ ├── ioe.c │ │ │ │ ├── ioe │ │ │ │ │ ├── audio.c │ │ │ │ │ ├── disk.c │ │ │ │ │ ├── gpu.c │ │ │ │ │ ├── input.c │ │ │ │ │ └── timer.c │ │ │ │ ├── mpe.c │ │ │ │ ├── platform.c │ │ │ │ ├── platform.h │ │ │ │ ├── trap.S │ │ │ │ ├── trm.c │ │ │ │ └── vme.c │ │ │ │ └── x86 │ │ │ │ ├── nemu │ │ │ │ ├── cte.c │ │ │ │ ├── start.S │ │ │ │ ├── trap.S │ │ │ │ └── vme.c │ │ │ │ ├── qemu │ │ │ │ ├── boot │ │ │ │ │ ├── Makefile │ │ │ │ │ ├── genboot.py │ │ │ │ │ ├── main.c │ │ │ │ │ └── start.S │ │ │ │ ├── cte.c │ │ │ │ ├── ioe.c │ │ │ │ ├── mpe.c │ │ │ │ ├── start32.S │ │ │ │ ├── start64.S │ │ │ │ ├── trap32.S │ │ │ │ ├── trap64.S │ │ │ │ ├── trm.c │ │ │ │ ├── vme.c │ │ │ │ └── x86-qemu.h │ │ │ │ └── x86.h │ │ ├── klib │ │ │ ├── Makefile │ │ │ ├── include │ │ │ │ ├── klib-macros.h │ │ │ │ └── klib.h │ │ │ └── src │ │ │ │ ├── cpp.c │ │ │ │ ├── int64.c │ │ │ │ ├── stdio.c │ │ │ │ ├── stdlib.c │ │ │ │ └── string.c │ │ └── scripts │ │ │ ├── isa │ │ │ ├── x86.mk │ │ │ └── x86_64.mk │ │ │ ├── native.mk │ │ │ ├── platform │ │ │ └── qemu.mk │ │ │ ├── x86-qemu.mk │ │ │ └── x86_64-qemu.mk │ ├── amgame │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── include │ │ │ └── game.h │ │ └── src │ │ │ ├── game.c │ │ │ ├── keyboard.c │ │ │ └── video.c │ └── test │ │ ├── Makefile │ │ ├── main.c │ │ ├── main.o │ │ ├── say.c │ │ └── say.o ├── M1-pstree │ ├── README.md │ ├── a.out │ ├── pstree │ ├── pstree.c │ └── version ├── README.md ├── a.out ├── hanoi-nr.c ├── hanoi-r.c ├── hello-goodbye.c ├── logisim.c ├── main.c ├── seven-seg.py ├── simulator.c └── tryopen.c ├── lesson_2 ├── M2-libco │ └── libco │ │ ├── Makefile │ │ ├── README.md │ │ ├── co.c │ │ ├── co.h │ │ └── tests │ │ ├── Makefile │ │ ├── co-test.h │ │ └── main.c ├── README.md ├── alipay.c ├── dekker.py ├── hello.c ├── mem-ordering.c ├── model-checker.py ├── mutex-bad.py ├── note.md ├── peterson-barrier.c ├── peterson-flag.py ├── shm-test.c ├── stack-probe.c ├── sum.c └── thread.h ├── lesson_3 ├── README.md ├── a.txt ├── fish.c ├── fix-pc-cv.c ├── model-checker.py ├── pc-check.py ├── pc-cv.c ├── pc-cv.py ├── pc-sem.c ├── pc.c ├── sem.py ├── spinlock.py ├── sum-scalability.c ├── thread-sync.h └── thread.h ├── lesson_4 ├── fib.go ├── mandelbrot.c ├── producer-consumer.go ├── thread-sync.h └── thread.h ├── lesson_5 ├── ilp-demo.c ├── rdrand.c └── thread-os.c ├── lesson_6 ├── clone-example.c ├── clone.c ├── dsu.c ├── execve-demo.c ├── execve-fork.c ├── exit-demo.c ├── fork-and.c ├── fork-demo.c ├── fork-printf.c ├── linux-minimal.zip ├── linux-minimal │ ├── Makefile │ ├── build │ │ └── initramfs.cpio.gz │ ├── initramfs │ │ ├── bin │ │ │ └── busybox │ │ └── init │ └── vmlinuz ├── mmap-alloc.c ├── mmap-disk.py ├── vdso.c └── vertify_vfork.c ├── lesson_7 ├── env.c ├── memset-race.c └── sh-xv6.c ├── lesson_8 └── cow-test.c └── lesson_9 ├── popcount.c └── segfault.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.out 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NJU操作系统设计实现学习笔记 2 | 3 | [课程地址](https://jyywiki.cn/OS/2022/index.html) 4 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/Makefile: -------------------------------------------------------------------------------- 1 | export TOKEN := ??? 2 | 3 | # ----- DO NOT MODIFY ----- 4 | 5 | ifeq ($(NAME),) 6 | $(error Should make in each lab's directory) 7 | endif 8 | 9 | SRCS := $(shell find . -maxdepth 1 -name "*.c") 10 | DEPS := $(shell find . -maxdepth 1 -name "*.h") $(SRCS) 11 | CFLAGS += -O1 -std=gnu11 -ggdb -Wall -Werror -Wno-unused-result -Wno-unused-value -Wno-unused-variable 12 | 13 | .PHONY: all git test clean commit-and-make 14 | 15 | .DEFAULT_GOAL := commit-and-make 16 | commit-and-make: git all 17 | 18 | $(NAME)-64: $(DEPS) # 64bit binary 19 | gcc -m64 $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) 20 | 21 | $(NAME)-32: $(DEPS) # 32bit binary 22 | gcc -m32 $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) 23 | 24 | $(NAME)-64.so: $(DEPS) # 64bit shared library 25 | gcc -fPIC -shared -m64 $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) 26 | 27 | $(NAME)-32.so: $(DEPS) # 32bit shared library 28 | gcc -fPIC -shared -m32 $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) 29 | 30 | clean: 31 | rm -f $(NAME)-64 $(NAME)-32 $(NAME)-64.so $(NAME)-32.so 32 | 33 | include ../Makefile.lab 34 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/Makefile.lab: -------------------------------------------------------------------------------- 1 | # ----- DO NOT MODIFY ----- 2 | 3 | export COURSE := OS2022 4 | URL := 'http://jyywiki.cn/static/submit-os2022.sh' 5 | 6 | submit: 7 | @cd $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) && \ 8 | curl -sSLf '$(URL)' && \ 9 | curl -sSLf '$(URL)' | bash 10 | 11 | git: 12 | @git add $(shell find . -name "*.c") $(shell find . -name "*.h") -A --ignore-errors 13 | @while (test -e .git/index.lock); do sleep 0.1; done 14 | @(uname -a && uptime) | git commit -F - -q --author='tracer-nju ' --no-verify --allow-empty 15 | @sync 16 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*/ 3 | !*.h 4 | !*.c 5 | !*.cc 6 | !*.S 7 | !*.ld 8 | !*.sh 9 | !*.py 10 | !*.mk 11 | !Makefile 12 | !README 13 | !LICENSE 14 | .* 15 | _* 16 | *~ 17 | build/ 18 | !.gitignore 19 | .vscode -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/LICENSE: -------------------------------------------------------------------------------- 1 | The AbstractMachine software is: 2 | 3 | Copyright (c) 2018-2021 Yanyan Jiang and Zihao Yu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for AbstractMachine Kernels and Libraries 2 | 3 | ### *Get a more readable version of this Makefile* by `make html` (requires python-markdown) 4 | html: 5 | cat Makefile | sed 's/^\([^#]\)/ \1/g' | markdown_py > Makefile.html 6 | .PHONY: html 7 | 8 | ## 1. Basic Setup and Checks 9 | 10 | ### Default to create a bare-metal kernel image 11 | ifeq ($(MAKECMDGOALS),) 12 | MAKECMDGOALS = image 13 | .DEFAULT_GOAL = image 14 | endif 15 | 16 | ### Override checks when `make clean/clean-all/html` 17 | ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html),) 18 | 19 | ### Print build info message 20 | $(info # Building $(NAME)-$(MAKECMDGOALS) [$(ARCH)]) 21 | 22 | ### Check: environment variable `$AM_HOME` looks sane 23 | ifeq ($(wildcard $(AM_HOME)/am/include/am.h),) 24 | $(error $$AM_HOME must be an AbstractMachine repo) 25 | endif 26 | 27 | ### Check: environment variable `$ARCH` must be in the supported list 28 | ARCHS = $(basename $(notdir $(shell ls $(AM_HOME)/scripts/*.mk))) 29 | ifeq ($(filter $(ARCHS), $(ARCH)), ) 30 | $(error Expected $$ARCH in {$(ARCHS)}, Got "$(ARCH)") 31 | endif 32 | 33 | ### Extract instruction set architecture (`ISA`) and platform from `$ARCH`. Example: `ARCH=x86_64-qemu -> ISA=x86_64; PLATFORM=qemu` 34 | ARCH_SPLIT = $(subst -, ,$(ARCH)) 35 | ISA = $(word 1,$(ARCH_SPLIT)) 36 | PLATFORM = $(word 2,$(ARCH_SPLIT)) 37 | 38 | ### Check if there is something to build 39 | ifeq ($(flavor SRCS), undefined) 40 | $(error Nothing to build) 41 | endif 42 | 43 | ### Checks end here 44 | endif 45 | 46 | ## 2. General Compilation Targets 47 | 48 | ### Create the destination directory (`build/$ARCH`) 49 | WORK_DIR = $(shell pwd) 50 | DST_DIR = $(WORK_DIR)/build/$(ARCH) 51 | $(shell mkdir -p $(DST_DIR)) 52 | 53 | ### Compilation targets (a binary image or archive) 54 | IMAGE_REL = build/$(NAME)-$(ARCH) 55 | IMAGE = $(abspath $(IMAGE_REL)) 56 | ARCHIVE = $(WORK_DIR)/build/$(NAME)-$(ARCH).a 57 | 58 | ### Collect the files to be linked: object files (`.o`) and libraries (`.a`) 59 | OBJS = $(addprefix $(DST_DIR)/, $(addsuffix .o, $(basename $(SRCS)))) 60 | LIBS := $(sort $(LIBS) am klib) # lazy evaluation ("=") causes infinite recursions 61 | LINKAGE = $(OBJS) \ 62 | $(addsuffix -$(ARCH).a, $(join \ 63 | $(addsuffix /build/, $(addprefix $(AM_HOME)/, $(LIBS))), \ 64 | $(LIBS) )) 65 | 66 | ## 3. General Compilation Flags 67 | 68 | ### (Cross) compilers, e.g., mips-linux-gnu-g++ 69 | AS = $(CROSS_COMPILE)gcc 70 | CC = $(CROSS_COMPILE)gcc 71 | CXX = $(CROSS_COMPILE)g++ 72 | LD = $(CROSS_COMPILE)ld 73 | OBJDUMP = $(CROSS_COMPILE)objdump 74 | OBJCOPY = $(CROSS_COMPILE)objcopy 75 | READELF = $(CROSS_COMPILE)readelf 76 | 77 | ### Compilation flags 78 | INC_PATH += $(WORK_DIR)/include $(addsuffix /include/, $(addprefix $(AM_HOME)/, $(LIBS))) 79 | INCFLAGS += $(addprefix -I, $(INC_PATH)) 80 | 81 | CFLAGS += -O2 -MMD -Wall -Werror $(INCFLAGS) \ 82 | -D__ISA__=\"$(ISA)\" -D__ISA_$(shell echo $(ISA) | tr a-z A-Z)__ \ 83 | -D__ARCH__=$(ARCH) -D__ARCH_$(shell echo $(ARCH) | tr a-z A-Z | tr - _) \ 84 | -D__PLATFORM__=$(PLATFORM) -D__PLATFORM_$(shell echo $(PLATFORM) | tr a-z A-Z | tr - _) \ 85 | -DARCH_H=\"arch/$(ARCH).h\" \ 86 | -fno-asynchronous-unwind-tables -fno-builtin -fno-stack-protector \ 87 | -Wno-main -U_FORTIFY_SOURCE 88 | CXXFLAGS += $(CFLAGS) -ffreestanding -fno-rtti -fno-exceptions 89 | ASFLAGS += -MMD $(INCFLAGS) 90 | 91 | ## 4. Arch-Specific Configurations 92 | 93 | ### Paste in arch-specific configurations (e.g., from `scripts/x86_64-qemu.mk`) 94 | -include $(AM_HOME)/scripts/$(ARCH).mk 95 | 96 | ### Fall back to native gcc/binutils if there is no cross compiler 97 | ifeq ($(wildcard $(shell which $(CC))),) 98 | $(info # $(CC) not found; fall back to default gcc and binutils) 99 | CROSS_COMPILE := 100 | endif 101 | 102 | ## 5. Compilation Rules 103 | 104 | ### Rule (compile): a single `.c` -> `.o` (gcc) 105 | $(DST_DIR)/%.o: %.c 106 | @mkdir -p $(dir $@) && echo + CC $< 107 | @$(CC) -std=gnu11 $(CFLAGS) -c -o $@ $(realpath $<) 108 | 109 | ### Rule (compile): a single `.cc` -> `.o` (g++) 110 | $(DST_DIR)/%.o: %.cc 111 | @mkdir -p $(dir $@) && echo + CXX $< 112 | @$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<) 113 | 114 | ### Rule (compile): a single `.cpp` -> `.o` (g++) 115 | $(DST_DIR)/%.o: %.cpp 116 | @mkdir -p $(dir $@) && echo + CXX $< 117 | @$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<) 118 | 119 | ### Rule (compile): a single `.S` -> `.o` (gcc, which preprocesses and calls as) 120 | $(DST_DIR)/%.o: %.S 121 | @mkdir -p $(dir $@) && echo + AS $< 122 | @$(AS) $(ASFLAGS) -c -o $@ $(realpath $<) 123 | 124 | ### Rule (recursive make): build a dependent library (am, klib, ...) 125 | $(LIBS): %: 126 | @$(MAKE) -s -C $(AM_HOME)/$* archive 127 | 128 | ### Rule (link): objects (`*.o`) and libraries (`*.a`) -> `IMAGE.elf`, the final ELF binary to be packed into image (ld) 129 | $(IMAGE).elf: $(OBJS) am $(LIBS) 130 | @echo + LD "->" $(IMAGE_REL).elf 131 | @$(LD) $(LDFLAGS) -o $(IMAGE).elf --start-group $(LINKAGE) --end-group 132 | 133 | ### Rule (archive): objects (`*.o`) -> `ARCHIVE.a` (ar) 134 | $(ARCHIVE): $(OBJS) 135 | @echo + AR "->" $(shell realpath $@ --relative-to .) 136 | @ar rcs $(ARCHIVE) $(OBJS) 137 | 138 | ### Rule (`#include` dependencies): paste in `.d` files generated by gcc on `-MMD` 139 | -include $(addprefix $(DST_DIR)/, $(addsuffix .d, $(basename $(SRCS)))) 140 | 141 | ## 6. Miscellaneous 142 | 143 | ### Build order control 144 | image: image-dep 145 | archive: $(ARCHIVE) 146 | image-dep: $(OBJS) am $(LIBS) 147 | @echo \# Creating image [$(ARCH)] 148 | .PHONY: image image-dep archive run $(LIBS) 149 | 150 | ### Clean a single project (remove `build/`) 151 | clean: 152 | rm -rf Makefile.html $(WORK_DIR)/build/ 153 | .PHONY: clean 154 | 155 | ### Clean all sub-projects within depth 2 (and ignore errors) 156 | CLEAN_ALL = $(dir $(shell find . -mindepth 2 -name Makefile)) 157 | clean-all: $(CLEAN_ALL) clean 158 | $(CLEAN_ALL): 159 | -@$(MAKE) -s -C $@ clean 160 | .PHONY: clean-all $(CLEAN_ALL) 161 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/README: -------------------------------------------------------------------------------- 1 | AbstractMachine is a minimal, modularized, and machine-independent 2 | abstraction layer of the computer hardware: 3 | 4 | * physical memory and direct execution (The "Turing Machine"); 5 | * basic model for input and output devices (I/O Extension); 6 | * interrupt/exception and processor context management (Context Extension); 7 | * virtual memory and protection (Virtual Memory Extension); 8 | * multiprocessing (Multiprocessing Extension). 9 | 10 | CONTACTS 11 | 12 | Bug reports and suggestions go to Yanyan Jiang (jyy@nju.edu.cn) and Zihao 13 | Yu (yuzihao@ict.ac.cn). 14 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/Makefile: -------------------------------------------------------------------------------- 1 | NAME := am 2 | SRCS = $(addprefix src/, $(AM_SRCS)) 3 | INC_PATH += $(AM_HOME)/am/src 4 | 5 | include $(AM_HOME)/Makefile 6 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/am.h: -------------------------------------------------------------------------------- 1 | #ifndef AM_H__ 2 | #define AM_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include ARCH_H // this macro is defined in $CFLAGS 8 | // examples: "arch/x86-qemu.h", "arch/native.h", ... 9 | 10 | // Memory protection flags 11 | #define MMAP_NONE 0x00000000 // no access 12 | #define MMAP_READ 0x00000001 // can read 13 | #define MMAP_WRITE 0x00000002 // can write 14 | 15 | // Memory area for [@start, @end) 16 | typedef struct { 17 | void *start, *end; 18 | } Area; 19 | 20 | // Arch-dependent processor context 21 | typedef struct Context Context; 22 | 23 | // An event of type @event, caused by @cause of pointer @ref 24 | typedef struct { 25 | enum { 26 | EVENT_NULL = 0, 27 | EVENT_YIELD, EVENT_SYSCALL, EVENT_PAGEFAULT, EVENT_ERROR, 28 | EVENT_IRQ_TIMER, EVENT_IRQ_IODEV, 29 | } event; 30 | uintptr_t cause, ref; 31 | const char *msg; 32 | } Event; 33 | 34 | // A protected address space with user memory @area 35 | // and arch-dependent @ptr 36 | typedef struct { 37 | int pgsize; 38 | Area area; 39 | void *ptr; 40 | } AddrSpace; 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | // ----------------------- TRM: Turing Machine ----------------------- 47 | extern Area heap; 48 | void putch (char ch); 49 | void halt (int code) __attribute__((__noreturn__)); 50 | 51 | // -------------------- IOE: Input/Output Devices -------------------- 52 | bool ioe_init (void); 53 | void ioe_read (int reg, void *buf); 54 | void ioe_write (int reg, void *buf); 55 | #include "amdev.h" 56 | 57 | // ---------- CTE: Interrupt Handling and Context Switching ---------- 58 | bool cte_init (Context *(*handler)(Event ev, Context *ctx)); 59 | void yield (void); 60 | bool ienabled (void); 61 | void iset (bool enable); 62 | Context *kcontext (Area kstack, void (*entry)(void *), void *arg); 63 | 64 | // ----------------------- VME: Virtual Memory ----------------------- 65 | bool vme_init (void *(*pgalloc)(int), void (*pgfree)(void *)); 66 | void protect (AddrSpace *as); 67 | void unprotect (AddrSpace *as); 68 | void map (AddrSpace *as, void *vaddr, void *paddr, int prot); 69 | Context *ucontext (AddrSpace *as, Area kstack, void *entry); 70 | 71 | // ---------------------- MPE: Multi-Processing ---------------------- 72 | bool mpe_init (void (*entry)()); 73 | int cpu_count (void); 74 | int cpu_current (void); 75 | int atomic_xchg (int *addr, int newval); 76 | 77 | #ifdef __cplusplus 78 | } 79 | #endif 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/amdev.h: -------------------------------------------------------------------------------- 1 | #ifndef __AMDEV_H__ 2 | #define __AMDEV_H__ 3 | 4 | // **MAY SUBJECT TO CHANGE IN THE FUTURE** 5 | 6 | #define AM_DEVREG(id, reg, perm, ...) \ 7 | enum { AM_##reg = (id) }; \ 8 | typedef struct { __VA_ARGS__; } AM_##reg##_T; 9 | 10 | AM_DEVREG( 1, UART_CONFIG, RD, bool present); 11 | AM_DEVREG( 2, UART_TX, WR, char data); 12 | AM_DEVREG( 3, UART_RX, RD, char data); 13 | AM_DEVREG( 4, TIMER_CONFIG, RD, bool present, has_rtc); 14 | AM_DEVREG( 5, TIMER_RTC, RD, int year, month, day, hour, minute, second); 15 | AM_DEVREG( 6, TIMER_UPTIME, RD, uint64_t us); 16 | AM_DEVREG( 7, INPUT_CONFIG, RD, bool present); 17 | AM_DEVREG( 8, INPUT_KEYBRD, RD, bool keydown; int keycode); 18 | AM_DEVREG( 9, GPU_CONFIG, RD, bool present, has_accel; int width, height, vmemsz); 19 | AM_DEVREG(10, GPU_STATUS, RD, bool ready); 20 | AM_DEVREG(11, GPU_FBDRAW, WR, int x, y; void *pixels; int w, h; bool sync); 21 | AM_DEVREG(12, GPU_MEMCPY, WR, uint32_t dest; void *src; int size); 22 | AM_DEVREG(13, GPU_RENDER, WR, uint32_t root); 23 | AM_DEVREG(14, AUDIO_CONFIG, RD, bool present; int bufsize); 24 | AM_DEVREG(15, AUDIO_CTRL, WR, int freq, channels, samples); 25 | AM_DEVREG(16, AUDIO_STATUS, RD, int count); 26 | AM_DEVREG(17, AUDIO_PLAY, WR, Area buf); 27 | AM_DEVREG(18, DISK_CONFIG, RD, bool present; int blksz, blkcnt); 28 | AM_DEVREG(19, DISK_STATUS, RD, bool ready); 29 | AM_DEVREG(20, DISK_BLKIO, WR, bool write; void *buf; int blkno, blkcnt); 30 | AM_DEVREG(21, NET_CONFIG, RD, bool present); 31 | AM_DEVREG(22, NET_STATUS, RD, int rx_len, tx_len); 32 | AM_DEVREG(23, NET_TX, WR, Area buf); 33 | AM_DEVREG(24, NET_RX, WR, Area buf); 34 | 35 | // Input 36 | 37 | #define AM_KEYS(_) \ 38 | _(ESCAPE) _(F1) _(F2) _(F3) _(F4) _(F5) _(F6) _(F7) _(F8) _(F9) _(F10) _(F11) _(F12) \ 39 | _(GRAVE) _(1) _(2) _(3) _(4) _(5) _(6) _(7) _(8) _(9) _(0) _(MINUS) _(EQUALS) _(BACKSPACE) \ 40 | _(TAB) _(Q) _(W) _(E) _(R) _(T) _(Y) _(U) _(I) _(O) _(P) _(LEFTBRACKET) _(RIGHTBRACKET) _(BACKSLASH) \ 41 | _(CAPSLOCK) _(A) _(S) _(D) _(F) _(G) _(H) _(J) _(K) _(L) _(SEMICOLON) _(APOSTROPHE) _(RETURN) \ 42 | _(LSHIFT) _(Z) _(X) _(C) _(V) _(B) _(N) _(M) _(COMMA) _(PERIOD) _(SLASH) _(RSHIFT) \ 43 | _(LCTRL) _(APPLICATION) _(LALT) _(SPACE) _(RALT) _(RCTRL) \ 44 | _(UP) _(DOWN) _(LEFT) _(RIGHT) _(INSERT) _(DELETE) _(HOME) _(END) _(PAGEUP) _(PAGEDOWN) 45 | 46 | #define AM_KEY_NAMES(key) AM_KEY_##key, 47 | enum { 48 | AM_KEY_NONE = 0, 49 | AM_KEYS(AM_KEY_NAMES) 50 | }; 51 | 52 | // GPU 53 | 54 | #define AM_GPU_TEXTURE 1 55 | #define AM_GPU_SUBTREE 2 56 | #define AM_GPU_NULL 0xffffffff 57 | 58 | typedef uint32_t gpuptr_t; 59 | 60 | struct gpu_texturedesc { 61 | uint16_t w, h; 62 | gpuptr_t pixels; 63 | } __attribute__((packed)); 64 | 65 | struct gpu_canvas { 66 | uint16_t type, w, h, x1, y1, w1, h1; 67 | gpuptr_t sibling; 68 | union { 69 | gpuptr_t child; 70 | struct gpu_texturedesc texture; 71 | }; 72 | } __attribute__((packed)); 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/arch/mips32-nemu.h: -------------------------------------------------------------------------------- 1 | #ifndef __ARCH_H__ 2 | #define __ARCH_H__ 3 | 4 | struct Context { 5 | // TODO: fix the order of these members to match trap.S 6 | uintptr_t hi, gpr[32], epc, cause, lo, status; 7 | void *pdir; 8 | }; 9 | 10 | #define GPR1 gpr[2] // v0 11 | #define GPR2 gpr[0] 12 | #define GPR3 gpr[0] 13 | #define GPR4 gpr[0] 14 | #define GPRx gpr[0] 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/arch/native.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_H__ 2 | #define ARCH_H__ 3 | 4 | #ifndef __USE_GNU 5 | # define __USE_GNU 6 | #endif 7 | 8 | #include 9 | 10 | struct Context { 11 | uintptr_t ksp; 12 | void *vm_head; 13 | ucontext_t uc; 14 | // skip the red zone of the stack frame, see the amd64 ABI manual for details 15 | uint8_t redzone[128]; 16 | }; 17 | 18 | #define GPR1 uc.uc_mcontext.gregs[REG_RDI] 19 | #define GPR2 uc.uc_mcontext.gregs[REG_RSI] 20 | #define GPR3 uc.uc_mcontext.gregs[REG_RDX] 21 | #define GPR4 uc.uc_mcontext.gregs[REG_RCX] 22 | #define GPRx uc.uc_mcontext.gregs[REG_RAX] 23 | 24 | #undef __USE_GNU 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/arch/riscv32-nemu.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_H__ 2 | #define ARCH_H__ 3 | 4 | struct Context { 5 | // TODO: fix the order of these members to match trap.S 6 | uintptr_t mepc, mcause, gpr[32], mstatus; 7 | void *pdir; 8 | }; 9 | 10 | #define GPR1 gpr[17] // a7 11 | #define GPR2 gpr[0] 12 | #define GPR3 gpr[0] 13 | #define GPR4 gpr[0] 14 | #define GPRx gpr[0] 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/arch/riscv64-mycpu.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_H__ 2 | #define ARCH_H__ 3 | 4 | struct Context { 5 | // TODO: fix the order of these members to match trap.S 6 | uintptr_t mepc, mcause, gpr[32], mstatus; 7 | }; 8 | 9 | #define GPR1 gpr[17] // a7 10 | #define GPR2 gpr[0] 11 | #define GPR3 gpr[0] 12 | #define GPR4 gpr[0] 13 | #define GPRx gpr[0] 14 | #endif 15 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/arch/riscv64-nemu.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_H__ 2 | #define ARCH_H__ 3 | 4 | struct Context { 5 | // TODO: fix the order of these members to match trap.S 6 | uintptr_t mepc, mcause, gpr[32], mstatus; 7 | void *pdir; 8 | }; 9 | 10 | #define GPR1 gpr[17] // a7 11 | #define GPR2 gpr[0] 12 | #define GPR3 gpr[0] 13 | #define GPR4 gpr[0] 14 | #define GPRx gpr[0] 15 | #endif 16 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/arch/spike.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_H__ 2 | #define ARCH_H__ 3 | 4 | struct Context { 5 | uintptr_t gpr[1]; 6 | }; 7 | 8 | #define GPR1 gpr[0] 9 | #define GPR2 gpr[0] 10 | #define GPR3 gpr[0] 11 | #define GPR4 gpr[0] 12 | #define GPRx gpr[0] 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/arch/x86-nemu.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_H__ 2 | #define ARCH_H__ 3 | 4 | struct Context { 5 | // TODO: fix the order of these members to match trap.S 6 | uintptr_t esi, ebx, eax, eip, edx, eflags, ecx, cs, esp, edi, ebp; 7 | void *cr3; 8 | int irq; 9 | }; 10 | 11 | #define GPR1 eax 12 | #define GPR2 eip 13 | #define GPR3 eip 14 | #define GPR4 eip 15 | #define GPRx eip 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/arch/x86-qemu.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_H__ 2 | #define ARCH_H__ 3 | 4 | struct Context { 5 | void *cr3; 6 | uint32_t ds, eax, ebx, ecx, edx, 7 | esp0, esi, edi, ebp, 8 | eip, cs, eflags, esp, ss3; 9 | }; 10 | 11 | #define GPR1 eax 12 | #define GPR2 ebx 13 | #define GPR3 ecx 14 | #define GPR4 edx 15 | #define GPRx eax 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/include/arch/x86_64-qemu.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_H__ 2 | #define ARCH_H__ 3 | 4 | struct Context { 5 | void *cr3; 6 | uint64_t rax, rbx, rcx, rdx, 7 | rbp, rsi, rdi, 8 | r8, r9, r10, r11, 9 | r12, r13, r14, r15, 10 | rip, cs, rflags, 11 | rsp, ss, rsp0; 12 | }; 13 | 14 | 15 | #define GPR1 rdi 16 | #define GPR2 rsi 17 | #define GPR3 rdx 18 | #define GPR4 rcx 19 | #define GPRx rax 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/cte.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "platform.h" 4 | 5 | #define TIMER_HZ 100 6 | #define SYSCALL_INSTR_LEN 7 7 | 8 | static Context* (*user_handler)(Event, Context*) = NULL; 9 | 10 | void __am_kcontext_start(); 11 | void __am_switch(Context *c); 12 | int __am_in_userspace(void *addr); 13 | void __am_pmem_protect(); 14 | void __am_pmem_unprotect(); 15 | 16 | void __am_panic_on_return() { panic("should not reach here\n"); } 17 | 18 | static void irq_handle(Context *c) { 19 | c->vm_head = thiscpu->vm_head; 20 | c->ksp = thiscpu->ksp; 21 | 22 | if (thiscpu->ev.event == EVENT_ERROR) { 23 | uintptr_t rip = c->uc.uc_mcontext.gregs[REG_RIP]; 24 | printf("Unhandle signal '%s' at rip = %p, badaddr = %p, cause = 0x%x\n", 25 | thiscpu->ev.msg, rip, thiscpu->ev.ref, thiscpu->ev.cause); 26 | assert(0); 27 | } 28 | c = user_handler(thiscpu->ev, c); 29 | assert(c != NULL); 30 | 31 | __am_switch(c); 32 | 33 | // magic call to restore context 34 | void (*p)(Context *c) = (void *)(uintptr_t)0x100008; 35 | p(c); 36 | __am_panic_on_return(); 37 | } 38 | 39 | static void setup_stack(uintptr_t event, ucontext_t *uc) { 40 | void *rip = (void *)uc->uc_mcontext.gregs[REG_RIP]; 41 | extern uint8_t _start, _etext; 42 | int trap_from_user = __am_in_userspace(rip); 43 | int signal_safe = IN_RANGE(rip, RANGE(&_start, &_etext)) || trap_from_user || 44 | // Hack here: "+13" points to the instruction after syscall. This is the 45 | // instruction which will trigger the pending signal if interrupt is enabled. 46 | (rip == (void *)&sigprocmask + 13); 47 | 48 | if (((event == EVENT_IRQ_IODEV) || (event == EVENT_IRQ_TIMER)) && !signal_safe) { 49 | // Shared libraries contain code which are not reenterable. 50 | // If the signal comes when executing code in shared libraries, 51 | // the signal handler can not call any function which is not signal-safe, 52 | // else the behavior is undefined (may be dead lock). 53 | // To handle this, we just refuse to handle the signal and return directly 54 | // to pretend missing the interrupt. 55 | // See man 7 signal-safety for more information. 56 | return; 57 | } 58 | 59 | if (trap_from_user) __am_pmem_unprotect(); 60 | 61 | // skip the instructions causing SIGSEGV for syscall 62 | if (event == EVENT_SYSCALL) { rip += SYSCALL_INSTR_LEN; } 63 | uc->uc_mcontext.gregs[REG_RIP] = (uintptr_t)rip; 64 | 65 | // switch to kernel stack if we were previously in user space 66 | uintptr_t rsp = trap_from_user ? thiscpu->ksp : uc->uc_mcontext.gregs[REG_RSP]; 67 | rsp -= sizeof(Context); 68 | // keep (rsp + 8) % 16 == 0 to support SSE 69 | if ((rsp + 8) % 16 != 0) rsp -= 8; 70 | Context *c = (void *)rsp; 71 | 72 | // save the context on the stack 73 | c->uc = *uc; 74 | 75 | // disable interrupt 76 | __am_get_intr_sigmask(&uc->uc_sigmask); 77 | 78 | // call irq_handle after returning from the signal handler 79 | uc->uc_mcontext.gregs[REG_RDI] = (uintptr_t)c; 80 | uc->uc_mcontext.gregs[REG_RIP] = (uintptr_t)irq_handle; 81 | uc->uc_mcontext.gregs[REG_RSP] = (uintptr_t)c; 82 | } 83 | 84 | static void iret(ucontext_t *uc) { 85 | Context *c = (void *)uc->uc_mcontext.gregs[REG_RDI]; 86 | // restore the context 87 | *uc = c->uc; 88 | thiscpu->ksp = c->ksp; 89 | if (__am_in_userspace((void *)uc->uc_mcontext.gregs[REG_RIP])) __am_pmem_protect(); 90 | } 91 | 92 | static void sig_handler(int sig, siginfo_t *info, void *ucontext) { 93 | thiscpu->ev = (Event) {0}; 94 | thiscpu->ev.event = EVENT_ERROR; 95 | switch (sig) { 96 | case SIGUSR1: thiscpu->ev.event = EVENT_IRQ_IODEV; break; 97 | case SIGUSR2: thiscpu->ev.event = EVENT_YIELD; break; 98 | case SIGVTALRM: thiscpu->ev.event = EVENT_IRQ_TIMER; break; 99 | case SIGSEGV: 100 | if (info->si_code == SEGV_ACCERR) { 101 | switch ((uintptr_t)info->si_addr) { 102 | case 0x100000: thiscpu->ev.event = EVENT_SYSCALL; break; 103 | case 0x100008: iret(ucontext); return; 104 | } 105 | } 106 | if (__am_in_userspace(info->si_addr)) { 107 | assert(thiscpu->ev.event == EVENT_ERROR); 108 | thiscpu->ev.event = EVENT_PAGEFAULT; 109 | switch (info->si_code) { 110 | case SEGV_MAPERR: thiscpu->ev.cause = MMAP_READ; break; 111 | // we do not support mapped user pages with MMAP_NONE 112 | case SEGV_ACCERR: thiscpu->ev.cause = MMAP_WRITE; break; 113 | default: assert(0); 114 | } 115 | thiscpu->ev.ref = (uintptr_t)info->si_addr; 116 | } 117 | break; 118 | } 119 | 120 | if (thiscpu->ev.event == EVENT_ERROR) { 121 | thiscpu->ev.ref = (uintptr_t)info->si_addr; 122 | thiscpu->ev.cause = (uintptr_t)info->si_code; 123 | thiscpu->ev.msg = strsignal(sig); 124 | } 125 | setup_stack(thiscpu->ev.event, ucontext); 126 | } 127 | 128 | // signal handlers are inherited across fork() 129 | static void install_signal_handler() { 130 | struct sigaction s; 131 | memset(&s, 0, sizeof(s)); 132 | s.sa_sigaction = sig_handler; 133 | s.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK; 134 | __am_get_intr_sigmask(&s.sa_mask); 135 | 136 | int ret = sigaction(SIGVTALRM, &s, NULL); 137 | assert(ret == 0); 138 | ret = sigaction(SIGUSR1, &s, NULL); 139 | assert(ret == 0); 140 | ret = sigaction(SIGUSR2, &s, NULL); 141 | assert(ret == 0); 142 | ret = sigaction(SIGSEGV, &s, NULL); 143 | assert(ret == 0); 144 | } 145 | 146 | // setitimer() are inherited across fork(), should be called again from children 147 | void __am_init_timer_irq() { 148 | iset(0); 149 | 150 | struct itimerval it = {}; 151 | it.it_value.tv_sec = 0; 152 | it.it_value.tv_usec = 1000000 / TIMER_HZ; 153 | it.it_interval = it.it_value; 154 | int ret = setitimer(ITIMER_VIRTUAL, &it, NULL); 155 | assert(ret == 0); 156 | } 157 | 158 | bool cte_init(Context*(*handler)(Event, Context*)) { 159 | user_handler = handler; 160 | 161 | install_signal_handler(); 162 | __am_init_timer_irq(); 163 | return true; 164 | } 165 | 166 | Context* kcontext(Area kstack, void (*entry)(void *), void *arg) { 167 | Context *c = (Context*)kstack.end - 1; 168 | 169 | __am_get_example_uc(c); 170 | c->uc.uc_mcontext.gregs[REG_RIP] = (uintptr_t)__am_kcontext_start; 171 | c->uc.uc_mcontext.gregs[REG_RSP] = (uintptr_t)kstack.end; 172 | 173 | int ret = sigemptyset(&(c->uc.uc_sigmask)); // enable interrupt 174 | assert(ret == 0); 175 | 176 | c->vm_head = NULL; 177 | 178 | c->GPR1 = (uintptr_t)arg; 179 | c->GPR2 = (uintptr_t)entry; 180 | return c; 181 | } 182 | 183 | void yield() { 184 | raise(SIGUSR2); 185 | } 186 | 187 | bool ienabled() { 188 | sigset_t set; 189 | int ret = sigprocmask(0, NULL, &set); 190 | assert(ret == 0); 191 | return __am_is_sigmask_sti(&set); 192 | } 193 | 194 | void iset(bool enable) { 195 | extern sigset_t __am_intr_sigmask; 196 | // NOTE: sigprocmask does not supported in multithreading 197 | int ret = sigprocmask(enable ? SIG_UNBLOCK : SIG_BLOCK, &__am_intr_sigmask, NULL); 198 | assert(ret == 0); 199 | } 200 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/ioe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool __am_has_ioe = false; 5 | static bool ioe_init_done = false; 6 | 7 | void __am_timer_init(); 8 | void __am_gpu_init(); 9 | void __am_input_init(); 10 | void __am_audio_init(); 11 | void __am_disk_init(); 12 | void __am_input_config(AM_INPUT_CONFIG_T *); 13 | void __am_timer_config(AM_TIMER_CONFIG_T *); 14 | void __am_timer_rtc(AM_TIMER_RTC_T *); 15 | void __am_timer_uptime(AM_TIMER_UPTIME_T *); 16 | void __am_input_keybrd(AM_INPUT_KEYBRD_T *); 17 | void __am_gpu_config(AM_GPU_CONFIG_T *); 18 | void __am_gpu_status(AM_GPU_STATUS_T *); 19 | void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *); 20 | void __am_audio_config(AM_AUDIO_CONFIG_T *); 21 | void __am_audio_ctrl(AM_AUDIO_CTRL_T *); 22 | void __am_audio_status(AM_AUDIO_STATUS_T *); 23 | void __am_audio_play(AM_AUDIO_PLAY_T *); 24 | void __am_disk_config(AM_DISK_CONFIG_T *cfg); 25 | void __am_disk_status(AM_DISK_STATUS_T *stat); 26 | void __am_disk_blkio(AM_DISK_BLKIO_T *io); 27 | static void __am_uart_config(AM_UART_CONFIG_T *cfg) { cfg->present = false; } 28 | static void __am_net_config (AM_NET_CONFIG_T *cfg) { cfg->present = false; } 29 | 30 | typedef void (*handler_t)(void *buf); 31 | static void *lut[128] = { 32 | [AM_TIMER_CONFIG] = __am_timer_config, 33 | [AM_TIMER_RTC ] = __am_timer_rtc, 34 | [AM_TIMER_UPTIME] = __am_timer_uptime, 35 | [AM_INPUT_CONFIG] = __am_input_config, 36 | [AM_INPUT_KEYBRD] = __am_input_keybrd, 37 | [AM_GPU_CONFIG ] = __am_gpu_config, 38 | [AM_GPU_FBDRAW ] = __am_gpu_fbdraw, 39 | [AM_GPU_STATUS ] = __am_gpu_status, 40 | [AM_UART_CONFIG ] = __am_uart_config, 41 | [AM_AUDIO_CONFIG] = __am_audio_config, 42 | [AM_AUDIO_CTRL ] = __am_audio_ctrl, 43 | [AM_AUDIO_STATUS] = __am_audio_status, 44 | [AM_AUDIO_PLAY ] = __am_audio_play, 45 | [AM_DISK_CONFIG ] = __am_disk_config, 46 | [AM_DISK_STATUS ] = __am_disk_status, 47 | [AM_DISK_BLKIO ] = __am_disk_blkio, 48 | [AM_NET_CONFIG ] = __am_net_config, 49 | }; 50 | 51 | bool ioe_init() { 52 | panic_on(cpu_current() != 0, "call ioe_init() in other CPUs"); 53 | panic_on(ioe_init_done, "double-initialization"); 54 | __am_has_ioe = true; 55 | return true; 56 | } 57 | 58 | static void fail(void *buf) { panic("access nonexist register"); } 59 | 60 | void __am_ioe_init() { 61 | for (int i = 0; i < LENGTH(lut); i++) 62 | if (!lut[i]) lut[i] = fail; 63 | __am_timer_init(); 64 | __am_gpu_init(); 65 | __am_input_init(); 66 | __am_audio_init(); 67 | __am_disk_init(); 68 | ioe_init_done = true; 69 | } 70 | 71 | static void do_io(int reg, void *buf) { 72 | if (!ioe_init_done) { 73 | __am_ioe_init(); 74 | } 75 | ((handler_t)lut[reg])(buf); 76 | } 77 | 78 | void ioe_read (int reg, void *buf) { do_io(reg, buf); } 79 | void ioe_write(int reg, void *buf) { do_io(reg, buf); } 80 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/ioe/audio.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static int rfd = -1, wfd = -1; 8 | static volatile int count = 0; 9 | 10 | void __am_audio_init() { 11 | int fds[2]; 12 | int ret = pipe2(fds, O_NONBLOCK); 13 | assert(ret == 0); 14 | rfd = fds[0]; 15 | wfd = fds[1]; 16 | } 17 | 18 | static void audio_play(void *userdata, uint8_t *stream, int len) { 19 | int nread = len; 20 | if (count < len) nread = count; 21 | int b = 0; 22 | while (b < nread) { 23 | int n = read(rfd, stream, nread); 24 | if (n > 0) b += n; 25 | } 26 | 27 | count -= nread; 28 | if (len > nread) { 29 | memset(stream + nread, 0, len - nread); 30 | } 31 | } 32 | 33 | static void audio_write(uint8_t *buf, int len) { 34 | int nwrite = 0; 35 | while (nwrite < len) { 36 | int n = write(wfd, buf, len); 37 | if (n == -1) n = 0; 38 | count += n; 39 | nwrite += n; 40 | } 41 | } 42 | 43 | void __am_audio_ctrl(AM_AUDIO_CTRL_T *ctrl) { 44 | SDL_AudioSpec s = {}; 45 | s.freq = ctrl->freq; 46 | s.format = AUDIO_S16SYS; 47 | s.channels = ctrl->channels; 48 | s.samples = ctrl->samples; 49 | s.callback = audio_play; 50 | s.userdata = NULL; 51 | 52 | count = 0; 53 | int ret = SDL_InitSubSystem(SDL_INIT_AUDIO); 54 | if (ret == 0) { 55 | SDL_OpenAudio(&s, NULL); 56 | SDL_PauseAudio(0); 57 | } 58 | } 59 | 60 | void __am_audio_status(AM_AUDIO_STATUS_T *stat) { 61 | stat->count = count; 62 | } 63 | 64 | void __am_audio_play(AM_AUDIO_PLAY_T *ctl) { 65 | int len = ctl->buf.end - ctl->buf.start; 66 | audio_write(ctl->buf.start, len); 67 | } 68 | 69 | void __am_audio_config(AM_AUDIO_CONFIG_T *cfg) { 70 | cfg->present = true; 71 | cfg->bufsize = fcntl(rfd, F_GETPIPE_SZ); 72 | } 73 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/ioe/disk.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define BLKSZ 512 7 | 8 | static int disk_size = 0; 9 | static FILE *fp = NULL; 10 | 11 | void __am_disk_init() { 12 | const char *diskimg = getenv("diskimg"); 13 | if (diskimg) { 14 | fp = fopen(diskimg, "r+"); 15 | if (fp) { 16 | fseek(fp, 0, SEEK_END); 17 | disk_size = (ftell(fp) + 511) / 512; 18 | rewind(fp); 19 | } 20 | } 21 | } 22 | 23 | void __am_disk_config(AM_DISK_CONFIG_T *cfg) { 24 | cfg->present = (fp != NULL); 25 | cfg->blksz = BLKSZ; 26 | cfg->blkcnt = disk_size; 27 | } 28 | 29 | void __am_disk_status(AM_DISK_STATUS_T *stat) { 30 | stat->ready = 1; 31 | } 32 | 33 | void __am_disk_blkio(AM_DISK_BLKIO_T *io) { 34 | if (fp) { 35 | fseek(fp, io->blkno * BLKSZ, SEEK_SET); 36 | int ret; 37 | if (io->write) ret = fwrite(io->buf, io->blkcnt * BLKSZ, 1, fp); 38 | else ret = fread(io->buf, io->blkcnt * BLKSZ, 1, fp); 39 | assert(ret == 1); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/ioe/gpu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | //#define MODE_800x600 6 | #ifdef MODE_800x600 7 | # define W 800 8 | # define H 600 9 | #else 10 | # define W 400 11 | # define H 300 12 | #endif 13 | 14 | #define FPS 60 15 | 16 | #define RMASK 0x00ff0000 17 | #define GMASK 0x0000ff00 18 | #define BMASK 0x000000ff 19 | #define AMASK 0x00000000 20 | 21 | static SDL_Window *window = NULL; 22 | static SDL_Surface *surface = NULL; 23 | 24 | static Uint32 texture_sync(Uint32 interval, void *param) { 25 | SDL_BlitScaled(surface, NULL, SDL_GetWindowSurface(window), NULL); 26 | SDL_UpdateWindowSurface(window); 27 | return interval; 28 | } 29 | 30 | void __am_gpu_init() { 31 | SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER); 32 | window = SDL_CreateWindow("Native Application", 33 | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 34 | #ifdef MODE_800x600 35 | W, H, 36 | #else 37 | W * 2, H * 2, 38 | #endif 39 | SDL_WINDOW_SHOWN); 40 | surface = SDL_CreateRGBSurface(SDL_SWSURFACE, W, H, 32, 41 | RMASK, GMASK, BMASK, AMASK); 42 | SDL_AddTimer(1000 / FPS, texture_sync, NULL); 43 | } 44 | 45 | void __am_gpu_config(AM_GPU_CONFIG_T *cfg) { 46 | *cfg = (AM_GPU_CONFIG_T) { 47 | .present = true, .has_accel = false, 48 | .width = W, .height = H, 49 | .vmemsz = 0 50 | }; 51 | } 52 | 53 | void __am_gpu_status(AM_GPU_STATUS_T *stat) { 54 | stat->ready = true; 55 | } 56 | 57 | void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *ctl) { 58 | int x = ctl->x, y = ctl->y, w = ctl->w, h = ctl->h; 59 | if (w == 0 || h == 0) return; 60 | feclearexcept(-1); 61 | SDL_Surface *s = SDL_CreateRGBSurfaceFrom(ctl->pixels, w, h, 32, w * sizeof(uint32_t), 62 | RMASK, GMASK, BMASK, AMASK); 63 | SDL_Rect rect = { .x = x, .y = y }; 64 | SDL_BlitSurface(s, NULL, surface, &rect); 65 | SDL_FreeSurface(s); 66 | } 67 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/ioe/input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define KEYDOWN_MASK 0x8000 5 | 6 | #define KEY_QUEUE_LEN 1024 7 | static int key_queue[KEY_QUEUE_LEN] = {}; 8 | static int key_f = 0, key_r = 0; 9 | static SDL_mutex *key_queue_lock = NULL; 10 | 11 | #define XX(k) [SDL_SCANCODE_##k] = AM_KEY_##k, 12 | static int keymap[256] = { 13 | AM_KEYS(XX) 14 | }; 15 | 16 | static int event_thread(void *args) { 17 | SDL_Event event; 18 | while (1) { 19 | SDL_WaitEvent(&event); 20 | switch (event.type) { 21 | case SDL_QUIT: halt(0); 22 | case SDL_KEYDOWN: 23 | case SDL_KEYUP: { 24 | SDL_Keysym k = event.key.keysym; 25 | int keydown = event.key.type == SDL_KEYDOWN; 26 | int scancode = k.scancode; 27 | if (keymap[scancode] != 0) { 28 | int am_code = keymap[scancode] | (keydown ? KEYDOWN_MASK : 0); 29 | SDL_LockMutex(key_queue_lock); 30 | key_queue[key_r] = am_code; 31 | key_r = (key_r + 1) % KEY_QUEUE_LEN; 32 | SDL_UnlockMutex(key_queue_lock); 33 | void __am_send_kbd_intr(); 34 | __am_send_kbd_intr(); 35 | } 36 | break; 37 | } 38 | } 39 | } 40 | } 41 | 42 | void __am_input_init() { 43 | key_queue_lock = SDL_CreateMutex(); 44 | SDL_CreateThread(event_thread, "event thread", NULL); 45 | } 46 | 47 | void __am_input_config(AM_INPUT_CONFIG_T *cfg) { 48 | cfg->present = true; 49 | } 50 | 51 | void __am_input_keybrd(AM_INPUT_KEYBRD_T *kbd) { 52 | int k = AM_KEY_NONE; 53 | 54 | SDL_LockMutex(key_queue_lock); 55 | if (key_f != key_r) { 56 | k = key_queue[key_f]; 57 | key_f = (key_f + 1) % KEY_QUEUE_LEN; 58 | } 59 | SDL_UnlockMutex(key_queue_lock); 60 | 61 | kbd->keydown = (k & KEYDOWN_MASK ? true : false); 62 | kbd->keycode = k & ~KEYDOWN_MASK; 63 | } 64 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/ioe/timer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static struct timeval boot_time = {}; 6 | 7 | void __am_timer_config(AM_TIMER_CONFIG_T *cfg) { 8 | cfg->present = cfg->has_rtc = true; 9 | } 10 | 11 | void __am_timer_rtc(AM_TIMER_RTC_T *rtc) { 12 | time_t t = time(NULL); 13 | struct tm *tm = localtime(&t); 14 | rtc->second = tm->tm_sec; 15 | rtc->minute = tm->tm_min; 16 | rtc->hour = tm->tm_hour; 17 | rtc->day = tm->tm_mday; 18 | rtc->month = tm->tm_mon + 1; 19 | rtc->year = tm->tm_year + 1900; 20 | } 21 | 22 | void __am_timer_uptime(AM_TIMER_UPTIME_T *uptime) { 23 | struct timeval now; 24 | gettimeofday(&now, NULL); 25 | long seconds = now.tv_sec - boot_time.tv_sec; 26 | long useconds = now.tv_usec - boot_time.tv_usec; 27 | uptime->us = seconds * 1000000 + (useconds + 500); 28 | } 29 | 30 | void __am_timer_init() { 31 | gettimeofday(&boot_time, NULL); 32 | } 33 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/mpe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "platform.h" 3 | 4 | int __am_mpe_init = 0; 5 | extern bool __am_has_ioe; 6 | void __am_ioe_init(); 7 | 8 | bool mpe_init(void (*entry)()) { 9 | __am_mpe_init = 1; 10 | 11 | int sync_pipe[2]; 12 | assert(0 == pipe(sync_pipe)); 13 | 14 | for (int i = 1; i < cpu_count(); i++) { 15 | if (fork() == 0) { 16 | char ch; 17 | assert(read(sync_pipe[0], &ch, 1) == 1); 18 | assert(ch == '+'); 19 | close(sync_pipe[0]); close(sync_pipe[1]); 20 | 21 | thiscpu->cpuid = i; 22 | __am_init_timer_irq(); 23 | entry(); 24 | } 25 | } 26 | 27 | if (__am_has_ioe) { 28 | __am_ioe_init(); 29 | } 30 | 31 | for (int i = 1; i < cpu_count(); i++) { 32 | assert(write(sync_pipe[1], "+", 1) == 1); 33 | } 34 | close(sync_pipe[0]); close(sync_pipe[1]); 35 | 36 | entry(); 37 | panic("MP entry should not return\n"); 38 | } 39 | 40 | int cpu_count() { 41 | extern int __am_ncpu; 42 | return __am_ncpu; 43 | } 44 | 45 | int cpu_current() { 46 | return thiscpu->cpuid; 47 | } 48 | 49 | int atomic_xchg(int *addr, int newval) { 50 | return atomic_exchange((int *)addr, newval); 51 | } 52 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/platform.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "platform.h" 9 | 10 | #define MAX_CPU 16 11 | #define TRAP_PAGE_START (void *)0x100000 12 | #define PMEM_START (void *)0x1000000 // for nanos-lite with vme disabled 13 | #define PMEM_SIZE (128 * 1024 * 1024) // 128MB 14 | static int pmem_fd = 0; 15 | static void *pmem = NULL; 16 | static ucontext_t uc_example = {}; 17 | static int sys_pgsz; 18 | static void *(*memcpy_libc)(void *, const void *, size_t) = NULL; 19 | sigset_t __am_intr_sigmask = {}; 20 | __am_cpu_t *__am_cpu_struct = NULL; 21 | int __am_ncpu = 0; 22 | int __am_pgsize; 23 | 24 | static void save_context_handler(int sig, siginfo_t *info, void *ucontext) { 25 | memcpy_libc(&uc_example, ucontext, sizeof(uc_example)); 26 | } 27 | 28 | static void save_example_context() { 29 | // getcontext() does not save segment registers. In the signal 30 | // handler, restoring a context previously saved by getcontext() 31 | // will trigger segmentation fault because of the invalid segment 32 | // registers. So we save the example context during signal handling 33 | // to get a context with everything valid. 34 | struct sigaction s; 35 | void *(*memset_libc)(void *, int, size_t) = dlsym(RTLD_NEXT, "memset"); 36 | memset_libc(&s, 0, sizeof(s)); 37 | s.sa_sigaction = save_context_handler; 38 | s.sa_flags = SA_SIGINFO; 39 | int ret = sigaction(SIGUSR1, &s, NULL); 40 | assert(ret == 0); 41 | 42 | raise(SIGUSR1); 43 | 44 | s.sa_flags = 0; 45 | s.sa_handler = SIG_DFL; 46 | ret = sigaction(SIGUSR1, &s, NULL); 47 | assert(ret == 0); 48 | } 49 | 50 | static void setup_sigaltstack() { 51 | stack_t ss; 52 | ss.ss_sp = thiscpu->sigstack; 53 | ss.ss_size = sizeof(thiscpu->sigstack); 54 | ss.ss_flags = 0; 55 | int ret = sigaltstack(&ss, NULL); 56 | assert(ret == 0); 57 | } 58 | 59 | int main(const char *args); 60 | 61 | static void init_platform() __attribute__((constructor)); 62 | static void init_platform() { 63 | // create memory object and set up mapping to simulate the physical memory 64 | pmem_fd = memfd_create("pmem", 0); 65 | assert(pmem_fd != -1); 66 | assert(0 == ftruncate(pmem_fd, PMEM_SIZE)); 67 | 68 | pmem = mmap(PMEM_START, PMEM_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, 69 | MAP_SHARED | MAP_FIXED, pmem_fd, 0); 70 | assert(pmem != (void *)-1); 71 | 72 | // allocate private per-cpu structure 73 | thiscpu = mmap(NULL, sizeof(*thiscpu), PROT_READ | PROT_WRITE, 74 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 75 | assert(thiscpu != (void *)-1); 76 | thiscpu->cpuid = 0; 77 | thiscpu->vm_head = NULL; 78 | 79 | // create trap page to receive syscall and yield by SIGSEGV 80 | sys_pgsz = sysconf(_SC_PAGESIZE); 81 | void *ret = mmap(TRAP_PAGE_START, sys_pgsz, PROT_NONE, 82 | MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0); 83 | assert(ret != (void *)-1); 84 | 85 | // save the address of memcpy() in glibc, since it may be linked with klib 86 | memcpy_libc = dlsym(RTLD_NEXT, "memcpy"); 87 | assert(memcpy_libc != NULL); 88 | 89 | // remap writable sections as MAP_SHARED 90 | Elf64_Phdr *phdr = (void *)getauxval(AT_PHDR); 91 | int phnum = (int)getauxval(AT_PHNUM); 92 | int i; 93 | int ret2; 94 | for (i = 0; i < phnum; i ++) { 95 | if (phdr[i].p_type == PT_LOAD && (phdr[i].p_flags & PF_W)) { 96 | // allocate temporary memory 97 | extern char end; 98 | void *vaddr = (void *)&end - phdr[i].p_memsz; 99 | uintptr_t pad = (uintptr_t)vaddr & 0xfff; 100 | void *vaddr_align = vaddr - pad; 101 | uintptr_t size = phdr[i].p_memsz + pad; 102 | void *temp_mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 103 | assert(temp_mem != (void *)-1); 104 | 105 | // save data and bss sections 106 | memcpy_libc(temp_mem, vaddr_align, size); 107 | 108 | // save the address of mmap() which will be used after munamp(), 109 | // since calling the library functions requires accessing GOT, which will be unmapped 110 | void *(*mmap_libc)(void *, size_t, int, int, int, off_t) = dlsym(RTLD_NEXT, "mmap"); 111 | assert(mmap_libc != NULL); 112 | // load the address of memcpy() on stack, which can still be accessed 113 | // after the data section is unmapped 114 | void *(*volatile memcpy_libc_temp)(void *, const void *, size_t) = memcpy_libc; 115 | 116 | // unmap the data and bss sections 117 | ret2 = munmap(vaddr_align, size); 118 | assert(ret2 == 0); 119 | 120 | // map the sections again with MAP_SHARED, which will be shared across fork() 121 | ret = mmap_libc(vaddr_align, size, PROT_READ | PROT_WRITE | PROT_EXEC, 122 | MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0); 123 | assert(ret == vaddr_align); 124 | 125 | // restore the data in the sections 126 | memcpy_libc_temp(vaddr_align, temp_mem, size); 127 | 128 | // unmap the temporary memory 129 | ret2 = munmap(temp_mem, size); 130 | assert(ret2 == 0); 131 | } 132 | } 133 | 134 | // set up the AM heap 135 | heap = RANGE(pmem, pmem + PMEM_SIZE); 136 | 137 | // initialize sigmask for interrupts 138 | ret2 = sigemptyset(&__am_intr_sigmask); 139 | assert(ret2 == 0); 140 | ret2 = sigaddset(&__am_intr_sigmask, SIGVTALRM); 141 | assert(ret2 == 0); 142 | ret2 = sigaddset(&__am_intr_sigmask, SIGUSR1); 143 | assert(ret2 == 0); 144 | 145 | // setup alternative signal stack 146 | setup_sigaltstack(); 147 | 148 | // save the context template 149 | save_example_context(); 150 | uc_example.uc_mcontext.fpregs = NULL; // clear the FPU context 151 | __am_get_intr_sigmask(&uc_example.uc_sigmask); 152 | 153 | // disable interrupts by default 154 | iset(0); 155 | 156 | // set ncpu 157 | const char *smp = getenv("smp"); 158 | __am_ncpu = smp ? atoi(smp) : 1; 159 | assert(0 < __am_ncpu && __am_ncpu <= MAX_CPU); 160 | 161 | // set pgsize 162 | const char *pgsize = getenv("pgsize"); 163 | __am_pgsize = pgsize ? atoi(pgsize) : sys_pgsz; 164 | assert(__am_pgsize > 0 && __am_pgsize % sys_pgsz == 0); 165 | 166 | // set stdout unbuffered 167 | setbuf(stdout, NULL); 168 | 169 | const char *args = getenv("mainargs"); 170 | halt(main(args ? args : "")); // call main here! 171 | } 172 | 173 | void __am_exit_platform(int code) { 174 | // let Linux clean up other resource 175 | extern int __am_mpe_init; 176 | if (__am_mpe_init && cpu_count() > 1) kill(0, SIGKILL); 177 | exit(code); 178 | } 179 | 180 | void __am_pmem_map(void *va, void *pa, int prot) { 181 | // translate AM prot to mmap prot 182 | int mmap_prot = PROT_NONE; 183 | // we do not support executable bit, so mark 184 | // all readable pages executable as well 185 | if (prot & MMAP_READ) mmap_prot |= PROT_READ | PROT_EXEC; 186 | if (prot & MMAP_WRITE) mmap_prot |= PROT_WRITE; 187 | void *ret = mmap(va, __am_pgsize, mmap_prot, 188 | MAP_SHARED | MAP_FIXED, pmem_fd, (uintptr_t)(pa - pmem)); 189 | assert(ret != (void *)-1); 190 | } 191 | 192 | void __am_pmem_unmap(void *va) { 193 | int ret = munmap(va, __am_pgsize); 194 | assert(ret == 0); 195 | } 196 | 197 | void __am_get_example_uc(Context *r) { 198 | memcpy_libc(&r->uc, &uc_example, sizeof(uc_example)); 199 | } 200 | 201 | void __am_get_intr_sigmask(sigset_t *s) { 202 | memcpy_libc(s, &__am_intr_sigmask, sizeof(__am_intr_sigmask)); 203 | } 204 | 205 | int __am_is_sigmask_sti(sigset_t *s) { 206 | return !sigismember(s, SIGVTALRM); 207 | } 208 | 209 | void __am_send_kbd_intr() { 210 | kill(getpid(), SIGUSR1); 211 | } 212 | 213 | void __am_pmem_protect() { 214 | // int ret = mprotect(PMEM_START, PMEM_SIZE, PROT_NONE); 215 | // assert(ret == 0); 216 | } 217 | 218 | void __am_pmem_unprotect() { 219 | // int ret = mprotect(PMEM_START, PMEM_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC); 220 | // assert(ret == 0); 221 | } 222 | 223 | // This dummy function will be called in trm.c. 224 | // The purpose of this dummy function is to let linker add this file to the object 225 | // file set. Without it, the constructor of @_init_platform will not be linked. 226 | void __am_platform_dummy() { 227 | } 228 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/platform.h: -------------------------------------------------------------------------------- 1 | #ifndef __PLATFORM_H__ 2 | #define __PLATFORM_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void __am_get_example_uc(Context *r); 11 | void __am_get_intr_sigmask(sigset_t *s); 12 | int __am_is_sigmask_sti(sigset_t *s); 13 | void __am_init_timer_irq(); 14 | void __am_pmem_map(void *va, void *pa, int prot); 15 | void __am_pmem_unmap(void *va); 16 | 17 | // per-cpu structure 18 | typedef struct { 19 | void *vm_head; 20 | uintptr_t ksp; 21 | int cpuid; 22 | Event ev; // similar to cause register in mips/riscv 23 | uint8_t sigstack[SIGSTKSZ]; 24 | } __am_cpu_t; 25 | extern __am_cpu_t *__am_cpu_struct; 26 | #define thiscpu __am_cpu_struct 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/trap.S: -------------------------------------------------------------------------------- 1 | .global __am_kcontext_start 2 | __am_kcontext_start: 3 | // rdi = arg, rsi = entry 4 | 5 | // (rsp + 8) should be multiple of 16 when 6 | // control is transfered to the function entry point. 7 | // See amd64 ABI manual for more details 8 | andq $0xfffffffffffffff0, %rsp 9 | call *%rsi 10 | call __am_panic_on_return 11 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/trm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void __am_platform_dummy(); 6 | void __am_exit_platform(int code); 7 | 8 | void trm_init() { 9 | __am_platform_dummy(); 10 | } 11 | 12 | void putch(char ch) { 13 | putchar(ch); 14 | } 15 | 16 | void halt(int code) { 17 | const char *fmt = "Exit code = 40h\n"; 18 | for (const char *p = fmt; *p; p++) { 19 | char ch = *p; 20 | if (ch == '0' || ch == '4') { 21 | ch = "0123456789abcdef"[(code >> (ch - '0')) & 0xf]; 22 | } 23 | putch(ch); 24 | } 25 | __am_exit_platform(code); 26 | putstr("Should not reach here!\n"); 27 | while (1); 28 | } 29 | 30 | Area heap = {}; 31 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/native/vme.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include "platform.h" 4 | 5 | #define USER_SPACE RANGE(0x40000000, 0xc0000000) 6 | 7 | typedef struct PageMap { 8 | void *va; 9 | void *pa; 10 | struct PageMap *next; 11 | int prot; 12 | int is_mapped; 13 | char key[32]; // used for hsearch_r() 14 | } PageMap; 15 | 16 | typedef struct VMHead { 17 | PageMap *head; 18 | struct hsearch_data hash; 19 | int nr_page; 20 | } VMHead; 21 | 22 | #define list_foreach(p, head) \ 23 | for (p = (PageMap *)(head); p != NULL; p = p->next) 24 | 25 | extern int __am_pgsize; 26 | static int vme_enable = 0; 27 | static void* (*pgalloc)(int) = NULL; 28 | static void (*pgfree)(void *) = NULL; 29 | 30 | bool vme_init(void* (*pgalloc_f)(int), void (*pgfree_f)(void*)) { 31 | pgalloc = pgalloc_f; 32 | pgfree = pgfree_f; 33 | vme_enable = 1; 34 | return true; 35 | } 36 | 37 | void protect(AddrSpace *as) { 38 | assert(as != NULL); 39 | VMHead *h = pgalloc(__am_pgsize); // used as head of the list 40 | assert(h != NULL); 41 | memset(h, 0, sizeof(*h)); 42 | int max_pg = (USER_SPACE.end - USER_SPACE.start) / __am_pgsize; 43 | int ret = hcreate_r(max_pg, &h->hash); 44 | assert(ret != 0); 45 | 46 | as->ptr = h; 47 | as->pgsize = __am_pgsize; 48 | as->area = USER_SPACE; 49 | } 50 | 51 | void unprotect(AddrSpace *as) { 52 | } 53 | 54 | void __am_switch(Context *c) { 55 | if (!vme_enable) return; 56 | 57 | VMHead *head = c->vm_head; 58 | VMHead *now_head = thiscpu->vm_head; 59 | if (head == now_head) goto end; 60 | 61 | PageMap *pp; 62 | if (now_head != NULL) { 63 | // munmap all mappings 64 | list_foreach(pp, now_head->head) { 65 | if (pp->is_mapped) { 66 | __am_pmem_unmap(pp->va); 67 | pp->is_mapped = false; 68 | } 69 | } 70 | } 71 | 72 | if (head != NULL) { 73 | // mmap all mappings 74 | list_foreach(pp, head->head) { 75 | assert(IN_RANGE(pp->va, USER_SPACE)); 76 | __am_pmem_map(pp->va, pp->pa, pp->prot); 77 | pp->is_mapped = true; 78 | } 79 | } 80 | 81 | end: 82 | thiscpu->vm_head = head; 83 | } 84 | 85 | void map(AddrSpace *as, void *va, void *pa, int prot) { 86 | assert(IN_RANGE(va, USER_SPACE)); 87 | assert((uintptr_t)va % __am_pgsize == 0); 88 | assert((uintptr_t)pa % __am_pgsize == 0); 89 | assert(as != NULL); 90 | PageMap *pp = NULL; 91 | VMHead *vm_head = as->ptr; 92 | assert(vm_head != NULL); 93 | char buf[32]; 94 | snprintf(buf, 32, "%x", va); 95 | ENTRY item = { .key = buf }; 96 | ENTRY *item_find; 97 | hsearch_r(item, FIND, &item_find, &vm_head->hash); 98 | if (item_find == NULL) { 99 | pp = pgalloc(__am_pgsize); // this will waste memory, any better idea? 100 | snprintf(pp->key, 32, "%x", va); 101 | item.key = pp->key; 102 | item.data = pp; 103 | int ret = hsearch_r(item, ENTER, &item_find, &vm_head->hash); 104 | assert(ret != 0); 105 | vm_head->nr_page ++; 106 | } else { 107 | pp = item_find->data; 108 | } 109 | pp->va = va; 110 | pp->pa = pa; 111 | pp->prot = prot; 112 | pp->is_mapped = false; 113 | pp->next = vm_head->head; 114 | vm_head->head = pp; 115 | 116 | if (vm_head == thiscpu->vm_head) { 117 | // enforce the map immediately 118 | __am_pmem_map(pp->va, pp->pa, pp->prot); 119 | pp->is_mapped = true; 120 | } 121 | } 122 | 123 | Context* ucontext(AddrSpace *as, Area kstack, void *entry) { 124 | Context *c = (Context*)kstack.end - 1; 125 | 126 | __am_get_example_uc(c); 127 | c->uc.uc_mcontext.gregs[REG_RIP] = (uintptr_t)entry; 128 | c->uc.uc_mcontext.gregs[REG_RSP] = (uintptr_t)USER_SPACE.end; 129 | 130 | int ret = sigemptyset(&(c->uc.uc_sigmask)); // enable interrupt 131 | assert(ret == 0); 132 | c->vm_head = as->ptr; 133 | 134 | c->ksp = (uintptr_t)kstack.end; 135 | 136 | return c; 137 | } 138 | 139 | int __am_in_userspace(void *addr) { 140 | return vme_enable && thiscpu->vm_head != NULL && IN_RANGE(addr, USER_SPACE); 141 | } 142 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/nemu/cte.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define NR_IRQ 256 // IDT size 6 | #define SEG_KCODE 1 7 | #define SEG_KDATA 2 8 | 9 | static Context* (*user_handler)(Event, Context*) = NULL; 10 | 11 | void __am_irq0(); 12 | void __am_vecsys(); 13 | void __am_vectrap(); 14 | void __am_vecnull(); 15 | 16 | 17 | Context* __am_irq_handle(Context *c) { 18 | if (user_handler) { 19 | Event ev = {0}; 20 | switch (c->irq) { 21 | default: ev.event = EVENT_ERROR; break; 22 | } 23 | 24 | c = user_handler(ev, c); 25 | assert(c != NULL); 26 | } 27 | 28 | return c; 29 | } 30 | 31 | bool cte_init(Context*(*handler)(Event, Context*)) { 32 | static GateDesc32 idt[NR_IRQ]; 33 | 34 | // initialize IDT 35 | for (unsigned int i = 0; i < NR_IRQ; i ++) { 36 | idt[i] = GATE32(STS_TG, KSEL(SEG_KCODE), __am_vecnull, DPL_KERN); 37 | } 38 | 39 | // ----------------------- interrupts ---------------------------- 40 | idt[32] = GATE32(STS_IG, KSEL(SEG_KCODE), __am_irq0, DPL_KERN); 41 | // ---------------------- system call ---------------------------- 42 | idt[0x80] = GATE32(STS_TG, KSEL(SEG_KCODE), __am_vecsys, DPL_USER); 43 | idt[0x81] = GATE32(STS_TG, KSEL(SEG_KCODE), __am_vectrap, DPL_KERN); 44 | 45 | set_idt(idt, sizeof(idt)); 46 | 47 | // register event handler 48 | user_handler = handler; 49 | 50 | return true; 51 | } 52 | 53 | 54 | Context* kcontext(Area kstack, void (*entry)(void *), void *arg) { 55 | return NULL; 56 | } 57 | 58 | void yield() { 59 | asm volatile("int $0x81"); 60 | } 61 | 62 | bool ienabled() { 63 | return false; 64 | } 65 | 66 | void iset(bool enable) { 67 | } 68 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/nemu/start.S: -------------------------------------------------------------------------------- 1 | .section entry, "ax" 2 | .globl _start 3 | .type _start, @function 4 | 5 | _start: 6 | mov $0, %ebp 7 | mov $_stack_pointer, %esp 8 | call _trm_init # never return 9 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/nemu/trap.S: -------------------------------------------------------------------------------- 1 | #----|------------entry------------|---irq id---|-----handler-----| 2 | .globl __am_vecsys; __am_vecsys: pushl $0x80; jmp __am_asm_trap 3 | .globl __am_vectrap; __am_vectrap: pushl $0x81; jmp __am_asm_trap 4 | .globl __am_irq0; __am_irq0: pushl $32; jmp __am_asm_trap 5 | .globl __am_vecnull; __am_vecnull: pushl $-1; jmp __am_asm_trap 6 | 7 | 8 | __am_asm_trap: 9 | pushal 10 | 11 | pushl $0 12 | 13 | pushl %esp 14 | call __am_irq_handle 15 | 16 | addl $4, %esp 17 | 18 | addl $4, %esp 19 | popal 20 | addl $4, %esp 21 | 22 | iret 23 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/nemu/vme.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static AddrSpace kas = {}; 6 | static void* (*pgalloc_usr)(int) = NULL; 7 | static void (*pgfree_usr)(void*) = NULL; 8 | static int vme_enable = 0; 9 | 10 | static Area segments[] = { // Kernel memory mappings 11 | NEMU_PADDR_SPACE 12 | }; 13 | 14 | #define USER_SPACE RANGE(0x40000000, 0xc0000000) 15 | 16 | bool vme_init(void* (*pgalloc_f)(int), void (*pgfree_f)(void*)) { 17 | pgalloc_usr = pgalloc_f; 18 | pgfree_usr = pgfree_f; 19 | 20 | kas.ptr = pgalloc_f(PGSIZE); 21 | 22 | int i; 23 | for (i = 0; i < LENGTH(segments); i ++) { 24 | void *va = segments[i].start; 25 | for (; va < segments[i].end; va += PGSIZE) { 26 | map(&kas, va, va, 0); 27 | } 28 | } 29 | 30 | set_cr3(kas.ptr); 31 | set_cr0(get_cr0() | CR0_PG); 32 | vme_enable = 1; 33 | 34 | return true; 35 | } 36 | 37 | void protect(AddrSpace *as) { 38 | PTE *updir = (PTE*)(pgalloc_usr(PGSIZE)); 39 | as->ptr = updir; 40 | as->area = USER_SPACE; 41 | as->pgsize = PGSIZE; 42 | // map kernel space 43 | memcpy(updir, kas.ptr, PGSIZE); 44 | } 45 | 46 | void unprotect(AddrSpace *as) { 47 | } 48 | 49 | void __am_get_cur_as(Context *c) { 50 | c->cr3 = (vme_enable ? (void *)get_cr3() : NULL); 51 | } 52 | 53 | void __am_switch(Context *c) { 54 | if (vme_enable && c->cr3 != NULL) { 55 | set_cr3(c->cr3); 56 | } 57 | } 58 | 59 | void map(AddrSpace *as, void *va, void *pa, int prot) { 60 | } 61 | 62 | Context* ucontext(AddrSpace *as, Area kstack, void *entry) { 63 | return NULL; 64 | } 65 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/boot/Makefile: -------------------------------------------------------------------------------- 1 | SRCS := start.S main.c 2 | bootblock.o: $(SRCS) Makefile 3 | @echo + CC $(SRCS) 4 | @$(CROSS_COMPILE)gcc -static -m32 -fno-pic -Os -nostdlib -Ttext 0x7c00 -I$(AM_HOME)/am/src -o bootblock.o $(SRCS) 5 | @python3 genboot.py bootblock.o 6 | 7 | clean: 8 | rm -rf *.o 9 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/boot/genboot.py: -------------------------------------------------------------------------------- 1 | import os, sys, pathlib, subprocess 2 | 3 | f = pathlib.Path(sys.argv[1]) 4 | try: 5 | objcopy = os.getenv('CROSS_COMPILE', '') + 'objcopy' 6 | data = subprocess.run( 7 | [objcopy, '-S', '-O', 'binary', '-j', '.text', f, '/dev/stdout'], 8 | capture_output=True).stdout 9 | assert len(data) <= 510 10 | data += b'\0' * (510 - len(data)) + b'\x55\xaa' 11 | f.write_bytes(data) 12 | except: 13 | f.unlink() 14 | raise 15 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/boot/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define SECTSIZE 512 6 | #define ARGSIZE 1024 7 | 8 | static inline void wait_disk(void) { 9 | while ((inb(0x1f7) & 0xc0) != 0x40); 10 | } 11 | 12 | static inline void read_disk(void *buf, int sect) { 13 | wait_disk(); 14 | outb(0x1f2, 1); 15 | outb(0x1f3, sect); 16 | outb(0x1f4, sect >> 8); 17 | outb(0x1f5, sect >> 16); 18 | outb(0x1f6, (sect >> 24) | 0xE0); 19 | outb(0x1f7, 0x20); 20 | wait_disk(); 21 | for (int i = 0; i < SECTSIZE / 4; i ++) { 22 | ((uint32_t *)buf)[i] = inl(0x1f0); 23 | } 24 | } 25 | 26 | static inline void copy_from_disk(void *buf, int nbytes, int disk_offset) { 27 | uint32_t cur = (uint32_t)buf & ~(SECTSIZE - 1); 28 | uint32_t ed = (uint32_t)buf + nbytes; 29 | uint32_t sect = (disk_offset / SECTSIZE) + (ARGSIZE / SECTSIZE) + 1; 30 | for(; cur < ed; cur += SECTSIZE, sect ++) 31 | read_disk((void *)cur, sect); 32 | } 33 | 34 | static void load_program(uint32_t filesz, uint32_t memsz, uint32_t paddr, uint32_t offset) { 35 | copy_from_disk((void *)paddr, filesz, offset); 36 | char *bss = (void *)(paddr + filesz); 37 | for (uint32_t i = filesz; i != memsz; i++) { 38 | *bss++ = 0; 39 | } 40 | } 41 | 42 | static void load_elf64(Elf64_Ehdr *elf) { 43 | Elf64_Phdr *ph = (Elf64_Phdr *)((char *)elf + elf->e_phoff); 44 | for (int i = 0; i < elf->e_phnum; i++, ph++) { 45 | load_program( 46 | (uint32_t)ph->p_filesz, 47 | (uint32_t)ph->p_memsz, 48 | (uint32_t)ph->p_paddr, 49 | (uint32_t)ph->p_offset 50 | ); 51 | } 52 | } 53 | 54 | static void load_elf32(Elf32_Ehdr *elf) { 55 | Elf32_Phdr *ph = (Elf32_Phdr *)((char *)elf + elf->e_phoff); 56 | for (int i = 0; i < elf->e_phnum; i++, ph++) { 57 | load_program( 58 | (uint32_t)ph->p_filesz, 59 | (uint32_t)ph->p_memsz, 60 | (uint32_t)ph->p_paddr, 61 | (uint32_t)ph->p_offset 62 | ); 63 | } 64 | } 65 | 66 | void load_kernel(void) { 67 | Elf32_Ehdr *elf32 = (void *)0x8000; 68 | Elf64_Ehdr *elf64 = (void *)0x8000; 69 | int is_ap = boot_record()->is_ap; 70 | 71 | if (!is_ap) { 72 | // load argument (string) to memory 73 | copy_from_disk((void *)MAINARG_ADDR, 1024, -1024); 74 | // load elf header to memory 75 | copy_from_disk(elf32, 4096, 0); 76 | if (elf32->e_machine == EM_X86_64) { 77 | load_elf64(elf64); 78 | } else { 79 | load_elf32(elf32); 80 | } 81 | } else { 82 | // everything should be loaded 83 | } 84 | 85 | if (elf32->e_machine == EM_X86_64) { 86 | ((void(*)())(uint32_t)elf64->e_entry)(); 87 | } else { 88 | ((void(*)())(uint32_t)elf32->e_entry)(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/boot/start.S: -------------------------------------------------------------------------------- 1 | #define CR0_PE 0x00000001 2 | 3 | #define GDT_ENTRY(n) \ 4 | ((n) << 3) 5 | 6 | #define SEG_NULLASM \ 7 | .word 0, 0; \ 8 | .byte 0, 0, 0, 0 9 | 10 | #define SEG_ASM(type, base, lim) \ 11 | .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ 12 | .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ 13 | (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) 14 | 15 | .code16 16 | .globl _start 17 | _start: 18 | cli 19 | 20 | xorw %ax, %ax 21 | movw %ax, %ds 22 | movw %ax, %es 23 | movw %ax, %ss 24 | 25 | # Set a 640 x 480 x 32 video mode 26 | mov $0x4f01, %ax 27 | mov $0x0112, %cx 28 | mov $0x4000, %di 29 | int $0x10 30 | 31 | mov $0x4f02, %ax 32 | mov $0x4112, %bx 33 | int $0x10 34 | 35 | lgdt gdtdesc 36 | movl %cr0, %eax 37 | orl $CR0_PE, %eax 38 | movl %eax, %cr0 39 | ljmp $GDT_ENTRY(1), $start32 40 | 41 | .code32 42 | start32: 43 | movw $GDT_ENTRY(2), %ax 44 | movw %ax, %ds 45 | movw %ax, %es 46 | movw %ax, %ss 47 | 48 | movl $0xa000, %esp 49 | call load_kernel 50 | 51 | # GDT 52 | .p2align 2 53 | gdt: 54 | SEG_NULLASM 55 | SEG_ASM(0xA, 0x0, 0xffffffff) 56 | SEG_ASM(0x2, 0x0, 0xffffffff) 57 | 58 | gdtdesc: 59 | .word (gdtdesc - gdt - 1) 60 | .long gdt 61 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/cte.c: -------------------------------------------------------------------------------- 1 | #include "x86-qemu.h" 2 | 3 | static Context* (*user_handler)(Event, Context*) = NULL; 4 | #if __x86_64__ 5 | static GateDesc64 idt[NR_IRQ]; 6 | #define GATE GATE64 7 | #else 8 | static GateDesc32 idt[NR_IRQ]; 9 | #define GATE GATE32 10 | #endif 11 | 12 | #define IRQHANDLE_DECL(id, dpl, err) \ 13 | void __am_irq##id(); 14 | 15 | IRQS(IRQHANDLE_DECL) 16 | void __am_irqall(); 17 | void __am_kcontext_start(); 18 | 19 | void __am_irq_handle(struct trap_frame *tf) { 20 | Context *saved_ctx = &tf->saved_context; 21 | Event ev = { 22 | .event = EVENT_NULL, 23 | .cause = 0, .ref = 0, 24 | .msg = "(no message)", 25 | }; 26 | 27 | #if __x86_64 28 | saved_ctx->rip = tf->rip; 29 | saved_ctx->cs = tf->cs; 30 | saved_ctx->rflags = tf->rflags; 31 | saved_ctx->rsp = tf->rsp; 32 | saved_ctx->rsp0 = CPU->tss.rsp0; 33 | saved_ctx->ss = tf->ss; 34 | #else 35 | saved_ctx->eip = tf->eip; 36 | saved_ctx->cs = tf->cs; 37 | saved_ctx->eflags = tf->eflags; 38 | saved_ctx->esp0 = CPU->tss.esp0; 39 | saved_ctx->ss3 = USEL(SEG_UDATA); 40 | // no ss/esp saved for DPL_KERNEL 41 | saved_ctx->esp = (tf->cs & DPL_USER ? tf->esp : (uint32_t)(tf + 1) - 8); 42 | #endif 43 | saved_ctx->cr3 = (void *)get_cr3(); 44 | 45 | #define IRQ T_IRQ0 + 46 | #define MSG(m) ev.msg = m; 47 | 48 | if (IRQ 0 <= tf->irq && tf->irq < IRQ 32) { 49 | __am_lapic_eoi(); 50 | } 51 | 52 | switch (tf->irq) { 53 | case IRQ 0: MSG("timer interrupt (lapic)") 54 | ev.event = EVENT_IRQ_TIMER; break; 55 | case IRQ 1: MSG("I/O device IRQ1 (keyboard)") 56 | ev.event = EVENT_IRQ_IODEV; break; 57 | case IRQ 4: MSG("I/O device IRQ4 (COM1)") 58 | ev.event = EVENT_IRQ_IODEV; break; 59 | case EX_SYSCALL: MSG("int $0x80 system call") 60 | ev.event = EVENT_SYSCALL; break; 61 | case EX_YIELD: MSG("int $0x81 yield") 62 | ev.event = EVENT_YIELD; break; 63 | case EX_DE: MSG("DE #0 divide by zero") 64 | ev.event = EVENT_ERROR; break; 65 | case EX_UD: MSG("UD #6 invalid opcode") 66 | ev.event = EVENT_ERROR; break; 67 | case EX_NM: MSG("NM #7 coprocessor error") 68 | ev.event = EVENT_ERROR; break; 69 | case EX_DF: MSG("DF #8 double fault") 70 | ev.event = EVENT_ERROR; break; 71 | case EX_TS: MSG("TS #10 invalid TSS") 72 | ev.event = EVENT_ERROR; break; 73 | case EX_NP: MSG("NP #11 segment/gate not present") 74 | ev.event = EVENT_ERROR; break; 75 | case EX_SS: MSG("SS #12 stack fault") 76 | ev.event = EVENT_ERROR; break; 77 | case EX_GP: MSG("GP #13, general protection fault") 78 | ev.event = EVENT_ERROR; break; 79 | case EX_PF: MSG("PF #14, page fault, @cause: PROT_XXX") 80 | ev.event = EVENT_PAGEFAULT; 81 | if (tf->errcode & 0x1) ev.cause |= MMAP_NONE; 82 | if (tf->errcode & 0x2) ev.cause |= MMAP_WRITE; 83 | else ev.cause |= MMAP_READ; 84 | ev.ref = get_cr2(); 85 | break; 86 | default: MSG("unrecognized interrupt/exception") 87 | ev.event = EVENT_ERROR; 88 | ev.cause = tf->errcode; 89 | break; 90 | } 91 | 92 | Context *ret_ctx = user_handler(ev, saved_ctx); 93 | panic_on(!ret_ctx, "returning to NULL context"); 94 | 95 | if (ret_ctx->cr3) { 96 | set_cr3(ret_ctx->cr3); 97 | #if __x86_64__ 98 | CPU->tss.rsp0 = ret_ctx->rsp0; 99 | #else 100 | CPU->tss.ss0 = KSEL(SEG_KDATA); 101 | CPU->tss.esp0 = ret_ctx->esp0; 102 | #endif 103 | } 104 | 105 | __am_iret(ret_ctx); 106 | } 107 | 108 | bool cte_init(Context *(*handler)(Event, Context *)) { 109 | panic_on(cpu_current() != 0, "init CTE in non-bootstrap CPU"); 110 | panic_on(!handler, "no interrupt handler"); 111 | 112 | for (int i = 0; i < NR_IRQ; i ++) { 113 | idt[i] = GATE(STS_TG, KSEL(SEG_KCODE), __am_irqall, DPL_KERN); 114 | } 115 | #define IDT_ENTRY(id, dpl, err) \ 116 | idt[id] = GATE(STS_TG, KSEL(SEG_KCODE), __am_irq##id, DPL_##dpl); 117 | IRQS(IDT_ENTRY) 118 | 119 | user_handler = handler; 120 | return true; 121 | } 122 | 123 | void yield() { 124 | interrupt(0x81); 125 | } 126 | 127 | bool ienabled() { 128 | return (get_efl() & FL_IF) != 0; 129 | } 130 | 131 | void iset(bool enable) { 132 | if (enable) sti(); 133 | else cli(); 134 | } 135 | 136 | void __am_panic_on_return() { panic("kernel context returns"); } 137 | 138 | Context* kcontext(Area kstack, void (*entry)(void *), void *arg) { 139 | Context *ctx = kstack.end - sizeof(Context); 140 | *ctx = (Context) { 0 }; 141 | 142 | #if __x86_64__ 143 | ctx->cs = KSEL(SEG_KCODE); 144 | ctx->rip = (uintptr_t)__am_kcontext_start; 145 | ctx->rflags = FL_IF; 146 | ctx->rsp = (uintptr_t)kstack.end; 147 | #else 148 | ctx->ds = KSEL(SEG_KDATA); 149 | ctx->cs = KSEL(SEG_KCODE); 150 | ctx->eip = (uintptr_t)__am_kcontext_start; 151 | ctx->eflags = FL_IF; 152 | ctx->esp = (uintptr_t)kstack.end; 153 | #endif 154 | 155 | ctx->GPR1 = (uintptr_t)arg; 156 | ctx->GPR2 = (uintptr_t)entry; 157 | 158 | return ctx; 159 | } 160 | 161 | void __am_percpu_initirq() { 162 | __am_ioapic_enable(IRQ_KBD, 0); 163 | __am_ioapic_enable(IRQ_COM1, 0); 164 | set_idt(idt, sizeof(idt)); 165 | } 166 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/mpe.c: -------------------------------------------------------------------------------- 1 | #include "x86-qemu.h" 2 | 3 | struct cpu_local __am_cpuinfo[MAX_CPU] = {}; 4 | static void (* volatile user_entry)(); 5 | static int ap_ready = 0; 6 | 7 | static void call_user_entry() { 8 | user_entry(); 9 | panic("MPE entry should not return"); 10 | } 11 | 12 | bool mpe_init(void (*entry)()) { 13 | user_entry = entry; 14 | boot_record()->jmp_code = 0x000bfde9; // (16-bit) jmp (0x7c00) 15 | for (int cpu = 1; cpu < __am_ncpu; cpu++) { 16 | boot_record()->is_ap = 1; 17 | __am_lapic_bootap(cpu, (void *)boot_record()); 18 | while (xchg(&ap_ready, 0) != 1) { 19 | pause(); 20 | } 21 | } 22 | call_user_entry(); 23 | return true; 24 | } 25 | 26 | static void othercpu_entry() { 27 | __am_percpu_init(); 28 | xchg(&ap_ready, 1); 29 | call_user_entry(); 30 | } 31 | 32 | void __am_othercpu_entry() { 33 | stack_switch_call(stack_top(&CPU->stack), othercpu_entry, 0); 34 | } 35 | 36 | int cpu_count() { 37 | return __am_ncpu; 38 | } 39 | 40 | int cpu_current(void) { 41 | return __am_lapic[8] >> 24; 42 | } 43 | 44 | int atomic_xchg(int *addr, int newval) { 45 | return xchg(addr, newval); 46 | } 47 | 48 | void __am_stop_the_world() { 49 | boot_record()->jmp_code = 0x0000feeb; // (16-bit) jmp . 50 | for (int cpu_ = 0; cpu_ < __am_ncpu; cpu_++) { 51 | if (cpu_ != cpu_current()) { 52 | __am_lapic_bootap(cpu_, (void *)boot_record()); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/start32.S: -------------------------------------------------------------------------------- 1 | #include "x86-qemu.h" 2 | 3 | .globl _start 4 | _start: 5 | pushl $MAINARG_ADDR 6 | pushl $0 7 | jmp _start_c 8 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/start64.S: -------------------------------------------------------------------------------- 1 | #include 2 | #include "x86-qemu.h" 3 | 4 | .code32 5 | .globl _start 6 | _start: 7 | movl $(PDPT_ADDR | PTE_P | PTE_W), %eax 8 | cmpl (PML4_ADDR), %eax 9 | je .long_mode_init 10 | 11 | movl $(PDPT_ADDR | PTE_P | PTE_W), %eax 12 | movl %eax, (PML4_ADDR) 13 | 14 | movl $0, %ecx 15 | movl $512, %esi // 512 pages 16 | // | 17 | .loop: // x 18 | movl %ecx, %eax // | 19 | shll $30, %eax // | 20 | orl $(PTE_P | PTE_W | PTE_PS), %eax // 1 GiB page 21 | movl %eax, PDPT_ADDR(, %ecx, 8) 22 | 23 | movl %ecx, %eax 24 | shrl $2, %eax 25 | movl %eax, PDPT_ADDR + 4(, %ecx, 8) 26 | 27 | inc %ecx 28 | cmp %esi, %ecx 29 | jne .loop 30 | 31 | .long_mode_init: 32 | movl $PML4_ADDR, %eax 33 | movl %eax, %cr3 // %cr3 = PML4 base 34 | movl $CR4_PAE, %eax 35 | movl %eax, %cr4 // %cr4.PAE = 1 36 | movl $0xc0000080, %ecx 37 | rdmsr 38 | orl $0x100, %eax 39 | wrmsr // %EFER.LME = 1 40 | movl %cr0, %eax 41 | orl $CR0_PG, %eax 42 | movl %eax, %cr0 // %cr0.PG = 1 43 | lgdt gdt_ptr // bootstrap GDT 44 | ljmp $8, $_start64 // should not return 45 | 46 | .code64 47 | _start64: 48 | movw $0, %ax 49 | movw %ax, %ds 50 | movw %ax, %es 51 | movw %ax, %ss 52 | movw %ax, %fs 53 | movw %ax, %gs 54 | 55 | movq $MAINARG_ADDR, %rdi 56 | pushq $0 57 | jmp _start_c 58 | 59 | .align 16 60 | gdt_ptr: 61 | .word gdt64_end - gdt64_begin - 1 62 | .quad gdt64_begin 63 | 64 | gdt64_begin: 65 | .long 0x00000000 // 0: null desc 66 | .long 0x00000000 67 | .long 0x00000000 // 1: code 68 | .long 0x00209800 69 | gdt64_end: 70 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/trap32.S: -------------------------------------------------------------------------------- 1 | #include "x86-qemu.h" 2 | 3 | .globl __am_kcontext_start 4 | __am_kcontext_start: 5 | // eax = arg, ebx = entry 6 | pushl %eax 7 | pushl $__am_panic_on_return 8 | jmpl *%ebx 9 | 10 | trap: 11 | cli 12 | 13 | subl $20, %esp 14 | pushl %ebp 15 | pushl %edi 16 | pushl %esi 17 | pushl $0 18 | pushl %edx 19 | pushl %ecx 20 | pushl %ebx 21 | pushl %eax 22 | movw %ds, %ax 23 | pushl %eax 24 | pushl $0 25 | 26 | movw $KSEL(SEG_KDATA), %ax 27 | movw %ax, %ds 28 | movw %ax, %es 29 | movw %ax, %ss 30 | 31 | pushl %esp 32 | call __am_irq_handle 33 | 34 | .globl __am_iret 35 | __am_iret: 36 | addl $4, %esp 37 | popl %eax 38 | movl %eax, %esp 39 | 40 | addl $4, %esp 41 | popl %eax 42 | movw %ax, %ds 43 | movw %ax, %es 44 | 45 | cmpw $KSEL(SEG_KCODE), 36(%esp) 46 | je .kernel_iret 47 | 48 | .user_iret: 49 | popl %eax 50 | popl %ebx 51 | popl %ecx 52 | popl %edx 53 | addl $4, %esp 54 | popl %esi 55 | popl %edi 56 | popl %ebp 57 | iret 58 | 59 | .kernel_iret: 60 | popl %eax 61 | popl %ebx 62 | popl %ecx 63 | popl %edx 64 | addl $4, %esp 65 | 66 | /* stack frame: 67 | 28 ss 68 | 24 esp (not popped by iret when returning to ring0) 69 | 20 eflags ---> move to new-esp 70 | 16 cs 71 | 12 eip 72 | 8 ebp 73 | 4 edi 74 | 0 esi <--- %esp 75 | */ 76 | 77 | movl %esp, %ebp 78 | movl 24(%ebp), %edi // %edi is new-esp 79 | 80 | movl 20(%ebp), %esi; movl %esi, -4(%edi) 81 | movl 16(%ebp), %esi; movl %esi, -8(%edi) 82 | movl 12(%ebp), %esi; movl %esi, -12(%edi) 83 | movl 8(%ebp), %esi; movl %esi, -16(%edi) 84 | movl 4(%ebp), %esi; movl %esi, -20(%edi) 85 | movl 0(%ebp), %esi; movl %esi, -24(%edi) 86 | 87 | leal -24(%edi), %esp 88 | 89 | popl %esi 90 | popl %edi 91 | popl %ebp 92 | iret 93 | 94 | #define NOERR push $0 95 | #define ERR 96 | #define IRQ_DEF(id, dpl, err) \ 97 | .globl __am_irq##id; __am_irq##id: cli; err; push $id; jmp trap; 98 | IRQS(IRQ_DEF) 99 | .globl __am_irqall; __am_irqall: cli; push $0; push $-1; jmp trap; 100 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/trap64.S: -------------------------------------------------------------------------------- 1 | #include "x86-qemu.h" 2 | 3 | .globl __am_kcontext_start 4 | __am_kcontext_start: 5 | // rdi = arg, rsi = entry 6 | pushq $__am_panic_on_return 7 | jmpq *%rsi 8 | 9 | trap: 10 | cli 11 | subq $48, %rsp 12 | pushq %r15 13 | pushq %r14 14 | pushq %r13 15 | pushq %r12 16 | pushq %r11 17 | pushq %r10 18 | pushq %r9 19 | pushq %r8 20 | pushq %rdi 21 | pushq %rsi 22 | pushq %rbp 23 | pushq %rdx 24 | pushq %rcx 25 | pushq %rbx 26 | pushq %rax 27 | pushq $0 // cr3, saved in __am_irq_handle 28 | 29 | movq %rsp, %rdi 30 | call __am_irq_handle 31 | 32 | .globl __am_iret 33 | __am_iret: 34 | movq %rdi, %rsp 35 | movq 160(%rsp), %rax 36 | movw %ax, %ds 37 | movw %ax, %es 38 | addq $8, %rsp 39 | popq %rax 40 | popq %rbx 41 | popq %rcx 42 | popq %rdx 43 | popq %rbp 44 | popq %rsi 45 | popq %rdi 46 | popq %r8 47 | popq %r9 48 | popq %r10 49 | popq %r11 50 | popq %r12 51 | popq %r13 52 | popq %r14 53 | popq %r15 54 | iretq 55 | 56 | #define NOERR push $0 57 | #define ERR 58 | #define IRQ_DEF(id, dpl, err) \ 59 | .globl __am_irq##id; __am_irq##id: cli; err; push $id; jmp trap; 60 | IRQS(IRQ_DEF) 61 | .globl __am_irqall; __am_irqall: cli; push $0; push $-1; jmp trap; 62 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/trm.c: -------------------------------------------------------------------------------- 1 | #include "x86-qemu.h" 2 | 3 | Area heap = {}; 4 | int __am_ncpu = 0; 5 | 6 | int main(const char *args); 7 | 8 | static void call_main(const char *args) { 9 | halt(main(args)); 10 | } 11 | 12 | void _start_c(char *args) { 13 | if (boot_record()->is_ap) { 14 | __am_othercpu_entry(); 15 | } else { 16 | __am_bootcpu_init(); 17 | stack_switch_call(stack_top(&CPU->stack), call_main, (uintptr_t)args); 18 | } 19 | } 20 | 21 | void __am_bootcpu_init() { 22 | heap = __am_heap_init(); 23 | __am_lapic_init(); 24 | __am_ioapic_init(); 25 | __am_percpu_init(); 26 | } 27 | 28 | void __am_percpu_init() { 29 | __am_percpu_initgdt(); 30 | __am_percpu_initlapic(); 31 | __am_percpu_initirq(); 32 | } 33 | 34 | void putch(char ch) { 35 | #define COM1 0x3f8 36 | outb(COM1, ch); 37 | } 38 | 39 | void halt(int code) { 40 | const char *hex = "0123456789abcdef"; 41 | const char *fmt = "CPU #$ Halt (40).\n"; 42 | cli(); 43 | __am_stop_the_world(); 44 | for (const char *p = fmt; *p; p++) { 45 | char ch = *p; 46 | switch (ch) { 47 | case '$': 48 | putch(hex[cpu_current()]); 49 | break; 50 | case '0': case '4': 51 | putch(hex[(code >> (ch - '0')) & 0xf]); 52 | break; 53 | default: 54 | putch(ch); 55 | } 56 | } 57 | outw(0x604, 0x2000); // offer of qemu :) 58 | while (1) hlt(); 59 | } 60 | 61 | Area __am_heap_init() { 62 | extern char end; 63 | outb(0x70, 0x34); 64 | uint32_t lo = inb(0x71); 65 | outb(0x70, 0x35); 66 | uint32_t hi = inb(0x71) + 1; 67 | return RANGE(ROUNDUP(&end, 1 << 20), (uintptr_t)((lo | hi << 8) << 16)); 68 | } 69 | 70 | void __am_lapic_init() { 71 | for (char *st = (char *)0xf0000; st != (char *)0xffffff; st ++) { 72 | if (*(volatile uint32_t *)st == 0x5f504d5f) { 73 | uint32_t mpconf_ptr = ((volatile MPDesc *)st)->conf; 74 | MPConf *conf = (void *)((uintptr_t)(mpconf_ptr)); 75 | __am_lapic = (void *)((uintptr_t)(conf->lapicaddr)); 76 | for (volatile char *ptr = (char *)(conf + 1); 77 | ptr < (char *)conf + conf->length; ptr += 8) { 78 | if (*ptr == '\0') { 79 | ptr += 12; 80 | panic_on(++__am_ncpu > MAX_CPU, "cannot support > MAX_CPU processors"); 81 | } 82 | } 83 | return; 84 | } 85 | } 86 | bug(); 87 | } 88 | 89 | void __am_percpu_initgdt() { 90 | #if __x86_64__ 91 | SegDesc *gdt = CPU->gdt; 92 | TSS64 *tss = &CPU->tss; 93 | gdt[SEG_KCODE] = SEG64(STA_X | STA_R, DPL_KERN); 94 | gdt[SEG_KDATA] = SEG64(STA_W, DPL_KERN); 95 | gdt[SEG_UCODE] = SEG64(STA_X | STA_R, DPL_USER); 96 | gdt[SEG_UDATA] = SEG64(STA_W, DPL_USER); 97 | gdt[SEG_TSS] = SEG16(STS_T32A, tss, sizeof(*tss)-1, DPL_KERN); 98 | bug_on((uintptr_t)tss >> 32); 99 | set_gdt(gdt, sizeof(gdt[0]) * (NR_SEG + 1)); 100 | set_tr(KSEL(SEG_TSS)); 101 | #else 102 | SegDesc *gdt = CPU->gdt; 103 | TSS32 *tss = &CPU->tss; 104 | gdt[SEG_KCODE] = SEG32(STA_X | STA_R, 0, 0xffffffff, DPL_KERN); 105 | gdt[SEG_KDATA] = SEG32(STA_W, 0, 0xffffffff, DPL_KERN); 106 | gdt[SEG_UCODE] = SEG32(STA_X | STA_R, 0, 0xffffffff, DPL_USER); 107 | gdt[SEG_UDATA] = SEG32(STA_W, 0, 0xffffffff, DPL_USER); 108 | gdt[SEG_TSS] = SEG16(STS_T32A, tss, sizeof(*tss)-1, DPL_KERN); 109 | set_gdt(gdt, sizeof(gdt[0]) * NR_SEG); 110 | set_tr(KSEL(SEG_TSS)); 111 | #endif 112 | } 113 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/vme.c: -------------------------------------------------------------------------------- 1 | #include "x86-qemu.h" 2 | 3 | const struct mmu_config mmu = { 4 | .pgsize = 4096, 5 | #if __x86_64__ 6 | .ptlevels = 4, 7 | .pgtables = { 8 | { "CR3", 0x000000000000, 0, 0 }, 9 | { "PML4", 0xff8000000000, 39, 9 }, 10 | { "PDPT", 0x007fc0000000, 30, 9 }, 11 | { "PD", 0x00003fe00000, 21, 9 }, 12 | { "PT", 0x0000001ff000, 12, 9 }, 13 | }, 14 | #else 15 | .ptlevels = 2, 16 | .pgtables = { 17 | { "CR3", 0x00000000, 0, 0 }, 18 | { "PD", 0xffc00000, 22, 10 }, 19 | { "PT", 0x003ff000, 12, 10 }, 20 | }, 21 | #endif 22 | }; 23 | 24 | static const struct vm_area vm_areas[] = { 25 | #ifdef __x86_64__ 26 | { RANGE(0x100000000000, 0x108000000000), 0 }, // 512 GiB user space 27 | { RANGE(0x000000000000, 0x008000000000), 1 }, // 512 GiB kernel 28 | #else 29 | { RANGE( 0x40000000, 0x80000000), 0 }, // 1 GiB user space 30 | { RANGE( 0x00000000, 0x40000000), 1 }, // 1 GiB kernel 31 | { RANGE( 0xfd000000, 0x00000000), 1 }, // memory-mapped I/O 32 | #endif 33 | }; 34 | #define uvm_area (vm_areas[0].area) 35 | 36 | static uintptr_t *kpt; 37 | static void *(*pgalloc)(int size); 38 | static void (*pgfree)(void *); 39 | 40 | static void *pgallocz() { 41 | uintptr_t *base = pgalloc(mmu.pgsize); 42 | panic_on(!base, "cannot allocate page"); 43 | for (int i = 0; i < mmu.pgsize / sizeof(uintptr_t); i++) { 44 | base[i] = 0; 45 | } 46 | return base; 47 | } 48 | 49 | static int indexof(uintptr_t addr, const struct ptinfo *info) { 50 | return ((uintptr_t)addr & info->mask) >> info->shift; 51 | } 52 | 53 | static uintptr_t baseof(uintptr_t addr) { 54 | return addr & ~(mmu.pgsize - 1); 55 | } 56 | 57 | static uintptr_t *ptwalk(AddrSpace *as, uintptr_t addr, int flags) { 58 | uintptr_t cur = (uintptr_t)&as->ptr; 59 | 60 | for (int i = 0; i <= mmu.ptlevels; i++) { 61 | const struct ptinfo *ptinfo = &mmu.pgtables[i]; 62 | uintptr_t *pt = (uintptr_t *)cur, next_page; 63 | int index = indexof(addr, ptinfo); 64 | if (i == mmu.ptlevels) return &pt[index]; 65 | 66 | if (!(pt[index] & PTE_P)) { 67 | next_page = (uintptr_t)pgallocz(); 68 | pt[index] = next_page | PTE_P | flags; 69 | } else { 70 | next_page = baseof(pt[index]); 71 | } 72 | cur = next_page; 73 | } 74 | bug(); 75 | } 76 | 77 | static void teardown(int level, uintptr_t *pt) { 78 | if (level > mmu.ptlevels) return; 79 | for (int index = 0; index < (1 << mmu.pgtables[level].bits); index++) { 80 | if ((pt[index] & PTE_P) && (pt[index] & PTE_U)) { 81 | teardown(level + 1, (void *)baseof(pt[index])); 82 | } 83 | } 84 | if (level >= 1) { 85 | pgfree(pt); 86 | } 87 | } 88 | 89 | bool vme_init(void *(*_pgalloc)(int size), void (*_pgfree)(void *)) { 90 | panic_on(cpu_current() != 0, "init VME in non-bootstrap CPU"); 91 | pgalloc = _pgalloc; 92 | pgfree = _pgfree; 93 | 94 | #if __x86_64__ 95 | kpt = (void *)PML4_ADDR; 96 | #else 97 | AddrSpace as; 98 | as.ptr = NULL; 99 | for (int i = 0; i < LENGTH(vm_areas); i++) { 100 | const struct vm_area *vma = &vm_areas[i]; 101 | if (vma->kernel) { 102 | for (uintptr_t cur = (uintptr_t)vma->area.start; 103 | cur != (uintptr_t)vma->area.end; 104 | cur += mmu.pgsize) { 105 | *ptwalk(&as, cur, PTE_W) = cur | PTE_P | PTE_W; 106 | } 107 | } 108 | } 109 | kpt = (void *)baseof((uintptr_t)as.ptr); 110 | #endif 111 | 112 | set_cr3(kpt); 113 | set_cr0(get_cr0() | CR0_PG); 114 | return true; 115 | } 116 | 117 | void protect(AddrSpace *as) { 118 | uintptr_t *upt = pgallocz(); 119 | 120 | for (int i = 0; i < LENGTH(vm_areas); i++) { 121 | const struct vm_area *vma = &vm_areas[i]; 122 | if (vma->kernel) { 123 | const struct ptinfo *info = &mmu.pgtables[1]; // level-1 page table 124 | for (uintptr_t cur = (uintptr_t)vma->area.start; 125 | cur != (uintptr_t)vma->area.end; 126 | cur += (1L << info->shift)) { 127 | int index = indexof(cur, info); 128 | upt[index] = kpt[index]; 129 | } 130 | } 131 | } 132 | as->pgsize = mmu.pgsize; 133 | as->area = uvm_area; 134 | as->ptr = (void *)((uintptr_t)upt | PTE_P | PTE_U); 135 | } 136 | 137 | void unprotect(AddrSpace *as) { 138 | teardown(0, (void *)&as->ptr); 139 | } 140 | 141 | void map(AddrSpace *as, void *va, void *pa, int prot) { 142 | panic_on(!IN_RANGE(va, uvm_area), "mapping an invalid address"); 143 | panic_on((uintptr_t)va != ROUNDDOWN(va, mmu.pgsize) || 144 | (uintptr_t)pa != ROUNDDOWN(pa, mmu.pgsize), "non-page-boundary address"); 145 | 146 | uintptr_t *ptentry = ptwalk(as, (uintptr_t)va, PTE_W | PTE_U); 147 | if (prot == MMAP_NONE) { 148 | panic_on(!(*ptentry & PTE_P), "unmapping a non-mapped page"); 149 | *ptentry = 0; 150 | } else { 151 | panic_on(*ptentry & PTE_P, "remapping a mapped page"); 152 | uintptr_t pte = (uintptr_t)pa | PTE_P | PTE_U | ((prot & MMAP_WRITE) ? PTE_W : 0); 153 | *ptentry = pte; 154 | } 155 | ptwalk(as, (uintptr_t)va, PTE_W | PTE_U); 156 | } 157 | 158 | Context *ucontext(AddrSpace *as, Area kstack, void *entry) { 159 | Context *ctx = kstack.end - sizeof(Context); 160 | *ctx = (Context) { 0 }; 161 | 162 | #if __x86_64__ 163 | ctx->cs = USEL(SEG_UCODE); 164 | ctx->ss = USEL(SEG_UDATA); 165 | ctx->rip = (uintptr_t)entry; 166 | ctx->rflags = FL_IF; 167 | ctx->rsp = (uintptr_t)uvm_area.end; 168 | ctx->rsp0 = (uintptr_t)kstack.end; 169 | #else 170 | ctx->cs = USEL(SEG_UCODE); 171 | ctx->ds = USEL(SEG_UDATA); 172 | ctx->ss3 = USEL(SEG_UDATA); 173 | ctx->eip = (uintptr_t)entry; 174 | ctx->eflags = FL_IF; 175 | ctx->esp = (uintptr_t)uvm_area.end; 176 | ctx->esp0 = (uintptr_t)kstack.end; 177 | #endif 178 | ctx->cr3 = as->ptr; 179 | 180 | return ctx; 181 | } 182 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/am/src/x86/qemu/x86-qemu.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PML4_ADDR 0x1000 4 | #define PDPT_ADDR 0x2000 5 | 6 | #define NR_SEG 6 // GDT size 7 | #define SEG_KCODE 1 // Kernel code 8 | #define SEG_KDATA 2 // Kernel data/stack 9 | #define SEG_UCODE 3 // User code 10 | #define SEG_UDATA 4 // User data/stack 11 | #define SEG_TSS 5 // Global unique task state segement 12 | 13 | #define NR_IRQ 256 // IDT size 14 | 15 | #ifndef __ASSEMBLER__ 16 | 17 | #include 18 | #include 19 | 20 | struct kernel_stack { 21 | uint8_t stack[8192]; 22 | }; 23 | 24 | static inline void *stack_top(struct kernel_stack *stk) { 25 | return stk->stack + sizeof(stk->stack); 26 | } 27 | 28 | struct mmu_config { 29 | int ptlevels, pgsize; 30 | struct ptinfo { 31 | const char *name; 32 | uintptr_t mask; 33 | int shift, bits; 34 | } pgtables[]; 35 | }; 36 | 37 | struct vm_area { 38 | Area area; 39 | int kernel; 40 | }; 41 | 42 | void __am_iret(Context *ctx); 43 | 44 | struct cpu_local { 45 | AddrSpace *uvm; 46 | #if __x86_64__ 47 | SegDesc gdt[NR_SEG + 1]; 48 | TSS64 tss; 49 | #else 50 | SegDesc gdt[NR_SEG]; 51 | TSS32 tss; 52 | #endif 53 | struct kernel_stack stack; 54 | }; 55 | 56 | #if __x86_64__ 57 | struct trap_frame { 58 | Context saved_context; 59 | uint64_t irq, errcode; 60 | uint64_t rip, cs, rflags, rsp, ss; 61 | }; 62 | #else 63 | struct trap_frame { 64 | Context saved_context; 65 | uint32_t irq, errcode; 66 | uint32_t eip, cs, eflags, esp, ss; 67 | }; 68 | #endif 69 | 70 | extern volatile uint32_t *__am_lapic; 71 | extern int __am_ncpu; 72 | extern struct cpu_local __am_cpuinfo[MAX_CPU]; 73 | 74 | #define CPU (&__am_cpuinfo[cpu_current()]) 75 | 76 | #define bug_on(cond) \ 77 | do { \ 78 | if (cond) panic("internal error (likely a bug in AM)"); \ 79 | } while (0) 80 | 81 | #define bug() bug_on(1) 82 | 83 | // apic utils 84 | void __am_lapic_eoi(); 85 | void __am_ioapic_init(); 86 | void __am_lapic_bootap(uint32_t cpu, void *address); 87 | void __am_ioapic_enable(int irq, int cpu); 88 | 89 | // x86-specific operations 90 | void __am_bootcpu_init(); 91 | void __am_percpu_init(); 92 | Area __am_heap_init(); 93 | void __am_lapic_init(); 94 | void __am_othercpu_entry(); 95 | void __am_percpu_initirq(); 96 | void __am_percpu_initgdt(); 97 | void __am_percpu_initlapic(); 98 | void __am_stop_the_world(); 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/klib/Makefile: -------------------------------------------------------------------------------- 1 | NAME = klib 2 | SRCS = $(shell find src/ -name "*.c") 3 | include $(AM_HOME)/Makefile 4 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/klib/include/klib-macros.h: -------------------------------------------------------------------------------- 1 | #ifndef KLIB_MACROS_H__ 2 | #define KLIB_MACROS_H__ 3 | 4 | #define ROUNDUP(a, sz) ((((uintptr_t)a) + (sz) - 1) & ~((sz) - 1)) 5 | #define ROUNDDOWN(a, sz) ((((uintptr_t)a)) & ~((sz) - 1)) 6 | #define LENGTH(arr) (sizeof(arr) / sizeof((arr)[0])) 7 | #define RANGE(st, ed) (Area) { .start = (void *)(st), .end = (void *)(ed) } 8 | #define IN_RANGE(ptr, area) ((area).start <= (ptr) && (ptr) < (area).end) 9 | 10 | #define STRINGIFY(s) #s 11 | #define TOSTRING(s) STRINGIFY(s) 12 | #define _CONCAT(x, y) x ## y 13 | #define CONCAT(x, y) _CONCAT(x, y) 14 | 15 | #define putstr(s) \ 16 | ({ for (const char *p = s; *p; p++) putch(*p); }) 17 | 18 | #define io_read(reg) \ 19 | ({ reg##_T __io_param; \ 20 | ioe_read(reg, &__io_param); \ 21 | __io_param; }) 22 | 23 | #define io_write(reg, ...) \ 24 | ({ reg##_T __io_param = (reg##_T) { __VA_ARGS__ }; \ 25 | ioe_write(reg, &__io_param); }) 26 | 27 | #define static_assert(const_cond) \ 28 | static char CONCAT(_static_assert_, __LINE__) [(const_cond) ? 1 : -1] __attribute__((unused)) 29 | 30 | #define panic_on(cond, s) \ 31 | ({ if (cond) { \ 32 | putstr("AM Panic: "); putstr(s); \ 33 | putstr(" @ " __FILE__ ":" TOSTRING(__LINE__) " \n"); \ 34 | halt(1); \ 35 | } }) 36 | 37 | #define panic(s) panic_on(1, s) 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/klib/include/klib.h: -------------------------------------------------------------------------------- 1 | #ifndef KLIB_H__ 2 | #define KLIB_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | // #define __NATIVE_USE_KLIB__ 13 | 14 | // string.h 15 | void *memset (void *s, int c, size_t n); 16 | void *memcpy (void *dst, const void *src, size_t n); 17 | void *memmove (void *dst, const void *src, size_t n); 18 | int memcmp (const void *s1, const void *s2, size_t n); 19 | size_t strlen (const char *s); 20 | char *strcat (char *dst, const char *src); 21 | char *strcpy (char *dst, const char *src); 22 | char *strncpy (char *dst, const char *src, size_t n); 23 | int strcmp (const char *s1, const char *s2); 24 | int strncmp (const char *s1, const char *s2, size_t n); 25 | 26 | // stdlib.h 27 | void srand (unsigned int seed); 28 | int rand (void); 29 | void *malloc (size_t size); 30 | void free (void *ptr); 31 | int abs (int x); 32 | int atoi (const char *nptr); 33 | 34 | // stdio.h 35 | int printf (const char *format, ...); 36 | int sprintf (char *str, const char *format, ...); 37 | int snprintf (char *str, size_t size, const char *format, ...); 38 | int vsprintf (char *str, const char *format, va_list ap); 39 | int vsnprintf (char *str, size_t size, const char *format, va_list ap); 40 | 41 | // assert.h 42 | #ifdef NDEBUG 43 | #define assert(ignore) ((void)0) 44 | #else 45 | #define assert(cond) \ 46 | do { \ 47 | if (!(cond)) { \ 48 | printf("Assertion fail at %s:%d\n", __FILE__, __LINE__); \ 49 | halt(1); \ 50 | } \ 51 | } while (0) 52 | #endif 53 | 54 | #ifdef __cplusplus 55 | } 56 | #endif 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/klib/src/cpp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef __ISA_NATIVE__ 5 | 6 | void __dso_handle() { 7 | } 8 | 9 | void __cxa_guard_acquire() { 10 | } 11 | 12 | void __cxa_guard_release() { 13 | } 14 | 15 | void __cxa_atexit() { 16 | assert(0); 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/klib/src/stdio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | #if !defined(__ISA_NATIVE__) || defined(__NATIVE_USE_KLIB__) 8 | 9 | int printf(const char *fmt, ...) { 10 | panic("Not implemented"); 11 | } 12 | 13 | int vsprintf(char *out, const char *fmt, va_list ap) { 14 | panic("Not implemented"); 15 | } 16 | 17 | int sprintf(char *out, const char *fmt, ...) { 18 | panic("Not implemented"); 19 | } 20 | 21 | int snprintf(char *out, size_t n, const char *fmt, ...) { 22 | panic("Not implemented"); 23 | } 24 | 25 | int vsnprintf(char *out, size_t n, const char *fmt, va_list ap) { 26 | panic("Not implemented"); 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/klib/src/stdlib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #if !defined(__ISA_NATIVE__) || defined(__NATIVE_USE_KLIB__) 6 | static unsigned long int next = 1; 7 | 8 | int rand(void) { 9 | // RAND_MAX assumed to be 32767 10 | next = next * 1103515245 + 12345; 11 | return (unsigned int)(next/65536) % 32768; 12 | } 13 | 14 | void srand(unsigned int seed) { 15 | next = seed; 16 | } 17 | 18 | int abs(int x) { 19 | return (x < 0 ? -x : x); 20 | } 21 | 22 | int atoi(const char* nptr) { 23 | int x = 0; 24 | while (*nptr == ' ') { nptr ++; } 25 | while (*nptr >= '0' && *nptr <= '9') { 26 | x = x * 10 + *nptr - '0'; 27 | nptr ++; 28 | } 29 | return x; 30 | } 31 | 32 | void *malloc(size_t size) { 33 | // On native, malloc() will be called during initializaion of C runtime. 34 | // Therefore do not call panic() here, else it will yield a dead recursion: 35 | // panic() -> putchar() -> (glibc) -> malloc() -> panic() 36 | #if !(defined(__ISA_NATIVE__) && defined(__NATIVE_USE_KLIB__)) 37 | panic("Not implemented"); 38 | #endif 39 | return NULL; 40 | } 41 | 42 | void free(void *ptr) { 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/klib/src/string.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #if !defined(__ISA_NATIVE__) || defined(__NATIVE_USE_KLIB__) 6 | 7 | size_t strlen(const char *s) { 8 | panic("Not implemented"); 9 | } 10 | 11 | char *strcpy(char *dst, const char *src) { 12 | panic("Not implemented"); 13 | } 14 | 15 | char *strncpy(char *dst, const char *src, size_t n) { 16 | panic("Not implemented"); 17 | } 18 | 19 | char *strcat(char *dst, const char *src) { 20 | panic("Not implemented"); 21 | } 22 | 23 | int strcmp(const char *s1, const char *s2) { 24 | panic("Not implemented"); 25 | } 26 | 27 | int strncmp(const char *s1, const char *s2, size_t n) { 28 | panic("Not implemented"); 29 | } 30 | 31 | void *memset(void *s, int c, size_t n) { 32 | panic("Not implemented"); 33 | } 34 | 35 | void *memmove(void *dst, const void *src, size_t n) { 36 | panic("Not implemented"); 37 | } 38 | 39 | void *memcpy(void *out, const void *in, size_t n) { 40 | panic("Not implemented"); 41 | } 42 | 43 | int memcmp(const void *s1, const void *s2, size_t n) { 44 | panic("Not implemented"); 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/scripts/isa/x86.mk: -------------------------------------------------------------------------------- 1 | export CROSS_COMPILE := x86_64-linux-gnu- 2 | CFLAGS += -m32 -fno-pic -fno-omit-frame-pointer -march=i386 3 | CFLAGS += -fcf-protection=none # remove endbr32 in Ubuntu 20.04 with a CPU newer than Comet Lake 4 | ASFLAGS += -m32 -fno-pic 5 | LDFLAGS += -melf_i386 6 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/scripts/isa/x86_64.mk: -------------------------------------------------------------------------------- 1 | export CROSS_COMPILE := x86_64-linux-gnu- 2 | CFLAGS += -m64 -fPIC -mno-sse 3 | ASFLAGS += -m64 -fPIC 4 | LDFLAGS += -melf_x86_64 5 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/scripts/native.mk: -------------------------------------------------------------------------------- 1 | AM_SRCS := native/trm.c \ 2 | native/ioe.c \ 3 | native/cte.c \ 4 | native/trap.S \ 5 | native/vme.c \ 6 | native/mpe.c \ 7 | native/platform.c \ 8 | native/ioe/input.c \ 9 | native/ioe/timer.c \ 10 | native/ioe/gpu.c \ 11 | native/ioe/audio.c \ 12 | native/ioe/disk.c \ 13 | 14 | CFLAGS += -fpie 15 | ASFLAGS += -fpie -pie 16 | 17 | image: 18 | @echo + LD "->" $(IMAGE_REL) 19 | @g++ -pie -o $(IMAGE) -Wl,--whole-archive $(LINKAGE) -Wl,-no-whole-archive -lSDL2 -ldl 20 | 21 | run: image 22 | $(IMAGE) 23 | 24 | gdb: image 25 | gdb -ex "handle SIGUSR1 SIGUSR2 SIGSEGV noprint nostop" $(IMAGE) 26 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/scripts/platform/qemu.mk: -------------------------------------------------------------------------------- 1 | .PHONY: build-arg 2 | 3 | LDFLAGS += -N -Ttext-segment=0x00100000 4 | QEMU_FLAGS += -serial mon:stdio \ 5 | -machine accel=tcg \ 6 | -smp "$(smp)" \ 7 | -drive format=raw,file=$(IMAGE) 8 | 9 | build-arg: image 10 | @( echo -n $(mainargs); ) | dd if=/dev/stdin of=$(IMAGE) bs=512 count=2 seek=1 conv=notrunc status=none 11 | 12 | BOOT_HOME := $(AM_HOME)/am/src/x86/qemu/boot 13 | 14 | image: $(IMAGE).elf 15 | @$(MAKE) -s -C $(BOOT_HOME) 16 | @echo + CREATE "->" $(IMAGE_REL) 17 | @( cat $(BOOT_HOME)/bootblock.o; head -c 1024 /dev/zero; cat $(IMAGE).elf ) > $(IMAGE) 18 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/scripts/x86-qemu.mk: -------------------------------------------------------------------------------- 1 | include $(AM_HOME)/scripts/isa/x86.mk 2 | include $(AM_HOME)/scripts/platform/qemu.mk 3 | 4 | AM_SRCS := x86/qemu/start32.S \ 5 | x86/qemu/trap32.S \ 6 | x86/qemu/trm.c \ 7 | x86/qemu/cte.c \ 8 | x86/qemu/ioe.c \ 9 | x86/qemu/vme.c \ 10 | x86/qemu/mpe.c 11 | 12 | run: build-arg 13 | @qemu-system-i386 $(QEMU_FLAGS) 14 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/abstract-machine/scripts/x86_64-qemu.mk: -------------------------------------------------------------------------------- 1 | include $(AM_HOME)/scripts/isa/x86_64.mk 2 | include $(AM_HOME)/scripts/platform/qemu.mk 3 | 4 | AM_SRCS := x86/qemu/start64.S \ 5 | x86/qemu/trap64.S \ 6 | x86/qemu/trm.c \ 7 | x86/qemu/cte.c \ 8 | x86/qemu/ioe.c \ 9 | x86/qemu/vme.c \ 10 | x86/qemu/mpe.c 11 | 12 | run: build-arg 13 | @qemu-system-x86_64 $(QEMU_FLAGS) 14 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/amgame/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/amgame/Makefile: -------------------------------------------------------------------------------- 1 | NAME := amgame 2 | SRCS := $(shell find -L ./src/ -name "*.c") 3 | export MODULE := L0 4 | export AM_HOME := $(PWD)/../abstract-machine 5 | ifeq ($(ARCH),) 6 | export ARCH := x86_64-qemu 7 | endif 8 | 9 | include $(AM_HOME)/Makefile 10 | include ../Makefile.lab 11 | image: git 12 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/amgame/include/game.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void splash(); 7 | void print_key(); 8 | static inline void puts(const char *s) { 9 | for (; *s; s++) putch(*s); 10 | } 11 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/amgame/src/game.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Operating system is a C program! 4 | int main(const char *args) { 5 | ioe_init(); 6 | 7 | puts("mainargs = \""); 8 | puts(args); // make run mainargs=xxx 9 | puts("\"\n"); 10 | 11 | splash(); 12 | 13 | puts("Press any key to see its key code...\n"); 14 | while (1) { 15 | print_key(); 16 | } 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/amgame/src/keyboard.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define KEYNAME(key) \ 4 | [AM_KEY_##key] = #key, 5 | static const char *key_names[] = { 6 | AM_KEYS(KEYNAME) 7 | }; 8 | 9 | void print_key() { 10 | AM_INPUT_KEYBRD_T event = { .keycode = AM_KEY_NONE }; 11 | ioe_read(AM_INPUT_KEYBRD, &event); 12 | if (event.keycode != AM_KEY_NONE && event.keydown) { 13 | puts("Key pressed: "); 14 | puts(key_names[event.keycode]); 15 | puts("\n"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/amgame/src/video.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SIDE 16 4 | static int w, h; 5 | 6 | static void init() { 7 | AM_GPU_CONFIG_T info = {0}; 8 | ioe_read(AM_GPU_CONFIG, &info); 9 | w = info.width; 10 | h = info.height; 11 | } 12 | 13 | static void draw_tile(int x, int y, int w, int h, uint32_t color) { 14 | uint32_t pixels[w * h]; // WARNING: large stack-allocated memory 15 | AM_GPU_FBDRAW_T event = { 16 | .x = x, .y = y, .w = w, .h = h, .sync = 1, 17 | .pixels = pixels, 18 | }; 19 | for (int i = 0; i < w * h; i++) { 20 | pixels[i] = color; 21 | } 22 | ioe_write(AM_GPU_FBDRAW, &event); 23 | } 24 | 25 | void splash() { 26 | init(); 27 | for (int x = 0; x * SIDE <= w; x ++) { 28 | for (int y = 0; y * SIDE <= h; y++) { 29 | if ((x & 1) ^ (y & 1)) { 30 | draw_tile(x * SIDE, y * SIDE, SIDE, SIDE, 0xffffff); // white 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/test/Makefile: -------------------------------------------------------------------------------- 1 | ME := hello 2 | SRCS := main.c say.c 3 | include $(AM_HOME)/Makefile.app 4 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/test/main.c: -------------------------------------------------------------------------------- 1 | void say(const char* s); 2 | int main() { 3 | say("hello\n"); 4 | } 5 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/test/main.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeZephyr/NJU_OS_LAB/0ff86f953f8e2bde886512a20649bdb7a2f4bc7f/lesson_1/L0-amgame/test/main.o -------------------------------------------------------------------------------- /lesson_1/L0-amgame/test/say.c: -------------------------------------------------------------------------------- 1 | void putch(char ch); 2 | int putchar(int ch); 3 | 4 | void say(const char* s) { 5 | for (; *s; s++) { 6 | #ifdef __ARCH__ 7 | putch(*s); // AbstractMachine,没有 libc,调用 TRM API 打印字符 8 | #else 9 | putchar(*s); // 操作系统,调用libc打印字符 10 | #endif 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lesson_1/L0-amgame/test/say.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeZephyr/NJU_OS_LAB/0ff86f953f8e2bde886512a20649bdb7a2f4bc7f/lesson_1/L0-amgame/test/say.o -------------------------------------------------------------------------------- /lesson_1/M1-pstree/README.md: -------------------------------------------------------------------------------- 1 | # NJUOSLAB M1:打印进程树(pstree功能实现) 2 | 3 | 把系统中的进程按照父亲-孩子的树状结构打印到终端。 4 | 5 | -p 或 --show-pids: 打印每个进程的进程号。 6 | -n 或 --numeric-sort: 按照pid的数值从小到大顺序输出一个进程的直接孩子。 7 | -V 或 --version: 打印版本信息。 8 | 9 | /proc/pid/stat 文件中有name和ppid 10 | 11 | TODO: 12 | 1. 当进程名称出现空格 “ ” 时,如:(tmux:sever),存在bug 因为读取/proc/1234(对应进程pid)/stat文件出现空格读取错位存在bug需要解决。 13 | 2. 考虑更贴合实际pstree的输出: 对于叶子节点 /proc/1234/task/ 文件夹下会存在叶子节点开启的子进程( 还会有个该进程的副本文件夹 )文件夹,里面stat文件记录pid和进程名称,且这种进程不会在/proc/ 下出现,pstree中这种进程输出样式为 [{ *** }}] 14 | 15 | 思考: 16 | 1. 万一我得到进程号以后,进去发现文件没了 (进程终止了),怎么办?会不会有这种情况?万一有我的程序会不会 crash……? 17 | 2. 进程的信息一直在变,文件的内容也一直在变 (两次 cat 的结果不同)。那我会不会读到不一致的信息(前一半是旧信息、新一半是新信息)?这两个问题都是 race condition 导致的;我们将会在并发部分回到这个话题。 18 | 3. 如果我不确信这些事会不会发生,我有没有写一个程序,至少在压力环境下测试一下它们有没有可能发生?嗯,如果我同时运行很多程序,每个程序都不断扫描目录、读取文件,也观察不到这个问题,至少应该可以放点心。 19 | -------------------------------------------------------------------------------- /lesson_1/M1-pstree/a.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeZephyr/NJU_OS_LAB/0ff86f953f8e2bde886512a20649bdb7a2f4bc7f/lesson_1/M1-pstree/a.out -------------------------------------------------------------------------------- /lesson_1/M1-pstree/pstree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeZephyr/NJU_OS_LAB/0ff86f953f8e2bde886512a20649bdb7a2f4bc7f/lesson_1/M1-pstree/pstree -------------------------------------------------------------------------------- /lesson_1/M1-pstree/pstree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define true ((unsigned char) (1)) 9 | #define false ((unsigned char) (0)) 10 | 11 | unsigned char normal = false, show_pids = false, numberic_sort = false, show_version = false; 12 | 13 | typedef struct pidinfo { 14 | char name[50]; // 进程名称 15 | pid_t pid; // 进程id 16 | pid_t ppid; // 父进程id 17 | struct pidinfo* next; // 指向下一个进程 18 | }PidInfo; 19 | PidInfo pidinfos[1000]; 20 | int pid_count; 21 | 22 | int childrens_index[500]; 23 | 24 | // 定义进程树节点的结构体 25 | typedef struct processtree { 26 | pid_t pid; // 进程id 27 | char name[50]; // 进程名称 28 | struct childprocesses* children; 29 | }Processtree; 30 | // 定义子进程链表结点的结构体 31 | struct childprocesses { 32 | struct childprocesses* next; // 指向下一个子进程的指针 33 | Processtree* process; // 指向当前子进程的指针 34 | }; 35 | 36 | /* 37 | * 初始化操作 38 | */ 39 | void init() { 40 | show_pids = false; 41 | numberic_sort = false; 42 | show_version = false; 43 | pid_count = 0; 44 | } 45 | /* 46 | * 获取命令行参数 47 | * params: argc表示命令行参数的数量(包括程序名本身), argv为命令行参数的具体值 48 | * return: 经过处理的参数值 49 | */ 50 | int get_command_line_ops(int argc, char* argv[]) { 51 | // 目前只处理一个和两个参数值的情况 52 | assert(argc >= 1 && argc <= 2); 53 | if (argc == 1) { 54 | normal = true; 55 | return 0; 56 | } 57 | if (!strcmp(argv[1], "-p") || !strcmp(argv[1], "--show-pids")) { 58 | show_pids = true; 59 | } else if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numberic-sort")) { 60 | numberic_sort = true; 61 | } else if (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version")) { 62 | show_version = true; 63 | } else { 64 | return -1; 65 | } 66 | assert(!argv[argc]); 67 | return 0; 68 | } 69 | /* 70 | * 设置进程ppid和name 71 | */ 72 | void set_process_ppid_pname(PidInfo* node) { 73 | char* str = (char*)malloc(sizeof(char) * 20); 74 | if (!str) { 75 | fprintf(stderr, "分配内存失败\n"); 76 | exit(1); 77 | } 78 | // 使用sprintf将进程id转换为字符串 79 | sprintf(str, "%d", node->pid); 80 | 81 | // 构建进程状态文件路径 (/proc/pid/stat) 82 | char* processpath = (char*)malloc(sizeof("/proc/") + sizeof(str) + sizeof("/stat")); 83 | if (!processpath) { 84 | fprintf(stderr, "分配内存失败\n"); 85 | exit(1); 86 | } 87 | strcpy(processpath, "/proc/"); 88 | strcat(processpath, str); 89 | strcat(processpath, "/stat"); 90 | FILE* fp = fopen(processpath, "r"); 91 | if (fp) { 92 | char temp; 93 | pid_t _pid, _ppid; 94 | char pname[50]; 95 | fscanf(fp, "%d (%s %c %d", &_pid, pname, &temp, &_ppid); 96 | pname[strlen(pname) - 1] = '\0'; 97 | // 验证进程id是否相等 98 | assert(node->pid == _pid); 99 | strcpy(node->name, pname); 100 | node->ppid = _ppid; 101 | fclose(fp); 102 | free(str); 103 | } else { 104 | fprintf(stderr, "打开文件失败\n"); 105 | free(str); 106 | exit(1); 107 | } 108 | } 109 | /* 110 | * 设置进程信息 111 | */ 112 | void set_process_info() { 113 | DIR *dir = opendir("/proc"); 114 | if (!dir) { 115 | fprintf(stderr, "无法打开 /proc 目录\n"); 116 | exit(EXIT_FAILURE); 117 | } else { 118 | int pid = 0; 119 | struct dirent* entry; 120 | PidInfo node; 121 | while ((entry = readdir(dir)) != NULL) { 122 | // 利用atoi转换,如果不是有效数字返回0 123 | if ((pid = atoi(entry->d_name)) == 0) { 124 | continue; 125 | } 126 | node.pid = pid; 127 | set_process_ppid_pname(&node); 128 | pidinfos[pid_count++] = node; 129 | } 130 | } 131 | } 132 | /* 133 | * params: pid为父进程ID, index为要保存的子进程索引 134 | * 查找指定父进程ID的所有紫禁城索引 135 | */ 136 | int find_all_childrens(pid_t pid, int index[]) { 137 | int count = 0; 138 | for (int i = 0; i < pid_count; i++) { 139 | if (pidinfos[i].ppid == pid) { 140 | index[count++] = i; 141 | } 142 | } 143 | return count; 144 | } 145 | void create_tree(Processtree* root, int tab_length) { 146 | char str[100] = {0}; 147 | int childrens_index[500] = {0}; // 所有子进程在pidinfos中的下标 148 | int count = find_all_childrens(root->pid, childrens_index); 149 | 150 | if (!count) { // 叶子结点,没有孩子 151 | printf("%s(%d)", root->name, root->pid); // TODO 还有结点,在/proc/pid/task/.../stat中 152 | return; 153 | } 154 | sprintf(str, "%s(%d)-", root->name, root->pid); 155 | printf("%s", str); 156 | 157 | int flag = 0; 158 | if (count > 1) { 159 | printf("+-"); 160 | flag = 2; 161 | } 162 | 163 | root->children = (struct childprocesses*)malloc(sizeof(struct childprocesses)); 164 | if (root->children == NULL) { 165 | fprintf(stderr, "分配内存失败\n"); 166 | exit(1); 167 | } 168 | struct childprocesses* node = root->children; 169 | 170 | for (int i = 0; i < count; i++) { 171 | node->process = (Processtree*)malloc(sizeof(Processtree)); 172 | if (!node->process) { 173 | fprintf(stderr, "分配内存失败\n"); 174 | exit(1); 175 | } 176 | node->process->pid = pidinfos[childrens_index[i]].pid; 177 | strcpy(node->process->name, pidinfos[childrens_index[i]].name); 178 | create_tree(node->process, strlen(str) + tab_length + flag); 179 | if (i + 1 < count) { 180 | node->next = (struct childprocesses*)malloc(sizeof(struct childprocesses)); 181 | if (!node->next) { 182 | fprintf(stderr, "分配内存失败\n"); 183 | exit(1); 184 | } 185 | node = node->next; 186 | printf("\n"); 187 | for (int j = 0; j < strlen(str) + tab_length; j++) { 188 | printf(" "); 189 | } 190 | printf("|-"); 191 | } 192 | } 193 | free(root); 194 | } 195 | void create_tree_nopid(Processtree* root, int tab_length) { 196 | char str[100] = {0}; 197 | int childrens_index[500] = {0}; // 所有子进程在pidinfos中的下标 198 | int count = find_all_childrens(root->pid, childrens_index); 199 | 200 | if (!count) { // 叶子结点,没有孩子 201 | printf("%s", root->name); // TODO 还有结点,在/proc/pid/task/.../stat中 202 | return; 203 | } 204 | sprintf(str, "%s-", root->name); 205 | printf("%s", str); 206 | 207 | int flag = 0; 208 | if (count > 1) { 209 | printf("+-"); 210 | flag = 2; 211 | } 212 | 213 | root->children = (struct childprocesses*)malloc(sizeof(struct childprocesses)); 214 | if (root->children == NULL) { 215 | fprintf(stderr, "分配内存失败\n"); 216 | exit(1); 217 | } 218 | struct childprocesses* node = root->children; 219 | 220 | for (int i = 0; i < count; i++) { 221 | node->process = (Processtree*)malloc(sizeof(Processtree)); 222 | if (!node->process) { 223 | fprintf(stderr, "分配内存失败\n"); 224 | exit(1); 225 | } 226 | node->process->pid = pidinfos[childrens_index[i]].pid; 227 | strcpy(node->process->name, pidinfos[childrens_index[i]].name); 228 | create_tree_nopid(node->process, strlen(str) + tab_length + flag); 229 | if (i + 1 < count) { 230 | node->next = (struct childprocesses*)malloc(sizeof(struct childprocesses)); 231 | if (!node->next) { 232 | fprintf(stderr, "分配内存失败\n"); 233 | exit(1); 234 | } 235 | node = node->next; 236 | printf("\n"); 237 | for (int j = 0; j < strlen(str) + tab_length; j++) { 238 | printf(" "); 239 | } 240 | printf("|-"); 241 | } 242 | } 243 | free(root); 244 | } 245 | /* 246 | * 返回版本信息 247 | */ 248 | char* get_version() { 249 | char *filename = "version"; 250 | char *version_info = NULL; 251 | // 文件指针 252 | FILE *fp = fopen(filename, "r"); 253 | long file_size; 254 | if (fp) { 255 | fseek(fp, 0, SEEK_END); 256 | file_size = ftell(fp); 257 | fseek(fp, 0, SEEK_SET); 258 | 259 | // 动态分配足够内存 260 | version_info = (char *)malloc(file_size + 1); 261 | 262 | // 检查内存是否分配成功 263 | if (version_info == NULL) { 264 | fprintf(stderr, "内存分配失败\n"); 265 | fclose(fp); 266 | exit(EXIT_FAILURE); 267 | } 268 | 269 | fread(version_info, 1, file_size, fp); 270 | version_info[file_size] = '\0'; 271 | // 关闭文件 272 | fclose(fp); 273 | 274 | } else { 275 | fprintf(stderr, "无法打开文件: %s\n", filename); 276 | // 退出程序并返回错误码 277 | exit(EXIT_FAILURE); 278 | } 279 | return version_info; 280 | } 281 | int main(int argc, char *argv[]) { 282 | /* 283 | printf("有%d个参数\n", argc); 284 | for (int i = 0; i < argc; i++) { 285 | printf("argv[%d] = %s\n", i, argv[i]); 286 | } 287 | */ 288 | init(); 289 | if (get_command_line_ops(argc, argv) == -1) { 290 | printf("Illegal parameter formats!\n"); 291 | return -1; 292 | } 293 | if (normal == true) { 294 | set_process_info(); 295 | Processtree* root = (Processtree*)malloc(sizeof(Processtree)); 296 | root->pid = pidinfos[0].pid; 297 | strcpy(root->name, pidinfos[0].name); 298 | create_tree_nopid(root, 0); 299 | } 300 | if (show_pids == true) { 301 | set_process_info(); 302 | Processtree* root = (Processtree*)malloc(sizeof(Processtree)); 303 | root->pid = pidinfos[0].pid; 304 | strcpy(root->name, pidinfos[0].name); 305 | create_tree(root, 0); 306 | } 307 | if (numberic_sort == true) { 308 | set_process_info(); 309 | Processtree* root = (Processtree*)malloc(sizeof(Processtree)); 310 | root->pid = pidinfos[0].pid; 311 | strcpy(root->name, pidinfos[0].name); 312 | create_tree_nopid(root, 0); 313 | } 314 | if (show_version == true) { 315 | printf("%s", get_version()); 316 | } 317 | return 0; 318 | } 319 | -------------------------------------------------------------------------------- /lesson_1/M1-pstree/version: -------------------------------------------------------------------------------- 1 | pstree (PSmisc) UNKNOWN Copyright (C) 1993-2019 Werner Almesberger and Craig Small PSmisc comes with ABSOLUTELY NO WARRANTY. 2 | This is free software, and you are welcome to redistribute it under the terms of the GNU General Public License. 3 | For more information about these matters, see the files named COPYING. 4 | -------------------------------------------------------------------------------- /lesson_1/README.md: -------------------------------------------------------------------------------- 1 | ## 代码解释 2 | 1. simulator.c,模拟数字逻辑电路 3 | 2. 将logisim.c编译运行的结果通过管道传给seven-seg.py 4 | `gcc logisim.c && ./a.out | python3 seven-seg.py` 5 | 3. hanoi-nr.c就是将hanoi-r.c递归程序转换为非递归程序 6 | 7 | ## 笔记 8 | C程序的状态机模型(语义, semantics) 9 | * 状态 = stack frame的列表(每个frame有PC) + 全局变量 10 | * 初始状态 = main(argc, argv), 全局变量初始化 11 | * 迁移 = 执行top stack frame PC的语句; PC++ 12 | * 函数调用 = push frame(frame.PC = 入口) 13 | * 函数返回 = pop frame 14 | -------------------------------------------------------------------------------- /lesson_1/a.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeZephyr/NJU_OS_LAB/0ff86f953f8e2bde886512a20649bdb7a2f4bc7f/lesson_1/a.out -------------------------------------------------------------------------------- /lesson_1/hanoi-nr.c: -------------------------------------------------------------------------------- 1 | // 定义结构体 Frame, 表示递归调用栈的帧 2 | typedef struct { 3 | int pc, n; // 程序计数器和参数n 4 | char from, to, via; // 参数from,to,via分别表示起始柱,目标柱和辅助柱 5 | } Frame; 6 | 7 | // 宏定义call,用于进行递归调用 8 | #define call(...) ({ *(++top) = (Frame) { .pc = 0, __VA_ARGS__ }; }) 9 | // 宏定义ret,用于返回上一层递归调用 10 | #define ret() ({ top--; }) 11 | // 宏定义goto,用于跳转到指定位置 12 | #define goto(loc) ({ f->pc = (loc) - 1; }) 13 | 14 | void hanoi(int n, char from, char to, char via) { 15 | // 生命Frame数组stk作为递归调用栈,初始化top为stk数组的头部前一位 16 | Frame stk[64], *top = stk - 1; 17 | 18 | // 调用宏定义call,将当前递归调用的信息入栈 19 | call(n, from, to, via); 20 | for (Frame *f; (f = top) >= stk; f->pc++) { 21 | switch (f->pc) { 22 | case 0: if (f->n == 1) { printf("%c -> %c\n", f->from, f->to); goto(4); } break; 23 | case 1: call(f->n - 1, f->from, f->via, f->to); break; 24 | case 2: call( 1, f->from, f->to, f->via); break; 25 | case 3: call(f->n - 1, f->via, f->to, f->from); break; 26 | case 4: ret(); break; 27 | default: assert(0); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lesson_1/hanoi-r.c: -------------------------------------------------------------------------------- 1 | void hanoi(int n, char from, char to, char via) { 2 | if (n == 1) printf("%c -> %c\n", from, to); 3 | else { 4 | hanoi(n - 1, from, via, to); 5 | hanoi(1, from, to, via); 6 | hanoi(n - 1, via, to, from); 7 | } 8 | return; 9 | } 10 | -------------------------------------------------------------------------------- /lesson_1/hello-goodbye.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __attribute__((constructor)) void hello() { 4 | printf("Hello, World\n"); 5 | } 6 | 7 | // See also: atexit(3) 8 | __attribute__((destructor)) void goodbye() { 9 | printf("Goodbye, Cruel OS World!\n"); 10 | } 11 | 12 | int main() { 13 | } 14 | -------------------------------------------------------------------------------- /lesson_1/logisim.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // 定义宏,用于迭代所有输入寄存器 5 | #define REGS_FOREACH(_) _(X) _(Y) 6 | // 定义宏,用于迭代所有输出寄存器 7 | #define OUTS_FOREACH(_) _(A) _(B) _(C) _(D) _(E) _(F) _(G) 8 | // 更新逻辑运算结果 9 | #define RUN_LOGIC X1 = !X && Y; \ 10 | Y1 = !X && !Y; \ 11 | A = (!X && !Y) || (X && !Y); \ 12 | B = 1; \ 13 | C = (!X && !Y) || (!X && Y); \ 14 | D = (!X && !Y) || (X && !Y); \ 15 | E = (!X && !Y) || (X && !Y); \ 16 | F = (!X && !Y); \ 17 | G = (X && !Y); 18 | #define DEFINE(X) static int X, X##1; 19 | #define UPDATE(X) X = X##1; 20 | #define PRINT(X) printf(#X " = %d; ", X); 21 | 22 | int main() { 23 | REGS_FOREACH(DEFINE); 24 | OUTS_FOREACH(DEFINE); 25 | while (1) { 26 | RUN_LOGIC; 27 | OUTS_FOREACH(PRINT); 28 | REGS_FOREACH(UPDATE); 29 | putchar('\n'); 30 | // 刷新标准输出缓冲区,确保打印及时显示 31 | fflush(stdout); 32 | sleep(1); 33 | } 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /lesson_1/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "hanoi-nr.c" 5 | 6 | int main() { 7 | hanoi(3, 'A', 'B', 'C'); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /lesson_1/seven-seg.py: -------------------------------------------------------------------------------- 1 | import fileinput 2 | 3 | TEMPLATE = ''' 4 | \033[2J\033[1;1f 5 | AAAAAAAAA 6 | FF BB 7 | FF BB 8 | FF BB 9 | FF BB 10 | GGGGGGGGGG 11 | EE CC 12 | EE CC 13 | EE CC 14 | EE CC 15 | DDDDDDDDD 16 | ''' 17 | BLOCK = { 18 | 0: '\033[37m░\033[0m', # STFW: ANSI Escape Code 19 | 1: '\033[31m█\033[0m', 20 | } 21 | VARS = 'ABCDEFG' 22 | 23 | for v in VARS: 24 | globals()[v] = 0 25 | stdin = fileinput.input() 26 | 27 | while True: 28 | exec(stdin.readline()) 29 | pic = TEMPLATE 30 | for v in VARS: 31 | pic = pic.replace(v, BLOCK[globals()[v]]) # 'A' -> BLOCK[A], ... 32 | print(pic) 33 | -------------------------------------------------------------------------------- /lesson_1/simulator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define REGS_FOREACH(_) _(X) _(Y) // 其实就是调用_(X)和_(Y)函数 5 | // 更新逻辑运算结果 6 | #define RUN_LOGIC X1 = !X && Y; \ 7 | Y1 = !X && !Y; 8 | #define DEFINE(X) static int X, X##1; // 定义变量X和X1,其中##起拼接作用 9 | #define UPDATE(X) X = X##1; 10 | #define PRINT(X) printf(#X " = %d; ", X); // 打印,其中#X则是可以理解为打印变量名 11 | 12 | int main() { 13 | REGS_FOREACH(DEFINE); 14 | while(1) { 15 | RUN_LOGIC; // 运行逻辑电路,并将结果存储在临时变量中 16 | REGS_FOREACH(PRINT); // 打印当前寄存器的值 17 | REGS_FOREACH(UPDATE); // 更新寄存器的值 18 | putchar('\n'); 19 | sleep(1); // 休眠1秒,模拟数字电路时钟周期 20 | } 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /lesson_1/tryopen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void try_open(const char *fname) { 6 | int fd = open(fname, O_RDWR); 7 | printf("open(\"%s\") returns %d\n", fname, fd); 8 | if (fd < 0) { 9 | perror(" FAIL"); 10 | } else { 11 | printf(" SUCCESS!\n"); 12 | close(fd); 13 | } 14 | } 15 | 16 | int main() { 17 | try_open("/something/not/exist"); 18 | try_open("/dev"); // hard drive 19 | } 20 | -------------------------------------------------------------------------------- /lesson_2/M2-libco/libco/Makefile: -------------------------------------------------------------------------------- 1 | NAME := $(shell basename $(PWD)) 2 | export MODULE := M2 3 | all: $(NAME)-64.so $(NAME)-32.so 4 | CFLAGS += -U_FORTIFY_SOURCE 5 | 6 | include ../Makefile 7 | -------------------------------------------------------------------------------- /lesson_2/M2-libco/libco/README.md: -------------------------------------------------------------------------------- 1 | # 协程库(libco) 2 | 链接libco.so的程序被赋予使用协程库的能力,它和pthread相当类似: 3 | 1. co_start(name, func, arg)创建一个新的协程,并返回一个指向struct co的指针(类似于pthread_create) 4 | * 新创建的协程从函数func开始执行,并传入参数arg。新创建的协程不会立即执行,而是调用co_start的协程继续执行 5 | * 使用协程的应用程序不需要知道struct co的具体定义,因此请把该定义留在co.c中;框架代码中并没有限定struct co结构体的设计,所以可以自由发挥 6 | 2. co_wait(co)表示当前协程需要等待,直到co协程的执行完成后才能继续执行(类似于pthread_join) 7 | * 在被等待的协程结束后,co_wait()返回前,co_start分配的struct co需要被释放。如果你使用malloc(),需要使用free()释放即可 8 | * 因此,每个协程只能被co_wait()一次(使用协程库的程序应当保证除了初始协程外,其他协程都必须被co_wait()恰好一次,否则会造成内存泄漏) 9 | 3. 协程运行后一直在CPU上执行,直到func函数返回或调用co_yield()使当前运行的协程暂时放弃执行。调用co_yield()会切换到其他协程执行。当系统中有多个可运行的协程时(包括当前协程),你应当随机选择下一个系统中可运行的协程 10 | 4. main()函数的执行也是一个协程,因此可以在main()中调用co_yield()。main()函数返回后,无论有多少协程,进程都将直接终止。 11 | 12 | 下面是一个协程使用的例子,创建了两个(永不结束的)协程,分别打印a和b。由于co_yield()之后切换到的协程是随机的(可能切换到它自己),因此可能会看到随机的ab交替出现的序列,例如ababba… 13 | 14 | ```c 15 | #include 16 | #include "co.h" 17 | 18 | void entry(void *arg) { 19 | while (1) { 20 | printf("%s", (const char *)arg); 21 | co_yield(); 22 | } 23 | } 24 | 25 | int main() { 26 | struct co *co1 = co_start("co1", entry, "a"); 27 | struct co *co2 = co_start("co2", entry, "b"); 28 | co_wait(co1); // never returns 29 | co_wait(co2); 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /lesson_2/M2-libco/libco/co.c: -------------------------------------------------------------------------------- 1 | #include "co.h" 2 | #include 3 | #include 4 | #include 5 | 6 | enum co_status { 7 | CO_NEW = 1, // 新创建,还未执行过 8 | CO_RUNNING, // 已经执行过 9 | CO_WAITING, // 在 co_wait 上等待 10 | CO_DEAD, // 已经结束,还未释放资源 11 | }; 12 | 13 | #define K 1024 14 | #define STACK_SIZE (64 * K) // 栈大小 15 | 16 | // 协程结构体 17 | struct co{ 18 | const char* name; 19 | void (*func)(void *); // co_start 指定的入口地址和参数 20 | void* (arg); 21 | 22 | enum co_status status; // 协程的状态 23 | struct co* waiter; // 是否还有其他协程在等待当前协程 24 | jmp_buf context; // 寄存器现场(setjmp.h) 25 | unsigned char stack[STACK_SIZE]; // 协程的堆栈 26 | }; 27 | 28 | // 双向链表 29 | typedef struct CONODE { 30 | struct co* coroutine; 31 | 32 | struct CONODE* prev; // 前一个结点 33 | struct CONODE* next; // 下一个结点 34 | }CoNode; 35 | 36 | static CoNode* co_node = NULL; 37 | 38 | // 全局构造函数 39 | static __attribute__((constructor)) void co_constructor(void) { 40 | curretn = co_start("main", NULL, NULL); 41 | current->status = CO_RUNNING; 42 | } 43 | 44 | // 全局析构函数 45 | static __attribute__((destructor)) void co_destructor(void) { 46 | if (co_node == NULL) { 47 | return; 48 | } 49 | while (co_node) { 50 | current = co_node->coroutine; 51 | free(current); 52 | free(co_node_remove()); 53 | } 54 | } 55 | 56 | /* 57 | * 如果co_node == NULL,则创建一个新的双向循环链表即可,并返回 58 | * 如果co_node != NULL,则在co_node和co_node->prev之间插入,仍然返回co_node的值 59 | */ 60 | static void co_node_insert(struct co* coroutine) { 61 | CoNode* victim = (CoNode*)malloc(sizeof(CoNode)); 62 | 63 | assert(victim); 64 | 65 | victim->coroutine = coroutine; 66 | if (co_node == NULL) { 67 | victim->prev = victim->next = victim; // 循环 68 | } else { 69 | victim->next = co_node->next; 70 | victim->prev = co_node; 71 | victim->next->prev = victim; 72 | victim->prev->next = victim; 73 | } 74 | } 75 | 76 | /* 77 | * 如果当前只剩node一个,则返回这个 78 | * 否则,拉去当前co_node对应的协程,并沿着next方向移动 79 | */ 80 | static CoNode* co_node_remove() { 81 | CoNode* victim = NULL; 82 | if (co_node == NULL) { 83 | return NULL; 84 | } else if (co_node->next == co_node) { 85 | victim = co_node; 86 | co_node = NULL; 87 | } else { 88 | victim = co_node; // 拿走当前协程 89 | 90 | co_node = co_node->prev; 91 | co_node->next = victim->next; 92 | co_node->next->prev = co_node; 93 | } 94 | return victim; 95 | } 96 | 97 | struct co* co_start(const char *name, void (*func)(void *), void *arg) { 98 | struct co* new_co = (struct co*)malloc(sizeof(struct co)); // 创建新协程 99 | 100 | assert(new_co); // 判断是否创建成功 101 | 102 | // 赋值 103 | new_co->name = name; 104 | new_co->func = func; 105 | new_co->arg = arg; 106 | new_co->status = CO_NEW; 107 | new_co->waiter = NULL; 108 | 109 | co_node_insert(new_co); 110 | return new_co; 111 | } 112 | 113 | /* 114 | * 切换栈,即让选中协程的所有堆栈信息在自己的堆栈中,而非调用者的堆栈。保存调用者需要保存的寄存器,并调用指定的函数 115 | */ 116 | static inline void stack_switch_call(void *sp, void *entry, void* arg) { 117 | asm volatile ( 118 | #if __x86_64__ 119 | "movq %%rcx, 0(%0); movq %0, %%rsp; movq %2, %%rdi; call *%1" 120 | : : "b"((uintptr_t)sp - 16), "d"((uintptr_t)entry), "a"((uintptr_t)arg) 121 | #else 122 | "movl %%ecx, 4(%0); movl %0, %%esp; movl %2, 0(%0); call *%1" 123 | : : "b"((uintptr_t)sp - 8), "d"((uintptr_t)entry), "a"((uintptr_t)arg) 124 | #endif 125 | 126 | ); 127 | 128 | } 129 | 130 | /* 131 | * 从调用的指定函数返回,并恢复相关的寄存器。 132 | * 这里需要注意的是,其和上面并不是对称的 133 | * 因为调用协程给了新创建的选中协程的堆栈,则选中协程以后就在自己的堆栈上执行 134 | * 永远不会返回到调用协程的堆栈。 135 | */ 136 | static inline void restore_return() { 137 | asm volatile ( 138 | #if __x86_64__ 139 | "movq 0(%%rsp), %%rcx" : : 140 | #else 141 | "movl 4(%%esp), %%ecx" : : 142 | #endif 143 | 144 | ); 145 | 146 | } 147 | 148 | #define __LONG_JUMP_STATUS (1) 149 | 150 | void co_wait(struct co *co) { 151 | assert(coroutine); 152 | 153 | if (coroutine->status != CO_DEAD) { 154 | coroutine->waiter = current; 155 | current->status = CO_WAITING; 156 | co_yield(); 157 | } 158 | 159 | /* 160 | * 释放coroutine对应的CoNode 161 | */ 162 | while (co_node->coroutine != coroutine) { 163 | co_node = co_node->next; 164 | } 165 | 166 | assert(co_node->coroutine == coroutine); 167 | 168 | free(coroutine); 169 | free(co_node_remove()); 170 | } 171 | 172 | void co_yield() { 173 | int status = setjmp(current->context); 174 | if (!status) { 175 | // 此时开始查找待选中的进程,因为co_node应该指向的就是current对应的即诶单,因此首先向下移动一个,使当前线程优先级最低 176 | co_node = co_node->next; 177 | while(!((current = co_node->coroutine)->status == CO_NEW || current->status == CO_RUNNING)) { 178 | co_node = co_node->next; 179 | } 180 | 181 | assert(current); 182 | 183 | if (current->status == CO_RUNNING) { 184 | longjmp(current->context, __LONG_JUMP_STATUS); 185 | } else { 186 | ((struct co volatile*)current)->status = CO_RUNNING; // 这里如果直接赋值,编译器和后面的覆写进行优化 187 | 188 | // 栈由高地址向低地址生长 189 | stack_switch_call(current->stack + STACK_SIZE, current->func, current->arg); 190 | 191 | // 恢复相关寄存器 192 | restore_return(); 193 | 194 | // 此时协程已经完成执行 195 | current->status = CO_DEAD; 196 | 197 | if (current->waiter) { 198 | current->waiter->status = CO_RUNNING; 199 | } 200 | co_yield(); 201 | } 202 | } 203 | 204 | assert(status && current->status == CO_RUNNING); // 此时一定是选中的进程通过longjmp跳转到的情况执行到这里 205 | } 206 | -------------------------------------------------------------------------------- /lesson_2/M2-libco/libco/co.h: -------------------------------------------------------------------------------- 1 | struct co* co_start(const char* name, void (*func)(void *), void* args); 2 | void co_yield(); 3 | void co_wait(struct co *co); 4 | -------------------------------------------------------------------------------- /lesson_2/M2-libco/libco/tests/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test libco 2 | 3 | all: libco-test-64 libco-test-32 4 | 5 | test: libco all 6 | @echo "==== TEST 64 bit ====" 7 | @LD_LIBRARY_PATH=.. ./libco-test-64 8 | @echo "==== TEST 32 bit ====" 9 | @LD_LIBRARY_PATH=.. ./libco-test-32 10 | 11 | libco-test-64: main.c 12 | 13 | libco: 14 | @cd .. && make -s 15 | 16 | libco-test-64: main.c 17 | gcc -I.. -L.. -m64 main.c -o libco-test-64 -lco-64 18 | 19 | libco-test-32: main.c 20 | gcc -I.. -L.. -m32 main.c -o libco-test-32 -lco-32 21 | 22 | clean: 23 | rm -f libco-test-* 24 | -------------------------------------------------------------------------------- /lesson_2/M2-libco/libco/tests/co-test.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | //=============================================================== 4 | // COPY FROM: https://isis.poly.edu/kulesh/stuff/src/klist/list.h 5 | //=============================================================== 6 | 7 | /* This file is from Linux Kernel (include/linux/list.h) 8 | * and modified by simply removing hardware prefetching of list items. 9 | * Here by copyright, credits attributed to wherever they belong. 10 | * Kulesh Shanmugasundaram (kulesh [squiggly] isis.poly.edu) 11 | */ 12 | 13 | /* 14 | * Simple doubly linked list implementation. 15 | * 16 | * Some of the internal func_ts ("__xxx") are useful when 17 | * manipulating whole lists rather than single entries, as 18 | * sometimes we already know the next/prev entries and we can 19 | * generate better code by using them directly rather than 20 | * using the generic single-entry routines. 21 | */ 22 | 23 | struct list_head { 24 | struct list_head *next, *prev; 25 | }; 26 | 27 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 28 | 29 | #define LIST_HEAD(name) \ 30 | struct list_head name = LIST_HEAD_INIT(name) 31 | 32 | #define INIT_LIST_HEAD(ptr) do { \ 33 | (ptr)->next = (ptr); (ptr)->prev = (ptr); \ 34 | } while (0) 35 | 36 | /* 37 | * Insert a new entry between two known consecutive entries. 38 | * 39 | * This is only for internal list manipulation where we know 40 | * the prev/next entries already! 41 | */ 42 | static inline void __list_add(struct list_head *new, 43 | struct list_head *prev, 44 | struct list_head *next) 45 | { 46 | next->prev = new; 47 | new->next = next; 48 | new->prev = prev; 49 | prev->next = new; 50 | } 51 | 52 | /** 53 | * list_add - add a new entry 54 | * @new: new entry to be added 55 | * @head: list head to add it after 56 | * 57 | * Insert a new entry after the specified head. 58 | * This is good for implementing stacks. 59 | */ 60 | static inline void list_add(struct list_head *new, struct list_head *head) 61 | { 62 | __list_add(new, head, head->next); 63 | } 64 | 65 | /** 66 | * list_add_tail - add a new entry 67 | * @new: new entry to be added 68 | * @head: list head to add it before 69 | * 70 | * Insert a new entry before the specified head. 71 | * This is useful for implementing queues. 72 | */ 73 | static inline void list_add_tail(struct list_head *new, struct list_head *head) 74 | { 75 | __list_add(new, head->prev, head); 76 | } 77 | 78 | /* 79 | * Delete a list entry by making the prev/next entries 80 | * point to each other. 81 | * 82 | * This is only for internal list manipulation where we know 83 | * the prev/next entries already! 84 | */ 85 | static inline void __list_del(struct list_head *prev, struct list_head *next) 86 | { 87 | next->prev = prev; 88 | prev->next = next; 89 | } 90 | 91 | /** 92 | * list_del - deletes entry from list. 93 | * @entry: the element to delete from the list. 94 | * Note: list_empty on entry does not return true after this, the entry is in an undefined state. 95 | */ 96 | static inline void list_del(struct list_head *entry) 97 | { 98 | __list_del(entry->prev, entry->next); 99 | entry->next = (void *) 0; 100 | entry->prev = (void *) 0; 101 | } 102 | 103 | /** 104 | * list_del_init - deletes entry from list and reinitialize it. 105 | * @entry: the element to delete from the list. 106 | */ 107 | static inline void list_del_init(struct list_head *entry) 108 | { 109 | __list_del(entry->prev, entry->next); 110 | INIT_LIST_HEAD(entry); 111 | } 112 | 113 | /** 114 | * list_move - delete from one list and add as another's head 115 | * @list: the entry to move 116 | * @head: the head that will precede our entry 117 | */ 118 | static inline void list_move(struct list_head *list, struct list_head *head) 119 | { 120 | __list_del(list->prev, list->next); 121 | list_add(list, head); 122 | } 123 | 124 | /** 125 | * list_move_tail - delete from one list and add as another's tail 126 | * @list: the entry to move 127 | * @head: the head that will follow our entry 128 | */ 129 | static inline void list_move_tail(struct list_head *list, 130 | struct list_head *head) 131 | { 132 | __list_del(list->prev, list->next); 133 | list_add_tail(list, head); 134 | } 135 | 136 | /** 137 | * list_empty - tests whether a list is empty 138 | * @head: the list to test. 139 | */ 140 | static inline int list_empty(struct list_head *head) 141 | { 142 | return head->next == head; 143 | } 144 | 145 | static inline void __list_splice(struct list_head *list, 146 | struct list_head *head) 147 | { 148 | struct list_head *first = list->next; 149 | struct list_head *last = list->prev; 150 | struct list_head *at = head->next; 151 | 152 | first->prev = head; 153 | head->next = first; 154 | 155 | last->next = at; 156 | at->prev = last; 157 | } 158 | 159 | /** 160 | * list_splice - join two lists 161 | * @list: the new list to add. 162 | * @head: the place to add it in the first list. 163 | */ 164 | static inline void list_splice(struct list_head *list, struct list_head *head) 165 | { 166 | if (!list_empty(list)) 167 | __list_splice(list, head); 168 | } 169 | 170 | /** 171 | * list_splice_init - join two lists and reinitialise the emptied list. 172 | * @list: the new list to add. 173 | * @head: the place to add it in the first list. 174 | * 175 | * The list at @list is reinitialised 176 | */ 177 | static inline void list_splice_init(struct list_head *list, 178 | struct list_head *head) 179 | { 180 | if (!list_empty(list)) { 181 | __list_splice(list, head); 182 | INIT_LIST_HEAD(list); 183 | } 184 | } 185 | 186 | /** 187 | * list_entry - get the struct for this entry 188 | * @ptr: the &struct list_head pointer. 189 | * @type: the type of the struct this is embedded in. 190 | * @member: the name of the list_struct within the struct. 191 | */ 192 | #define list_entry(ptr, type, member) \ 193 | ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) 194 | 195 | /** 196 | * list_for_each - iterate over a list 197 | * @pos: the &struct list_head to use as a loop counter. 198 | * @head: the head for your list. 199 | */ 200 | #define list_for_each(pos, head) \ 201 | for (pos = (head)->next; pos != (head); \ 202 | pos = pos->next) 203 | /** 204 | * list_for_each_prev - iterate over a list backwards 205 | * @pos: the &struct list_head to use as a loop counter. 206 | * @head: the head for your list. 207 | */ 208 | #define list_for_each_prev(pos, head) \ 209 | for (pos = (head)->prev; pos != (head); \ 210 | pos = pos->prev) 211 | 212 | /** 213 | * list_for_each_safe - iterate over a list safe against removal of list entry 214 | * @pos: the &struct list_head to use as a loop counter. 215 | * @n: another &struct list_head to use as temporary storage 216 | * @head: the head for your list. 217 | */ 218 | #define list_for_each_safe(pos, n, head) \ 219 | for (pos = (head)->next, n = pos->next; pos != (head); \ 220 | pos = n, n = pos->next) 221 | 222 | /** 223 | * list_for_each_entry - iterate over list of given type 224 | * @pos: the type * to use as a loop counter. 225 | * @head: the head for your list. 226 | * @member: the name of the list_struct within the struct. 227 | */ 228 | #define list_for_each_entry(pos, head, member) \ 229 | for (pos = list_entry((head)->next, typeof(*pos), member); \ 230 | &pos->member != (head); \ 231 | pos = list_entry(pos->member.next, typeof(*pos), member)) 232 | 233 | /** 234 | * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry 235 | * @pos: the type * to use as a loop counter. 236 | * @n: another type * to use as temporary storage 237 | * @head: the head for your list. 238 | * @member: the name of the list_struct within the struct. 239 | */ 240 | #define list_for_each_entry_safe(pos, n, head, member) \ 241 | for (pos = list_entry((head)->next, typeof(*pos), member), \ 242 | n = list_entry(pos->member.next, typeof(*pos), member); \ 243 | &pos->member != (head); \ 244 | pos = n, n = list_entry(n->member.next, typeof(*n), member)) 245 | 246 | 247 | typedef struct Queue_t { 248 | struct list_head list; 249 | int sz; 250 | int cap; 251 | } Queue; 252 | 253 | typedef struct Item_t { 254 | void *data; 255 | struct list_head link; 256 | } Item; 257 | 258 | static inline Queue* q_new() { 259 | Queue* queue = (Queue*)malloc(sizeof(Queue)); 260 | if (!queue) { 261 | fprintf(stderr, "New queue failure\n"); 262 | exit(1); 263 | } 264 | queue->cap = 100; 265 | queue->sz = 0; 266 | INIT_LIST_HEAD(&queue->list); 267 | return queue; 268 | } 269 | 270 | static inline void q_free(Queue *queue) { 271 | Item *pos, *next; 272 | list_for_each_entry_safe(pos, next, &queue->list, link) { 273 | list_del(&pos->link); 274 | free(pos); 275 | } 276 | free(queue); 277 | } 278 | 279 | static inline int q_is_full(Queue *queue) { 280 | return queue->sz == queue->cap; 281 | } 282 | 283 | static inline int q_is_empty(Queue *queue) { 284 | return list_empty(&queue->list); 285 | } 286 | 287 | static inline void q_push(Queue *queue, Item *item) { 288 | if (q_is_full(queue)) { 289 | fprintf(stderr, "Push queue failure\n"); 290 | return; 291 | } 292 | list_add_tail(&item->link, &queue->list); 293 | queue->sz += 1; 294 | } 295 | 296 | static inline Item* q_pop(Queue *queue) { 297 | if (q_is_empty(queue)) { 298 | return NULL; 299 | } 300 | 301 | Item *item = list_entry(queue->list.next, Item, link); 302 | list_del(&item->link); 303 | 304 | queue->sz -= 1; 305 | return item; 306 | } 307 | -------------------------------------------------------------------------------- /lesson_2/M2-libco/libco/tests/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "co-test.h" 6 | 7 | int g_count = 0; 8 | 9 | static void add_count() { 10 | g_count++; 11 | } 12 | 13 | static int get_count() { 14 | return g_count; 15 | } 16 | 17 | static void work_loop(void *arg) { 18 | const char *s = (const char*)arg; 19 | for (int i = 0; i < 100; ++i) { 20 | printf("%s%d ", s, get_count()); 21 | add_count(); 22 | co_yield(); 23 | } 24 | } 25 | 26 | static void work(void *arg) { 27 | work_loop(arg); 28 | } 29 | 30 | static void test_1() { 31 | 32 | struct co *thd1 = co_start("thread-1", work, "X"); 33 | struct co *thd2 = co_start("thread-2", work, "Y"); 34 | 35 | co_wait(thd1); 36 | co_wait(thd2); 37 | 38 | // printf("\n"); 39 | } 40 | 41 | // ----------------------------------------------- 42 | 43 | static int g_running = 1; 44 | 45 | static void do_produce(Queue *queue) { 46 | assert(!q_is_full(queue)); 47 | Item *item = (Item*)malloc(sizeof(Item)); 48 | if (!item) { 49 | fprintf(stderr, "New item failure\n"); 50 | return; 51 | } 52 | item->data = (char*)malloc(10); 53 | if (!item->data) { 54 | fprintf(stderr, "New data failure\n"); 55 | free(item); 56 | return; 57 | } 58 | memset(item->data, 0, 10); 59 | sprintf(item->data, "libco-%d", g_count++); 60 | q_push(queue, item); 61 | } 62 | 63 | static void producer(void *arg) { 64 | Queue *queue = (Queue*)arg; 65 | for (int i = 0; i < 100; ) { 66 | if (!q_is_full(queue)) { 67 | // co_yield(); 68 | do_produce(queue); 69 | i += 1; 70 | } 71 | co_yield(); 72 | } 73 | } 74 | 75 | static void do_consume(Queue *queue) { 76 | assert(!q_is_empty(queue)); 77 | 78 | Item *item = q_pop(queue); 79 | if (item) { 80 | printf("%s ", (char *)item->data); 81 | free(item->data); 82 | free(item); 83 | } 84 | } 85 | 86 | static void consumer(void *arg) { 87 | Queue *queue = (Queue*)arg; 88 | while (g_running) { 89 | if (!q_is_empty(queue)) { 90 | do_consume(queue); 91 | } 92 | co_yield(); 93 | } 94 | } 95 | 96 | static void test_2() { 97 | 98 | Queue *queue = q_new(); 99 | 100 | struct co *thd1 = co_start("producer-1", producer, queue); 101 | struct co *thd2 = co_start("producer-2", producer, queue); 102 | struct co *thd3 = co_start("consumer-1", consumer, queue); 103 | struct co *thd4 = co_start("consumer-2", consumer, queue); 104 | 105 | co_wait(thd1); 106 | co_wait(thd2); 107 | 108 | g_running = 0; 109 | 110 | co_wait(thd3); 111 | co_wait(thd4); 112 | 113 | while (!q_is_empty(queue)) { 114 | do_consume(queue); 115 | } 116 | 117 | q_free(queue); 118 | } 119 | 120 | int main() { 121 | setbuf(stdout, NULL); 122 | 123 | printf("Test #1. Expect: (X|Y){0, 1, 2, ..., 199}\n"); 124 | test_1(); 125 | 126 | printf("\n\nTest #2. Expect: (libco-){200, 201, 202, ..., 399}\n"); 127 | test_2(); 128 | 129 | printf("\n\n"); 130 | 131 | return 0; 132 | } 133 | -------------------------------------------------------------------------------- /lesson_2/README.md: -------------------------------------------------------------------------------- 1 | ## 多处理器编程 2 | 3 | 4 | ## 理解并发程序执行 5 | 6 | * hello.c,体验多线程编程 7 | * shm-test.c,证明线程确实共享内存 8 | * stack-probe.c,证明线程有独立的堆栈空间 9 | * peterson-barrierc 其中条件判断语句中的barrier是多余的,这个条件语句的结果不依赖于之前的写操作 10 | -------------------------------------------------------------------------------- /lesson_2/alipay.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | unsigned long balance = 100; 4 | 5 | void Alipay_withdraw(int amt) { 6 | if (balance >= amt) { 7 | usleep(1); // unexpected delays 8 | balance -= amt; 9 | } 10 | } 11 | 12 | void Talipay(int id) { 13 | Alipay_withdraw(100); 14 | } 15 | 16 | int main() { 17 | create(Talipay); 18 | create(Talipay); 19 | join(); 20 | printf("balance = %lu\n", balance); 21 | } 22 | -------------------------------------------------------------------------------- /lesson_2/dekker.py: -------------------------------------------------------------------------------- 1 | class Dekker: 2 | flag = [False, False] 3 | turn = 0 4 | 5 | @thread 6 | def t1(self): 7 | this, another = 0, 1 8 | while True: 9 | self.flag[this] = True 10 | while self.flag[another]: 11 | if self.turn == another: 12 | self.flag[this] = False 13 | while self.turn == another: 14 | pass 15 | self.flag[this] = True 16 | cs = True 17 | del cs 18 | self.turn = another 19 | self.flag[this] = False 20 | 21 | 22 | @thread 23 | def t2(self): 24 | this, another = 1, 0 25 | while True: 26 | self.flag[this] = True 27 | while self.flag[another]: 28 | if self.turn == another: 29 | self.flag[this] = False 30 | while self.turn == another: 31 | pass 32 | self.flag[this] = True 33 | cs = True 34 | del cs 35 | self.turn = another 36 | self.flag[this] = False 37 | 38 | @marker 39 | def mark_t1(self, state): 40 | if localvar(state, 't1', 'cs'): return 'blue' 41 | 42 | @marker 43 | def mark_t2(self, state): 44 | if localvar(state, 't2', 'cs'): return 'green' 45 | 46 | @marker 47 | def mark_both(self, state): 48 | if localvar(state, 't1', 'cs') and localvar(state, 't2', 'cs'): 49 | return 'red' 50 | -------------------------------------------------------------------------------- /lesson_2/hello.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | void Ta() { 4 | while (1) { 5 | printf("a "); 6 | } 7 | } 8 | void Tb() { 9 | while (1) { 10 | printf("b "); 11 | } 12 | } 13 | 14 | int main() { 15 | create(Ta); 16 | create(Tb); 17 | } 18 | -------------------------------------------------------------------------------- /lesson_2/mem-ordering.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | int x = 0, y = 0; 4 | 5 | atomic_int flag; 6 | #define FLAG atomic_load(&flag) 7 | #define FLAG_XOR(val) atomic_fetch_xor(&flag, val) 8 | #define WAIT_FOR(cond) while (!(cond)) ; 9 | 10 | __attribute__((noinline)) 11 | void write_x_read_y() { 12 | int y_val; 13 | asm volatile( 14 | "movl $1, %0;" // x = 1 15 | "movl %2, %1;" // y_val = y 16 | : "=m"(x), "=r"(y_val) : "m"(y) 17 | ); 18 | printf("%d ", y_val); 19 | } 20 | 21 | __attribute__((noinline)) 22 | void write_y_read_x() { 23 | int x_val; 24 | asm volatile( 25 | "movl $1, %0;" // y = 1 26 | "movl %2, %1;" // x_val = x 27 | : "=m"(y), "=r"(x_val) : "m"(x) 28 | ); 29 | printf("%d ", x_val); 30 | } 31 | 32 | void T1(int id) { 33 | while (1) { 34 | WAIT_FOR((FLAG & 1)); 35 | write_x_read_y(); 36 | FLAG_XOR(1); 37 | } 38 | } 39 | 40 | void T2() { 41 | while (1) { 42 | WAIT_FOR((FLAG & 2)); 43 | write_y_read_x(); 44 | FLAG_XOR(2); 45 | } 46 | } 47 | 48 | void Tsync() { 49 | while (1) { 50 | x = y = 0; 51 | __sync_synchronize(); // full barrier 52 | // usleep(1); // + delay 53 | assert(FLAG == 0); 54 | FLAG_XOR(3); 55 | // T1 and T2 clear 0/1-bit, respectively 56 | WAIT_FOR(FLAG == 0); 57 | printf("\n"); fflush(stdout); 58 | } 59 | } 60 | 61 | int main() { 62 | create(T1); 63 | create(T2); 64 | create(Tsync); 65 | } 66 | -------------------------------------------------------------------------------- /lesson_2/model-checker.py: -------------------------------------------------------------------------------- 1 | import inspect, ast, astor, copy, sys 2 | from pathlib import Path 3 | 4 | threads, marker_fn = [], [] 5 | 6 | def thread(fn): 7 | '''Decorate a member function as a thread''' 8 | global threads 9 | threads.append(fn.__name__) 10 | return fn 11 | 12 | def marker(fn): 13 | '''Decorate a member function as a state marker''' 14 | global marker_fn 15 | marker_fn.append(fn) 16 | 17 | def localvar(s, t, varname): 18 | '''Return local variable value of thread t in state s''' 19 | return s.get(t, (0, {}))[1].get(varname, None) 20 | 21 | def checkpoint(): 22 | '''Instrumented `yield checkpoint()` goes here''' 23 | f = inspect.stack()[1].frame # stack[1] is the caller of checkpoint() 24 | return (f.f_lineno, { k: v for k, v in f.f_locals.items() if k != 'self' }) 25 | 26 | def hack(Class): 27 | '''Hack Class to instrument @mc.thread functions''' 28 | class Instrument(ast.NodeTransformer): 29 | def generic_visit(self, node, in_fn=False): 30 | if isinstance(node, ast.FunctionDef): 31 | if node.name in threads: 32 | # a @mc.thread function -> instrument it 33 | in_fn, node.decorator_list = True, [] 34 | elif node.decorator_list: 35 | # a decorated function like @mc.mark -> remove it 36 | return None 37 | 38 | body = [] 39 | for line in getattr(node, 'body', []): 40 | # prepend each line with `yield checkpoint()` 41 | if in_fn: body.append( 42 | ast.Expr(ast.Yield( 43 | ast.Call(func=ast.Name(checkpoint.__name__, ctx=ast.Load()), 44 | args=[], keywords=[]))) ) 45 | body.append(self.generic_visit(line, in_fn)) 46 | node.body = body 47 | return node 48 | 49 | if not hasattr(Class, 'hacked'): 50 | hacked_ast = Instrument().visit(ast.parse(Class.source)) 51 | hacked_src, vars = astor.to_source(hacked_ast), {} 52 | # set a breakpoint() here to see **magic happens**! 53 | exec(hacked_src, globals(), vars) 54 | Class.hacked, Class.hacked_src = vars[Class.__name__], hacked_src 55 | return Class 56 | 57 | def execute(Class, trace): 58 | '''Execute trace (like [0,0,0,2,2,1,1,1]) on Class''' 59 | def attrs(obj): 60 | for attr in dir(obj): 61 | val = getattr(obj, attr) 62 | if not attr.startswith('__') and type(val) in [bool, int, str, list, tuple, dict]: 63 | yield attr, val 64 | 65 | obj = hack(Class).hacked() 66 | for attr, val in attrs(obj): 67 | setattr(obj, attr, copy.deepcopy(val)) 68 | 69 | T = [] 70 | for t in threads: 71 | fn = getattr(obj, t) 72 | T.append(fn()) # a generator for a thread 73 | S = { t: T[i].__next__() for i, t in enumerate(threads) } 74 | 75 | while trace: 76 | chosen, tname, trace = trace[0], threads[trace[0]], trace[1:] 77 | try: 78 | if T[chosen]: 79 | S[tname] = T[chosen].__next__() 80 | except StopIteration: 81 | S.pop(tname) 82 | T[chosen] = None 83 | 84 | for attr, val in attrs(obj): 85 | S[attr] = val 86 | return obj, S 87 | 88 | class State: 89 | def __init__(self, Class, trace): 90 | self.trace = trace 91 | self.obj, self.state = execute(Class, trace) 92 | self.name = f's{abs(State.freeze(self.state).__hash__())}' 93 | 94 | @staticmethod 95 | def freeze(obj): 96 | '''Create an object's hashable frozen (immutable) counterpart''' 97 | if obj is None or type(obj) in [str, int, bool]: 98 | return obj 99 | elif type(obj) in [list, tuple]: 100 | return tuple(State.freeze(x) for x in obj) 101 | elif type(obj) in [dict]: 102 | return tuple(sorted( 103 | zip(obj.keys(), (State.freeze(v) for v in obj.values())) 104 | )) 105 | raise ValueError('Cannot freeze') 106 | 107 | def serialize(Class, s0, vertices, edges): 108 | '''Serialize all model checking results''' 109 | print(f'CLASS({repr(Class.hacked_src)})') 110 | 111 | sid = { s0.name: 0 } 112 | def name(s): 113 | if s.name not in sid: 114 | sid[s.name] = len(sid) 115 | return repr(f's{sid[s.name]}') 116 | 117 | for u in vertices.values(): 118 | mk = [f(u.obj, u.state) for f in marker_fn if f(u.obj, u.state)] 119 | print(f'STATE({name(u)}, {repr(u.state)}, {repr(mk)})') 120 | 121 | for u, v, chosen in edges: 122 | print(f'TRANS({name(u)}, {name(v)}, {repr(threads[chosen])})') 123 | 124 | def check_bfs(Class): 125 | '''Enumerate all possible thread interleavings of @mc.thread functions''' 126 | s0 = State(Class, trace=[]) 127 | 128 | # breadth-first search to find all possible thread interleavings 129 | queue, vertices, edges = [s0], {s0.name: s0}, [] 130 | while queue: 131 | u, queue = queue[0], queue[1:] 132 | for chosen, _ in enumerate(threads): 133 | v = State(Class, u.trace + [chosen]) 134 | if v.name not in vertices: 135 | queue.append(v) 136 | vertices[v.name] = v 137 | edges.append((u, v, chosen)) 138 | 139 | serialize(Class, s0, vertices, edges) 140 | 141 | src, vars = Path(sys.argv[1]).read_text(), {} 142 | exec(src, globals(), vars) 143 | Class = [C for C in vars.values() if type(C) == type].pop() 144 | setattr(Class, 'source', src) 145 | check_bfs(Class) 146 | -------------------------------------------------------------------------------- /lesson_2/mutex-bad.py: -------------------------------------------------------------------------------- 1 | class Mutex: 2 | locked = '' 3 | 4 | @thread 5 | def t1(self): 6 | while True: 7 | while self.locked == '🔒': 8 | pass 9 | self.locked = '🔒' 10 | cs = True 11 | del cs 12 | self.locked = '' 13 | 14 | @thread 15 | def t2(self): 16 | while True: 17 | while self.locked == '🔒': 18 | pass 19 | self.locked = '🔒' 20 | cs = True 21 | del cs 22 | self.locked = '' 23 | 24 | @marker 25 | def mark_t1(self, state): 26 | if localvar(state, 't1', 'cs'): return 'blue' 27 | 28 | @marker 29 | def mark_t2(self, state): 30 | if localvar(state, 't2', 'cs'): return 'green' 31 | 32 | @marker 33 | def mark_both(self, state): 34 | if localvar(state, 't1', 'cs') and localvar(state, 't2', 'cs'): 35 | return 'red' 36 | -------------------------------------------------------------------------------- /lesson_2/note.md: -------------------------------------------------------------------------------- 1 | ## 1 并发 2 | * 操作系统是最早的并发程序之一 3 | * 今天遍地为什么都是多处理系统 4 | 5 | 并发的基本单位:线程。 6 | 共享内存的多个执行流: 7 | 1. 执行流拥有独立的堆栈/寄存器 8 | 2. 共享全部的内存(指针可以互相引用) 9 | -------------------------------------------------------------------------------- /lesson_2/peterson-barrier.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | #define A 1 4 | #define B 2 5 | 6 | #define BARRIER __sync_synchronize() // 用于确保指令重排序时不会破坏程序的预期行为 7 | 8 | atomic_int nested; // 验证同时只有一个进程进入过临界区 9 | atomic_long count; // 统计有多少个进程进入过临界区 10 | 11 | void critical_section() { 12 | long cnt = atomic_fetch_add(&count, 1); // 原子操作返回的是执行假发操作前的值 13 | int i = atomic_fetch_add(&nested, 1) + 1; 14 | if (i != 1) { 15 | printf("%d threads in the critical section @ count=%ld\n", i, cnt); 16 | assert(0); 17 | } 18 | atomic_fetch_add(&nested, -1); 19 | } 20 | 21 | int volatile x = 0, y = 0, turn; 22 | 23 | void TA() { 24 | while (1) { 25 | x = 1; BARRIER; 26 | turn = B; BARRIER; // <- this is critcal for x86 27 | while (1) { 28 | if (!y) break; BARRIER; 29 | if (turn != B) break; BARRIER; 30 | } 31 | critical_section(); 32 | x = 0; BARRIER; 33 | } 34 | } 35 | 36 | void TB() { 37 | while (1) { 38 | y = 1; BARRIER; 39 | turn = A; BARRIER; 40 | while (1) { 41 | if (!x) break; BARRIER; 42 | if (turn != A) break; BARRIER; 43 | } 44 | critical_section(); 45 | y = 0; BARRIER; 46 | } 47 | } 48 | 49 | int main() { 50 | create(TA); 51 | create(TB); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /lesson_2/peterson-flag.py: -------------------------------------------------------------------------------- 1 | class Peterson: 2 | flag = ' ' 3 | turn = ' ' 4 | 5 | @thread 6 | def t1(self): 7 | while True: 8 | self.flag = '🏴' + self.flag[1] 9 | self.turn = '🏳' 10 | while self.flag[1] != ' ' and self.turn == '🏳': 11 | pass 12 | cs = True 13 | del cs 14 | self.flag = ' ' + self.flag[1] 15 | 16 | @thread 17 | def t2(self): 18 | while True: 19 | self.flag = self.flag[0] + '🏳' 20 | self.turn = '🏴' 21 | while self.flag[0] != ' ' and self.turn == '🏴': 22 | pass 23 | cs = True 24 | del cs 25 | self.flag = self.flag[0] + ' ' 26 | 27 | @marker 28 | def mark_t1(self, state): 29 | if localvar(state, 't1', 'cs'): return 'blue' 30 | 31 | @marker 32 | def mark_t2(self, state): 33 | if localvar(state, 't2', 'cs'): return 'green' 34 | 35 | @marker 36 | def mark_both(self, state): 37 | if localvar(state, 't1', 'cs') and localvar(state, 't2', 'cs'): 38 | return 'red' 39 | -------------------------------------------------------------------------------- /lesson_2/shm-test.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | int x = 0; 4 | 5 | void Thello(int id) { 6 | usleep(id * 100000); 7 | printf("Hello from thread #%c\n", "123456789ABCDEF"[x++]); 8 | } 9 | 10 | int main() { 11 | for (int i = 0; i < 10; i++) { 12 | create(Thello); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lesson_2/stack-probe.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | __thread char *base, *cur; // thread-local variables 4 | __thread int id; 5 | 6 | // objdump to see how thread-local variables are implemented 7 | __attribute__((noinline)) void set_cur(void *ptr) { cur = ptr; } 8 | __attribute__((noinline)) char *get_cur() { return cur; } 9 | 10 | void stackoverflow(int n) { 11 | set_cur(&n); 12 | if (n % 1024 == 0) { 13 | int sz = base - get_cur(); 14 | printf("Stack size of T%d >= %d KB\n", id, sz / 1024); 15 | } 16 | stackoverflow(n + 1); 17 | } 18 | 19 | void Tprobe(int tid) { 20 | id = tid; 21 | base = (void *)&tid; 22 | stackoverflow(0); 23 | } 24 | 25 | int main() { 26 | setbuf(stdout, NULL); 27 | for (int i = 0; i < 4; i++) { 28 | create(Tprobe); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lesson_2/sum.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | #define N 100000000 4 | 5 | long sum = 0; 6 | 7 | void Tsum() { 8 | for (int i = 0; i < N; i++) { 9 | // sum++; 10 | asm volatile("lock add $1, %0": "+m"(sum)); 11 | } 12 | } 13 | 14 | int main() { 15 | create(Tsum); 16 | create(Tsum); 17 | join(); 18 | printf("sum = %ld\n", sum); 19 | } 20 | -------------------------------------------------------------------------------- /lesson_2/thread.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define NTHREAD 64 10 | 11 | // 线程状态 12 | enum { T_FREE = 0, T_LIVE, T_DEAD, }; 13 | 14 | // 表示线程的结构体 15 | struct thread { 16 | int id, status; // 线程表示服和线程状态 17 | pthread_t thread; // 表示线程 18 | void (*entry)(int); // 线程入口点的函数指针 19 | }; 20 | 21 | // 线程池和指向线程池当前位置的指针 22 | struct thread tpool[NTHREAD], *tptr = tpool; 23 | 24 | // 包装实际线程入口函数的函数 25 | void *wrapper(void *arg) { 26 | struct thread *thread = (struct thread *)arg; 27 | thread->entry(thread->id); 28 | return NULL; 29 | } 30 | 31 | // 创建新线程的函数 32 | void create(void *fn) { 33 | assert(tptr - tpool < NTHREAD); // 确保线程池没有满 34 | *tptr = (struct thread) { 35 | .id = tptr - tpool + 1, // 为线程分配唯一标识符 36 | .status = T_LIVE, // 设置线程状态为T_LIVE 37 | .entry = fn, // 设置线程入口函数 38 | }; 39 | pthread_create(&(tptr->thread), NULL, wrapper, tptr); // 创建新线程 40 | ++tptr; // 将指针移动到线程池中的下一个位置 41 | } 42 | 43 | // 等待所有活动线程的函数 44 | void join() { 45 | for (int i = 0; i < NTHREAD; i++) { 46 | struct thread *t = &tpool[i]; 47 | if (t->status == T_LIVE) { 48 | pthread_join(t->thread, NULL); // 等待线程结束 49 | t->status = T_DEAD; // 将线程状态更新为T_DEAD 50 | } 51 | } 52 | } 53 | 54 | // 析构函数,在程序退出时清理资源 55 | __attribute__((destructor)) void cleanup() { 56 | join(); // 在程序退出时等待所有活动线程结束 57 | } 58 | -------------------------------------------------------------------------------- /lesson_3/README.md: -------------------------------------------------------------------------------- 1 | # 并发控制:互斥和同步 2 | 3 | Scalability:性能的新维度 4 | 5 | 同一份计算任务,时间和空间会随处理器数量的增长而变化 6 | 在sum-scalability.c中很好的说明了这一点 7 | 8 | pc.c 用互斥锁实现了同步 9 | pc-check.py用来压力测试 10 | `gcc pc.c -lpthread` 11 | `./a.out 5 | python3 pc-check.py 5` 12 | -------------------------------------------------------------------------------- /lesson_3/fish.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | #define LENGTH(arr) (sizeof(arr) / sizeof(arr[0])) 4 | 5 | enum { A = 1, B, C, D, E, F, }; 6 | 7 | struct rule { 8 | int from, ch, to; 9 | }; 10 | 11 | struct rule rules[] = { 12 | { A, '<', B }, 13 | { B, '>', C }, 14 | { C, '<', D }, 15 | { A, '>', E }, 16 | { E, '<', F }, 17 | { F, '>', D }, 18 | { D, '_', A }, 19 | }; 20 | int current = A, quota = 1; 21 | 22 | pthread_mutex_t lk = PTHREAD_MUTEX_INITIALIZER; 23 | pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 24 | 25 | int next(char ch) { 26 | for (int i = 0; i < LENGTH(rules); i++) { 27 | struct rule *rule = &rules[i]; 28 | if (rule->from == current && rule->ch == ch) { 29 | return rule->to; 30 | } 31 | } 32 | return 0; 33 | } 34 | 35 | void fish_before(char ch) { 36 | pthread_mutex_lock(&lk); 37 | while (!(next(ch) && quota)) { 38 | // can proceed only if (next(ch) && quota) 39 | pthread_cond_wait(&cond, &lk); 40 | } 41 | quota--; 42 | pthread_mutex_unlock(&lk); 43 | } 44 | 45 | void fish_after(char ch) { 46 | pthread_mutex_lock(&lk); 47 | quota++; 48 | current = next(ch); 49 | assert(current); 50 | pthread_cond_broadcast(&cond); 51 | pthread_mutex_unlock(&lk); 52 | } 53 | 54 | const char roles[] = ".<<<<<>>>>___"; 55 | 56 | void fish_thread(int id) { 57 | char role = roles[id]; 58 | while (1) { 59 | fish_before(role); 60 | putchar(role); // can be long; no lock protection 61 | fish_after(role); 62 | } 63 | } 64 | 65 | int main() { 66 | setbuf(stdout, NULL); 67 | for (int i = 0; i < strlen(roles); i++) 68 | create(fish_thread); 69 | } 70 | -------------------------------------------------------------------------------- /lesson_3/fix-pc-cv.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | #include "thread-sync.h" 3 | 4 | int n, count = 0; 5 | mutex_t lk = MUTEX_INIT(); 6 | cond_t cv = COND_INIT(); 7 | 8 | void Tproduce() { 9 | while (1) { 10 | mutex_lock(&lk); 11 | while (count == n) { 12 | cond_wait(&cv, &lk); 13 | } 14 | assert(count != n); 15 | printf("("); count++; 16 | cond_broadcast(&cv); 17 | mutex_unlock(&lk); 18 | } 19 | } 20 | 21 | void Tconsume() { 22 | while (1) { 23 | mutex_lock(&lk); 24 | while (count == 0) { 25 | pthread_cond_wait(&cv, &lk); 26 | } 27 | assert(count != 0); 28 | printf(")"); count--; 29 | cond_broadcast(&cv); 30 | mutex_unlock(&lk); 31 | } 32 | } 33 | 34 | int main(int argc, char *argv[]) { 35 | assert(argc == 2); 36 | n = atoi(argv[1]); 37 | setbuf(stdout, NULL); 38 | for (int i = 0; i < n; i++) { 39 | create(Tproduce); 40 | create(Tconsume); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lesson_3/model-checker.py: -------------------------------------------------------------------------------- 1 | import inspect, ast, astor, copy, sys 2 | from pathlib import Path 3 | 4 | threads, marker_fn = [], [] 5 | 6 | def thread(fn): 7 | '''Decorate a member function as a thread''' 8 | global threads 9 | threads.append(fn.__name__) 10 | return fn 11 | 12 | def marker(fn): 13 | '''Decorate a member function as a state marker''' 14 | global marker_fn 15 | marker_fn.append(fn) 16 | 17 | def localvar(s, t, varname): 18 | '''Return local variable value of thread t in state s''' 19 | return s.get(t, (0, {}))[1].get(varname, None) 20 | 21 | def checkpoint(): 22 | '''Instrumented `yield checkpoint()` goes here''' 23 | f = inspect.stack()[1].frame # stack[1] is the caller of checkpoint() 24 | return (f.f_lineno, { k: v for k, v in f.f_locals.items() if k != 'self' }) 25 | 26 | def hack(Class): 27 | '''Hack Class to instrument @mc.thread functions''' 28 | class Instrument(ast.NodeTransformer): 29 | def generic_visit(self, node, in_fn=False): 30 | if isinstance(node, ast.FunctionDef): 31 | if node.name in threads: 32 | # a @mc.thread function -> instrument it 33 | in_fn, node.decorator_list = True, [] 34 | elif node.decorator_list: 35 | # a decorated function like @mc.mark -> remove it 36 | return None 37 | 38 | body = [] 39 | for line in getattr(node, 'body', []): 40 | # prepend each line with `yield checkpoint()` 41 | if in_fn: body.append( 42 | ast.Expr(ast.Yield( 43 | ast.Call(func=ast.Name(checkpoint.__name__, ctx=ast.Load()), 44 | args=[], keywords=[]))) ) 45 | body.append(self.generic_visit(line, in_fn)) 46 | node.body = body 47 | return node 48 | 49 | if not hasattr(Class, 'hacked'): 50 | hacked_ast = Instrument().visit(ast.parse(Class.source)) 51 | hacked_src, vars = astor.to_source(hacked_ast), {} 52 | # set a breakpoint() here to see **magic happens**! 53 | exec(hacked_src, globals(), vars) 54 | Class.hacked, Class.hacked_src = vars[Class.__name__], hacked_src 55 | return Class 56 | 57 | def execute(Class, trace): 58 | '''Execute trace (like [0,0,0,2,2,1,1,1]) on Class''' 59 | def attrs(obj): 60 | for attr in dir(obj): 61 | val = getattr(obj, attr) 62 | if not attr.startswith('__') and type(val) in [bool, int, str, list, tuple, dict]: 63 | yield attr, val 64 | 65 | obj = hack(Class).hacked() 66 | for attr, val in attrs(obj): 67 | setattr(obj, attr, copy.deepcopy(val)) 68 | 69 | T = [] 70 | for t in threads: 71 | fn = getattr(obj, t) 72 | T.append(fn()) # a generator for a thread 73 | S = { t: T[i].__next__() for i, t in enumerate(threads) } 74 | 75 | while trace: 76 | chosen, tname, trace = trace[0], threads[trace[0]], trace[1:] 77 | try: 78 | if T[chosen]: 79 | S[tname] = T[chosen].__next__() 80 | except StopIteration: 81 | S.pop(tname) 82 | T[chosen] = None 83 | 84 | for attr, val in attrs(obj): 85 | S[attr] = val 86 | return obj, S 87 | 88 | class State: 89 | def __init__(self, Class, trace): 90 | self.trace = trace 91 | self.obj, self.state = execute(Class, trace) 92 | self.name = f's{abs(State.freeze(self.state).__hash__())}' 93 | 94 | @staticmethod 95 | def freeze(obj): 96 | '''Create an object's hashable frozen (immutable) counterpart''' 97 | if obj is None or type(obj) in [str, int, bool]: 98 | return obj 99 | elif type(obj) in [list, tuple]: 100 | return tuple(State.freeze(x) for x in obj) 101 | elif type(obj) in [dict]: 102 | return tuple(sorted( 103 | zip(obj.keys(), (State.freeze(v) for v in obj.values())) 104 | )) 105 | raise ValueError('Cannot freeze') 106 | 107 | def serialize(Class, s0, vertices, edges): 108 | '''Serialize all model checking results''' 109 | print(f'CLASS({repr(Class.hacked_src)})') 110 | 111 | sid = { s0.name: 0 } 112 | def name(s): 113 | if s.name not in sid: 114 | sid[s.name] = len(sid) 115 | return repr(f's{sid[s.name]}') 116 | 117 | for u in vertices.values(): 118 | mk = [f(u.obj, u.state) for f in marker_fn if f(u.obj, u.state)] 119 | print(f'STATE({name(u)}, {repr(u.state)}, {repr(mk)})') 120 | 121 | for u, v, chosen in edges: 122 | print(f'TRANS({name(u)}, {name(v)}, {repr(threads[chosen])})') 123 | 124 | def check_bfs(Class): 125 | '''Enumerate all possible thread interleavings of @mc.thread functions''' 126 | s0 = State(Class, trace=[]) 127 | 128 | # breadth-first search to find all possible thread interleavings 129 | queue, vertices, edges = [s0], {s0.name: s0}, [] 130 | while queue: 131 | u, queue = queue[0], queue[1:] 132 | for chosen, _ in enumerate(threads): 133 | v = State(Class, u.trace + [chosen]) 134 | if v.name not in vertices: 135 | queue.append(v) 136 | vertices[v.name] = v 137 | edges.append((u, v, chosen)) 138 | 139 | serialize(Class, s0, vertices, edges) 140 | 141 | src, vars = Path(sys.argv[1]).read_text(), {} 142 | exec(src, globals(), vars) 143 | Class = [C for C in vars.values() if type(C) == type].pop() 144 | setattr(Class, 'source', src) 145 | check_bfs(Class) 146 | -------------------------------------------------------------------------------- /lesson_3/pc-check.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | limit = int(sys.argv[1]) 4 | count, n = 0, 100000 5 | while True: 6 | for ch in sys.stdin.read(n): 7 | if ch == '(': count += 1 8 | if ch == ')': count -= 1 9 | assert 0 <= count <= limit 10 | print(f'{n} Ok.') 11 | -------------------------------------------------------------------------------- /lesson_3/pc-cv.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | #include "thread-sync.h" 3 | 4 | int n, count = 0; 5 | mutex_t lk = MUTEX_INIT(); 6 | cond_t cv = COND_INIT(); 7 | 8 | void Tproduce() { 9 | while (1) { 10 | mutex_lock(&lk); 11 | if (count == n) { 12 | cond_wait(&cv, &lk); 13 | } 14 | printf("("); count++; 15 | cond_signal(&cv); 16 | mutex_unlock(&lk); 17 | } 18 | } 19 | 20 | void Tconsume() { 21 | while (1) { 22 | mutex_lock(&lk); 23 | if (count == 0) { 24 | pthread_cond_wait(&cv, &lk); 25 | } 26 | printf(")"); count--; 27 | cond_signal(&cv); 28 | mutex_unlock(&lk); 29 | } 30 | } 31 | 32 | int main(int argc, char *argv[]) { 33 | assert(argc == 2); 34 | n = atoi(argv[1]); 35 | setbuf(stdout, NULL); 36 | for (int i = 0; i < n; i++) { 37 | create(Tproduce); 38 | create(Tconsume); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lesson_3/pc-cv.py: -------------------------------------------------------------------------------- 1 | class ProducerConsumer: 2 | locked, count, log, waits = '', 0, '', '' 3 | 4 | def tryacquire(self): 5 | self.locked, seen = '🔒', self.locked 6 | return seen == '' 7 | 8 | def release(self): 9 | self.locked = '' 10 | 11 | @thread 12 | def tp(self): 13 | for _ in range(2): 14 | while not self.tryacquire(): pass # mutex_lock() 15 | 16 | if self.count == 1: 17 | # cond_wait 18 | _, self.waits = self.release(), self.waits + '1' 19 | while '1' in self.waits: pass 20 | while not self.tryacquire(): pass 21 | 22 | self.log, self.count = self.log + '(', self.count + 1 23 | self.waits = self.waits[1:] # cond_signal 24 | self.release() # mutex_unlock() 25 | 26 | @thread 27 | def tc1(self): 28 | while not self.tryacquire(): pass 29 | 30 | if self.count == 0: 31 | _, self.waits = self.release(), self.waits + '2' 32 | while '2' in self.waits: pass 33 | while not self.tryacquire(): pass 34 | 35 | self.log, self.count = self.log + ')', self.count - 1 36 | 37 | self.waits = self.waits[1:] 38 | self.release() 39 | 40 | @thread 41 | def tc2(self): 42 | while not self.tryacquire(): pass 43 | 44 | if self.count == 0: 45 | _, self.waits = self.release(), self.waits + '3' 46 | while '3' in self.waits: pass 47 | while not self.tryacquire(): pass 48 | 49 | self.log, self.count = self.log + ')', self.count - 1 50 | 51 | self.waits = self.waits[1:] 52 | self.release() 53 | 54 | @marker 55 | def mark_negative(self, state): 56 | count = 0 57 | for ch in self.log: 58 | if ch == '(': count += 1 59 | if ch == ')': count -= 1 60 | if count < 0: return 'red' 61 | -------------------------------------------------------------------------------- /lesson_3/pc-sem.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | #include "thread-sync.h" 3 | 4 | sem_t fill, empty; 5 | 6 | void producer() { 7 | while (1) { 8 | P(&empty); 9 | printf("("); 10 | V(&fill); 11 | } 12 | } 13 | 14 | void consumer() { 15 | while (1) { 16 | P(&fill); 17 | printf(")"); 18 | V(&empty); 19 | } 20 | } 21 | 22 | int main(int argc, char *argv[]) { 23 | assert(argc == 2); 24 | SEM_INIT(&fill, 0); 25 | SEM_INIT(&empty, atoi(argv[1])); 26 | for (int i = 0; i < 8; i++) { 27 | create(producer); 28 | create(consumer); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lesson_3/pc.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | #include "thread-sync.h" 3 | 4 | int n, count = 0; 5 | mutex_t lk = MUTEX_INIT(); 6 | 7 | void Tproduce() { 8 | while (1) { 9 | retry: 10 | mutex_lock(&lk); 11 | if (count == n) { 12 | mutex_unlock(&lk); 13 | goto retry; 14 | } 15 | count++; 16 | printf("("); 17 | mutex_unlock(&lk); 18 | } 19 | } 20 | 21 | void Tconsume() { 22 | while (1) { 23 | retry: 24 | mutex_lock(&lk); 25 | if (count == 0) { 26 | mutex_unlock(&lk); 27 | goto retry; 28 | } 29 | count--; 30 | printf(")"); 31 | mutex_unlock(&lk); 32 | } 33 | } 34 | 35 | int main(int argc, char *argv[]) { 36 | assert(argc == 2); 37 | n = atoi(argv[1]); 38 | setbuf(stdout, NULL); 39 | for (int i = 0; i < 8; i++) { 40 | create(Tproduce); 41 | create(Tconsume); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lesson_3/sem.py: -------------------------------------------------------------------------------- 1 | class Semaphore: 2 | token, waits = 1, '' 3 | 4 | def P(self, tid): 5 | if self.token > 0: 6 | self.token -= 1 7 | return True 8 | else: 9 | self.waits = self.waits + tid 10 | return False 11 | 12 | def V(self): 13 | if self.waits: 14 | self.waits = self.waits[1:] 15 | else: 16 | self.token += 1 17 | 18 | @thread 19 | def t1(self): 20 | self.P('1') 21 | while '1' in self.waits: pass 22 | cs = True 23 | del cs 24 | self.V() 25 | 26 | @thread 27 | def t2(self): 28 | self.P('2') 29 | while '2' in self.waits: pass 30 | cs = True 31 | del cs 32 | self.V() 33 | 34 | @marker 35 | def mark_t1(self, state): 36 | if localvar(state, 't1', 'cs'): return 'blue' 37 | 38 | @marker 39 | def mark_t2(self, state): 40 | if localvar(state, 't2', 'cs'): return 'green' 41 | 42 | @marker 43 | def mark_both(self, state): 44 | if localvar(state, 't1', 'cs') and localvar(state, 't2', 'cs'): 45 | return 'red' 46 | -------------------------------------------------------------------------------- /lesson_3/spinlock.py: -------------------------------------------------------------------------------- 1 | class Spinlock: 2 | locked = '' 3 | 4 | @thread 5 | def t1(self): 6 | while True: 7 | while True: 8 | self.locked, seen = '🔒', self.locked 9 | if seen != '🔒': break 10 | cs = True 11 | del cs 12 | self.locked = '' 13 | 14 | @thread 15 | def t2(self): 16 | while True: 17 | while True: 18 | self.locked, seen = '🔒', self.locked 19 | if seen != '🔒': break 20 | cs = True 21 | del cs 22 | self.locked = '' 23 | 24 | @marker 25 | def mark_t1(self, state): 26 | if localvar(state, 't1', 'cs'): return 'blue' 27 | 28 | @marker 29 | def mark_t2(self, state): 30 | if localvar(state, 't2', 'cs'): return 'green' 31 | 32 | @marker 33 | def mark_both(self, state): 34 | if localvar(state, 't1', 'cs') and localvar(state, 't2', 'cs'): 35 | return 'red' 36 | -------------------------------------------------------------------------------- /lesson_3/sum-scalability.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | #include "thread-sync.h" 3 | 4 | #define N 10000000 5 | spinlock_t lock = SPIN_INIT(); 6 | 7 | long n, sum = 0; 8 | 9 | void Tsum() { 10 | for (int i = 0; i < n; i++) { 11 | spin_lock(&lock); 12 | sum++; 13 | spin_unlock(&lock); 14 | } 15 | } 16 | 17 | int main(int argc, char *argv[]) { 18 | assert(argc == 2); 19 | int nthread = atoi(argv[1]); 20 | n = N / nthread; 21 | for (int i = 0; i < nthread; i++) { 22 | create(Tsum); 23 | } 24 | join(); 25 | assert(sum == n * nthread); 26 | } 27 | -------------------------------------------------------------------------------- /lesson_3/thread-sync.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Spinlock 4 | typedef int spinlock_t; 5 | #define SPIN_INIT() 0 6 | 7 | static inline int atomic_xchg(volatile int *addr, int newval) { 8 | int result; 9 | asm volatile ("lock xchg %0, %1": 10 | "+m"(*addr), "=a"(result) : "1"(newval) : "memory"); 11 | return result; 12 | } 13 | 14 | void spin_lock(spinlock_t *lk) { 15 | while (1) { 16 | intptr_t value = atomic_xchg(lk, 1); 17 | if (value == 0) { 18 | break; 19 | } 20 | } 21 | } 22 | void spin_unlock(spinlock_t *lk) { 23 | atomic_xchg(lk, 0); 24 | } 25 | 26 | // Mutex 27 | typedef pthread_mutex_t mutex_t; 28 | #define MUTEX_INIT() PTHREAD_MUTEX_INITIALIZER 29 | void mutex_lock(mutex_t *lk) { pthread_mutex_lock(lk); } 30 | void mutex_unlock(mutex_t *lk) { pthread_mutex_unlock(lk); } 31 | 32 | // Conditional Variable 33 | typedef pthread_cond_t cond_t; 34 | #define COND_INIT() PTHREAD_COND_INITIALIZER 35 | #define cond_wait pthread_cond_wait 36 | #define cond_broadcast pthread_cond_broadcast 37 | #define cond_signal pthread_cond_signal 38 | 39 | // Semaphore 40 | #define P sem_wait 41 | #define V sem_post 42 | #define SEM_INIT(sem, val) sem_init(sem, 0, val) 43 | -------------------------------------------------------------------------------- /lesson_3/thread.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define NTHREAD 64 10 | 11 | // 线程状态 12 | enum { T_FREE = 0, T_LIVE, T_DEAD, }; 13 | 14 | // 表示线程的结构体 15 | struct thread { 16 | int id, status; // 线程表示服和线程状态 17 | pthread_t thread; // 表示线程 18 | void (*entry)(int); // 线程入口点的函数指针 19 | }; 20 | 21 | // 线程池和指向线程池当前位置的指针 22 | struct thread tpool[NTHREAD], *tptr = tpool; 23 | 24 | // 包装实际线程入口函数的函数 25 | void *wrapper(void *arg) { 26 | struct thread *thread = (struct thread *)arg; 27 | thread->entry(thread->id); 28 | return NULL; 29 | } 30 | 31 | // 创建新线程的函数 32 | void create(void *fn) { 33 | assert(tptr - tpool < NTHREAD); // 确保线程池没有满 34 | *tptr = (struct thread) { 35 | .id = tptr - tpool + 1, // 为线程分配唯一标识符 36 | .status = T_LIVE, // 设置线程状态为T_LIVE 37 | .entry = fn, // 设置线程入口函数 38 | }; 39 | pthread_create(&(tptr->thread), NULL, wrapper, tptr); // 创建新线程 40 | ++tptr; // 将指针移动到线程池中的下一个位置 41 | } 42 | 43 | // 等待所有活动线程的函数 44 | void join() { 45 | for (int i = 0; i < NTHREAD; i++) { 46 | struct thread *t = &tpool[i]; 47 | if (t->status == T_LIVE) { 48 | pthread_join(t->thread, NULL); // 等待线程结束 49 | t->status = T_DEAD; // 将线程状态更新为T_DEAD 50 | } 51 | } 52 | } 53 | 54 | // 析构函数,在程序退出时清理资源 55 | __attribute__((destructor)) void cleanup() { 56 | join(); // 在程序退出时等待所有活动线程结束 57 | } 58 | -------------------------------------------------------------------------------- /lesson_4/fib.go: -------------------------------------------------------------------------------- 1 | // Example from "The Go Programming Language" 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | go spinner(100 * time.Millisecond) 12 | const n = 45 13 | fibN := fib(n) // slow 14 | fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN) 15 | } 16 | 17 | func spinner(delay time.Duration) { 18 | for { 19 | for _, r := range `-\|/` { 20 | fmt.Printf("\r%c", r) 21 | time.Sleep(delay) 22 | } 23 | } 24 | } 25 | 26 | func fib(x int) int { 27 | if x < 2 { return x } 28 | return fib(x - 1) + fib(x - 2) 29 | } 30 | -------------------------------------------------------------------------------- /lesson_4/mandelbrot.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | #include 3 | 4 | int NT; 5 | #define W 6400 6 | #define H 6400 7 | #define IMG_FILE "mandelbrot.ppm" 8 | 9 | static inline int belongs(int x, int y, int t) { 10 | return x / (W / NT) == t; 11 | } 12 | 13 | int x[W][H]; 14 | int volatile done = 0; 15 | 16 | void display(FILE *fp, int step) { 17 | static int rnd = 1; 18 | int w = W / step, h = H / step; 19 | // STFW: Portable Pixel Map 20 | fprintf(fp, "P6\n%d %d 255\n", w, h); 21 | for (int j = 0; j < H; j += step) { 22 | for (int i = 0; i < W; i += step) { 23 | int n = x[i][j]; 24 | int r = 255 * pow((n - 80) / 800.0, 3); 25 | int g = 255 * pow((n - 80) / 800.0, 0.7); 26 | int b = 255 * pow((n - 80) / 800.0, 0.5); 27 | fputc(r, fp); fputc(g, fp); fputc(b, fp); 28 | } 29 | } 30 | } 31 | 32 | void Tworker(int tid) { 33 | for (int i = 0; i < W; i++) 34 | for (int j = 0; j < H; j++) 35 | if (belongs(i, j, tid - 1)) { 36 | double a = 0, b = 0, c, d; 37 | while ((c = a * a) + (d = b * b) < 4 && x[i][j]++ < 880) { 38 | b = 2 * a * b + j * 1024.0 / H * 8e-9 - 0.645411; 39 | a = c - d + i * 1024.0 / W * 8e-9 + 0.356888; 40 | } 41 | } 42 | done++; 43 | } 44 | 45 | void Tdisplay() { 46 | float ms = 0; 47 | while (1) { 48 | FILE *fp = popen("viu -", "w"); assert(fp); 49 | display(fp, W / 256); 50 | pclose(fp); 51 | if (done == NT) break; 52 | usleep(1000000 / 5); 53 | ms += 1000.0 / 5; 54 | } 55 | printf("Approximate render time: %.1lfs\n", ms / 1000); 56 | 57 | FILE *fp = fopen(IMG_FILE, "w"); assert(fp); 58 | display(fp, 2); 59 | fclose(fp); 60 | } 61 | 62 | int main(int argc, char *argv[]) { 63 | assert(argc == 2); 64 | NT = atoi(argv[1]); 65 | for (int i = 0; i < NT; i++) { 66 | create(Tworker); 67 | } 68 | create(Tdisplay); 69 | join(); 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /lesson_4/producer-consumer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | var stream = make(chan int, 10) 6 | const n = 4 7 | 8 | func produce() { 9 | for i := 0; ; i++ { 10 | fmt.Println("produce", i) 11 | stream <- i 12 | } 13 | } 14 | 15 | func consume() { 16 | for { 17 | x := <- stream 18 | fmt.Println("consume", x) 19 | } 20 | } 21 | 22 | func main() { 23 | for i := 0; i < n; i++ { 24 | go produce() 25 | } 26 | consume() 27 | } 28 | -------------------------------------------------------------------------------- /lesson_4/thread-sync.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Spinlock 4 | typedef int spinlock_t; 5 | #define SPIN_INIT() 0 6 | 7 | static inline int atomic_xchg(volatile int *addr, int newval) { 8 | int result; 9 | asm volatile ("lock xchg %0, %1": 10 | "+m"(*addr), "=a"(result) : "1"(newval) : "memory"); 11 | return result; 12 | } 13 | 14 | void spin_lock(spinlock_t *lk) { 15 | while (1) { 16 | intptr_t value = atomic_xchg(lk, 1); 17 | if (value == 0) { 18 | break; 19 | } 20 | } 21 | } 22 | void spin_unlock(spinlock_t *lk) { 23 | atomic_xchg(lk, 0); 24 | } 25 | 26 | // Mutex 27 | typedef pthread_mutex_t mutex_t; 28 | #define MUTEX_INIT() PTHREAD_MUTEX_INITIALIZER 29 | void mutex_lock(mutex_t *lk) { pthread_mutex_lock(lk); } 30 | void mutex_unlock(mutex_t *lk) { pthread_mutex_unlock(lk); } 31 | 32 | // Conditional Variable 33 | typedef pthread_cond_t cond_t; 34 | #define COND_INIT() PTHREAD_COND_INITIALIZER 35 | #define cond_wait pthread_cond_wait 36 | #define cond_broadcast pthread_cond_broadcast 37 | #define cond_signal pthread_cond_signal 38 | 39 | // Semaphore 40 | #define P sem_wait 41 | #define V sem_post 42 | #define SEM_INIT(sem, val) sem_init(sem, 0, val) 43 | -------------------------------------------------------------------------------- /lesson_4/thread.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define NTHREAD 64 10 | 11 | // 线程状态 12 | enum { T_FREE = 0, T_LIVE, T_DEAD, }; 13 | 14 | // 表示线程的结构体 15 | struct thread { 16 | int id, status; // 线程表示服和线程状态 17 | pthread_t thread; // 表示线程 18 | void (*entry)(int); // 线程入口点的函数指针 19 | }; 20 | 21 | // 线程池和指向线程池当前位置的指针 22 | struct thread tpool[NTHREAD], *tptr = tpool; 23 | 24 | // 包装实际线程入口函数的函数 25 | void *wrapper(void *arg) { 26 | struct thread *thread = (struct thread *)arg; 27 | thread->entry(thread->id); 28 | return NULL; 29 | } 30 | 31 | // 创建新线程的函数 32 | void create(void *fn) { 33 | assert(tptr - tpool < NTHREAD); // 确保线程池没有满 34 | *tptr = (struct thread) { 35 | .id = tptr - tpool + 1, // 为线程分配唯一标识符 36 | .status = T_LIVE, // 设置线程状态为T_LIVE 37 | .entry = fn, // 设置线程入口函数 38 | }; 39 | pthread_create(&(tptr->thread), NULL, wrapper, tptr); // 创建新线程 40 | ++tptr; // 将指针移动到线程池中的下一个位置 41 | } 42 | 43 | // 等待所有活动线程的函数 44 | void join() { 45 | for (int i = 0; i < NTHREAD; i++) { 46 | struct thread *t = &tpool[i]; 47 | if (t->status == T_LIVE) { 48 | pthread_join(t->thread, NULL); // 等待线程结束 49 | t->status = T_DEAD; // 将线程状态更新为T_DEAD 50 | } 51 | } 52 | } 53 | 54 | // 析构函数,在程序退出时清理资源 55 | __attribute__((destructor)) void cleanup() { 56 | join(); // 在程序退出时等待所有活动线程结束 57 | } 58 | -------------------------------------------------------------------------------- /lesson_5/ilp-demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define LOOP 1000000000ul 5 | 6 | __attribute__((noinline)) void loop() { 7 | for (long i = 0; i < LOOP; i++) { 8 | asm volatile( 9 | "mov $1, %%rax;" 10 | "mov $1, %%rdi;" 11 | "mov $1, %%rsi;" 12 | "mov $1, %%rdx;" 13 | "mov $1, %%rcx;" 14 | "mov $1, %%r10;" 15 | "mov $1, %%r8;" 16 | "mov $1, %%r9;" 17 | :::"rax", "rdi", "rsi", "rdx", "rcx", "r10", "r8", "r9"); 18 | } 19 | } 20 | 21 | int main() { 22 | clock_t st = clock(); 23 | loop(); 24 | clock_t ed = clock(); 25 | double inst = LOOP * (8 + 2) / 1000000000; 26 | double ips = inst / ((ed - st) * 1.0 / CLOCKS_PER_SEC); 27 | printf("%.2lfG instructions/s\n", ips); 28 | } 29 | -------------------------------------------------------------------------------- /lesson_5/rdrand.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | uint64_t volatile val = 114514; 6 | asm volatile ("rdrand %0": "=r"(val)); 7 | printf("rdrand returns %016lx\n", val); 8 | } 9 | -------------------------------------------------------------------------------- /lesson_5/thread-os.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define MAX_CPU 8 6 | 7 | // union允许在统一内存空间中存储不同类型的数据,用于节省内存空间,因为它的大小取决于最大的成员的大小 8 | typedef union task { 9 | struct { 10 | const char *name; // 任务名称 11 | union task *next; // 下一个任务的指针 12 | void (*entry)(void *); // 任务的入口函数孩子会怎 13 | Context *context; // 上下文结构体指针,保存任务的运行状态 14 | }; 15 | uint8_t stack[8192]; // 任务的堆栈空间 16 | } Task; 17 | 18 | // 保存每一个CPU对应的当前任务 19 | Task *currents[MAX_CPU]; 20 | 21 | // 定义宏, 获取当前CPU对应的档案任务 22 | #define current currents[cpu_current()] 23 | 24 | // user-defined tasks 25 | 26 | int locked = 0; 27 | void lock() { while (atomic_xchg(&locked, 1)); } // 锁 28 | void unlock() { atomic_xchg(&locked, 0); } // 解锁 29 | 30 | void func(void *arg) { 31 | while (1) { 32 | lock(); 33 | printf("Thread-%s on CPU #%d\n", arg, cpu_current()); // 输出当前线程信息 34 | unlock(); 35 | for (int volatile i = 0; i < 100000; i++) ; // 空循环,模拟任务执行 36 | } 37 | } 38 | 39 | // 初始化任务 40 | Task tasks[] = { 41 | { .name = "A", .entry = func }, 42 | { .name = "B", .entry = func }, 43 | { .name = "C", .entry = func }, 44 | { .name = "D", .entry = func }, 45 | { .name = "E", .entry = func }, 46 | }; 47 | 48 | // ------------------ 49 | 50 | // 中断处理函数 51 | Context *on_interrupt(Event ev, Context *ctx) { 52 | extern Task tasks[]; // 声明外部的任务数组 53 | if (!current) current = &tasks[0]; // 如果当前任务为空,则设置为任务数组的第一个任务 54 | else current->context = ctx; // 否则更新当前任务的上下文指针 55 | do { 56 | current = current->next; // 切换到下一个任务 57 | } while ((current - tasks) % cpu_count() != cpu_current()); // 循环直到找到当前CPU对应的任务 58 | return current->context; 59 | } 60 | 61 | void mp_entry() { 62 | iset(true); // 设置中断允许 63 | yield(); // 切换到下一个任务 64 | } 65 | 66 | int main() { 67 | cte_init(on_interrupt); // 设置中断处理函数 68 | 69 | for (int i = 0; i < LENGTH(tasks); i++) { 70 | Task *task = &tasks[i]; 71 | Area stack = (Area) { &task->context + 1, task + 1 }; 72 | task->context = kcontext(stack, task->entry, (void *)task->name); 73 | task->next = &tasks[(i + 1) % LENGTH(tasks)]; 74 | } 75 | mpe_init(mp_entry); 76 | } 77 | -------------------------------------------------------------------------------- /lesson_6/clone-example.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */ 10 | // 宏,简化错误处理 11 | #define ERREXIT(msg) { perror(msg); exit(EXIT_FAILURE); } 12 | // 安全分配内存函数,分配失败报告错误 13 | #define CHECKALLOC(ptr, msg) ({ void *p = ptr; if (p == NULL) {ERREXIT(msg);} p;}) 14 | 15 | /* 16 | * 子进程函数 17 | * params: 接受一个void *类型参数,但是没有被使用过,后面的声明是用于告诉编译器这个参数是未被使用的 18 | */ 19 | static int childFunc(void *arg __attribute__((unused))) { 20 | puts("child: start"); 21 | sleep(2); 22 | puts("child: terminate"); 23 | return 0; /* Child terminates now */ 24 | 25 | } 26 | 27 | int main(int argc, char *argv[]) { 28 | /* Start of stack buffer */ 29 | char **stacks; 30 | /* Child process's pids */ 31 | pid_t *pids; 32 | size_t nproc, i; 33 | 34 | // 接受两个参数 35 | if (argc != 2) { 36 | puts("Wrong way to execute the program:\n" 37 | "\t\t./waitpid nProcesses\n" 38 | "example:\t./waitpid 2"); 39 | 40 | return EXIT_FAILURE; 41 | 42 | } 43 | // 初始化nproc,表示要创建的子进程数 44 | nproc = atol(argv[1]); /* Process count */ 45 | 46 | // 分配内存空间 47 | stacks = CHECKALLOC(malloc(nproc * sizeof(void *)), "malloc"); 48 | pids = CHECKALLOC(malloc(nproc * sizeof(pid_t)), "malloc"); 49 | 50 | for (i = 0; i < nproc; i++) { 51 | char *stackTop; /* End of stack buffer */ 52 | stacks[i] = CHECKALLOC(malloc(STACK_SIZE), "stack malloc"); 53 | // 得到栈顶位置 54 | stackTop = stacks[i] + STACK_SIZE; 55 | 56 | /* 57 | * 创建子进程 58 | * 第一个标志表示在子进程清除线程组ID(TID),目的是为了避免子进程与父进程或其他子进程的线程组ID冲突 59 | * 第二个表示告诉在子进程中设置线程ID,目的是为了允许父进程在子进程中追踪线程 60 | * 告诉 clone 系统调用在子进程中重新安装信号处理程序,目的是为了允许子进程捕获和处理信号,而不是传递给父进程。 61 | */ 62 | pids[i] = clone(childFunc, stackTop, CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | SIGCHLD, NULL); 63 | if (pids[i] == -1) 64 | ERREXIT("clone"); 65 | printf("clone() returned %ld\n", (long)pids[i]); 66 | 67 | } 68 | 69 | sleep(1); 70 | 71 | // 等待所有子进程 72 | for (i = 0; i < nproc; i++) { 73 | // 第一个参数为子进程id,第二个参数表示不关心子进程的退出状态,第三个参数表示等待任何子进程 74 | if (waitpid(pids[i], NULL, 0) == -1) 75 | ERREXIT("waitpid"); 76 | printf("child %ld has terminated\n", (long)pids[i]); 77 | 78 | } 79 | 80 | // 回收内存空间 81 | for (i = 0; i < nproc; i++) 82 | free(stacks[i]); 83 | free(stacks); 84 | free(pids); 85 | return EXIT_SUCCESS; 86 | 87 | } 88 | -------------------------------------------------------------------------------- /lesson_6/clone.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int thread_func(void *arg) { 10 | printf("Child thread started\n"); 11 | return 0; 12 | } 13 | 14 | int main(int argc, char *argv[]) { 15 | // 为子线程分配堆栈空间 16 | void *stack = malloc(1024*1024); 17 | assert(stack); 18 | 19 | pid_t pid = clone(thread_func, stack + 1024 + 1024, CLONE_VM, NULL); 20 | if (pid < 0) { 21 | perror("clone error"); 22 | free(stack); 23 | exit(EXIT_FAILURE); 24 | } 25 | 26 | printf("Parent thread waiting for child...\n"); 27 | waitpid(pid, NULL, 0); 28 | printf("Child thread finished\n"); 29 | free(stack); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /lesson_6/dsu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void foo() { printf("In old function %s\n", __func__); } 9 | void foo_new() { printf("In new function %s\n", __func__); } 10 | 11 | // 48 b8 ff ff ff ff ff ff ff ff movabs $0xffffffffffffffff,%rax 12 | // ff e0 jmpq *%rax 13 | void DSU(void *old, void *new) { 14 | #define ROUNDDOWN(ptr) ((void *)(((uintptr_t)ptr) & ~0xfff)) 15 | size_t pg_size = sysconf(_SC_PAGESIZE); 16 | char *pg_boundary = ROUNDDOWN(old); 17 | int flags = PROT_WRITE | PROT_READ | PROT_EXEC; 18 | 19 | printf("Dynamically updating... "); fflush(stdout); 20 | 21 | mprotect(pg_boundary, 2 * pg_size, flags); 22 | memcpy(old + 0, "\x48\xb8", 2); 23 | memcpy(old + 2, &new, 8); 24 | memcpy(old + 10, "\xff\xe0", 2); 25 | mprotect(pg_boundary, 2 * pg_size, flags & ~PROT_WRITE); 26 | 27 | printf("Done\n"); fflush(stdout); 28 | } 29 | 30 | int main() { 31 | foo(); 32 | DSU(foo, foo_new); 33 | foo(); 34 | } 35 | -------------------------------------------------------------------------------- /lesson_6/execve-demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | char * const argv[] = { 6 | "/bin/bash", "-c", "env", NULL, 7 | }; 8 | char * const envp[] = { 9 | "HELLO=WORLD", NULL, 10 | }; 11 | execve(argv[0], argv, envp); 12 | printf("Hello, World!\n"); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /lesson_6/execve-fork.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() { 7 | pid_t pid = fork(); 8 | 9 | if (pid == 0) { 10 | // 子进程 11 | char * const argv[] = {"/bin/echo", "Hello, World!", NULL}; 12 | char * const envp[] = {NULL}; 13 | execve("/bin/echo", argv, envp); 14 | } else if (pid > 0) { 15 | // 父进程 16 | wait(NULL); // 等待子进程结束 17 | printf("Child process finished.\n"); 18 | } else { 19 | // fork失败 20 | perror("fork"); 21 | return 1; 22 | } 23 | return 0; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lesson_6/exit-demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void func() { 9 | printf("Goodbye, Cruel OS World!\n"); 10 | } 11 | 12 | int main(int argc, char *argv[]) { 13 | atexit(func); 14 | 15 | if (argc < 2) return EXIT_FAILURE; 16 | 17 | if (strcmp(argv[1], "exit") == 0) 18 | exit(0); 19 | if (strcmp(argv[1], "_exit") == 0) 20 | _exit(0); 21 | if (strcmp(argv[1], "__exit") == 0) 22 | syscall(SYS_exit, 0); 23 | } 24 | -------------------------------------------------------------------------------- /lesson_6/fork-and.c: -------------------------------------------------------------------------------- 1 | nclude 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | fork() && 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /lesson_6/fork-demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | pid_t pid1 = fork(); 6 | pid_t pid2 = fork(); 7 | pid_t pid3 = fork(); 8 | pid_t pid = getpid(); 9 | printf("The PID of the current process is %d\n Hello World from (%d, %d, %d)\n", pid, pid1, pid2, pid3); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /lesson_6/fork-printf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | int n = 2; 7 | for (int i = 0; i < n; i++) { 8 | fork(); 9 | printf("Hello\n"); 10 | fflush(stdout); 11 | } 12 | for (int i = 0; i < n; i++) { 13 | wait(NULL); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lesson_6/linux-minimal.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeZephyr/NJU_OS_LAB/0ff86f953f8e2bde886512a20649bdb7a2f4bc7f/lesson_6/linux-minimal.zip -------------------------------------------------------------------------------- /lesson_6/linux-minimal/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: initramfs run clean 2 | 3 | $(shell mkdir -p build) 4 | 5 | initramfs: 6 | @cd initramfs && find . -print0 | cpio --null -ov --format=newc | gzip -9 \ 7 | > ../build/initramfs.cpio.gz 8 | 9 | run: 10 | @qemu-system-x86_64 \ 11 | -nographic \ 12 | -serial mon:stdio \ 13 | -m 128 \ 14 | -kernel vmlinuz \ 15 | -initrd build/initramfs.cpio.gz \ 16 | -append "console=ttyS0 quiet acpi=off" 17 | 18 | clean: 19 | @rm -rf build 20 | -------------------------------------------------------------------------------- /lesson_6/linux-minimal/build/initramfs.cpio.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeZephyr/NJU_OS_LAB/0ff86f953f8e2bde886512a20649bdb7a2f4bc7f/lesson_6/linux-minimal/build/initramfs.cpio.gz -------------------------------------------------------------------------------- /lesson_6/linux-minimal/initramfs/bin/busybox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeZephyr/NJU_OS_LAB/0ff86f953f8e2bde886512a20649bdb7a2f4bc7f/lesson_6/linux-minimal/initramfs/bin/busybox -------------------------------------------------------------------------------- /lesson_6/linux-minimal/initramfs/init: -------------------------------------------------------------------------------- 1 | #!/bin/busybox sh 2 | 3 | c1="arch ash base64 cat chattr chgrp chmod chown conspy cp cpio cttyhack date dd df dmesg dnsdomainname dumpkmap echo ed egrep false fatattr fdflush fgrep fsync getopt grep gunzip gzip hostname hush ionice iostat ipcalc kbd_mode kill link linux32 linux64 ln login ls lsattr lzop makemime mkdir mknod mktemp more mount mountpoint mpstat mt mv netstat nice nuke pidof ping ping6 pipe_progress printenv ps pwd reformime resume rev rm rmdir rpm run-parts scriptreplay sed setarch setpriv setserial sh sleep stat stty su sync tar touch true umount uname usleep vi watch zcat" 4 | c2="[ [[ awk basename bc beep blkdiscard bunzip2 bzcat bzip2 cal chpst chrt chvt cksum clear cmp comm crontab cryptpw cut dc deallocvt diff dirname dos2unix dpkg dpkg-deb du dumpleases eject env envdir envuidgid expand expr factor fallocate fgconsole find flock fold free ftpget ftpput fuser groups hd head hexdump hexedit hostid id install ipcrm ipcs killall last less logger logname lpq lpr lsof lspci lsscsi lsusb lzcat lzma man md5sum mesg microcom mkfifo mkpasswd nc nl nmeter nohup nproc nsenter nslookup od openvt passwd paste patch pgrep pkill pmap printf pscan" 5 | c3="pstree pwdx readlink realpath renice reset resize rpm2cpio runsv runsvdir rx script seq setfattr setkeycodes setsid setuidgid sha1sum sha256sum sha3sum sha512sum showkey shred shuf smemcap softlimit sort split ssl_client strings sum sv svc svok tac tail taskset tcpsvd tee telnet test tftp time timeout top tr traceroute traceroute6 truncate ts tty ttysize udhcpc6 udpsvd unexpand uniq unix2dos unlink unlzma unshare unxz unzip uptime users uudecode uuencode vlock volname w wall wc wget which who whoami whois xargs xxd xz xzcat yes" 6 | for cmd in $c1 $c2 $c3; do 7 | /bin/busybox ln -s /bin/busybox /bin/$cmd 8 | done 9 | mkdir -p /proc && mount -t proc none /proc 10 | mkdir -p /sys && mount -t sysfs none /sys 11 | export PS1='(linux) ' 12 | 13 | # Rock'n Roll! 14 | /bin/busybox sh 15 | -------------------------------------------------------------------------------- /lesson_6/linux-minimal/vmlinuz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeZephyr/NJU_OS_LAB/0ff86f953f8e2bde886512a20649bdb7a2f4bc7f/lesson_6/linux-minimal/vmlinuz -------------------------------------------------------------------------------- /lesson_6/mmap-alloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define GiB * (1024LL * 1024 * 1024) 8 | 9 | int main() { 10 | volatile uint8_t *p = mmap(NULL, 3 GiB, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 11 | printf("mmap: %lx\n", (uintptr_t)p); 12 | if ((intptr_t)p == -1) { 13 | perror("cannot map"); 14 | exit(1); 15 | } 16 | *(int *)(p + 1 GiB) = 114; 17 | *(int *)(p + 2 GiB) = 514; 18 | printf("Read get: %d\n", *(int *)(p + 1 GiB)); 19 | printf("Read get: %d\n", *(int *)(p + 2 GiB)); 20 | } 21 | -------------------------------------------------------------------------------- /lesson_6/mmap-disk.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import mmap, hexdump 4 | 5 | with open('/dev/sda', 'rb') as fp: 6 | mm = mmap.mmap(fp.fileno(), prot=mmap.PROT_READ, length=128 << 30) 7 | hexdump.hexdump(mm[:512]) 8 | -------------------------------------------------------------------------------- /lesson_6/vdso.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | double gettime() { 7 | struct timeval t; 8 | gettimeofday(&t, NULL); // trapless system call 9 | return t.tv_sec + t.tv_usec / 1000000.0; 10 | } 11 | 12 | int main() { 13 | printf("Time stamp: %ld\n", time(NULL)); // trapless system call 14 | double st = gettime(); 15 | sleep(1); 16 | double ed = gettime(); 17 | printf("Time: %.6lfs\n", ed - st); 18 | } 19 | -------------------------------------------------------------------------------- /lesson_6/vertify_vfork.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() { 9 | // 在父进程中分配内存并初始化 10 | char *buffer = malloc(1024); 11 | assert(buffer != NULL); 12 | memset(buffer, 'A', 1024); 13 | 14 | // 使用vfork创建子进程 15 | pid_t pid = vfork(); 16 | if (pid < 0) { 17 | perror("vfork error"); 18 | exit(EXIT_FAILURE); 19 | } else if (pid == 0) { 20 | // 子进程 21 | printf("Child process: PID = %d\n", getpid()); 22 | 23 | // 修改内容 24 | memset(buffer, 'B', 1024); 25 | // 子进程 26 | char * const argv[] = {"/bin/echo", "Hello, Linux!", NULL}; 27 | char * const envp[] = {NULL}; 28 | // 执行exec函数 29 | execve(argv[0], argv, envp); 30 | } else { 31 | // 父进程 32 | printf("Parent process: PID = %d, child's PID = %d\n", getpid(), pid); 33 | // 验证内存内容是否被子进程修改 34 | for (int i = 0; i < 1024; i++) { 35 | if (buffer[i] != 'B') { 36 | printf("Memory corruption detected at index %d\n", i); 37 | exit(EXIT_FAILURE); 38 | } 39 | } 40 | printf("Memory is consistent\n"); 41 | } 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /lesson_7/env.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | extern char **environ; 5 | for (char **env = environ; *env; env++) { 6 | printf("%s\n", *env); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lesson_7/memset-race.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | char buf[1 << 30]; 4 | 5 | void foo(int id) { 6 | memset(buf, '0' + id, sizeof(buf) - 1); 7 | } 8 | 9 | int main() { 10 | for (int i = 0; i < 4; i++) 11 | create(foo); 12 | join(); 13 | puts(buf); 14 | } 15 | -------------------------------------------------------------------------------- /lesson_7/sh-xv6.c: -------------------------------------------------------------------------------- 1 | // Linux port of xv6-riscv shell (no libc) 2 | // Compile with "-ffreestanding"! 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Parsed command representation 10 | enum { EXEC = 1, REDIR, PIPE, LIST, BACK }; 11 | 12 | #define MAXARGS 10 13 | #define NULL ((void *)0) 14 | 15 | struct cmd { 16 | int type; 17 | }; 18 | 19 | struct execcmd { 20 | int type; 21 | char *argv[MAXARGS], *eargv[MAXARGS]; 22 | }; 23 | 24 | struct redircmd { 25 | int type, fd, mode; 26 | char *file, *efile; 27 | struct cmd* cmd; 28 | }; 29 | 30 | struct pipecmd { 31 | int type; 32 | struct cmd *left, *right; 33 | }; 34 | 35 | struct listcmd { 36 | int type; 37 | struct cmd *left, *right; 38 | }; 39 | 40 | struct backcmd { 41 | int type; 42 | struct cmd* cmd; 43 | }; 44 | 45 | struct cmd* parsecmd(char*); 46 | 47 | // Minimum runtime library 48 | long syscall(int num, ...) { 49 | va_list ap; 50 | va_start(ap, num); 51 | register long a0 asm ("rax") = num; 52 | register long a1 asm ("rdi") = va_arg(ap, long); 53 | register long a2 asm ("rsi") = va_arg(ap, long); 54 | register long a3 asm ("rdx") = va_arg(ap, long); 55 | register long a4 asm ("r10") = va_arg(ap, long); 56 | va_end(ap); 57 | asm volatile("syscall" 58 | : "+r"(a0) : "r"(a1), "r"(a2), "r"(a3), "r"(a4) 59 | : "memory", "rcx", "r8", "r9", "r11"); 60 | return a0; 61 | } 62 | 63 | size_t strlen(const char *s) { 64 | size_t len = 0; 65 | for (; *s; s++) len++; 66 | return len; 67 | } 68 | 69 | char *strchr(const char *s, int c) { 70 | for (; *s; s++) { 71 | if (*s == c) return (char *)s; 72 | } 73 | return NULL; 74 | } 75 | 76 | void print(const char *s, ...) { 77 | va_list ap; 78 | va_start(ap, s); 79 | while (s) { 80 | syscall(SYS_write, 2, s, strlen(s)); 81 | s = va_arg(ap, const char *); 82 | } 83 | va_end(ap); 84 | } 85 | 86 | #define assert(cond) \ 87 | do { if (!(cond)) { \ 88 | print("Panicked.\n", NULL); \ 89 | syscall(SYS_exit, 1); } \ 90 | } while (0) 91 | 92 | static char mem[4096], *freem = mem; 93 | 94 | void *zalloc(size_t sz) { 95 | assert(freem + sz < mem + sizeof(mem)); 96 | void *ret = freem; 97 | freem += sz; 98 | return ret; 99 | } 100 | 101 | // Execute cmd. Never returns. 102 | void runcmd(struct cmd* cmd) { 103 | int p[2]; 104 | struct backcmd* bcmd; 105 | struct execcmd* ecmd; 106 | struct listcmd* lcmd; 107 | struct pipecmd* pcmd; 108 | struct redircmd* rcmd; 109 | 110 | if (cmd == 0) syscall(SYS_exit, 1); 111 | 112 | switch (cmd->type) { 113 | case EXEC: 114 | ecmd = (struct execcmd*)cmd; 115 | if (ecmd->argv[0] == 0) syscall(SYS_exit, 1); 116 | syscall(SYS_execve, ecmd->argv[0], ecmd->argv, NULL); 117 | print("fail to exec ", ecmd->argv[0], "\n", NULL); 118 | break; 119 | 120 | case REDIR: 121 | rcmd = (struct redircmd*)cmd; 122 | syscall(SYS_close, rcmd->fd); 123 | if (syscall(SYS_open, rcmd->file, rcmd->mode, 0644) < 0) { 124 | print("fail to open ", rcmd->file, "\n", NULL); 125 | syscall(SYS_exit, 1); 126 | } 127 | runcmd(rcmd->cmd); 128 | break; 129 | 130 | case LIST: 131 | lcmd = (struct listcmd*)cmd; 132 | if (syscall(SYS_fork) == 0) runcmd(lcmd->left); 133 | syscall(SYS_wait4, -1, 0, 0, 0); 134 | runcmd(lcmd->right); 135 | break; 136 | 137 | case PIPE: 138 | pcmd = (struct pipecmd*)cmd; 139 | assert(syscall(SYS_pipe, p) >= 0); 140 | if (syscall(SYS_fork) == 0) { 141 | syscall(SYS_close, 1); 142 | syscall(SYS_dup, p[1]); 143 | syscall(SYS_close, p[0]); 144 | syscall(SYS_close, p[1]); 145 | runcmd(pcmd->left); 146 | } 147 | if (syscall(SYS_fork) == 0) { 148 | syscall(SYS_close, 0); 149 | syscall(SYS_dup, p[0]); 150 | syscall(SYS_close, p[0]); 151 | syscall(SYS_close, p[1]); 152 | runcmd(pcmd->right); 153 | } 154 | syscall(SYS_close, p[0]); 155 | syscall(SYS_close, p[1]); 156 | syscall(SYS_wait4, -1, 0, 0, 0); 157 | syscall(SYS_wait4, -1, 0, 0, 0); 158 | break; 159 | 160 | case BACK: 161 | bcmd = (struct backcmd*)cmd; 162 | if (syscall(SYS_fork) == 0) runcmd(bcmd->cmd); 163 | break; 164 | 165 | default: 166 | assert(0); 167 | } 168 | syscall(SYS_exit, 0); 169 | } 170 | 171 | int getcmd(char* buf, int nbuf) { 172 | print("> ", NULL); 173 | for (int i = 0; i < nbuf; i++) buf[i] = '\0'; 174 | 175 | while (nbuf-- > 1) { 176 | int nread = syscall(SYS_read, 0, buf, 1); 177 | if (nread <= 0) return -1; 178 | if (*(buf++) == '\n') break; 179 | } 180 | return 0; 181 | } 182 | 183 | void _start() { 184 | static char buf[100]; 185 | 186 | // Read and run input commands. 187 | while (getcmd(buf, sizeof(buf)) >= 0) { 188 | if (buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' ') { 189 | // Chdir must be called by the parent, not the child. 190 | buf[strlen(buf) - 1] = 0; // chop \n 191 | if (syscall(SYS_chdir, buf + 3) < 0) print("cannot cd ", buf + 3, "\n", NULL); 192 | continue; 193 | } 194 | if (syscall(SYS_fork) == 0) runcmd(parsecmd(buf)); 195 | syscall(SYS_wait4, -1, 0, 0, 0); 196 | } 197 | syscall(SYS_exit, 0); 198 | } 199 | 200 | // Constructors 201 | 202 | struct cmd* execcmd(void) { 203 | struct execcmd* cmd; 204 | 205 | cmd = zalloc(sizeof(*cmd)); 206 | cmd->type = EXEC; 207 | return (struct cmd*)cmd; 208 | } 209 | 210 | struct cmd* redircmd(struct cmd* subcmd, char* file, char* efile, int mode, 211 | int fd) { 212 | struct redircmd* cmd; 213 | 214 | cmd = zalloc(sizeof(*cmd)); 215 | cmd->type = REDIR; 216 | cmd->cmd = subcmd; 217 | cmd->file = file; 218 | cmd->efile = efile; 219 | cmd->mode = mode; 220 | cmd->fd = fd; 221 | return (struct cmd*)cmd; 222 | } 223 | 224 | struct cmd* pipecmd(struct cmd* left, struct cmd* right) { 225 | struct pipecmd* cmd; 226 | 227 | cmd = zalloc(sizeof(*cmd)); 228 | cmd->type = PIPE; 229 | cmd->left = left; 230 | cmd->right = right; 231 | return (struct cmd*)cmd; 232 | } 233 | 234 | struct cmd* listcmd(struct cmd* left, struct cmd* right) { 235 | struct listcmd* cmd; 236 | 237 | cmd = zalloc(sizeof(*cmd)); 238 | cmd->type = LIST; 239 | cmd->left = left; 240 | cmd->right = right; 241 | return (struct cmd*)cmd; 242 | } 243 | 244 | struct cmd* backcmd(struct cmd* subcmd) { 245 | struct backcmd* cmd; 246 | 247 | cmd = zalloc(sizeof(*cmd)); 248 | cmd->type = BACK; 249 | cmd->cmd = subcmd; 250 | return (struct cmd*)cmd; 251 | } 252 | 253 | // Parsing 254 | 255 | char whitespace[] = " \t\r\n\v"; 256 | char symbols[] = "<|>&;()"; 257 | 258 | int gettoken(char** ps, char* es, char** q, char** eq) { 259 | char* s; 260 | int ret; 261 | 262 | s = *ps; 263 | while (s < es && strchr(whitespace, *s)) s++; 264 | if (q) *q = s; 265 | ret = *s; 266 | switch (*s) { 267 | case 0: 268 | break; 269 | case '|': case '(': case ')': case ';': case '&': case '<': 270 | s++; 271 | break; 272 | case '>': 273 | s++; 274 | if (*s == '>') { 275 | ret = '+'; s++; 276 | } 277 | break; 278 | default: 279 | ret = 'a'; 280 | while (s < es && !strchr(whitespace, *s) && !strchr(symbols, *s)) s++; 281 | break; 282 | } 283 | if (eq) *eq = s; 284 | 285 | while (s < es && strchr(whitespace, *s)) s++; 286 | *ps = s; 287 | return ret; 288 | } 289 | 290 | int peek(char** ps, char* es, char* toks) { 291 | char* s; 292 | 293 | s = *ps; 294 | while (s < es && strchr(whitespace, *s)) s++; 295 | *ps = s; 296 | return *s && strchr(toks, *s); 297 | } 298 | 299 | struct cmd* parseline(char**, char*); 300 | struct cmd* parsepipe(char**, char*); 301 | struct cmd* parseexec(char**, char*); 302 | struct cmd* nulterminate(struct cmd*); 303 | 304 | struct cmd* parsecmd(char* s) { 305 | char* es; 306 | struct cmd* cmd; 307 | 308 | es = s + strlen(s); 309 | cmd = parseline(&s, es); 310 | peek(&s, es, ""); 311 | assert(s == es); 312 | nulterminate(cmd); 313 | return cmd; 314 | } 315 | 316 | struct cmd* parseline(char** ps, char* es) { 317 | struct cmd* cmd; 318 | 319 | cmd = parsepipe(ps, es); 320 | while (peek(ps, es, "&")) { 321 | gettoken(ps, es, 0, 0); 322 | cmd = backcmd(cmd); 323 | } 324 | if (peek(ps, es, ";")) { 325 | gettoken(ps, es, 0, 0); 326 | cmd = listcmd(cmd, parseline(ps, es)); 327 | } 328 | return cmd; 329 | } 330 | 331 | struct cmd* parsepipe(char** ps, char* es) { 332 | struct cmd* cmd; 333 | 334 | cmd = parseexec(ps, es); 335 | if (peek(ps, es, "|")) { 336 | gettoken(ps, es, 0, 0); 337 | cmd = pipecmd(cmd, parsepipe(ps, es)); 338 | } 339 | return cmd; 340 | } 341 | 342 | struct cmd* parseredirs(struct cmd* cmd, char** ps, char* es) { 343 | int tok; 344 | char *q, *eq; 345 | 346 | while (peek(ps, es, "<>")) { 347 | tok = gettoken(ps, es, 0, 0); 348 | assert(gettoken(ps, es, &q, &eq) == 'a'); 349 | switch (tok) { 350 | case '<': 351 | cmd = redircmd(cmd, q, eq, O_RDONLY, 0); 352 | break; 353 | case '>': 354 | cmd = redircmd(cmd, q, eq, O_WRONLY | O_CREAT | O_TRUNC, 1); 355 | break; 356 | case '+': // >> 357 | cmd = redircmd(cmd, q, eq, O_WRONLY | O_CREAT, 1); 358 | break; 359 | } 360 | } 361 | return cmd; 362 | } 363 | 364 | struct cmd* parseblock(char** ps, char* es) { 365 | struct cmd* cmd; 366 | 367 | assert(peek(ps, es, "(")); 368 | gettoken(ps, es, 0, 0); 369 | cmd = parseline(ps, es); 370 | assert(peek(ps, es, ")")); 371 | gettoken(ps, es, 0, 0); 372 | cmd = parseredirs(cmd, ps, es); 373 | return cmd; 374 | } 375 | 376 | struct cmd* parseexec(char** ps, char* es) { 377 | char *q, *eq; 378 | int tok, argc; 379 | struct execcmd* cmd; 380 | struct cmd* ret; 381 | 382 | if (peek(ps, es, "(")) return parseblock(ps, es); 383 | 384 | ret = execcmd(); 385 | cmd = (struct execcmd*)ret; 386 | 387 | argc = 0; 388 | ret = parseredirs(ret, ps, es); 389 | while (!peek(ps, es, "|)&;")) { 390 | if ((tok = gettoken(ps, es, &q, &eq)) == 0) break; 391 | assert(tok == 'a'); 392 | cmd->argv[argc] = q; 393 | cmd->eargv[argc] = eq; 394 | assert(++argc < MAXARGS); 395 | ret = parseredirs(ret, ps, es); 396 | } 397 | cmd->argv[argc] = 0; 398 | cmd->eargv[argc] = 0; 399 | return ret; 400 | } 401 | 402 | // NUL-terminate all the counted strings. 403 | struct cmd* nulterminate(struct cmd* cmd) { 404 | int i; 405 | struct backcmd* bcmd; 406 | struct execcmd* ecmd; 407 | struct listcmd* lcmd; 408 | struct pipecmd* pcmd; 409 | struct redircmd* rcmd; 410 | 411 | if (cmd == 0) return 0; 412 | 413 | switch (cmd->type) { 414 | case EXEC: 415 | ecmd = (struct execcmd*)cmd; 416 | for (i = 0; ecmd->argv[i]; i++) *ecmd->eargv[i] = 0; 417 | break; 418 | 419 | case REDIR: 420 | rcmd = (struct redircmd*)cmd; 421 | nulterminate(rcmd->cmd); 422 | *rcmd->efile = 0; 423 | break; 424 | 425 | case PIPE: 426 | pcmd = (struct pipecmd*)cmd; 427 | nulterminate(pcmd->left); 428 | nulterminate(pcmd->right); 429 | break; 430 | 431 | case LIST: 432 | lcmd = (struct listcmd*)cmd; 433 | nulterminate(lcmd->left); 434 | nulterminate(lcmd->right); 435 | break; 436 | 437 | case BACK: 438 | bcmd = (struct backcmd*)cmd; 439 | nulterminate(bcmd->cmd); 440 | break; 441 | } 442 | return cmd; 443 | } 444 | -------------------------------------------------------------------------------- /lesson_8/cow-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define NPROC 1000 9 | #define MB 128 10 | #define SIZE (MB * (1 << 20)) 11 | 12 | #define xstr(s) str(s) 13 | #define str(s) #s 14 | 15 | int main() { 16 | char *data = malloc(SIZE); // 128MB shared memory 17 | memset(data, '_', SIZE); 18 | 19 | for (int i = 0; i < NPROC - 1; i++) { 20 | if (fork() == 0) break; 21 | } 22 | 23 | // NPROC processes go here 24 | 25 | asm volatile(".fill 1048576 * " xstr(MB) ", 1, 0x90"); // 128MB shared code 26 | 27 | unsigned int idx = 0; 28 | int fd = open("/dev/urandom", O_RDONLY); assert(fd > 0); 29 | read(fd, &idx, sizeof(idx)); 30 | close(fd); 31 | idx %= 1048576 * MB; 32 | 33 | data[idx] = '.'; 34 | printf("pid = %d, write data[%u]\n", getpid(), idx); 35 | 36 | while (1) { 37 | sleep(1); // not terminate 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lesson_9/popcount.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __attribute__((noinline)) 4 | int popcount(int x) { 5 | int s = 0; 6 | int b0 = (x >> 0) & 1; 7 | s += b0; 8 | int b1 = (x >> 1) & 1; 9 | s += b1; 10 | int b2 = (x >> 2) & 1; 11 | s += b2; 12 | int b3 = (x >> 3) & 1; 13 | s += b3; 14 | return s; 15 | } 16 | 17 | int main() { 18 | printf("%d\n", popcount(0b1101)); 19 | } 20 | -------------------------------------------------------------------------------- /lesson_9/segfault.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void bar() { 4 | *(int *)NULL = 1; 5 | } 6 | 7 | void foo() { 8 | bar(); 9 | } 10 | 11 | int main() { 12 | foo(); 13 | } 14 | --------------------------------------------------------------------------------